Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: slashed fp and BTC rewards tracker #371

Merged
merged 14 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## Unreleased

### Bug fixes

- [#371](https://github.com/babylonlabs-io/babylon/pull/371) Do not prune BTC
reward tracker structures at the slash of finality provider.

## v1.0.0-rc.1

### Improvements

- [#306](https://github.com/babylonlabs-io/babylon/pull/306) feat: improve BTC reward distribution with
Expand Down
24 changes: 20 additions & 4 deletions x/finality/keeper/power_dist_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,13 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents(
types.EmitSlashedFPEvent(sdkCtx, typedEvent.SlashedFp.Pk)
fpBTCPKHex := typedEvent.SlashedFp.Pk.MarshalHex()
slashedFPs[fpBTCPKHex] = struct{}{}
fp := k.loadFP(ctx, fpByBtcPkHex, fpBTCPKHex)
if err := k.IncentiveKeeper.FpSlashed(ctx, fp.Address()); err != nil {
panic(err)
}
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
// TODO(rafilx): handle slashed fps prunning
// It is not possible to slash fp and delete all of his data at the
// babylon block height that is being processed, because
// the function RewardBTCStaking is called a few blocks behind.
// If the data is deleted at the slash event, when slashed fps are
// receveing rewards from a few blocks behind HandleRewarding
// verifies the next block height to be rewarded.
case *types.EventPowerDistUpdate_JailedFp:
// record jailed fps
types.EmitJailedFPEvent(sdkCtx, typedEvent.JailedFp.Pk)
Expand Down Expand Up @@ -320,6 +323,11 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents(
// sort new finality providers in activeBTCDels to ensure determinism
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
fpActiveBtcPkHexList := make([]string, 0, len(activedSatsByFpBtcPk))
for fpBTCPKHex := range activedSatsByFpBtcPk {
// if the fp was slashed, should not even be added to the list
_, isSlashed := slashedFPs[fpBTCPKHex]
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
if isSlashed {
continue
}
fpActiveBtcPkHexList = append(fpActiveBtcPkHexList, fpBTCPKHex)
}
sort.SliceStable(fpActiveBtcPkHexList, func(i, j int) bool {
Expand All @@ -332,6 +340,14 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents(
newFP := k.loadFP(ctx, fpByBtcPkHex, fpBTCPKHex)
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
fpDistInfo := ftypes.NewFinalityProviderDistInfo(newFP)

// check for jailing cases
if _, ok := jailedFPs[fpBTCPKHex]; ok {
fpDistInfo.IsJailed = true
}
if _, ok := unjailedFPs[fpBTCPKHex]; ok {
fpDistInfo.IsJailed = false
}

// add each BTC delegation
fpActiveSats := activedSatsByFpBtcPk[fpBTCPKHex]
for _, activatedSats := range fpActiveSats {
Expand Down
149 changes: 115 additions & 34 deletions x/finality/keeper/power_dist_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,46 +83,51 @@ func FuzzProcessAllPowerDistUpdateEvents_Determinism(f *testing.F) {
})
}

func FuzzProcessAllPowerDistUpdateEvents_ActiveAndUnbondTogether(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)
func CreateFpAndBtcDel(
t *testing.T,
r *rand.Rand,
) (h *testutil.Helper, del *types.BTCDelegation) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

f.Fuzz(func(t *testing.T, seed int64) {
r := rand.New(rand.NewSource(seed))
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// mock BTC light client and BTC checkpoint modules
btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl)
btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl)
h = testutil.NewHelper(t, btclcKeeper, btccKeeper)

// mock BTC light client and BTC checkpoint modules
btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl)
btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl)
h := testutil.NewHelper(t, btclcKeeper, btccKeeper)
// set all parameters
h.GenAndApplyParams(r)
changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net)
require.NoError(t, err)

// set all parameters
h.GenAndApplyParams(r)
changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net)
require.NoError(t, err)
_, fpPK, _ := h.CreateFinalityProvider(r)

// empty dist cache
dc := ftypes.NewVotingPowerDistCache()
delSK, _, err := datagen.GenRandomBTCKeyPair(r)
h.NoError(err)
_, _, del, _, _, _, err = h.CreateDelegationWithBtcBlockHeight(
r,
delSK,
fpPK,
changeAddress.EncodeAddress(),
int64(2*10e8),
1000,
0,
0,
false,
false,
10,
30,
)
h.NoError(err)
return h, del
}

_, fpPK, _ := h.CreateFinalityProvider(r)
func FuzzProcessAllPowerDistUpdateEvents_ActiveAndUnbondTogether(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)

delSK, _, err := datagen.GenRandomBTCKeyPair(r)
h.NoError(err)
_, _, del, _, _, _, err := h.CreateDelegationWithBtcBlockHeight(
r,
delSK,
fpPK,
changeAddress.EncodeAddress(),
int64(2*10e8),
1000,
0,
0,
false,
false,
10,
30,
)
h.NoError(err)
f.Fuzz(func(t *testing.T, seed int64) {
r := rand.New(rand.NewSource(seed))
h, del := CreateFpAndBtcDel(t, r)

eventActive := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{
StakingTxHash: del.MustGetStakingTxHash().String(),
Expand All @@ -134,11 +139,87 @@ func FuzzProcessAllPowerDistUpdateEvents_ActiveAndUnbondTogether(f *testing.F) {
})
events := []*types.EventPowerDistUpdate{eventActive, eventUnbond}

newDc := h.FinalityKeeper.ProcessAllPowerDistUpdateEvents(h.Ctx, ftypes.NewVotingPowerDistCache(), events)
require.Len(t, newDc.FinalityProviders, 0)
})
}

