From 5099b377ce8fc7b860b1007db8215c06b65fe108 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 28 Mar 2023 10:37:05 -0700 Subject: [PATCH 01/24] updates --- .../slashing/v1beta1/slashing.pulsar.go | 18 ++++++----- proto/cosmos/slashing/v1beta1/slashing.proto | 32 ++++++++++++------- x/slashing/types/slashing.pb.go | 18 ++++++----- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/api/cosmos/slashing/v1beta1/slashing.pulsar.go b/api/cosmos/slashing/v1beta1/slashing.pulsar.go index 0b70d3683cd5..09293e7b43d3 100644 --- a/api/cosmos/slashing/v1beta1/slashing.pulsar.go +++ b/api/cosmos/slashing/v1beta1/slashing.pulsar.go @@ -1416,19 +1416,21 @@ 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 bit + // array. 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 bit array. MissedBlocksCounter int64 `protobuf:"varint,6,opt,name=missed_blocks_counter,json=missedBlocksCounter,proto3" json:"missed_blocks_counter,omitempty"` } diff --git a/proto/cosmos/slashing/v1beta1/slashing.proto b/proto/cosmos/slashing/v1beta1/slashing.proto index a4d2129b1438..15dcb8ba56da 100644 --- a/proto/cosmos/slashing/v1beta1/slashing.proto +++ b/proto/cosmos/slashing/v1beta1/slashing.proto @@ -16,20 +16,25 @@ 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 bit + // array. 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 bit array. int64 missed_blocks_counter = 6; } @@ -44,8 +49,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, diff --git a/x/slashing/types/slashing.pb.go b/x/slashing/types/slashing.pb.go index ce901400b720..3cff50d690b5 100644 --- a/x/slashing/types/slashing.pb.go +++ b/x/slashing/types/slashing.pb.go @@ -35,19 +35,21 @@ 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 bit + // array. 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 bit array. MissedBlocksCounter int64 `protobuf:"varint,6,opt,name=missed_blocks_counter,json=missedBlocksCounter,proto3" json:"missed_blocks_counter,omitempty"` } From 6ae94729f015d8d5b346cd7862de1f77319b95a2 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 28 Mar 2023 10:46:38 -0700 Subject: [PATCH 02/24] updates --- x/slashing/types/keys.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x/slashing/types/keys.go b/x/slashing/types/keys.go index 02a86e167e28..3dc1238595b3 100644 --- a/x/slashing/types/keys.go +++ b/x/slashing/types/keys.go @@ -17,6 +17,11 @@ const ( // RouterKey is the message route for slashing RouterKey = ModuleName + + // MissedBlockWindowChunkSize defines the chunk size of a missed block window + // bit array. Chunks are used to reduce the size and write overhead of IAVL + // nodes. + MissedBlockWindowChunkSize int16 = 1024 // 2^10 ) // Keys for slashing store From 41b4fe883fa453466503961d3c12c917534bd0a7 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 28 Mar 2023 12:18:01 -0700 Subject: [PATCH 03/24] updates --- .../slashing/v1beta1/slashing.pulsar.go | 5 +- proto/cosmos/slashing/v1beta1/slashing.proto | 5 +- x/slashing/keeper/signing_info.go | 175 +++++++++++------- x/slashing/types/keys.go | 44 +++-- x/slashing/types/slashing.pb.go | 5 +- 5 files changed, 143 insertions(+), 91 deletions(-) diff --git a/api/cosmos/slashing/v1beta1/slashing.pulsar.go b/api/cosmos/slashing/v1beta1/slashing.pulsar.go index 09293e7b43d3..f5de168f40e0 100644 --- a/api/cosmos/slashing/v1beta1/slashing.pulsar.go +++ b/api/cosmos/slashing/v1beta1/slashing.pulsar.go @@ -1420,8 +1420,7 @@ type ValidatorSigningInfo struct { StartHeight int64 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` // 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 bit - // array. + // 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"` @@ -1430,7 +1429,7 @@ type ValidatorSigningInfo struct { // configured misbehavior. Tombstoned bool `protobuf:"varint,5,opt,name=tombstoned,proto3" json:"tombstoned,omitempty"` // A counter of missed (unsigned) blocks. It is used to avoid unnecessary - // reads in the missed block bit array. + // reads in the missed block bitmap. MissedBlocksCounter int64 `protobuf:"varint,6,opt,name=missed_blocks_counter,json=missedBlocksCounter,proto3" json:"missed_blocks_counter,omitempty"` } diff --git a/proto/cosmos/slashing/v1beta1/slashing.proto b/proto/cosmos/slashing/v1beta1/slashing.proto index 15dcb8ba56da..65bd4800e677 100644 --- a/proto/cosmos/slashing/v1beta1/slashing.proto +++ b/proto/cosmos/slashing/v1beta1/slashing.proto @@ -20,8 +20,7 @@ message ValidatorSigningInfo { int64 start_height = 2; // 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 bit - // array. + // 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 [ @@ -34,7 +33,7 @@ message ValidatorSigningInfo { // configured misbehavior. bool tombstoned = 5; // A counter of missed (unsigned) blocks. It is used to avoid unnecessary - // reads in the missed block bit array. + // reads in the missed block bitmap. int64 missed_blocks_counter = 6; } diff --git a/x/slashing/keeper/signing_info.go b/x/slashing/keeper/signing_info.go index df72366e306f..18fd60cd08e6 100644 --- a/x/slashing/keeper/signing_info.go +++ b/x/slashing/keeper/signing_info.go @@ -3,8 +3,6 @@ package keeper import ( "time" - gogotypes "github.com/cosmos/gogoproto/types" - storetypes "cosmossdk.io/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -13,20 +11,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,52 +55,70 @@ 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 -} +// // 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 +// } + +// // 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) { +// store := ctx.KVStore(k.storeKey) +// bz := k.cdc.MustMarshal(&gogotypes.BoolValue{Value: missed}) +// store.Set(types.ValidatorMissedBlockBitArrayKey(address, index), bz) +// } + +// // clearValidatorMissedBlockBitArray deletes every instance of ValidatorMissedBlockBitArray in the store +// func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) { +// store := ctx.KVStore(k.storeKey) +// iter := storetypes.KVStorePrefixIterator(store, types.ValidatorMissedBlockBitArrayPrefixKey(address)) +// defer iter.Close() +// for ; iter.Valid(); iter.Next() { +// store.Delete(iter.Key()) +// } +// } // 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. @@ -141,20 +158,48 @@ 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) { +// ============================================================================ + +// GetMissedBlockBitmapValue returns a validator's missed block bitmap value at +// the given index, where index is the block height offset in the signing window. +func (k Keeper) GetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, index int64) bool { + // assume index provided is non-zero based + pos := index - 1 + + // get the chunk or "word" in the logical bitmap + chunkIndex := pos / types.MissedBlockBitmapChunkSize + + // get the position in the chunk of the logical bitmap + bitIndex := pos % types.MissedBlockBitmapChunkSize + 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[bitIndex] == 1 } -// clearValidatorMissedBlockBitArray deletes every instance of ValidatorMissedBlockBitArray in the store -func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) { +// SetMissedBlockBitmapValue sets a validator's missed block bitmap value at the +// given index, where index is the block height offset in the signing window. +// If missed=true, the bit is set to 1, otherwise it is set to 0. +func (k Keeper) SetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, index int64, missed bool) { + // assume index provided is non-zero based + pos := index - 1 + + // get the chunk or "word" in the logical bitmap + chunkIndex := pos / types.MissedBlockBitmapChunkSize + + // get the position in the chunk of the logical bitmap + bitIndex := pos % types.MissedBlockBitmapChunkSize + store := ctx.KVStore(k.storeKey) - iter := storetypes.KVStorePrefixIterator(store, types.ValidatorMissedBlockBitArrayPrefixKey(address)) - defer iter.Close() - for ; iter.Valid(); iter.Next() { - store.Delete(iter.Key()) + key := types.ValidatorMissedBlockBitmapKey(addr, chunkIndex) + + chunk := store.Get(key) + if missed { + chunk[bitIndex] = 1 + } else { + chunk[bitIndex] = 0 } + + store.Set(key, chunk) } diff --git a/x/slashing/types/keys.go b/x/slashing/types/keys.go index 3dc1238595b3..e21450980af4 100644 --- a/x/slashing/types/keys.go +++ b/x/slashing/types/keys.go @@ -18,10 +18,18 @@ const ( // RouterKey is the message route for slashing RouterKey = ModuleName - // MissedBlockWindowChunkSize defines the chunk size of a missed block window - // bit array. Chunks are used to reduce the size and write overhead of IAVL - // nodes. - MissedBlockWindowChunkSize int16 = 1024 // 2^10 + // MissedBlockBitmapChunkSize defines the chunk size of a missed block bitmap. + // Chunks are used to reduce the storage and write overhead of IAVL nodes. + // + // 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 ) + 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 int64 = 1024 // 2^10 ) // Keys for slashing store @@ -29,15 +37,15 @@ const ( // // - 0x01: ValidatorSigningInfo // -// - 0x02: bool +// - 0x02: bitmap_chunk (MissedBlockBitmapChunkSize bytes) // // - 0x03: 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) @@ -54,17 +62,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 diff --git a/x/slashing/types/slashing.pb.go b/x/slashing/types/slashing.pb.go index 3cff50d690b5..2673f6a77f58 100644 --- a/x/slashing/types/slashing.pb.go +++ b/x/slashing/types/slashing.pb.go @@ -39,8 +39,7 @@ type ValidatorSigningInfo struct { StartHeight int64 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` // 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 bit - // array. + // 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"` @@ -49,7 +48,7 @@ type ValidatorSigningInfo struct { // configured misbehavior. Tombstoned bool `protobuf:"varint,5,opt,name=tombstoned,proto3" json:"tombstoned,omitempty"` // A counter of missed (unsigned) blocks. It is used to avoid unnecessary - // reads in the missed block bit array. + // reads in the missed block bitmap. MissedBlocksCounter int64 `protobuf:"varint,6,opt,name=missed_blocks_counter,json=missedBlocksCounter,proto3" json:"missed_blocks_counter,omitempty"` } From 5b201e41516ddde1c4391d41635d737b3ad4ce42 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 28 Mar 2023 12:19:09 -0700 Subject: [PATCH 04/24] updates --- x/slashing/types/keys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/slashing/types/keys.go b/x/slashing/types/keys.go index e21450980af4..00a6a5b1dbae 100644 --- a/x/slashing/types/keys.go +++ b/x/slashing/types/keys.go @@ -37,7 +37,7 @@ const ( // // - 0x01: ValidatorSigningInfo // -// - 0x02: bitmap_chunk (MissedBlockBitmapChunkSize bytes) +// - 0x02: bitmap_chunk // // - 0x03: cryptotypes.PubKey From 0537529a172be9401188b278a772c341332d4dee Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 29 Mar 2023 10:41:18 -0700 Subject: [PATCH 05/24] updates --- go.mod | 1 + go.sum | 2 + x/slashing/keeper/signing_info.go | 87 ++++++++++++++++++++----------- x/slashing/types/keys.go | 12 +++-- 4 files changed, 70 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index 70ab9d28e0c1..f3fb735f848f 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 29c5061b81fb..4009aa59eacc 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/x/slashing/keeper/signing_info.go b/x/slashing/keeper/signing_info.go index 18fd60cd08e6..4c0155c021a8 100644 --- a/x/slashing/keeper/signing_info.go +++ b/x/slashing/keeper/signing_info.go @@ -4,6 +4,8 @@ import ( "time" 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" @@ -160,46 +162,73 @@ func (k Keeper) IsTombstoned(ctx sdk.Context, consAddr sdk.ConsAddress) bool { // ============================================================================ -// GetMissedBlockBitmapValue returns a validator's missed block bitmap value at -// the given index, where index is the block height offset in the signing window. -func (k Keeper) GetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, index int64) bool { - // assume index provided is non-zero based - pos := index - 1 - - // get the chunk or "word" in the logical bitmap - chunkIndex := pos / types.MissedBlockBitmapChunkSize - - // get the position in the chunk of the logical bitmap - bitIndex := pos % types.MissedBlockBitmapChunkSize - +// 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) chunk := store.Get(types.ValidatorMissedBlockBitmapKey(addr, chunkIndex)) - - return chunk[bitIndex] == 1 + return chunk } -// SetMissedBlockBitmapValue sets a validator's missed block bitmap value at the -// given index, where index is the block height offset in the signing window. -// If missed=true, the bit is set to 1, otherwise it is set to 0. -func (k Keeper) SetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, index int64, missed bool) { - // assume index provided is non-zero based - pos := index - 1 +// 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) + 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 := pos / types.MissedBlockBitmapChunkSize + chunkIndex := index / types.MissedBlockBitmapChunkSize + + bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) + chunk := k.GetMissedBlockBitmapChunk(ctx, addr, chunkIndex) + if err := bs.UnmarshalBinary(chunk); err != nil { + return false, errors.Wrapf(err, "failed to decode bitmap chunk; index: %d", index) + } - // get the position in the chunk of the logical bitmap - bitIndex := pos % types.MissedBlockBitmapChunkSize + // get the bit position in the chunk of the logical bitmap + bitIndex := index % types.MissedBlockBitmapChunkSize + return bs.Test(uint(bitIndex)), nil +} - store := ctx.KVStore(k.storeKey) - key := types.ValidatorMissedBlockBitmapKey(addr, chunkIndex) +// 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 - chunk := store.Get(key) + bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) + bz := k.GetMissedBlockBitmapChunk(ctx, addr, chunkIndex) + if err := bs.UnmarshalBinary(bz); 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 { - chunk[bitIndex] = 1 + bs.Set(bitIndex) } else { - chunk[bitIndex] = 0 + bs.Clear(bitIndex) } - store.Set(key, chunk) + chunk, err := bs.MarshalBinary() + if err != nil { + return errors.Wrapf(err, "failed to encode bitmap chunk; index: %d", index) + } + + k.SetMissedBlockBitmapChunk(ctx, addr, chunkIndex, chunk) + return nil } diff --git a/x/slashing/types/keys.go b/x/slashing/types/keys.go index 00a6a5b1dbae..b15d96c92f98 100644 --- a/x/slashing/types/keys.go +++ b/x/slashing/types/keys.go @@ -18,8 +18,14 @@ const ( // RouterKey is the message route for slashing RouterKey = ModuleName - // MissedBlockBitmapChunkSize defines the chunk size of a missed block bitmap. - // Chunks are used to reduce the storage and write overhead of IAVL nodes. + // 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 @@ -29,7 +35,7 @@ const ( // // As for the storage overhead, with the same factor f, it is as follows: // (N - 256) + (N / ChunkSize) * (512 * f) - MissedBlockBitmapChunkSize int64 = 1024 // 2^10 + MissedBlockBitmapChunkSize int64 = 1024 // 2^10 bits ) // Keys for slashing store From b4af2a3163d6dd5774bbc70f4dc47dd32c603688 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 29 Mar 2023 11:24:39 -0700 Subject: [PATCH 06/24] updates --- x/slashing/keeper/genesis.go | 8 ++- x/slashing/keeper/infractions.go | 38 +++++++---- x/slashing/keeper/signing_info.go | 105 ++++++++++++++++++------------ x/slashing/types/keys.go | 2 +- 4 files changed, 95 insertions(+), 58 deletions(-) diff --git a/x/slashing/keeper/genesis.go b/x/slashing/keeper/genesis.go index e53ef3730c06..7972a530e548 100644 --- a/x/slashing/keeper/genesis.go +++ b/x/slashing/keeper/genesis.go @@ -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 initialize 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,9 @@ 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) + keeper.SetMissedBlockBitmapValue(ctx, address, missed.Index, missed.Missed) } } diff --git a/x/slashing/keeper/infractions.go b/x/slashing/keeper/infractions.go index 17f56d04c6cc..7cb57f702b07 100644 --- a/x/slashing/keeper/infractions.go +++ b/x/slashing/keeper/infractions.go @@ -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,36 @@ 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. + k.SetMissedBlockBitmapValue(ctx, consAddr, index, true) 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. + k.SetMissedBlockBitmapValue(ctx, consAddr, index, false) 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 +116,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", diff --git a/x/slashing/keeper/signing_info.go b/x/slashing/keeper/signing_info.go index 4c0155c021a8..b446ae8224ca 100644 --- a/x/slashing/keeper/signing_info.go +++ b/x/slashing/keeper/signing_info.go @@ -57,20 +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, @@ -104,24 +90,6 @@ func (k Keeper) IterateValidatorSigningInfos(ctx sdk.Context, // return missedBlocks // } -// // 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) { -// store := ctx.KVStore(k.storeKey) -// bz := k.cdc.MustMarshal(&gogotypes.BoolValue{Value: missed}) -// store.Set(types.ValidatorMissedBlockBitArrayKey(address, index), bz) -// } - -// // clearValidatorMissedBlockBitArray deletes every instance of ValidatorMissedBlockBitArray in the store -// func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) { -// store := ctx.KVStore(k.storeKey) -// iter := storetypes.KVStorePrefixIterator(store, types.ValidatorMissedBlockBitArrayPrefixKey(address)) -// defer iter.Close() -// for ; iter.Valid(); iter.Next() { -// store.Delete(iter.Key()) -// } -// } - // 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) { @@ -160,8 +128,6 @@ func (k Keeper) IsTombstoned(ctx sdk.Context, consAddr sdk.ConsAddress) bool { return signInfo.Tombstoned } -// ============================================================================ - // 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 { @@ -190,8 +156,10 @@ func (k Keeper) GetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) chunk := k.GetMissedBlockBitmapChunk(ctx, addr, chunkIndex) - if err := bs.UnmarshalBinary(chunk); err != nil { - return false, errors.Wrapf(err, "failed to decode bitmap chunk; index: %d", index) + 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 @@ -211,9 +179,11 @@ func (k Keeper) SetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, chunkIndex := index / types.MissedBlockBitmapChunkSize bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) - bz := k.GetMissedBlockBitmapChunk(ctx, addr, chunkIndex) - if err := bs.UnmarshalBinary(bz); err != nil { - return errors.Wrapf(err, "failed to decode bitmap chunk; index: %d", index) + 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 @@ -224,11 +194,64 @@ func (k Keeper) SetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, bs.Clear(bitIndex) } - chunk, err := bs.MarshalBinary() + updatedChunk, err := bs.MarshalBinary() if err != nil { return errors.Wrapf(err, "failed to encode bitmap chunk; index: %d", index) } - k.SetMissedBlockBitmapChunk(ctx, addr, chunkIndex, chunk) + 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++ { + 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 := []types.MissedBlock{} + k.IterateMissedBlockBitmap(ctx, addr, func(index int64, missed bool) (stop bool) { + missedBlocks = append(missedBlocks, types.NewMissedBlock(index, missed)) + return false + }) + + return missedBlocks +} diff --git a/x/slashing/types/keys.go b/x/slashing/types/keys.go index b15d96c92f98..856780f86c48 100644 --- a/x/slashing/types/keys.go +++ b/x/slashing/types/keys.go @@ -35,7 +35,7 @@ const ( // // As for the storage overhead, with the same factor f, it is as follows: // (N - 256) + (N / ChunkSize) * (512 * f) - MissedBlockBitmapChunkSize int64 = 1024 // 2^10 bits + MissedBlockBitmapChunkSize = 1024 // 2^10 bits ) // Keys for slashing store From 162c79ac8e566f9d7c3620620c27f864b528ce27 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 29 Mar 2023 11:27:12 -0700 Subject: [PATCH 07/24] updates --- x/slashing/keeper/signing_info.go | 33 ------------------------------- 1 file changed, 33 deletions(-) diff --git a/x/slashing/keeper/signing_info.go b/x/slashing/keeper/signing_info.go index b446ae8224ca..f054403cdb05 100644 --- a/x/slashing/keeper/signing_info.go +++ b/x/slashing/keeper/signing_info.go @@ -57,39 +57,6 @@ func (k Keeper) IterateValidatorSigningInfos(ctx sdk.Context, } } -// // 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) { From d4c5bbd5cbcb5030f7a6e853479306d5fbdc0050 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 29 Mar 2023 12:23:58 -0700 Subject: [PATCH 08/24] updates --- x/slashing/keeper/signing_info_test.go | 17 +++++++++++------ x/slashing/simulation/decoder.go | 2 +- x/slashing/simulation/decoder_test.go | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/x/slashing/keeper/signing_info_test.go b/x/slashing/keeper/signing_info_test.go index f33fbf34b53d..fba3448f1111 100644 --- a/x/slashing/keeper/signing_info_test.go +++ b/x/slashing/keeper/signing_info_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -53,7 +54,7 @@ func (s *KeeperTestSuite) TestValidatorSigningInfo() { require.Equal(sInfo.JailedUntil, jailTime) } -func (s *KeeperTestSuite) TestValidatorMissedBlockBitArray() { +func (s *KeeperTestSuite) TestValidatorMissedBlockBitmap() { ctx, keeper := s.ctx, s.slashingKeeper require := s.Require() @@ -67,25 +68,29 @@ func (s *KeeperTestSuite) TestValidatorMissedBlockBitArray() { missed bool }{ { - name: "missed block with false", + name: "missed block", index: 50, missed: false, }, { - name: "missed block with true", + name: "signed block", 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) + keeper.SetMissedBlockBitmapValue(ctx, consAddr, tc.index, tc.missed) + missed, err := keeper.GetMissedBlockBitmapValue(ctx, consAddr, tc.index) + require.NoError(err) require.Equal(missed, tc.missed) + missedBlocks := keeper.GetValidatorMissedBlocks(ctx, consAddr) - require.Equal(len(missedBlocks), ind+1) + fmt.Println(missedBlocks) + require.Equal(len(missedBlocks), slashingtypes.MissedBlockBitmapChunkSize) require.Equal(missedBlocks[ind].Index, tc.index) require.Equal(missedBlocks[ind].Missed, tc.missed) }) diff --git a/x/slashing/simulation/decoder.go b/x/slashing/simulation/decoder.go index d295a284ac18..1e96739d6680 100644 --- a/x/slashing/simulation/decoder.go +++ b/x/slashing/simulation/decoder.go @@ -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) diff --git a/x/slashing/simulation/decoder_test.go b/x/slashing/simulation/decoder_test.go index 974defe4a258..890e22adff3c 100644 --- a/x/slashing/simulation/decoder_test.go +++ b/x/slashing/simulation/decoder_test.go @@ -37,7 +37,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 }, From 78af46dfaf4a24649742ebdb9aa9b4d8a6670147 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Sat, 1 Apr 2023 19:53:21 -0400 Subject: [PATCH 09/24] updates --- x/slashing/keeper/signing_info.go | 7 ++- x/slashing/keeper/signing_info_test.go | 68 +++++++++++++------------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/x/slashing/keeper/signing_info.go b/x/slashing/keeper/signing_info.go index f054403cdb05..34b7c6e2a7f8 100644 --- a/x/slashing/keeper/signing_info.go +++ b/x/slashing/keeper/signing_info.go @@ -214,9 +214,12 @@ func (k Keeper) IterateMissedBlockBitmap(ctx sdk.Context, addr sdk.ConsAddress, // GetValidatorMissedBlocks returns array of missed blocks for given validator. func (k Keeper) GetValidatorMissedBlocks(ctx sdk.Context, addr sdk.ConsAddress) []types.MissedBlock { - missedBlocks := []types.MissedBlock{} + missedBlocks := make([]types.MissedBlock, 0, k.SignedBlocksWindow(ctx)) k.IterateMissedBlockBitmap(ctx, addr, func(index int64, missed bool) (stop bool) { - missedBlocks = append(missedBlocks, types.NewMissedBlock(index, missed)) + if missed { + missedBlocks = append(missedBlocks, types.NewMissedBlock(index, missed)) + } + return false }) diff --git a/x/slashing/keeper/signing_info_test.go b/x/slashing/keeper/signing_info_test.go index fba3448f1111..c08e59f26520 100644 --- a/x/slashing/keeper/signing_info_test.go +++ b/x/slashing/keeper/signing_info_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -54,45 +53,44 @@ func (s *KeeperTestSuite) TestValidatorSigningInfo() { require.Equal(sInfo.JailedUntil, jailTime) } -func (s *KeeperTestSuite) TestValidatorMissedBlockBitmap() { +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)) - - testCases := []struct { - name string - index int64 - missed bool - }{ - { - name: "missed block", - index: 50, - missed: false, - }, - { - name: "signed block", - index: 51, - missed: true, - }, - } + for _, window := range []int64{100, 32_000} { + params := testutil.TestParams() + params.SignedBlocksWindow = window + require.NoError(keeper.SetParams(ctx, params)) - for ind, tc := range testCases { - tc := tc - s.Run(tc.name, func() { - keeper.SetMissedBlockBitmapValue(ctx, consAddr, tc.index, tc.missed) + // 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) - missed, err := keeper.GetMissedBlockBitmapValue(ctx, consAddr, tc.index) + missed, err := keeper.GetMissedBlockBitmapValue(ctx, consAddr, idx) require.NoError(err) - require.Equal(missed, tc.missed) - - missedBlocks := keeper.GetValidatorMissedBlocks(ctx, consAddr) - fmt.Println(missedBlocks) - require.Equal(len(missedBlocks), slashingtypes.MissedBlockBitmapChunkSize) - require.Equal(missedBlocks[ind].Index, tc.index) - require.Equal(missedBlocks[ind].Missed, tc.missed) - }) + 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) } } From f3414bb474fd628ff4f8a36456aad52697edf2d6 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Sat, 1 Apr 2023 19:56:18 -0400 Subject: [PATCH 10/24] updates --- x/slashing/migrations/v2/keys.go | 21 +++++++++++++++++++++ x/slashing/migrations/v2/store_test.go | 5 ++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 x/slashing/migrations/v2/keys.go diff --git a/x/slashing/migrations/v2/keys.go b/x/slashing/migrations/v2/keys.go new file mode 100644 index 000000000000..26f3f97544f3 --- /dev/null +++ b/x/slashing/migrations/v2/keys.go @@ -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...) +} diff --git a/x/slashing/migrations/v2/store_test.go b/x/slashing/migrations/v2/store_test.go index b824981cb465..da583b79e6d4 100644 --- a/x/slashing/migrations/v2/store_test.go +++ b/x/slashing/migrations/v2/store_test.go @@ -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", From c7c6a2a1b5bd8162b0f1a077f544f13a358ca3e0 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Sat, 1 Apr 2023 20:10:13 -0400 Subject: [PATCH 11/24] updates --- client/v2/go.sum | 2 -- go.mod | 2 +- simapp/go.mod | 1 + simapp/go.sum | 2 ++ tests/go.mod | 1 + tests/go.sum | 2 ++ 6 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/v2/go.sum b/client/v2/go.sum index 82da01a5f017..d64eeefde8bc 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -39,8 +39,6 @@ cosmossdk.io/api v0.3.2-0.20230313131911-55bf5d4efbe7 h1:4LrWK+uGP5IxznxtHHsHD+Z cosmossdk.io/api v0.3.2-0.20230313131911-55bf5d4efbe7/go.mod h1:yVns7mKgcsG+hZW/3C5FdJtC6QYWdFIcRlKb9+5HV5g= cosmossdk.io/collections v0.0.0-20230309163709-87da587416ba h1:S4PYij/tX3Op/hwenVEN9D+M27JRcwSwVqE3UA0BnwM= cosmossdk.io/collections v0.0.0-20230309163709-87da587416ba/go.mod h1:lpS+G8bGC2anqzWdndTzjnQnuMO/qAcgZUkGJp4i3rc= -cosmossdk.io/core v0.6.1 h1:OBy7TI2W+/gyn2z40vVvruK3di+cAluinA6cybFbE7s= -cosmossdk.io/core v0.6.1/go.mod h1:g3MMBCBXtxbDWBURDVnJE7XML4BG5qENhs0gzkcpuFA= cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4 h1:l1scDTT2VX18ZuR6P0irvT/bAP0h4297D/Lka5nz2vE= cosmossdk.io/core v0.6.2-0.20230323161322-ccd8d40119e4/go.mod h1:J8R0E7soOpQFVqFiFd7EKepXCPpINa2n2t2EqbEsXnY= cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw= diff --git a/go.mod b/go.mod index b3d625f2743d..6f21c6fad91d 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 + github.com/google/go-cmp v0.5.9 github.com/google/gofuzz v1.2.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 @@ -103,7 +104,6 @@ require ( github.com/golang/glog v1.0.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/go-cmp v0.5.9 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect diff --git a/simapp/go.mod b/simapp/go.mod index 70c184c26608..f65e730e60df 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -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 diff --git a/simapp/go.sum b/simapp/go.sum index 45c0c08f3352..ab81b25ea1ce 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -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= diff --git a/tests/go.mod b/tests/go.mod index 295ce60794f7..17a471d79148 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -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 diff --git a/tests/go.sum b/tests/go.sum index e57f3664d74b..5f730c58ce00 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -269,6 +269,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= From d93802932970ce91b28f28bd95e3f24185ec7044 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Sat, 1 Apr 2023 20:40:05 -0400 Subject: [PATCH 12/24] updates --- x/slashing/keeper/migrations.go | 7 +++++++ x/slashing/module.go | 14 ++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/x/slashing/keeper/migrations.go b/x/slashing/keeper/migrations.go index fca5255e6f68..8b7c92147531 100644 --- a/x/slashing/keeper/migrations.go +++ b/x/slashing/keeper/migrations.go @@ -30,3 +30,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 { + panic("NOT IMPLEMENTED YET") +} diff --git a/x/slashing/module.go b/x/slashing/module.go index 7374e27ab075..d4e8cd38a505 100644 --- a/x/slashing/module.go +++ b/x/slashing/module.go @@ -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 From 9a2d75d03adb0b0673efdc6a73ac58ce993583a0 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 3 Apr 2023 09:57:13 -0400 Subject: [PATCH 13/24] updates --- x/slashing/keeper/migrations.go | 3 +- x/slashing/migrations/v4/keys.go | 48 +++++++++ x/slashing/migrations/v4/migrate.go | 146 ++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 x/slashing/migrations/v4/keys.go create mode 100644 x/slashing/migrations/v4/migrate.go diff --git a/x/slashing/keeper/migrations.go b/x/slashing/keeper/migrations.go index 8b7c92147531..5fb281ccc6c0 100644 --- a/x/slashing/keeper/migrations.go +++ b/x/slashing/keeper/migrations.go @@ -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. @@ -35,5 +36,5 @@ func (m Migrator) Migrate2to3(ctx sdk.Context) error { // version 3 to version 4. Specifically, it migrates the validator missed block // bitmap. func (m Migrator) Migrate3to4(ctx sdk.Context) error { - panic("NOT IMPLEMENTED YET") + return v4.Migrate(ctx, m.keeper.cdc, ctx.KVStore(m.keeper.storeKey), m.keeper.GetParams(ctx)) } diff --git a/x/slashing/migrations/v4/keys.go b/x/slashing/migrations/v4/keys.go new file mode 100644 index 000000000000..87bfce6088d9 --- /dev/null +++ b/x/slashing/migrations/v4/keys.go @@ -0,0 +1,48 @@ +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 validatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) { + kv.AssertKeyAtLeastLength(key, 2) + addr := key[1:] + kv.AssertKeyLength(addr, addrLen) + 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...) +} diff --git a/x/slashing/migrations/v4/migrate.go b/x/slashing/migrations/v4/migrate.go new file mode 100644 index 000000000000..d659eb9d0076 --- /dev/null +++ b/x/slashing/migrations/v4/migrate.go @@ -0,0 +1,146 @@ +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" +) + +func Migrate(ctx sdk.Context, cdc codec.BinaryCodec, store storetypes.KVStore, params types.Params) error { + missedBlocks := make([]types.ValidatorMissedBlocks, 0) + 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 _, mb := range missedBlocks { + addr, err := sdk.ConsAddressFromBech32(mb.Address) + if err != nil { + return err + } + + deleteValidatorMissedBlockBitArray(ctx, store, addr) + + for _, b := range mb.MissedBlocks { + 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), +) { + var index int64 + + for ; index < params.SignedBlocksWindow; index++ { + var missed gogotypes.BoolValue + bz := store.Get(validatorMissedBlockBitArrayKey(addr, index)) + if bz == nil { + continue + } + + cdc.MustUnmarshal(bz, &missed) + if cb(index, missed.Value) { + break + } + } +} + +func getValidatorMissedBlocks( + ctx sdk.Context, + cdc codec.BinaryCodec, + store storetypes.KVStore, + addr sdk.ConsAddress, + params types.Params, +) []types.MissedBlock { + 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 +} From 9a0d4e42de80826bad8d976539537bcee69ea883 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 3 Apr 2023 14:17:19 -0400 Subject: [PATCH 14/24] updates --- x/slashing/migrations/v4/keys.go | 19 ++++--- x/slashing/migrations/v4/migrate.go | 16 +++--- x/slashing/migrations/v4/migrate_test.go | 66 ++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 14 deletions(-) create mode 100644 x/slashing/migrations/v4/migrate_test.go diff --git a/x/slashing/migrations/v4/keys.go b/x/slashing/migrations/v4/keys.go index 87bfce6088d9..d27c341e3750 100644 --- a/x/slashing/migrations/v4/keys.go +++ b/x/slashing/migrations/v4/keys.go @@ -15,14 +15,19 @@ const ( ) var ( - validatorSigningInfoKeyPrefix = []byte{0x01} + ValidatorSigningInfoKeyPrefix = []byte{0x01} validatorMissedBlockBitArrayKeyPrefix = []byte{0x02} ) -func validatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) { - kv.AssertKeyAtLeastLength(key, 2) - addr := key[1:] - kv.AssertKeyLength(addr, addrLen) +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) } @@ -30,7 +35,7 @@ func validatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte { return append(validatorMissedBlockBitArrayKeyPrefix, v.Bytes()...) } -func validatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte { +func ValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte { b := make([]byte, 8) binary.LittleEndian.PutUint64(b, uint64(i)) return append(validatorMissedBlockBitArrayPrefixKey(v), b...) @@ -40,7 +45,7 @@ func validatorMissedBlockBitmapPrefixKey(v sdk.ConsAddress) []byte { return append(validatorMissedBlockBitArrayKeyPrefix, address.MustLengthPrefix(v.Bytes())...) } -func validatorMissedBlockBitmapKey(v sdk.ConsAddress, chunkIndex int64) []byte { +func ValidatorMissedBlockBitmapKey(v sdk.ConsAddress, chunkIndex int64) []byte { bz := make([]byte, 8) binary.LittleEndian.PutUint64(bz, uint64(chunkIndex)) diff --git a/x/slashing/migrations/v4/migrate.go b/x/slashing/migrations/v4/migrate.go index d659eb9d0076..29afa826fa47 100644 --- a/x/slashing/migrations/v4/migrate.go +++ b/x/slashing/migrations/v4/migrate.go @@ -11,11 +11,14 @@ import ( "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 { missedBlocks := make([]types.ValidatorMissedBlocks, 0) iterateValidatorSigningInfos(ctx, cdc, store, func(addr sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool) { bechAddr := addr.String() - localMissedBlocks := getValidatorMissedBlocks(ctx, cdc, store, addr, params) + localMissedBlocks := GetValidatorMissedBlocks(ctx, cdc, store, addr, params) missedBlocks = append(missedBlocks, types.ValidatorMissedBlocks{ Address: bechAddr, @@ -51,11 +54,11 @@ func iterateValidatorSigningInfos( store storetypes.KVStore, cb func(address sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool), ) { - iter := storetypes.KVStorePrefixIterator(store, validatorSigningInfoKeyPrefix) + iter := storetypes.KVStorePrefixIterator(store, ValidatorSigningInfoKeyPrefix) defer iter.Close() for ; iter.Valid(); iter.Next() { - address := validatorSigningInfoAddress(iter.Key()) + address := ValidatorSigningInfoAddress(iter.Key()) var info types.ValidatorSigningInfo cdc.MustUnmarshal(iter.Value(), &info) @@ -77,7 +80,7 @@ func iterateValidatorMissedBlockBitArray( for ; index < params.SignedBlocksWindow; index++ { var missed gogotypes.BoolValue - bz := store.Get(validatorMissedBlockBitArrayKey(addr, index)) + bz := store.Get(ValidatorMissedBlockBitArrayKey(addr, index)) if bz == nil { continue } @@ -89,7 +92,7 @@ func iterateValidatorMissedBlockBitArray( } } -func getValidatorMissedBlocks( +func GetValidatorMissedBlocks( ctx sdk.Context, cdc codec.BinaryCodec, store storetypes.KVStore, @@ -117,7 +120,7 @@ func deleteValidatorMissedBlockBitArray(ctx sdk.Context, store storetypes.KVStor 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) + key := ValidatorMissedBlockBitmapKey(addr, chunkIndex) bs := bitset.New(uint(missedBlockBitmapChunkSize)) chunk := store.Get(key) @@ -141,6 +144,5 @@ func setMissedBlockBitmapValue(ctx sdk.Context, store storetypes.KVStore, addr s } store.Set(key, updatedChunk) - return nil } diff --git a/x/slashing/migrations/v4/migrate_test.go b/x/slashing/migrations/v4/migrate_test.go new file mode 100644 index 000000000000..85286517d4f8 --- /dev/null +++ b/x/slashing/migrations/v4/migrate_test.go @@ -0,0 +1,66 @@ +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" +) + +const missedBlockBitmapChunkSize = 1024 // 2^10 bits + +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 / missedBlockBitmapChunkSize + chunk := store.Get(v4.ValidatorMissedBlockBitmapKey(consAddr, chunkIndex)) + require.NotNil(t, chunk) + + bs := bitset.New(uint(missedBlockBitmapChunkSize)) + require.NoError(t, bs.UnmarshalBinary(chunk)) + + // ensure all even blocks are missed + bitIndex := uint(i % 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) +} From b61ed0192782be6c42ca8ef193569a0fb3be3448 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 3 Apr 2023 14:18:38 -0400 Subject: [PATCH 15/24] updates --- x/slashing/migrations/v4/keys.go | 2 +- x/slashing/migrations/v4/migrate.go | 6 +++--- x/slashing/migrations/v4/migrate_test.go | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/x/slashing/migrations/v4/keys.go b/x/slashing/migrations/v4/keys.go index d27c341e3750..e2e4c7104f12 100644 --- a/x/slashing/migrations/v4/keys.go +++ b/x/slashing/migrations/v4/keys.go @@ -11,7 +11,7 @@ import ( const ( addrLen = 20 - missedBlockBitmapChunkSize = 1024 // 2^10 bits + MissedBlockBitmapChunkSize = 1024 // 2^10 bits ) var ( diff --git a/x/slashing/migrations/v4/migrate.go b/x/slashing/migrations/v4/migrate.go index 29afa826fa47..879e057111ed 100644 --- a/x/slashing/migrations/v4/migrate.go +++ b/x/slashing/migrations/v4/migrate.go @@ -119,10 +119,10 @@ func deleteValidatorMissedBlockBitArray(ctx sdk.Context, store storetypes.KVStor 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 + chunkIndex := index / MissedBlockBitmapChunkSize key := ValidatorMissedBlockBitmapKey(addr, chunkIndex) - bs := bitset.New(uint(missedBlockBitmapChunkSize)) + bs := bitset.New(uint(MissedBlockBitmapChunkSize)) chunk := store.Get(key) if chunk != nil { if err := bs.UnmarshalBinary(chunk); err != nil { @@ -131,7 +131,7 @@ func setMissedBlockBitmapValue(ctx sdk.Context, store storetypes.KVStore, addr s } // get the bit position in the chunk of the logical bitmap - bitIndex := uint(index % missedBlockBitmapChunkSize) + bitIndex := uint(index % MissedBlockBitmapChunkSize) if missed { bs.Set(bitIndex) } else { diff --git a/x/slashing/migrations/v4/migrate_test.go b/x/slashing/migrations/v4/migrate_test.go index 85286517d4f8..a49ae658166f 100644 --- a/x/slashing/migrations/v4/migrate_test.go +++ b/x/slashing/migrations/v4/migrate_test.go @@ -16,8 +16,6 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" ) -const missedBlockBitmapChunkSize = 1024 // 2^10 bits - var consAddr = sdk.ConsAddress(sdk.AccAddress([]byte("addr1_______________"))) func TestMigrate(t *testing.T) { @@ -47,15 +45,15 @@ func TestMigrate(t *testing.T) { require.Empty(t, entries) for i := int64(0); i < params.SignedBlocksWindow; i++ { - chunkIndex := i / missedBlockBitmapChunkSize + chunkIndex := i / v4.MissedBlockBitmapChunkSize chunk := store.Get(v4.ValidatorMissedBlockBitmapKey(consAddr, chunkIndex)) require.NotNil(t, chunk) - bs := bitset.New(uint(missedBlockBitmapChunkSize)) + bs := bitset.New(uint(v4.MissedBlockBitmapChunkSize)) require.NoError(t, bs.UnmarshalBinary(chunk)) // ensure all even blocks are missed - bitIndex := uint(i % missedBlockBitmapChunkSize) + bitIndex := uint(i % v4.MissedBlockBitmapChunkSize) require.Equal(t, i%2 == 0, bs.Test(bitIndex)) require.Equal(t, i%2 == 1, !bs.Test(bitIndex)) } From 17c2f9be4c97374ddf61eb86e1f6cd55cae9ab89 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 3 Apr 2023 14:21:32 -0400 Subject: [PATCH 16/24] updates --- x/slashing/migrations/v4/migrate.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x/slashing/migrations/v4/migrate.go b/x/slashing/migrations/v4/migrate.go index 879e057111ed..8d20d68dc0ea 100644 --- a/x/slashing/migrations/v4/migrate.go +++ b/x/slashing/migrations/v4/migrate.go @@ -15,6 +15,8 @@ import ( // 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. missedBlocks := make([]types.ValidatorMissedBlocks, 0) iterateValidatorSigningInfos(ctx, cdc, store, func(addr sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool) { bechAddr := addr.String() @@ -28,6 +30,8 @@ func Migrate(ctx sdk.Context, cdc codec.BinaryCodec, store storetypes.KVStore, p 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 { @@ -38,6 +42,9 @@ func Migrate(ctx sdk.Context, cdc codec.BinaryCodec, store storetypes.KVStore, p for _, b := range mb.MissedBlocks { if b.Missed { + // Note: It is not necessary to store entries with missed=false, i.e. + // where the bit is zer, since when the bitmap is initialized, all non-set + // bits are already zero. if err := setMissedBlockBitmapValue(ctx, store, addr, b.Index, true); err != nil { return err } From 56b634b98a27b845cd71207fc25ae61242d5d706 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 3 Apr 2023 14:22:16 -0400 Subject: [PATCH 17/24] updates --- x/slashing/migrations/v4/migrate.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/slashing/migrations/v4/migrate.go b/x/slashing/migrations/v4/migrate.go index 8d20d68dc0ea..f0bdca48b7d0 100644 --- a/x/slashing/migrations/v4/migrate.go +++ b/x/slashing/migrations/v4/migrate.go @@ -41,10 +41,10 @@ func Migrate(ctx sdk.Context, cdc codec.BinaryCodec, store storetypes.KVStore, p 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 zer, since when the bitmap is initialized, all non-set bits + // are already zero. if b.Missed { - // Note: It is not necessary to store entries with missed=false, i.e. - // where the bit is zer, since when the bitmap is initialized, all non-set - // bits are already zero. if err := setMissedBlockBitmapValue(ctx, store, addr, b.Index, true); err != nil { return err } From f8802cdc640dad892df3efd718b35316d256e630 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 3 Apr 2023 14:22:38 -0400 Subject: [PATCH 18/24] updates --- x/slashing/migrations/v4/migrate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/slashing/migrations/v4/migrate.go b/x/slashing/migrations/v4/migrate.go index f0bdca48b7d0..0b9543913b66 100644 --- a/x/slashing/migrations/v4/migrate.go +++ b/x/slashing/migrations/v4/migrate.go @@ -42,7 +42,7 @@ func Migrate(ctx sdk.Context, cdc codec.BinaryCodec, store storetypes.KVStore, p for _, b := range mb.MissedBlocks { // Note: It is not necessary to store entries with missed=false, i.e. where - // the bit is zer, since when the bitmap is initialized, all non-set bits + // the bit is zer0, 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 { From 709d5137bbe40ac8de83a64b3f17292acc5c1ceb Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 3 Apr 2023 14:22:56 -0400 Subject: [PATCH 19/24] updates --- x/slashing/migrations/v4/migrate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/slashing/migrations/v4/migrate.go b/x/slashing/migrations/v4/migrate.go index 0b9543913b66..1cb9d775f34b 100644 --- a/x/slashing/migrations/v4/migrate.go +++ b/x/slashing/migrations/v4/migrate.go @@ -42,7 +42,7 @@ func Migrate(ctx sdk.Context, cdc codec.BinaryCodec, store storetypes.KVStore, p for _, b := range mb.MissedBlocks { // Note: It is not necessary to store entries with missed=false, i.e. where - // the bit is zer0, since when the bitmap is initialized, all non-set bits + // 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 { From c1dc5d3b79d95832ce4f1831ad4b2ad33770dcdd Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 3 Apr 2023 14:28:37 -0400 Subject: [PATCH 20/24] updates --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 716314bba60d..d3622885e392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,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` @@ -94,6 +95,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. From 33f759ad8a4d110cee786d6838fe4263fac2f7f1 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 4 Apr 2023 11:35:12 -0400 Subject: [PATCH 21/24] updates --- x/slashing/keeper/genesis.go | 6 ++++-- x/slashing/keeper/signing_info.go | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/x/slashing/keeper/genesis.go b/x/slashing/keeper/genesis.go index 7972a530e548..dda2da2431d6 100644 --- a/x/slashing/keeper/genesis.go +++ b/x/slashing/keeper/genesis.go @@ -6,7 +6,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// InitGenesis initialize default parameters and the keeper's address to +// 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, @@ -36,7 +36,9 @@ func (keeper Keeper) InitGenesis(ctx sdk.Context, stakingKeeper types.StakingKee } for _, missed := range array.MissedBlocks { - keeper.SetMissedBlockBitmapValue(ctx, address, missed.Index, missed.Missed) + if err := keeper.SetMissedBlockBitmapValue(ctx, address, missed.Index, missed.Missed); err != nil { + panic(err) + } } } diff --git a/x/slashing/keeper/signing_info.go b/x/slashing/keeper/signing_info.go index 34b7c6e2a7f8..ac4f86b8c47d 100644 --- a/x/slashing/keeper/signing_info.go +++ b/x/slashing/keeper/signing_info.go @@ -129,7 +129,8 @@ func (k Keeper) GetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, } } - // get the bit position in the chunk of the logical bitmap + // 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 } @@ -203,6 +204,7 @@ func (k Keeper) IterateMissedBlockBitmap(ctx sdk.Context, addr sdk.ConsAddress, } 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 } From 41df52d0bbaf309230200685df2ee8aa7413201c Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 4 Apr 2023 11:39:56 -0400 Subject: [PATCH 22/24] updates --- x/slashing/migrations/v4/migrate.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/x/slashing/migrations/v4/migrate.go b/x/slashing/migrations/v4/migrate.go index 1cb9d775f34b..01480c05e185 100644 --- a/x/slashing/migrations/v4/migrate.go +++ b/x/slashing/migrations/v4/migrate.go @@ -17,7 +17,7 @@ import ( 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. - missedBlocks := make([]types.ValidatorMissedBlocks, 0) + 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) @@ -83,17 +83,15 @@ func iterateValidatorMissedBlockBitArray( params types.Params, cb func(index int64, missed bool) (stop bool), ) { - var index int64 - - for ; index < params.SignedBlocksWindow; index++ { + for i := int64(0); i < params.SignedBlocksWindow; i++ { var missed gogotypes.BoolValue - bz := store.Get(ValidatorMissedBlockBitArrayKey(addr, index)) + bz := store.Get(ValidatorMissedBlockBitArrayKey(addr, i)) if bz == nil { continue } cdc.MustUnmarshal(bz, &missed) - if cb(index, missed.Value) { + if cb(i, missed.Value) { break } } @@ -106,7 +104,7 @@ func GetValidatorMissedBlocks( addr sdk.ConsAddress, params types.Params, ) []types.MissedBlock { - missedBlocks := []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 From abc3417327bf29c3d388973cc880f0e0848600ea Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 4 Apr 2023 11:43:08 -0400 Subject: [PATCH 23/24] updates --- x/slashing/keeper/signing_info.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/x/slashing/keeper/signing_info.go b/x/slashing/keeper/signing_info.go index ac4f86b8c47d..628b262369ea 100644 --- a/x/slashing/keeper/signing_info.go +++ b/x/slashing/keeper/signing_info.go @@ -95,17 +95,17 @@ func (k Keeper) IsTombstoned(ctx sdk.Context, consAddr sdk.ConsAddress) bool { return signInfo.Tombstoned } -// GetMissedBlockBitmapChunk gets the bitmap chunk at the given chunk index for +// 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 { +func (k Keeper) getMissedBlockBitmapChunk(ctx sdk.Context, addr sdk.ConsAddress, chunkIndex int64) []byte { store := ctx.KVStore(k.storeKey) chunk := store.Get(types.ValidatorMissedBlockBitmapKey(addr, chunkIndex)) return chunk } -// SetMissedBlockBitmapChunk sets the bitmap chunk at the given chunk index for +// 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) { +func (k Keeper) setMissedBlockBitmapChunk(ctx sdk.Context, addr sdk.ConsAddress, chunkIndex int64, chunk []byte) { store := ctx.KVStore(k.storeKey) key := types.ValidatorMissedBlockBitmapKey(addr, chunkIndex) store.Set(key, chunk) @@ -122,7 +122,7 @@ func (k Keeper) GetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, chunkIndex := index / types.MissedBlockBitmapChunkSize bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) - chunk := k.GetMissedBlockBitmapChunk(ctx, addr, chunkIndex) + 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) @@ -147,7 +147,7 @@ func (k Keeper) SetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, chunkIndex := index / types.MissedBlockBitmapChunkSize bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) - chunk := k.GetMissedBlockBitmapChunk(ctx, addr, chunkIndex) + 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) @@ -167,7 +167,7 @@ func (k Keeper) SetMissedBlockBitmapValue(ctx sdk.Context, addr sdk.ConsAddress, return errors.Wrapf(err, "failed to encode bitmap chunk; index: %d", index) } - k.SetMissedBlockBitmapChunk(ctx, addr, chunkIndex, updatedChunk) + k.setMissedBlockBitmapChunk(ctx, addr, chunkIndex, updatedChunk) return nil } From 4079eba8ce18caa1ee850225d9f8cc5535d4115f Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 4 Apr 2023 16:31:34 -0400 Subject: [PATCH 24/24] updates --- x/slashing/keeper/infractions.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/x/slashing/keeper/infractions.go b/x/slashing/keeper/infractions.go index 7cb57f702b07..557a48a639cf 100644 --- a/x/slashing/keeper/infractions.go +++ b/x/slashing/keeper/infractions.go @@ -52,13 +52,19 @@ func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr cryptotypes.Addre case !previous && missed: // Bitmap value has changed from not missed to missed, so we flip the bit // and increment the counter. - k.SetMissedBlockBitmapValue(ctx, consAddr, index, true) + if err := k.SetMissedBlockBitmapValue(ctx, consAddr, index, true); err != nil { + panic(err) + } + signInfo.MissedBlocksCounter++ case previous && !missed: // Bitmap value has changed from missed to not missed, so we flip the bit // and decrement the counter. - k.SetMissedBlockBitmapValue(ctx, consAddr, index, false) + if err := k.SetMissedBlockBitmapValue(ctx, consAddr, index, false); err != nil { + panic(err) + } + signInfo.MissedBlocksCounter-- default: