Slow Misty Iguana
High
The attacker creates a large number of fps to conduct DoS attacks.
- The creation of fp only verifies the proof of possession. After the creation is completed, the user can create a new fp again after transferring the assets.
- There is no limit on the number of fp creations.
- BeginBlock -> UpdatePowerDist will traverse all activated fps including those with a stake amount of 0.
- The amount of BTC held by the attacker reaches the possession requirement.
- The attacker creates fp.
- The attacker transfers BTC to another address.
- The attacker creates fp again.
- The attacker stake the minimum number of tokens to fp, and fp is activated, and then unstakes.
- The attacker repeats the above steps to create any number of fp.
- Due to too many fps, the chain becomes slow.
- The attacker only spends the gas fee, the invalid fp will be permanently stored in the fp list, and each block will be traversed.
Slow ABCI methods
The creation of fp only verifies proof of possession:
func (ms msgServer) CreateFinalityProvider(goCtx context.Context, req *types.MsgCreateFinalityProvider) (*types.MsgCreateFinalityProviderResponse, error) {
......
-> // verify proof of possession
-> if err := req.Pop.Verify(fpAddr, req.BtcPk, ms.btcNet); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession: %v", err)
}
if err := ms.AddFinalityProvider(ctx, req); err != nil {
return nil, err
}
return &types.MsgCreateFinalityProviderResponse{}, nil
}
Therefore, the attacker can create any number of FPs after transferring BTC.
After fp is staked, it will be activated and added to VotingPowerDistCache
:
func (k Keeper) ProcessAllPowerDistUpdateEvents(
ctx context.Context,
dc *ftypes.VotingPowerDistCache,
events []*types.EventPowerDistUpdate,
) *ftypes.VotingPowerDistCache {
sdkCtx := sdk.UnwrapSDKContext(ctx)
for _, event := range events {
switch typedEvent := event.Ev.(type) {
case *types.EventPowerDistUpdate_BtcDelStateUpdate:
delEvent := typedEvent.BtcDelStateUpdate
delStkTxHash := delEvent.StakingTxHash
btcDel, err := k.BTCStakingKeeper.GetBTCDelegation(ctx, delStkTxHash)
if err != nil {
panic(err) // only programming error
}
switch delEvent.NewState {
-> case types.BTCDelegationStatus_ACTIVE:
// newly active BTC delegation
// add the BTC delegation to each restaked finality provider
for _, fpBTCPK := range btcDel.FpBtcPkList {
fpBTCPKHex := fpBTCPK.MarshalHex()
-> activedSatsByFpBtcPk[fpBTCPKHex] = append(activedSatsByFpBtcPk[fpBTCPKHex], btcDel.TotalSat)
}
......
}
Even if the stake is cancelled, fp is still saved in the VotingPowerDistCache
. UpdatePowerDist
only sorts the dc and does not remove invalid fp.
BeginBlock -> UpdatePowerDist -> ProcessAllPowerDistUpdateEvents will traverse all fp:
func (k Keeper) UpdatePowerDist(ctx context.Context) {
height := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)
// GetBTCHeightAtBabylonHeight(HeaderInfo().Height)
btcTipHeight := k.BTCStakingKeeper.GetCurrentBTCHeight(ctx)
// get the power dist cache in the last height
-> dc := k.GetVotingPowerDistCache(ctx, height-1)
if dc == nil {
// no BTC staker at the prior height
dc = ftypes.NewVotingPowerDistCache()
}
// get all power distribution update events during the previous tip and the current tip
lastBTCTipHeight := k.BTCStakingKeeper.GetBTCHeightAtBabylonHeight(ctx, height-1)
events := k.BTCStakingKeeper.GetAllPowerDistUpdateEvents(ctx, lastBTCTipHeight, btcTipHeight)
// clear all events that have been consumed in this function
defer func() {
for i := lastBTCTipHeight; i <= btcTipHeight; i++ {
k.BTCStakingKeeper.ClearPowerDistUpdateEvents(ctx, i)
}
}()
// reconcile old voting power distribution cache and new events to construct the new distribution
-> newDc := k.ProcessAllPowerDistUpdateEvents(ctx, dc, events)
// record voting power and cache for this height
k.recordVotingPowerAndCache(ctx, newDc)
// emit events for finality providers with state updates
k.handleFPStateUpdates(ctx, dc, newDc)
// record metrics
k.recordMetrics(newDc)
}
func (k Keeper) ProcessAllPowerDistUpdateEvents(
ctx context.Context,
dc *ftypes.VotingPowerDistCache,
events []*types.EventPowerDistUpdate,
) *ftypes.VotingPowerDistCache {
......
// iterate over all finality providers and apply all events
-> for i := range dc.FinalityProviders {
// create a copy of the finality provider
fp := *dc.FinalityProviders[i]
fpBTCPKHex := fp.BtcPk.MarshalHex()
......
}
}
- Limit the number of fp creations
- Remove invalid fp from fp list.