-
Notifications
You must be signed in to change notification settings - Fork 134
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!: compute partial set #1642
Conversation
@@ -139,19 +139,30 @@ func (k msgServer) SubmitConsumerDoubleVoting(goCtx context.Context, msg *types. | |||
func (k msgServer) OptIn(goCtx context.Context, msg *types.MsgOptIn) (*types.MsgOptInResponse, error) { | |||
ctx := sdk.UnwrapSDKContext(goCtx) | |||
|
|||
valAddress, err := sdk.ConsAddressFromBech32(msg.ProviderAddr) | |||
providerValidatorAddr, err := sdk.ValAddressFromBech32(msg.ProviderAddr) |
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.
Fixed this. Similarly to AssignConsumerKey
we consider that ProviderAddr
in the message is the operator address and use this operator address to get the consensus address that is used later on.
We made a similar change in OptOut
.
x/ccv/provider/keeper/keeper.go
Outdated
@@ -1190,14 +1190,17 @@ func (k Keeper) SetOptedIn( | |||
chainID string, | |||
providerAddr types.ProviderConsAddress, | |||
blockHeight uint64, | |||
power uint64, |
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.
Introduce power
in the opted-in state so we can see if we should send a ValidatorUpdate
down to CometBFT based on whether the power changed since last time it was sent. Only makes sense in an epoch-based setting because in a non-epoch-based setting, we could use the validator updates from the staking
module.
x/ccv/provider/keeper/proposal.go
Outdated
@@ -255,6 +255,12 @@ func (k Keeper) MakeConsumerGenesis( | |||
return false | |||
}) | |||
|
|||
// currentValidators := k.GetOptedIn(ctx, chainID) |
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.
Leaving those commented for now to show how ComputeValidatorUpdates
, etc. are going to be used.
k.DeleteAllOptedIn(ctx, chainID) | ||
for _, val := range nextValidators { | ||
k.SetOptedIn(ctx, chainID, val.ProviderAddr, val.BlockHeight, val.Power) | ||
} |
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.
Seems very inneficient to delete all opten in entries and set the new ones. If in an epoch there will be no changes in the voting powers, this will be a very expensive way to leave the state unchanged.
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.
Is my understanding correct that at this stage, nothing is written to disk? Only if the transactions succeed, at the end all the changes would get written. If that's the case, why's this expensive? Wouldn't this correspond to a simple write in memory?
I agree that most likely the validator set won't change between epochs but probably it's unlikely that powers would remain the same between epochs for all validators, although this depends on how long an epoch is. Even in this case, I would need to loop through all currently opted in validators and compare with the nextValidators
to see if a change took place.
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.
might be premature optimization to worry about this? especially once we have epochs this happens rarely and it's at most ~200 entries being deleted and written per consumer
x/ccv/provider/keeper/keeper.go
Outdated
powerBytes := make([]byte, 8) | ||
binary.BigEndian.PutUint64(powerBytes, power) | ||
|
||
store.Set(types.OptedInKey(chainID, providerAddr), append(blockHeightBytes, powerBytes...)) |
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.
Store OptedInValidator
as a value (define it in the proto files).
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.
Is the recommendation here to define it in proto to simplify our life later on when we implement queries that return opted-in validators?
|
||
// ComputeNextValidators computes the next validator set that is responsible for validating on a consumer chain. | ||
// The returned opted-in validators correspond to the next `currentValidators`. | ||
func (k Keeper) ComputeNextValidators(ctx sdk.Context, |
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.
I feel the logic in this function can be moved to ComputeValidatorUpdates
.
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.
Yes, that's true. I believe that splitting this into two distinct methods makes it easier to read because the ValidateUpdate
s do not have a 1-to-1 correspodence with the next OptedInValidator
s.
But I don't have strong feelings on this and we can combine them as well.
Co-authored-by: Marius Poke <[email protected]>
Co-authored-by: Marius Poke <[email protected]>
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.
Added a few comments. Having both ComputeValidatorUpdates and ComputeNextValidators feels weird to me - maybe we could reconsider the idea of taking diffs between new and old validator sets here
@@ -67,3 +72,152 @@ func (k Keeper) HandleOptOut(ctx sdk.Context, chainID string, providerAddr types | |||
|
|||
return nil | |||
} | |||
|
|||
// ComputeValidatorUpdates computes the validator updates needed to be sent to the consumer chain to capture | |||
// the newly opted-in and opted-out validators, as well as validators that unbonded. |
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.
// the newly opted-in and opted-out validators, as well as validators that unbonded. | |
// the newly opted-in and opted-out validators, as well as validators that started unbonding. |
continue | ||
} | ||
|
||
// if `val` has unbonded, its `GetLastValidatorPower` power returns 0 |
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.
// if `val` has unbonded, its `GetLastValidatorPower` power returns 0 | |
// if `val` has started unbonded, its `GetLastValidatorPower` power returns 0 |
I think there is subtle ambiguity with the current comments, since there are two things related to unbonding,
namely starting unbonding and finishing unbonding.
I think you referred to the start of unbonding here, right?
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.
I wanted to refer to "not bonded" which means neither "unbonding" nor "unbonded." Technically, if a validator was bonded before and now "not bonded" anymore, it is in the "unbonding" phase. Might be easier to just state "not bonded" and that would be consistent with the check !validator.isBonded()
I do in other places.
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.
Yeah, that would be clearer I think
continue | ||
} | ||
|
||
// if the voting power did not change since the last time the validator opted in, do not create an update |
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.
Comment does not seem correct to me - should this read
// if the voting power did not change since the last time the validator opted in, do not create an update | |
// if the voting power did not change between now and the last validator power, no need to add an update for this validator |
?
Also, to better understand, with epochs, GetLastValidatorPower
will need to be replaced with something like GetValidatorPowerAtLastEpoch
, right?
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.
The comment should probably be rephrased to:
// if the voting power is the same as the one the validator had when it opted in, do not create an update
because the voting power could change multiple times during an epoch, but at the end of an epoch, it could be that the validator has again the same voting power it had when it opted in.
Also, to better understand, with epochs, GetLastValidatorPower will need to be replaced with something like GetValidatorPowerAtLastEpoch, right?
Not sure I understand what you mean here. GetLastValidatorPower
returns the most recent validator power and ComputeValidatorUpdates
would be called at the end of every epoch.
Think of the following example. A validator V
opts in at the end of an epoch e
with power p
. If at a later epoch e + X
(X >= 1
), validator V
has still the same power p
, then we should not send a ValidatorUpdate
for V
and this is what this checks here.
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.
Right. GetLastValidatorPower will return the validator power in the last block. But what we need to compare against is the validator power at the last epoch. If epochLength==1, (as is the case right now) these coincide, but if epochLength != 1 this is not the case anymore.
|
||
// ComputeValidatorUpdates computes the validator updates needed to be sent to the consumer chain to capture | ||
// the newly opted-in and opted-out validators, as well as validators that unbonded. | ||
func (k Keeper) ComputeValidatorUpdates(ctx sdk.Context, |
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.
I feel like I am missing context so my judgement may not be accurate, but it feels like one of ComputeValidatorUpdates and ComputeNextValidators is unnecessary?
Wouldn't NextValidators + a diffing method like Jehan suggested a bit ago do the trick?
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.
See my previous comment here.
More specific to the potentiality of a diff method, I guess you could first call ComputeNextValidators
and then call diff(currentValidators, nextValidators)
but this diff
method would be equally complex ComputeValidatorUpdates
and would have a different method signature compared to ComputeNextValidators
. So, I don't see much the advantage to this.
Does that sound reasonable?
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.
Yeah, I agree. I think I would still find the diff method more elegant, but it probably comes down to personal preference, and at the end of the day both are ok.
k.DeleteAllOptedIn(ctx, chainID) | ||
for _, val := range nextValidators { | ||
k.SetOptedIn(ctx, chainID, val.ProviderAddr, val.BlockHeight, val.Power) | ||
} |
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.
might be premature optimization to worry about this? especially once we have epochs this happens rarely and it's at most ~200 entries being deleted and written per consumer
@@ -279,7 +285,14 @@ func (k Keeper) MakeConsumerGenesis( | |||
} | |||
|
|||
// Apply key assignments to the initial valset. | |||
initialUpdatesWithConsumerKeys := k.MustApplyKeyAssignmentToValUpdates(ctx, chainID, initialUpdates) | |||
initialUpdatesWithConsumerKeys := k.MustApplyKeyAssignmentToValUpdates(ctx, chainID, initialUpdates, | |||
func(address types.ProviderConsAddress) bool { |
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.
I think MustApplyKeyAssignmentToValUpdates
will be reworked anyway to work with PSS.
But is it necessary to do this IsOptin
check through a function argument?
Description
Closes: #1606
This PR introduces the logic needed to compute the
ValidatorUpdate
s inComputeValidatorUpdates
.Author Checklist
All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.
I have...
!
to the type prefix if the change is state-machine breakingCHANGELOG.md
Reviewers Checklist
All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.
I have...
!
the type prefix if the change is state-machine breaking