Slow Misty Iguana
Medium
gov may set total voting power to 0, causing the chain to halt, and it cannot be restored.
total voting power can be set to 0
All fp's in some block are not voted
- All fp's in a block are not voted
- GOV calls
ResumeFinalityProposal
to restore this block. - The total voting power is set to 0, the block can never enter the Finalized state and cannot be restored.
halt of chain
The ResumeFinalityProposal
is used to restore the abnormal block, the fp who did not vote gets jailed, then sets their voting power to 0, and calls the TallyBlocks
function again:
func (k Keeper) HandleResumeFinalityProposal(ctx sdk.Context, fpPksHex []string, haltingHeight uint32) error {
......
//halt 3 current 5 6
for h := uint64(haltingHeight); h <= uint64(currentHeight); h++ {
distCache := k.GetVotingPowerDistCache(ctx, h)
activeFps := distCache.GetActiveFinalityProviderSet()
for _, fpToJail := range fpPks {
if fp, exists := activeFps[fpToJail.MarshalHex()]; exists {
fp.IsJailed = true
-> k.SetVotingPower(ctx, fpToJail.MustMarshal(), h, 0)
}
}
distCache.ApplyActiveFinalityProviders(params.MaxActiveFinalityProviders)
// set the voting power distribution cache of the current height
k.SetVotingPowerDistCache(ctx, h, distCache)
}
-> k.TallyBlocks(ctx)
return nil
}
https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/finality/keeper/gov.go#L78
The problem is that if all fp's do not vote, their Voting power will be removed altogether, which will result in total voting power = 0,
if totalVotingPower == 0, causes the tally
function to return false, so blocks will not enter Finalized
, which is unrecoverable.
func (k Keeper) TallyBlocks(ctx context.Context) {
......
switch {
case fpSet != nil && !ib.Finalized:
// has finality providers, non-finalised: tally and try to finalise the block
voterBTCPKs := k.GetVoters(ctx, ib.Height)
-> if tally(fpSet, voterBTCPKs) {
// if this block gets >2/3 votes, finalise it
k.finalizeBlock(ctx, ib)
} else {
// if not, then this block and all subsequent blocks should not be finalised
// thus, we need to break here
break finalizationLoop
}
......
}
}
func tally(fpSet map[string]uint64, voterBTCPKs map[string]struct{}) bool {
totalPower := uint64(0)
votedPower := uint64(0)
for pkStr, power := range fpSet {
totalPower += power
if _, ok := voterBTCPKs[pkStr]; ok {
votedPower += power
}
}
-> return votedPower*3 > totalPower*2
}
func (k Keeper) HandleResumeFinalityProposal(ctx sdk.Context, fpPksHex []string, haltingHeight uint32) error {
......
for h := uint64(haltingHeight); h <= uint64(currentHeight); h++ {
distCache := k.GetVotingPowerDistCache(ctx, h)
activeFps := distCache.GetActiveFinalityProviderSet()
for _, fpToJail := range fpPks {
if fp, exists := activeFps[fpToJail.MarshalHex()]; exists {
fp.IsJailed = true
k.SetVotingPower(ctx, fpToJail.MustMarshal(), h, 0)
}
}
distCache.ApplyActiveFinalityProviders(params.MaxActiveFinalityProviders) //100 101
// set the voting power distribution cache of the current height
k.SetVotingPowerDistCache(ctx, h, distCache)
}
+ if totalVotingPower == 0 {
+ return ERROR
+ }
}