Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: less time intensive slashing migration #580

Merged
merged 15 commits into from
Mar 21, 2024
5 changes: 5 additions & 0 deletions x/slashing/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"github.com/cosmos/cosmos-sdk/x/slashing/types"
)

var deprecatedBitArrayPruneLimitPerBlock = 2000
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be convinced to lower this, but I would rather have the one time payment of deleting every block frontloaded at time of upgrade rather than slight degradation for a very long time.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would agree with this


// BeginBlocker check for infraction evidence or downtime of validators
// on every begin block
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) {
Expand All @@ -23,4 +25,7 @@
for _, voteInfo := range req.LastCommitInfo.GetVotes() {
k.HandleValidatorSignatureWithParams(ctx, params, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock)
}

// If there are still entries for the deprecated MissedBlockBitArray, delete them up until we hit the per block limit
k.DeleteDeprecatedValidatorMissedBlockBitArray(ctx, deprecatedBitArrayPruneLimitPerBlock)
Dismissed Show dismissed Hide dismissed
}
41 changes: 41 additions & 0 deletions x/slashing/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
v4 "github.com/cosmos/cosmos-sdk/x/slashing/migrations/v4"
"github.com/cosmos/cosmos-sdk/x/slashing/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
Expand Down Expand Up @@ -115,3 +116,43 @@ func (k Keeper) deleteAddrPubkeyRelation(ctx sdk.Context, addr cryptotypes.Addre
store := ctx.KVStore(k.storeKey)
store.Delete(types.AddrPubkeyRelationKey(addr))
}

func (k Keeper) DeleteDeprecatedValidatorMissedBlockBitArray(ctx sdk.Context, iterationLimit int) {
store := ctx.KVStore(k.storeKey)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going between using store vs using params a few times. I thought params was the best at first since we already call getParams in the BeginBlocker, but it felt kind of awkward.

if store.Get(types.IsPruningKey) == nil {
return
}

valSignInfoIter := sdk.KVStorePrefixIterator(store, types.ValidatorSigningInfoKeyPrefix)
defer valSignInfoIter.Close()

iterationCounter := 0
for ; valSignInfoIter.Valid(); valSignInfoIter.Next() {
address := types.ValidatorSigningInfoAddress(valSignInfoIter.Key())

// created anonymous function to scope defer statement
func() {
iter := storetypes.KVStorePrefixIterator(store, v4.DeprecatedValidatorMissedBlockBitArrayPrefixKey(address))
defer iter.Close()

for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
iterationCounter++
if iterationCounter >= iterationLimit {
break
}
}
}()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using anon func due to nested defers


if iterationCounter >= iterationLimit {
break
}
}

ctx.Logger().Info("Deleted deprecated missed block bit arrays", "count", iterationCounter)

