Skip to content

Commit

Permalink
feat: include rotate keys logic in abci (#18236)
Browse files Browse the repository at this point in the history
  • Loading branch information
atheeshp authored Dec 22, 2023
1 parent 51b72d2 commit 19e66a9
Show file tree
Hide file tree
Showing 23 changed files with 1,051 additions and 239 deletions.
1 change: 1 addition & 0 deletions tests/integration/staking/keeper/unbonding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func SetupUnbondingTests(t *testing.T, f *fixture, hookCalled *bool, ubdeID *uin
mockStackingHooks.EXPECT().BeforeDelegationSharesModified(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockStackingHooks.EXPECT().BeforeValidatorModified(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockStackingHooks.EXPECT().BeforeValidatorSlashed(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockStackingHooks.EXPECT().AfterConsensusPubKeyUpdate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
f.stakingKeeper.SetHooks(types.NewMultiStakingHooks(mockStackingHooks))

addrDels = simtestutil.AddTestAddrsIncremental(f.bankKeeper, f.stakingKeeper, f.sdkCtx, 2, math.NewInt(10000))
Expand Down
5 changes: 5 additions & 0 deletions x/distribution/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"cosmossdk.io/x/distribution/types"
stakingtypes "cosmossdk.io/x/staking/types"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

Expand Down Expand Up @@ -187,3 +188,7 @@ func (h Hooks) BeforeDelegationRemoved(_ context.Context, _ sdk.AccAddress, _ sd
func (h Hooks) AfterUnbondingInitiated(_ context.Context, _ uint64) error {
return nil
}

func (h Hooks) AfterConsensusPubKeyUpdate(_ context.Context, _, _ cryptotypes.PubKey, _ sdk.Coin) error {
return nil
}
9 changes: 9 additions & 0 deletions x/evidence/keeper/infraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ func (k Keeper) handleEquivocationEvidence(ctx context.Context, evidence *types.
}

if len(validator.GetOperator()) != 0 {
// Get the consAddr from the validator read from the store and not from the evidence,
// because if the validator has rotated its key, the key in evidence could be outdated.
// (ValidatorByConsAddr can get a validator even if the key has been rotated)
valConsAddr, err := validator.GetConsAddr()
if err != nil {
return err
}
consAddr = valConsAddr

if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil {
// Ignore evidence that cannot be handled.
//
Expand Down
14 changes: 14 additions & 0 deletions x/slashing/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
sdkmath "cosmossdk.io/math"
"cosmossdk.io/x/slashing/types"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

Expand Down Expand Up @@ -99,3 +100,16 @@ func (h Hooks) BeforeValidatorSlashed(_ context.Context, _ sdk.ValAddress, _ sdk
func (h Hooks) AfterUnbondingInitiated(_ context.Context, _ uint64) error {
return nil
}

// AfterConsensusPubKeyUpdate triggers the functions to rotate the signing-infos also sets address pubkey relation.
func (h Hooks) AfterConsensusPubKeyUpdate(ctx context.Context, oldPubKey, newPubKey cryptotypes.PubKey, _ sdk.Coin) error {
if err := h.k.performConsensusPubKeyUpdate(ctx, oldPubKey, newPubKey); err != nil {
return err
}

if err := h.k.AddrPubkeyRelation.Remove(ctx, oldPubKey.Address()); err != nil {
return err
}

return nil
}
71 changes: 60 additions & 11 deletions x/slashing/keeper/signing_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/x/slashing/types"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

Expand Down Expand Up @@ -75,13 +76,35 @@ func (k Keeper) SetMissedBlockBitmapChunk(ctx context.Context, addr sdk.ConsAddr
return k.ValidatorMissedBlockBitmap.Set(ctx, collections.Join(addr.Bytes(), uint64(chunkIndex)), chunk)
}

// getPreviousConsKey checks if the key rotated, returns the old consKey to get the missed blocks
// because missed blocks are still pointing to the old key
func (k Keeper) getPreviousConsKey(ctx context.Context, addr sdk.ConsAddress) (sdk.ConsAddress, error) {
oldPk, err := k.sk.ValidatorIdentifier(ctx, addr)
if err != nil {
return nil, err
}

if oldPk != nil {
return oldPk, nil
}

return addr, nil
}

// 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 context.Context, addr sdk.ConsAddress, index int64) (bool, error) {
// check the key rotated, if rotated use the returned consKey to get the missed blocks
// because missed blocks are still pointing to the old key
addr, err := k.getPreviousConsKey(ctx, addr)
if err != nil {
return false, err
}

// get the chunk or "word" in the logical bitmap
chunkIndex := index / types.MissedBlockBitmapChunkSize

Expand Down Expand Up @@ -111,6 +134,13 @@ func (k Keeper) GetMissedBlockBitmapValue(ctx context.Context, addr sdk.ConsAddr
// index is used to fetch the chunk in the bitmap and the relative bit in that
// chunk.
func (k Keeper) SetMissedBlockBitmapValue(ctx context.Context, addr sdk.ConsAddress, index int64, missed bool) error {
// check the key rotated, if rotated use the returned consKey to get the missed blocks
// because missed blocks are still pointing to the old key
addr, err := k.getPreviousConsKey(ctx, addr)
if err != nil {
return err
}

// get the chunk or "word" in the logical bitmap
chunkIndex := index / types.MissedBlockBitmapChunkSize

Expand Down Expand Up @@ -144,19 +174,21 @@ func (k Keeper) SetMissedBlockBitmapValue(ctx context.Context, addr sdk.ConsAddr

// DeleteMissedBlockBitmap removes a validator's missed block bitmap from state.
func (k Keeper) DeleteMissedBlockBitmap(ctx context.Context, addr sdk.ConsAddress) error {
// check the key rotated, if rotated use the returned consKey to delete the missed blocks
// because missed blocks are still pointing to the old key
addr, err := k.getPreviousConsKey(ctx, addr)
if err != nil {
return err
}

rng := collections.NewPrefixedPairRange[[]byte, uint64](addr.Bytes())
err := k.ValidatorMissedBlockBitmap.Walk(ctx, rng, func(key collections.Pair[[]byte, uint64], value []byte) (bool, error) {
return 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 true, err
}
return false, nil
})
if err != nil {
return err
}

return nil
}

// IterateMissedBlockBitmap iterates over a validator's signed blocks window
Expand All @@ -168,7 +200,7 @@ func (k Keeper) DeleteMissedBlockBitmap(ctx context.Context, addr sdk.ConsAddres
func (k Keeper) IterateMissedBlockBitmap(ctx context.Context, addr sdk.ConsAddress, cb func(index int64, missed bool) (stop bool)) error {
var index int64
rng := collections.NewPrefixedPairRange[[]byte, uint64](addr.Bytes())
err := k.ValidatorMissedBlockBitmap.Walk(ctx, rng, func(key collections.Pair[[]byte, uint64], value []byte) (bool, error) {
return 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(value); err != nil {
Expand All @@ -185,10 +217,6 @@ func (k Keeper) IterateMissedBlockBitmap(ctx context.Context, addr sdk.ConsAddre
}
return false, nil
})
if err != nil {
return err
}
return nil
}

// GetValidatorMissedBlocks returns array of missed blocks for given validator.
Expand All @@ -209,3 +237,24 @@ func (k Keeper) GetValidatorMissedBlocks(ctx context.Context, addr sdk.ConsAddre

return missedBlocks, err
}

// performConsensusPubKeyUpdate updates cons address to its pub key relation
// Updates signing info, missed blocks (removes old one, and sets new one)
func (k Keeper) performConsensusPubKeyUpdate(ctx context.Context, oldPubKey, newPubKey cryptotypes.PubKey) error {
// Connect new consensus address with PubKey
if err := k.AddrPubkeyRelation.Set(ctx, newPubKey.Address(), newPubKey); err != nil {
return err
}

// Migrate ValidatorSigningInfo from oldPubKey to newPubKey
signingInfo, err := k.ValidatorSigningInfo.Get(ctx, sdk.ConsAddress(oldPubKey.Address()))
if err != nil {
return types.ErrInvalidConsPubKey.Wrap("failed to get signing info for old public key")
}

if err := k.ValidatorSigningInfo.Set(ctx, sdk.ConsAddress(newPubKey.Address()), signingInfo); err != nil {
return err
}

return k.ValidatorSigningInfo.Remove(ctx, sdk.ConsAddress(oldPubKey.Address()))
}
12 changes: 12 additions & 0 deletions x/slashing/keeper/signing_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package keeper_test
import (
"time"

"github.com/golang/mock/gomock"

"cosmossdk.io/x/slashing/testutil"
slashingtypes "cosmossdk.io/x/slashing/types"

Expand Down Expand Up @@ -65,6 +67,8 @@ func (s *KeeperTestSuite) TestValidatorMissedBlockBitmap_SmallWindow() {
params.SignedBlocksWindow = window
require.NoError(keeper.Params.Set(ctx, params))

s.stakingKeeper.EXPECT().ValidatorIdentifier(gomock.Any(), consAddr).Return(consAddr, nil).AnyTimes()

// validator misses all blocks in the window
var valIdxOffset int64
for valIdxOffset < params.SignedBlocksWindow {
Expand Down Expand Up @@ -97,5 +101,13 @@ func (s *KeeperTestSuite) TestValidatorMissedBlockBitmap_SmallWindow() {
missedBlocks, err = keeper.GetValidatorMissedBlocks(ctx, consAddr)
require.NoError(err)
require.Len(missedBlocks, int(params.SignedBlocksWindow)-1)

// if the validator rotated it's key there will be different consKeys and a mapping will be added in the state.
consAddr1 := sdk.ConsAddress(sdk.AccAddress([]byte("addr1_______________")))
s.stakingKeeper.EXPECT().ValidatorIdentifier(gomock.Any(), consAddr1).Return(consAddr, nil).AnyTimes()

missedBlocks, err = keeper.GetValidatorMissedBlocks(ctx, consAddr1)
require.NoError(err)
require.Len(missedBlocks, int(params.SignedBlocksWindow)-1)
}
}
15 changes: 15 additions & 0 deletions x/slashing/testutil/expected_keepers_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions x/slashing/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ var (
ErrNoSigningInfoFound = errors.Register(ModuleName, 8, "no validator signing info found")
ErrValidatorTombstoned = errors.Register(ModuleName, 9, "validator already tombstoned")
ErrInvalidSigner = errors.Register(ModuleName, 10, "expected authority account as only signer for proposal message")
ErrInvalidConsPubKey = errors.Register(ModuleName, 11, "invalid consensus pubkey")
)
4 changes: 4 additions & 0 deletions x/slashing/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ type StakingKeeper interface {

// IsValidatorJailed returns if the validator is jailed.
IsValidatorJailed(ctx context.Context, addr sdk.ConsAddress) (bool, error)

// ValidatorIdentifier maps the new cons key to previous cons key (which is the address before the rotation).
// (that is: newConsKey -> oldConsKey)
ValidatorIdentifier(context.Context, sdk.ConsAddress) (sdk.ConsAddress, error)
}

// StakingHooks event hooks for staking validator object (noalias)
Expand Down
Loading

0 comments on commit 19e66a9

Please sign in to comment.