Skip to content

Commit

Permalink
feat(ADR-25): Remove voting power from jailed finality providers (#65)
Browse files Browse the repository at this point in the history
This PR partially closes #64 by jailing sluggish finality providers by
not assigning voting power to them in the voting power table. In
particular, this PR made the following changes:
1. Change the `sluggish` label of a finality provider to `jailed`.
2. Implement `AfterSluggishFinalityProviderDetected` hook in the
`x/btcstaking` module to set the label of a finality provider to
`jailed`.
3. Introduce `EventJailedFinalityProvider` event as
`EventPowerDistUpdate`, which is emitted after a finality provider is
jailed.
4. Introduce a new parameter `jail_duration` to `x/finality` (needs to
handle upgrade in a separate PR).
5. Introduce a new field `jailed_until` to `FinalityProviderSigningInfo`
in `x/finality`, which is calculated as the timestamp of the jailing
block plus the `jail_duration` of the parameter.
6. Clear the signing info record of the jailed finality provider and set
`jailed_until=jailingBlock.Time.Add(params.JailDuration)`.
7. Within the `BeginBlocker` of the next block, upon
`EventJailedFinalityProvider`, record the finality provider public key
and do not assign voting power to it.
8. Modify relevant tests
  • Loading branch information
gitferry authored Sep 16, 2024
1 parent 4758adf commit 2308deb
Show file tree
Hide file tree
Showing 34 changed files with 1,147 additions and 730 deletions.
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 {
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;
// 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];
}
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 {
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,
) *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

0 comments on commit 2308deb

Please sign in to comment.