Skip to content

Latest commit

 

History

History
124 lines (95 loc) · 3.57 KB

041.md

File metadata and controls

124 lines (95 loc) · 3.57 KB

Slow Misty Iguana

Medium

gov may set total voting power to 0, causing the chain to halt, and it cannot be restored.

Summary

gov may set total voting power to 0, causing the chain to halt, and it cannot be restored.

Root Cause

total voting power can be set to 0

Internal Pre-conditions

All fp's in some block are not voted

External Pre-conditions

Attack Path

  1. All fp's in a block are not voted
  2. GOV calls ResumeFinalityProposal to restore this block.
  3. The total voting power is set to 0, the block can never enter the Finalized state and cannot be restored.

Impact

halt of chain

PoC

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
}

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/finality/keeper/tallying.go#L103

Mitigation

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
+    }
}