Skip to content

Latest commit

 

History

History
111 lines (77 loc) · 4.37 KB

017.md

File metadata and controls

111 lines (77 loc) · 4.37 KB

Petite Fleece Cheetah

High

The finality provider is slashed even if he just voted for the fork without voting for the canonical block

Summary

The problem is that the current system slashes FPs that voted twice - both for the fork and for the canonical block.

Root Cause

The root cause lies in a fact that the protocol immediately sets the evidence if the voting was performed for the fork - and in the next lines of the AddFinalitySig() function slashes the finality provider even if the vote was just for the fork (without signing two blocks).

Internal Pre-conditions

External Pre-conditions

A FP adds a finality sig.

Attack Path

A FP adds a finality sig and gets unfairly slashed.

Impact

Finality providers get slashed even if they voted for a fork.

PoC

The current design of the system allows voting for the block but without voting for the fork at the same time:

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/finality/README.md

If the voted block's AppHash is different from the canonical block at the same height known by the Babylon node, then this means the finality provider has voted for a fork. Babylon node buffers this finality vote to the evidence storage. If the finality provider has also voted for the block at the same height, then this finality provider is slashed, i.e., its voting power is removed, equivocation evidence is recorded, and a slashing event is emitted.

If the voted block's AppHash is same as that of the canonical block at the same height, then this means the finality provider has voted for the canonical block, and the Babylon node will store this finality vote to the finality vote storage. If the finality provider has also voted for a fork block at the same height, then this finality provider will be slashed.

Consider the current functionality:

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

if !bytes.Equal(indexedBlock.AppHash, req.BlockAppHash) {
		// the finality provider votes for a fork!

		// construct evidence
		evidence := &types.Evidence{
			FpBtcPk:              req.FpBtcPk,
			BlockHeight:          req.BlockHeight,
			PubRand:              req.PubRand,
			CanonicalAppHash:     indexedBlock.AppHash,
			CanonicalFinalitySig: nil,
			ForkAppHash:          req.BlockAppHash,
			ForkFinalitySig:      req.FinalitySig,
		}

		// if this finality provider has also signed canonical block, slash it
		canonicalSig, err := ms.GetSig(ctx, req.BlockHeight, fpPK)
		if err == nil {
			// set canonial sig
			evidence.CanonicalFinalitySig = canonicalSig
			// slash this finality provider, including setting its voting power to
			// zero, extracting its BTC SK, and emit an event
			ms.slashFinalityProvider(ctx, req.FpBtcPk, evidence)
		}

		// save evidence
		ms.SetEvidence(ctx, evidence)

		// NOTE: we should NOT return error here, otherwise the state change triggered in this tx
		// (including the evidence) will be rolled back
		return &types.MsgAddFinalitySigResponse{}, nil
	}

First, the function addFinalitySig() sets the evidence. And then couple lines later:

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

	// if this finality provider has signed the canonical block before,
	// slash it via extracting its secret key, and emit an event
	if ms.HasEvidence(ctx, req.FpBtcPk, req.BlockHeight) {
		// the finality provider has voted for a fork before!
		// If this evidence is at the same height as this signature, slash this finality provider

		// get evidence
		evidence, err := ms.GetEvidence(ctx, req.FpBtcPk, req.BlockHeight)
		if err != nil {
			panic(fmt.Errorf("failed to get evidence despite HasEvidence returns true"))
		}

		// set canonical sig to this evidence
		evidence.CanonicalFinalitySig = req.FinalitySig
		ms.SetEvidence(ctx, evidence)

		// slash this finality provider, including setting its voting power to
		// zero, extracting its BTC SK, and emit an event
		ms.slashFinalityProvider(ctx, req.FpBtcPk, evidence)
	}

The problem is that it may be the first vote for the fork without previously voting for the canonical block and the FP will still get slashed as the evidence is set before.

Mitigation

Change the implementation so the finality providers are not slashed upon the voting for the fork (without votiing for the canonical block as well).