Skip to content

Commit

Permalink
separate inclusion proof handler
Browse files Browse the repository at this point in the history
  • Loading branch information
gitferry committed Oct 1, 2024
1 parent 0196e09 commit 813356d
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 123 deletions.
37 changes: 21 additions & 16 deletions x/btcstaking/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (

asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature"
bbn "github.com/babylonlabs-io/babylon/types"
btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types"
"github.com/babylonlabs-io/babylon/x/btcstaking/types"
)

Expand Down Expand Up @@ -190,8 +189,8 @@ func NewEditFinalityProviderCmd() *cobra.Command {

func NewCreateBTCDelegationCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create-btc-delegation [btc_pk] [pop_hex] [staking_tx_info] [fp_pk] [staking_time] [staking_value] [slashing_tx] [delegator_slashing_sig] [unbonding_tx] [unbonding_slashing_tx] [unbonding_time] [unbonding_value] [delegator_unbonding_slashing_sig]",
Args: cobra.ExactArgs(13),
Use: "create-btc-delegation [btc_pk] [pop_hex] [staking_tx] [inclusion_proof] [fp_pk] [staking_time] [staking_value] [slashing_tx] [delegator_slashing_sig] [unbonding_tx] [unbonding_slashing_tx] [unbonding_time] [unbonding_value] [delegator_unbonding_slashing_sig]",
Args: cobra.ExactArgs(14),
Short: "Create a BTC delegation",
Long: strings.TrimSpace(
`Create a BTC delegation.`, // TODO: example
Expand All @@ -215,67 +214,72 @@ func NewCreateBTCDelegationCmd() *cobra.Command {
return err
}

// get staking tx info
stakingTxInfo, err := btcctypes.NewTransactionInfoFromHex(args[2])
// get staking tx bytes
stakingTx, err := hex.DecodeString(args[2])
if err != nil {
return err
}

inclusionProof, err := types.NewInclusionProofFromHex(args[3])
if err != nil {
return err
}

// TODO: Support multiple finality providers
// get finality provider PK
fpPK, err := bbn.NewBIP340PubKeyFromHex(args[3])
fpPK, err := bbn.NewBIP340PubKeyFromHex(args[4])
if err != nil {
return err
}

// get staking time
stakingTime, err := parseLockTime(args[4])
stakingTime, err := parseLockTime(args[5])
if err != nil {
return err
}

stakingValue, err := parseBtcAmount(args[5])
stakingValue, err := parseBtcAmount(args[6])
if err != nil {
return err
}

// get slashing tx
slashingTx, err := types.NewBTCSlashingTxFromHex(args[6])
slashingTx, err := types.NewBTCSlashingTxFromHex(args[7])
if err != nil {
return err
}

// get delegator sig on slashing tx
delegatorSlashingSig, err := bbn.NewBIP340SignatureFromHex(args[7])
delegatorSlashingSig, err := bbn.NewBIP340SignatureFromHex(args[8])
if err != nil {
return err
}

// get unbonding tx
_, unbondingTxBytes, err := bbn.NewBTCTxFromHex(args[8])
_, unbondingTxBytes, err := bbn.NewBTCTxFromHex(args[9])
if err != nil {
return err
}

// get unbonding slashing tx
unbondingSlashingTx, err := types.NewBTCSlashingTxFromHex(args[9])
unbondingSlashingTx, err := types.NewBTCSlashingTxFromHex(args[10])
if err != nil {
return err
}

// get staking time
unbondingTime, err := parseLockTime(args[10])
unbondingTime, err := parseLockTime(args[11])
if err != nil {
return err
}

unbondingValue, err := parseBtcAmount(args[11])
unbondingValue, err := parseBtcAmount(args[12])
if err != nil {
return err
}

// get delegator sig on unbonding slashing tx
delegatorUnbondingSlashingSig, err := bbn.NewBIP340SignatureFromHex(args[12])
delegatorUnbondingSlashingSig, err := bbn.NewBIP340SignatureFromHex(args[13])
if err != nil {
return err
}
Expand All @@ -287,7 +291,8 @@ func NewCreateBTCDelegationCmd() *cobra.Command {
Pop: pop,
StakingTime: uint32(stakingTime),
StakingValue: int64(stakingValue),
StakingTx: stakingTxInfo,
StakingTx: stakingTx,
StakingTxInclusionProof: inclusionProof,
SlashingTx: slashingTx,
DelegatorSlashingSig: delegatorSlashingSig,
UnbondingTx: unbondingTxBytes,
Expand Down
27 changes: 16 additions & 11 deletions x/btcstaking/keeper/btc_delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"fmt"

"cosmossdk.io/store/prefix"
asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature"
bbn "github.com/babylonlabs-io/babylon/types"
"github.com/babylonlabs-io/babylon/x/btcstaking/types"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"

asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature"
bbn "github.com/babylonlabs-io/babylon/types"
"github.com/babylonlabs-io/babylon/x/btcstaking/types"
)

// AddBTCDelegation adds a BTC delegation post verification to the system, including
Expand Down Expand Up @@ -58,14 +59,18 @@ func (k Keeper) AddBTCDelegation(ctx sdk.Context, btcDel *types.BTCDelegation) e

// NOTE: we don't need to record events for pending BTC delegations since these
// do not affect voting power distribution

// record event that the BTC delegation will become unbonded at endHeight-w
unbondedEvent := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{
StakingTxHash: stakingTxHash.String(),
NewState: types.BTCDelegationStatus_UNBONDED,
})
wValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout
k.addPowerDistUpdateEvent(ctx, btcDel.EndHeight-wValue, unbondedEvent)
// NOTE: we only insert unbonded event if EndHeight > 0, indicating that the
// delegation is already included on BTC with sufficient confirmations
if btcDel.EndHeight > 0 {
// record event that the BTC delegation will become unbonded at endHeight-w
unbondedEvent := types.NewEventPowerDistUpdateWithBTCDel(&types.EventBTCDelegationStateUpdate{
StakingTxHash: stakingTxHash.String(),
NewState: types.BTCDelegationStatus_UNBONDED,
})
// NOTE: we should have verified that EndHeight > btcTip.Height + CheckpointFinalizationTimeout
wValue := k.btccKeeper.GetParams(ctx).CheckpointFinalizationTimeout
k.addPowerDistUpdateEvent(ctx, btcDel.EndHeight-wValue, unbondedEvent)
}

return nil
}
Expand Down
62 changes: 62 additions & 0 deletions x/btcstaking/keeper/inclusion_proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package keeper

