Skip to content

Commit

Permalink
feat: add cli to create babylon create btc delegation from staking tx
Browse files Browse the repository at this point in the history
  • Loading branch information
RafilxTenfen committed Nov 20, 2024
1 parent 7a53e09 commit ea40b9f
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 74 deletions.
57 changes: 22 additions & 35 deletions cmd/stakercli/daemon/daemoncommands.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package daemon
import (
"context"
"errors"
"fmt"
"strconv"

"github.com/babylonlabs-io/btc-staker/cmd/stakercli/helpers"
scfg "github.com/babylonlabs-io/btc-staker/stakercfg"
dc "github.com/babylonlabs-io/btc-staker/stakerservice/client"
"github.com/babylonlabs-io/networks/parameters/parser"
"github.com/cometbft/cometbft/libs/os"
"github.com/urfave/cli"
)

Expand Down Expand Up @@ -358,7 +361,7 @@ func stake(ctx *cli.Context) error {

func stakeFromPhase1TxBTC(ctx *cli.Context) error {

Check failure on line 362 in cmd/stakercli/daemon/daemoncommands.go

View workflow job for this annotation

GitHub Actions / lint_test / lint

func `stakeFromPhase1TxBTC` is unused (unused)
daemonAddress := ctx.String(stakingDaemonAddressFlag)
client, err := dc.NewStakerServiceJsonRpcClient(daemonAddress)
client, err := dc.NewStakerServiceJSONRPCClient(daemonAddress)
if err != nil {
return err
}
Expand All @@ -369,44 +372,28 @@ func stakeFromPhase1TxBTC(ctx *cli.Context) error {
if len(stakingTransactionHash) == 0 {
return errors.New("staking tx hash hex is empty")
}
// staking details is not good, because it loads from db, not BTC
// stakingTx, err := client.StakingDetails(sctx, stakingTransactionHash)

// stakingTx.

// sig, err := app.wc.SignBip322NativeSegwit(babylonAddrHash, stakerAddress)

// if err != nil {
// return nil, err
// }

// pop, err := cl.NewBabylonBip322Pop(
// babylonAddrHash,
// sig,
// stakerAddress,
// )

// if err := app.txTracker.AddTransactionSentToBTC(
// stakingTx,
// stakingOutputIdx,
// cmd.stakingTime,
// cmd.fpBtcPks,
// babylonPopToDbPop(cmd.pop),
// cmd.stakerAddress,
// ); err != nil {
// return nil, err
// }

// results, err := client.Stake(sctx, stakerAddress, stakingAmount, fpPks, stakingTimeBlocks, sendToBabylonFirst)
// if err != nil {
// return err
// }
inputGlobalParamsFilePath := ctx.Args().First()
if len(inputGlobalParamsFilePath) == 0 {
return errors.New("json file input is empty")
}

// client.WatchStaking()
if !os.FileExists(inputGlobalParamsFilePath) {
return fmt.Errorf("json file input %s does not exist", inputGlobalParamsFilePath)
}

// helpers.PrintRespJSON(results)
// QUEST: should the params be loaded from the chain?
// maybe it is good to still use the global as input as this is actually
// a phase1 tx being transitioned, so the user would already have the global
// params in hand to create the BTC staking tx
globalParams, err := parser.NewParsedGlobalParamsFromFile(inputGlobalParamsFilePath)
if err != nil {
return fmt.Errorf("error parsing file %s: %w", inputGlobalParamsFilePath, err)
}

return nil
stakerAddress := ctx.String(stakerAddressFlag)
_, err = client.BtcDelegationFromBtcStakingTx(sctx, stakerAddress, stakingTransactionHash, globalParams)
return err
}

func unstake(ctx *cli.Context) error {
Expand Down
1 change: 1 addition & 0 deletions staker/babylontypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func (app *App) buildDelegation(
)
return dg, nil
}

return app.buildOwnedDelegation(
req,
stakerAddress,
Expand Down
83 changes: 66 additions & 17 deletions staker/stakerapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/avast/retry-go/v4"
"github.com/babylonlabs-io/babylon/btcstaking"

Check failure on line 12 in staker/stakerapp.go

View workflow job for this annotation

GitHub Actions / lint_test / lint

ST1019: package "github.com/babylonlabs-io/babylon/btcstaking" is being imported more than once (stylecheck)
staking "github.com/babylonlabs-io/babylon/btcstaking"

Check failure on line 13 in staker/stakerapp.go

View workflow job for this annotation

GitHub Actions / lint_test / lint

ST1019(related information): other import of "github.com/babylonlabs-io/babylon/btcstaking" (stylecheck)
cl "github.com/babylonlabs-io/btc-staker/babylonclient"
"github.com/babylonlabs-io/btc-staker/metrics"
Expand All @@ -22,7 +23,6 @@ import (

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
Expand Down Expand Up @@ -507,6 +507,33 @@ func (app *App) mustSetTxSpentOnBtc(hash *chainhash.Hash) {
}
}

// AddBTCTransactionToDbAndCheckStatus receives a BTC staking transaction that should be
// already included in the BTC network
func (app *App) AddBTCTransactionToDbAndCheckStatus(

Check failure on line 512 in staker/stakerapp.go

View workflow job for this annotation

GitHub Actions / lint_test / lint

ST1003: method AddBTCTransactionToDbAndCheckStatus should be AddBTCTransactionToDBAndCheckStatus (stylecheck)
stakerAddr btcutil.Address,
stkTx *wire.MsgTx,
parsedStakingTx *btcstaking.ParsedV0StakingTx,
) error {
pop, err := app.createPop(stakerAddr)
if err != nil {
return err
}

if err := app.addTransactionSentToBTC(
stkTx,
uint32(parsedStakingTx.StakingOutputIdx),
parsedStakingTx.OpReturnData.StakingTime,
[]*btcec.PublicKey{parsedStakingTx.OpReturnData.FinalityProviderPublicKey.PubKey},
babylonPopToDBPop(pop),
stakerAddr,
); err != nil {
return err
}

// not fun to call check transaction, because there is no request and way to get the tx result
return app.checkTransactionsStatus()
}

// TODO: We should also handle case when btc node or babylon node lost data and start from scratch
// i.e keep track what is last known block height on both chains and detect if after restart
// for some reason they are behind staker
Expand Down Expand Up @@ -1826,25 +1853,11 @@ func (app *App) StakeFunds(
// build proof of possession, no point moving forward if staker do not have all
// the necessary keys
stakerPubKey, err := app.wc.AddressPublicKey(stakerAddress)

if err != nil {
return nil, err
}

babylonAddrHash := tmhash.Sum(app.babylonClient.GetKeyAddress().Bytes())

sig, err := app.wc.SignBip322NativeSegwit(babylonAddrHash, stakerAddress)

if err != nil {
return nil, err
}

pop, err := cl.NewBabylonBip322Pop(
babylonAddrHash,
sig,
stakerAddress,
)

pop, err := app.createPop(stakerAddress)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -2208,6 +2221,42 @@ func (app *App) UnbondStaking(
return &unbondingTxHash, nil
}

func (app *StakerApp) TxDetailsBTC(stakingTxHash *chainhash.Hash) (*btcjson.GetTransactionResult, *btcutil.Tx, error) {
func (app *App) addTransactionSentToBTC(
stakingTx *wire.MsgTx,
stakingOutputIdx uint32,
stakingTime uint16,
fpPubKeys []*btcec.PublicKey,
pop *stakerdb.ProofOfPossession,
btcStakerAddr btcutil.Address,
) error {
return app.txTracker.AddTransactionSentToBTC(
stakingTx,
stakingOutputIdx,
stakingTime,
fpPubKeys,
pop,
btcStakerAddr,
)
}

func (app *App) BtcParams() *chaincfg.Params {
return app.network
}

func (app *App) TxDetailsBTC(stakingTxHash *chainhash.Hash) (*btcutil.Tx, error) {
return app.wc.Tx(stakingTxHash)
}

func (app *App) createPop(stakerAddress btcutil.Address) (*cl.BabylonPop, error) {
babylonAddrHash := tmhash.Sum(app.babylonClient.GetKeyAddress().Bytes())
sig, err := app.wc.SignBip322NativeSegwit(babylonAddrHash, stakerAddress)
if err != nil {
return nil, err
}

return cl.NewBabylonBip322Pop(
babylonAddrHash,
sig,
stakerAddress,
)
}
21 changes: 21 additions & 0 deletions stakerservice/client/rpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

service "github.com/babylonlabs-io/btc-staker/stakerservice"
"github.com/babylonlabs-io/networks/parameters/parser"
jsonrpcclient "github.com/cometbft/cometbft/rpc/jsonrpc/client"
)

Expand Down Expand Up @@ -85,6 +86,26 @@ func (c *StakerServiceJSONRPCClient) Stake(
return result, nil
}

func (c *StakerServiceJSONRPCClient) BtcDelegationFromBtcStakingTx(
ctx context.Context,
stakerAddress string,
btcStkTxHash string,
globalParams *parser.ParsedGlobalParams,
) (*service.ResultBtcDelegationFromBtcStakingTx, error) {
result := new(service.ResultBtcDelegationFromBtcStakingTx)

params := make(map[string]interface{})
params["stakerAddress"] = stakerAddress
params["btcStkTxHash"] = btcStkTxHash
params["globalParams"] = globalParams

_, err := c.client.Call(ctx, "btc_delegation_from_btc_staking_tx", params, result)
if err != nil {
return nil, err
}
return result, nil
}

func (c *StakerServiceJSONRPCClient) ListStakingTransactions(ctx context.Context, offset *int, limit *int) (*service.ListStakingTransactionsResponse, error) {
result := new(service.ListStakingTransactionsResponse)

Expand Down
61 changes: 50 additions & 11 deletions stakerservice/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ import (
"strings"
"sync/atomic"

"github.com/babylonlabs-io/babylon/btcstaking"
"github.com/babylonlabs-io/btc-staker/babylonclient"
str "github.com/babylonlabs-io/btc-staker/staker"
scfg "github.com/babylonlabs-io/btc-staker/stakercfg"
"github.com/babylonlabs-io/btc-staker/stakerdb"
"github.com/babylonlabs-io/networks/parameters/parser"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/cometbft/cometbft/libs/log"
Expand Down Expand Up @@ -125,20 +128,56 @@ func (s *StakerService) stake(_ *rpctypes.Context,
}, nil
}

func (s *StakerService) bbnStakeFromBTCStakingTx(_ *rpctypes.Context,
func (s *StakerService) btcDelegationFromBtcStakingTx(
_ *rpctypes.Context,
stakerAddress string,
btcStkTxHash string,
) (*struct{}, error) {
globalParams *parser.ParsedGlobalParams,
) (*ResultBtcDelegationFromBtcStakingTx, error) {
stkTxHash, err := chainhash.NewHashFromStr(btcStkTxHash)
if err != nil {
return nil, err
}

tx, _, err := s.staker.TxDetailsBTC(stkTxHash)
stkTx, err := s.staker.TxDetailsBTC(stkTxHash)
if err != nil {
return nil, err
}

stakerAddr, err := btcutil.DecodeAddress(stakerAddress, &s.config.ActiveNetParams)
if err != nil {
return nil, err
}

wireStkTx := stkTx.MsgTx()
parsedStakingTx, err := parseV0StakingTx(globalParams, s.staker.BtcParams(), wireStkTx)
if err != nil {
return nil, err
}

return nil, nil
if err := s.staker.AddBTCTransactionToDbAndCheckStatus(stakerAddr, wireStkTx, parsedStakingTx); err != nil {
return nil, err
}

return &ResultBtcDelegationFromBtcStakingTx{}, nil
}

func parseV0StakingTx(globalParams *parser.ParsedGlobalParams, btcParams *chaincfg.Params, wireStkTx *wire.MsgTx) (*btcstaking.ParsedV0StakingTx, error) {
for i := len(globalParams.Versions) - 1; i >= 0; i-- {
params := globalParams.Versions[i]
parsedStakingTx, err := btcstaking.ParseV0StakingTx(
wireStkTx,
params.Tag,
params.CovenantPks,
params.CovenantQuorum,
btcParams,
)
if err != nil {
continue
}
return parsedStakingTx, nil
}
return nil, fmt.Errorf("failed to parse BTC staking tx %s using the global params %+v", wireStkTx.TxHash().String(), globalParams)
}

func (s *StakerService) stakingDetails(_ *rpctypes.Context,
Expand Down Expand Up @@ -562,13 +601,13 @@ func (s *StakerService) GetRoutes() RoutesMap {
// info AP
"health": rpc.NewRPCFunc(s.health, ""),
// staking API
"stake": rpc.NewRPCFunc(s.stake, "stakerAddress,stakingAmount,fpBtcPks,stakingTimeBlocks,sendToBabylonFirst"),
"bbnStakeFromBTCStakingTx": rpc.NewRPCFunc(s.bbnStakeFromBTCStakingTx, "btcStkTxHash"),
"staking_details": rpc.NewRPCFunc(s.stakingDetails, "stakingTxHash"),
"spend_stake": rpc.NewRPCFunc(s.spendStake, "stakingTxHash"),
"list_staking_transactions": rpc.NewRPCFunc(s.listStakingTransactions, "offset,limit"),
"unbond_staking": rpc.NewRPCFunc(s.unbondStaking, "stakingTxHash"),
"withdrawable_transactions": rpc.NewRPCFunc(s.withdrawableTransactions, "offset,limit"),
"stake": rpc.NewRPCFunc(s.stake, "stakerAddress,stakingAmount,fpBtcPks,stakingTimeBlocks,sendToBabylonFirst"),
"btc_delegation_from_btc_staking_tx": rpc.NewRPCFunc(s.btcDelegationFromBtcStakingTx, "stakerAddress,btcStkTxHash,globalParams"),
"staking_details": rpc.NewRPCFunc(s.stakingDetails, "stakingTxHash"),
"spend_stake": rpc.NewRPCFunc(s.spendStake, "stakingTxHash"),
"list_staking_transactions": rpc.NewRPCFunc(s.listStakingTransactions, "offset,limit"),
"unbond_staking": rpc.NewRPCFunc(s.unbondStaking, "stakingTxHash"),
"withdrawable_transactions": rpc.NewRPCFunc(s.withdrawableTransactions, "offset,limit"),
// watch api
"watch_staking_tx": rpc.NewRPCFunc(s.watchStaking, "stakingTx,stakingTime,stakingValue,stakerBtcPk,fpBtcPks,slashingTx,slashingTxSig,stakerBabylonAddr,stakerAddress,stakerBtcSig,unbondingTx,slashUnbondingTx,slashUnbondingTxSig,unbondingTime,popType"),

Expand Down
2 changes: 2 additions & 0 deletions stakerservice/stakerdresponses.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package stakerservice

type ResultHealth struct{}

type ResultBtcDelegationFromBtcStakingTx struct{}

type ResultStake struct {
TxHash string `json:"tx_hash"`
}
Expand Down
13 changes: 4 additions & 9 deletions walletcontroller/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,19 +268,14 @@ func (w *RPCWalletController) getTxDetails(req notifier.ConfRequest, msg string)
return res, nofitierStateToWalletState(state), nil
}

// Tx implements WalletController.
func (w *RpcWalletController) Tx(txHash *chainhash.Hash) (*btcjson.GetTransactionResult, *btcutil.Tx, error) {
tx, err := w.Client.GetTransaction(txHash)
if err != nil {
return nil, nil, err
}

// Tx returns the raw transaction based on the transaction hash
func (w *RPCWalletController) Tx(txHash *chainhash.Hash) (*btcutil.Tx, error) {
rawTx, err := w.Client.GetRawTransaction(txHash)
if err != nil {
return nil, nil, err
return nil, err
}

return tx, rawTx, nil
return rawTx, nil
}

// Fetch info about transaction from mempool or blockchain, requires node to have enabled transaction index
Expand Down
3 changes: 1 addition & 2 deletions walletcontroller/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package walletcontroller
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
Expand Down Expand Up @@ -67,7 +66,7 @@ type WalletController interface {
SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) (*chainhash.Hash, error)
ListOutputs(onlySpendable bool) ([]Utxo, error)
TxDetails(txHash *chainhash.Hash, pkScript []byte) (*notifier.TxConfirmation, TxStatus, error)
Tx(txHash *chainhash.Hash) (*btcjson.GetTransactionResult, *btcutil.Tx, error)
Tx(txHash *chainhash.Hash) (*btcutil.Tx, error)
SignBip322NativeSegwit(msg []byte, address btcutil.Address) (wire.TxWitness, error)
// SignOneInputTaprootSpendingTransaction signs transactions with one taproot input that
// uses script spending path.
Expand Down

0 comments on commit ea40b9f

Please sign in to comment.