Skip to content

Commit

Permalink
btcstaking: recording slashed height and disallowing slashed validato…
Browse files Browse the repository at this point in the history
…r to vote (#50)
  • Loading branch information
SebastianElvis authored Aug 4, 2023
1 parent 7ec25a6 commit 26331f3
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 92 deletions.
18 changes: 16 additions & 2 deletions proto/babylon/btcstaking/v1/btcstaking.proto
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ message BTCValidator {
bytes btc_pk = 2 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BIP340PubKey" ];
// pop is the proof of possession of babylon_pk and btc_pk
ProofOfPossession pop = 3;
// slashed indicates whether the BTC validator is slashed or not
bool slashed = 4;
// slashed_babylon_height indicates the Babylon height when
// the BTC validator is slashed.
// if it's 0 then the BTC validator is not slashed
uint64 slashed_babylon_height = 4;
// slashed_btc_height indicates the BTC height when
// the BTC validator is slashed.
// if it's 0 then the BTC validator is not slashed
uint64 slashed_btc_height = 5;
}

// BTCValidatorWithMeta wraps the BTCValidator with meta data.
Expand All @@ -28,6 +34,14 @@ message BTCValidatorWithMeta {
uint64 height = 2;
// voting_power is the voting power of this BTC validator at the given height
uint64 voting_power = 3;
// slashed_babylon_height indicates the Babylon height when
// the BTC validator is slashed.
// if it's 0 then the BTC validator is not slashed
uint64 slashed_babylon_height = 4;
// slashed_btc_height indicates the BTC height when
// the BTC validator is slashed.
// if it's 0 then the BTC validator is not slashed
uint64 slashed_btc_height = 5;
}

// BTCDelegation defines a BTC delegation
Expand Down
10 changes: 8 additions & 2 deletions x/btcstaking/keeper/btc_validators.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper

import (
"fmt"

"github.com/babylonchain/babylon/x/btcstaking/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -38,10 +40,14 @@ func (k Keeper) SlashBTCValidator(ctx sdk.Context, valBTCPK []byte) error {
if err != nil {
return err
}
if btcVal.Slashed {
if btcVal.IsSlashed() {
return types.ErrBTCValAlreadySlashed
}
btcVal.Slashed = true
btcVal.SlashedBabylonHeight = uint64(ctx.BlockHeight())
btcVal.SlashedBtcHeight, err = k.GetCurrentBTCHeight(ctx)
if err != nil {
panic(fmt.Errorf("failed to get current BTC height: %w", err))
}
k.SetBTCValidator(ctx, btcVal)
return nil
}
Expand Down
8 changes: 5 additions & 3 deletions x/btcstaking/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,11 @@ func (k Keeper) ActiveBTCValidatorsAtHeight(ctx context.Context, req *types.Quer
votingPower := k.GetVotingPower(sdkCtx, key, req.Height)
if votingPower > 0 {
btcValidatorWithMeta := types.BTCValidatorWithMeta{
BtcPk: btcValidator.BtcPk,
Height: req.Height,
VotingPower: votingPower,
BtcPk: btcValidator.BtcPk,
Height: req.Height,
VotingPower: votingPower,
SlashedBabylonHeight: btcValidator.SlashedBabylonHeight,
SlashedBtcHeight: btcValidator.SlashedBtcHeight,
}
btcValidatorsWithMeta = append(btcValidatorsWithMeta, &btcValidatorWithMeta)
}
Expand Down
2 changes: 1 addition & 1 deletion x/btcstaking/keeper/voting_power_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (k Keeper) RecordVotingPowerTable(ctx sdk.Context) {
// failed to get a BTC validator with voting power is a programming error
panic(err)
}
if btcVal.Slashed {
if btcVal.IsSlashed() {
// slashed BTC validator is removed from BTC validator set
continue
}
Expand Down
26 changes: 18 additions & 8 deletions x/btcstaking/keeper/voting_power_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ func FuzzVotingPowerTable(f *testing.F) {
}
}

// Case 1: assert none of validators has voting power (since BTC height is 0)
/*
Case 1: assert none of validators has voting power (since BTC height is 0)
*/
babylonHeight := datagen.RandomInt(r, 10) + 1
ctx = ctx.WithBlockHeight(int64(babylonHeight))
btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 0}).Times(1)
Expand All @@ -75,7 +77,9 @@ func FuzzVotingPowerTable(f *testing.F) {
_, err = keeper.GetBTCStakingActivatedHeight(ctx)
require.Error(t, err)

// Case 2: move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power
/*
Case 2: move to 1st BTC block, then assert the first numBTCValsWithVotingPower validators have voting power
*/
babylonHeight += datagen.RandomInt(r, 10) + 1
ctx = ctx.WithBlockHeight(int64(babylonHeight))
btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 1}).Times(1)
Expand All @@ -102,19 +106,23 @@ func FuzzVotingPowerTable(f *testing.F) {
require.NoError(t, err)
require.Equal(t, babylonHeight, activatedHeight)

// Case 3: move to 2nd BTC block and slash a random BTC validator,
// then assert the slashed BTC validator does not have voting power
babylonHeight += datagen.RandomInt(r, 10) + 1
ctx = ctx.WithBlockHeight(int64(babylonHeight))
btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 2}).Times(1)
/*
Case 3: slash a random BTC validator and move on
then assert the slashed BTC validator does not have voting power
*/
// slash a random BTC validator
slashedIdx := datagen.RandomInt(r, int(numBTCValsWithVotingPower))
slashedVal := btcVals[slashedIdx]
err = keeper.SlashBTCValidator(ctx, slashedVal.BtcPk.MustMarshal())
require.NoError(t, err)
// move to later Babylon height and 2nd BTC height
babylonHeight += datagen.RandomInt(r, 10) + 1
ctx = ctx.WithBlockHeight(int64(babylonHeight))
btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 2}).Times(1)
// index height and record power table
keeper.IndexBTCHeight(ctx)
keeper.RecordVotingPowerTable(ctx)
// check if the slashed BTC validator's voting power becomes zero
for i := uint64(0); i < numBTCValsWithVotingPower; i++ {
power := keeper.GetVotingPower(ctx, *btcVals[i].BtcPk, babylonHeight)
if i == slashedIdx {
Expand All @@ -139,7 +147,9 @@ func FuzzVotingPowerTable(f *testing.F) {
require.Equal(t, powerTable[btcVals[i].BtcPk.MarshalHex()], power)
}

// Case 4: move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height)
/*
Case 4: move to 999th BTC block, then assert none of validators has voting power (since end height - w < BTC height)
*/
babylonHeight += datagen.RandomInt(r, 10) + 1
ctx = ctx.WithBlockHeight(int64(babylonHeight))
btclcKeeper.EXPECT().GetTipInfo(gomock.Any()).Return(&btclctypes.BTCHeaderInfo{Height: 999}).Times(1)
Expand Down
4 changes: 4 additions & 0 deletions x/btcstaking/types/btcstaking.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import (
"github.com/btcsuite/btcd/wire"
)

func (v *BTCValidator) IsSlashed() bool {
return v.SlashedBabylonHeight > 0
}

func (v *BTCValidator) ValidateBasic() error {
// ensure fields are non-empty and well-formatted
if v.BabylonPk == nil {
Expand Down
Loading

0 comments on commit 26331f3

Please sign in to comment.