import (
"fmt"

"github.com/btcsuite/btcd/btcutil"
sdk "github.com/cosmos/cosmos-sdk/types"

btcckpttypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types"
"github.com/babylonlabs-io/babylon/x/btcstaking/types"
)

// VerifyInclusionProofAndGetHeight verifies the inclusion proof of the given staking tx
// and returns the inclusion height
func (k Keeper) VerifyInclusionProofAndGetHeight(
ctx sdk.Context,
stakingTx *btcutil.Tx,
stakingTime uint64,
inclusionProof *types.ParsedProofOfInclusion,
) (uint64, error) {
btccParams := k.btccKeeper.GetParams(ctx)
// Check:
// - timelock of staking tx
// - staking tx is k-deep
// - staking tx inclusion proof
stakingTxHeader := k.btclcKeeper.GetHeaderByHash(ctx, inclusionProof.HeaderHash)

if stakingTxHeader == nil {
return 0, fmt.Errorf("header that includes the staking tx is not found")
}

// no need to do more validations to the btc header as it was already
// validated by the btclightclient module
btcHeader := stakingTxHeader.Header.ToBlockHeader()

proofValid := btcckpttypes.VerifyInclusionProof(
stakingTx,
&btcHeader.MerkleRoot,
inclusionProof.Proof,
inclusionProof.Index,
)

if !proofValid {
return 0, types.ErrInvalidStakingTx.Wrapf("not included in the Bitcoin chain")
}

startHeight := stakingTxHeader.Height
//
endHeight := stakingTxHeader.Height + stakingTime

btcTip := k.btclcKeeper.GetTipInfo(ctx)
stakingTxDepth := btcTip.Height - stakingTxHeader.Height
if stakingTxDepth < btccParams.BtcConfirmationDepth {
return 0, types.ErrInvalidStakingTx.Wrapf("not k-deep: k=%d; depth=%d", btccParams.BtcConfirmationDepth, stakingTxDepth)
}
// ensure staking tx's timelock has more than w BTC blocks left
if btcTip.Height+btccParams.CheckpointFinalizationTimeout >= endHeight {
return 0, types.ErrInvalidStakingTx.Wrapf("staking tx's timelock has no more than w(=%d) blocks left", btccParams.CheckpointFinalizationTimeout)
}

return startHeight, nil
}
29 changes: 4 additions & 25 deletions x/btcstaking/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func (h *Helper) CreateDelegationCustom(
serializedStakingTx, err := bbn.SerializeBTCTx(testStakingInfo.StakingTx)
h.NoError(err)

txInfo := btcctypes.NewTransactionInfo(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, serializedStakingTx, btcHeaderWithProof.SpvProof.MerkleNodes)
txInclusionProof := types.NewInclusionProof(&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, btcHeaderWithProof.SpvProof.MerkleNodes)

// mock for testing k-deep stuff
h.BTCLightClientKeeper.EXPECT().GetHeaderByHash(gomock.Eq(h.Ctx), gomock.Eq(btcHeader.Hash())).Return(&btclctypes.BTCHeaderInfo{Header: &btcHeader, Height: 10}).AnyTimes()
Expand Down Expand Up @@ -260,7 +260,8 @@ func (h *Helper) CreateDelegationCustom(
Pop: pop,
StakingTime: uint32(stakingTimeBlocks),
StakingValue: stakingValue,
StakingTx: txInfo,
StakingTx: serializedStakingTx,
StakingTxInclusionProof: txInclusionProof,
SlashingTx: testStakingInfo.SlashingTx,
DelegatorSlashingSig: delegatorSig,
UnbondingTx: serializedUnbondingTx,
Expand Down Expand Up @@ -305,7 +306,7 @@ func (h *Helper) CreateDelegation(

h.NoError(err)

stakingMsgTx, err := bbn.NewBTCTxFromBytes(msgCreateBTCDel.StakingTx.Transaction)
stakingMsgTx, err := bbn.NewBTCTxFromBytes(msgCreateBTCDel.StakingTx)
h.NoError(err)
btcDel, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingMsgTx.TxHash().String())
h.NoError(err)
Expand Down Expand Up @@ -422,25 +423,3 @@ func (h *Helper) CreateCovenantSigs(
require.Len(h.t, actualDelWithCovenantSigs.BtcUndelegation.CovenantSlashingSigs[0].AdaptorSigs, 1)

}

func (h *Helper) GetDelegationAndCheckValues(
r *rand.Rand,
msgCreateBTCDel *types.MsgCreateBTCDelegation,
fpPK *btcec.PublicKey,
delegatorPK *btcec.PublicKey,
stakingTxHash string,
) *types.BTCDelegation {
actualDel, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash)
h.NoError(err)
// TODO: update pop in BTC delegation
require.Equal(h.t, msgCreateBTCDel.StakerAddr, actualDel.StakerAddr)
require.Equal(h.t, msgCreateBTCDel.Pop, actualDel.Pop)
require.Equal(h.t, msgCreateBTCDel.StakingTx.Transaction, actualDel.StakingTx)
require.Equal(h.t, msgCreateBTCDel.SlashingTx, actualDel.SlashingTx)
// ensure the BTC delegation in DB is correctly formatted
err = actualDel.ValidateBasic()
h.NoError(err)
// delegation is not activated by covenant yet
require.False(h.t, actualDel.HasCovenantQuorums(h.BTCStakingKeeper.GetParams(h.Ctx).CovenantQuorum))
return actualDel
}
55 changes: 17 additions & 38 deletions x/btcstaking/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"strings"
"time"

btcckpttypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
"github.com/btcsuite/btcd/btcec/v2"
Expand Down Expand Up @@ -173,43 +171,24 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre
return nil, err
}

