Slow Misty Iguana
Medium
totalPower's inclusion of the Jailed provider's voting power may result in a vote never reaching a 2/3 quorum
totalPower's inclusion of the Jailed provider's voting power may result in a vote never reaching a 2/3 quorum
totalPower's inclusion of the Jailed provider's voting power
There are providers being Jailed
result in a vote never reaching a 2/3 quorum , blocks can never enter Finalize state and chains cannot work.
TheAddFinalitySig
function is used to add the vote, but if the provider is in theJailed
state, this function returns an error.
Therefore, the Jailed 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.IsJailed() {
return nil, bstypes.ErrFpAlreadyJailed.Wrapf("finality provider public key: %s", fpPK.MarshalHex())
}
.......
}
But the problem is thattotalPower
is calculated based onVotingPowerTable
and does not subtract Jailed 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
}
AddFinalitySig
can vote on multiple blocks, assuming that two blocks are voting at the same time,
The current block height is n+2, and the VotingPowerTable
now contains the voting power of both blocks n and n+1.
provider votes for block n, at which point the provider is Jailed and his vote for block n is valid(block n is fine),
But for block n+1 this provider cannot vote, the problem is that totalVotingPower
in block n+1 has not subtracted the VotingPower
of this provider.
If the provider has more VotingPower
or if more providers are Jailed, the vote will never reach a 2/3 quorum.
It is very likely that the system will put lazy fp's in jail, and there will be a certain period of time, if there are many fp's in prison in the same period, if the total voting power is more than 2/3, the vote in that period may never pass, because the fp's cannot vote.
x/finality/abci.go/EndBlocker -> HandleLiveness -> HandleFinalityProviderLiveness
func (k Keeper) HandleFinalityProviderLiveness(ctx context.Context, fpPk *types.BIP340PubKey, missed bool, height int64) error {
......
updated, signInfo, err := k.updateSigningInfo(ctx, fpPk, missed, height)
if err != nil {
return err
}
signedBlocksWindow := params.SignedBlocksWindow
minSignedPerWindow := params.MinSignedPerWindowInt()
if missed {
k.Logger(sdkCtx).Debug(
"absent finality provider",
"height", height,
"public_key", fpPk.MarshalHex(),
"missed", signInfo.MissedBlocksCounter,
"threshold", minSignedPerWindow,
)
}
minHeight := signInfo.StartHeight + signedBlocksWindow
maxMissed := signedBlocksWindow - minSignedPerWindow
// if the number of missed block reaches the threshold within the sliding window
// jail the finality provider
if height > minHeight && signInfo.MissedBlocksCounter > maxMissed {
updated = true
if err := k.jailSluggishFinalityProvider(ctx, fpPk); err != nil {
return fmt.Errorf("failed to jail sluggish finality provider %s: %w", fpPk.MarshalHex(), err)
}
-> signInfo.JailedUntil = sdkCtx.HeaderInfo().Time.Add(params.JailDuration)
// we need to reset the counter & bitmap so that the finality provider won't be
// immediately jailed after unjailing.
signInfo.MissedBlocksCounter = 0
if err := k.DeleteMissedBlockBitmap(ctx, fpPk); err != nil {
return fmt.Errorf("failed to remove the missed block bit map: %w", err)
}
k.Logger(sdkCtx).Info(
"finality provider is jailed",
"height", height,
"public_key", fpPk.MarshalHex(),
)
}
// Set the updated signing info
if updated {
-> return k.FinalityProviderSigningTracker.Set(ctx, fpPk.MustMarshal(), *signInfo)
}
return nil
}
totalPower = totalPower - JailedVotingPower