Skip to content

Latest commit

 

History

History
94 lines (71 loc) · 3.07 KB

024.md

File metadata and controls

94 lines (71 loc) · 3.07 KB

Slow Misty Iguana

Medium

totalPower's inclusion of the slashed provider's voting power may result in a vote never reaching a 2/3 quorum

Summary

totalPower's inclusion of the slashed provider's voting power may result in a vote never reaching a 2/3 quorum

Root Cause

totalPower's inclusion of the slashed provider's voting power

Internal Pre-conditions

There are providers being slashed

External Pre-conditions

Attack Path

Impact

result in a vote never reaching a 2/3 quorum , blocks can never enter Finalize state and chains cannot work.

PoC

TheAddFinalitySig function is used to add the vote, but if the provider is in theslashed state, this function returns an error. Therefore, the slashed provider cannot vote.

// AddFinalitySig adds a new vote to a given block
func (ms msgServer) AddFinalitySig(goCtx context.Context, req *types.MsgAddFinalitySig) (*types.MsgAddFinalitySigResponse, error) {
	.......
	if fp.IsSlashed() {
		return nil, bstypes.ErrFpAlreadySlashed.Wrapf("finality provider public key: %s", fpPK.MarshalHex())
	}

	if fp.IsJailed() {
		return nil, bstypes.ErrFpAlreadyJailed.Wrapf("finality provider public key: %s", fpPK.MarshalHex())
	}
        .......
}

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/finality/keeper/msg_server.go#L112-L118

But the problem is thattotalPower is calculated based onVotingPowerTable and does not subtract Slashed VotingPower:

func (k Keeper) TallyBlocks(ctx context.Context) {
......
	// get the finality provider set of this block
->	fpSet := k.GetVotingPowerTable(ctx, ib.Height)

	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#L50

AddFinalitySig can vote on multiple blocks, assuming that two blocks are voting at the same time, The current block height is n+2, and theVotingPowerTable now contains the voting power of both blocks n and n+1. provider votes for block n, at which point the provider is slashed and his vote for block n is valid(block n is fine), But for block n+1 this provider cannot vote, the problem is thattotalVotingPower in block n+1 has not subtracted theVotingPower of this provider. If the provider has moreVotingPower or if more providers are slashed, the vote will never reach a 2/3 quorum.

Mitigation

totalPower = totalPower - slashedVotingPower