Slow Misty Iguana
High
If fp is not in the VotingPowerDistCache
, unbondedSats
cannot be removed.
fp exists in unbondedSatsByFpBtcPk
, but not in dc.FinalityProviders
(VotingPowerDistCache).
- The staker runs the unstake operation. The
BTCDelegationStatus_UNBONDED
event is sent and processed. - The fp corresponding to unstake is not in the
VotingPowerDistCache
.
- Malicious fp(attacker) increases their stake amount
- The attacker reduces the amount staked or gets jailed, not in the ActiveFps list (not in the VotingPowerDistCache).
- The attacker unstake(), and
fp.TotalBondedSat
did not decrease. - The attacker restake to increase TotalBondedSat.
- By repeating the operation, the attacker can add any number of
TotalBondedSat
Steal funds or vote manipulation
When processing UNBONDED
events, the ProcessAllPowerDistUpdateEvents
function now puts the required unbonded quantity into the unbondedSatsByFpBtcPk
array, using the address of fp as the key:
func (k Keeper) processPowerDistUpdateEventUnbond(
ctx context.Context,
cacheFpByBtcPkHex map[string]*types.FinalityProvider,
btcDel *types.BTCDelegation,
unbondedSatsByFpBtcPk map[string][]uint64,
) {
for _, fpBTCPK := range btcDel.FpBtcPkList {
fpBTCPKHex := fpBTCPK.MarshalHex()
-> unbondedSatsByFpBtcPk[fpBTCPKHex] = append(unbondedSatsByFpBtcPk[fpBTCPKHex], btcDel.TotalSat)
}
k.processRewardTracker(ctx, cacheFpByBtcPkHex, btcDel, func(fp, del sdk.AccAddress, sats uint64) {
k.MustProcessBtcDelegationUnbonded(ctx, fp, del, sats)
})
}
Then iterate through the unbondedSatsByFpBtcPk
array and reduce fp.TotalBondedSat
:
if fpUnbondedSats, ok := unbondedSatsByFpBtcPk[fpBTCPKHex]; ok {
// handle unbonded delegations for this finality provider
for _, unbodedSats := range fpUnbondedSats {
-> // RemoveBondedSats -> v.TotalBondedSat -= sats
-> fp.RemoveBondedSats(unbodedSats)
}
// remove the finality provider entry in fpUnbondedSats map, so that
// after the for loop the rest entries in fpUnbondedSats belongs to new
// finality providers that might have btc delegations entries
// that activated and unbonded in the same slice of events
delete(unbondedSatsByFpBtcPk, fpBTCPKHex)
}
But the problem is that the function does not traverse unbondedSatsByFpBtcPk
separately, but traverses it again when traversing dc.FinalityProviders
and fpActiveBtcPkHexList
:
If the key of fp exists in unbondedSatsByFpBtcPk
, but does not exist in dc.FinalityProviders
and fpActiveBtcPkHexList
, the fp will not be processed and fp.TotalBondedSat will not be reduced.
fpActiveBtcPkHexList
will only add content when there is an Active event.
dc.FinalityProviders
is the currently active ActiveFps,
dc := k.GetVotingPowerDistCache(ctx, height-1)
Therefore, all keys of fp may not exist in these two arrays.
TotalBondedSat
is used to calculate rewards and voting rights, which can lead to loss of funds or vote manipulation.
Traverse unbondedSatsByFpBtcPk
individually like activedSatsByFpBtcPk