-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
feat: include rotate keys logic in abci #18236
Changes from 82 commits
b6b512a
7c7322a
9aef4f4
37675e6
a9be784
2a713b5
43923ce
d674890
c9f32a7
20f27b7
a51cca3
cacc0d8
e9231e4
5086c5f
202e2ef
3b52332
4843ae1
67e15e0
8ffaa71
c093905
cce3c19
4a63bde
4350e5b
bbcb932
7b2b9bd
6e8eb7f
04f1cbe
55496f9
535d00f
46ae281
12274e9
b19d163
e463855
552e683
2ea32bc
fb3363f
9976530
f0113eb
d4439ec
87aebe8
3a0f4da
89ca04f
6d138b8
49f1745
4183c78
327929f
efb8022
dfbb63a
dd8c72c
0359d96
441f6ee
f41fdf0
c7563f5
44731ce
8a00a07
7ed213f
78c4eda
b236334
a4b06da
cae4de4
1539aaa
e30bece
d4bf237
20bf052
10f6e38
d32c833
05e560a
e27c72a
aee5587
06d7170
d560884
17852c2
9a7a82b
f98c94c
1a2a897
1f04845
82bc8f5
03469a5
a8544f0
ea22a3d
3756d84
91fe4f1
5ed6003
1191858
75eb409
088255b
c221c55
6b55e4b
0f1011b
cc2c0a1
3d5c8b4
1ca3562
cddbc58
f362a93
42979cb
fbd4e41
9961cc5
a8bab2f
159375e
663c8ef
19bf12b
320af37
fcabe10
5e4736a
2eedad0
97c871d
8f219bd
1b1f4b7
c742783
7a4419a
075865b
98862ef
bc53191
693a7eb
bd92cec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,13 @@ func (k Keeper) handleEquivocationEvidence(ctx context.Context, evidence *types. | |
} | ||
|
||
if len(validator.GetOperator()) != 0 { | ||
// get the consAddr again, this is because validator might've rotated it's key. | ||
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. | ||
// | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code correctly checks if the validator has an operator and attempts to get the updated consensus address. However, there is a potential issue with the error handling in line 50. If if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil {
if errors.Is(err, someErrorTypeIndicatingNotFound) {
// Handle not found error specifically
logger.Error(fmt.Sprintf("ignore evidence; expected public key for validator %s not found", consAddr))
return nil
}
// Handle other potential errors
return err
} |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,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" | ||
) | ||
|
||
|
@@ -209,3 +210,44 @@ func (k Keeper) GetValidatorMissedBlocks(ctx context.Context, addr sdk.ConsAddre | |
|
||
return missedBlocks, err | ||
} | ||
|
||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. private? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this would get removed if we went with the map look up right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are using map look-up only for missed blocks, we need to keep this method for migrating the signing-info (which has one entry for a validator). |
||
// 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 | ||
} | ||
|
||
if err := k.ValidatorSigningInfo.Remove(ctx, sdk.ConsAddress(oldPubKey.Address())); err != nil { | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return err | ||
} | ||
|
||
// Migrate ValidatorMissedBlockBitArray from oldPubKey to newPubKey | ||
missedBlocks, err := k.GetValidatorMissedBlocks(ctx, sdk.ConsAddress(oldPubKey.Address())) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := k.DeleteMissedBlockBitmap(ctx, sdk.ConsAddress(oldPubKey.Address())); err != nil { | ||
return err | ||
} | ||
|
||
for _, missed := range missedBlocks { | ||
if err := k.SetMissedBlockBitmapValue(ctx, sdk.ConsAddress(newPubKey.Address()), missed.Index, missed.Missed); err != nil { | ||
return err | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if there's a way we can just update the existing record address? Or is it indexed by address? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, in slashing module most of the places consAddress is being used in state for keys. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems expensive, is there anyway to make it lazy? im worried for chains like neutron a dos vector could be get in the validator set, miss 70k+ blocks migrate and watch the chain halt for the migration. Maybe we dont need to migrate this at all? Staking forever keeps a map of old key to new key for slashing?
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return nil | ||
} | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,10 +6,14 @@ import ( | |
"time" | ||
|
||
"cosmossdk.io/collections" | ||
"cosmossdk.io/collections/indexes" | ||
errorsmod "cosmossdk.io/errors" | ||
"cosmossdk.io/x/staking/types" | ||
|
||
codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
) | ||
alexanderbez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// maxRotations is the value of max rotations can be made in unbonding period for a validator. | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
@@ -47,6 +51,54 @@ func (k Keeper) setConsPubKeyRotationHistory( | |
return k.setConsKeyQueue(ctx, queueTime, valAddr) | ||
} | ||
|
||
// This method gets called from the `ApplyAndReturnValidatorSetUpdates`(from endblocker) method. | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// | ||
// This method makes the relative state changes to update the keys, | ||
// also maintains a map with old to new conskey rotation which is needed to retrieve the old conskey. | ||
// And also triggers the hook to make changes required in slashing and distribution modules. | ||
func (k Keeper) updateToNewPubkey(ctx context.Context, val types.Validator, oldPubKey, newPubKey *codectypes.Any, fee sdk.Coin) error { | ||
consAddr, err := val.GetConsAddr() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := k.ValidatorByConsensusAddress.Remove(ctx, consAddr); err != nil { | ||
return err | ||
} | ||
|
||
if err := k.DeleteValidatorByPowerIndex(ctx, val); err != nil { | ||
return err | ||
} | ||
|
||
val.ConsensusPubkey = newPubKey | ||
if err := k.SetValidator(ctx, val); err != nil { | ||
return err | ||
} | ||
if err := k.SetValidatorByConsAddr(ctx, val); err != nil { | ||
return err | ||
} | ||
if err := k.SetValidatorByPowerIndex(ctx, val); err != nil { | ||
return err | ||
} | ||
|
||
oldPk, ok := oldPubKey.GetCachedValue().(cryptotypes.PubKey) | ||
if !ok { | ||
return errorsmod.Wrapf(sdkerrors.ErrInvalidType, "Expecting cryptotypes.PubKey, got %T", oldPk) | ||
} | ||
|
||
newPk, ok := newPubKey.GetCachedValue().(cryptotypes.PubKey) | ||
if !ok { | ||
return errorsmod.Wrapf(sdkerrors.ErrInvalidType, "Expecting cryptotypes.PubKey, got %T", newPk) | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// Sets a map to newly rotated consensus key with old consensus key | ||
if err := k.RotatedConsKeyMapIndex.Set(ctx, oldPk.Address(), newPk.Address()); err != nil { | ||
return err | ||
} | ||
|
||
return k.Hooks().AfterConsensusPubKeyUpdate(ctx, oldPk, newPk, fee) | ||
} | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// exceedsMaxRotations returns true if the key rotations exceed the limit, currently we are limiting one rotation for unbonding period. | ||
func (k Keeper) exceedsMaxRotations(ctx context.Context, valAddr sdk.ValAddress) error { | ||
count := 0 | ||
|
@@ -90,3 +142,61 @@ func bytesSliceExists(sliceList [][]byte, targetBytes []byte) bool { | |
} | ||
return false | ||
} | ||
|
||
// UpdateAllMaturedConsKeyRotatedKeys udpates all the matured key rotations. | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
func (k Keeper) UpdateAllMaturedConsKeyRotatedKeys(ctx sdk.Context, maturedTime time.Time) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds like this should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we make this private as well. Want to avoid another module accidentally calling this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are using this method in the tests |
||
maturedRotatedValAddrs, err := k.GetAllMaturedRotatedKeys(ctx, maturedTime) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, valAddr := range maturedRotatedValAddrs { | ||
err := k.deleteConsKeyIndexKey(ctx, valAddr, maturedTime) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
||
// deleteConsKeyIndexKey deletes the key which is formed with the given valAddr, time. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this comment is wrong as it's not correctly describing what happens inside the function. The function deletes all ValidatorConsensusKeyRotationRecordIndexKey for a valAddr that time is <= to the passed in |
||
func (k Keeper) deleteConsKeyIndexKey(ctx sdk.Context, valAddr sdk.ValAddress, ts time.Time) error { | ||
rng := new(collections.Range[collections.Pair[[]byte, time.Time]]). | ||
StartInclusive(collections.Join(valAddr.Bytes(), time.Time{})). | ||
EndInclusive(collections.Join(valAddr.Bytes(), ts)) | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return k.ValidatorConsensusKeyRotationRecordIndexKey.Walk(ctx, rng, func(key collections.Pair[[]byte, time.Time]) (stop bool, err error) { | ||
return false, k.ValidatorConsensusKeyRotationRecordIndexKey.Remove(ctx, key) | ||
}) | ||
Comment on lines
+203
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Comment on lines
+201
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
} | ||
|
||
// GetAllMaturedRotatedKeys returns all matured valaddresses . | ||
func (k Keeper) GetAllMaturedRotatedKeys(ctx sdk.Context, matureTime time.Time) ([][]byte, error) { | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
valAddrs := [][]byte{} | ||
|
||
// get an iterator for all timeslices from time 0 until the current HeaderInfo time | ||
rng := new(collections.Range[time.Time]).EndInclusive(matureTime) | ||
err := k.ValidatorConsensusKeyRotationRecordQueue.Walk(ctx, rng, func(key time.Time, value types.ValAddrsOfRotatedConsKeys) (stop bool, err error) { | ||
valAddrs = append(valAddrs, value.Addresses...) | ||
return false, k.ValidatorConsensusKeyRotationRecordQueue.Remove(ctx, key) | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return valAddrs, nil | ||
} | ||
|
||
// GetBlockConsPubKeyRotationHistory iterator over the rotation history for the given height. | ||
func (k Keeper) GetBlockConsPubKeyRotationHistory(ctx context.Context) ([]types.ConsPubKeyRotationHistory, error) { | ||
sdkCtx := sdk.UnwrapSDKContext(ctx) | ||
|
||
iterator, err := k.RotationHistory.Indexes.Block.MatchExact(ctx, uint64(sdkCtx.BlockHeight())) | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
return nil, err | ||
} | ||
defer iterator.Close() | ||
|
||
return indexes.CollectValues(ctx, k.RotationHistory, iterator) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package keeper_test | ||
|
||
import ( | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
func (s *KeeperTestSuite) TestHookAfterConsensusPubKeyUpdate() { | ||
stKeeper := s.stakingKeeper | ||
ctx := s.ctx | ||
require := s.Require() | ||
|
||
rotationFee := sdk.NewInt64Coin("stake", 1000000) | ||
err := stKeeper.Hooks().AfterConsensusPubKeyUpdate(ctx, PKs[0], PKs[1], rotationFee) | ||
require.NoError(err) | ||
} | ||
Comment on lines
+1
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test function // Additional assertions should be added here to check for the expected state changes.
// For example, if the community pool is supposed to be updated, assert that it has the expected value.
// If slashing conditions are to be checked, assert that they are handled correctly. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ import ( | |
"cosmossdk.io/math" | ||
"cosmossdk.io/x/staking/types" | ||
|
||
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" | ||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
|
@@ -112,6 +114,11 @@ func (k Keeper) BlockValidatorUpdates(ctx context.Context) ([]abci.ValidatorUpda | |
) | ||
} | ||
|
||
err = k.UpdateAllMaturedConsKeyRotatedKeys(sdkCtx, sdkCtx.HeaderInfo().Time) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return validatorUpdates, nil | ||
} | ||
|
||
|
@@ -235,6 +242,54 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx context.Context) (updates | |
updates = append(updates, validator.ABCIValidatorUpdateZero()) | ||
} | ||
|
||
// ApplyAndReturnValidatorSetUpdates checks if there is ConsPubKeyRotationHistory | ||
// with ConsPubKeyRotationHistory.RotatedHeight == ctx.BlockHeight() and if so, generates 2 ValidatorUpdate, | ||
// one for a remove validator and one for create new validator | ||
historyObjects, err := k.GetBlockConsPubKeyRotationHistory(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, history := range historyObjects { | ||
valAddr := history.OperatorAddress | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
validator := k.mustGetValidator(ctx, valAddr) | ||
|
||
oldPk := history.OldConsPubkey.GetCachedValue().(cryptotypes.PubKey) | ||
oldTmPk, err := cryptocodec.ToCmtProtoPublicKey(oldPk) | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
newPk := history.NewConsPubkey.GetCachedValue().(cryptotypes.PubKey) | ||
newTmPk, err := cryptocodec.ToCmtProtoPublicKey(newPk) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if !(validator.Jailed || validator.Status != types.Bonded) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment here as this kind of checks can be confusing (and I think it might be wrong). This means that a validator update will be added/modified if the validator is not jailed (regardless of the bonding status); but if the validator is jailed only if the validator is bonded. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You cannot be jailed and bonded. As soon as you're jailed, you're put into the unbonding statue/queue. Agree with @facundomedica here. Let's (A) avoid There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
updates = append(updates, abci.ValidatorUpdate{ | ||
PubKey: oldTmPk, | ||
Power: 0, | ||
}) | ||
|
||
updates = append(updates, abci.ValidatorUpdate{ | ||
PubKey: newTmPk, | ||
Power: validator.ConsensusPower(powerReduction), | ||
}) | ||
|
||
if err := k.updateToNewPubkey(ctx, validator, history.OldConsPubkey, history.NewConsPubkey, history.Fee); err != nil { | ||
return nil, err | ||
} | ||
} | ||
} | ||
|
||
// TODO: at previousVotes Iteration logic of AllocateTokens, previousVote using OldConsPubKey | ||
// match up with ConsPubKeyRotationHistory, and replace validator for token allocation | ||
atheeshp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Update the pools based on the recent updates in the validator set: | ||
// - The tokens from the non-bonded candidates that enter the new validator set need to be transferred | ||
// to the Bonded pool. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain this a little bit more? Getting the consAddr this way doesn't get it from the store again if that's what you meant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@atheeshp ping on this one, I think you added more comments but I'm still confused 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an evidence for a validator's behaviour can be submitted till the unbonding period window.
When a validator rotates it's key that will update the
validator.ConsAddr
to the new address in the validator details immediately. But if the evidence submitted (within the unbonding period of the rotation) with old cons address we cannot find the required details of the validator in the slashing state because of the key rotation. To find them we need to get the validator's rotated consAddr which will be present atvalidator.consAddr