func FuzzProcessAllPowerDistUpdateEvents_ActiveAndSlashTogether(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)

f.Fuzz(func(t *testing.T, seed int64) {
r := rand.New(rand.NewSource(seed))
h, del := CreateFpAndBtcDel(t, r)

eventActive := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{
StakingTxHash: del.MustGetStakingTxHash().String(),
NewState: types.BTCDelegationStatus_ACTIVE,
})
eventSlash := types.NewEventPowerDistUpdateWithSlashedFP(&del.FpBtcPkList[0])
events := []*types.EventPowerDistUpdate{eventActive, eventSlash}

dc := ftypes.NewVotingPowerDistCache()
newDc := h.FinalityKeeper.ProcessAllPowerDistUpdateEvents(h.Ctx, dc, events)
require.Len(t, newDc.FinalityProviders, 0)
})
}

func FuzzProcessAllPowerDistUpdateEvents_ActiveAndJailTogether(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)

f.Fuzz(func(t *testing.T, seed int64) {
r := rand.New(rand.NewSource(seed))
h, del := CreateFpAndBtcDel(t, r)

eventActive := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{
StakingTxHash: del.MustGetStakingTxHash().String(),
NewState: types.BTCDelegationStatus_ACTIVE,
})
eventJailed := types.NewEventPowerDistUpdateWithJailedFP(&del.FpBtcPkList[0])
events := []*types.EventPowerDistUpdate{eventActive, eventJailed}

newDc := h.FinalityKeeper.ProcessAllPowerDistUpdateEvents(h.Ctx, ftypes.NewVotingPowerDistCache(), events)
for _, fp := range newDc.FinalityProviders {
fp.IsTimestamped = true
}
newDc.ApplyActiveFinalityProviders(100)
require.Len(t, newDc.FinalityProviders, 1)
require.Zero(t, newDc.TotalVotingPower)
})
}

func FuzzProcessAllPowerDistUpdateEvents_SlashActiveFp(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)

