diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index e2ffa041e..232ba358b 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -345,12 +345,28 @@ func (ms msgServer) AddBTCDelegationInclusionProof( return nil, fmt.Errorf("invalid inclusion proof: %w", err) } - // 6. set start height and end height and save it to db + // 6. Check if parameters used the validate /create the delegation are the same + // as the one in the BTC light client + _, version, err := ms.GetParamsForBtcHeight(ctx, uint64(timeInfo.StartHeight)) + if err != nil { + return nil, err + } + + if btcDel.ParamsVersion != version { + return nil, types.ErrParamsVersionMismatch.Wrapf( + "params version in BTC delegation: %d, params version at height %d: %d", + btcDel.ParamsVersion, + timeInfo.StartHeight, + version, + ) + } + + // 7. set start height and end height and save it to db btcDel.StartHeight = timeInfo.StartHeight btcDel.EndHeight = timeInfo.EndHeight ms.setBTCDelegation(ctx, btcDel) - // 7. emit events + // 8. emit events stakingTxHash := btcDel.MustGetStakingTxHash() newInclusionProofEvent := types.NewInclusionProofEvent( diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index 29cbebf89..df1362a9a 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -387,6 +387,93 @@ func TestProperVersionInDelegation(t *testing.T) { require.Equal(t, uint32(2), actualDel1.ParamsVersion) } +func TestRejectActivationOfTheDelegationCreatedWithOldParams(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // mock BTC light client and BTC checkpoint modules + btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) + btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) + h := testutil.NewHelper(t, btclcKeeper, btccKeeper) + + // set all parameters + covenantSKs, _ := h.GenAndApplyParams(r) + + changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) + require.NoError(t, err) + + // generate and insert new finality provider + _, fpPK, _ := h.CreateFinalityProvider(r) + + // create fresh version of params + currentParams := h.BTCStakingKeeper.GetParams(h.Ctx) + // params will be activate at block height 2 + currentParams.BtcActivationHeight = currentParams.BtcActivationHeight + 1 + // Update new params + err = h.BTCStakingKeeper.SetParams(h.Ctx, currentParams) + require.NoError(t, err) + + // generate and insert new BTC delegation + stakingValue := int64(2 * 10e8) + delSK, _, err := datagen.GenRandomBTCKeyPair(r) + h.NoError(err) + stakingTxHash, msgCreateBTCDel, _, headerInfo, inclusionProof, _, err := h.CreateDelegationWithBtcBlockHeight( + r, + delSK, + fpPK, + changeAddress.EncodeAddress(), + stakingValue, + 1000, + 0, + 0, + // use the pre-approval flow + true, + false, + // staking tx will be included in BTC block height 1, which is before the activation of the new params + 1, + ) + h.NoError(err) + require.NotNil(t, headerInfo) + require.NotNil(t, inclusionProof) + + // ensure consistency between the msg and the BTC delegation in DB + actualDel, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + require.NotNil(t, actualDel) + + msgs := h.GenerateCovenantSignaturesMessages(r, covenantSKs, msgCreateBTCDel, actualDel) + for _, msg := range msgs { + _, err = h.MsgServer.AddCovenantSigs(h.Ctx, msg) + h.NoError(err) + } + + // get updated delegation + actualDel, err = h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) + h.NoError(err) + require.NotNil(t, actualDel) + + 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) + require.Equal(t, types.BTCDelegationStatus_VERIFIED, status) + + msg := &types.MsgAddBTCDelegationInclusionProof{ + StakingTxHash: stakingTxHash, + StakingTxInclusionProof: inclusionProof, + } + + // mock BTC header that includes the staking tx + h.BTCLightClientKeeper.EXPECT().GetHeaderByHash(gomock.Eq(h.Ctx), gomock.Eq(headerInfo.Header.Hash())).Return(headerInfo, nil).AnyTimes() + + // Call the AddBTCDelegationInclusionProof handler + _, err = h.MsgServer.AddBTCDelegationInclusionProof(h.Ctx, msg) + h.Error(err) + require.ErrorAs(t, err, &types.ErrBTCDelegationNotFound) +} + func FuzzAddCovenantSigs(f *testing.F) { datagen.AddRandomSeedsToFuzzer(f, 10) diff --git a/x/btcstaking/types/errors.go b/x/btcstaking/types/errors.go index a213d28ae..d44a12195 100644 --- a/x/btcstaking/types/errors.go +++ b/x/btcstaking/types/errors.go @@ -30,4 +30,5 @@ var ( ErrFpAlreadyJailed = errorsmod.Register(ModuleName, 1121, "the finality provider has already been jailed") ErrFpNotJailed = errorsmod.Register(ModuleName, 1122, "the finality provider is not jailed") ErrDuplicatedCovenantSig = errorsmod.Register(ModuleName, 1123, "the covenant signature is already submitted") + ErrParamsVersionMismatch = errorsmod.Register(ModuleName, 1124, "the parameters version in the BTC delegation is different from the parameters active at staking transaction inclusion height") )