diff --git a/CHANGELOG.md b/CHANGELOG.md index 615ba18d93..39e2129613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking Changes +* (x/slashing) [#17568](https://github.com/cosmos/cosmos-sdk/pull/17568) Use collections for `ValidatorMissedBlockBitmap`: + * remove from `types`: `ValidatorMissedBlockBitmapPrefixKey`, `ValidatorMissedBlockBitmapKey` * (x/staking) [#17481](https://github.com/cosmos/cosmos-sdk/pull/17481) Use collections for `UnbondingQueue`: * remove from `Keeper`: `UBDQueueIterator` * remove from `types`: `GetUnbondingDelegationTimeKey` diff --git a/x/slashing/keeper/keeper.go b/x/slashing/keeper/keeper.go index 55e1abee30..f0d33a8967 100644 --- a/x/slashing/keeper/keeper.go +++ b/x/slashing/keeper/keeper.go @@ -25,11 +25,12 @@ type Keeper struct { // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. - authority string - Schema collections.Schema - Params collections.Item[types.Params] - ValidatorSigningInfo collections.Map[sdk.ConsAddress, types.ValidatorSigningInfo] - AddrPubkeyRelation collections.Map[[]byte, cryptotypes.PubKey] + authority string + Schema collections.Schema + Params collections.Item[types.Params] + ValidatorSigningInfo collections.Map[sdk.ConsAddress, types.ValidatorSigningInfo] + AddrPubkeyRelation collections.Map[[]byte, cryptotypes.PubKey] + ValidatorMissedBlockBitmap collections.Map[collections.Pair[[]byte, uint64], []byte] } // NewKeeper creates a slashing keeper @@ -56,6 +57,13 @@ func NewKeeper(cdc codec.BinaryCodec, legacyAmino *codec.LegacyAmino, storeServi sdk.LengthPrefixedBytesKey, // sdk.LengthPrefixedBytesKey is needed to retain state compatibility codec.CollInterfaceValue[cryptotypes.PubKey](cdc), ), + ValidatorMissedBlockBitmap: collections.NewMap( + sb, + types.ValidatorMissedBlockBitmapKeyPrefix, + "validator_missed_block_bitmap", + collections.PairKeyCodec(sdk.LengthPrefixedBytesKey, collections.Uint64Key), + collections.BytesValue, + ), } schema, err := sb.Build() diff --git a/x/slashing/keeper/keeper_test.go b/x/slashing/keeper/keeper_test.go index a0068455b3..0b39ed993f 100644 --- a/x/slashing/keeper/keeper_test.go +++ b/x/slashing/keeper/keeper_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "encoding/binary" "testing" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -18,6 +19,7 @@ import ( sdktestutil "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" + addresstypes "github.com/cosmos/cosmos-sdk/types/address" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -36,10 +38,12 @@ type KeeperTestSuite struct { slashingKeeper slashingkeeper.Keeper queryClient slashingtypes.QueryClient msgServer slashingtypes.MsgServer + key *storetypes.KVStoreKey } func (s *KeeperTestSuite) SetupTest() { key := storetypes.NewKVStoreKey(slashingtypes.StoreKey) + s.key = key storeService := runtime.NewKVStoreService(key) testCtx := sdktestutil.DefaultContextWithDB(s.T(), key, storetypes.NewTransientStoreKey("transient_test")) ctx := testCtx.Ctx.WithBlockHeader(cmtproto.Header{Time: cmttime.Now()}) @@ -131,6 +135,45 @@ func (s *KeeperTestSuite) TestJailAndSlashWithInfractionReason() { s.Require().NoError(s.slashingKeeper.Jail(s.ctx, consAddr)) } +// 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)) + + validatorMissedBlockBitmapKeyPrefix := []byte{0x02} // Prefix for missed block bitmap + return append(append(validatorMissedBlockBitmapKeyPrefix, addresstypes.MustLengthPrefix(v.Bytes())...), bz...) +} + +func (s *KeeperTestSuite) TestValidatorMissedBlockBMMigrationToColls() { + s.SetupTest() + + consAddr := sdk.ConsAddress(sdk.AccAddress([]byte("addr1_______________"))) + index := int64(0) + err := sdktestutil.DiffCollectionsMigration( + s.ctx, + s.key, + 100, + func(i int64) { + s.ctx.KVStore(s.key).Set(validatorMissedBlockBitmapKey(consAddr, index), []byte{}) + }, + "7ad1f994d45ec9495ae5f990a3fba100c2cc70167a154c33fb43882dc004eafd", + ) + s.Require().NoError(err) + + err = sdktestutil.DiffCollectionsMigration( + s.ctx, + s.key, + 100, + func(i int64) { + err := s.slashingKeeper.SetMissedBlockBitmapChunk(s.ctx, consAddr, index, []byte{}) + s.Require().NoError(err) + }, + "7ad1f994d45ec9495ae5f990a3fba100c2cc70167a154c33fb43882dc004eafd", + ) + s.Require().NoError(err) +} + func TestKeeperTestSuite(t *testing.T) { suite.Run(t, new(KeeperTestSuite)) } diff --git a/x/slashing/keeper/signing_info.go b/x/slashing/keeper/signing_info.go index 69dc21df46..af46ea446a 100644 --- a/x/slashing/keeper/signing_info.go +++ b/x/slashing/keeper/signing_info.go @@ -2,12 +2,13 @@ package keeper import ( "context" + "errors" "time" "github.com/bits-and-blooms/bitset" - "cosmossdk.io/errors" - storetypes "cosmossdk.io/store/types" + "cosmossdk.io/collections" + errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/slashing/types" @@ -25,7 +26,7 @@ func (k Keeper) HasValidatorSigningInfo(ctx context.Context, consAddr sdk.ConsAd func (k Keeper) JailUntil(ctx context.Context, consAddr sdk.ConsAddress, jailTime time.Time) error { signInfo, err := k.ValidatorSigningInfo.Get(ctx, consAddr) if err != nil { - return errors.Wrap(err, "cannot jail validator that does not have any signing information") + return errorsmod.Wrap(err, "cannot jail validator that does not have any signing information") } signInfo.JailedUntil = jailTime @@ -61,17 +62,25 @@ func (k Keeper) IsTombstoned(ctx context.Context, consAddr sdk.ConsAddress) bool // getMissedBlockBitmapChunk gets the bitmap chunk at the given chunk index for // a validator's missed block signing window. func (k Keeper) getMissedBlockBitmapChunk(ctx context.Context, addr sdk.ConsAddress, chunkIndex int64) ([]byte, error) { - store := k.storeService.OpenKVStore(ctx) - chunk, err := store.Get(types.ValidatorMissedBlockBitmapKey(addr, chunkIndex)) - return chunk, err + consAddr, err := k.sk.ConsensusAddressCodec().StringToBytes(addr.String()) + if err != nil { + return nil, err + } + chunk, err := k.ValidatorMissedBlockBitmap.Get(ctx, collections.Join(consAddr, uint64(chunkIndex))) + if err != nil && !errors.Is(err, collections.ErrNotFound) { + return nil, err + } + return chunk, nil } -// 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 context.Context, addr sdk.ConsAddress, chunkIndex int64, chunk []byte) error { - store := k.storeService.OpenKVStore(ctx) - key := types.ValidatorMissedBlockBitmapKey(addr, chunkIndex) - return store.Set(key, chunk) +func (k Keeper) SetMissedBlockBitmapChunk(ctx context.Context, addr sdk.ConsAddress, chunkIndex int64, chunk []byte) error { + consAddr, err := k.sk.ConsensusAddressCodec().StringToBytes(addr.String()) + if err != nil { + return err + } + return k.ValidatorMissedBlockBitmap.Set(ctx, collections.Join(consAddr, uint64(chunkIndex)), chunk) } // GetMissedBlockBitmapValue returns true if a validator missed signing a block @@ -87,12 +96,12 @@ func (k Keeper) GetMissedBlockBitmapValue(ctx context.Context, addr sdk.ConsAddr bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) chunk, err := k.getMissedBlockBitmapChunk(ctx, addr, chunkIndex) if err != nil { - return false, errors.Wrapf(err, "failed to get bitmap chunk; index: %d", index) + return false, errorsmod.Wrapf(err, "failed to get 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) + return false, errorsmod.Wrapf(err, "failed to decode bitmap chunk; index: %d", index) } } @@ -116,12 +125,12 @@ func (k Keeper) SetMissedBlockBitmapValue(ctx context.Context, addr sdk.ConsAddr bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) chunk, err := k.getMissedBlockBitmapChunk(ctx, addr, chunkIndex) if err != nil { - return errors.Wrapf(err, "failed to get bitmap chunk; index: %d", index) + return errorsmod.Wrapf(err, "failed to get bitmap chunk; index: %d", index) } if chunk != nil { if err := bs.UnmarshalBinary(chunk); err != nil { - return errors.Wrapf(err, "failed to decode bitmap chunk; index: %d", index) + return errorsmod.Wrapf(err, "failed to decode bitmap chunk; index: %d", index) } } @@ -135,28 +144,30 @@ func (k Keeper) SetMissedBlockBitmapValue(ctx context.Context, addr sdk.ConsAddr updatedChunk, err := bs.MarshalBinary() if err != nil { - return errors.Wrapf(err, "failed to encode bitmap chunk; index: %d", index) + return errorsmod.Wrapf(err, "failed to encode bitmap chunk; index: %d", index) } - return k.setMissedBlockBitmapChunk(ctx, addr, chunkIndex, updatedChunk) + return k.SetMissedBlockBitmapChunk(ctx, addr, chunkIndex, updatedChunk) } // DeleteMissedBlockBitmap removes a validator's missed block bitmap from state. func (k Keeper) DeleteMissedBlockBitmap(ctx context.Context, addr sdk.ConsAddress) error { - store := k.storeService.OpenKVStore(ctx) - prefix := types.ValidatorMissedBlockBitmapPrefixKey(addr) - iter, err := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix)) + consAddr, err := k.sk.ConsensusAddressCodec().StringToBytes(addr.String()) if err != nil { return err } - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - err = store.Delete(iter.Key()) + rng := collections.NewPrefixedPairRange[[]byte, uint64](consAddr) + err = k.ValidatorMissedBlockBitmap.Walk(ctx, rng, func(key collections.Pair[[]byte, uint64], value []byte) (bool, error) { + err := k.ValidatorMissedBlockBitmap.Remove(ctx, key) if err != nil { - return err + return true, err } + return false, nil + }) + if err != nil { + return err } + return nil } @@ -167,20 +178,17 @@ func (k Keeper) DeleteMissedBlockBitmap(ctx context.Context, addr sdk.ConsAddres // Note: A callback will only be executed over all bitmap chunks that exist in // state. func (k Keeper) IterateMissedBlockBitmap(ctx context.Context, addr sdk.ConsAddress, cb func(index int64, missed bool) (stop bool)) error { - store := k.storeService.OpenKVStore(ctx) - prefix := types.ValidatorMissedBlockBitmapPrefixKey(addr) - iter, err := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix)) + consAddr, err := k.sk.ConsensusAddressCodec().StringToBytes(addr.String()) if err != nil { return err } - defer iter.Close() - var index int64 - for ; iter.Valid(); iter.Next() { + rng := collections.NewPrefixedPairRange[[]byte, uint64](consAddr) + err = k.ValidatorMissedBlockBitmap.Walk(ctx, rng, func(key collections.Pair[[]byte, uint64], value []byte) (bool, error) { bs := bitset.New(uint(types.MissedBlockBitmapChunkSize)) - if err := bs.UnmarshalBinary(iter.Value()); err != nil { - return errors.Wrapf(err, "failed to decode bitmap chunk; index: %v", string(iter.Key())) + if err := bs.UnmarshalBinary(value); err != nil { + return true, errorsmod.Wrapf(err, "failed to decode bitmap chunk; index: %v", key) } for i := uint(0); i < types.MissedBlockBitmapChunkSize; i++ { @@ -191,6 +199,10 @@ func (k Keeper) IterateMissedBlockBitmap(ctx context.Context, addr sdk.ConsAddre index++ } + return false, nil + }) + if err != nil { + return err } return nil } diff --git a/x/slashing/simulation/decoder_test.go b/x/slashing/simulation/decoder_test.go index dc98a2d1ee..d234377677 100644 --- a/x/slashing/simulation/decoder_test.go +++ b/x/slashing/simulation/decoder_test.go @@ -27,12 +27,10 @@ func TestDecodeStore(t *testing.T) { dec := simulation.NewDecodeStore(cdc) info := types.NewValidatorSigningInfo(consAddr1, 0, 1, time.Now().UTC(), false, 0) - missed := []byte{1} // we want to display the bytes for simulation diffs kvPairs := kv.Pairs{ Pairs: []kv.Pair{ {Key: types.ValidatorSigningInfoKey(consAddr1), Value: cdc.MustMarshal(&info)}, - {Key: types.ValidatorMissedBlockBitmapKey(consAddr1, 6), Value: missed}, {Key: []byte{0x99}, Value: []byte{0x99}}, // This test should panic }, } @@ -43,7 +41,6 @@ func TestDecodeStore(t *testing.T) { panics bool }{ {"ValidatorSigningInfo", fmt.Sprintf("%v\n%v", info, info), false}, - {"ValidatorMissedBlockBitArray", fmt.Sprintf("missedA: %v\nmissedB: %v\n", missed, missed), false}, {"other", "", true}, } for i, tt := range tests { diff --git a/x/slashing/types/keys.go b/x/slashing/types/keys.go index 3894777d93..91a8c9fe1c 100644 --- a/x/slashing/types/keys.go +++ b/x/slashing/types/keys.go @@ -1,8 +1,6 @@ package types import ( - "encoding/binary" - "cosmossdk.io/collections" sdk "github.com/cosmos/cosmos-sdk/types" @@ -51,7 +49,7 @@ const ( var ( ParamsKey = collections.NewPrefix(0) // Prefix for params key ValidatorSigningInfoKeyPrefix = collections.NewPrefix(1) // Prefix for signing info - ValidatorMissedBlockBitmapKeyPrefix = []byte{0x02} // Prefix for missed block bitmap + ValidatorMissedBlockBitmapKeyPrefix = collections.NewPrefix(2) // Prefix for missed block bitmap AddrPubkeyRelationKeyPrefix = collections.NewPrefix(3) // Prefix for address-pubkey relation ) @@ -59,18 +57,3 @@ var ( func ValidatorSigningInfoKey(v sdk.ConsAddress) []byte { return append(ValidatorSigningInfoKeyPrefix, 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())...) -} - -// 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(ValidatorMissedBlockBitmapPrefixKey(v), bz...) -}