f.Fuzz(func(t *testing.T, seed int64) {
t.Parallel()
r := rand.New(rand.NewSource(seed))
h, del := CreateFpAndBtcDel(t, r)

eventActive := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{
StakingTxHash: del.MustGetStakingTxHash().String(),
NewState: types.BTCDelegationStatus_ACTIVE,
})
events := []*types.EventPowerDistUpdate{eventActive}

newDc := h.FinalityKeeper.ProcessAllPowerDistUpdateEvents(h.Ctx, ftypes.NewVotingPowerDistCache(), events)
for _, fp := range newDc.FinalityProviders {
fp.IsTimestamped = true
}
newDc.ApplyActiveFinalityProviders(100)
require.Equal(t, newDc.TotalVotingPower, del.TotalSat)

// afer the fp has some active voting power slash it
eventSlash := types.NewEventPowerDistUpdateWithSlashedFP(&del.FpBtcPkList[0])
events = []*types.EventPowerDistUpdate{eventSlash}

newDc = h.FinalityKeeper.ProcessAllPowerDistUpdateEvents(h.Ctx, newDc, events)
newDc.ApplyActiveFinalityProviders(100)
require.Len(t, newDc.FinalityProviders, 0)
require.Equal(t, newDc.TotalVotingPower, uint64(0))
})
}

func FuzzSlashFinalityProviderEvent(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)

Expand Down
1 change: 0 additions & 1 deletion x/finality/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,4 @@ type IncentiveKeeper interface {
IndexRefundableMsg(ctx context.Context, msg sdk.Msg)
BtcDelegationActivated(ctx context.Context, fp, del sdk.AccAddress, sat uint64) error
BtcDelegationUnbonded(ctx context.Context, fp, del sdk.AccAddress, sat uint64) error
FpSlashed(ctx context.Context, fp sdk.AccAddress) error
}
3 changes: 2 additions & 1 deletion x/incentive/keeper/btc_staking_gauge.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"fmt"

sdkmath "cosmossdk.io/math"
"cosmossdk.io/store/prefix"
Expand Down Expand Up @@ -55,7 +56,7 @@ func (k Keeper) RewardBTCStaking(ctx context.Context, height uint64, dc *ftypes.
// reward the rest of coins to each BTC delegation proportional to its voting power portion
coinsForBTCDels := coinsForFpsAndDels.Sub(coinsForCommission...)
if err := k.AddFinalityProviderRewardsForBtcDelegations(ctx, fp.GetAddress(), coinsForBTCDels); err != nil {
panic(err)
panic(fmt.Errorf("failed to add fp rewards for btc delegation %s at height %d: %w", fp.GetAddress().String(), height, err))
}
}
// TODO: prune unnecessary state (delete BTCStakingGauge after the amount is used)
Expand Down
8 changes: 7 additions & 1 deletion x/incentive/keeper/reward_tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func FuzzCheckFpSlashed(f *testing.F) {
del2RwdGauge := k.GetRewardGauge(ctx, types.BTCDelegationType, del2)
coins.RequireCoinsDiffInPointOnePercentMargin(t, del2Fp1Rwds, del2RwdGauge.Coins)

// verifies that everything was deleted
// verifies that everything was deleted for fp1
_, err = k.GetBTCDelegationRewardsTracker(ctx, fp1, del1)
require.EqualError(t, err, types.ErrBTCDelegationRewardsTrackerNotFound.Error())
_, err = k.GetBTCDelegationRewardsTracker(ctx, fp1, del2)
Expand All @@ -74,6 +74,12 @@ func FuzzCheckFpSlashed(f *testing.F) {
_, err = k.GetFinalityProviderHistoricalRewards(ctx, fp1, 1)
require.EqualError(t, err, types.ErrFPHistoricalRewardsNotFound.Error())

// verifies that for fp2 is all there
_, err = k.GetFinalityProviderCurrentRewards(ctx, fp2)
require.NoError(t, err)
_, err = k.GetFinalityProviderHistoricalRewards(ctx, fp2, 1)
require.NoError(t, err)

count := 0
err = k.iterBtcDelegationsByDelegator(ctx, del1, func(del, fp sdk.AccAddress) error {
count++
Expand Down