Skip to content

Commit

Permalink
fix getStatus call on BTCDelegation (#318)
Browse files Browse the repository at this point in the history
With recent changes:
- enforcing that `min_unbonding_time` is always larger than
`checkpoint_finalization_timeout`
- changing `min_unbonding_time` to exact `unbonding_time`

`GetStatus` can now rely on `UnbondingTime` in delegation instead of
`checkpoint_finalization_timeout`
  • Loading branch information
KonradStaniec authored Dec 4, 2024
1 parent 99d1037 commit aa32cd4
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 55 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ delegations using correct parameters version
- [#310](https://github.com/babylonlabs-io/babylon/pull/310) implement adr-37 -
making params valid for btc light client ranges

### Bug fixes

- [#318](https://github.com/babylonlabs-io/babylon/pull/318) Fix BTC delegation status check
to relay on UnbondingTime in delegation

## v0.17.2

### Improvements
Expand Down
11 changes: 4 additions & 7 deletions testutil/btcstaking-helper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ func (h *Helper) CreateDelegationWithBtcBlockHeight(
) (string, *types.MsgCreateBTCDelegation, *types.BTCDelegation, *btclctypes.BTCHeaderInfo, *types.InclusionProof, *UnbondingTxInfo, error) {
stakingTimeBlocks := stakingTime
bsParams := h.BTCStakingKeeper.GetParams(h.Ctx)
bcParams := h.BTCCheckpointKeeper.GetParams(h.Ctx)
covPKs, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks)
h.NoError(err)

Expand Down Expand Up @@ -384,7 +383,7 @@ func (h *Helper) CreateDelegationWithBtcBlockHeight(
h.NoError(err)

// ensure the delegation is still pending
require.Equal(h.t, btcDel.GetStatus(btcTipHeight, bcParams.CheckpointFinalizationTimeout, bsParams.CovenantQuorum), types.BTCDelegationStatus_PENDING)
require.Equal(h.t, btcDel.GetStatus(btcTipHeight, bsParams.CovenantQuorum), types.BTCDelegationStatus_PENDING)

if usePreApproval {
// the BTC delegation does not have inclusion proof
Expand Down Expand Up @@ -481,7 +480,6 @@ func (h *Helper) CreateCovenantSigs(
msgCreateBTCDel *types.MsgCreateBTCDelegation,
del *types.BTCDelegation,
) {
bcParams := h.BTCCheckpointKeeper.GetParams(h.Ctx)
bsParams := h.BTCStakingKeeper.GetParams(h.Ctx)

stakingTx, err := bbn.NewBTCTxFromBytes(del.StakingTx)
Expand Down Expand Up @@ -510,7 +508,7 @@ func (h *Helper) CreateCovenantSigs(
require.Len(h.t, actualDelWithCovenantSigs.BtcUndelegation.CovenantSlashingSigs[0].AdaptorSigs, 1)

// ensure the BTC delegation is verified (if using pre-approval flow) or active
status := actualDelWithCovenantSigs.GetStatus(btcTipHeight, bcParams.CheckpointFinalizationTimeout, bsParams.CovenantQuorum)
status := actualDelWithCovenantSigs.GetStatus(btcTipHeight, bsParams.CovenantQuorum)
if msgCreateBTCDel.StakingTxInclusionProof != nil {
// not pre-approval flow, the BTC delegation should be active
require.Equal(h.t, status, types.BTCDelegationStatus_ACTIVE)
Expand All @@ -525,13 +523,12 @@ func (h *Helper) AddInclusionProof(
btcHeader *btclctypes.BTCHeaderInfo,
proof *types.InclusionProof,
) {
bcParams := h.BTCCheckpointKeeper.GetParams(h.Ctx)
bsParams := h.BTCStakingKeeper.GetParams(h.Ctx)

// Get the BTC delegation and ensure it's verified
del, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash)
h.NoError(err)
status := del.GetStatus(btcTipHeight, bcParams.CheckpointFinalizationTimeout, bsParams.CovenantQuorum)
status := del.GetStatus(btcTipHeight, bsParams.CovenantQuorum)
require.Equal(h.t, status, types.BTCDelegationStatus_VERIFIED, "the BTC delegation shall be verified")

// Create the MsgAddBTCDelegationInclusionProof message
Expand All @@ -551,7 +548,7 @@ func (h *Helper) AddInclusionProof(
// has been activated
updatedDel, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash)
h.NoError(err)
status = updatedDel.GetStatus(btcTipHeight, bcParams.CheckpointFinalizationTimeout, bsParams.CovenantQuorum)
status = updatedDel.GetStatus(btcTipHeight, bsParams.CovenantQuorum)
require.Equal(h.t, status, types.BTCDelegationStatus_ACTIVE, "the BTC delegation shall be active")
}

Expand Down
8 changes: 1 addition & 7 deletions x/btcstaking/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ func (k Keeper) BTCDelegations(ctx context.Context, req *types.QueryBTCDelegatio

// get current BTC height
btcTipHeight := k.btclcKeeper.GetTipInfo(ctx).Height
// get value of w
wValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout

store := k.btcDelegationStore(ctx)
var btcDels []*types.BTCDelegationResponse
Expand All @@ -97,7 +95,7 @@ func (k Keeper) BTCDelegations(ctx context.Context, req *types.QueryBTCDelegatio
k.cdc.MustUnmarshal(value, &btcDel)

// hit if the queried status is ANY or matches the BTC delegation status
status := btcDel.GetStatus(btcTipHeight, wValue, covenantQuorum)
status := btcDel.GetStatus(btcTipHeight, covenantQuorum)
if req.Status == types.BTCDelegationStatus_ANY || status == req.Status {
if accumulate {
resp := types.NewBTCDelegationResponse(&btcDel, status)
Expand Down Expand Up @@ -137,7 +135,6 @@ func (k Keeper) FinalityProviderDelegations(ctx context.Context, req *types.Quer
sdkCtx := sdk.UnwrapSDKContext(ctx)
btcDelStore := k.btcDelegatorFpStore(sdkCtx, fpPK)

currentWValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout
btcHeight := k.btclcKeeper.GetTipInfo(ctx).Height
covenantQuorum := k.GetParams(ctx).CovenantQuorum

Expand All @@ -154,7 +151,6 @@ func (k Keeper) FinalityProviderDelegations(ctx context.Context, req *types.Quer
for i, btcDel := range curBTCDels.Dels {
status := btcDel.GetStatus(
btcHeight,
currentWValue,
covenantQuorum,
)
btcDelsResp[i] = types.NewBTCDelegationResponse(btcDel, status)
Expand Down Expand Up @@ -190,10 +186,8 @@ func (k Keeper) BTCDelegation(ctx context.Context, req *types.QueryBTCDelegation
return nil, types.ErrBTCDelegationNotFound
}

currentWValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout
status := btcDel.GetStatus(
k.btclcKeeper.GetTipInfo(ctx).Height,
currentWValue,
k.GetParams(ctx).CovenantQuorum,
)

Expand Down
12 changes: 6 additions & 6 deletions x/btcstaking/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,8 +452,7 @@ func (ms msgServer) AddCovenantSigs(goCtx context.Context, req *types.MsgAddCove

// ensure BTC delegation is still pending, i.e., not unbonded
btcTipHeight := ms.btclcKeeper.GetTipInfo(ctx).Height
wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout
status := btcDel.GetStatus(btcTipHeight, wValue, params.CovenantQuorum)
status := btcDel.GetStatus(btcTipHeight, params.CovenantQuorum)
if status == types.BTCDelegationStatus_UNBONDED {
ms.Logger(ctx).Debug("Received covenant signature after the BTC delegation is already unbonded", "covenant pk", req.Pk.MarshalHex())
return nil, types.ErrInvalidCovenantSig.Wrap("the BTC delegation is already unbonded")
Expand Down Expand Up @@ -599,9 +598,11 @@ func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndele

// ensure the BTC delegation with the given staking tx hash is active
btcTip := ms.btclcKeeper.GetTipInfo(ctx)
wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout

btcDelStatus := btcDel.GetStatus(btcTip.Height, wValue, bsParams.CovenantQuorum)
btcDelStatus := btcDel.GetStatus(
btcTip.Height,
bsParams.CovenantQuorum,
)

if btcDelStatus == types.BTCDelegationStatus_UNBONDED {
return nil, types.ErrInvalidBTCUndelegateReq.Wrap("cannot unbond an unbonded BTC delegation")
Expand Down Expand Up @@ -706,9 +707,8 @@ func (ms msgServer) SelectiveSlashingEvidence(goCtx context.Context, req *types.
// ensure the BTC delegation is active, or its BTC undelegation receives an
// unbonding signature from the staker
btcTip := ms.btclcKeeper.GetTipInfo(ctx)
wValue := ms.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout
covQuorum := bsParams.CovenantQuorum
if btcDel.GetStatus(btcTip.Height, wValue, covQuorum) != types.BTCDelegationStatus_ACTIVE && !btcDel.IsUnbondedEarly() {
if btcDel.GetStatus(btcTip.Height, covQuorum) != types.BTCDelegationStatus_ACTIVE && !btcDel.IsUnbondedEarly() {
return nil, types.ErrBTCDelegationNotFound.Wrap("a BTC delegation that is not active or unbonding early cannot be slashed")
}

Expand Down
19 changes: 8 additions & 11 deletions x/btcstaking/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,10 +545,9 @@ func FuzzAddCovenantSigs(f *testing.F) {
require.True(h.T(), actualDel.BtcUndelegation.HasCovenantQuorums(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum))

tipHeight := h.BTCLightClientKeeper.GetTipInfo(h.Ctx).Height
checkpointTimeout := h.BTCCheckpointKeeper.GetParams(h.Ctx).CheckpointFinalizationTimeout
covenantQuorum := h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum
status := actualDel.GetStatus(tipHeight, checkpointTimeout, covenantQuorum)
votingPower := actualDel.VotingPower(tipHeight, checkpointTimeout, covenantQuorum)
status := actualDel.GetStatus(tipHeight, covenantQuorum)
votingPower := actualDel.VotingPower(tipHeight, covenantQuorum)

if usePreApproval {
require.Equal(t, status, types.BTCDelegationStatus_VERIFIED)
Expand Down Expand Up @@ -607,10 +606,9 @@ func FuzzAddBTCDelegationInclusionProof(f *testing.F) {

// ensure the BTC delegation is now verified and does not have voting power
tipHeight := h.BTCLightClientKeeper.GetTipInfo(h.Ctx).Height
checkpointTimeout := h.BTCCheckpointKeeper.GetParams(h.Ctx).CheckpointFinalizationTimeout
covenantQuorum := h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum
status := actualDel.GetStatus(tipHeight, checkpointTimeout, covenantQuorum)
votingPower := actualDel.VotingPower(tipHeight, checkpointTimeout, covenantQuorum)
status := actualDel.GetStatus(tipHeight, covenantQuorum)
votingPower := actualDel.VotingPower(tipHeight, covenantQuorum)

require.Equal(t, status, types.BTCDelegationStatus_VERIFIED)
require.Zero(t, votingPower)
Expand All @@ -621,8 +619,8 @@ func FuzzAddBTCDelegationInclusionProof(f *testing.F) {

actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash)
h.NoError(err)
status = actualDel.GetStatus(tipHeight, checkpointTimeout, covenantQuorum)
votingPower = actualDel.VotingPower(tipHeight, checkpointTimeout, covenantQuorum)
status = actualDel.GetStatus(tipHeight, covenantQuorum)
votingPower = actualDel.VotingPower(tipHeight, covenantQuorum)

require.Equal(t, status, types.BTCDelegationStatus_ACTIVE)
require.Equal(t, uint64(stakingValue), votingPower)
Expand All @@ -646,7 +644,6 @@ func FuzzBTCUndelegate(f *testing.F) {
covenantSKs, _ := h.GenAndApplyParams(r)

bsParams := h.BTCStakingKeeper.GetParams(h.Ctx)
wValue := h.BTCCheckpointKeeper.GetParams(h.Ctx).CheckpointFinalizationTimeout

changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net)
require.NoError(t, err)
Expand Down Expand Up @@ -681,7 +678,7 @@ func FuzzBTCUndelegate(f *testing.F) {
actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash)
h.NoError(err)
btcTip := h.BTCLightClientKeeper.GetTipInfo(h.Ctx).Height
status := actualDel.GetStatus(btcTip, wValue, bsParams.CovenantQuorum)
status := actualDel.GetStatus(btcTip, bsParams.CovenantQuorum)
require.Equal(t, types.BTCDelegationStatus_ACTIVE, status)

msg := &types.MsgBTCUndelegate{
Expand All @@ -704,7 +701,7 @@ func FuzzBTCUndelegate(f *testing.F) {
// ensure the BTC delegation is unbonded
actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash)
h.NoError(err)
status = actualDel.GetStatus(btcTip, wValue, bsParams.CovenantQuorum)
status = actualDel.GetStatus(btcTip, bsParams.CovenantQuorum)
require.Equal(t, types.BTCDelegationStatus_UNBONDED, status)
})
}
Expand Down
36 changes: 17 additions & 19 deletions x/btcstaking/types/btc_delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,19 @@ func (d *BTCDelegation) FinalityProviderKeys() []string {
return fpPks
}

// GetStatus returns the status of the BTC Delegation based on BTC height, w value, and covenant quorum
// Pending: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation does not have covenant signatures
// Active: the BTC height is in the range of d's [startHeight, endHeight-w] and the delegation has quorum number of signatures over slashing tx, unbonding tx, and slashing unbonding tx from covenant committee
// Unbonded: the BTC height is larger than `endHeight-w` or the BTC delegation has received a signature on unbonding tx from the delegator
func (d *BTCDelegation) GetStatus(btcHeight uint32, w uint32, covenantQuorum uint32) BTCDelegationStatus {
// GetStatus returns the status of the BTC Delegation based on BTC height,
// unbonding time, and covenant quorum
// Pending: the BTC height is in the range of d's [startHeight, endHeight-unbondingTime]
// and the delegation does not have covenant signatures
// Active: the BTC height is in the range of d's [startHeight, endHeight-unbondingTime]
// and the delegation has quorum number of signatures over slashing tx,
// unbonding tx, and slashing unbonding tx from covenant committee
// Unbonded: the BTC height is larger than `endHeight-unbondingTime` or the
// BTC delegation has received a signature on unbonding tx from the delegator
func (d *BTCDelegation) GetStatus(
btcHeight uint32,
covenantQuorum uint32,
) BTCDelegationStatus {
if d.IsUnbondedEarly() {
return BTCDelegationStatus_UNBONDED
}
Expand All @@ -119,8 +127,8 @@ func (d *BTCDelegation) GetStatus(btcHeight uint32, w uint32, covenantQuorum uin

// At this point we already have covenant quorum and inclusion proof,
// we can check the status based on the BTC height
if btcHeight < d.StartHeight || btcHeight+w > d.EndHeight {
// staking tx's timelock has not begun, or is less than w BTC
if btcHeight < d.StartHeight || btcHeight+d.UnbondingTime > d.EndHeight {
// staking tx's timelock has not begun, or is less than unbonding time BTC
// blocks left, or is expired
return BTCDelegationStatus_UNBONDED
}
Expand All @@ -133,10 +141,9 @@ func (d *BTCDelegation) GetStatus(btcHeight uint32, w uint32, covenantQuorum uin
}

// VotingPower returns the voting power of the BTC delegation at a given BTC height
// and a given w value.
// The BTC delegation d has voting power iff it is active.
func (d *BTCDelegation) VotingPower(btcHeight uint32, w uint32, covenantQuorum uint32) uint64 {
if d.GetStatus(btcHeight, w, covenantQuorum) != BTCDelegationStatus_ACTIVE {
func (d *BTCDelegation) VotingPower(btcHeight uint32, covenantQuorum uint32) uint64 {
if d.GetStatus(btcHeight, covenantQuorum) != BTCDelegationStatus_ACTIVE {
return 0
}
return d.GetTotalSat()
Expand Down Expand Up @@ -486,12 +493,3 @@ func (i *BTCDelegatorDelegationIndex) Add(stakingTxHash chainhash.Hash) error {

return nil
}

// VotingPower calculates the total voting power of all BTC delegations
func (dels *BTCDelegatorDelegations) VotingPower(btcHeight uint32, w uint32, covenantQuorum uint32) uint64 {
power := uint64(0)
for _, del := range dels.Dels {
power += del.VotingPower(btcHeight, w, covenantQuorum)
}
return power
}
9 changes: 4 additions & 5 deletions x/btcstaking/types/btc_delegation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ func FuzzBTCDelegation(f *testing.F) {

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

unbondingTime := uint32(datagen.RandomInt(r, 50))
btcDel := &types.BTCDelegation{}
// randomise voting power
btcDel.TotalSat = datagen.RandomInt(r, 100000)
btcDel.BtcUndelegation = &types.BTCUndelegation{}

btcDel.UnbondingTime = unbondingTime
// randomise covenant sig
hasCovenantSig := datagen.RandomInt(r, 2) == 0
if hasCovenantSig {
Expand Down Expand Up @@ -56,11 +56,10 @@ func FuzzBTCDelegation(f *testing.F) {

// randomise BTC tip and w
btcHeight := btcDel.StartHeight + uint32(datagen.RandomInt(r, 50))
w := uint32(datagen.RandomInt(r, 50))

// test expected voting power
hasVotingPower := hasCovenantSig && btcDel.StartHeight <= btcHeight && btcHeight+w <= btcDel.EndHeight
actualVotingPower := btcDel.VotingPower(btcHeight, w, 1)
hasVotingPower := hasCovenantSig && btcDel.StartHeight <= btcHeight && btcHeight+unbondingTime <= btcDel.EndHeight
actualVotingPower := btcDel.VotingPower(btcHeight, 1)
if hasVotingPower {
require.Equal(t, btcDel.TotalSat, actualVotingPower)
} else {
Expand Down

0 comments on commit aa32cd4

Please sign in to comment.