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

feat(ADR-25): Remove voting power from jailed finality providers #65

Merged
merged 13 commits into from
Sep 16, 2024
8 changes: 4 additions & 4 deletions proto/babylon/btcstaking/v1/btcstaking.proto
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ message FinalityProvider {
// the finality provider is slashed.
// if it's 0 then the finality provider is not slashed
uint64 slashed_btc_height = 7;
// sluggish defines whether the finality provider is detected sluggish
bool sluggish = 8;
// jailed defines whether the finality provider is jailed
bool jailed = 8;
}

// FinalityProviderWithMeta wraps the FinalityProvider with metadata.
Expand All @@ -54,8 +54,8 @@ message FinalityProviderWithMeta {
// the finality provider is slashed.
// if it's 0 then the finality provider is not slashed
uint64 slashed_btc_height = 5;
// sluggish defines whether the finality provider is detected sluggish
bool sluggish = 6;
// jailed defines whether the finality provider is detected jailed
bool jailed = 6;
}

// BTCDelegation defines a BTC delegation
Expand Down
10 changes: 9 additions & 1 deletion proto/babylon/btcstaking/v1/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,19 @@ message EventPowerDistUpdate {
bytes pk = 1 [ (gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey" ];
}

// EventJailedFinalityProvider defines an event that a finality provider
// is jailed after being detected sluggish
message EventJailedFinalityProvider {
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
bytes pk = 1 [ (gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey" ];
}

// ev is the event that affects voting power distribution
oneof ev {
// slashed_fp means a finality provider is slashed
EventSlashedFinalityProvider slashed_fp = 1;
// jailed_fp means a finality provider is jailed
EventJailedFinalityProvider jailed_fp = 2;
SebastianElvis marked this conversation as resolved.
Show resolved Hide resolved
// btc_del_state_update means a BTC delegation's state is updated
EventBTCDelegationStateUpdate btc_del_state_update = 2;
EventBTCDelegationStateUpdate btc_del_state_update = 3;
}
}
3 changes: 3 additions & 0 deletions proto/babylon/btcstaking/v1/incentive.proto
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ message FinalityProviderDistInfo {
// is_timestamped indicates whether the finality provider
// has timestamped public randomness committed
bool is_timestamped = 6;
// is_jailed indicates whether the finality provider
// is jailed, if so, it should not be assigned voting power
bool is_jailed = 7;
}

// BTCDelDistInfo contains the information related to reward distribution for a BTC delegation
Expand Down
4 changes: 2 additions & 2 deletions proto/babylon/btcstaking/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,6 @@ message FinalityProviderResponse {
uint64 height = 8;
// voting_power is the voting power of this finality provider at the given height
uint64 voting_power = 9;
// sluggish defines whether the finality provider is detected sluggish
bool sluggish = 10;
// jailed defines whether the finality provider is jailed
bool jailed = 10;
}
13 changes: 3 additions & 10 deletions proto/babylon/finality/v1/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,9 @@ message EventSlashedFinalityProvider {
Evidence evidence = 1;
}

// EventSluggishFinalityProviderDetected is the event emitted when a finality provider is
// detected as sluggish
message EventSluggishFinalityProviderDetected {
// public_key is the BTC public key of the finality provider
string public_key = 1;
}

// EventSluggishFinalityProviderReverted is the event emitted when a sluggish finality
// provider is no longer considered sluggish
message EventSluggishFinalityProviderReverted {
// EventJailedFinalityProvider is the event emitted when a finality provider is
// jailed due to inactivity
message EventJailedFinalityProvider {
// public_key is the BTC public key of the finality provider
string public_key = 1;
}
5 changes: 5 additions & 0 deletions proto/babylon/finality/v1/finality.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package babylon.finality.v1;
option go_package = "github.com/babylonlabs-io/babylon/x/finality/types";

import "gogoproto/gogo.proto";
import "amino/amino.proto";
import "google/protobuf/timestamp.proto";

// IndexedBlock is the necessary metadata and finalization status of a block
message IndexedBlock {
Expand Down Expand Up @@ -64,4 +66,7 @@ message FinalityProviderSigningInfo {
// missed_blocks_counter defines a counter to avoid unnecessary array reads.
// Note that `Sum(MissedBlocksBitArray)` always equals `MissedBlocksCounter`.
int64 missed_blocks_counter = 3;
// Timestamp until which the validator is jailed due to liveness downtime.
google.protobuf.Timestamp jailed_until = 4
[(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (amino.dont_omitempty) = true];
RafilxTenfen marked this conversation as resolved.
Show resolved Hide resolved
}
6 changes: 5 additions & 1 deletion proto/babylon/finality/v1/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package babylon.finality.v1;
import "gogoproto/gogo.proto";
import "amino/amino.proto";
import "cosmos_proto/cosmos.proto";
import "google/protobuf/duration.proto";

option go_package = "github.com/babylonlabs-io/babylon/x/finality/types";

Expand All @@ -16,7 +17,7 @@ message Params {
// vote before being judged as missing their voting turn on the given block
int64 finality_sig_timeout = 2;
// min_signed_per_window defines the minimum number of blocks that a finality provider is required to sign
// within the sliding window to avoid being detected as sluggish
// within the sliding window to avoid being jailed
bytes min_signed_per_window = 3 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
Expand All @@ -26,4 +27,7 @@ message Params {
// min_pub_rand is the minimum number of public randomness each
// message should commit
uint64 min_pub_rand = 4;
// jail_duration is the minimum period of time that a finality provider remains jailed
google.protobuf.Duration jail_duration = 5
[(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdduration) = true];
}
32 changes: 24 additions & 8 deletions x/btcstaking/keeper/finality_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,24 +102,40 @@ func (k Keeper) SlashFinalityProvider(ctx context.Context, fpBTCPK []byte) error
return nil
}

// RevertSluggishFinalityProvider sets the Sluggish flag of the given finality provider
// to false
func (k Keeper) RevertSluggishFinalityProvider(ctx context.Context, fpBTCPK []byte) error {
// JailFinalityProvider jails a finality provider with the given PK
// A jailed finality provider will not have voting power until it is
// unjailed (assuming it still ranks top N and has timestamped pub rand)
func (k Keeper) JailFinalityProvider(ctx context.Context, fpBTCPK []byte) error {
// ensure finality provider exists
fp, err := k.GetFinalityProvider(ctx, fpBTCPK)
if err != nil {
return err
}

// ignore the finality provider is already slashed
// or detected as sluggish
if fp.IsSlashed() || fp.IsSluggish() {
return nil
// ensure finality provider is not slashed yet
if fp.IsSlashed() {
return types.ErrFpAlreadySlashed
}

fp.Sluggish = false
// ensure finality provider is not jailed yet
if fp.IsJailed() {
return types.ErrFpAlreadyJailed
}

// set finality provider to be jailed
fp.Jailed = true
k.setFinalityProvider(ctx, fp)

btcTip := k.btclcKeeper.GetTipInfo(ctx)
if btcTip == nil {
return fmt.Errorf("failed to get current BTC tip")
}

// record jailed event. The next `BeginBlock` will consume this
// event for updating the finality provider set
powerUpdateEvent := types.NewEventPowerDistUpdateWithJailedFP(fp.BtcPk)
k.addPowerDistUpdateEvent(ctx, btcTip.Height, powerUpdateEvent)

return nil
}

Expand Down
16 changes: 1 addition & 15 deletions x/btcstaking/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package keeper

import (
"context"
"fmt"

bbntypes "github.com/babylonlabs-io/babylon/types"
"github.com/babylonlabs-io/babylon/x/finality/types"
Expand All @@ -22,18 +21,5 @@ func (k Keeper) Hooks() Hooks {

// AfterSluggishFinalityProviderDetected updates the status of the given finality provider to `sluggish`
func (h Hooks) AfterSluggishFinalityProviderDetected(ctx context.Context, fpPk *bbntypes.BIP340PubKey) error {
SebastianElvis marked this conversation as resolved.
Show resolved Hide resolved
fp, err := h.k.GetFinalityProvider(ctx, fpPk.MustMarshal())
if err != nil {
return err
}

if fp.IsSluggish() {
return fmt.Errorf("the finality provider %s is already detected as sluggish", fpPk.MarshalHex())
}

fp.Sluggish = true

h.k.setFinalityProvider(ctx, fp)

return nil
return h.k.JailFinalityProvider(ctx, fpPk.MustMarshal())
}
28 changes: 20 additions & 8 deletions x/btcstaking/keeper/power_dist_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (k Keeper) UpdatePowerDist(ctx context.Context) {

// reconcile old voting power distribution cache and new events
// to construct the new distribution
newDc := k.ProcessAllPowerDistUpdateEvents(ctx, dc, events, maxActiveFps)
newDc := k.ProcessAllPowerDistUpdateEvents(ctx, dc, events)

// record voting power and cache for this height
k.recordVotingPowerAndCache(ctx, dc, newDc, maxActiveFps)
Expand All @@ -91,11 +91,11 @@ func (k Keeper) recordVotingPowerAndCache(ctx context.Context, prevDc, newDc *ty

// label fps with whether it has timestamped pub rand so that these fps
// will not be assigned voting power
for _, fp := range newDc.FinalityProviders {
for _, fpDistInfo := range newDc.FinalityProviders {
// TODO calling HasTimestampedPubRand potentially iterates
// all the pub rand committed by the fp, which might slow down
// all the pub rand committed by the fpDistInfo, which might slow down
// the process, need optimization
fp.IsTimestamped = k.FinalityKeeper.HasTimestampedPubRand(ctx, fp.BtcPk, babylonTipHeight)
fpDistInfo.IsTimestamped = k.FinalityKeeper.HasTimestampedPubRand(ctx, fpDistInfo.BtcPk, babylonTipHeight)
}

// apply the finality provider voting power dist info to the new cache
Expand Down Expand Up @@ -150,7 +150,6 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents(
ctx context.Context,
dc *types.VotingPowerDistCache,
events []*types.EventPowerDistUpdate,
maxActiveFps uint32,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this max being enforced somewhere else? Just curious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, here

k.recordVotingPowerAndCache(ctx, dc, newDc, maxActiveFps)
.

) *types.VotingPowerDistCache {
// a map where key is finality provider's BTC PK hex and value is a list
// of BTC delegations that newly become active under this provider
Expand All @@ -159,9 +158,11 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents(
unbondedBTCDels := map[string]struct{}{}
// a map where key is slashed finality providers' BTC PK
slashedFPs := map[string]struct{}{}
// a map where key is jailed finality providers' BTC PK
jailedFPs := map[string]struct{}{}

/*
filter and classify all events into new/expired BTC delegations and slashed FPs
filter and classify all events into new/expired BTC delegations and jailed/slashed FPs
*/
for _, event := range events {
switch typedEvent := event.Ev.(type) {
Expand All @@ -183,8 +184,11 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents(
unbondedBTCDels[delEvent.StakingTxHash] = struct{}{}
}
case *types.EventPowerDistUpdate_SlashedFp:
// slashed finality providers
// record slashed fps
slashedFPs[typedEvent.SlashedFp.Pk.MarshalHex()] = struct{}{}
case *types.EventPowerDistUpdate_JailedFp:
// record jailed fps
jailedFPs[typedEvent.JailedFp.Pk.MarshalHex()] = struct{}{}
}
}

Expand All @@ -208,11 +212,19 @@ func (k Keeper) ProcessAllPowerDistUpdateEvents(

fpBTCPKHex := fp.BtcPk.MarshalHex()

// if this finality provider is slashed, continue to avoid recording it
// if this finality provider is slashed, continue to avoid
// assigning delegation to it
if _, ok := slashedFPs[fpBTCPKHex]; ok {
continue
}

// set IsJailed to be true if the fp is jailed
// Note that jailed fp can still accept delegations
// but won't be assigned with voting power
if _, ok := jailedFPs[fpBTCPKHex]; ok {
fp.IsJailed = true
}

// add all BTC delegations that are not unbonded to the new finality provider
for j := range dc.FinalityProviders[i].BtcDels {
btcDel := *dc.FinalityProviders[i].BtcDels[j]
Expand Down
Loading
Loading