Skip to content

Latest commit

 

History

History
81 lines (61 loc) · 2.69 KB

060.md

File metadata and controls

81 lines (61 loc) · 2.69 KB

Slow Misty Iguana

Medium

Unfair reward distribution v2

Summary

HandleRewarding's loop skips unfinalized blocks, but the reward is not redistributed after the skipped block is Finalized.

Root Cause

The unfinalized block in theHandleRewarding function is skipped and no further processing is performed.

Internal Pre-conditions

  1. Multiple blocks are voting at the same time.
  2. from current block to current - FinalitySigTimeout are in voting state (unfinalized),

External Pre-conditions

Attack Path

  1. Some FPs did not vote in many blocks, resulting in current block to current - FinalitySigTimeout are in voting state (unfinalized).
  2. gov removes the unvoted fp through ResumeFinalityProposal and reply the block.
  3. FP that has already voted cannot get rewards.

Impact

In some blocks, fp cannot get reward, which is unfair reward calculation.

PoC

HandleRewarding is used to distribute reward, called when each block Endblock:

x/finality/abci.go/EndBlocker() -> HandleRewarding()

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/finality/keeper/rewarding.go#L33-L35

// HandleRewarding calls the reward to stakers if the block is finalized
func (k Keeper) HandleRewarding(ctx context.Context, targetHeight int64) {
        ......
	// targetHeight = heightToExamine = HeaderInfo().Height - k.GetParams(ctx).FinalitySigTimeout
	for height := nextHeightToReward; height <= uint64(targetHeight); height++ {
		block, err := k.GetBlock(ctx, height)
		if err != nil {
			panic(err)
		}
->		if !block.Finalized {
->			continue
		}
		k.rewardBTCStaking(ctx, height)
		nextHeightToReward = height + 1
	}

	if nextHeightToReward != copiedNextHeightToReward {
		k.SetNextHeightToReward(ctx, nextHeightToReward)
	}
}

The problem is that unfinalized blocks are skipped in this loop. After thecontinue statement,nextHeightToRewardwill be set after this block and the reward for this block will no longer be distributed.

This will happen if from current block to current - FinalitySigTimeout are in voting state (unfinalized), This happens when many FPs do not vote in many blocks, and blocks that are not finalized will appear during HandleRewarding.

At this time, gov may need to use ResumeFinalityProposal to reply to the block and remove the unvoted fp, but the fp that has already voted will not receive any reward because HandleRewarding has already been executed.

Mitigation

	for height := nextHeightToReward; height <= uint64(targetHeight); height++ {
		block, err := k.GetBlock(ctx, height)
		if err != nil {
			panic(err)
		}
		if !block.Finalized {
-			continue
+			break
		}
		k.rewardBTCStaking(ctx, height)
		nextHeightToReward = height + 1
	}