// if we have deleted all the deprecated missed block bit arrays, we can delete the pruning key (setting to nil)
if iterationCounter == 0 {
store.Delete(types.IsPruningKey)
}
}
19 changes: 12 additions & 7 deletions x/slashing/migrations/v4/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ import (
const MissedBlockBitmapChunkSize = 1024 // 2^10 bits

var (
ValidatorSigningInfoKeyPrefix = []byte{0x01}
validatorMissedBlockBitArrayKeyPrefix = []byte{0x02}
ValidatorSigningInfoKeyPrefix = []byte{0x01}
deprecatedValidatorMissedBlockBitArrayKeyPrefix = []byte{0x02}

// NOTE: sdk v0.50 uses the same key prefix for both deprecated and new missed block bitmaps.
// We needed to use a new key, because we are skipping deletion of all old keys at upgrade time
// due to how long this would bring the chain down. We use 0x10 here to prevent overlap with any future keys.
validatorMissedBlockBitMapKeyPrefix = []byte{0x10}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set to 0x10 to prevent overlap if more keys are introduced in the next sdk release we upgrade to

)

func ValidatorSigningInfoKey(v sdk.ConsAddress) []byte {
Expand All @@ -27,18 +32,18 @@ func ValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) {
return sdk.ConsAddress(addr)
}

func validatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte {
return append(validatorMissedBlockBitArrayKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
func DeprecatedValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte {
return append(deprecatedValidatorMissedBlockBitArrayKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
}

func ValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte {
func DeprecatedValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
return append(validatorMissedBlockBitArrayPrefixKey(v), b...)
return append(DeprecatedValidatorMissedBlockBitArrayPrefixKey(v), b...)
}

func validatorMissedBlockBitmapPrefixKey(v sdk.ConsAddress) []byte {
return append(validatorMissedBlockBitArrayKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
return append(validatorMissedBlockBitMapKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
}

func ValidatorMissedBlockBitmapKey(v sdk.ConsAddress, chunkIndex int64) []byte {
Expand Down
28 changes: 17 additions & 11 deletions x/slashing/migrations/v4/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ func Migrate(ctx sdk.Context, cdc codec.BinaryCodec, store storetypes.KVStore, p
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)

// We opt to reset all validators missed blocks to improve upgrade performance
// localMissedBlocks := GetValidatorMissedBlocks(ctx, cdc, store, addr, params)

missedBlocks = append(missedBlocks, types.ValidatorMissedBlocks{
Address: bechAddr,
MissedBlocks: localMissedBlocks,
MissedBlocks: []types.MissedBlock{},
})

return false
Expand All @@ -39,7 +41,10 @@ func Migrate(ctx sdk.Context, cdc codec.BinaryCodec, store storetypes.KVStore, p
return err
}

deleteValidatorMissedBlockBitArray(ctx, store, addr)
// We skip the deletion here in favor of spreading out across multiple block for performance reasons
// We set the isPruning key to true to indicate that we are in the process of pruning
store.Set(types.IsPruningKey, []byte{1})
// deleteDeprecatedValidatorMissedBlockBitArray(ctx, store, addr)

for _, b := range mb.MissedBlocks {
// Note: It is not necessary to store entries with missed=false, i.e. where
Expand Down Expand Up @@ -86,7 +91,7 @@ func iterateValidatorMissedBlockBitArray(
) {
for i := int64(0); i < params.SignedBlocksWindow; i++ {
var missed gogotypes.BoolValue
bz := store.Get(ValidatorMissedBlockBitArrayKey(addr, i))
bz := store.Get(DeprecatedValidatorMissedBlockBitArrayKey(addr, i))
if bz == nil {
continue
}
Expand Down Expand Up @@ -114,14 +119,15 @@ func GetValidatorMissedBlocks(
return missedBlocks
}

func deleteValidatorMissedBlockBitArray(ctx sdk.Context, store storetypes.KVStore, addr sdk.ConsAddress) {
iter := storetypes.KVStorePrefixIterator(store, validatorMissedBlockBitArrayPrefixKey(addr))
defer iter.Close()
// No longer use this
// func deleteDeprecatedValidatorMissedBlockBitArray(ctx sdk.Context, store storetypes.KVStore, addr sdk.ConsAddress) {
// iter := storetypes.KVStorePrefixIterator(store, DeprecatedValidatorMissedBlockBitArrayPrefixKey(addr))
// defer iter.Close()

for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}
// 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
Expand Down
20 changes: 11 additions & 9 deletions x/slashing/migrations/v4/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package v4_test
import (
"testing"

"github.com/bits-and-blooms/bitset"
gogotypes "github.com/cosmos/gogoproto/types"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -35,7 +34,7 @@ func TestMigrate(t *testing.T) {
// all even blocks are missed
missed := &gogotypes.BoolValue{Value: i%2 == 0}
bz := cdc.MustMarshal(missed)
store.Set(v4.ValidatorMissedBlockBitArrayKey(consAddr, i), bz)
store.Set(v4.DeprecatedValidatorMissedBlockBitArrayKey(consAddr, i), bz)
}

err := v4.Migrate(ctx, cdc, store, params)
Expand All @@ -44,15 +43,18 @@ func TestMigrate(t *testing.T) {
for i := int64(0); i < params.SignedBlocksWindow; i++ {
chunkIndex := i / v4.MissedBlockBitmapChunkSize
chunk := store.Get(v4.ValidatorMissedBlockBitmapKey(consAddr, chunkIndex))
require.NotNil(t, chunk)

bs := bitset.New(uint(v4.MissedBlockBitmapChunkSize))
require.NoError(t, bs.UnmarshalBinary(chunk))
// We reset all validators missed blocks to improve upgrade performance,
// so we expect all chunks to be empty
require.Nil(t, chunk)

// ensure all even blocks are missed
bitIndex := uint(i % v4.MissedBlockBitmapChunkSize)
require.Equal(t, i%2 == 0, bs.Test(bitIndex))
require.Equal(t, i%2 == 1, !bs.Test(bitIndex))
// bs := bitset.New(uint(v4.MissedBlockBitmapChunkSize))
// require.NoError(t, bs.UnmarshalBinary(chunk))

// // ensure all even blocks are missed
// bitIndex := uint(i % v4.MissedBlockBitmapChunkSize)
// require.Equal(t, i%2 == 0, bs.Test(bitIndex))
// require.Equal(t, i%2 == 1, !bs.Test(bitIndex))
}

// ensure there's only one chunk for a window of size 100
Expand Down
12 changes: 8 additions & 4 deletions x/slashing/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ const (
// - 0x03<accAddrLen (1 Byte)><accAddr_Bytes>: cryptotypes.PubKey

var (
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
ParamsKey = []byte{0x00} // Prefix for params key
ValidatorSigningInfoKeyPrefix = []byte{0x01} // Prefix for signing info
AddrPubkeyRelationKeyPrefix = []byte{0x03} // Prefix for address-pubkey relation

ValidatorMissedBlockBitmapKeyPrefix = []byte{0x10} // Prefix for missed block bitmap

IsPruningKey = []byte{0x09}
TrueByteValue = []byte{0x01}
)

// ValidatorSigningInfoKey - stored by *Consensus* address (not operator address)
Expand Down
Loading