-
Notifications
You must be signed in to change notification settings - Fork 42
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(ADR-024): bugs in voting power assignment #38
Changes from 5 commits
2b1a90a
b74b8b7
0588060
0ce82b9
6e7436f
f3099ee
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 |
---|---|---|
|
@@ -17,4 +17,4 @@ | |
"title": "any title", | ||
"summary": "any summary", | ||
"expedited": false | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,7 @@ func (k Keeper) UpdatePowerDist(ctx context.Context) { | |
if len(events) == 0 { | ||
if dc != nil { | ||
// map everything in prev height to this height | ||
k.recordVotingPowerAndCache(ctx, dc) | ||
k.recordVotingPowerAndCache(ctx, dc, nil, maxActiveFps) | ||
} | ||
return | ||
} | ||
|
@@ -56,31 +56,61 @@ func (k Keeper) UpdatePowerDist(ctx context.Context) { | |
// to construct the new distribution | ||
newDc := k.ProcessAllPowerDistUpdateEvents(ctx, dc, events, maxActiveFps) | ||
|
||
// find newly bonded finality providers and execute the hooks | ||
newBondedFinalityProviders := newDc.FindNewActiveFinalityProviders(dc) | ||
for _, fp := range newBondedFinalityProviders { | ||
if err := k.hooks.AfterFinalityProviderActivated(ctx, fp.BtcPk); err != nil { | ||
panic(fmt.Errorf("failed to execute after finality provider %s bonded", fp.BtcPk.MarshalHex())) | ||
} | ||
} | ||
|
||
// record voting power and cache for this height | ||
k.recordVotingPowerAndCache(ctx, newDc) | ||
k.recordVotingPowerAndCache(ctx, dc, newDc, maxActiveFps) | ||
// record metrics | ||
k.recordMetrics(newDc) | ||
} | ||
|
||
func (k Keeper) recordVotingPowerAndCache(ctx context.Context, dc *types.VotingPowerDistCache) { | ||
func (k Keeper) recordVotingPowerAndCache(ctx context.Context, prevDc, newDc *types.VotingPowerDistCache, maxActiveFps uint32) { | ||
if prevDc == nil { | ||
panic("the previous voting power distribution cache cannot be nil") | ||
} | ||
|
||
// deep copy the previous dist cache if the new dist cache is nil | ||
if newDc == nil { | ||
newDc = types.NewVotingPowerDistCache() | ||
newDc.TotalVotingPower = prevDc.TotalVotingPower | ||
newDc.NumActiveFps = prevDc.NumActiveFps | ||
newFps := make([]*types.FinalityProviderDistInfo, len(prevDc.FinalityProviders)) | ||
for i, prevFp := range prevDc.FinalityProviders { | ||
newFp := *prevFp | ||
newFps[i] = &newFp | ||
} | ||
newDc.FinalityProviders = newFps | ||
} | ||
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. Why do we need a deep copy here? It seems that 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. if we don't do deep copy, the change at Line 90 will change the |
||
|
||
babylonTipHeight := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) | ||
|
||
// label fps with whether it has timestamped pub rand | ||
for _, fp := range newDc.FinalityProviders { | ||
// TODO calling HasTimestampedPubRand potentially iterates | ||
// all the pub rand committed by the fp, which might slow down | ||
// the process, need optimization | ||
fp.IsTimestamped = k.FinalityKeeper.HasTimestampedPubRand(ctx, fp.BtcPk, babylonTipHeight) | ||
} | ||
|
||
// filter out the top N finality providers and their total voting power, and | ||
// record them in the new cache | ||
newDc.ApplyActiveFinalityProviders(maxActiveFps) | ||
|
||
// set voting power table for this height | ||
for i := uint32(0); i < dc.NumActiveFps; i++ { | ||
fp := dc.FinalityProviders[i] | ||
for i := uint32(0); i < newDc.NumActiveFps; i++ { | ||
fp := newDc.FinalityProviders[i] | ||
k.SetVotingPower(ctx, fp.BtcPk.MustMarshal(), babylonTipHeight, fp.TotalVotingPower) | ||
} | ||
|
||
// find newly activated finality providers and execute the hooks | ||
newActivatedFinalityProviders := newDc.FindNewActiveFinalityProviders(prevDc) | ||
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. for the case where 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 thought the same but turned out there is an issue if we don't take care of this (took me a long time to realize this...). So, even if no new events, some fps could have newly timestamped pub rand and become a new activated fp. For example, we only have one fp and one delegation to it at height 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. got it, thanks for explaining! |
||
for _, fp := range newActivatedFinalityProviders { | ||
if err := k.hooks.AfterFinalityProviderActivated(ctx, fp.BtcPk); err != nil { | ||
panic(fmt.Errorf("failed to execute after finality provider %s activated", fp.BtcPk.MarshalHex())) | ||
} | ||
k.Logger(sdk.UnwrapSDKContext(ctx)).Info("a new finality provider is activated", "pk", fp.BtcPk.MarshalHex()) | ||
} | ||
|
||
// set the voting power distribution cache of the current height | ||
k.setVotingPowerDistCache(ctx, babylonTipHeight, dc) | ||
k.setVotingPowerDistCache(ctx, babylonTipHeight, newDc) | ||
} | ||
|
||
func (k Keeper) recordMetrics(dc *types.VotingPowerDistCache) { | ||
|
@@ -112,7 +142,6 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents( | |
events []*types.EventPowerDistUpdate, | ||
maxActiveFps uint32, | ||
) *types.VotingPowerDistCache { | ||
height := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height) | ||
// a map where key is finality provider's BTC PK hex and value is a list | ||
// of BTC delegations that newly become active under this provider | ||
activeBTCDels := map[string][]*types.BTCDelegation{} | ||
|
@@ -236,18 +265,6 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents( | |
} | ||
} | ||
|
||
// label fps that does not have timestamped pub rand | ||
for _, fp := range newDc.FinalityProviders { | ||
// TODO calling HasTimestampedPubRand potentially iterates | ||
// all the pub rand committed by the fp, which might slow down | ||
// the process, need optimization | ||
fp.IsTimestamped = k.FinalityKeeper.HasTimestampedPubRand(ctx, fp.BtcPk, height) | ||
} | ||
|
||
// filter out the top N finality providers and their total voting power, and | ||
// record them in the new cache | ||
newDc.ApplyActiveFinalityProviders(maxActiveFps) | ||
|
||
return newDc | ||
} | ||
|
||
|
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 it would be cleaner to move this copying process to above the call to
recordVotingPowerAndCache
in case when there is no new events i.e somewehre under// map everything in prev height to this height
comment.Otherwise this
nil
check just generate questions like:prevDc
to be non-nil ?etc.
Where if this is handled on call site we can add comments along the line
that becouse there is no new power changing events, the new dc is just copy of the old one and the deep copy is need due to recordVotingPowerAndCache modifying provided arguments.
(or something like that)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.
Agreed allowing
nil
parameter is hacky. Fixed in f3099ee