diff --git a/CHANGELOG.md b/CHANGELOG.md index 045600608..62d3e9774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/testutil/btcstaking-helper/keeper.go b/testutil/btcstaking-helper/keeper.go index 8c86a5616..5c39fc655 100644 --- a/testutil/btcstaking-helper/keeper.go +++ b/testutil/btcstaking-helper/keeper.go @@ -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) @@ -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 @@ -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) @@ -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) @@ -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 @@ -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") } diff --git a/x/btcstaking/keeper/grpc_query.go b/x/btcstaking/keeper/grpc_query.go index 1f3f45f3d..414bfc259 100644 --- a/x/btcstaking/keeper/grpc_query.go +++ b/x/btcstaking/keeper/grpc_query.go @@ -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 @@ -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) @@ -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 @@ -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) @@ -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, ) diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index 232ba358b..1f5de40f4 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -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") @@ -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") @@ -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") } diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index df1362a9a..672c9d4fb 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -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) @@ -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) @@ -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) @@ -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) @@ -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{ @@ -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) }) } diff --git a/x/btcstaking/types/btc_delegation.go b/x/btcstaking/types/btc_delegation.go index e9a877a26..aba0e8c87 100644 --- a/x/btcstaking/types/btc_delegation.go +++ b/x/btcstaking/types/btc_delegation.go @@ -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 } @@ -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 } @@ -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() @@ -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 -} diff --git a/x/btcstaking/types/btc_delegation_test.go b/x/btcstaking/types/btc_delegation_test.go index b15788dfa..b4fafc6e5 100644 --- a/x/btcstaking/types/btc_delegation_test.go +++ b/x/btcstaking/types/btc_delegation_test.go @@ -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 { @@ -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 {