// 6. Check:
// - timelock of staking tx
// - staking tx is k-deep
// - staking tx inclusion proof
stakingTxHeader := ms.btclcKeeper.GetHeaderByHash(ctx, parsedMsg.StakingTxProofOfInclusion.HeaderHash)

if stakingTxHeader == nil {
return nil, fmt.Errorf("header that includes the staking tx is not found")
}

// no need to do more validations to the btc header as it was already
// validated by the btclightclient module
btcHeader := stakingTxHeader.Header.ToBlockHeader()

proofValid := btcckpttypes.VerifyInclusionProof(
btcutil.NewTx(parsedMsg.StakingTx.Transaction),
&btcHeader.MerkleRoot,
parsedMsg.StakingTxProofOfInclusion.Proof,
parsedMsg.StakingTxProofOfInclusion.Index,
)

if !proofValid {
return nil, types.ErrInvalidStakingTx.Wrapf("not included in the Bitcoin chain")
}

startHeight := stakingTxHeader.Height

endHeight := stakingTxHeader.Height + uint64(parsedMsg.StakingTime)
// 6. If the delegation contains the inclusion proof, we need to verify the proof
// and set start height and end height
var startHeight, endHeight uint64
if parsedMsg.StakingTxProofOfInclusion != nil {
inclusionHeight, err := ms.VerifyInclusionProofAndGetHeight(
ctx,
btcutil.NewTx(parsedMsg.StakingTx.Transaction),
uint64(parsedMsg.StakingTime),
parsedMsg.StakingTxProofOfInclusion)
if err != nil {
return nil, fmt.Errorf("invalid inclusion proof: %w", err)
}

btcTip := ms.btclcKeeper.GetTipInfo(ctx)
stakingTxDepth := btcTip.Height - stakingTxHeader.Height
if stakingTxDepth < btccParams.BtcConfirmationDepth {
return nil, types.ErrInvalidStakingTx.Wrapf("not k-deep: k=%d; depth=%d", btccParams.BtcConfirmationDepth, stakingTxDepth)
}
// ensure staking tx's timelock has more than w BTC blocks left
if btcTip.Height+btccParams.CheckpointFinalizationTimeout >= endHeight {
return nil, types.ErrInvalidStakingTx.Wrapf("staking tx's timelock has no more than w(=%d) blocks left", btccParams.CheckpointFinalizationTimeout)
startHeight = inclusionHeight
endHeight = startHeight + uint64(parsedMsg.StakingTime)
} else {
// consume base gas fee to compensate future submission of inclusion proof
ctx.GasMeter().ConsumeGas(ms.GetParams(ctx).DelegationCreationBaseGasFee, "delegation creation fee")
}

// 7.all good, construct BTCDelegation and insert BTC delegation
Expand Down
8 changes: 4 additions & 4 deletions x/btcstaking/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func FuzzCreateBTCDelegation(f *testing.F) {
h.NoError(err)
require.Equal(h.t, msgCreateBTCDel.StakerAddr, actualDel.StakerAddr)
require.Equal(h.t, msgCreateBTCDel.Pop, actualDel.Pop)
require.Equal(h.t, msgCreateBTCDel.StakingTx.Transaction, actualDel.StakingTx)
require.Equal(h.t, msgCreateBTCDel.StakingTx, actualDel.StakingTx)
require.Equal(h.t, msgCreateBTCDel.SlashingTx, actualDel.SlashingTx)
// ensure the BTC delegation in DB is correctly formatted
err = actualDel.ValidateBasic()
Expand Down Expand Up @@ -596,9 +596,8 @@ func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) {
prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil)
btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, stakingMsgTx)
btcHeader := btcHeaderWithProof.HeaderBytes
txInfo := btcctypes.NewTransactionInfo(
txInclusionProof := types.NewInclusionProof(
&btcctypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()},
serializedStakingTx,
btcHeaderWithProof.SpvProof.MerkleNodes,
)

Expand Down Expand Up @@ -646,7 +645,8 @@ func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) {
Pop: pop,
StakingTime: uint32(stakingTimeBlocks),
StakingValue: stakingValue,
StakingTx: txInfo,
StakingTx: serializedStakingTx,
StakingTxInclusionProof: txInclusionProof,
SlashingTx: testStakingInfo.SlashingTx,
DelegatorSlashingSig: delegatorSig,
UnbondingTx: unbondingTx,
Expand Down
Loading

0 comments on commit 813356d

Please sign in to comment.