From 580adf80d1fc9a537aa58155d2a303498d871fa8 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 16 Oct 2024 14:40:09 -0300 Subject: [PATCH 01/38] chore: init command draft --- cmd/stakercli/daemon/daemoncommands.go | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/cmd/stakercli/daemon/daemoncommands.go b/cmd/stakercli/daemon/daemoncommands.go index fe13558..c51c3d9 100644 --- a/cmd/stakercli/daemon/daemoncommands.go +++ b/cmd/stakercli/daemon/daemoncommands.go @@ -133,6 +133,26 @@ var stakeCmd = cli.Command{ Action: stake, } +var stakeFromPhase1Cmd = cli.Command{ + Name: "stake-from-phase1", + ShortName: "stfp1", + Usage: "stake-from-phase1", + Description: "Creates a Babylon staking transaction from the Phase1 BTC staking tx", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: stakingDaemonAddressFlag, + Usage: "full address of the staker daemon in format tcp:://:", + Value: defaultStakingDaemonAddress, + }, + cli.StringFlag{ + Name: stakingTransactionHashFlag, + Usage: "Hash of original staking transaction in bitcoin hex format", + Required: true, + }, + }, + Action: stake, +} + var unstakeCmd = cli.Command{ Name: "unstake", ShortName: "ust", @@ -335,6 +355,33 @@ func stake(ctx *cli.Context) error { return nil } +func stakeFromPhase1TxBTC(ctx *cli.Context) error { + daemonAddress := ctx.String(stakingDaemonAddressFlag) + client, err := dc.NewStakerServiceJsonRpcClient(daemonAddress) + if err != nil { + return err + } + + sctx := context.Background() + + stakingTransactionHash := ctx.String(stakingTransactionHashFlag) + stakingTx, err := client.StakingDetails(sctx, stakingTransactionHash) + if err != nil { + return err + } + + // results, err := client.Stake(sctx, stakerAddress, stakingAmount, fpPks, stakingTimeBlocks, sendToBabylonFirst) + // if err != nil { + // return err + // } + + // client.WatchStaking() + + // helpers.PrintRespJSON(results) + + return nil +} + func unstake(ctx *cli.Context) error { daemonAddress := ctx.String(stakingDaemonAddressFlag) client, err := dc.NewStakerServiceJsonRpcClient(daemonAddress) From db1692a45271879b366923dc997ca63a4b8cde00 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 16 Oct 2024 17:42:24 -0300 Subject: [PATCH 02/38] chore: add get tx details from tx hash only --- walletcontroller/client.go | 15 +++++++++++++++ walletcontroller/interface.go | 2 ++ 2 files changed, 17 insertions(+) diff --git a/walletcontroller/client.go b/walletcontroller/client.go index ce48e1a..bdce0bb 100644 --- a/walletcontroller/client.go +++ b/walletcontroller/client.go @@ -271,6 +271,21 @@ 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 + } + + rawTx, err := w.Client.GetRawTransaction(txHash) + if err != nil { + return nil, nil, err + } + + return tx, rawTx, nil +} + // Fetch info about transaction from mempool or blockchain, requires node to have enabled transaction index func (w *RpcWalletController) TxDetails(txHash *chainhash.Hash, pkScript []byte) (*notifier.TxConfirmation, TxStatus, error) { req, err := notifier.NewConfRequest(txHash, pkScript) diff --git a/walletcontroller/interface.go b/walletcontroller/interface.go index d0e2b2d..66eab7a 100644 --- a/walletcontroller/interface.go +++ b/walletcontroller/interface.go @@ -3,6 +3,7 @@ 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" @@ -66,6 +67,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) SignBip322NativeSegwit(msg []byte, address btcutil.Address) (wire.TxWitness, error) // SignOneInputTaprootSpendingTransaction signs transactions with one taproot input that // uses script spending path. From d76b933c68e685aaf9a1ba06fec4fccba7672e31 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Tue, 5 Nov 2024 14:16:47 -0300 Subject: [PATCH 03/38] chore: add initial impl to btc staking from phase 1 --- cmd/stakercli/daemon/daemoncommands.go | 35 +++++++++++++++++++++++--- staker/stakerapp.go | 5 ++++ stakerservice/service.go | 17 +++++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/cmd/stakercli/daemon/daemoncommands.go b/cmd/stakercli/daemon/daemoncommands.go index c51c3d9..3cf70e7 100644 --- a/cmd/stakercli/daemon/daemoncommands.go +++ b/cmd/stakercli/daemon/daemoncommands.go @@ -2,6 +2,7 @@ package daemon import ( "context" + "errors" "strconv" "github.com/babylonlabs-io/btc-staker/cmd/stakercli/helpers" @@ -150,7 +151,7 @@ var stakeFromPhase1Cmd = cli.Command{ Required: true, }, }, - Action: stake, + Action: stakeFromPhase1TxBTC, } var unstakeCmd = cli.Command{ @@ -365,10 +366,36 @@ func stakeFromPhase1TxBTC(ctx *cli.Context) error { sctx := context.Background() stakingTransactionHash := ctx.String(stakingTransactionHashFlag) - stakingTx, err := client.StakingDetails(sctx, stakingTransactionHash) - if err != nil { - return err + 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 { diff --git a/staker/stakerapp.go b/staker/stakerapp.go index d7813ec..d57881d 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -23,6 +23,7 @@ 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" @@ -2188,3 +2189,7 @@ func (app *StakerApp) UnbondStaking( unbondingTxHash := tx.UnbondingTxData.UnbondingTx.TxHash() return &unbondingTxHash, nil } + +func (app *StakerApp) TxDetailsBTC(stakingTxHash *chainhash.Hash) (*btcjson.GetTransactionResult, *btcutil.Tx, error) { + return app.wc.Tx(stakingTxHash) +} diff --git a/stakerservice/service.go b/stakerservice/service.go index 45f5274..5faa942 100644 --- a/stakerservice/service.go +++ b/stakerservice/service.go @@ -126,6 +126,22 @@ func (s *StakerService) stake(_ *rpctypes.Context, }, nil } +func (s *StakerService) bbnStakeFromBTCStakingTx(_ *rpctypes.Context, + btcStkTxHash string, +) (*struct{}, error) { + stkTxHash, err := chainhash.NewHashFromStr(btcStkTxHash) + if err != nil { + return nil, err + } + + tx, _, err := s.staker.TxDetailsBTC(stkTxHash) + if err != nil { + return nil, err + } + + return nil, nil +} + func (s *StakerService) stakingDetails(_ *rpctypes.Context, stakingTxHash string) (*StakingDetails, error) { @@ -551,6 +567,7 @@ func (s *StakerService) GetRoutes() RoutesMap { "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"), From ea40b9f85ff303e927cd07d62b100d9533052f35 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Tue, 19 Nov 2024 21:00:20 -0300 Subject: [PATCH 04/38] feat: add cli to create babylon create btc delegation from staking tx --- cmd/stakercli/daemon/daemoncommands.go | 57 +++++++----------- staker/babylontypes.go | 1 + staker/stakerapp.go | 83 ++++++++++++++++++++------ stakerservice/client/rpcclient.go | 21 +++++++ stakerservice/service.go | 61 +++++++++++++++---- stakerservice/stakerdresponses.go | 2 + walletcontroller/client.go | 13 ++-- walletcontroller/interface.go | 3 +- 8 files changed, 167 insertions(+), 74 deletions(-) diff --git a/cmd/stakercli/daemon/daemoncommands.go b/cmd/stakercli/daemon/daemoncommands.go index 696cb50..40150d8 100644 --- a/cmd/stakercli/daemon/daemoncommands.go +++ b/cmd/stakercli/daemon/daemoncommands.go @@ -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" ) @@ -358,7 +361,7 @@ func stake(ctx *cli.Context) error { func stakeFromPhase1TxBTC(ctx *cli.Context) error { daemonAddress := ctx.String(stakingDaemonAddressFlag) - client, err := dc.NewStakerServiceJsonRpcClient(daemonAddress) + client, err := dc.NewStakerServiceJSONRPCClient(daemonAddress) if err != nil { return err } @@ -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 { diff --git a/staker/babylontypes.go b/staker/babylontypes.go index 09579e6..f4ee61f 100644 --- a/staker/babylontypes.go +++ b/staker/babylontypes.go @@ -169,6 +169,7 @@ func (app *App) buildDelegation( ) return dg, nil } + return app.buildOwnedDelegation( req, stakerAddress, diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 48b7c22..843a624 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -9,6 +9,7 @@ import ( "time" "github.com/avast/retry-go/v4" + "github.com/babylonlabs-io/babylon/btcstaking" staking "github.com/babylonlabs-io/babylon/btcstaking" cl "github.com/babylonlabs-io/btc-staker/babylonclient" "github.com/babylonlabs-io/btc-staker/metrics" @@ -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" @@ -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( + 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 @@ -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 } @@ -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, + ) +} diff --git a/stakerservice/client/rpcclient.go b/stakerservice/client/rpcclient.go index 48070cc..a23db07 100644 --- a/stakerservice/client/rpcclient.go +++ b/stakerservice/client/rpcclient.go @@ -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" ) @@ -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) diff --git a/stakerservice/service.go b/stakerservice/service.go index d3fe671..3109bc7 100644 --- a/stakerservice/service.go +++ b/stakerservice/service.go @@ -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" @@ -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, @@ -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"), diff --git a/stakerservice/stakerdresponses.go b/stakerservice/stakerdresponses.go index 3a76474..286b136 100644 --- a/stakerservice/stakerdresponses.go +++ b/stakerservice/stakerdresponses.go @@ -2,6 +2,8 @@ package stakerservice type ResultHealth struct{} +type ResultBtcDelegationFromBtcStakingTx struct{} + type ResultStake struct { TxHash string `json:"tx_hash"` } diff --git a/walletcontroller/client.go b/walletcontroller/client.go index dd1efef..8dc0833 100644 --- a/walletcontroller/client.go +++ b/walletcontroller/client.go @@ -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 diff --git a/walletcontroller/interface.go b/walletcontroller/interface.go index 66eab7a..c3ecd23 100644 --- a/walletcontroller/interface.go +++ b/walletcontroller/interface.go @@ -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" @@ -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. From 0374d0bc2238015035b9051a6e94ced2f637fa4b Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Tue, 19 Nov 2024 21:03:29 -0300 Subject: [PATCH 05/38] chore: add #90 to changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f808b53..0bf4789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Unreleased +### Improvements + +* [#90](https://github.com/babylonlabs-io/btc-staker/pull/90) Add CLI to create +babylon BTC delegation from phase-1 BTC staking transaction. + ## v0.11.0 ### Improvements From b41aa7a77c7b0985a12678cba32261d16ab21587 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Tue, 19 Nov 2024 21:19:19 -0300 Subject: [PATCH 06/38] chore: add stake command to CLI --- cmd/stakercli/daemon/daemoncommands.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/stakercli/daemon/daemoncommands.go b/cmd/stakercli/daemon/daemoncommands.go index 40150d8..f5a821c 100644 --- a/cmd/stakercli/daemon/daemoncommands.go +++ b/cmd/stakercli/daemon/daemoncommands.go @@ -30,6 +30,7 @@ var DaemonCommands = []cli.Command{ listStakingTransactionsCmd, withdrawableTransactionsCmd, unbondCmd, + stakeFromPhase1Cmd, }, }, } @@ -140,8 +141,8 @@ var stakeCmd = cli.Command{ var stakeFromPhase1Cmd = cli.Command{ Name: "stake-from-phase1", ShortName: "stfp1", - Usage: "stake-from-phase1", - Description: "Creates a Babylon staking transaction from the Phase1 BTC staking tx", + Usage: "stakercli stake-from-phase1 [fullpath/to/global_parameters.json]", + Description: "Creates a Babylon BTC delegation transaction from the Phase1 BTC staking tx", Flags: []cli.Flag{ cli.StringFlag{ Name: stakingDaemonAddressFlag, From 90d7a953045b4d9affff247a38f796129d63d650 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Tue, 19 Nov 2024 21:30:03 -0300 Subject: [PATCH 07/38] fix: lint --- staker/stakerapp.go | 7 +++---- stakerservice/service.go | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 843a624..234c591 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -9,7 +9,6 @@ import ( "time" "github.com/avast/retry-go/v4" - "github.com/babylonlabs-io/babylon/btcstaking" staking "github.com/babylonlabs-io/babylon/btcstaking" cl "github.com/babylonlabs-io/btc-staker/babylonclient" "github.com/babylonlabs-io/btc-staker/metrics" @@ -507,12 +506,12 @@ func (app *App) mustSetTxSpentOnBtc(hash *chainhash.Hash) { } } -// AddBTCTransactionToDbAndCheckStatus receives a BTC staking transaction that should be +// AddBTCTransactionToDBAndCheckStatus receives a BTC staking transaction that should be // already included in the BTC network -func (app *App) AddBTCTransactionToDbAndCheckStatus( +func (app *App) AddBTCTransactionToDBAndCheckStatus( stakerAddr btcutil.Address, stkTx *wire.MsgTx, - parsedStakingTx *btcstaking.ParsedV0StakingTx, + parsedStakingTx *staking.ParsedV0StakingTx, ) error { pop, err := app.createPop(stakerAddr) if err != nil { diff --git a/stakerservice/service.go b/stakerservice/service.go index 3109bc7..dc1b2d3 100644 --- a/stakerservice/service.go +++ b/stakerservice/service.go @@ -155,7 +155,7 @@ func (s *StakerService) btcDelegationFromBtcStakingTx( return nil, err } - if err := s.staker.AddBTCTransactionToDbAndCheckStatus(stakerAddr, wireStkTx, parsedStakingTx); err != nil { + if err := s.staker.AddBTCTransactionToDBAndCheckStatus(stakerAddr, wireStkTx, parsedStakingTx); err != nil { return nil, err } From 0fe2886db96ca6be72a708b86aebb5a29dd25a91 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 20 Nov 2024 13:47:10 -0300 Subject: [PATCH 08/38] chore: move cli and manager to separated file --- itest/e2e_test.go | 1179 +++---------------------------------- itest/manager.go | 1165 ++++++++++++++++++++++++++++++++++++ itest/testutil/app_cli.go | 67 +++ 3 files changed, 1303 insertions(+), 1108 deletions(-) create mode 100644 itest/manager.go create mode 100644 itest/testutil/app_cli.go diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 7fd123f..5977221 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -4,1141 +4,33 @@ package e2etest import ( - "bytes" "context" "encoding/hex" - "errors" "fmt" - "math/rand" - "net" - "net/netip" - "os" - "path/filepath" - "strconv" - "sync" "testing" "time" "github.com/babylonlabs-io/btc-staker/itest/containers" - "github.com/babylonlabs-io/btc-staker/itest/testutil" "github.com/babylonlabs-io/babylon/crypto/bip322" btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" - "github.com/cometbft/cometbft/crypto/tmhash" - staking "github.com/babylonlabs-io/babylon/btcstaking" - txformat "github.com/babylonlabs-io/babylon/btctxformatter" "github.com/babylonlabs-io/babylon/testutil/datagen" bbntypes "github.com/babylonlabs-io/babylon/types" - btcstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" - ckpttypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" - "github.com/babylonlabs-io/btc-staker/babylonclient" - "github.com/babylonlabs-io/btc-staker/metrics" "github.com/babylonlabs-io/btc-staker/proto" "github.com/babylonlabs-io/btc-staker/staker" "github.com/babylonlabs-io/btc-staker/stakercfg" - service "github.com/babylonlabs-io/btc-staker/stakerservice" - dc "github.com/babylonlabs-io/btc-staker/stakerservice/client" "github.com/babylonlabs-io/btc-staker/types" - "github.com/babylonlabs-io/btc-staker/utils" "github.com/babylonlabs-io/btc-staker/walletcontroller" - "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/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkquerytypes "github.com/cosmos/cosmos-sdk/types/query" - sttypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/lightningnetwork/lnd/kvdb" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" ) -// bitcoin params used for testing -var ( - r = rand.New(rand.NewSource(time.Now().Unix())) - - regtestParams = &chaincfg.RegressionNetParams - - eventuallyWaitTimeOut = 10 * time.Second - eventuallyPollTime = 250 * time.Millisecond -) - -// keyToAddr maps the passed private to corresponding p2pkh address. -func keyToAddr(key *btcec.PrivateKey, net *chaincfg.Params) (btcutil.Address, error) { - serializedKey := key.PubKey().SerializeCompressed() - pubKeyAddr, err := btcutil.NewAddressPubKey(serializedKey, net) - if err != nil { - return nil, err - } - return pubKeyAddr.AddressPubKeyHash(), nil -} - -func defaultStakerConfig(t *testing.T, walletName, passphrase, bitcoindHost string) (*stakercfg.Config, *rpcclient.Client) { - defaultConfig := stakercfg.DefaultConfig() - - // both wallet and node are bicoind - defaultConfig.BtcNodeBackendConfig.ActiveWalletBackend = types.BitcoindWalletBackend - defaultConfig.BtcNodeBackendConfig.ActiveNodeBackend = types.BitcoindNodeBackend - defaultConfig.ActiveNetParams = *regtestParams - - // Fees configuration - defaultConfig.BtcNodeBackendConfig.FeeMode = "dynamic" - defaultConfig.BtcNodeBackendConfig.EstimationMode = types.DynamicFeeEstimation - - bitcoindUser := "user" - bitcoindPass := "pass" - - // Wallet configuration - defaultConfig.WalletRPCConfig.Host = bitcoindHost - defaultConfig.WalletRPCConfig.User = bitcoindUser - defaultConfig.WalletRPCConfig.Pass = bitcoindPass - defaultConfig.WalletRPCConfig.DisableTLS = true - defaultConfig.WalletConfig.WalletPass = passphrase - defaultConfig.WalletConfig.WalletName = walletName - - // node configuration - defaultConfig.BtcNodeBackendConfig.Bitcoind.RPCHost = bitcoindHost - defaultConfig.BtcNodeBackendConfig.Bitcoind.RPCUser = bitcoindUser - defaultConfig.BtcNodeBackendConfig.Bitcoind.RPCPass = bitcoindPass - - // Use rpc polling, as it is our default mode and it is a bit more troublesome - // to configure ZMQ from inside the bitcoind docker container - defaultConfig.BtcNodeBackendConfig.Bitcoind.RPCPolling = true - defaultConfig.BtcNodeBackendConfig.Bitcoind.BlockPollingInterval = 1 * time.Second - defaultConfig.BtcNodeBackendConfig.Bitcoind.TxPollingInterval = 1 * time.Second - - defaultConfig.StakerConfig.BabylonStallingInterval = 1 * time.Second - defaultConfig.StakerConfig.UnbondingTxCheckInterval = 1 * time.Second - defaultConfig.StakerConfig.CheckActiveInterval = 1 * time.Second - - // TODO: After bumping relayer version sending transactions concurrently fails wih - // fatal error: concurrent map writes - // For now diable concurrent sends but this need to be sorted out - defaultConfig.StakerConfig.MaxConcurrentTransactions = 1 - - testRpcClient, err := rpcclient.New(&rpcclient.ConnConfig{ - Host: bitcoindHost, - User: bitcoindUser, - Pass: bitcoindPass, - DisableTLS: true, - DisableConnectOnNew: true, - DisableAutoReconnect: false, - // we use post mode as it sure it works with either bitcoind or btcwallet - // we may need to re-consider it later if we need any notifications - HTTPPostMode: true, - }, nil) - require.NoError(t, err) - - return &defaultConfig, testRpcClient -} - -type TestManager struct { - Config *stakercfg.Config - Db kvdb.Backend - Sa *staker.App - BabylonClient *babylonclient.BabylonController - WalletPubKey *btcec.PublicKey - MinerAddr btcutil.Address - wg *sync.WaitGroup - serviceAddress string - StakerClient *dc.StakerServiceJSONRPCClient - CovenantPrivKeys []*btcec.PrivateKey - BitcoindHandler *BitcoindTestHandler - TestRpcClient *rpcclient.Client - manger *containers.Manager -} - -type testStakingData struct { - StakerKey *btcec.PublicKey - StakerBabylonAddr sdk.AccAddress - FinalityProviderBabylonPrivKeys []*secp256k1.PrivKey - FinalityProviderBabylonAddrs []sdk.AccAddress - FinalityProviderBtcPrivKeys []*btcec.PrivateKey - FinalityProviderBtcKeys []*btcec.PublicKey - StakingTime uint16 - StakingAmount int64 -} - -func (d *testStakingData) GetNumRestakedFPs() int { - return len(d.FinalityProviderBabylonPrivKeys) -} - -func (tm *TestManager) getTestStakingData( - t *testing.T, - stakerKey *btcec.PublicKey, - stakingTime uint16, - stakingAmount int64, - numRestakedFPs int, -) *testStakingData { - fpBTCSKs, fpBTCPKs, err := datagen.GenRandomBTCKeyPairs(r, numRestakedFPs) - require.NoError(t, err) - - fpBBNSKs, fpBBNAddrs := make([]*secp256k1.PrivKey, numRestakedFPs), make([]sdk.AccAddress, numRestakedFPs) - strAddrs := make([]string, numRestakedFPs) - for i := 0; i < numRestakedFPs; i++ { - fpBBNSK := secp256k1.GenPrivKey() - fpAddr := sdk.AccAddress(fpBBNSK.PubKey().Address().Bytes()) - - fpBBNSKs[i] = fpBBNSK - fpBBNAddrs[i] = fpAddr - strAddrs[i] = fpAddr.String() - } - - _, _, err = tm.manger.BabylondTxBankMultiSend(t, "node0", "1000000ubbn", strAddrs...) - require.NoError(t, err) - - return &testStakingData{ - StakerKey: stakerKey, - // the staker babylon addr needs to be the same one that is going to sign - // the transaction in the end - StakerBabylonAddr: tm.BabylonClient.GetKeyAddress(), - FinalityProviderBabylonPrivKeys: fpBBNSKs, - FinalityProviderBabylonAddrs: fpBBNAddrs, - FinalityProviderBtcPrivKeys: fpBTCSKs, - FinalityProviderBtcKeys: fpBTCPKs, - StakingTime: stakingTime, - StakingAmount: stakingAmount, - } -} - -func (td *testStakingData) withStakingTime(time uint16) *testStakingData { - tdCopy := *td - tdCopy.StakingTime = time - return &tdCopy -} - -func (td *testStakingData) withStakingAmout(amout int64) *testStakingData { - tdCopy := *td - tdCopy.StakingAmount = int64(amout) - return &tdCopy -} - -func StartManager( - t *testing.T, - ctx context.Context, - numMatureOutputsInWallet uint32, -) *TestManager { - manager, err := containers.NewManager(t) - require.NoError(t, err) - - bitcoindHandler := NewBitcoindHandler(t, manager) - bitcoind := bitcoindHandler.Start() - passphrase := "pass" - walletName := "test-wallet" - _ = bitcoindHandler.CreateWallet(walletName, passphrase) - // only outputs which are 100 deep are mature - br := bitcoindHandler.GenerateBlocks(int(numMatureOutputsInWallet) + 100) - - minerAddressDecoded, err := btcutil.DecodeAddress(br.Address, regtestParams) - require.NoError(t, err) - - quorum := 2 - numCovenants := 3 - var coventantPrivKeys []*btcec.PrivateKey - for i := 0; i < numCovenants; i++ { - covenantPrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - coventantPrivKeys = append(coventantPrivKeys, covenantPrivKey) - } - - var buff bytes.Buffer - err = regtestParams.GenesisBlock.Header.Serialize(&buff) - require.NoError(t, err) - baseHeaderHex := hex.EncodeToString(buff.Bytes()) - - pkScript, err := txscript.PayToAddrScript(minerAddressDecoded) - require.NoError(t, err) - - tmpDir, err := testutil.TempDir(t) - require.NoError(t, err) - babylond, err := manager.RunBabylondResource( - t, - tmpDir, - quorum, - baseHeaderHex, - hex.EncodeToString(pkScript), // all slashing will be sent back to wallet - coventantPrivKeys[0].PubKey(), - coventantPrivKeys[1].PubKey(), - coventantPrivKeys[2].PubKey(), - ) - require.NoError(t, err) - - rpcHost := fmt.Sprintf("127.0.0.1:%s", bitcoind.GetPort("18443/tcp")) - cfg, c := defaultStakerConfig(t, walletName, passphrase, rpcHost) - cfg.BtcNodeBackendConfig.Bitcoind.RPCHost = rpcHost - cfg.WalletRPCConfig.Host = fmt.Sprintf("127.0.0.1:%s", bitcoind.GetPort("18443/tcp")) - - // update port with the dynamically allocated one from docker - cfg.BabylonConfig.RPCAddr = fmt.Sprintf("http://localhost:%s", babylond.GetPort("26657/tcp")) - cfg.BabylonConfig.GRPCAddr = fmt.Sprintf("https://localhost:%s", babylond.GetPort("9090/tcp")) - - logger := logrus.New() - logger.SetLevel(logrus.DebugLevel) - logger.Out = os.Stdout - - // babylon configs for sending transactions - cfg.BabylonConfig.KeyDirectory = filepath.Join(tmpDir, "node0", "babylond") - // need to use this one to send otherwise we will have account sequence mismatch - // errors - cfg.BabylonConfig.Key = "test-spending-key" - - // Big adjustment to make sure we have enough gas in our transactions - cfg.BabylonConfig.GasAdjustment = 3.0 - - dirPath := filepath.Join(os.TempDir(), "stakerd", "e2etest") - err = os.MkdirAll(dirPath, 0755) - require.NoError(t, err) - dbTempDir, err := os.MkdirTemp(dirPath, "db") - require.NoError(t, err) - cfg.DBConfig.DBPath = dbTempDir - - dbbackend, err := stakercfg.GetDBBackend(cfg.DBConfig) - require.NoError(t, err) - - m := metrics.NewStakerMetrics() - stakerApp, err := staker.NewStakerAppFromConfig(cfg, logger, zapLogger, dbbackend, m) - require.NoError(t, err) - // we require separate client to send BTC headers to babylon node (interface does not need this method?) - bl, err := babylonclient.NewBabylonController(cfg.BabylonConfig, &cfg.ActiveNetParams, logger, zapLogger) - require.NoError(t, err) - - walletClient := stakerApp.Wallet() - - err = walletClient.UnlockWallet(20) - require.NoError(t, err) - - info, err := c.GetAddressInfo(br.Address) - require.NoError(t, err) - - pubKeyHex := *info.PubKey - pubKeyBytes, err := hex.DecodeString(pubKeyHex) - require.NoError(t, err) - walletPubKey, err := btcec.ParsePubKey(pubKeyBytes) - require.NoError(t, err) - - addressString := fmt.Sprintf("127.0.0.1:%d", testutil.AllocateUniquePort(t)) - addrPort := netip.MustParseAddrPort(addressString) - address := net.TCPAddrFromAddrPort(addrPort) - cfg.RPCListeners = append(cfg.RPCListeners, address) // todo(lazar): check with konrad who uses this - - stakerService := service.NewStakerService( - cfg, - stakerApp, - logger, - dbbackend, - ) - - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - err := stakerService.RunUntilShutdown(ctx) - if err != nil { - t.Fatalf("Error running server: %v", err) - } - }() - // Wait for the server to start - time.Sleep(3 * time.Second) - - stakerClient, err := dc.NewStakerServiceJSONRPCClient("tcp://" + addressString) - require.NoError(t, err) - - return &TestManager{ - Config: cfg, - Db: dbbackend, - Sa: stakerApp, - BabylonClient: bl, - WalletPubKey: walletPubKey, - MinerAddr: minerAddressDecoded, - wg: &wg, - serviceAddress: addressString, - StakerClient: stakerClient, - CovenantPrivKeys: coventantPrivKeys, - BitcoindHandler: bitcoindHandler, - TestRpcClient: c, - manger: manager, - } -} - -func (tm *TestManager) Stop(t *testing.T, cancelFunc context.CancelFunc) { - cancelFunc() - tm.wg.Wait() - err := tm.manger.ClearResources() - require.NoError(t, err) - err = os.RemoveAll(tm.Config.DBConfig.DBPath) - require.NoError(t, err) -} - -func (tm *TestManager) RestartApp(t *testing.T, newCtx context.Context, cancelFunc context.CancelFunc) { - // Restart the app with no-op action - tm.RestartAppWithAction(t, newCtx, cancelFunc, func(t *testing.T) {}) -} - -// RestartAppWithAction: -// 1. Stop the staker app -// 2. Perform provided action. Warning:this action must not use staker app as -// app is stopped at this point -// 3. Start the staker app -func (tm *TestManager) RestartAppWithAction(t *testing.T, ctx context.Context, cancelFunc context.CancelFunc, action func(t *testing.T)) { - // First stop the app - cancelFunc() - tm.wg.Wait() - - // Perform the action - action(t) - - // Now reset all components and start again - logger := logrus.New() - logger.SetLevel(logrus.DebugLevel) - logger.Out = os.Stdout - - dbbackend, err := stakercfg.GetDBBackend(tm.Config.DBConfig) - require.NoError(t, err) - m := metrics.NewStakerMetrics() - stakerApp, err := staker.NewStakerAppFromConfig(tm.Config, logger, zapLogger, dbbackend, m) - require.NoError(t, err) - - service := service.NewStakerService( - tm.Config, - stakerApp, - logger, - dbbackend, - ) - - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - err := service.RunUntilShutdown(ctx) - if err != nil { - t.Fatalf("Error running server: %v", err) - } - }() - // Wait for the server to start - time.Sleep(3 * time.Second) - - tm.wg = &wg - tm.Db = dbbackend - tm.Sa = stakerApp - stakerClient, err := dc.NewStakerServiceJSONRPCClient("tcp://" + tm.serviceAddress) - require.NoError(t, err) - tm.StakerClient = stakerClient -} - -func retrieveTransactionFromMempool(t *testing.T, client *rpcclient.Client, hashes []*chainhash.Hash) []*btcutil.Tx { - var txes []*btcutil.Tx - for _, txHash := range hashes { - tx, err := client.GetRawTransaction(txHash) - - if err != nil { - // this is e2e helper method, so this error most probably some of the - // transactions are still not in the mempool - return []*btcutil.Tx{} - } - - txes = append(txes, tx) - } - return txes -} - -func GetAllMinedBtcHeadersSinceGenesis(t *testing.T, c *rpcclient.Client) []*wire.BlockHeader { - height, err := c.GetBlockCount() - require.NoError(t, err) - - var headers []*wire.BlockHeader - - for i := 1; i <= int(height); i++ { - hash, err := c.GetBlockHash(int64(i)) - require.NoError(t, err) - header, err := c.GetBlockHeader(hash) - require.NoError(t, err) - headers = append(headers, header) - } - - return headers -} - -func opReturnScript(data []byte) []byte { - builder := txscript.NewScriptBuilder() - script, err := builder.AddOp(txscript.OP_RETURN).AddData(data).Script() - if err != nil { - panic(err) - } - return script -} - -func txToBytes(tx *wire.MsgTx) []byte { - buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) - _ = tx.Serialize(buf) - return buf.Bytes() -} - -func txsToBytes(txs []*wire.MsgTx) [][]byte { - var txsBytes [][]byte - for _, tx := range txs { - txsBytes = append(txsBytes, txToBytes(tx)) - } - return txsBytes -} - -func (tm *TestManager) FinalizeUntilEpoch(t *testing.T, epoch uint64) { - bbnClient := tm.BabylonClient.GetBBNClient() - ckptParams, err := bbnClient.BTCCheckpointParams() - require.NoError(t, err) - // wait until the checkpoint of this epoch is sealed - require.Eventually(t, func() bool { - lastSealedCkpt, err := bbnClient.LatestEpochFromStatus(ckpttypes.Sealed) - if err != nil { - return false - } - return epoch <= lastSealedCkpt.RawCheckpoint.EpochNum - }, 1*time.Minute, 1*time.Second) - - t.Logf("start finalizing epochs till %d", epoch) - // Random source for the generation of BTC data - // r := rand.New(rand.NewSource(time.Now().Unix())) - - // get all checkpoints of these epochs - pagination := &sdkquerytypes.PageRequest{ - Key: ckpttypes.CkptsObjectKey(1), - Limit: epoch, - } - resp, err := bbnClient.RawCheckpoints(pagination) - require.NoError(t, err) - require.Equal(t, int(epoch), len(resp.RawCheckpoints)) - - submitter := tm.BabylonClient.GetKeyAddress() - - for _, checkpoint := range resp.RawCheckpoints { - // currentBtcTipResp, err := tm.BabylonClient.QueryBtcLightClientTip() - // require.NoError(t, err) - // tipHeader, err := bbntypes.NewBTCHeaderBytesFromHex(currentBtcTipResp.HeaderHex) - // require.NoError(t, err) - - rawCheckpoint, err := checkpoint.Ckpt.ToRawCheckpoint() - require.NoError(t, err) - - btcCheckpoint, err := ckpttypes.FromRawCkptToBTCCkpt(rawCheckpoint, submitter) - require.NoError(t, err) - - babylonTagBytes, err := hex.DecodeString("01020304") - require.NoError(t, err) - - p1, p2, err := txformat.EncodeCheckpointData( - babylonTagBytes, - txformat.CurrentVersion, - btcCheckpoint, - ) - - err = tm.Sa.Wallet().UnlockWallet(60) - require.NoError(t, err) - tx1, err := tm.Sa.Wallet().CreateAndSignTx( - []*wire.TxOut{ - wire.NewTxOut(0, opReturnScript(p1)), - }, - 2000, - tm.MinerAddr, - nil, - ) - require.NoError(t, err) - _, err = tm.Sa.Wallet().SendRawTransaction(tx1, true) - require.NoError(t, err) - - resp1 := tm.BitcoindHandler.GenerateBlocks(1) - - tx2, err := tm.Sa.Wallet().CreateAndSignTx( - []*wire.TxOut{ - wire.NewTxOut(0, opReturnScript(p2)), - }, - 2000, - tm.MinerAddr, - nil, - ) - require.NoError(t, err) - _, err = tm.Sa.Wallet().SendRawTransaction(tx2, true) - require.NoError(t, err) - resp2 := tm.BitcoindHandler.GenerateBlocks(1) - - block1Hash, err := chainhash.NewHashFromStr(resp1.Blocks[0]) - require.NoError(t, err) - block2Hash, err := chainhash.NewHashFromStr(resp2.Blocks[0]) - require.NoError(t, err) - - block1, err := tm.TestRpcClient.GetBlock(block1Hash) - require.NoError(t, err) - block2, err := tm.TestRpcClient.GetBlock(block2Hash) - require.NoError(t, err) - - _, err = tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{ - &block1.Header, - &block2.Header, - }) - - header1Bytes := bbntypes.NewBTCHeaderBytesFromBlockHeader(&block1.Header) - header2Bytes := bbntypes.NewBTCHeaderBytesFromBlockHeader(&block2.Header) - - proof1, err := btcctypes.SpvProofFromHeaderAndTransactions(&header1Bytes, txsToBytes(block1.Transactions), 1) - require.NoError(t, err) - proof2, err := btcctypes.SpvProofFromHeaderAndTransactions(&header2Bytes, txsToBytes(block2.Transactions), 1) - require.NoError(t, err) - - _, err = tm.BabylonClient.InsertSpvProofs(submitter.String(), []*btcctypes.BTCSpvProof{ - proof1, - proof2, - }) - require.NoError(t, err) - - // // wait until this checkpoint is submitted - require.Eventually(t, func() bool { - ckpt, err := bbnClient.RawCheckpoint(checkpoint.Ckpt.EpochNum) - require.NoError(t, err) - return ckpt.RawCheckpoint.Status == ckpttypes.Submitted - }, eventuallyWaitTimeOut, eventuallyPollTime) - } - - tm.mineNEmptyBlocks(t, uint32(ckptParams.Params.CheckpointFinalizationTimeout), true) - - // // wait until the checkpoint of this epoch is finalised - require.Eventually(t, func() bool { - lastFinalizedCkpt, err := bbnClient.LatestEpochFromStatus(ckpttypes.Finalized) - if err != nil { - t.Logf("failed to get last finalized epoch: %v", err) - return false - } - return epoch <= lastFinalizedCkpt.RawCheckpoint.EpochNum - }, eventuallyWaitTimeOut, 1*time.Second) - - t.Logf("epoch %d is finalised", epoch) -} - -func (tm *TestManager) createAndRegisterFinalityProviders(t *testing.T, testStakingData *testStakingData) { - params, err := tm.BabylonClient.QueryStakingTracker() - require.NoError(t, err) - - for i := 0; i < testStakingData.GetNumRestakedFPs(); i++ { - // ensure the finality provider in testStakingData does not exist yet - fpResp, err := tm.BabylonClient.QueryFinalityProvider(testStakingData.FinalityProviderBtcKeys[i]) - require.Nil(t, fpResp) - require.Error(t, err) - require.True(t, errors.Is(err, babylonclient.ErrFinalityProviderDoesNotExist)) - - pop, err := btcstypes.NewPoPBTC(testStakingData.FinalityProviderBabylonAddrs[i], testStakingData.FinalityProviderBtcPrivKeys[i]) - require.NoError(t, err) - - btcFpKey := bbntypes.NewBIP340PubKeyFromBTCPK(testStakingData.FinalityProviderBtcKeys[i]) - - // get current finality providers - resp, err := tm.BabylonClient.QueryFinalityProviders(100, 0) - require.NoError(t, err) - // register the generated finality provider - err = tm.BabylonClient.RegisterFinalityProvider( - testStakingData.FinalityProviderBabylonAddrs[i], - testStakingData.FinalityProviderBabylonPrivKeys[i], - btcFpKey, - ¶ms.MinComissionRate, - &sttypes.Description{ - Moniker: "tester", - }, - pop, - ) - require.NoError(t, err) - - require.Eventually(t, func() bool { - resp2, err := tm.BabylonClient.QueryFinalityProviders(100, 0) - require.NoError(t, err) - - // After registration we should have one finality provider - return len(resp2.FinalityProviders) == len(resp.FinalityProviders)+1 - }, time.Minute, 250*time.Millisecond) - } -} - -func (tm *TestManager) sendHeadersToBabylon(t *testing.T, headers []*wire.BlockHeader) { - _, err := tm.BabylonClient.InsertBtcBlockHeaders(headers) - require.NoError(t, err) -} - -func (tm *TestManager) mineNEmptyBlocks(t *testing.T, numHeaders uint32, sendToBabylon bool) []*wire.BlockHeader { - resp := tm.BitcoindHandler.GenerateBlocks(int(numHeaders)) - - var minedHeaders []*wire.BlockHeader - for _, hash := range resp.Blocks { - hash, err := chainhash.NewHashFromStr(hash) - require.NoError(t, err) - header, err := tm.TestRpcClient.GetBlockHeader(hash) - require.NoError(t, err) - minedHeaders = append(minedHeaders, header) - } - - if sendToBabylon { - tm.sendHeadersToBabylon(t, minedHeaders) - } - - return minedHeaders -} - -func (tm *TestManager) mineBlock(t *testing.T) *wire.MsgBlock { - resp := tm.BitcoindHandler.GenerateBlocks(1) - hash, err := chainhash.NewHashFromStr(resp.Blocks[0]) - require.NoError(t, err) - header, err := tm.TestRpcClient.GetBlock(hash) - require.NoError(t, err) - return header -} - -func (tm *TestManager) sendStakingTxBTC( - t *testing.T, - testStakingData *testStakingData, - sendToBabylonFirst bool, -) *chainhash.Hash { - fpBTCPKs := []string{} - for i := 0; i < testStakingData.GetNumRestakedFPs(); i++ { - fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.FinalityProviderBtcKeys[i])) - fpBTCPKs = append(fpBTCPKs, fpBTCPK) - } - res, err := tm.StakerClient.Stake( - context.Background(), - tm.MinerAddr.String(), - testStakingData.StakingAmount, - fpBTCPKs, - int64(testStakingData.StakingTime), - sendToBabylonFirst, - ) - require.NoError(t, err) - txHash := res.TxHash - - stakingDetails, err := tm.StakerClient.StakingDetails(context.Background(), txHash) - require.NoError(t, err) - require.Equal(t, stakingDetails.StakingTxHash, txHash) - - if sendToBabylonFirst { - require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BABYLON.String()) - } else { - require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BTC.String()) - } - hashFromString, err := chainhash.NewHashFromStr(txHash) - require.NoError(t, err) - - // only wait for blocks if we are using the old flow, and send staking tx to BTC - // first - if !sendToBabylonFirst { - require.Eventually(t, func() bool { - txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{hashFromString}) - return len(txFromMempool) == 1 - }, eventuallyWaitTimeOut, eventuallyPollTime) - - mBlock := tm.mineBlock(t) - require.Equal(t, 2, len(mBlock.Transactions)) - - _, err = tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{&mBlock.Header}) - require.NoError(t, err) - } - return hashFromString -} - -func (tm *TestManager) sendMultipleStakingTx(t *testing.T, testStakingData []*testStakingData, sendToBabylonFirst bool) []*chainhash.Hash { - var hashes []*chainhash.Hash - for _, data := range testStakingData { - fpBTCPKs := []string{} - for i := 0; i < data.GetNumRestakedFPs(); i++ { - fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(data.FinalityProviderBtcKeys[i])) - fpBTCPKs = append(fpBTCPKs, fpBTCPK) - } - res, err := tm.StakerClient.Stake( - context.Background(), - tm.MinerAddr.String(), - data.StakingAmount, - fpBTCPKs, - int64(data.StakingTime), - sendToBabylonFirst, - ) - require.NoError(t, err) - txHash, err := chainhash.NewHashFromStr(res.TxHash) - require.NoError(t, err) - hashes = append(hashes, txHash) - } - - for _, txHash := range hashes { - txHash := txHash - hashStr := txHash.String() - stakingDetails, err := tm.StakerClient.StakingDetails(context.Background(), hashStr) - require.NoError(t, err) - require.Equal(t, stakingDetails.StakingTxHash, hashStr) - - if sendToBabylonFirst { - require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BABYLON.String()) - } else { - require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BTC.String()) - } - } - - if !sendToBabylonFirst { - mBlock := tm.mineBlock(t) - require.Equal(t, len(hashes)+1, len(mBlock.Transactions)) - - _, err := tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{&mBlock.Header}) - require.NoError(t, err) - } - return hashes -} - -func (tm *TestManager) sendWatchedStakingTx( - t *testing.T, - testStakingData *testStakingData, - params *babylonclient.StakingParams, -) *chainhash.Hash { - unbondingTme := uint16(params.FinalizationTimeoutBlocks) + 1 - - stakingInfo, err := staking.BuildStakingInfo( - testStakingData.StakerKey, - testStakingData.FinalityProviderBtcKeys, - params.CovenantPks, - params.CovenantQuruomThreshold, - testStakingData.StakingTime, - btcutil.Amount(testStakingData.StakingAmount), - regtestParams, - ) - require.NoError(t, err) - - err = tm.Sa.Wallet().UnlockWallet(20) - require.NoError(t, err) - - tx, err := tm.Sa.Wallet().CreateAndSignTx( - []*wire.TxOut{stakingInfo.StakingOutput}, - 2000, - tm.MinerAddr, - nil, - ) - require.NoError(t, err) - txHash := tx.TxHash() - _, err = tm.Sa.Wallet().SendRawTransaction(tx, true) - require.NoError(t, err) - - // Wait for tx to be in mempool - require.Eventually(t, func() bool { - tx, err := tm.TestRpcClient.GetRawTransaction(&txHash) - if err != nil { - return false - } - - if tx == nil { - return false - } - - return true - }, 1*time.Minute, eventuallyPollTime) - - stakingOutputIdx := 0 - - require.NoError(t, err) - - slashingTx, err := staking.BuildSlashingTxFromStakingTxStrict( - tx, - uint32(stakingOutputIdx), - params.SlashingPkScript, - testStakingData.StakerKey, - unbondingTme, - int64(params.MinSlashingTxFeeSat)+10, - params.SlashingRate, - regtestParams, - ) - require.NoError(t, err) - - stakingTxSlashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() - require.NoError(t, err) - - slashingSigResult, err := tm.Sa.Wallet().SignOneInputTaprootSpendingTransaction( - &walletcontroller.TaprootSigningRequest{ - FundingOutput: stakingInfo.StakingOutput, - TxToSign: slashingTx, - SignerAddress: tm.MinerAddr, - SpendDescription: &walletcontroller.SpendPathDescription{ - ControlBlock: &stakingTxSlashingPathInfo.ControlBlock, - ScriptLeaf: &stakingTxSlashingPathInfo.RevealedLeaf, - }, - }, - ) - - require.NoError(t, err) - require.NotNil(t, slashingSigResult.Signature) - - serializedStakingTx, err := utils.SerializeBtcTransaction(tx) - require.NoError(t, err) - serializedSlashingTx, err := utils.SerializeBtcTransaction(slashingTx) - require.NoError(t, err) - // Build unbonding related data - unbondingFee := params.UnbondingFee - unbondingAmount := btcutil.Amount(testStakingData.StakingAmount) - unbondingFee - - unbondingInfo, err := staking.BuildUnbondingInfo( - testStakingData.StakerKey, - testStakingData.FinalityProviderBtcKeys, - params.CovenantPks, - params.CovenantQuruomThreshold, - unbondingTme, - unbondingAmount, - regtestParams, - ) - require.NoError(t, err) - - unbondingSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() - require.NoError(t, err) - - unbondingTx := wire.NewMsgTx(2) - unbondingTx.AddTxIn(wire.NewTxIn(wire.NewOutPoint(&txHash, uint32(stakingOutputIdx)), nil, nil)) - unbondingTx.AddTxOut(unbondingInfo.UnbondingOutput) - - slashUnbondingTx, err := staking.BuildSlashingTxFromStakingTxStrict( - unbondingTx, - 0, - params.SlashingPkScript, - testStakingData.StakerKey, - unbondingTme, - int64(params.MinSlashingTxFeeSat)+10, - params.SlashingRate, - regtestParams, - ) - require.NoError(t, err) - - slashingUnbondingSigResult, err := tm.Sa.Wallet().SignOneInputTaprootSpendingTransaction( - &walletcontroller.TaprootSigningRequest{ - FundingOutput: unbondingTx.TxOut[0], - TxToSign: slashUnbondingTx, - SignerAddress: tm.MinerAddr, - SpendDescription: &walletcontroller.SpendPathDescription{ - ControlBlock: &unbondingSlashingPathInfo.ControlBlock, - ScriptLeaf: &unbondingSlashingPathInfo.RevealedLeaf, - }, - }, - ) - - require.NoError(t, err) - require.NotNil(t, slashingUnbondingSigResult.Signature) - - serializedUnbondingTx, err := utils.SerializeBtcTransaction(unbondingTx) - require.NoError(t, err) - serializedSlashUnbondingTx, err := utils.SerializeBtcTransaction(slashUnbondingTx) - require.NoError(t, err) - - babylonAddrHash := tmhash.Sum(testStakingData.StakerBabylonAddr.Bytes()) - - sig, err := tm.Sa.Wallet().SignBip322NativeSegwit(babylonAddrHash, tm.MinerAddr) - require.NoError(t, err) - - pop, err := babylonclient.NewBabylonBip322Pop( - babylonAddrHash, - sig, - tm.MinerAddr, - ) - require.NoError(t, err) - - fpBTCPKs := []string{} - for i := 0; i < testStakingData.GetNumRestakedFPs(); i++ { - fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.FinalityProviderBtcKeys[i])) - fpBTCPKs = append(fpBTCPKs, fpBTCPK) - } - _, err = tm.StakerClient.WatchStaking( - context.Background(), - hex.EncodeToString(serializedStakingTx), - int(testStakingData.StakingTime), - int(testStakingData.StakingAmount), - hex.EncodeToString(schnorr.SerializePubKey(testStakingData.StakerKey)), - fpBTCPKs, - hex.EncodeToString(serializedSlashingTx), - hex.EncodeToString(slashingSigResult.Signature.Serialize()), - testStakingData.StakerBabylonAddr.String(), - tm.MinerAddr.String(), - hex.EncodeToString(pop.BtcSig), - hex.EncodeToString(serializedUnbondingTx), - hex.EncodeToString(serializedSlashUnbondingTx), - hex.EncodeToString(slashingUnbondingSigResult.Signature.Serialize()), - int(unbondingTme), - // Use schnor verification - int(btcstypes.BTCSigType_BIP322), - ) - require.NoError(t, err) - - txs := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{&txHash}) - require.Len(t, txs, 1) - - mBlock := tm.mineBlock(t) - require.Equal(t, 2, len(mBlock.Transactions)) - _, err = tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{&mBlock.Header}) - require.NoError(t, err) - - return &txHash -} - -func (tm *TestManager) spendStakingTxWithHash(t *testing.T, stakingTxHash *chainhash.Hash) (*chainhash.Hash, *btcutil.Amount) { - res, err := tm.StakerClient.SpendStakingTransaction(context.Background(), stakingTxHash.String()) - require.NoError(t, err) - spendTxHash, err := chainhash.NewHashFromStr(res.TxHash) - require.NoError(t, err) - - iAmount, err := strconv.ParseInt(res.TxValue, 10, 64) - require.NoError(t, err) - spendTxValue := btcutil.Amount(iAmount) - - require.Eventually(t, func() bool { - txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{spendTxHash}) - return len(txFromMempool) == 1 - }, eventuallyWaitTimeOut, eventuallyPollTime) - - sendTx := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{spendTxHash})[0] - - // Tx is in mempool - txDetails, txState, err := tm.Sa.Wallet().TxDetails(spendTxHash, sendTx.MsgTx().TxOut[0].PkScript) - require.NoError(t, err) - require.Nil(t, txDetails) - require.Equal(t, txState, walletcontroller.TxInMemPool) - - // Block with spend is mined - mBlock1 := tm.mineBlock(t) - require.Equal(t, 2, len(mBlock1.Transactions)) - - // Tx is in chain - txDetails, txState, err = tm.Sa.Wallet().TxDetails(spendTxHash, sendTx.MsgTx().TxOut[0].PkScript) - require.NoError(t, err) - require.NotNil(t, txDetails) - require.Equal(t, txState, walletcontroller.TxInChain) - - return spendTxHash, &spendTxValue -} - -func (tm *TestManager) waitForStakingTxState(t *testing.T, txHash *chainhash.Hash, expectedState proto.TransactionState) { - require.Eventually(t, func() bool { - detailResult, err := tm.StakerClient.StakingDetails(context.Background(), txHash.String()) - if err != nil { - return false - } - return detailResult.StakingState == expectedState.String() - }, 1*time.Minute, eventuallyPollTime) -} - -func (tm *TestManager) walletUnspentsOutputsContainsOutput(t *testing.T, from btcutil.Address, withValue btcutil.Amount) bool { - unspentOutputs, err := tm.Sa.ListUnspentOutputs() - require.NoError(t, err) - - var containsOutput bool = false - - for _, output := range unspentOutputs { - if output.Address == tm.MinerAddr.String() && int64(output.Amount) == int64(withValue) { - containsOutput = true - } - } - - return containsOutput -} - -func (tm *TestManager) insertAllMinedBlocksToBabylon(t *testing.T) { - headers := GetAllMinedBtcHeadersSinceGenesis(t, tm.TestRpcClient) - _, err := tm.BabylonClient.InsertBtcBlockHeaders(headers) - require.NoError(t, err) -} - -func (tm *TestManager) insertCovenantSigForDelegation( - t *testing.T, - btcDel *btcstypes.BTCDelegationResponse, -) { - fpBTCPKs, err := bbntypes.NewBTCPKsFromBIP340PKs(btcDel.FpBtcPkList) - require.NoError(t, err) - - slashingTxBytes, err := hex.DecodeString(btcDel.SlashingTxHex) - require.NoError(t, err) - slashingTx := btcstypes.BTCSlashingTx(slashingTxBytes) - stakingTx := btcDel.StakingTxHex - stakingMsgTx, _, err := bbntypes.NewBTCTxFromHex(stakingTx) - require.NoError(t, err) - - cl := tm.Sa.BabylonController() - params, err := cl.Params() - require.NoError(t, err) - - stakingInfo, err := staking.BuildStakingInfo( - btcDel.BtcPk.MustToBTCPK(), - fpBTCPKs, - params.CovenantPks, - params.CovenantQuruomThreshold, - uint16(btcDel.EndHeight-btcDel.StartHeight), - btcutil.Amount(btcDel.TotalSat), - regtestParams, - ) - slashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() - require.NoError(t, err) - - covenantSlashingTxSigs, err := datagen.GenCovenantAdaptorSigs( - tm.CovenantPrivKeys, - fpBTCPKs, - stakingMsgTx, - slashingPathInfo.GetPkScriptPath(), - &slashingTx, - ) - require.NoError(t, err) - - // slash unbonding tx spends unbonding tx - unbondingMsgTx, _, err := bbntypes.NewBTCTxFromHex(btcDel.UndelegationResponse.UnbondingTxHex) - require.NoError(t, err) - unbondingInfo, err := staking.BuildUnbondingInfo( - btcDel.BtcPk.MustToBTCPK(), - fpBTCPKs, - params.CovenantPks, - params.CovenantQuruomThreshold, - uint16(btcDel.UnbondingTime), - btcutil.Amount(unbondingMsgTx.TxOut[0].Value), - regtestParams, - ) - unbondingSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() - require.NoError(t, err) - - // generate all covenant signatures from all covenant members - unbondingSlashingTx, err := btcstypes.NewBTCSlashingTxFromHex(btcDel.UndelegationResponse.SlashingTxHex) - require.NoError(t, err) - covenantUnbondingSlashingTxSigs, err := datagen.GenCovenantAdaptorSigs( - tm.CovenantPrivKeys, - fpBTCPKs, - unbondingMsgTx, - unbondingSlashingPathInfo.GetPkScriptPath(), - unbondingSlashingTx, - ) - require.NoError(t, err) - - // each covenant member submits signatures - unbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() - require.NoError(t, err) - covUnbondingSigs, err := datagen.GenCovenantUnbondingSigs( - tm.CovenantPrivKeys, - stakingMsgTx, - btcDel.StakingOutputIdx, - unbondingPathInfo.GetPkScriptPath(), - unbondingMsgTx, - ) - require.NoError(t, err) - - var messages []*btcstypes.MsgAddCovenantSigs - for i := 0; i < len(tm.CovenantPrivKeys); i++ { - msg := tm.BabylonClient.CreateCovenantMessage( - bbntypes.NewBIP340PubKeyFromBTCPK(tm.CovenantPrivKeys[i].PubKey()), - stakingMsgTx.TxHash().String(), - covenantSlashingTxSigs[i].AdaptorSigs, - bbntypes.NewBIP340SignatureFromBTCSig(covUnbondingSigs[i]), - covenantUnbondingSlashingTxSigs[i].AdaptorSigs, - ) - messages = append(messages, msg) - } - // we insert are covenant signatures in on message, this way staker - // program must handle the case of all signatures being present in Babylon - // delegation - // it also speeds up the tests - _, err = tm.BabylonClient.SubmitMultipleCovenantMessages(messages) - require.NoError(t, err) -} - func TestStakingFailures(t *testing.T) { t.Parallel() numMatureOutputs := uint32(200) @@ -1953,3 +845,74 @@ func TestRecoverAfterRestartDuringWithdrawal(t *testing.T) { // it should be possible ot spend from unbonding tx tm.spendStakingTxWithHash(t, txHash) } + +func TestStakeFromPhase1(t *testing.T) { + // need to have at least 300 block on testnet as only then segwit is activated. + // Mature output is out which has 100 confirmations, which means 200mature outputs + // will generate 300 blocks + numMatureOutputs := uint32(200) + ctx, cancel := context.WithCancel(context.Background()) + tm := StartManager(t, ctx, numMatureOutputs) + defer tm.Stop(t, cancel) + tm.insertAllMinedBlocksToBabylon(t) + + cl := tm.Sa.BabylonController() + params, err := cl.Params() + require.NoError(t, err) + // large staking time + stakingTime := uint16(1000) + testStakingData := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 50000, 1) + + tm.createAndRegisterFinalityProviders(t, testStakingData) + + txHash := tm.sendStakingTxBTC(t, testStakingData, false) + + go tm.mineNEmptyBlocks(t, params.ConfirmationTimeBlocks, true) + tm.waitForStakingTxState(t, txHash, proto.TransactionState_SENT_TO_BABYLON) + require.NoError(t, err) + + pend, err := tm.BabylonClient.QueryPendingBTCDelegations() + require.NoError(t, err) + require.Len(t, pend, 1) + // need to activate delegation to unbond + tm.insertCovenantSigForDelegation(t, pend[0]) + + tm.waitForStakingTxState(t, txHash, proto.TransactionState_DELEGATION_ACTIVE) + + resp, err := tm.StakerClient.UnbondStaking(context.Background(), txHash.String()) + require.NoError(t, err) + + unbondingTxHash, err := chainhash.NewHashFromStr(resp.UnbondingTxHash) + require.NoError(t, err) + + require.Eventually(t, func() bool { + tx, err := tm.TestRpcClient.GetRawTransaction(unbondingTxHash) + if err != nil { + return false + } + + if tx == nil { + return false + + } + + return true + }, 1*time.Minute, eventuallyPollTime) + + block := tm.mineBlock(t) + require.Equal(t, 2, len(block.Transactions)) + require.Equal(t, block.Transactions[1].TxHash(), *unbondingTxHash) + go tm.mineNEmptyBlocks(t, staker.UnbondingTxConfirmations, false) + tm.waitForStakingTxState(t, txHash, proto.TransactionState_UNBONDING_CONFIRMED_ON_BTC) + + withdrawableTransactionsResp, err := tm.StakerClient.WithdrawableTransactions(context.Background(), nil, nil) + require.NoError(t, err) + require.Len(t, withdrawableTransactionsResp.Transactions, 1) + + // We can spend unbonding tx immediately as in e2e test, finalization time is 4 blocks and we locked it + // finalization time + 1 i.e 5 blocks, but to consider unboning tx as confirmed we need to wait for 6 blocks + // so at this point time lock should already have passed + tm.spendStakingTxWithHash(t, txHash) + go tm.mineNEmptyBlocks(t, staker.SpendStakeTxConfirmations, false) + tm.waitForStakingTxState(t, txHash, proto.TransactionState_SPENT_ON_BTC) +} diff --git a/itest/manager.go b/itest/manager.go new file mode 100644 index 0000000..d0a9dd2 --- /dev/null +++ b/itest/manager.go @@ -0,0 +1,1165 @@ +//go:build e2e +// +build e2e + +package e2etest + +import ( + "bytes" + "context" + "encoding/hex" + "errors" + "fmt" + "math/rand" + "net" + "net/netip" + "os" + "path/filepath" + "strconv" + "sync" + "testing" + "time" + + "github.com/babylonlabs-io/btc-staker/itest/containers" + "github.com/babylonlabs-io/btc-staker/itest/testutil" + "github.com/ory/dockertest/v3" + + btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" + "github.com/cometbft/cometbft/crypto/tmhash" + + staking "github.com/babylonlabs-io/babylon/btcstaking" + txformat "github.com/babylonlabs-io/babylon/btctxformatter" + "github.com/babylonlabs-io/babylon/testutil/datagen" + bbntypes "github.com/babylonlabs-io/babylon/types" + btcstypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" + ckpttypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" + "github.com/babylonlabs-io/btc-staker/babylonclient" + "github.com/babylonlabs-io/btc-staker/metrics" + "github.com/babylonlabs-io/btc-staker/proto" + "github.com/babylonlabs-io/btc-staker/staker" + "github.com/babylonlabs-io/btc-staker/stakercfg" + service "github.com/babylonlabs-io/btc-staker/stakerservice" + dc "github.com/babylonlabs-io/btc-staker/stakerservice/client" + "github.com/babylonlabs-io/btc-staker/types" + "github.com/babylonlabs-io/btc-staker/utils" + "github.com/babylonlabs-io/btc-staker/walletcontroller" + "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/rpcclient" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkquerytypes "github.com/cosmos/cosmos-sdk/types/query" + sttypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" +) + +// bitcoin params used for testing +var ( + r = rand.New(rand.NewSource(time.Now().Unix())) + + regtestParams = &chaincfg.RegressionNetParams + + eventuallyWaitTimeOut = 10 * time.Second + eventuallyPollTime = 250 * time.Millisecond +) + +// keyToAddr maps the passed private to corresponding p2pkh address. +func keyToAddr(key *btcec.PrivateKey, net *chaincfg.Params) (btcutil.Address, error) { + serializedKey := key.PubKey().SerializeCompressed() + pubKeyAddr, err := btcutil.NewAddressPubKey(serializedKey, net) + if err != nil { + return nil, err + } + return pubKeyAddr.AddressPubKeyHash(), nil +} + +func defaultStakerConfig(t *testing.T, walletName, passphrase, bitcoindHost string) (*stakercfg.Config, *rpcclient.Client) { + defaultConfig := stakercfg.DefaultConfig() + + // both wallet and node are bicoind + defaultConfig.BtcNodeBackendConfig.ActiveWalletBackend = types.BitcoindWalletBackend + defaultConfig.BtcNodeBackendConfig.ActiveNodeBackend = types.BitcoindNodeBackend + defaultConfig.ActiveNetParams = *regtestParams + + // Fees configuration + defaultConfig.BtcNodeBackendConfig.FeeMode = "dynamic" + defaultConfig.BtcNodeBackendConfig.EstimationMode = types.DynamicFeeEstimation + + bitcoindUser := "user" + bitcoindPass := "pass" + + // Wallet configuration + defaultConfig.WalletRPCConfig.Host = bitcoindHost + defaultConfig.WalletRPCConfig.User = bitcoindUser + defaultConfig.WalletRPCConfig.Pass = bitcoindPass + defaultConfig.WalletRPCConfig.DisableTLS = true + defaultConfig.WalletConfig.WalletPass = passphrase + defaultConfig.WalletConfig.WalletName = walletName + + // node configuration + defaultConfig.BtcNodeBackendConfig.Bitcoind.RPCHost = bitcoindHost + defaultConfig.BtcNodeBackendConfig.Bitcoind.RPCUser = bitcoindUser + defaultConfig.BtcNodeBackendConfig.Bitcoind.RPCPass = bitcoindPass + + // Use rpc polling, as it is our default mode and it is a bit more troublesome + // to configure ZMQ from inside the bitcoind docker container + defaultConfig.BtcNodeBackendConfig.Bitcoind.RPCPolling = true + defaultConfig.BtcNodeBackendConfig.Bitcoind.BlockPollingInterval = 1 * time.Second + defaultConfig.BtcNodeBackendConfig.Bitcoind.TxPollingInterval = 1 * time.Second + + defaultConfig.StakerConfig.BabylonStallingInterval = 1 * time.Second + defaultConfig.StakerConfig.UnbondingTxCheckInterval = 1 * time.Second + defaultConfig.StakerConfig.CheckActiveInterval = 1 * time.Second + + // TODO: After bumping relayer version sending transactions concurrently fails wih + // fatal error: concurrent map writes + // For now diable concurrent sends but this need to be sorted out + defaultConfig.StakerConfig.MaxConcurrentTransactions = 1 + + testRpcClient, err := rpcclient.New(&rpcclient.ConnConfig{ + Host: bitcoindHost, + User: bitcoindUser, + Pass: bitcoindPass, + DisableTLS: true, + DisableConnectOnNew: true, + DisableAutoReconnect: false, + // we use post mode as it sure it works with either bitcoind or btcwallet + // we may need to re-consider it later if we need any notifications + HTTPPostMode: true, + }, nil) + require.NoError(t, err) + + return &defaultConfig, testRpcClient +} + +type TestManager struct { + Config *stakercfg.Config + Db kvdb.Backend + Sa *staker.App + BabylonClient *babylonclient.BabylonController + WalletPubKey *btcec.PublicKey + wg *sync.WaitGroup + serviceAddress string + StakerClient *dc.StakerServiceJSONRPCClient + CovenantPrivKeys []*btcec.PrivateKey + TestRpcClient *rpcclient.Client + manager *containers.Manager + TestManagerBTC +} + +type TestManagerBTC struct { + RawAddr string + MinerAddr btcutil.Address + BitcoindHandler *BitcoindTestHandler + Bitcoind *dockertest.Resource + WalletName string + WalletPassphrase string +} + +type testStakingData struct { + StakerKey *btcec.PublicKey + StakerBabylonAddr sdk.AccAddress + FinalityProviderBabylonPrivKeys []*secp256k1.PrivKey + FinalityProviderBabylonAddrs []sdk.AccAddress + FinalityProviderBtcPrivKeys []*btcec.PrivateKey + FinalityProviderBtcKeys []*btcec.PublicKey + StakingTime uint16 + StakingAmount int64 +} + +func (d *testStakingData) GetNumRestakedFPs() int { + return len(d.FinalityProviderBabylonPrivKeys) +} + +func (tm *TestManager) getTestStakingData( + t *testing.T, + stakerKey *btcec.PublicKey, + stakingTime uint16, + stakingAmount int64, + numRestakedFPs int, +) *testStakingData { + fpBTCSKs, fpBTCPKs, err := datagen.GenRandomBTCKeyPairs(r, numRestakedFPs) + require.NoError(t, err) + + fpBBNSKs, fpBBNAddrs := make([]*secp256k1.PrivKey, numRestakedFPs), make([]sdk.AccAddress, numRestakedFPs) + strAddrs := make([]string, numRestakedFPs) + for i := 0; i < numRestakedFPs; i++ { + fpBBNSK := secp256k1.GenPrivKey() + fpAddr := sdk.AccAddress(fpBBNSK.PubKey().Address().Bytes()) + + fpBBNSKs[i] = fpBBNSK + fpBBNAddrs[i] = fpAddr + strAddrs[i] = fpAddr.String() + } + + _, _, err = tm.manager.BabylondTxBankMultiSend(t, "node0", "1000000ubbn", strAddrs...) + require.NoError(t, err) + + return &testStakingData{ + StakerKey: stakerKey, + // the staker babylon addr needs to be the same one that is going to sign + // the transaction in the end + StakerBabylonAddr: tm.BabylonClient.GetKeyAddress(), + FinalityProviderBabylonPrivKeys: fpBBNSKs, + FinalityProviderBabylonAddrs: fpBBNAddrs, + FinalityProviderBtcPrivKeys: fpBTCSKs, + FinalityProviderBtcKeys: fpBTCPKs, + StakingTime: stakingTime, + StakingAmount: stakingAmount, + } +} + +func (td *testStakingData) withStakingTime(time uint16) *testStakingData { + tdCopy := *td + tdCopy.StakingTime = time + return &tdCopy +} + +func (td *testStakingData) withStakingAmout(amout int64) *testStakingData { + tdCopy := *td + tdCopy.StakingAmount = int64(amout) + return &tdCopy +} + +func StartManagerBtc( + t *testing.T, + ctx context.Context, + numMatureOutputsInWallet uint32, + manager *containers.Manager, +) *TestManagerBTC { + bitcoindHandler := NewBitcoindHandler(t, manager) + bitcoind := bitcoindHandler.Start() + passphrase := "pass" + walletName := "test-wallet" + _ = bitcoindHandler.CreateWallet(walletName, passphrase) + // only outputs which are 100 deep are mature + br := bitcoindHandler.GenerateBlocks(int(numMatureOutputsInWallet) + 100) + + minerAddressDecoded, err := btcutil.DecodeAddress(br.Address, regtestParams) + require.NoError(t, err) + + return &TestManagerBTC{ + RawAddr: br.Address, + MinerAddr: minerAddressDecoded, + BitcoindHandler: bitcoindHandler, + Bitcoind: bitcoind, + WalletName: walletName, + WalletPassphrase: passphrase, + } +} + +func StartManager( + t *testing.T, + ctx context.Context, + numMatureOutputsInWallet uint32, +) *TestManager { + manager, err := containers.NewManager(t) + require.NoError(t, err) + + tmBTC := StartManagerBtc(t, ctx, numMatureOutputsInWallet, manager) + + quorum := 2 + numCovenants := 3 + var coventantPrivKeys []*btcec.PrivateKey + for i := 0; i < numCovenants; i++ { + covenantPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + coventantPrivKeys = append(coventantPrivKeys, covenantPrivKey) + } + + var buff bytes.Buffer + err = regtestParams.GenesisBlock.Header.Serialize(&buff) + require.NoError(t, err) + baseHeaderHex := hex.EncodeToString(buff.Bytes()) + + pkScript, err := txscript.PayToAddrScript(tmBTC.MinerAddr) + require.NoError(t, err) + + tmpDir, err := testutil.TempDir(t) + require.NoError(t, err) + babylond, err := manager.RunBabylondResource( + t, + tmpDir, + quorum, + baseHeaderHex, + hex.EncodeToString(pkScript), // all slashing will be sent back to wallet + coventantPrivKeys[0].PubKey(), + coventantPrivKeys[1].PubKey(), + coventantPrivKeys[2].PubKey(), + ) + require.NoError(t, err) + + rpcHost := fmt.Sprintf("127.0.0.1:%s", tmBTC.Bitcoind.GetPort("18443/tcp")) + cfg, c := defaultStakerConfig(t, tmBTC.WalletName, tmBTC.WalletPassphrase, rpcHost) + cfg.BtcNodeBackendConfig.Bitcoind.RPCHost = rpcHost + cfg.WalletRPCConfig.Host = fmt.Sprintf("127.0.0.1:%s", tmBTC.Bitcoind.GetPort("18443/tcp")) + + // update port with the dynamically allocated one from docker + cfg.BabylonConfig.RPCAddr = fmt.Sprintf("http://localhost:%s", babylond.GetPort("26657/tcp")) + cfg.BabylonConfig.GRPCAddr = fmt.Sprintf("https://localhost:%s", babylond.GetPort("9090/tcp")) + + logger := logrus.New() + logger.SetLevel(logrus.DebugLevel) + logger.Out = os.Stdout + + // babylon configs for sending transactions + cfg.BabylonConfig.KeyDirectory = filepath.Join(tmpDir, "node0", "babylond") + // need to use this one to send otherwise we will have account sequence mismatch + // errors + cfg.BabylonConfig.Key = "test-spending-key" + + // Big adjustment to make sure we have enough gas in our transactions + cfg.BabylonConfig.GasAdjustment = 3.0 + + dirPath := filepath.Join(os.TempDir(), "stakerd", "e2etest") + err = os.MkdirAll(dirPath, 0755) + require.NoError(t, err) + dbTempDir, err := os.MkdirTemp(dirPath, "db") + require.NoError(t, err) + cfg.DBConfig.DBPath = dbTempDir + + dbbackend, err := stakercfg.GetDBBackend(cfg.DBConfig) + require.NoError(t, err) + + m := metrics.NewStakerMetrics() + stakerApp, err := staker.NewStakerAppFromConfig(cfg, logger, zapLogger, dbbackend, m) + require.NoError(t, err) + // we require separate client to send BTC headers to babylon node (interface does not need this method?) + bl, err := babylonclient.NewBabylonController(cfg.BabylonConfig, &cfg.ActiveNetParams, logger, zapLogger) + require.NoError(t, err) + + walletClient := stakerApp.Wallet() + + err = walletClient.UnlockWallet(20) + require.NoError(t, err) + + info, err := c.GetAddressInfo(tmBTC.RawAddr) + require.NoError(t, err) + + pubKeyHex := *info.PubKey + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + require.NoError(t, err) + walletPubKey, err := btcec.ParsePubKey(pubKeyBytes) + require.NoError(t, err) + + addressString := fmt.Sprintf("127.0.0.1:%d", testutil.AllocateUniquePort(t)) + addrPort := netip.MustParseAddrPort(addressString) + address := net.TCPAddrFromAddrPort(addrPort) + cfg.RPCListeners = append(cfg.RPCListeners, address) // todo(lazar): check with konrad who uses this + + stakerService := service.NewStakerService( + cfg, + stakerApp, + logger, + dbbackend, + ) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + err := stakerService.RunUntilShutdown(ctx) + if err != nil { + t.Fatalf("Error running server: %v", err) + } + }() + // Wait for the server to start + time.Sleep(3 * time.Second) + + stakerClient, err := dc.NewStakerServiceJSONRPCClient("tcp://" + addressString) + require.NoError(t, err) + + return &TestManager{ + Config: cfg, + Db: dbbackend, + Sa: stakerApp, + BabylonClient: bl, + WalletPubKey: walletPubKey, + wg: &wg, + serviceAddress: addressString, + StakerClient: stakerClient, + CovenantPrivKeys: coventantPrivKeys, + TestRpcClient: c, + manager: manager, + TestManagerBTC: *tmBTC, + } +} + +func (tm *TestManager) Stop(t *testing.T, cancelFunc context.CancelFunc) { + cancelFunc() + tm.wg.Wait() + err := tm.manager.ClearResources() + require.NoError(t, err) + err = os.RemoveAll(tm.Config.DBConfig.DBPath) + require.NoError(t, err) +} + +func (tm *TestManager) RestartApp(t *testing.T, newCtx context.Context, cancelFunc context.CancelFunc) { + // Restart the app with no-op action + tm.RestartAppWithAction(t, newCtx, cancelFunc, func(t *testing.T) {}) +} + +// RestartAppWithAction: +// 1. Stop the staker app +// 2. Perform provided action. Warning:this action must not use staker app as +// app is stopped at this point +// 3. Start the staker app +func (tm *TestManager) RestartAppWithAction(t *testing.T, ctx context.Context, cancelFunc context.CancelFunc, action func(t *testing.T)) { + // First stop the app + cancelFunc() + tm.wg.Wait() + + // Perform the action + action(t) + + // Now reset all components and start again + logger := logrus.New() + logger.SetLevel(logrus.DebugLevel) + logger.Out = os.Stdout + + dbbackend, err := stakercfg.GetDBBackend(tm.Config.DBConfig) + require.NoError(t, err) + m := metrics.NewStakerMetrics() + stakerApp, err := staker.NewStakerAppFromConfig(tm.Config, logger, zapLogger, dbbackend, m) + require.NoError(t, err) + + service := service.NewStakerService( + tm.Config, + stakerApp, + logger, + dbbackend, + ) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + err := service.RunUntilShutdown(ctx) + if err != nil { + t.Fatalf("Error running server: %v", err) + } + }() + // Wait for the server to start + time.Sleep(3 * time.Second) + + tm.wg = &wg + tm.Db = dbbackend + tm.Sa = stakerApp + stakerClient, err := dc.NewStakerServiceJSONRPCClient("tcp://" + tm.serviceAddress) + require.NoError(t, err) + tm.StakerClient = stakerClient +} + +func retrieveTransactionFromMempool(t *testing.T, client *rpcclient.Client, hashes []*chainhash.Hash) []*btcutil.Tx { + var txes []*btcutil.Tx + for _, txHash := range hashes { + tx, err := client.GetRawTransaction(txHash) + + if err != nil { + // this is e2e helper method, so this error most probably some of the + // transactions are still not in the mempool + return []*btcutil.Tx{} + } + + txes = append(txes, tx) + } + return txes +} + +func GetAllMinedBtcHeadersSinceGenesis(t *testing.T, c *rpcclient.Client) []*wire.BlockHeader { + height, err := c.GetBlockCount() + require.NoError(t, err) + + var headers []*wire.BlockHeader + + for i := 1; i <= int(height); i++ { + hash, err := c.GetBlockHash(int64(i)) + require.NoError(t, err) + header, err := c.GetBlockHeader(hash) + require.NoError(t, err) + headers = append(headers, header) + } + + return headers +} + +func opReturnScript(data []byte) []byte { + builder := txscript.NewScriptBuilder() + script, err := builder.AddOp(txscript.OP_RETURN).AddData(data).Script() + if err != nil { + panic(err) + } + return script +} + +func txToBytes(tx *wire.MsgTx) []byte { + buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) + _ = tx.Serialize(buf) + return buf.Bytes() +} + +func txsToBytes(txs []*wire.MsgTx) [][]byte { + var txsBytes [][]byte + for _, tx := range txs { + txsBytes = append(txsBytes, txToBytes(tx)) + } + return txsBytes +} + +func (tm *TestManager) FinalizeUntilEpoch(t *testing.T, epoch uint64) { + bbnClient := tm.BabylonClient.GetBBNClient() + ckptParams, err := bbnClient.BTCCheckpointParams() + require.NoError(t, err) + // wait until the checkpoint of this epoch is sealed + require.Eventually(t, func() bool { + lastSealedCkpt, err := bbnClient.LatestEpochFromStatus(ckpttypes.Sealed) + if err != nil { + return false + } + return epoch <= lastSealedCkpt.RawCheckpoint.EpochNum + }, 1*time.Minute, 1*time.Second) + + t.Logf("start finalizing epochs till %d", epoch) + // Random source for the generation of BTC data + // r := rand.New(rand.NewSource(time.Now().Unix())) + + // get all checkpoints of these epochs + pagination := &sdkquerytypes.PageRequest{ + Key: ckpttypes.CkptsObjectKey(1), + Limit: epoch, + } + resp, err := bbnClient.RawCheckpoints(pagination) + require.NoError(t, err) + require.Equal(t, int(epoch), len(resp.RawCheckpoints)) + + submitter := tm.BabylonClient.GetKeyAddress() + + for _, checkpoint := range resp.RawCheckpoints { + // currentBtcTipResp, err := tm.BabylonClient.QueryBtcLightClientTip() + // require.NoError(t, err) + // tipHeader, err := bbntypes.NewBTCHeaderBytesFromHex(currentBtcTipResp.HeaderHex) + // require.NoError(t, err) + + rawCheckpoint, err := checkpoint.Ckpt.ToRawCheckpoint() + require.NoError(t, err) + + btcCheckpoint, err := ckpttypes.FromRawCkptToBTCCkpt(rawCheckpoint, submitter) + require.NoError(t, err) + + babylonTagBytes, err := hex.DecodeString("01020304") + require.NoError(t, err) + + p1, p2, err := txformat.EncodeCheckpointData( + babylonTagBytes, + txformat.CurrentVersion, + btcCheckpoint, + ) + + err = tm.Sa.Wallet().UnlockWallet(60) + require.NoError(t, err) + tx1, err := tm.Sa.Wallet().CreateAndSignTx( + []*wire.TxOut{ + wire.NewTxOut(0, opReturnScript(p1)), + }, + 2000, + tm.MinerAddr, + nil, + ) + require.NoError(t, err) + _, err = tm.Sa.Wallet().SendRawTransaction(tx1, true) + require.NoError(t, err) + + resp1 := tm.BitcoindHandler.GenerateBlocks(1) + + tx2, err := tm.Sa.Wallet().CreateAndSignTx( + []*wire.TxOut{ + wire.NewTxOut(0, opReturnScript(p2)), + }, + 2000, + tm.MinerAddr, + nil, + ) + require.NoError(t, err) + _, err = tm.Sa.Wallet().SendRawTransaction(tx2, true) + require.NoError(t, err) + resp2 := tm.BitcoindHandler.GenerateBlocks(1) + + block1Hash, err := chainhash.NewHashFromStr(resp1.Blocks[0]) + require.NoError(t, err) + block2Hash, err := chainhash.NewHashFromStr(resp2.Blocks[0]) + require.NoError(t, err) + + block1, err := tm.TestRpcClient.GetBlock(block1Hash) + require.NoError(t, err) + block2, err := tm.TestRpcClient.GetBlock(block2Hash) + require.NoError(t, err) + + _, err = tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{ + &block1.Header, + &block2.Header, + }) + + header1Bytes := bbntypes.NewBTCHeaderBytesFromBlockHeader(&block1.Header) + header2Bytes := bbntypes.NewBTCHeaderBytesFromBlockHeader(&block2.Header) + + proof1, err := btcctypes.SpvProofFromHeaderAndTransactions(&header1Bytes, txsToBytes(block1.Transactions), 1) + require.NoError(t, err) + proof2, err := btcctypes.SpvProofFromHeaderAndTransactions(&header2Bytes, txsToBytes(block2.Transactions), 1) + require.NoError(t, err) + + _, err = tm.BabylonClient.InsertSpvProofs(submitter.String(), []*btcctypes.BTCSpvProof{ + proof1, + proof2, + }) + require.NoError(t, err) + + // // wait until this checkpoint is submitted + require.Eventually(t, func() bool { + ckpt, err := bbnClient.RawCheckpoint(checkpoint.Ckpt.EpochNum) + require.NoError(t, err) + return ckpt.RawCheckpoint.Status == ckpttypes.Submitted + }, eventuallyWaitTimeOut, eventuallyPollTime) + } + + tm.mineNEmptyBlocks(t, uint32(ckptParams.Params.CheckpointFinalizationTimeout), true) + + // // wait until the checkpoint of this epoch is finalised + require.Eventually(t, func() bool { + lastFinalizedCkpt, err := bbnClient.LatestEpochFromStatus(ckpttypes.Finalized) + if err != nil { + t.Logf("failed to get last finalized epoch: %v", err) + return false + } + return epoch <= lastFinalizedCkpt.RawCheckpoint.EpochNum + }, eventuallyWaitTimeOut, 1*time.Second) + + t.Logf("epoch %d is finalised", epoch) +} + +func (tm *TestManager) createAndRegisterFinalityProviders(t *testing.T, testStakingData *testStakingData) { + params, err := tm.BabylonClient.QueryStakingTracker() + require.NoError(t, err) + + for i := 0; i < testStakingData.GetNumRestakedFPs(); i++ { + // ensure the finality provider in testStakingData does not exist yet + fpResp, err := tm.BabylonClient.QueryFinalityProvider(testStakingData.FinalityProviderBtcKeys[i]) + require.Nil(t, fpResp) + require.Error(t, err) + require.True(t, errors.Is(err, babylonclient.ErrFinalityProviderDoesNotExist)) + + pop, err := btcstypes.NewPoPBTC(testStakingData.FinalityProviderBabylonAddrs[i], testStakingData.FinalityProviderBtcPrivKeys[i]) + require.NoError(t, err) + + btcFpKey := bbntypes.NewBIP340PubKeyFromBTCPK(testStakingData.FinalityProviderBtcKeys[i]) + + // get current finality providers + resp, err := tm.BabylonClient.QueryFinalityProviders(100, 0) + require.NoError(t, err) + // register the generated finality provider + err = tm.BabylonClient.RegisterFinalityProvider( + testStakingData.FinalityProviderBabylonAddrs[i], + testStakingData.FinalityProviderBabylonPrivKeys[i], + btcFpKey, + ¶ms.MinComissionRate, + &sttypes.Description{ + Moniker: "tester", + }, + pop, + ) + require.NoError(t, err) + + require.Eventually(t, func() bool { + resp2, err := tm.BabylonClient.QueryFinalityProviders(100, 0) + require.NoError(t, err) + + // After registration we should have one finality provider + return len(resp2.FinalityProviders) == len(resp.FinalityProviders)+1 + }, time.Minute, 250*time.Millisecond) + } +} + +func (tm *TestManager) sendHeadersToBabylon(t *testing.T, headers []*wire.BlockHeader) { + _, err := tm.BabylonClient.InsertBtcBlockHeaders(headers) + require.NoError(t, err) +} + +func (tm *TestManager) mineNEmptyBlocks(t *testing.T, numHeaders uint32, sendToBabylon bool) []*wire.BlockHeader { + resp := tm.BitcoindHandler.GenerateBlocks(int(numHeaders)) + + var minedHeaders []*wire.BlockHeader + for _, hash := range resp.Blocks { + hash, err := chainhash.NewHashFromStr(hash) + require.NoError(t, err) + header, err := tm.TestRpcClient.GetBlockHeader(hash) + require.NoError(t, err) + minedHeaders = append(minedHeaders, header) + } + + if sendToBabylon { + tm.sendHeadersToBabylon(t, minedHeaders) + } + + return minedHeaders +} + +func (tm *TestManager) mineBlock(t *testing.T) *wire.MsgBlock { + resp := tm.BitcoindHandler.GenerateBlocks(1) + hash, err := chainhash.NewHashFromStr(resp.Blocks[0]) + require.NoError(t, err) + header, err := tm.TestRpcClient.GetBlock(hash) + require.NoError(t, err) + return header +} + +func (tm *TestManager) sendStakingTxBTC( + t *testing.T, + testStakingData *testStakingData, + sendToBabylonFirst bool, +) *chainhash.Hash { + fpBTCPKs := []string{} + for i := 0; i < testStakingData.GetNumRestakedFPs(); i++ { + fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.FinalityProviderBtcKeys[i])) + fpBTCPKs = append(fpBTCPKs, fpBTCPK) + } + res, err := tm.StakerClient.Stake( + context.Background(), + tm.MinerAddr.String(), + testStakingData.StakingAmount, + fpBTCPKs, + int64(testStakingData.StakingTime), + sendToBabylonFirst, + ) + require.NoError(t, err) + txHash := res.TxHash + + stakingDetails, err := tm.StakerClient.StakingDetails(context.Background(), txHash) + require.NoError(t, err) + require.Equal(t, stakingDetails.StakingTxHash, txHash) + + if sendToBabylonFirst { + require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BABYLON.String()) + } else { + require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BTC.String()) + } + hashFromString, err := chainhash.NewHashFromStr(txHash) + require.NoError(t, err) + + // only wait for blocks if we are using the old flow, and send staking tx to BTC + // first + if !sendToBabylonFirst { + require.Eventually(t, func() bool { + txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{hashFromString}) + return len(txFromMempool) == 1 + }, eventuallyWaitTimeOut, eventuallyPollTime) + + mBlock := tm.mineBlock(t) + require.Equal(t, 2, len(mBlock.Transactions)) + + _, err = tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{&mBlock.Header}) + require.NoError(t, err) + } + return hashFromString +} + +func (tm *TestManager) sendMultipleStakingTx(t *testing.T, testStakingData []*testStakingData, sendToBabylonFirst bool) []*chainhash.Hash { + var hashes []*chainhash.Hash + for _, data := range testStakingData { + fpBTCPKs := []string{} + for i := 0; i < data.GetNumRestakedFPs(); i++ { + fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(data.FinalityProviderBtcKeys[i])) + fpBTCPKs = append(fpBTCPKs, fpBTCPK) + } + res, err := tm.StakerClient.Stake( + context.Background(), + tm.MinerAddr.String(), + data.StakingAmount, + fpBTCPKs, + int64(data.StakingTime), + sendToBabylonFirst, + ) + require.NoError(t, err) + txHash, err := chainhash.NewHashFromStr(res.TxHash) + require.NoError(t, err) + hashes = append(hashes, txHash) + } + + for _, txHash := range hashes { + txHash := txHash + hashStr := txHash.String() + stakingDetails, err := tm.StakerClient.StakingDetails(context.Background(), hashStr) + require.NoError(t, err) + require.Equal(t, stakingDetails.StakingTxHash, hashStr) + + if sendToBabylonFirst { + require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BABYLON.String()) + } else { + require.Equal(t, stakingDetails.StakingState, proto.TransactionState_SENT_TO_BTC.String()) + } + } + + if !sendToBabylonFirst { + mBlock := tm.mineBlock(t) + require.Equal(t, len(hashes)+1, len(mBlock.Transactions)) + + _, err := tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{&mBlock.Header}) + require.NoError(t, err) + } + return hashes +} + +func (tm *TestManager) sendWatchedStakingTx( + t *testing.T, + testStakingData *testStakingData, + params *babylonclient.StakingParams, +) *chainhash.Hash { + unbondingTme := uint16(params.FinalizationTimeoutBlocks) + 1 + + stakingInfo, err := staking.BuildStakingInfo( + testStakingData.StakerKey, + testStakingData.FinalityProviderBtcKeys, + params.CovenantPks, + params.CovenantQuruomThreshold, + testStakingData.StakingTime, + btcutil.Amount(testStakingData.StakingAmount), + regtestParams, + ) + require.NoError(t, err) + + err = tm.Sa.Wallet().UnlockWallet(20) + require.NoError(t, err) + + tx, err := tm.Sa.Wallet().CreateAndSignTx( + []*wire.TxOut{stakingInfo.StakingOutput}, + 2000, + tm.MinerAddr, + nil, + ) + require.NoError(t, err) + txHash := tx.TxHash() + _, err = tm.Sa.Wallet().SendRawTransaction(tx, true) + require.NoError(t, err) + + // Wait for tx to be in mempool + require.Eventually(t, func() bool { + tx, err := tm.TestRpcClient.GetRawTransaction(&txHash) + if err != nil { + return false + } + + if tx == nil { + return false + } + + return true + }, 1*time.Minute, eventuallyPollTime) + + stakingOutputIdx := 0 + + require.NoError(t, err) + + slashingTx, err := staking.BuildSlashingTxFromStakingTxStrict( + tx, + uint32(stakingOutputIdx), + params.SlashingPkScript, + testStakingData.StakerKey, + unbondingTme, + int64(params.MinSlashingTxFeeSat)+10, + params.SlashingRate, + regtestParams, + ) + require.NoError(t, err) + + stakingTxSlashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + + slashingSigResult, err := tm.Sa.Wallet().SignOneInputTaprootSpendingTransaction( + &walletcontroller.TaprootSigningRequest{ + FundingOutput: stakingInfo.StakingOutput, + TxToSign: slashingTx, + SignerAddress: tm.MinerAddr, + SpendDescription: &walletcontroller.SpendPathDescription{ + ControlBlock: &stakingTxSlashingPathInfo.ControlBlock, + ScriptLeaf: &stakingTxSlashingPathInfo.RevealedLeaf, + }, + }, + ) + + require.NoError(t, err) + require.NotNil(t, slashingSigResult.Signature) + + serializedStakingTx, err := utils.SerializeBtcTransaction(tx) + require.NoError(t, err) + serializedSlashingTx, err := utils.SerializeBtcTransaction(slashingTx) + require.NoError(t, err) + // Build unbonding related data + unbondingFee := params.UnbondingFee + unbondingAmount := btcutil.Amount(testStakingData.StakingAmount) - unbondingFee + + unbondingInfo, err := staking.BuildUnbondingInfo( + testStakingData.StakerKey, + testStakingData.FinalityProviderBtcKeys, + params.CovenantPks, + params.CovenantQuruomThreshold, + unbondingTme, + unbondingAmount, + regtestParams, + ) + require.NoError(t, err) + + unbondingSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + + unbondingTx := wire.NewMsgTx(2) + unbondingTx.AddTxIn(wire.NewTxIn(wire.NewOutPoint(&txHash, uint32(stakingOutputIdx)), nil, nil)) + unbondingTx.AddTxOut(unbondingInfo.UnbondingOutput) + + slashUnbondingTx, err := staking.BuildSlashingTxFromStakingTxStrict( + unbondingTx, + 0, + params.SlashingPkScript, + testStakingData.StakerKey, + unbondingTme, + int64(params.MinSlashingTxFeeSat)+10, + params.SlashingRate, + regtestParams, + ) + require.NoError(t, err) + + slashingUnbondingSigResult, err := tm.Sa.Wallet().SignOneInputTaprootSpendingTransaction( + &walletcontroller.TaprootSigningRequest{ + FundingOutput: unbondingTx.TxOut[0], + TxToSign: slashUnbondingTx, + SignerAddress: tm.MinerAddr, + SpendDescription: &walletcontroller.SpendPathDescription{ + ControlBlock: &unbondingSlashingPathInfo.ControlBlock, + ScriptLeaf: &unbondingSlashingPathInfo.RevealedLeaf, + }, + }, + ) + + require.NoError(t, err) + require.NotNil(t, slashingUnbondingSigResult.Signature) + + serializedUnbondingTx, err := utils.SerializeBtcTransaction(unbondingTx) + require.NoError(t, err) + serializedSlashUnbondingTx, err := utils.SerializeBtcTransaction(slashUnbondingTx) + require.NoError(t, err) + + babylonAddrHash := tmhash.Sum(testStakingData.StakerBabylonAddr.Bytes()) + + sig, err := tm.Sa.Wallet().SignBip322NativeSegwit(babylonAddrHash, tm.MinerAddr) + require.NoError(t, err) + + pop, err := babylonclient.NewBabylonBip322Pop( + babylonAddrHash, + sig, + tm.MinerAddr, + ) + require.NoError(t, err) + + fpBTCPKs := []string{} + for i := 0; i < testStakingData.GetNumRestakedFPs(); i++ { + fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.FinalityProviderBtcKeys[i])) + fpBTCPKs = append(fpBTCPKs, fpBTCPK) + } + _, err = tm.StakerClient.WatchStaking( + context.Background(), + hex.EncodeToString(serializedStakingTx), + int(testStakingData.StakingTime), + int(testStakingData.StakingAmount), + hex.EncodeToString(schnorr.SerializePubKey(testStakingData.StakerKey)), + fpBTCPKs, + hex.EncodeToString(serializedSlashingTx), + hex.EncodeToString(slashingSigResult.Signature.Serialize()), + testStakingData.StakerBabylonAddr.String(), + tm.MinerAddr.String(), + hex.EncodeToString(pop.BtcSig), + hex.EncodeToString(serializedUnbondingTx), + hex.EncodeToString(serializedSlashUnbondingTx), + hex.EncodeToString(slashingUnbondingSigResult.Signature.Serialize()), + int(unbondingTme), + // Use schnor verification + int(btcstypes.BTCSigType_BIP322), + ) + require.NoError(t, err) + + txs := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{&txHash}) + require.Len(t, txs, 1) + + mBlock := tm.mineBlock(t) + require.Equal(t, 2, len(mBlock.Transactions)) + _, err = tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{&mBlock.Header}) + require.NoError(t, err) + + return &txHash +} + +func (tm *TestManager) spendStakingTxWithHash(t *testing.T, stakingTxHash *chainhash.Hash) (*chainhash.Hash, *btcutil.Amount) { + res, err := tm.StakerClient.SpendStakingTransaction(context.Background(), stakingTxHash.String()) + require.NoError(t, err) + spendTxHash, err := chainhash.NewHashFromStr(res.TxHash) + require.NoError(t, err) + + iAmount, err := strconv.ParseInt(res.TxValue, 10, 64) + require.NoError(t, err) + spendTxValue := btcutil.Amount(iAmount) + + require.Eventually(t, func() bool { + txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{spendTxHash}) + return len(txFromMempool) == 1 + }, eventuallyWaitTimeOut, eventuallyPollTime) + + sendTx := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{spendTxHash})[0] + + // Tx is in mempool + txDetails, txState, err := tm.Sa.Wallet().TxDetails(spendTxHash, sendTx.MsgTx().TxOut[0].PkScript) + require.NoError(t, err) + require.Nil(t, txDetails) + require.Equal(t, txState, walletcontroller.TxInMemPool) + + // Block with spend is mined + mBlock1 := tm.mineBlock(t) + require.Equal(t, 2, len(mBlock1.Transactions)) + + // Tx is in chain + txDetails, txState, err = tm.Sa.Wallet().TxDetails(spendTxHash, sendTx.MsgTx().TxOut[0].PkScript) + require.NoError(t, err) + require.NotNil(t, txDetails) + require.Equal(t, txState, walletcontroller.TxInChain) + + return spendTxHash, &spendTxValue +} + +func (tm *TestManager) waitForStakingTxState(t *testing.T, txHash *chainhash.Hash, expectedState proto.TransactionState) { + require.Eventually(t, func() bool { + detailResult, err := tm.StakerClient.StakingDetails(context.Background(), txHash.String()) + if err != nil { + return false + } + return detailResult.StakingState == expectedState.String() + }, 1*time.Minute, eventuallyPollTime) +} + +func (tm *TestManager) walletUnspentsOutputsContainsOutput(t *testing.T, from btcutil.Address, withValue btcutil.Amount) bool { + unspentOutputs, err := tm.Sa.ListUnspentOutputs() + require.NoError(t, err) + + var containsOutput bool = false + + for _, output := range unspentOutputs { + if output.Address == tm.MinerAddr.String() && int64(output.Amount) == int64(withValue) { + containsOutput = true + } + } + + return containsOutput +} + +func (tm *TestManager) insertAllMinedBlocksToBabylon(t *testing.T) { + headers := GetAllMinedBtcHeadersSinceGenesis(t, tm.TestRpcClient) + _, err := tm.BabylonClient.InsertBtcBlockHeaders(headers) + require.NoError(t, err) +} + +func (tm *TestManager) insertCovenantSigForDelegation( + t *testing.T, + btcDel *btcstypes.BTCDelegationResponse, +) { + fpBTCPKs, err := bbntypes.NewBTCPKsFromBIP340PKs(btcDel.FpBtcPkList) + require.NoError(t, err) + + slashingTxBytes, err := hex.DecodeString(btcDel.SlashingTxHex) + require.NoError(t, err) + slashingTx := btcstypes.BTCSlashingTx(slashingTxBytes) + stakingTx := btcDel.StakingTxHex + stakingMsgTx, _, err := bbntypes.NewBTCTxFromHex(stakingTx) + require.NoError(t, err) + + cl := tm.Sa.BabylonController() + params, err := cl.Params() + require.NoError(t, err) + + stakingInfo, err := staking.BuildStakingInfo( + btcDel.BtcPk.MustToBTCPK(), + fpBTCPKs, + params.CovenantPks, + params.CovenantQuruomThreshold, + uint16(btcDel.EndHeight-btcDel.StartHeight), + btcutil.Amount(btcDel.TotalSat), + regtestParams, + ) + slashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + + covenantSlashingTxSigs, err := datagen.GenCovenantAdaptorSigs( + tm.CovenantPrivKeys, + fpBTCPKs, + stakingMsgTx, + slashingPathInfo.GetPkScriptPath(), + &slashingTx, + ) + require.NoError(t, err) + + // slash unbonding tx spends unbonding tx + unbondingMsgTx, _, err := bbntypes.NewBTCTxFromHex(btcDel.UndelegationResponse.UnbondingTxHex) + require.NoError(t, err) + unbondingInfo, err := staking.BuildUnbondingInfo( + btcDel.BtcPk.MustToBTCPK(), + fpBTCPKs, + params.CovenantPks, + params.CovenantQuruomThreshold, + uint16(btcDel.UnbondingTime), + btcutil.Amount(unbondingMsgTx.TxOut[0].Value), + regtestParams, + ) + unbondingSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + + // generate all covenant signatures from all covenant members + unbondingSlashingTx, err := btcstypes.NewBTCSlashingTxFromHex(btcDel.UndelegationResponse.SlashingTxHex) + require.NoError(t, err) + covenantUnbondingSlashingTxSigs, err := datagen.GenCovenantAdaptorSigs( + tm.CovenantPrivKeys, + fpBTCPKs, + unbondingMsgTx, + unbondingSlashingPathInfo.GetPkScriptPath(), + unbondingSlashingTx, + ) + require.NoError(t, err) + + // each covenant member submits signatures + unbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() + require.NoError(t, err) + covUnbondingSigs, err := datagen.GenCovenantUnbondingSigs( + tm.CovenantPrivKeys, + stakingMsgTx, + btcDel.StakingOutputIdx, + unbondingPathInfo.GetPkScriptPath(), + unbondingMsgTx, + ) + require.NoError(t, err) + + var messages []*btcstypes.MsgAddCovenantSigs + for i := 0; i < len(tm.CovenantPrivKeys); i++ { + msg := tm.BabylonClient.CreateCovenantMessage( + bbntypes.NewBIP340PubKeyFromBTCPK(tm.CovenantPrivKeys[i].PubKey()), + stakingMsgTx.TxHash().String(), + covenantSlashingTxSigs[i].AdaptorSigs, + bbntypes.NewBIP340SignatureFromBTCSig(covUnbondingSigs[i]), + covenantUnbondingSlashingTxSigs[i].AdaptorSigs, + ) + messages = append(messages, msg) + } + // we insert are covenant signatures in on message, this way staker + // program must handle the case of all signatures being present in Babylon + // delegation + // it also speeds up the tests + _, err = tm.BabylonClient.SubmitMultipleCovenantMessages(messages) + require.NoError(t, err) +} diff --git a/itest/testutil/app_cli.go b/itest/testutil/app_cli.go new file mode 100644 index 0000000..00f3e78 --- /dev/null +++ b/itest/testutil/app_cli.go @@ -0,0 +1,67 @@ +package testutil + +import ( + "bytes" + "encoding/json" + "fmt" + "math/rand" + "os" + "path/filepath" + "testing" + + "github.com/babylonlabs-io/babylon/testutil/datagen" + cmdadmin "github.com/babylonlabs-io/btc-staker/cmd/stakercli/admin" + cmddaemon "github.com/babylonlabs-io/btc-staker/cmd/stakercli/daemon" + "github.com/babylonlabs-io/btc-staker/cmd/stakercli/transaction" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" +) + +func TestApp() *cli.App { + app := cli.NewApp() + app.Name = "stakercli" + app.Commands = append(app.Commands, cmddaemon.DaemonCommands...) + app.Commands = append(app.Commands, cmdadmin.AdminCommands...) + app.Commands = append(app.Commands, transaction.TransactionCommands...) + return app +} + +func AppRunCreatePhase1StakingTx(r *rand.Rand, t *testing.T, app *cli.App, arguments []string) transaction.CreatePhase1StakingTxResponse { + args := []string{"stakercli", "transaction", "create-phase1-staking-transaction"} + args = append(args, arguments...) + output := appRunWithOutput(r, t, app, args) + + var data transaction.CreatePhase1StakingTxResponse + err := json.Unmarshal([]byte(output), &data) + require.NoError(t, err) + + return data +} + +func appRunWithOutput(r *rand.Rand, t *testing.T, app *cli.App, arguments []string) string { + outPut := filepath.Join(t.TempDir(), fmt.Sprintf("%s-out.txt", datagen.GenRandomHexStr(r, 10))) + outPutFile, err := os.Create(outPut) + require.NoError(t, err) + defer outPutFile.Close() + + // set file to stdout to read. + oldStd := os.Stdout + os.Stdout = outPutFile + + err = app.Run(arguments) + require.NoError(t, err) + + // set to old stdout + os.Stdout = oldStd + return readFromFile(t, outPutFile) +} + +func readFromFile(t *testing.T, f *os.File) string { + _, err := f.Seek(0, 0) + require.NoError(t, err) + + buf := new(bytes.Buffer) + _, err = buf.ReadFrom(f) + require.NoError(t, err) + return buf.String() +} From 2021de60e9fc6b707e2d950a4aaa83fc5ae63092 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 20 Nov 2024 15:10:21 -0300 Subject: [PATCH 09/38] chore: manager split btc data --- itest/e2e_test.go | 91 +++++++--------------------- itest/manager.go | 151 +++++++++++++++++++++++++++------------------- 2 files changed, 110 insertions(+), 132 deletions(-) diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 5977221..21e8c37 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -188,7 +188,7 @@ func TestSendingStakingTransactionWithPreApproval(t *testing.T) { tm.waitForStakingTxState(t, txHash, proto.TransactionState_VERIFIED) require.Eventually(t, func() bool { - txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{txHash}) + txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcBtcClient, []*chainhash.Hash{txHash}) return len(txFromMempool) == 1 }, eventuallyWaitTimeOut, eventuallyPollTime) @@ -224,7 +224,7 @@ func TestSendingStakingTransactionWithPreApproval(t *testing.T) { require.NoError(t, err) require.Eventually(t, func() bool { - tx, err := tm.TestRpcClient.GetRawTransaction(unbondingTxHash) + tx, err := tm.TestRpcBtcClient.GetRawTransaction(unbondingTxHash) if err != nil { return false } @@ -373,7 +373,7 @@ func TestMultiplePreApprovalTransactions(t *testing.T) { // Ultimately we will get 3 tx in the mempool meaning all staking transactions // use valid inputs require.Eventually(t, func() bool { - txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcClient, txHashes) + txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcBtcClient, txHashes) return len(txFromMempool) == 3 }, eventuallyWaitTimeOut, eventuallyPollTime) } @@ -517,7 +517,7 @@ func TestStakingUnbonding(t *testing.T) { require.NoError(t, err) require.Eventually(t, func() bool { - tx, err := tm.TestRpcClient.GetRawTransaction(unbondingTxHash) + tx, err := tm.TestRpcBtcClient.GetRawTransaction(unbondingTxHash) if err != nil { return false } @@ -593,7 +593,7 @@ func TestUnbondingRestartWaitingForSignatures(t *testing.T) { require.NoError(t, err) require.Eventually(t, func() bool { - tx, err := tm.TestRpcClient.GetRawTransaction(unbondingTxHash) + tx, err := tm.TestRpcBtcClient.GetRawTransaction(unbondingTxHash) if err != nil { return false } @@ -711,7 +711,7 @@ func TestBitcoindWalletBip322Signing(t *testing.T) { _ = h.CreateWallet(walletName, passphrase) rpcHost := fmt.Sprintf("127.0.0.1:%s", bitcoind.GetPort("18443/tcp")) - cfg, c := defaultStakerConfig(t, walletName, passphrase, rpcHost) + cfg, c := defaultStakerConfigAndBtc(t, walletName, passphrase, rpcHost) segwitAddress, err := c.GetNewAddress("") require.NoError(t, err) @@ -820,7 +820,7 @@ func TestRecoverAfterRestartDuringWithdrawal(t *testing.T) { unbondingTxHash, err := chainhash.NewHashFromStr(unbondResponse.UnbondingTxHash) require.NoError(t, err) require.Eventually(t, func() bool { - tx, err := tm.TestRpcClient.GetRawTransaction(unbondingTxHash) + tx, err := tm.TestRpcBtcClient.GetRawTransaction(unbondingTxHash) if err != nil { return false } @@ -847,72 +847,23 @@ func TestRecoverAfterRestartDuringWithdrawal(t *testing.T) { } func TestStakeFromPhase1(t *testing.T) { - // need to have at least 300 block on testnet as only then segwit is activated. - // Mature output is out which has 100 confirmations, which means 200mature outputs - // will generate 300 blocks - numMatureOutputs := uint32(200) + numMatureOutputsInWallet := uint32(200) ctx, cancel := context.WithCancel(context.Background()) - tm := StartManager(t, ctx, numMatureOutputs) - defer tm.Stop(t, cancel) - tm.insertAllMinedBlocksToBabylon(t) - - cl := tm.Sa.BabylonController() - params, err := cl.Params() - require.NoError(t, err) - // large staking time - stakingTime := uint16(1000) - testStakingData := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 50000, 1) - - tm.createAndRegisterFinalityProviders(t, testStakingData) - - txHash := tm.sendStakingTxBTC(t, testStakingData, false) - - go tm.mineNEmptyBlocks(t, params.ConfirmationTimeBlocks, true) - tm.waitForStakingTxState(t, txHash, proto.TransactionState_SENT_TO_BABYLON) - require.NoError(t, err) - - pend, err := tm.BabylonClient.QueryPendingBTCDelegations() - require.NoError(t, err) - require.Len(t, pend, 1) - // need to activate delegation to unbond - tm.insertCovenantSigForDelegation(t, pend[0]) - - tm.waitForStakingTxState(t, txHash, proto.TransactionState_DELEGATION_ACTIVE) - - resp, err := tm.StakerClient.UnbondStaking(context.Background(), txHash.String()) - require.NoError(t, err) - - unbondingTxHash, err := chainhash.NewHashFromStr(resp.UnbondingTxHash) + manager, err := containers.NewManager(t) require.NoError(t, err) - require.Eventually(t, func() bool { - tx, err := tm.TestRpcClient.GetRawTransaction(unbondingTxHash) - if err != nil { - return false - } - - if tx == nil { - return false - - } - - return true - }, 1*time.Minute, eventuallyPollTime) - - block := tm.mineBlock(t) - require.Equal(t, 2, len(block.Transactions)) - require.Equal(t, block.Transactions[1].TxHash(), *unbondingTxHash) - go tm.mineNEmptyBlocks(t, staker.UnbondingTxConfirmations, false) - tm.waitForStakingTxState(t, txHash, proto.TransactionState_UNBONDING_CONFIRMED_ON_BTC) + tmBTC := StartManagerBtc(t, ctx, numMatureOutputsInWallet, manager) + // defer tm.Stop(t, cancel) + defer func() { + cancel() + manager.ClearResources() + }() - withdrawableTransactionsResp, err := tm.StakerClient.WithdrawableTransactions(context.Background(), nil, nil) - require.NoError(t, err) - require.Len(t, withdrawableTransactionsResp.Transactions, 1) + minStakingTime := uint16(100) + stakerAddr := datagen.GenRandomAccount().GetAddress() + testStakingData := GetTestStakingData(t, tmBTC.WalletPubKey, minStakingTime, 10000, 1, stakerAddr) + fpKey := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.FinalityProviderBtcKeys[0])) - // We can spend unbonding tx immediately as in e2e test, finalization time is 4 blocks and we locked it - // finalization time + 1 i.e 5 blocks, but to consider unboning tx as confirmed we need to wait for 6 blocks - // so at this point time lock should already have passed - tm.spendStakingTxWithHash(t, txHash) - go tm.mineNEmptyBlocks(t, staker.SpendStakeTxConfirmations, false) - tm.waitForStakingTxState(t, txHash, proto.TransactionState_SPENT_ON_BTC) + require.NotNil(t, fpKey) + // tm.createAndRegisterFinalityProviders(t, testStakingData) } diff --git a/itest/manager.go b/itest/manager.go index d0a9dd2..6e970de 100644 --- a/itest/manager.go +++ b/itest/manager.go @@ -67,6 +67,9 @@ var ( eventuallyWaitTimeOut = 10 * time.Second eventuallyPollTime = 250 * time.Millisecond + + bitcoindUser = "user" + bitcoindPass = "pass" ) // keyToAddr maps the passed private to corresponding p2pkh address. @@ -79,7 +82,11 @@ func keyToAddr(key *btcec.PrivateKey, net *chaincfg.Params) (btcutil.Address, er return pubKeyAddr.AddressPubKeyHash(), nil } -func defaultStakerConfig(t *testing.T, walletName, passphrase, bitcoindHost string) (*stakercfg.Config, *rpcclient.Client) { +func defaultStakerConfigAndBtc(t *testing.T, walletName, passphrase, bitcoindHost string) (*stakercfg.Config, *rpcclient.Client) { + return defaultStakerConfig(t, walletName, passphrase, bitcoindHost), btcRpcTestClient(t, bitcoindHost) +} + +func defaultStakerConfig(t *testing.T, walletName, passphrase, bitcoindHost string) *stakercfg.Config { defaultConfig := stakercfg.DefaultConfig() // both wallet and node are bicoind @@ -91,9 +98,6 @@ func defaultStakerConfig(t *testing.T, walletName, passphrase, bitcoindHost stri defaultConfig.BtcNodeBackendConfig.FeeMode = "dynamic" defaultConfig.BtcNodeBackendConfig.EstimationMode = types.DynamicFeeEstimation - bitcoindUser := "user" - bitcoindPass := "pass" - // Wallet configuration defaultConfig.WalletRPCConfig.Host = bitcoindHost defaultConfig.WalletRPCConfig.User = bitcoindUser @@ -122,7 +126,11 @@ func defaultStakerConfig(t *testing.T, walletName, passphrase, bitcoindHost stri // For now diable concurrent sends but this need to be sorted out defaultConfig.StakerConfig.MaxConcurrentTransactions = 1 - testRpcClient, err := rpcclient.New(&rpcclient.ConnConfig{ + return &defaultConfig +} + +func btcRpcTestClient(t *testing.T, bitcoindHost string) *rpcclient.Client { + testRpcBtcClient, err := rpcclient.New(&rpcclient.ConnConfig{ Host: bitcoindHost, User: bitcoindUser, Pass: bitcoindPass, @@ -134,8 +142,7 @@ func defaultStakerConfig(t *testing.T, walletName, passphrase, bitcoindHost stri HTTPPostMode: true, }, nil) require.NoError(t, err) - - return &defaultConfig, testRpcClient + return testRpcBtcClient } type TestManager struct { @@ -143,12 +150,10 @@ type TestManager struct { Db kvdb.Backend Sa *staker.App BabylonClient *babylonclient.BabylonController - WalletPubKey *btcec.PublicKey wg *sync.WaitGroup serviceAddress string StakerClient *dc.StakerServiceJSONRPCClient CovenantPrivKeys []*btcec.PrivateKey - TestRpcClient *rpcclient.Client manager *containers.Manager TestManagerBTC } @@ -160,6 +165,9 @@ type TestManagerBTC struct { Bitcoind *dockertest.Resource WalletName string WalletPassphrase string + BitcoindHost string + WalletPubKey *btcec.PublicKey + TestRpcBtcClient *rpcclient.Client } type testStakingData struct { @@ -183,29 +191,44 @@ func (tm *TestManager) getTestStakingData( stakingTime uint16, stakingAmount int64, numRestakedFPs int, +) *testStakingData { + stkData := GetTestStakingData(t, stakerKey, stakingTime, stakingAmount, numRestakedFPs, tm.BabylonClient.GetKeyAddress()) + + strAddrs := make([]string, numRestakedFPs) + for i := 0; i < numRestakedFPs; i++ { + strAddrs[i] = stkData.FinalityProviderBabylonAddrs[i].String() + } + + _, _, err := tm.manager.BabylondTxBankMultiSend(t, "node0", "1000000ubbn", strAddrs...) + require.NoError(t, err) + return stkData +} + +func GetTestStakingData( + t *testing.T, + stakerKey *btcec.PublicKey, + stakingTime uint16, + stakingAmount int64, + numRestakedFPs int, + stakerBabylonAddr sdk.AccAddress, ) *testStakingData { fpBTCSKs, fpBTCPKs, err := datagen.GenRandomBTCKeyPairs(r, numRestakedFPs) require.NoError(t, err) fpBBNSKs, fpBBNAddrs := make([]*secp256k1.PrivKey, numRestakedFPs), make([]sdk.AccAddress, numRestakedFPs) - strAddrs := make([]string, numRestakedFPs) for i := 0; i < numRestakedFPs; i++ { fpBBNSK := secp256k1.GenPrivKey() fpAddr := sdk.AccAddress(fpBBNSK.PubKey().Address().Bytes()) fpBBNSKs[i] = fpBBNSK fpBBNAddrs[i] = fpAddr - strAddrs[i] = fpAddr.String() } - _, _, err = tm.manager.BabylondTxBankMultiSend(t, "node0", "1000000ubbn", strAddrs...) - require.NoError(t, err) - return &testStakingData{ StakerKey: stakerKey, // the staker babylon addr needs to be the same one that is going to sign // the transaction in the end - StakerBabylonAddr: tm.BabylonClient.GetKeyAddress(), + StakerBabylonAddr: stakerBabylonAddr, FinalityProviderBabylonPrivKeys: fpBBNSKs, FinalityProviderBabylonAddrs: fpBBNAddrs, FinalityProviderBtcPrivKeys: fpBTCSKs, @@ -251,6 +274,9 @@ func StartManagerBtc( Bitcoind: bitcoind, WalletName: walletName, WalletPassphrase: passphrase, + // BitcoindHost: bitcoindHost, + // WalletPubKey: walletPubKey, + // TestRpcBtcClient: rpcBtc, } } @@ -295,9 +321,9 @@ func StartManager( ) require.NoError(t, err) - rpcHost := fmt.Sprintf("127.0.0.1:%s", tmBTC.Bitcoind.GetPort("18443/tcp")) - cfg, c := defaultStakerConfig(t, tmBTC.WalletName, tmBTC.WalletPassphrase, rpcHost) - cfg.BtcNodeBackendConfig.Bitcoind.RPCHost = rpcHost + rpcBtcHost := fmt.Sprintf("127.0.0.1:%s", tmBTC.Bitcoind.GetPort("18443/tcp")) + cfg, c := defaultStakerConfigAndBtc(t, tmBTC.WalletName, tmBTC.WalletPassphrase, rpcBtcHost) + cfg.BtcNodeBackendConfig.Bitcoind.RPCHost = rpcBtcHost cfg.WalletRPCConfig.Host = fmt.Sprintf("127.0.0.1:%s", tmBTC.Bitcoind.GetPort("18443/tcp")) // update port with the dynamically allocated one from docker @@ -375,17 +401,18 @@ func StartManager( stakerClient, err := dc.NewStakerServiceJSONRPCClient("tcp://" + addressString) require.NoError(t, err) + tmBTC.TestRpcBtcClient = c + tmBTC.WalletPubKey = walletPubKey + tmBTC.BitcoindHost = rpcBtcHost return &TestManager{ Config: cfg, Db: dbbackend, Sa: stakerApp, BabylonClient: bl, - WalletPubKey: walletPubKey, wg: &wg, serviceAddress: addressString, StakerClient: stakerClient, CovenantPrivKeys: coventantPrivKeys, - TestRpcClient: c, manager: manager, TestManagerBTC: *tmBTC, } @@ -595,9 +622,9 @@ func (tm *TestManager) FinalizeUntilEpoch(t *testing.T, epoch uint64) { block2Hash, err := chainhash.NewHashFromStr(resp2.Blocks[0]) require.NoError(t, err) - block1, err := tm.TestRpcClient.GetBlock(block1Hash) + block1, err := tm.TestRpcBtcClient.GetBlock(block1Hash) require.NoError(t, err) - block2, err := tm.TestRpcClient.GetBlock(block2Hash) + block2, err := tm.TestRpcBtcClient.GetBlock(block2Hash) require.NoError(t, err) _, err = tm.BabylonClient.InsertBtcBlockHeaders([]*wire.BlockHeader{ @@ -642,29 +669,29 @@ func (tm *TestManager) FinalizeUntilEpoch(t *testing.T, epoch uint64) { t.Logf("epoch %d is finalised", epoch) } -func (tm *TestManager) createAndRegisterFinalityProviders(t *testing.T, testStakingData *testStakingData) { +func (tm *TestManager) createAndRegisterFinalityProviders(t *testing.T, stkData *testStakingData) { params, err := tm.BabylonClient.QueryStakingTracker() require.NoError(t, err) - for i := 0; i < testStakingData.GetNumRestakedFPs(); i++ { + for i := 0; i < stkData.GetNumRestakedFPs(); i++ { // ensure the finality provider in testStakingData does not exist yet - fpResp, err := tm.BabylonClient.QueryFinalityProvider(testStakingData.FinalityProviderBtcKeys[i]) + fpResp, err := tm.BabylonClient.QueryFinalityProvider(stkData.FinalityProviderBtcKeys[i]) require.Nil(t, fpResp) require.Error(t, err) require.True(t, errors.Is(err, babylonclient.ErrFinalityProviderDoesNotExist)) - pop, err := btcstypes.NewPoPBTC(testStakingData.FinalityProviderBabylonAddrs[i], testStakingData.FinalityProviderBtcPrivKeys[i]) + pop, err := btcstypes.NewPoPBTC(stkData.FinalityProviderBabylonAddrs[i], stkData.FinalityProviderBtcPrivKeys[i]) require.NoError(t, err) - btcFpKey := bbntypes.NewBIP340PubKeyFromBTCPK(testStakingData.FinalityProviderBtcKeys[i]) + btcFpKey := bbntypes.NewBIP340PubKeyFromBTCPK(stkData.FinalityProviderBtcKeys[i]) // get current finality providers resp, err := tm.BabylonClient.QueryFinalityProviders(100, 0) require.NoError(t, err) // register the generated finality provider err = tm.BabylonClient.RegisterFinalityProvider( - testStakingData.FinalityProviderBabylonAddrs[i], - testStakingData.FinalityProviderBabylonPrivKeys[i], + stkData.FinalityProviderBabylonAddrs[i], + stkData.FinalityProviderBabylonPrivKeys[i], btcFpKey, ¶ms.MinComissionRate, &sttypes.Description{ @@ -696,7 +723,7 @@ func (tm *TestManager) mineNEmptyBlocks(t *testing.T, numHeaders uint32, sendToB for _, hash := range resp.Blocks { hash, err := chainhash.NewHashFromStr(hash) require.NoError(t, err) - header, err := tm.TestRpcClient.GetBlockHeader(hash) + header, err := tm.TestRpcBtcClient.GetBlockHeader(hash) require.NoError(t, err) minedHeaders = append(minedHeaders, header) } @@ -712,27 +739,27 @@ func (tm *TestManager) mineBlock(t *testing.T) *wire.MsgBlock { resp := tm.BitcoindHandler.GenerateBlocks(1) hash, err := chainhash.NewHashFromStr(resp.Blocks[0]) require.NoError(t, err) - header, err := tm.TestRpcClient.GetBlock(hash) + header, err := tm.TestRpcBtcClient.GetBlock(hash) require.NoError(t, err) return header } func (tm *TestManager) sendStakingTxBTC( t *testing.T, - testStakingData *testStakingData, + stkData *testStakingData, sendToBabylonFirst bool, ) *chainhash.Hash { fpBTCPKs := []string{} - for i := 0; i < testStakingData.GetNumRestakedFPs(); i++ { - fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.FinalityProviderBtcKeys[i])) + for i := 0; i < stkData.GetNumRestakedFPs(); i++ { + fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(stkData.FinalityProviderBtcKeys[i])) fpBTCPKs = append(fpBTCPKs, fpBTCPK) } res, err := tm.StakerClient.Stake( context.Background(), tm.MinerAddr.String(), - testStakingData.StakingAmount, + stkData.StakingAmount, fpBTCPKs, - int64(testStakingData.StakingTime), + int64(stkData.StakingTime), sendToBabylonFirst, ) require.NoError(t, err) @@ -754,7 +781,7 @@ func (tm *TestManager) sendStakingTxBTC( // first if !sendToBabylonFirst { require.Eventually(t, func() bool { - txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{hashFromString}) + txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcBtcClient, []*chainhash.Hash{hashFromString}) return len(txFromMempool) == 1 }, eventuallyWaitTimeOut, eventuallyPollTime) @@ -767,9 +794,9 @@ func (tm *TestManager) sendStakingTxBTC( return hashFromString } -func (tm *TestManager) sendMultipleStakingTx(t *testing.T, testStakingData []*testStakingData, sendToBabylonFirst bool) []*chainhash.Hash { +func (tm *TestManager) sendMultipleStakingTx(t *testing.T, tStkData []*testStakingData, sendToBabylonFirst bool) []*chainhash.Hash { var hashes []*chainhash.Hash - for _, data := range testStakingData { + for _, data := range tStkData { fpBTCPKs := []string{} for i := 0; i < data.GetNumRestakedFPs(); i++ { fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(data.FinalityProviderBtcKeys[i])) @@ -815,18 +842,18 @@ func (tm *TestManager) sendMultipleStakingTx(t *testing.T, testStakingData []*te func (tm *TestManager) sendWatchedStakingTx( t *testing.T, - testStakingData *testStakingData, + tStkData *testStakingData, params *babylonclient.StakingParams, ) *chainhash.Hash { unbondingTme := uint16(params.FinalizationTimeoutBlocks) + 1 stakingInfo, err := staking.BuildStakingInfo( - testStakingData.StakerKey, - testStakingData.FinalityProviderBtcKeys, + tStkData.StakerKey, + tStkData.FinalityProviderBtcKeys, params.CovenantPks, params.CovenantQuruomThreshold, - testStakingData.StakingTime, - btcutil.Amount(testStakingData.StakingAmount), + tStkData.StakingTime, + btcutil.Amount(tStkData.StakingAmount), regtestParams, ) require.NoError(t, err) @@ -847,7 +874,7 @@ func (tm *TestManager) sendWatchedStakingTx( // Wait for tx to be in mempool require.Eventually(t, func() bool { - tx, err := tm.TestRpcClient.GetRawTransaction(&txHash) + tx, err := tm.TestRpcBtcClient.GetRawTransaction(&txHash) if err != nil { return false } @@ -867,7 +894,7 @@ func (tm *TestManager) sendWatchedStakingTx( tx, uint32(stakingOutputIdx), params.SlashingPkScript, - testStakingData.StakerKey, + tStkData.StakerKey, unbondingTme, int64(params.MinSlashingTxFeeSat)+10, params.SlashingRate, @@ -899,11 +926,11 @@ func (tm *TestManager) sendWatchedStakingTx( require.NoError(t, err) // Build unbonding related data unbondingFee := params.UnbondingFee - unbondingAmount := btcutil.Amount(testStakingData.StakingAmount) - unbondingFee + unbondingAmount := btcutil.Amount(tStkData.StakingAmount) - unbondingFee unbondingInfo, err := staking.BuildUnbondingInfo( - testStakingData.StakerKey, - testStakingData.FinalityProviderBtcKeys, + tStkData.StakerKey, + tStkData.FinalityProviderBtcKeys, params.CovenantPks, params.CovenantQuruomThreshold, unbondingTme, @@ -923,7 +950,7 @@ func (tm *TestManager) sendWatchedStakingTx( unbondingTx, 0, params.SlashingPkScript, - testStakingData.StakerKey, + tStkData.StakerKey, unbondingTme, int64(params.MinSlashingTxFeeSat)+10, params.SlashingRate, @@ -951,7 +978,7 @@ func (tm *TestManager) sendWatchedStakingTx( serializedSlashUnbondingTx, err := utils.SerializeBtcTransaction(slashUnbondingTx) require.NoError(t, err) - babylonAddrHash := tmhash.Sum(testStakingData.StakerBabylonAddr.Bytes()) + babylonAddrHash := tmhash.Sum(tStkData.StakerBabylonAddr.Bytes()) sig, err := tm.Sa.Wallet().SignBip322NativeSegwit(babylonAddrHash, tm.MinerAddr) require.NoError(t, err) @@ -964,20 +991,20 @@ func (tm *TestManager) sendWatchedStakingTx( require.NoError(t, err) fpBTCPKs := []string{} - for i := 0; i < testStakingData.GetNumRestakedFPs(); i++ { - fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.FinalityProviderBtcKeys[i])) + for i := 0; i < tStkData.GetNumRestakedFPs(); i++ { + fpBTCPK := hex.EncodeToString(schnorr.SerializePubKey(tStkData.FinalityProviderBtcKeys[i])) fpBTCPKs = append(fpBTCPKs, fpBTCPK) } _, err = tm.StakerClient.WatchStaking( context.Background(), hex.EncodeToString(serializedStakingTx), - int(testStakingData.StakingTime), - int(testStakingData.StakingAmount), - hex.EncodeToString(schnorr.SerializePubKey(testStakingData.StakerKey)), + int(tStkData.StakingTime), + int(tStkData.StakingAmount), + hex.EncodeToString(schnorr.SerializePubKey(tStkData.StakerKey)), fpBTCPKs, hex.EncodeToString(serializedSlashingTx), hex.EncodeToString(slashingSigResult.Signature.Serialize()), - testStakingData.StakerBabylonAddr.String(), + tStkData.StakerBabylonAddr.String(), tm.MinerAddr.String(), hex.EncodeToString(pop.BtcSig), hex.EncodeToString(serializedUnbondingTx), @@ -989,7 +1016,7 @@ func (tm *TestManager) sendWatchedStakingTx( ) require.NoError(t, err) - txs := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{&txHash}) + txs := retrieveTransactionFromMempool(t, tm.TestRpcBtcClient, []*chainhash.Hash{&txHash}) require.Len(t, txs, 1) mBlock := tm.mineBlock(t) @@ -1011,11 +1038,11 @@ func (tm *TestManager) spendStakingTxWithHash(t *testing.T, stakingTxHash *chain spendTxValue := btcutil.Amount(iAmount) require.Eventually(t, func() bool { - txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{spendTxHash}) + txFromMempool := retrieveTransactionFromMempool(t, tm.TestRpcBtcClient, []*chainhash.Hash{spendTxHash}) return len(txFromMempool) == 1 }, eventuallyWaitTimeOut, eventuallyPollTime) - sendTx := retrieveTransactionFromMempool(t, tm.TestRpcClient, []*chainhash.Hash{spendTxHash})[0] + sendTx := retrieveTransactionFromMempool(t, tm.TestRpcBtcClient, []*chainhash.Hash{spendTxHash})[0] // Tx is in mempool txDetails, txState, err := tm.Sa.Wallet().TxDetails(spendTxHash, sendTx.MsgTx().TxOut[0].PkScript) @@ -1043,7 +1070,7 @@ func (tm *TestManager) waitForStakingTxState(t *testing.T, txHash *chainhash.Has return false } return detailResult.StakingState == expectedState.String() - }, 1*time.Minute, eventuallyPollTime) + }, 2*time.Minute, eventuallyPollTime) } func (tm *TestManager) walletUnspentsOutputsContainsOutput(t *testing.T, from btcutil.Address, withValue btcutil.Amount) bool { @@ -1062,7 +1089,7 @@ func (tm *TestManager) walletUnspentsOutputsContainsOutput(t *testing.T, from bt } func (tm *TestManager) insertAllMinedBlocksToBabylon(t *testing.T) { - headers := GetAllMinedBtcHeadersSinceGenesis(t, tm.TestRpcClient) + headers := GetAllMinedBtcHeadersSinceGenesis(t, tm.TestRpcBtcClient) _, err := tm.BabylonClient.InsertBtcBlockHeaders(headers) require.NoError(t, err) } From 17ebc5daf4c1143779c016eb85b7ddfec6549cfd Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 20 Nov 2024 15:27:03 -0300 Subject: [PATCH 10/38] chore: split func to generate covenant pk --- itest/e2e_test.go | 32 ++++++++++++++++++++--- itest/manager.go | 65 ++++++++++++++++++++++------------------------- 2 files changed, 60 insertions(+), 37 deletions(-) diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 21e8c37..7566031 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -11,7 +11,9 @@ import ( "time" "github.com/babylonlabs-io/btc-staker/itest/containers" + "github.com/babylonlabs-io/btc-staker/itest/testutil" + "github.com/babylonlabs-io/babylon/btcstaking" "github.com/babylonlabs-io/babylon/crypto/bip322" btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" @@ -853,7 +855,6 @@ func TestStakeFromPhase1(t *testing.T) { require.NoError(t, err) tmBTC := StartManagerBtc(t, ctx, numMatureOutputsInWallet, manager) - // defer tm.Stop(t, cancel) defer func() { cancel() manager.ClearResources() @@ -862,8 +863,33 @@ func TestStakeFromPhase1(t *testing.T) { minStakingTime := uint16(100) stakerAddr := datagen.GenRandomAccount().GetAddress() testStakingData := GetTestStakingData(t, tmBTC.WalletPubKey, minStakingTime, 10000, 1, stakerAddr) - fpKey := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.FinalityProviderBtcKeys[0])) - require.NotNil(t, fpKey) + fpPkHex := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.FinalityProviderBtcKeys[0])) + btcStakerPkHex := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.StakerKey)) + + appCli := testutil.TestApp() + tagHex := datagen.GenRandomHexStr(r, btcstaking.TagLen) + + covenants := genCovenants(t, 1) + + covenantPkHex := hex.EncodeToString(schnorr.SerializePubKey(covenants[0].PubKey())) + + commonFlags := []string{ + fmt.Sprintf("--covenant-committee-pks=%s", covenantPkHex), + fmt.Sprintf("--tag=%s", tagHex), + "--covenant-quorum=1", "--network=regtest", + } + + fpDepositStakingAmount := 5000000 // 0.05BTC + fpStakingTimeLock := 52560 // 1 year + + createTxCmdArgs := []string{ + fmt.Sprintf("--staker-pk=%s", btcStakerPkHex), + fmt.Sprintf("--finality-provider-pk=%s", fpPkHex), + fmt.Sprintf("--staking-amount=%d", fpDepositStakingAmount), + fmt.Sprintf("--staking-time=%d", fpStakingTimeLock), + } + res := testutil.AppRunCreatePhase1StakingTx(r, t, appCli, append(createTxCmdArgs, commonFlags...)) + require.NotNil(t, res) // tm.createAndRegisterFinalityProviders(t, testStakingData) } diff --git a/itest/manager.go b/itest/manager.go index 6e970de..b12eb24 100644 --- a/itest/manager.go +++ b/itest/manager.go @@ -159,7 +159,6 @@ type TestManager struct { } type TestManagerBTC struct { - RawAddr string MinerAddr btcutil.Address BitcoindHandler *BitcoindTestHandler Bitcoind *dockertest.Resource @@ -267,16 +266,31 @@ func StartManagerBtc( minerAddressDecoded, err := btcutil.DecodeAddress(br.Address, regtestParams) require.NoError(t, err) + bitcoindHost := fmt.Sprintf("127.0.0.1:%s", bitcoind.GetPort("18443/tcp")) + rpcBtc := btcRpcTestClient(t, bitcoindHost) + + err = rpcBtc.WalletPassphrase(passphrase, 20) + require.NoError(t, err) + + info, err := rpcBtc.GetAddressInfo(br.Address) + require.NoError(t, err) + + pubKeyHex := *info.PubKey + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + require.NoError(t, err) + + walletPubKey, err := btcec.ParsePubKey(pubKeyBytes) + require.NoError(t, err) + return &TestManagerBTC{ - RawAddr: br.Address, MinerAddr: minerAddressDecoded, BitcoindHandler: bitcoindHandler, Bitcoind: bitcoind, WalletName: walletName, WalletPassphrase: passphrase, - // BitcoindHost: bitcoindHost, - // WalletPubKey: walletPubKey, - // TestRpcBtcClient: rpcBtc, + BitcoindHost: bitcoindHost, + WalletPubKey: walletPubKey, + TestRpcBtcClient: rpcBtc, } } @@ -291,13 +305,7 @@ func StartManager( tmBTC := StartManagerBtc(t, ctx, numMatureOutputsInWallet, manager) quorum := 2 - numCovenants := 3 - var coventantPrivKeys []*btcec.PrivateKey - for i := 0; i < numCovenants; i++ { - covenantPrivKey, err := btcec.NewPrivateKey() - require.NoError(t, err) - coventantPrivKeys = append(coventantPrivKeys, covenantPrivKey) - } + coventantPrivKeys := genCovenants(t, 3) var buff bytes.Buffer err = regtestParams.GenesisBlock.Header.Serialize(&buff) @@ -321,11 +329,7 @@ func StartManager( ) require.NoError(t, err) - rpcBtcHost := fmt.Sprintf("127.0.0.1:%s", tmBTC.Bitcoind.GetPort("18443/tcp")) - cfg, c := defaultStakerConfigAndBtc(t, tmBTC.WalletName, tmBTC.WalletPassphrase, rpcBtcHost) - cfg.BtcNodeBackendConfig.Bitcoind.RPCHost = rpcBtcHost - cfg.WalletRPCConfig.Host = fmt.Sprintf("127.0.0.1:%s", tmBTC.Bitcoind.GetPort("18443/tcp")) - + cfg := defaultStakerConfig(t, tmBTC.WalletName, tmBTC.WalletPassphrase, tmBTC.BitcoindHost) // update port with the dynamically allocated one from docker cfg.BabylonConfig.RPCAddr = fmt.Sprintf("http://localhost:%s", babylond.GetPort("26657/tcp")) cfg.BabylonConfig.GRPCAddr = fmt.Sprintf("https://localhost:%s", babylond.GetPort("9090/tcp")) @@ -360,20 +364,6 @@ func StartManager( bl, err := babylonclient.NewBabylonController(cfg.BabylonConfig, &cfg.ActiveNetParams, logger, zapLogger) require.NoError(t, err) - walletClient := stakerApp.Wallet() - - err = walletClient.UnlockWallet(20) - require.NoError(t, err) - - info, err := c.GetAddressInfo(tmBTC.RawAddr) - require.NoError(t, err) - - pubKeyHex := *info.PubKey - pubKeyBytes, err := hex.DecodeString(pubKeyHex) - require.NoError(t, err) - walletPubKey, err := btcec.ParsePubKey(pubKeyBytes) - require.NoError(t, err) - addressString := fmt.Sprintf("127.0.0.1:%d", testutil.AllocateUniquePort(t)) addrPort := netip.MustParseAddrPort(addressString) address := net.TCPAddrFromAddrPort(addrPort) @@ -401,9 +391,6 @@ func StartManager( stakerClient, err := dc.NewStakerServiceJSONRPCClient("tcp://" + addressString) require.NoError(t, err) - tmBTC.TestRpcBtcClient = c - tmBTC.WalletPubKey = walletPubKey - tmBTC.BitcoindHost = rpcBtcHost return &TestManager{ Config: cfg, Db: dbbackend, @@ -418,6 +405,16 @@ func StartManager( } } +func genCovenants(t *testing.T, numCovenants int) []*btcec.PrivateKey { + var coventantPrivKeys []*btcec.PrivateKey + for i := 0; i < numCovenants; i++ { + covenantPrivKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + coventantPrivKeys = append(coventantPrivKeys, covenantPrivKey) + } + return coventantPrivKeys +} + func (tm *TestManager) Stop(t *testing.T, cancelFunc context.CancelFunc) { cancelFunc() tm.wg.Wait() From 35ec1590ba5f34cbd3af1b7d63799269f0ad69a7 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 20 Nov 2024 17:43:52 -0300 Subject: [PATCH 11/38] chore: add staking of phase 1 test --- .../transaction/transactions_test.go | 75 ++++--------------- itest/bitcoind_node_setup.go | 3 +- itest/containers/containers.go | 7 +- itest/e2e_test.go | 50 +++++++++++-- itest/testutil/app_cli.go | 56 ++++++++++++++ 5 files changed, 122 insertions(+), 69 deletions(-) diff --git a/cmd/stakercli/transaction/transactions_test.go b/cmd/stakercli/transaction/transactions_test.go index 8208724..f0da5d4 100644 --- a/cmd/stakercli/transaction/transactions_test.go +++ b/cmd/stakercli/transaction/transactions_test.go @@ -28,9 +28,8 @@ import ( "github.com/stretchr/testify/require" "github.com/urfave/cli" - cmdadmin "github.com/babylonlabs-io/btc-staker/cmd/stakercli/admin" - cmddaemon "github.com/babylonlabs-io/btc-staker/cmd/stakercli/daemon" "github.com/babylonlabs-io/btc-staker/cmd/stakercli/transaction" + "github.com/babylonlabs-io/btc-staker/itest/testutil" "github.com/babylonlabs-io/btc-staker/utils" ) @@ -110,36 +109,12 @@ func FuzzFinalityProviderDeposit(f *testing.F) { fmt.Sprintf("--staking-time=%d", fpStakingTimeLock), } - app := testApp() - stakingTx := appRunCreatePhase1StakingTx(r, t, app, append(createTxCmdArgs, commonFlags...)) + app := testutil.TestApp() + stakingTx := testutil.AppRunCreatePhase1StakingTx(r, t, app, append(createTxCmdArgs, commonFlags...)) require.NotNil(t, stakingTx) }) } -func appRunCreatePhase1StakingTxWithParams(r *rand.Rand, t *testing.T, app *cli.App, arguments []string) transaction.CreatePhase1StakingTxResponse { - args := []string{"stakercli", "transaction", "create-phase1-staking-transaction-with-params"} - args = append(args, arguments...) - output := appRunWithOutput(r, t, app, args) - - var data transaction.CreatePhase1StakingTxResponse - err := json.Unmarshal([]byte(output), &data) - require.NoError(t, err) - - return data -} - -func appRunCreatePhase1StakingTx(r *rand.Rand, t *testing.T, app *cli.App, arguments []string) transaction.CreatePhase1StakingTxResponse { - args := []string{"stakercli", "transaction", "create-phase1-staking-transaction"} - args = append(args, arguments...) - output := appRunWithOutput(r, t, app, args) - - var data transaction.CreatePhase1StakingTxResponse - err := json.Unmarshal([]byte(output), &data) - require.NoError(t, err) - - return data -} - func appRunCheckPhase1StakingTxParams(r *rand.Rand, t *testing.T, app *cli.App, arguments []string) transaction.CheckPhase1StakingTxResponse { args := []string{"stakercli", "transaction", "check-phase1-staking-transaction-params"} args = append(args, arguments...) @@ -192,15 +167,6 @@ func readFromFile(t *testing.T, f *os.File) string { return buf.String() } -func testApp() *cli.App { - app := cli.NewApp() - app.Name = "stakercli" - app.Commands = append(app.Commands, cmddaemon.DaemonCommands...) - app.Commands = append(app.Commands, cmdadmin.AdminCommands...) - app.Commands = append(app.Commands, transaction.TransactionCommands...) - return app -} - func appRunCreatePhase1UnbondingTx(r *rand.Rand, t *testing.T, app *cli.App, arguments []string) transaction.CreatePhase1UnbondingTxResponse { args := []string{"stakercli", "transaction", "create-phase1-unbonding-transaction"} args = append(args, arguments...) @@ -227,17 +193,6 @@ func randRange(_ *rand.Rand, min, max int) int { return rand.Intn(max+1-min) + min } -func createTempFileWithParams(f *testing.F) string { - file, err := os.CreateTemp("", "tmpParams-*.json") - require.NoError(f, err) - defer file.Close() - _, err = file.Write(paramsMarshalled) - require.NoError(f, err) - info, err := file.Stat() - require.NoError(f, err) - return filepath.Join(os.TempDir(), info.Name()) -} - type StakeParameters struct { StakerPk *btcec.PublicKey FinalityProviderPk *btcec.PublicKey @@ -280,7 +235,7 @@ func createCustomValidStakeParams( func TestCheckPhase1StakingTransactionCmd(t *testing.T) { t.Parallel() - app := testApp() + app := testutil.TestApp() stakerCliCheckP1StkTx := []string{ "stakercli", "transaction", "check-phase1-staking-transaction", "--covenant-quorum=1", @@ -386,12 +341,12 @@ func TestCheckPhase1StakingTransactionCmd(t *testing.T) { // Property: Every create should end without error for valid params func FuzzCreatPhase1Tx(f *testing.F) { - paramsFilePath := createTempFileWithParams(f) + paramsFilePath := testutil.CreateTempFileWithParams(f) datagen.AddRandomSeedsToFuzzer(f, 5) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - app := testApp() + app := testutil.TestApp() var args []string args = append(args, paramsFilePath) @@ -400,7 +355,7 @@ func FuzzCreatPhase1Tx(f *testing.F) { args = append(args, createArgs...) - resCreate := appRunCreatePhase1StakingTxWithParams( + resCreate := testutil.AppRunCreatePhase1StakingTxWithParams( r, t, app, args, ) require.NotNil(t, resCreate) @@ -412,12 +367,12 @@ func keyToSchnorrHex(key *btcec.PublicKey) string { } func FuzzCheckPhase1Tx(f *testing.F) { - paramsFilePath := createTempFileWithParams(f) + paramsFilePath := testutil.CreateTempFileWithParams(f) datagen.AddRandomSeedsToFuzzer(f, 5) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - app := testApp() + app := testutil.TestApp() stakerParams, _ := createCustomValidStakeParams(t, r, &globalParams, &chaincfg.RegressionNetParams) @@ -462,7 +417,7 @@ func FuzzCheckPhase1Tx(f *testing.F) { } func FuzzCreateUnbondingTx(f *testing.F) { - paramsFilePath := createTempFileWithParams(f) + paramsFilePath := testutil.CreateTempFileWithParams(f) datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -495,7 +450,7 @@ func FuzzCreateUnbondingTx(f *testing.F) { fmt.Sprintf("--network=%s", chaincfg.RegressionNetParams.Name), } - app := testApp() + app := testutil.TestApp() unbondingTxResponse := appRunCreatePhase1UnbondingTx(r, t, app, createTxCmdArgs) require.NotNil(t, unbondingTxResponse) utx, _, err := bbn.NewBTCTxFromHex(unbondingTxResponse.UnbondingTxHex) @@ -512,7 +467,7 @@ func FuzzCreateUnbondingTx(f *testing.F) { } func FuzzCreateWithdrawalStaking(f *testing.F) { - paramsFilePath := createTempFileWithParams(f) + paramsFilePath := testutil.CreateTempFileWithParams(f) datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -557,7 +512,7 @@ func FuzzCreateWithdrawalStaking(f *testing.F) { fmt.Sprintf("--network=%s", chaincfg.RegressionNetParams.Name), } - app := testApp() + app := testutil.TestApp() wr := appRunCreatePhase1WithdrawalTx(r, t, app, createTxCmdArgs) require.NotNil(t, wr) @@ -600,7 +555,7 @@ func FuzzCreateWithdrawalStaking(f *testing.F) { } func FuzzCreateWithdrawalUnbonding(f *testing.F) { - paramsFilePath := createTempFileWithParams(f) + paramsFilePath := testutil.CreateTempFileWithParams(f) datagen.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { @@ -668,7 +623,7 @@ func FuzzCreateWithdrawalUnbonding(f *testing.F) { fmt.Sprintf("--network=%s", chaincfg.RegressionNetParams.Name), } - app := testApp() + app := testutil.TestApp() wr := appRunCreatePhase1WithdrawalTx(r, t, app, createTxCmdArgs) require.NotNil(t, wr) diff --git a/itest/bitcoind_node_setup.go b/itest/bitcoind_node_setup.go index 30d9084..fb3b6ea 100644 --- a/itest/bitcoind_node_setup.go +++ b/itest/bitcoind_node_setup.go @@ -3,13 +3,14 @@ package e2etest import ( "encoding/json" "fmt" - "github.com/ory/dockertest/v3" "os" "strconv" "strings" "testing" "time" + "github.com/ory/dockertest/v3" + "github.com/babylonlabs-io/btc-staker/itest/containers" "github.com/stretchr/testify/require" ) diff --git a/itest/containers/containers.go b/itest/containers/containers.go index d490131..841a84c 100644 --- a/itest/containers/containers.go +++ b/itest/containers/containers.go @@ -4,15 +4,16 @@ import ( "bytes" "context" "fmt" - bbn "github.com/babylonlabs-io/babylon/types" - "github.com/babylonlabs-io/btc-staker/itest/testutil" - "github.com/btcsuite/btcd/btcec/v2" "regexp" "strconv" "strings" "testing" "time" + bbn "github.com/babylonlabs-io/babylon/types" + "github.com/babylonlabs-io/btc-staker/itest/testutil" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/stretchr/testify/require" diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 7566031..1e2b80d 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -4,6 +4,7 @@ package e2etest import ( + "bytes" "context" "encoding/hex" "fmt" @@ -25,6 +26,7 @@ import ( "github.com/babylonlabs-io/btc-staker/types" "github.com/babylonlabs-io/btc-staker/walletcontroller" "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" @@ -880,16 +882,54 @@ func TestStakeFromPhase1(t *testing.T) { "--covenant-quorum=1", "--network=regtest", } - fpDepositStakingAmount := 5000000 // 0.05BTC - fpStakingTimeLock := 52560 // 1 year + paramsFilePath := testutil.CreateTempFileWithParams(t) + lastParams := testutil.GlobalParams.Versions[len(testutil.GlobalParams.Versions)-1] + fpDepositStakingAmount := lastParams.MinStakingAmount + inclusionHeight := lastParams.ActivationHeight + 1 + stakingTime := lastParams.MaxStakingTime createTxCmdArgs := []string{ + paramsFilePath, fmt.Sprintf("--staker-pk=%s", btcStakerPkHex), fmt.Sprintf("--finality-provider-pk=%s", fpPkHex), fmt.Sprintf("--staking-amount=%d", fpDepositStakingAmount), - fmt.Sprintf("--staking-time=%d", fpStakingTimeLock), + fmt.Sprintf("--tx-inclusion-height=%d", inclusionHeight), + fmt.Sprintf("--staking-time=%d", stakingTime), } - res := testutil.AppRunCreatePhase1StakingTx(r, t, appCli, append(createTxCmdArgs, commonFlags...)) - require.NotNil(t, res) + resP1StkTx := testutil.AppRunCreatePhase1StakingTxWithParams(r, t, appCli, append(createTxCmdArgs, commonFlags...)) + require.NotNil(t, resP1StkTx) + + stkTx, err := hex.DecodeString(resP1StkTx.StakingTxHex) + require.NoError(t, err) + + var tx wire.MsgTx + rbuf := bytes.NewReader(stkTx) + err = tx.DeserializeNoWitness(rbuf) + require.NoError(t, err) + + rpcBtc := tmBTC.TestRpcBtcClient + err = rpcBtc.WalletPassphrase(tmBTC.WalletPassphrase, 20) + require.NoError(t, err) + + resFundRawStkTx, err := rpcBtc.FundRawTransaction(&tx, btcjson.FundRawTransactionOpts{ + FeeRate: btcjson.Float64(0.02), + }, btcjson.Bool(false)) + require.NoError(t, err) + require.NotNil(t, resFundRawStkTx) + + signedStkTx, complete, err := rpcBtc.SignRawTransactionWithWallet(resFundRawStkTx.Transaction) + require.True(t, complete) + require.NoError(t, err) + require.NotNil(t, signedStkTx) + + txHash, err := rpcBtc.SendRawTransaction(signedStkTx, false) + require.NoError(t, err) + require.NotNil(t, txHash) + + tmBTC.BitcoindHandler.GenerateBlocks(5) + + stkTxResult, err := rpcBtc.GetTransaction(txHash) + require.NoError(t, err) + require.NotNil(t, stkTxResult) // tm.createAndRegisterFinalityProviders(t, testStakingData) } diff --git a/itest/testutil/app_cli.go b/itest/testutil/app_cli.go index 00f3e78..b88b537 100644 --- a/itest/testutil/app_cli.go +++ b/itest/testutil/app_cli.go @@ -13,10 +13,43 @@ import ( cmdadmin "github.com/babylonlabs-io/btc-staker/cmd/stakercli/admin" cmddaemon "github.com/babylonlabs-io/btc-staker/cmd/stakercli/daemon" "github.com/babylonlabs-io/btc-staker/cmd/stakercli/transaction" + "github.com/babylonlabs-io/networks/parameters/parser" "github.com/stretchr/testify/require" "github.com/urfave/cli" ) +var ( + defaultParam = parser.VersionedGlobalParams{ + Version: 0, + ActivationHeight: 100, + StakingCap: 3000000, + CapHeight: 0, + Tag: "01020304", + CovenantPks: []string{ + "03ffeaec52a9b407b355ef6967a7ffc15fd6c3fe07de2844d61550475e7a5233e5", + "03a5c60c2188e833d39d0fa798ab3f69aa12ed3dd2f3bad659effa252782de3c31", + "0359d3532148a597a2d05c0395bf5f7176044b1cd312f37701a9b4d0aad70bc5a4", + "0357349e985e742d5131e1e2b227b5170f6350ac2e2feb72254fcc25b3cee21a18", + "03c8ccb03c379e452f10c81232b41a1ca8b63d0baf8387e57d302c987e5abb8527", + }, + CovenantQuorum: 3, + UnbondingTime: 1000, + UnbondingFee: 1000, + MaxStakingAmount: 300000, + MinStakingAmount: 3000, + MaxStakingTime: 10000, + MinStakingTime: 100, + ConfirmationDepth: 10, + } + + GlobalParams = parser.GlobalParams{ + Versions: []*parser.VersionedGlobalParams{&defaultParam}, + } + + //nolint:errchkjson + paramsMarshalled, _ = json.Marshal(GlobalParams) +) + func TestApp() *cli.App { app := cli.NewApp() app.Name = "stakercli" @@ -38,6 +71,18 @@ func AppRunCreatePhase1StakingTx(r *rand.Rand, t *testing.T, app *cli.App, argum return data } +func AppRunCreatePhase1StakingTxWithParams(r *rand.Rand, t *testing.T, app *cli.App, arguments []string) transaction.CreatePhase1StakingTxResponse { + args := []string{"stakercli", "transaction", "create-phase1-staking-transaction-with-params"} + args = append(args, arguments...) + output := appRunWithOutput(r, t, app, args) + + var data transaction.CreatePhase1StakingTxResponse + err := json.Unmarshal([]byte(output), &data) + require.NoError(t, err) + + return data +} + func appRunWithOutput(r *rand.Rand, t *testing.T, app *cli.App, arguments []string) string { outPut := filepath.Join(t.TempDir(), fmt.Sprintf("%s-out.txt", datagen.GenRandomHexStr(r, 10))) outPutFile, err := os.Create(outPut) @@ -65,3 +110,14 @@ func readFromFile(t *testing.T, f *os.File) string { require.NoError(t, err) return buf.String() } + +func CreateTempFileWithParams(f require.TestingT) string { + file, err := os.CreateTemp("", "tmpParams-*.json") + require.NoError(f, err) + defer file.Close() + _, err = file.Write(paramsMarshalled) + require.NoError(f, err) + info, err := file.Stat() + require.NoError(f, err) + return filepath.Join(os.TempDir(), info.Name()) +} From 81b947ba2adc86b4f2ed87e8a7e1020d798384d2 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 20 Nov 2024 21:59:48 -0300 Subject: [PATCH 12/38] fix: lint --- .../transaction/transactions_test.go | 7 +-- itest/containers/containers.go | 16 ++----- itest/e2e_test.go | 5 ++- itest/manager.go | 44 ++++++++++++++----- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/cmd/stakercli/transaction/transactions_test.go b/cmd/stakercli/transaction/transactions_test.go index f0da5d4..c2a403e 100644 --- a/cmd/stakercli/transaction/transactions_test.go +++ b/cmd/stakercli/transaction/transactions_test.go @@ -70,9 +70,6 @@ var ( Versions: []*parser.VersionedGlobalParams{&defaultParam}, } - //nolint:errchkjson - paramsMarshalled, _ = json.Marshal(globalParams) - parsedGlobalParams, _ = parser.ParseGlobalParams(&globalParams) lastParams = parsedGlobalParams.Versions[len(parsedGlobalParams.Versions)-1] ) @@ -189,8 +186,8 @@ func appRunCreatePhase1WithdrawalTx(r *rand.Rand, t *testing.T, app *cli.App, ar return data } -func randRange(_ *rand.Rand, min, max int) int { - return rand.Intn(max+1-min) + min +func randRange(_ *rand.Rand, minV, maxV int) int { + return rand.Intn(maxV+1-minV) + minV } type StakeParameters struct { diff --git a/itest/containers/containers.go b/itest/containers/containers.go index 841a84c..41d538b 100644 --- a/itest/containers/containers.go +++ b/itest/containers/containers.go @@ -179,19 +179,11 @@ func (m *Manager) RunBabylondResource( coventantQuorum int, baseHeaderHex string, slashingPkScript string, - covenantPk1 *btcec.PublicKey, - covenantPk2 *btcec.PublicKey, - covenantPk3 *btcec.PublicKey, + covenantPks ...*btcec.PublicKey, ) (*dockertest.Resource, error) { - covenantPks := []*bbn.BIP340PubKey{ - bbn.NewBIP340PubKeyFromBTCPK(covenantPk1), - bbn.NewBIP340PubKeyFromBTCPK(covenantPk2), - bbn.NewBIP340PubKeyFromBTCPK(covenantPk3), - } - - var covenantPksStr []string - for _, pk := range covenantPks { - covenantPksStr = append(covenantPksStr, pk.MarshalHex()) + covenantPksStr := make([]string, len(covenantPks)) + for i, cvPk := range covenantPks { + covenantPksStr[i] = bbn.NewBIP340PubKeyFromBTCPK(cvPk).MarshalHex() } cmd := []string{ diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 1e2b80d..180b716 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -931,5 +931,8 @@ func TestStakeFromPhase1(t *testing.T) { stkTxResult, err := rpcBtc.GetTransaction(txHash) require.NoError(t, err) require.NotNil(t, stkTxResult) - // tm.createAndRegisterFinalityProviders(t, testStakingData) + + // at this point the BTC staking transaction is confirmed and was mined in BTC + // so the babylon chain can start and try to transition this staking BTC tx + // into a babylon BTC delegation in the cosmos side. } diff --git a/itest/manager.go b/itest/manager.go index b12eb24..0f3bc9e 100644 --- a/itest/manager.go +++ b/itest/manager.go @@ -146,6 +146,12 @@ func btcRpcTestClient(t *testing.T, bitcoindHost string) *rpcclient.Client { } type TestManager struct { + manager *containers.Manager + TestManagerStakerApp + TestManagerBTC +} + +type TestManagerStakerApp struct { Config *stakercfg.Config Db kvdb.Backend Sa *staker.App @@ -154,8 +160,6 @@ type TestManager struct { serviceAddress string StakerClient *dc.StakerServiceJSONRPCClient CovenantPrivKeys []*btcec.PrivateKey - manager *containers.Manager - TestManagerBTC } type TestManagerBTC struct { @@ -305,10 +309,32 @@ func StartManager( tmBTC := StartManagerBtc(t, ctx, numMatureOutputsInWallet, manager) quorum := 2 - coventantPrivKeys := genCovenants(t, 3) + covenantsNum := 3 + tmStakerApp := StartManagerStakerApp(t, ctx, tmBTC, manager, quorum, covenantsNum) + + return &TestManager{ + manager: manager, + TestManagerStakerApp: *tmStakerApp, + TestManagerBTC: *tmBTC, + } +} + +func StartManagerStakerApp( + t *testing.T, + ctx context.Context, + tmBTC *TestManagerBTC, + manager *containers.Manager, + covenantQuorum int, + covenantNum int, +) *TestManagerStakerApp { + coventantPrivKeys := genCovenants(t, covenantNum) + coventantPubKeys := make([]*btcec.PublicKey, covenantNum) + for i, cvPrivKey := range coventantPrivKeys { + coventantPubKeys[i] = cvPrivKey.PubKey() + } var buff bytes.Buffer - err = regtestParams.GenesisBlock.Header.Serialize(&buff) + err := regtestParams.GenesisBlock.Header.Serialize(&buff) require.NoError(t, err) baseHeaderHex := hex.EncodeToString(buff.Bytes()) @@ -320,12 +346,10 @@ func StartManager( babylond, err := manager.RunBabylondResource( t, tmpDir, - quorum, + covenantQuorum, baseHeaderHex, hex.EncodeToString(pkScript), // all slashing will be sent back to wallet - coventantPrivKeys[0].PubKey(), - coventantPrivKeys[1].PubKey(), - coventantPrivKeys[2].PubKey(), + coventantPubKeys..., ) require.NoError(t, err) @@ -391,7 +415,7 @@ func StartManager( stakerClient, err := dc.NewStakerServiceJSONRPCClient("tcp://" + addressString) require.NoError(t, err) - return &TestManager{ + return &TestManagerStakerApp{ Config: cfg, Db: dbbackend, Sa: stakerApp, @@ -400,8 +424,6 @@ func StartManager( serviceAddress: addressString, StakerClient: stakerClient, CovenantPrivKeys: coventantPrivKeys, - manager: manager, - TestManagerBTC: *tmBTC, } } From 7c745ddc6f55f5ba8a303807e80765adaa23ff26 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 21 Nov 2024 14:48:29 -0300 Subject: [PATCH 13/38] trytest: stuck at including finality provider to running chain --- babylonclient/babyloncontroller.go | 1 - itest/e2e_test.go | 74 +++++++++++++++++++++++++++--- itest/manager.go | 11 +++-- itest/testutil/app_cli.go | 16 ++++--- stakerservice/service.go | 22 ++++++--- 5 files changed, 99 insertions(+), 25 deletions(-) diff --git a/babylonclient/babyloncontroller.go b/babylonclient/babyloncontroller.go index 04a1a54..3c9435b 100644 --- a/babylonclient/babyloncontroller.go +++ b/babylonclient/babyloncontroller.go @@ -732,7 +732,6 @@ func (bc *BabylonController) RegisterFinalityProvider( Pop: pop, } - fpPrivKeyBBN.PubKey() relayerMsgs := bbnclient.ToProviderMsgs([]sdk.Msg{registerMsg}) _, err := bc.bbnClient.SendMessageWithSigner(context.Background(), fpAddr, fpPrivKeyBBN, relayerMsgs) diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 180b716..d961126 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -7,12 +7,15 @@ import ( "bytes" "context" "encoding/hex" + "encoding/json" "fmt" "testing" "time" "github.com/babylonlabs-io/btc-staker/itest/containers" "github.com/babylonlabs-io/btc-staker/itest/testutil" + "github.com/babylonlabs-io/btc-staker/stakerservice" + "github.com/babylonlabs-io/networks/parameters/parser" "github.com/babylonlabs-io/babylon/btcstaking" "github.com/babylonlabs-io/babylon/crypto/bip322" @@ -872,18 +875,45 @@ func TestStakeFromPhase1(t *testing.T) { appCli := testutil.TestApp() tagHex := datagen.GenRandomHexStr(r, btcstaking.TagLen) - covenants := genCovenants(t, 1) - - covenantPkHex := hex.EncodeToString(schnorr.SerializePubKey(covenants[0].PubKey())) + coventantPrivKeys := genCovenants(t, 1) + covenantPkSerializedHex := hex.EncodeToString(schnorr.SerializePubKey(coventantPrivKeys[0].PubKey())) + covenantPkHex := hex.EncodeToString(coventantPrivKeys[0].PubKey().SerializeCompressed()) commonFlags := []string{ - fmt.Sprintf("--covenant-committee-pks=%s", covenantPkHex), + fmt.Sprintf("--covenant-committee-pks=%s", covenantPkSerializedHex), fmt.Sprintf("--tag=%s", tagHex), "--covenant-quorum=1", "--network=regtest", } - paramsFilePath := testutil.CreateTempFileWithParams(t) - lastParams := testutil.GlobalParams.Versions[len(testutil.GlobalParams.Versions)-1] + lastParams := &parser.VersionedGlobalParams{ + Version: 0, + ActivationHeight: 100, + StakingCap: 3000000, + CapHeight: 0, + Tag: "01020304", + CovenantPks: []string{ + covenantPkHex, + }, + CovenantQuorum: 1, + UnbondingTime: 1000, + UnbondingFee: 1000, + MaxStakingAmount: 300000, + MinStakingAmount: 3000, + MaxStakingTime: 10000, + MinStakingTime: 100, + ConfirmationDepth: 10, + } + + globalParams := parser.GlobalParams{ + Versions: []*parser.VersionedGlobalParams{ + lastParams, + }, + } + + globalParamsMarshalled, err := json.Marshal(globalParams) + require.NoError(t, err) + + paramsFilePath := testutil.CreateTempFileWithData(t, "tmpParams-*.json", globalParamsMarshalled) fpDepositStakingAmount := lastParams.MinStakingAmount inclusionHeight := lastParams.ActivationHeight + 1 stakingTime := lastParams.MaxStakingTime @@ -926,13 +956,43 @@ func TestStakeFromPhase1(t *testing.T) { require.NoError(t, err) require.NotNil(t, txHash) - tmBTC.BitcoindHandler.GenerateBlocks(5) + tmBTC.BitcoindHandler.GenerateBlocks(15) stkTxResult, err := rpcBtc.GetTransaction(txHash) require.NoError(t, err) require.NotNil(t, stkTxResult) + parsedGlobalParams, err := parser.ParseGlobalParams(&globalParams) + require.NoError(t, err) + + // just to make sure it is able to parse the staking tx + paserdStkTx, err := stakerservice.ParseV0StakingTx(parsedGlobalParams, regtestParams, signedStkTx) + require.NoError(t, err) + require.NotNil(t, paserdStkTx) + // at this point the BTC staking transaction is confirmed and was mined in BTC // so the babylon chain can start and try to transition this staking BTC tx // into a babylon BTC delegation in the cosmos side. + tmStakerApp := StartManagerStakerApp(t, ctx, tmBTC, manager, 1, coventantPrivKeys) + + tm := &TestManager{ + manager: manager, + TestManagerStakerApp: *tmStakerApp, + TestManagerBTC: *tmBTC, + } + defer tm.Stop(t, cancel) + + _, _, err = tm.manager.BabylondTxBankMultiSend(t, "node0", "1000000ubbn", testStakingData.FinalityProviderBabylonAddrs[0].String()) + require.NoError(t, err) + + tm.insertAllMinedBlocksToBabylon(t) + tm.createAndRegisterFinalityProviders(t, testStakingData) + + stakerAddrStr := tmBTC.MinerAddr.String() + stkTxHash := txHash.String() + // miner address and the staker addr are the same guy + res, err := tmStakerApp.StakerClient.BtcDelegationFromBtcStakingTx(ctx, stakerAddrStr, stkTxHash, parsedGlobalParams) + require.NoError(t, err) + require.NotNil(t, res) + } diff --git a/itest/manager.go b/itest/manager.go index 0f3bc9e..79e5810 100644 --- a/itest/manager.go +++ b/itest/manager.go @@ -309,8 +309,8 @@ func StartManager( tmBTC := StartManagerBtc(t, ctx, numMatureOutputsInWallet, manager) quorum := 2 - covenantsNum := 3 - tmStakerApp := StartManagerStakerApp(t, ctx, tmBTC, manager, quorum, covenantsNum) + coventantPrivKeys := genCovenants(t, 3) + tmStakerApp := StartManagerStakerApp(t, ctx, tmBTC, manager, quorum, coventantPrivKeys) return &TestManager{ manager: manager, @@ -325,10 +325,9 @@ func StartManagerStakerApp( tmBTC *TestManagerBTC, manager *containers.Manager, covenantQuorum int, - covenantNum int, + coventantPrivKeys []*btcec.PrivateKey, ) *TestManagerStakerApp { - coventantPrivKeys := genCovenants(t, covenantNum) - coventantPubKeys := make([]*btcec.PublicKey, covenantNum) + coventantPubKeys := make([]*btcec.PublicKey, len(coventantPrivKeys)) for i, cvPrivKey := range coventantPrivKeys { coventantPubKeys[i] = cvPrivKey.PubKey() } @@ -415,6 +414,8 @@ func StartManagerStakerApp( stakerClient, err := dc.NewStakerServiceJSONRPCClient("tcp://" + addressString) require.NoError(t, err) + fmt.Printf("\n log config %+v", cfg) + return &TestManagerStakerApp{ Config: cfg, Db: dbbackend, diff --git a/itest/testutil/app_cli.go b/itest/testutil/app_cli.go index b88b537..4934d7a 100644 --- a/itest/testutil/app_cli.go +++ b/itest/testutil/app_cli.go @@ -111,13 +111,17 @@ func readFromFile(t *testing.T, f *os.File) string { return buf.String() } -func CreateTempFileWithParams(f require.TestingT) string { - file, err := os.CreateTemp("", "tmpParams-*.json") - require.NoError(f, err) +func CreateTempFileWithParams(t require.TestingT) string { + return CreateTempFileWithData(t, "tmpParams-*.json", paramsMarshalled) +} + +func CreateTempFileWithData(t require.TestingT, pattern string, data []byte) string { + file, err := os.CreateTemp("", pattern) + require.NoError(t, err) defer file.Close() - _, err = file.Write(paramsMarshalled) - require.NoError(f, err) + _, err = file.Write(data) + require.NoError(t, err) info, err := file.Stat() - require.NoError(f, err) + require.NoError(t, err) return filepath.Join(os.TempDir(), info.Name()) } diff --git a/stakerservice/service.go b/stakerservice/service.go index dc1b2d3..f72cb65 100644 --- a/stakerservice/service.go +++ b/stakerservice/service.go @@ -136,33 +136,38 @@ func (s *StakerService) btcDelegationFromBtcStakingTx( ) (*ResultBtcDelegationFromBtcStakingTx, error) { stkTxHash, err := chainhash.NewHashFromStr(btcStkTxHash) if err != nil { + s.logger.WithError(err).Info("err parse tx hash") return nil, err } stkTx, err := s.staker.TxDetailsBTC(stkTxHash) if err != nil { + s.logger.WithError(err).Info("err get tx details") return nil, err } stakerAddr, err := btcutil.DecodeAddress(stakerAddress, &s.config.ActiveNetParams) if err != nil { + s.logger.WithError(err).Info("err decode staker addr") return nil, err } wireStkTx := stkTx.MsgTx() - parsedStakingTx, err := parseV0StakingTx(globalParams, s.staker.BtcParams(), wireStkTx) + parsedStakingTx, err := ParseV0StakingTx(globalParams, s.staker.BtcParams(), wireStkTx) if err != nil { + s.logger.WithError(err).Info("err parse staking Tx with global params") return nil, err } if err := s.staker.AddBTCTransactionToDBAndCheckStatus(stakerAddr, wireStkTx, parsedStakingTx); err != nil { + s.logger.WithError(err).Info("err failing to add tx to DB and check status in app") return nil, err } return &ResultBtcDelegationFromBtcStakingTx{}, nil } -func parseV0StakingTx(globalParams *parser.ParsedGlobalParams, btcParams *chaincfg.Params, wireStkTx *wire.MsgTx) (*btcstaking.ParsedV0StakingTx, error) { +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( @@ -650,7 +655,10 @@ func (s *StakerService) RunUntilShutdown(ctx context.Context) error { } defer func() { - _ = s.staker.Stop() + err := s.staker.Stop() + if err != nil { + s.logger.WithError(err).Info("staker stop with error") + } s.logger.Info("staker stop complete") }() @@ -688,7 +696,7 @@ func (s *StakerService) RunUntilShutdown(ctx context.Context) error { // TODO: Add additional middleware, like CORS, TLS, etc. // TODO: Consider we need some websockets for some notications go func() { - s.logger.Debug("Starting Json RPC HTTP server ", "address", listenAddressStr) + s.logger.Debug("Starting Json RPC HTTP server ", "address: ", listenAddressStr) err := rpc.Serve( listener, @@ -696,8 +704,10 @@ func (s *StakerService) RunUntilShutdown(ctx context.Context) error { rpcLogger, config, ) - - s.logger.Error("Json RPC HTTP server stopped ", "err", err) + if err != nil { + s.logger.WithError(err).Error("problem at JSON RPC HTTP server") + } + s.logger.Info("Json RPC HTTP server stopped ") }() listeners[i] = listener From 323cdcf60d2b696726263f74dfadb79fd917c0b6 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 21 Nov 2024 17:28:22 -0300 Subject: [PATCH 14/38] chore: add last err to debug --- itest/e2e_test.go | 35 ++++++++++++++++++++--------------- itest/manager.go | 3 +++ stakerservice/service.go | 4 +++- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/itest/e2e_test.go b/itest/e2e_test.go index d961126..791027b 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -17,7 +17,6 @@ import ( "github.com/babylonlabs-io/btc-staker/stakerservice" "github.com/babylonlabs-io/networks/parameters/parser" - "github.com/babylonlabs-io/babylon/btcstaking" "github.com/babylonlabs-io/babylon/crypto/bip322" btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" @@ -860,10 +859,6 @@ func TestStakeFromPhase1(t *testing.T) { require.NoError(t, err) tmBTC := StartManagerBtc(t, ctx, numMatureOutputsInWallet, manager) - defer func() { - cancel() - manager.ClearResources() - }() minStakingTime := uint16(100) stakerAddr := datagen.GenRandomAccount().GetAddress() @@ -873,18 +868,11 @@ func TestStakeFromPhase1(t *testing.T) { btcStakerPkHex := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.StakerKey)) appCli := testutil.TestApp() - tagHex := datagen.GenRandomHexStr(r, btcstaking.TagLen) coventantPrivKeys := genCovenants(t, 1) covenantPkSerializedHex := hex.EncodeToString(schnorr.SerializePubKey(coventantPrivKeys[0].PubKey())) covenantPkHex := hex.EncodeToString(coventantPrivKeys[0].PubKey().SerializeCompressed()) - commonFlags := []string{ - fmt.Sprintf("--covenant-committee-pks=%s", covenantPkSerializedHex), - fmt.Sprintf("--tag=%s", tagHex), - "--covenant-quorum=1", "--network=regtest", - } - lastParams := &parser.VersionedGlobalParams{ Version: 0, ActivationHeight: 100, @@ -918,6 +906,12 @@ func TestStakeFromPhase1(t *testing.T) { inclusionHeight := lastParams.ActivationHeight + 1 stakingTime := lastParams.MaxStakingTime + commonFlags := []string{ + fmt.Sprintf("--covenant-committee-pks=%s", covenantPkSerializedHex), + fmt.Sprintf("--tag=%s", lastParams.Tag), + "--covenant-quorum=1", "--network=regtest", + } + createTxCmdArgs := []string{ paramsFilePath, fmt.Sprintf("--staker-pk=%s", btcStakerPkHex), @@ -965,7 +959,7 @@ func TestStakeFromPhase1(t *testing.T) { parsedGlobalParams, err := parser.ParseGlobalParams(&globalParams) require.NoError(t, err) - // just to make sure it is able to parse the staking tx + // Makes sure it is able to parse the staking tx paserdStkTx, err := stakerservice.ParseV0StakingTx(parsedGlobalParams, regtestParams, signedStkTx) require.NoError(t, err) require.NotNil(t, paserdStkTx) @@ -982,15 +976,26 @@ func TestStakeFromPhase1(t *testing.T) { } defer tm.Stop(t, cancel) + // verify that the chain is healthy + require.Eventually(t, func() bool { + _, err := tm.BabylonClient.Params() + return err == nil + }, time.Minute, 200*time.Millisecond) + + // funds the fpd _, _, err = tm.manager.BabylondTxBankMultiSend(t, "node0", "1000000ubbn", testStakingData.FinalityProviderBabylonAddrs[0].String()) require.NoError(t, err) tm.insertAllMinedBlocksToBabylon(t) tm.createAndRegisterFinalityProviders(t, testStakingData) + // tmBTC.WalletAddrInfo. stakerAddrStr := tmBTC.MinerAddr.String() - stkTxHash := txHash.String() - // miner address and the staker addr are the same guy + // stakerAddrStr := tmBTC.WalletPubKey + // stakerAddrStr := btcStakerPkHex + stkTxHash := signedStkTx.TxHash().String() + + // miner address and the staker addr are the same guy, maybe not res, err := tmStakerApp.StakerClient.BtcDelegationFromBtcStakingTx(ctx, stakerAddrStr, stkTxHash, parsedGlobalParams) require.NoError(t, err) require.NotNil(t, res) diff --git a/itest/manager.go b/itest/manager.go index 79e5810..2620676 100644 --- a/itest/manager.go +++ b/itest/manager.go @@ -44,6 +44,7 @@ import ( "github.com/babylonlabs-io/btc-staker/walletcontroller" "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" @@ -170,6 +171,7 @@ type TestManagerBTC struct { WalletPassphrase string BitcoindHost string WalletPubKey *btcec.PublicKey + WalletAddrInfo *btcjson.GetAddressInfoResult TestRpcBtcClient *rpcclient.Client } @@ -294,6 +296,7 @@ func StartManagerBtc( WalletPassphrase: passphrase, BitcoindHost: bitcoindHost, WalletPubKey: walletPubKey, + WalletAddrInfo: info, TestRpcBtcClient: rpcBtc, } } diff --git a/stakerservice/service.go b/stakerservice/service.go index f72cb65..b5979a0 100644 --- a/stakerservice/service.go +++ b/stakerservice/service.go @@ -168,6 +168,7 @@ func (s *StakerService) btcDelegationFromBtcStakingTx( } func ParseV0StakingTx(globalParams *parser.ParsedGlobalParams, btcParams *chaincfg.Params, wireStkTx *wire.MsgTx) (*btcstaking.ParsedV0StakingTx, error) { + var lastErr error for i := len(globalParams.Versions) - 1; i >= 0; i-- { params := globalParams.Versions[i] parsedStakingTx, err := btcstaking.ParseV0StakingTx( @@ -178,11 +179,12 @@ func ParseV0StakingTx(globalParams *parser.ParsedGlobalParams, btcParams *chainc btcParams, ) if err != nil { + lastErr = err continue } return parsedStakingTx, nil } - return nil, fmt.Errorf("failed to parse BTC staking tx %s using the global params %+v", wireStkTx.TxHash().String(), globalParams) + return nil, fmt.Errorf("err: %s failed to parse BTC staking tx %s using the global params %+v", lastErr.Error(), wireStkTx.TxHash().String(), globalParams) } func (s *StakerService) stakingDetails(_ *rpctypes.Context, From f57a4464563e471b971dd8d65b4352fc604a96f5 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Fri, 22 Nov 2024 05:43:20 +0100 Subject: [PATCH 15/38] pass json serializable params --- cmd/stakercli/daemon/daemoncommands.go | 4 +++- itest/e2e_test.go | 4 +++- stakerservice/client/rpcclient.go | 2 +- stakerservice/service.go | 16 ++++++++++++++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/cmd/stakercli/daemon/daemoncommands.go b/cmd/stakercli/daemon/daemoncommands.go index f5a821c..792f7dc 100644 --- a/cmd/stakercli/daemon/daemoncommands.go +++ b/cmd/stakercli/daemon/daemoncommands.go @@ -391,9 +391,11 @@ func stakeFromPhase1TxBTC(ctx *cli.Context) error { if err != nil { return fmt.Errorf("error parsing file %s: %w", inputGlobalParamsFilePath, err) } + fmt.Printf("globalParams: %v\n", globalParams) stakerAddress := ctx.String(stakerAddressFlag) - _, err = client.BtcDelegationFromBtcStakingTx(sctx, stakerAddress, stakingTransactionHash, globalParams) + // TODO: pass global params and not nill + _, err = client.BtcDelegationFromBtcStakingTx(sctx, stakerAddress, stakingTransactionHash, nil) return err } diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 791027b..7d307fb 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -873,6 +873,8 @@ func TestStakeFromPhase1(t *testing.T) { covenantPkSerializedHex := hex.EncodeToString(schnorr.SerializePubKey(coventantPrivKeys[0].PubKey())) covenantPkHex := hex.EncodeToString(coventantPrivKeys[0].PubKey().SerializeCompressed()) + fmt.Printf("covenantPkSerializedHex: %s\n", covenantPkSerializedHex) + lastParams := &parser.VersionedGlobalParams{ Version: 0, ActivationHeight: 100, @@ -996,7 +998,7 @@ func TestStakeFromPhase1(t *testing.T) { stkTxHash := signedStkTx.TxHash().String() // miner address and the staker addr are the same guy, maybe not - res, err := tmStakerApp.StakerClient.BtcDelegationFromBtcStakingTx(ctx, stakerAddrStr, stkTxHash, parsedGlobalParams) + res, err := tmStakerApp.StakerClient.BtcDelegationFromBtcStakingTx(ctx, stakerAddrStr, stkTxHash, &globalParams) require.NoError(t, err) require.NotNil(t, res) diff --git a/stakerservice/client/rpcclient.go b/stakerservice/client/rpcclient.go index a23db07..6021dab 100644 --- a/stakerservice/client/rpcclient.go +++ b/stakerservice/client/rpcclient.go @@ -90,7 +90,7 @@ func (c *StakerServiceJSONRPCClient) BtcDelegationFromBtcStakingTx( ctx context.Context, stakerAddress string, btcStkTxHash string, - globalParams *parser.ParsedGlobalParams, + globalParams *parser.GlobalParams, ) (*service.ResultBtcDelegationFromBtcStakingTx, error) { result := new(service.ResultBtcDelegationFromBtcStakingTx) diff --git a/stakerservice/service.go b/stakerservice/service.go index b5979a0..cd160c0 100644 --- a/stakerservice/service.go +++ b/stakerservice/service.go @@ -132,7 +132,7 @@ func (s *StakerService) btcDelegationFromBtcStakingTx( _ *rpctypes.Context, stakerAddress string, btcStkTxHash string, - globalParams *parser.ParsedGlobalParams, + globalParams *parser.GlobalParams, ) (*ResultBtcDelegationFromBtcStakingTx, error) { stkTxHash, err := chainhash.NewHashFromStr(btcStkTxHash) if err != nil { @@ -153,7 +153,14 @@ func (s *StakerService) btcDelegationFromBtcStakingTx( } wireStkTx := stkTx.MsgTx() - parsedStakingTx, err := ParseV0StakingTx(globalParams, s.staker.BtcParams(), wireStkTx) + + parsedGlobalParams, err := parser.ParseGlobalParams(globalParams) + if err != nil { + s.logger.WithError(err).Info("err parse global params") + return nil, err + } + + parsedStakingTx, err := ParseV0StakingTx(parsedGlobalParams, s.staker.BtcParams(), wireStkTx) if err != nil { s.logger.WithError(err).Info("err parse staking Tx with global params") return nil, err @@ -171,6 +178,11 @@ func ParseV0StakingTx(globalParams *parser.ParsedGlobalParams, btcParams *chainc var lastErr error for i := len(globalParams.Versions) - 1; i >= 0; i-- { params := globalParams.Versions[i] + fmt.Printf("Parsing the v0 staking tx with the following covenant pks: \n") + for _, pk := range params.CovenantPks { + fmt.Printf("pk: %s\n", hex.EncodeToString(schnorr.SerializePubKey(pk))) + } + parsedStakingTx, err := btcstaking.ParseV0StakingTx( wireStkTx, params.Tag, From 916d27d3df655b59a3c7f4ae005c77e0fabab2fe Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Fri, 22 Nov 2024 17:20:12 -0300 Subject: [PATCH 16/38] chore: refactory send transaction to phase 1 --- cmd/stakercli/daemon/daemoncommands.go | 19 ++++++- itest/e2e_test.go | 37 +++++++++++-- staker/stakerapp.go | 75 +++++++++++++------------- stakerservice/client/rpcclient.go | 22 +++++++- stakerservice/service.go | 64 +++++++++++----------- 5 files changed, 142 insertions(+), 75 deletions(-) diff --git a/cmd/stakercli/daemon/daemoncommands.go b/cmd/stakercli/daemon/daemoncommands.go index f5a821c..c3b26d6 100644 --- a/cmd/stakercli/daemon/daemoncommands.go +++ b/cmd/stakercli/daemon/daemoncommands.go @@ -42,6 +42,7 @@ const ( fpPksFlag = "finality-providers-pks" stakingTransactionHashFlag = "staking-transaction-hash" stakerAddressFlag = "staker-address" + txInclusionHeightFlag = "tx-inclusion-height" ) var ( @@ -154,6 +155,16 @@ var stakeFromPhase1Cmd = cli.Command{ Usage: "Hash of original staking transaction in bitcoin hex format", Required: true, }, + cli.StringFlag{ + Name: stakerAddressFlag, + Usage: "BTC address of the staker in hex", + Required: true, + }, + cli.Uint64Flag{ + Name: txInclusionHeightFlag, + Usage: "Expected BTC height at which transaction was included. This value is important to choose correct global parameters for transaction", + Required: true, + }, }, Action: stakeFromPhase1TxBTC, } @@ -392,8 +403,14 @@ func stakeFromPhase1TxBTC(ctx *cli.Context) error { return fmt.Errorf("error parsing file %s: %w", inputGlobalParamsFilePath, err) } + stakingTxInclusionHeight := ctx.Uint64(txInclusionHeightFlag) + paramsForHeight := globalParams.GetVersionedGlobalParamsByHeight(stakingTxInclusionHeight) + if paramsForHeight == nil { + return fmt.Errorf("error getting param version from global params %s with height %d", inputGlobalParamsFilePath, stakingTxInclusionHeight) + } + stakerAddress := ctx.String(stakerAddressFlag) - _, err = client.BtcDelegationFromBtcStakingTx(sctx, stakerAddress, stakingTransactionHash, globalParams) + _, err = client.BtcDelegationFromBtcStakingTx(sctx, stakerAddress, stakingTransactionHash, paramsForHeight) return err } diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 791027b..1be5498 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -14,9 +14,9 @@ import ( "github.com/babylonlabs-io/btc-staker/itest/containers" "github.com/babylonlabs-io/btc-staker/itest/testutil" - "github.com/babylonlabs-io/btc-staker/stakerservice" "github.com/babylonlabs-io/networks/parameters/parser" + "github.com/babylonlabs-io/babylon/btcstaking" "github.com/babylonlabs-io/babylon/crypto/bip322" btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" @@ -902,7 +902,7 @@ func TestStakeFromPhase1(t *testing.T) { require.NoError(t, err) paramsFilePath := testutil.CreateTempFileWithData(t, "tmpParams-*.json", globalParamsMarshalled) - fpDepositStakingAmount := lastParams.MinStakingAmount + stakingAmount := lastParams.MaxStakingAmount - 1 inclusionHeight := lastParams.ActivationHeight + 1 stakingTime := lastParams.MaxStakingTime @@ -916,7 +916,7 @@ func TestStakeFromPhase1(t *testing.T) { paramsFilePath, fmt.Sprintf("--staker-pk=%s", btcStakerPkHex), fmt.Sprintf("--finality-provider-pk=%s", fpPkHex), - fmt.Sprintf("--staking-amount=%d", fpDepositStakingAmount), + fmt.Sprintf("--staking-amount=%d", stakingAmount), fmt.Sprintf("--tx-inclusion-height=%d", inclusionHeight), fmt.Sprintf("--staking-time=%d", stakingTime), } @@ -949,6 +949,7 @@ func TestStakeFromPhase1(t *testing.T) { txHash, err := rpcBtc.SendRawTransaction(signedStkTx, false) require.NoError(t, err) require.NotNil(t, txHash) + require.Equal(t, txHash.String(), signedStkTx.TxHash().String()) tmBTC.BitcoindHandler.GenerateBlocks(15) @@ -959,8 +960,16 @@ func TestStakeFromPhase1(t *testing.T) { parsedGlobalParams, err := parser.ParseGlobalParams(&globalParams) require.NoError(t, err) + lastParamsVersioned := parsedGlobalParams.Versions[len(parsedGlobalParams.Versions)-1] + // Makes sure it is able to parse the staking tx - paserdStkTx, err := stakerservice.ParseV0StakingTx(parsedGlobalParams, regtestParams, signedStkTx) + paserdStkTx, err := btcstaking.ParseV0StakingTx( + signedStkTx, + lastParamsVersioned.Tag, + lastParamsVersioned.CovenantPks, + lastParamsVersioned.CovenantQuorum, + regtestParams, + ) require.NoError(t, err) require.NotNil(t, paserdStkTx) @@ -996,8 +1005,26 @@ func TestStakeFromPhase1(t *testing.T) { stkTxHash := signedStkTx.TxHash().String() // miner address and the staker addr are the same guy, maybe not - res, err := tmStakerApp.StakerClient.BtcDelegationFromBtcStakingTx(ctx, stakerAddrStr, stkTxHash, parsedGlobalParams) + res, err := tmStakerApp.StakerClient.BtcDelegationFromBtcStakingTx(ctx, stakerAddrStr, stkTxHash, lastParamsVersioned) require.NoError(t, err) require.NotNil(t, res) + // wait for BTC delegation to become active + cl := tm.Sa.BabylonController() + params, err := cl.Params() + require.NoError(t, err) + + go tm.mineNEmptyBlocks(t, params.ConfirmationTimeBlocks, true) + tm.waitForStakingTxState(t, txHash, proto.TransactionState_SENT_TO_BABYLON) + + pend, err := tm.BabylonClient.QueryPendingBTCDelegations() + require.NoError(t, err) + require.Len(t, pend, 1) + // need to activate delegation to unbond + tm.insertCovenantSigForDelegation(t, pend[0]) + tm.waitForStakingTxState(t, txHash, proto.TransactionState_DELEGATION_ACTIVE) + + delInfo, err := tm.BabylonClient.QueryDelegationInfo(txHash) + require.NoError(t, err) + require.True(t, delInfo.Active) } diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 234c591..4862594 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -18,6 +18,7 @@ import ( "github.com/babylonlabs-io/btc-staker/types" "github.com/babylonlabs-io/btc-staker/utils" "github.com/babylonlabs-io/btc-staker/walletcontroller" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "go.uber.org/zap" "github.com/btcsuite/btcd/btcec/v2" @@ -506,31 +507,60 @@ 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( +// SendPhase1Transaction receives the BTC staking transaction hash that +// should be already in BTC and creates the necessary data to submit +// the BTC delegation into the consumer chain +func (app *App) SendPhase1Transaction( stakerAddr btcutil.Address, - stkTx *wire.MsgTx, - parsedStakingTx *staking.ParsedV0StakingTx, + stkTxHash *chainhash.Hash, + tag []byte, + covenantPks []*secp256k1.PublicKey, + covenantQuorum uint32, ) error { + stkTx, err := app.wc.Tx(stkTxHash) + if err != nil { + app.logger.WithError(err).Info("err get tx details") + return err + } + + wireStkTx := stkTx.MsgTx() + parsedStakingTx, err := staking.ParseV0StakingTx(wireStkTx, tag, covenantPks, covenantQuorum, app.network) + if err != nil { + app.logger.WithError(err).Info("err parse staking Tx with global params") + return err + } + + // unlock wallet to generate pop + // TODO: consider unlock/lock with defer + err = app.wc.UnlockWallet(defaultWalletUnlockTimeout) + if err != nil { + return err + } + pop, err := app.createPop(stakerAddr) if err != nil { return err } - if err := app.addTransactionSentToBTC( - stkTx, + if err := app.txTracker.AddTransactionSentToBTC( + wireStkTx, uint32(parsedStakingTx.StakingOutputIdx), parsedStakingTx.OpReturnData.StakingTime, []*btcec.PublicKey{parsedStakingTx.OpReturnData.FinalityProviderPublicKey.PubKey}, babylonPopToDBPop(pop), stakerAddr, ); err != nil { + app.logger.WithError(err).Info("err to set tx as sent on BTC") return err } - // not fun to call check transaction, because there is no request and way to get the tx result - return app.checkTransactionsStatus() + stakingParams, err := app.babylonClient.Params() + if err != nil { + return err + } + + // pushes the confirmation into the btc-staker mechanism and verifies if it is deep enough + return app.waitForStakingTransactionConfirmation(stkTxHash, parsedStakingTx.StakingOutput.PkScript, stakingParams.ConfirmationTimeBlocks, app.currentBestBlockHeight.Load()) } // TODO: We should also handle case when btc node or babylon node lost data and start from scratch @@ -1356,7 +1386,6 @@ func (app *App) handlePostApprovalCmd( } tx, fullySignd, err := app.wc.SignRawTransaction(stakingTx) - if err != nil { return nil, err } @@ -2220,32 +2249,6 @@ func (app *App) UnbondStaking( return &unbondingTxHash, nil } -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) diff --git a/stakerservice/client/rpcclient.go b/stakerservice/client/rpcclient.go index a23db07..a4f4581 100644 --- a/stakerservice/client/rpcclient.go +++ b/stakerservice/client/rpcclient.go @@ -2,9 +2,11 @@ package client import ( "context" + "encoding/hex" service "github.com/babylonlabs-io/btc-staker/stakerservice" "github.com/babylonlabs-io/networks/parameters/parser" + "github.com/btcsuite/btcd/btcec/v2" jsonrpcclient "github.com/cometbft/cometbft/rpc/jsonrpc/client" ) @@ -90,14 +92,16 @@ func (c *StakerServiceJSONRPCClient) BtcDelegationFromBtcStakingTx( ctx context.Context, stakerAddress string, btcStkTxHash string, - globalParams *parser.ParsedGlobalParams, + versionedParams *parser.ParsedVersionedGlobalParams, ) (*service.ResultBtcDelegationFromBtcStakingTx, error) { result := new(service.ResultBtcDelegationFromBtcStakingTx) params := make(map[string]interface{}) params["stakerAddress"] = stakerAddress params["btcStkTxHash"] = btcStkTxHash - params["globalParams"] = globalParams + params["tag"] = versionedParams.Tag + params["covenantPksHex"] = parseCovenantsPubKeyToHex(versionedParams.CovenantPks...) + params["covenantQuorum"] = versionedParams.CovenantQuorum _, err := c.client.Call(ctx, "btc_delegation_from_btc_staking_tx", params, result) if err != nil { @@ -106,6 +110,20 @@ func (c *StakerServiceJSONRPCClient) BtcDelegationFromBtcStakingTx( return result, nil } +func parseCovenantsPubKeyToHex(pks ...*btcec.PublicKey) []string { + pksHex := make([]string, len(pks)) + for i, pk := range pks { + pksHex[i] = parseCovenantPubKeyToHex(pk) + } + return pksHex +} + +// parseCovenantPubKeyFromHex parses public key into serialized compressed +// with 33 bytes and in hex string +func parseCovenantPubKeyToHex(pk *btcec.PublicKey) string { + return hex.EncodeToString(pk.SerializeCompressed()) +} + func (c *StakerServiceJSONRPCClient) ListStakingTransactions(ctx context.Context, offset *int, limit *int) (*service.ListStakingTransactionsResponse, error) { result := new(service.ListStakingTransactionsResponse) diff --git a/stakerservice/service.go b/stakerservice/service.go index b5979a0..7faee9a 100644 --- a/stakerservice/service.go +++ b/stakerservice/service.go @@ -12,16 +12,13 @@ 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" @@ -132,7 +129,9 @@ func (s *StakerService) btcDelegationFromBtcStakingTx( _ *rpctypes.Context, stakerAddress string, btcStkTxHash string, - globalParams *parser.ParsedGlobalParams, + tag []byte, + covenantPksHex []string, + covenantQuorum uint32, ) (*ResultBtcDelegationFromBtcStakingTx, error) { stkTxHash, err := chainhash.NewHashFromStr(btcStkTxHash) if err != nil { @@ -140,51 +139,54 @@ func (s *StakerService) btcDelegationFromBtcStakingTx( return nil, err } - stkTx, err := s.staker.TxDetailsBTC(stkTxHash) - if err != nil { - s.logger.WithError(err).Info("err get tx details") - return nil, err - } - stakerAddr, err := btcutil.DecodeAddress(stakerAddress, &s.config.ActiveNetParams) if err != nil { s.logger.WithError(err).Info("err decode staker addr") return nil, err } - wireStkTx := stkTx.MsgTx() - parsedStakingTx, err := ParseV0StakingTx(globalParams, s.staker.BtcParams(), wireStkTx) + covenantPks, err := parseCovenantsPubKeyFromHex(covenantPksHex...) if err != nil { - s.logger.WithError(err).Info("err parse staking Tx with global params") + s.logger.WithError(err).Infof("err decode covenant pks %s", covenantPksHex) return nil, err } - if err := s.staker.AddBTCTransactionToDBAndCheckStatus(stakerAddr, wireStkTx, parsedStakingTx); err != nil { - s.logger.WithError(err).Info("err failing to add tx to DB and check status in app") + err = s.staker.SendPhase1Transaction(stakerAddr, stkTxHash, tag, covenantPks, covenantQuorum) + if err != nil { + s.logger.WithError(err).Info("err to send phase 1 tx") return nil, err } return &ResultBtcDelegationFromBtcStakingTx{}, nil } -func ParseV0StakingTx(globalParams *parser.ParsedGlobalParams, btcParams *chaincfg.Params, wireStkTx *wire.MsgTx) (*btcstaking.ParsedV0StakingTx, error) { - var lastErr 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, - ) +func parseCovenantsPubKeyFromHex(covenantPksHex ...string) ([]*btcec.PublicKey, error) { + covenantPks := make([]*btcec.PublicKey, len(covenantPksHex)) + for i, covenantPkHex := range covenantPksHex { + covPk, err := parseCovenantPubKeyFromHex(covenantPkHex) if err != nil { - lastErr = err - continue + return nil, err } - return parsedStakingTx, nil + covenantPks[i] = covPk } - return nil, fmt.Errorf("err: %s failed to parse BTC staking tx %s using the global params %+v", lastErr.Error(), wireStkTx.TxHash().String(), globalParams) + + return covenantPks, nil +} + +// parseCovenantPubKeyFromHex parses public key string to btc public key +// the input should be 33 bytes +func parseCovenantPubKeyFromHex(pkStr string) (*btcec.PublicKey, error) { + pkBytes, err := hex.DecodeString(pkStr) + if err != nil { + return nil, err + } + + pk, err := btcec.ParsePubKey(pkBytes) + if err != nil { + return nil, err + } + + return pk, nil } func (s *StakerService) stakingDetails(_ *rpctypes.Context, @@ -609,7 +611,7 @@ func (s *StakerService) GetRoutes() RoutesMap { "health": rpc.NewRPCFunc(s.health, ""), // staking API "stake": rpc.NewRPCFunc(s.stake, "stakerAddress,stakingAmount,fpBtcPks,stakingTimeBlocks,sendToBabylonFirst"), - "btc_delegation_from_btc_staking_tx": rpc.NewRPCFunc(s.btcDelegationFromBtcStakingTx, "stakerAddress,btcStkTxHash,globalParams"), + "btc_delegation_from_btc_staking_tx": rpc.NewRPCFunc(s.btcDelegationFromBtcStakingTx, "stakerAddress,btcStkTxHash,tag,covenantPksHex,covenantQuorum"), "staking_details": rpc.NewRPCFunc(s.stakingDetails, "stakingTxHash"), "spend_stake": rpc.NewRPCFunc(s.spendStake, "stakingTxHash"), "list_staking_transactions": rpc.NewRPCFunc(s.listStakingTransactions, "offset,limit"), From fdc155ef2bad18200d34cb697d8a337f1db13a5b Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Fri, 22 Nov 2024 17:40:24 -0300 Subject: [PATCH 17/38] chore: fix comment --- cmd/stakercli/daemon/daemoncommands.go | 14 +++++--------- itest/e2e_test.go | 7 +------ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/cmd/stakercli/daemon/daemoncommands.go b/cmd/stakercli/daemon/daemoncommands.go index c3b26d6..f09bb54 100644 --- a/cmd/stakercli/daemon/daemoncommands.go +++ b/cmd/stakercli/daemon/daemoncommands.go @@ -140,10 +140,11 @@ var stakeCmd = cli.Command{ } var stakeFromPhase1Cmd = cli.Command{ - Name: "stake-from-phase1", - ShortName: "stfp1", - Usage: "stakercli stake-from-phase1 [fullpath/to/global_parameters.json]", - Description: "Creates a Babylon BTC delegation transaction from the Phase1 BTC staking tx", + Name: "stake-from-phase1", + ShortName: "stfp1", + Usage: "\nstakercli daemon stake-from-phase1 [fullpath/to/global_parameters.json]" + + " --staking-transaction-hash [txHashHex] --staker-address [btcStakerAddrHex] --tx-inclusion-height [blockHeightTxInclusion]", + Description: "Creates a Babylon BTC delegation transaction from the Phase1 BTC staking transaction", Flags: []cli.Flag{ cli.StringFlag{ Name: stakingDaemonAddressFlag, @@ -379,7 +380,6 @@ func stakeFromPhase1TxBTC(ctx *cli.Context) error { } sctx := context.Background() - stakingTransactionHash := ctx.String(stakingTransactionHashFlag) if len(stakingTransactionHash) == 0 { return errors.New("staking tx hash hex is empty") @@ -394,10 +394,6 @@ func stakeFromPhase1TxBTC(ctx *cli.Context) error { return fmt.Errorf("json file input %s does not exist", inputGlobalParamsFilePath) } - // 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) diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 7298f0b..c995f68 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -956,13 +956,8 @@ func TestStakeFromPhase1(t *testing.T) { tm.insertAllMinedBlocksToBabylon(t) tm.createAndRegisterFinalityProviders(t, testStakingData) - // tmBTC.WalletAddrInfo. stakerAddrStr := tmBTC.MinerAddr.String() - // stakerAddrStr := tmBTC.WalletPubKey - // stakerAddrStr := btcStakerPkHex stkTxHash := signedStkTx.TxHash().String() - - // miner address and the staker addr are the same guy, maybe not res, err := tmStakerApp.StakerClient.BtcDelegationFromBtcStakingTx(ctx, stakerAddrStr, stkTxHash, lastParamsVersioned) require.NoError(t, err) require.NotNil(t, res) @@ -978,7 +973,7 @@ func TestStakeFromPhase1(t *testing.T) { pend, err := tm.BabylonClient.QueryPendingBTCDelegations() require.NoError(t, err) require.Len(t, pend, 1) - // need to activate delegation to unbond + tm.insertCovenantSigForDelegation(t, pend[0]) tm.waitForStakingTxState(t, txHash, proto.TransactionState_DELEGATION_ACTIVE) From 15cb74d032a5d123e38ac930cc20db98e2528272 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 09:18:00 -0300 Subject: [PATCH 18/38] chore: address PR comments --- cmd/stakercli/transaction/transactions_test.go | 4 ++-- itest/testutil/{app_cli.go => appcli.go} | 0 staker/stakerapp.go | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename itest/testutil/{app_cli.go => appcli.go} (100%) diff --git a/cmd/stakercli/transaction/transactions_test.go b/cmd/stakercli/transaction/transactions_test.go index c2a403e..cd7c143 100644 --- a/cmd/stakercli/transaction/transactions_test.go +++ b/cmd/stakercli/transaction/transactions_test.go @@ -186,8 +186,8 @@ func appRunCreatePhase1WithdrawalTx(r *rand.Rand, t *testing.T, app *cli.App, ar return data } -func randRange(_ *rand.Rand, minV, maxV int) int { - return rand.Intn(maxV+1-minV) + minV +func randRange(r *rand.Rand, minV, maxV int) int { + return r.Intn(maxV+1-minV) + minV } type StakeParameters struct { diff --git a/itest/testutil/app_cli.go b/itest/testutil/appcli.go similarity index 100% rename from itest/testutil/app_cli.go rename to itest/testutil/appcli.go diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 4862594..2886b1a 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -537,6 +537,7 @@ func (app *App) SendPhase1Transaction( return err } + // pop only works for native segwit address pop, err := app.createPop(stakerAddr) if err != nil { return err From ffff690015e1ef59348ebe355cee9b2a0fdbdaf1 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 09:47:49 -0300 Subject: [PATCH 19/38] chore: rollback rand --- cmd/stakercli/transaction/transactions_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/stakercli/transaction/transactions_test.go b/cmd/stakercli/transaction/transactions_test.go index cd7c143..81c347f 100644 --- a/cmd/stakercli/transaction/transactions_test.go +++ b/cmd/stakercli/transaction/transactions_test.go @@ -186,8 +186,8 @@ func appRunCreatePhase1WithdrawalTx(r *rand.Rand, t *testing.T, app *cli.App, ar return data } -func randRange(r *rand.Rand, minV, maxV int) int { - return r.Intn(maxV+1-minV) + minV +func randRange(minV, maxV int) int { + return rand.Intn(maxV+1-minV) + minV } type StakeParameters struct { @@ -211,8 +211,8 @@ func createCustomValidStakeParams( require.NoError(t, err) fpKey, err := btcec.NewPrivateKey() require.NoError(t, err) - stakingTime := randRange(r, int(lastParams.MinStakingTime), int(lastParams.MaxStakingTime)) - stakingAmount := btcutil.Amount(randRange(r, int(lastParams.MinStakingAmount), int(lastParams.MaxStakingAmount))) + stakingTime := randRange(int(lastParams.MinStakingTime), int(lastParams.MaxStakingTime)) + stakingAmount := btcutil.Amount(randRange(int(lastParams.MinStakingAmount), int(lastParams.MaxStakingAmount))) var args []string args = append(args, fmt.Sprintf("--staker-pk=%s", hex.EncodeToString(schnorr.SerializePubKey(stakerKey.PubKey())))) From 503c8a50d0bc7832ee701cb058f00358bbbde520 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 09:59:36 -0300 Subject: [PATCH 20/38] chore: use rand previous created --- cmd/stakercli/transaction/transactions_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/stakercli/transaction/transactions_test.go b/cmd/stakercli/transaction/transactions_test.go index 81c347f..cd7c143 100644 --- a/cmd/stakercli/transaction/transactions_test.go +++ b/cmd/stakercli/transaction/transactions_test.go @@ -186,8 +186,8 @@ func appRunCreatePhase1WithdrawalTx(r *rand.Rand, t *testing.T, app *cli.App, ar return data } -func randRange(minV, maxV int) int { - return rand.Intn(maxV+1-minV) + minV +func randRange(r *rand.Rand, minV, maxV int) int { + return r.Intn(maxV+1-minV) + minV } type StakeParameters struct { @@ -211,8 +211,8 @@ func createCustomValidStakeParams( require.NoError(t, err) fpKey, err := btcec.NewPrivateKey() require.NoError(t, err) - stakingTime := randRange(int(lastParams.MinStakingTime), int(lastParams.MaxStakingTime)) - stakingAmount := btcutil.Amount(randRange(int(lastParams.MinStakingAmount), int(lastParams.MaxStakingAmount))) + stakingTime := randRange(r, int(lastParams.MinStakingTime), int(lastParams.MaxStakingTime)) + stakingAmount := btcutil.Amount(randRange(r, int(lastParams.MinStakingAmount), int(lastParams.MaxStakingAmount))) var args []string args = append(args, fmt.Sprintf("--staker-pk=%s", hex.EncodeToString(schnorr.SerializePubKey(stakerKey.PubKey())))) From 86b1da92675f0b05a55da04cabe996093e10ace1 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 14:14:27 -0300 Subject: [PATCH 21/38] chore: add possible responde of BTC delegation to consumer --- staker/babylontypes.go | 7 +++ staker/stakerapp.go | 88 ++++++++++++++++++------------ stakerdb/trackedtranactionstore.go | 2 - 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/staker/babylontypes.go b/staker/babylontypes.go index d13081f..3c146f5 100644 --- a/staker/babylontypes.go +++ b/staker/babylontypes.go @@ -32,6 +32,13 @@ type sendDelegationRequest struct { // the inclusion proof inclusionInfo *inclusionInfo requiredInclusionBlockDepth uint32 + // response of the BTC delegation request sent + response chan *sendDelegationResponse +} + +type sendDelegationResponse struct { + txHashConsumerChain string + err error } func (app *App) buildOwnedDelegation( diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 2886b1a..532bc4d 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -18,6 +18,7 @@ import ( "github.com/babylonlabs-io/btc-staker/types" "github.com/babylonlabs-io/btc-staker/utils" "github.com/babylonlabs-io/btc-staker/walletcontroller" + pv "github.com/cosmos/relayer/v2/relayer/provider" "github.com/decred/dcrd/dcrec/secp256k1/v4" "go.uber.org/zap" @@ -124,6 +125,7 @@ type App struct { m *metrics.StakerMetrics stakingRequestedCmdChan chan *stakingRequestCmd + migrateStakingCmd chan *stakingRequestCmd stakingTxBtcConfirmedEvChan chan *stakingTxBtcConfirmedEvent delegationSubmittedToBabylonEvChan chan *delegationSubmittedToBabylonEvent delegationActivatedPostApprovalEvChan chan *delegationActivatedPostApprovalEvent @@ -230,6 +232,8 @@ func NewStakerAppFromDeps( logger: logger, quit: make(chan struct{}), stakingRequestedCmdChan: make(chan *stakingRequestCmd), + // channel to receive requests of transition of BTC staking tx to consumer BTC delegation + migrateStakingCmd: make(chan *stakingRequestCmd), // event for when transaction is confirmed on BTC stakingTxBtcConfirmedEvChan: make(chan *stakingTxBtcConfirmedEvent), @@ -560,6 +564,10 @@ func (app *App) SendPhase1Transaction( return err } + // check if the BTC tx is deep enough + // build the babylon delegation + // send delegation to babylon + // pushes the confirmation into the btc-staker mechanism and verifies if it is deep enough return app.waitForStakingTransactionConfirmation(stkTxHash, parsedStakingTx.StakingOutput.PkScript, stakingParams.ConfirmationTimeBlocks, app.currentBestBlockHeight.Load()) } @@ -885,22 +893,18 @@ func (app *App) checkTransactionsStatus() error { func (app *App) waitForStakingTxConfirmation( txHash chainhash.Hash, depthOnBtcChain uint32, - ev *notifier.ConfirmationEvent) { + ev *notifier.ConfirmationEvent, +) { defer app.wg.Done() - // check we are not shutting down - select { - case <-app.quit: - ev.Cancel() - return - - default: - } - for { // TODO add handling of more events like ev.NegativeConf which signals that // transaction have beer reorged out of the chain select { + case <-app.quit: + // app is quitting, cancel the event + ev.Cancel() + return case conf := <-ev.Confirmed: stakingEvent := &stakingTxBtcConfirmedEvent{ stakingTxHash: conf.Tx.TxHash(), @@ -924,10 +928,7 @@ func (app *App) waitForStakingTxConfirmation( "btcTxHash": txHash, "confLeft": u, }).Debugf("Staking transaction received confirmation") - case <-app.quit: - // app is quitting, cancel the event - ev.Cancel() - return + } } } @@ -1246,17 +1247,32 @@ func (app *App) buildAndSendDelegation( req *sendDelegationRequest, stakerAddress btcutil.Address, storedTx *stakerdb.StoredTransaction, -) (*cl.DelegationData, error) { +) (*pv.RelayerTxResponse, *cl.DelegationData, error) { delegation, err := app.buildDelegation(req, stakerAddress, storedTx) if err != nil { - return nil, err + return nil, nil, err } - _, err = app.babylonMsgSender.SendDelegation(delegation, req.requiredInclusionBlockDepth) + + resp, err := app.babylonMsgSender.SendDelegation(delegation, req.requiredInclusionBlockDepth) if err != nil { - return nil, err + utils.PushOrQuit[*sendDelegationResponse]( + req.response, + &sendDelegationResponse{ + err: err, + }, + app.quit, + ) + return nil, nil, err } - return delegation, nil + utils.PushOrQuit[*sendDelegationResponse]( + req.response, + &sendDelegationResponse{ + txHashConsumerChain: resp.TxHash, + }, + app.quit, + ) + return resp, delegation, nil } func (app *App) sendDelegationToBabylonTask( @@ -1270,10 +1286,11 @@ func (app *App) sendDelegationToBabylonTask( ctx, cancel := app.appQuitContext() defer cancel() - var delegationData *cl.DelegationData + var ( + delegationData *cl.DelegationData + ) err := retry.Do(func() error { - del, err := app.buildAndSendDelegation(req, stakerAddress, storedTx) - + _, del, err := app.buildAndSendDelegation(req, stakerAddress, storedTx) if err != nil { if errors.Is(err, cl.ErrInvalidBabylonExecution) { return retry.Unrecoverable(err) @@ -1297,20 +1314,21 @@ func (app *App) sendDelegationToBabylonTask( err, "Failed to deliver delegation to babylon due to error.", ) - } else { - // report success with the values we sent to Babylon - ev := &delegationSubmittedToBabylonEvent{ - stakingTxHash: req.txHash, - unbondingTx: delegationData.Ud.UnbondingTransaction, - unbondingTime: delegationData.Ud.UnbondingTxUnbondingTime, - } + return + } - utils.PushOrQuit[*delegationSubmittedToBabylonEvent]( - app.delegationSubmittedToBabylonEvChan, - ev, - app.quit, - ) + // report success with the values we sent to Babylon + ev := &delegationSubmittedToBabylonEvent{ + stakingTxHash: req.txHash, + unbondingTx: delegationData.Ud.UnbondingTransaction, + unbondingTime: delegationData.Ud.UnbondingTxUnbondingTime, } + + utils.PushOrQuit[*delegationSubmittedToBabylonEvent]( + app.delegationSubmittedToBabylonEvChan, + ev, + app.quit, + ) } func (app *App) handlePreApprovalCmd( @@ -1340,7 +1358,7 @@ func (app *App) handlePreApprovalCmd( requiredInclusionBlockDepth: cmd.requiredDepthOnBtcChain, } - delegationData, err := app.buildAndSendDelegation( + _, delegationData, err := app.buildAndSendDelegation( req, cmd.stakerAddress, fakeStoredTx, diff --git a/stakerdb/trackedtranactionstore.go b/stakerdb/trackedtranactionstore.go index 86db762..a3cb7f8 100644 --- a/stakerdb/trackedtranactionstore.go +++ b/stakerdb/trackedtranactionstore.go @@ -837,14 +837,12 @@ func (c *TrackedTransactionStore) AddWatchedTransaction( } serializedSig := slashingTxSig.Serialize() - serializedUnbondingTx, err := utils.SerializeBtcTransaction(unbondingTx) if err != nil { return err } serializedSlashUnbondingTx, err := utils.SerializeBtcTransaction(slashUnbondingTx) - if err != nil { return err } From cb955133d252e18daa8e6455282ff0e666249813 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 18:26:34 -0300 Subject: [PATCH 22/38] chore: try to refactory to receive BTC delegation tx hash --- staker/babylontypes.go | 22 ++- staker/stakerapp.go | 227 +++++++++++++++++------------ stakerdb/trackedtranactionstore.go | 1 - stakerservice/service.go | 6 +- stakerservice/stakerdresponses.go | 4 +- walletcontroller/interface.go | 31 ++++ 6 files changed, 185 insertions(+), 106 deletions(-) diff --git a/staker/babylontypes.go b/staker/babylontypes.go index 3c146f5..94cb869 100644 --- a/staker/babylontypes.go +++ b/staker/babylontypes.go @@ -27,13 +27,11 @@ type inclusionInfo struct { } type sendDelegationRequest struct { - txHash chainhash.Hash + btcTxHash chainhash.Hash // optional field, if not provided, delegation will be sent to Babylon without // the inclusion proof inclusionInfo *inclusionInfo requiredInclusionBlockDepth uint32 - // response of the BTC delegation request sent - response chan *sendDelegationResponse } type sendDelegationResponse struct { @@ -64,7 +62,7 @@ func (app *App) buildOwnedDelegation( // valid and btc confirmed staking transacion, but for some reason we cannot // build delegation data using our own set of libraries app.logger.WithFields(logrus.Fields{ - "btcTxHash": req.txHash, + "btcTxHash": req.btcTxHash, "stakerAddress": stakerAddress, "err": err, }).Fatalf("Failed to build delegation data for already confirmed staking transaction") @@ -145,13 +143,13 @@ func (app *App) buildDelegation( stakerAddress btcutil.Address, storedTx *stakerdb.StoredTransaction) (*cl.DelegationData, error) { if storedTx.Watched { - watchedData, err := app.txTracker.GetWatchedTransactionData(&req.txHash) + watchedData, err := app.txTracker.GetWatchedTransactionData(&req.btcTxHash) if err != nil { // Fatal error as if delegation is watched, the watched data must be in database // and must be not malformed app.logger.WithFields(logrus.Fields{ - "btcTxHash": req.txHash, + "btcTxHash": req.btcTxHash, "stakerAddress": stakerAddress, "err": err, }).Fatalf("Failed to build delegation data for already confirmed staking transaction") @@ -490,3 +488,15 @@ func (app *App) activateVerifiedDelegation( } } } + +func newSendDelegationRequest( + btcStakingTxHash *chainhash.Hash, + inclusionInfo *inclusionInfo, + requiredInclusionBlockDepth uint32, +) *sendDelegationRequest { + return &sendDelegationRequest{ + btcTxHash: *btcStakingTxHash, + inclusionInfo: inclusionInfo, + requiredInclusionBlockDepth: requiredInclusionBlockDepth, + } +} diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 532bc4d..941975f 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -520,35 +520,28 @@ func (app *App) SendPhase1Transaction( tag []byte, covenantPks []*secp256k1.PublicKey, covenantQuorum uint32, -) error { - stkTx, err := app.wc.Tx(stkTxHash) +) (btcDelegationTxHash string, err error) { + parsedStakingTx, notifierTx, status, err := walletcontroller.StkTxV0ParsedWithBlock(app.wc, app.network, stkTxHash, tag, covenantPks, covenantQuorum) if err != nil { - app.logger.WithError(err).Info("err get tx details") - return err + app.logger.WithError(err).Info("err getting tx details") + return "", err } - - wireStkTx := stkTx.MsgTx() - parsedStakingTx, err := staking.ParseV0StakingTx(wireStkTx, tag, covenantPks, covenantQuorum, app.network) - if err != nil { - app.logger.WithError(err).Info("err parse staking Tx with global params") - return err + if status != walletcontroller.TxInChain { + app.logger.WithError(err).Info("BTC tx not on chain") + return "", err } - // unlock wallet to generate pop - // TODO: consider unlock/lock with defer - err = app.wc.UnlockWallet(defaultWalletUnlockTimeout) - if err != nil { - return err - } + // check if the BTC tx is deep enough + // build the babylon delegation + // send delegation to babylon - // pop only works for native segwit address - pop, err := app.createPop(stakerAddr) + pop, err := app.unlockAndCreatePop(stakerAddr) if err != nil { - return err + return "", err } if err := app.txTracker.AddTransactionSentToBTC( - wireStkTx, + notifierTx.Tx, uint32(parsedStakingTx.StakingOutputIdx), parsedStakingTx.OpReturnData.StakingTime, []*btcec.PublicKey{parsedStakingTx.OpReturnData.FinalityProviderPublicKey.PubKey}, @@ -556,20 +549,52 @@ func (app *App) SendPhase1Transaction( stakerAddr, ); err != nil { app.logger.WithError(err).Info("err to set tx as sent on BTC") - return err + return "", err } stakingParams, err := app.babylonClient.Params() if err != nil { - return err + return "", err } - // check if the BTC tx is deep enough - // build the babylon delegation - // send delegation to babylon + if err := checkConfirmationDepth(app.currentBestBlockHeight.Load(), notifierTx.BlockHeight, stakingParams.ConfirmationTimeBlocks); err != nil { + app.logger.WithError(err).Info("err to check confirmation depth of tx") + return "", err + } + + if err := app.txTracker.SetTxConfirmed(stkTxHash, notifierTx.BlockHash, notifierTx.BlockHeight); err != nil { + return "", err + } + + req := newSendDelegationRequest( + stkTxHash, + app.newBtcInclusionInfo(notifierTx), + stakingParams.ConfirmationTimeBlocks, + ) + + ts, err := app.txTracker.GetTransaction(stkTxHash) + if err != nil { + app.logger.WithError(err).Errorf("Error getting transaction state for tx %s", stkTxHash) + return "", err + } + + delData, resp, err := app.sendDelegationToBabylonTaskWithRetry(req, stakerAddr, ts) + if err != nil { + app.logger.WithFields(logrus.Fields{ + "btcTxHash": stakerAddr, + "err": err, + }).Debugf("Sending BTC delegation failed") + return "", err + } + + if err := app.txTracker.SetTxSentToBabylon(stkTxHash, delData.Ud.UnbondingTransaction, delData.Ud.UnbondingTxUnbondingTime); err != nil { + return "", err + } - // pushes the confirmation into the btc-staker mechanism and verifies if it is deep enough - return app.waitForStakingTransactionConfirmation(stkTxHash, parsedStakingTx.StakingOutput.PkScript, stakingParams.ConfirmationTimeBlocks, app.currentBestBlockHeight.Load()) + app.logger.WithFields(logrus.Fields{ + "consumerBtcDelegationTxHash": resp.TxHash, + }).Debugf("Sending BTC delegation was a sucess") + return resp.TxHash, nil } // TODO: We should also handle case when btc node or babylon node lost data and start from scratch @@ -733,21 +758,12 @@ func (app *App) checkTransactionsStatus() error { "btcTxConfirmationBlockHeight": details.BlockHeight, }).Debug("Already confirmed transaction not sent to babylon yet. Initiate sending") - iclusionProof := app.mustBuildInclusionProof( - details.Block, - details.TxIndex, + req := newSendDelegationRequest( + stakingTxHash, + app.newBtcInclusionInfo(details), + stakingParams.ConfirmationTimeBlocks, ) - req := &sendDelegationRequest{ - txHash: *stakingTxHash, - inclusionInfo: &inclusionInfo{ - txIndex: details.TxIndex, - inclusionBlock: details.Block, - inclusionProof: iclusionProof, - }, - requiredInclusionBlockDepth: stakingParams.ConfirmationTimeBlocks, - } - app.wg.Add(1) go app.sendDelegationToBabylonTask(req, stakerAddress, tx) } @@ -977,6 +993,17 @@ func (app *App) mustBuildInclusionProof( return proof } +func (app *App) newBtcInclusionInfo(notifierTx *notifier.TxConfirmation) *inclusionInfo { + return &inclusionInfo{ + txIndex: notifierTx.TxIndex, + inclusionBlock: notifierTx.Block, + inclusionProof: app.mustBuildInclusionProof( + notifierTx.Block, + notifierTx.TxIndex, + ), + } +} + func (app *App) retrieveExternalDelegationData(stakerAddress btcutil.Address) (*externalDelegationData, error) { params, err := app.babylonClient.Params() if err != nil { @@ -1255,23 +1282,9 @@ func (app *App) buildAndSendDelegation( resp, err := app.babylonMsgSender.SendDelegation(delegation, req.requiredInclusionBlockDepth) if err != nil { - utils.PushOrQuit[*sendDelegationResponse]( - req.response, - &sendDelegationResponse{ - err: err, - }, - app.quit, - ) return nil, nil, err } - utils.PushOrQuit[*sendDelegationResponse]( - req.response, - &sendDelegationResponse{ - txHashConsumerChain: resp.TxHash, - }, - app.quit, - ) return resp, delegation, nil } @@ -1282,15 +1295,45 @@ func (app *App) sendDelegationToBabylonTask( ) { defer app.wg.Done() + delegationData, _, err := app.sendDelegationToBabylonTaskWithRetry(req, stakerAddress, storedTx) + if err != nil { + app.reportCriticialError( + req.btcTxHash, + err, + "Failed to deliver delegation to babylon due to error.", + ) + return + } + + // report success with the values we sent to Babylon + ev := &delegationSubmittedToBabylonEvent{ + stakingTxHash: req.btcTxHash, + unbondingTx: delegationData.Ud.UnbondingTransaction, + unbondingTime: delegationData.Ud.UnbondingTxUnbondingTime, + } + + utils.PushOrQuit[*delegationSubmittedToBabylonEvent]( + app.delegationSubmittedToBabylonEvChan, + ev, + app.quit, + ) +} + +func (app *App) sendDelegationToBabylonTaskWithRetry( + req *sendDelegationRequest, + stakerAddress btcutil.Address, + storedTx *stakerdb.StoredTransaction, +) (*cl.DelegationData, *pv.RelayerTxResponse, error) { // using app quit context to cancel retrying when app is shutting down ctx, cancel := app.appQuitContext() defer cancel() var ( delegationData *cl.DelegationData + response *pv.RelayerTxResponse ) err := retry.Do(func() error { - _, del, err := app.buildAndSendDelegation(req, stakerAddress, storedTx) + resp, del, err := app.buildAndSendDelegation(req, stakerAddress, storedTx) if err != nil { if errors.Is(err, cl.ErrInvalidBabylonExecution) { return retry.Unrecoverable(err) @@ -1299,36 +1342,21 @@ func (app *App) sendDelegationToBabylonTask( } delegationData = del + response = resp return nil }, longRetryOps( ctx, app.config.StakerConfig.BabylonStallingInterval, - app.onLongRetryFunc(&req.txHash, "Failed to deliver delegation to babylon due to error."), + app.onLongRetryFunc(&req.btcTxHash, "Failed to deliver delegation to babylon due to error."), )..., ) if err != nil { - app.reportCriticialError( - req.txHash, - err, - "Failed to deliver delegation to babylon due to error.", - ) - return - } - - // report success with the values we sent to Babylon - ev := &delegationSubmittedToBabylonEvent{ - stakingTxHash: req.txHash, - unbondingTx: delegationData.Ud.UnbondingTransaction, - unbondingTime: delegationData.Ud.UnbondingTxUnbondingTime, + return nil, nil, err } - utils.PushOrQuit[*delegationSubmittedToBabylonEvent]( - app.delegationSubmittedToBabylonEvChan, - ev, - app.quit, - ) + return delegationData, response, nil } func (app *App) handlePreApprovalCmd( @@ -1352,12 +1380,7 @@ func (app *App) handlePreApprovalCmd( stakingTxHash := stakingTx.TxHash() - req := &sendDelegationRequest{ - txHash: stakingTxHash, - inclusionInfo: nil, - requiredInclusionBlockDepth: cmd.requiredDepthOnBtcChain, - } - + req := newSendDelegationRequest(&stakingTxHash, nil, cmd.requiredDepthOnBtcChain) _, delegationData, err := app.buildAndSendDelegation( req, cmd.stakerAddress, @@ -1559,16 +1582,15 @@ func (app *App) handleStakingEvents() { ev.txIndex, ) - req := &sendDelegationRequest{ - txHash: ev.stakingTxHash, - inclusionInfo: &inclusionInfo{ + req := newSendDelegationRequest( + &ev.stakingTxHash, + &inclusionInfo{ txIndex: ev.txIndex, inclusionBlock: ev.inlusionBlock, inclusionProof: proof, }, - requiredInclusionBlockDepth: ev.blockDepth, - } - + ev.blockDepth, + ) storedTx, stakerAddress := app.mustGetTransactionAndStakerAddress(&ev.stakingTxHash) app.m.DelegationsConfirmedOnBtc.Inc() @@ -1889,10 +1911,7 @@ func (app *App) StakeFunds( stakingAmount, params.MinStakingValue, params.MaxStakingValue) } - // unlock wallet for the rest of the operations - // TODO consider unlock/lock with defer - err = app.wc.UnlockWallet(defaultWalletUnlockTimeout) - + pop, err := app.unlockAndCreatePop(stakerAddress) if err != nil { return nil, err } @@ -1904,11 +1923,6 @@ func (app *App) StakeFunds( return nil, err } - pop, err := app.createPop(stakerAddress) - if err != nil { - return nil, err - } - stakingInfo, err := staking.BuildStakingInfo( stakerPubKey, fpPks, @@ -2268,8 +2282,16 @@ func (app *App) UnbondStaking( return &unbondingTxHash, nil } -func (app *App) createPop(stakerAddress btcutil.Address) (*cl.BabylonPop, error) { +func (app *App) unlockAndCreatePop(stakerAddress btcutil.Address) (*cl.BabylonPop, error) { + // unlock wallet for the rest of the operations + // TODO consider unlock/lock with defer + err := app.wc.UnlockWallet(defaultWalletUnlockTimeout) + if err != nil { + return nil, err + } + babylonAddrHash := tmhash.Sum(app.babylonClient.GetKeyAddress().Bytes()) + // pop only works for native segwit address sig, err := app.wc.SignBip322NativeSegwit(babylonAddrHash, stakerAddress) if err != nil { return nil, err @@ -2281,3 +2303,16 @@ func (app *App) createPop(stakerAddress btcutil.Address) (*cl.BabylonPop, error) stakerAddress, ) } + +func checkConfirmationDepth(tipBlockHeight, txInclusionBlockHeight, confirmationTimeBlocks uint32) error { + if txInclusionBlockHeight >= tipBlockHeight { + return fmt.Errorf("inclusion block height: %d should be lower than current tip: %d", txInclusionBlockHeight, tipBlockHeight) + } + if (tipBlockHeight - txInclusionBlockHeight) < confirmationTimeBlocks { + return fmt.Errorf( + "BTC tx not deep enough, current tip: %d, tx inclusion height: %d, confirmations needed: %d", + tipBlockHeight, txInclusionBlockHeight, confirmationTimeBlocks, + ) + } + return nil +} diff --git a/stakerdb/trackedtranactionstore.go b/stakerdb/trackedtranactionstore.go index a3cb7f8..4b10f82 100644 --- a/stakerdb/trackedtranactionstore.go +++ b/stakerdb/trackedtranactionstore.go @@ -1101,7 +1101,6 @@ func (c *TrackedTransactionStore) GetTransaction(txHash *chainhash.Hash) (*Store } txFromDB, err := protoTxToStoredTransaction(&storedTxProto) - if err != nil { return err } diff --git a/stakerservice/service.go b/stakerservice/service.go index 7faee9a..6225414 100644 --- a/stakerservice/service.go +++ b/stakerservice/service.go @@ -151,13 +151,15 @@ func (s *StakerService) btcDelegationFromBtcStakingTx( return nil, err } - err = s.staker.SendPhase1Transaction(stakerAddr, stkTxHash, tag, covenantPks, covenantQuorum) + btcDelegationTxHash, err := s.staker.SendPhase1Transaction(stakerAddr, stkTxHash, tag, covenantPks, covenantQuorum) if err != nil { s.logger.WithError(err).Info("err to send phase 1 tx") return nil, err } - return &ResultBtcDelegationFromBtcStakingTx{}, nil + return &ResultBtcDelegationFromBtcStakingTx{ + TxHashDelegationBTC: btcDelegationTxHash, + }, nil } func parseCovenantsPubKeyFromHex(covenantPksHex ...string) ([]*btcec.PublicKey, error) { diff --git a/stakerservice/stakerdresponses.go b/stakerservice/stakerdresponses.go index 286b136..a8e8486 100644 --- a/stakerservice/stakerdresponses.go +++ b/stakerservice/stakerdresponses.go @@ -2,7 +2,9 @@ package stakerservice type ResultHealth struct{} -type ResultBtcDelegationFromBtcStakingTx struct{} +type ResultBtcDelegationFromBtcStakingTx struct { + TxHashDelegationBTC string `json:"tx_hash_delegation_btc"` +} type ResultStake struct { TxHash string `json:"tx_hash"` diff --git a/walletcontroller/interface.go b/walletcontroller/interface.go index c3ecd23..2dcd151 100644 --- a/walletcontroller/interface.go +++ b/walletcontroller/interface.go @@ -1,12 +1,16 @@ package walletcontroller import ( + staking "github.com/babylonlabs-io/babylon/btcstaking" "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/txscript" "github.com/btcsuite/btcd/wire" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + notifier "github.com/lightningnetwork/lnd/chainntnfs" ) @@ -76,3 +80,30 @@ type WalletController interface { outputIdx uint32, ) (bool, error) } + +func StkTxV0ParsedWithBlock( + wc WalletController, + btcNetwork *chaincfg.Params, + stkTxHash *chainhash.Hash, + tag []byte, + covenantPks []*secp256k1.PublicKey, + covenantQuorum uint32, +) (*staking.ParsedV0StakingTx, *notifier.TxConfirmation, TxStatus, error) { + stkTx, err := wc.Tx(stkTxHash) + if err != nil { + return nil, nil, TxNotFound, err + } + + wireStkTx := stkTx.MsgTx() + parsedStakingTx, err := staking.ParseV0StakingTx(wireStkTx, tag, covenantPks, covenantQuorum, btcNetwork) + if err != nil { + return nil, nil, TxNotFound, err + } + + notifierTx, status, err := wc.TxDetails(stkTxHash, parsedStakingTx.StakingOutput.PkScript) + if err != nil { + return nil, nil, TxNotFound, err + } + + return parsedStakingTx, notifierTx, status, nil +} From 6bea332cb50d412d877437e78110f13dfd3b345e Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 18:38:28 -0300 Subject: [PATCH 23/38] chore: moved to use command --- staker/commands.go | 27 +++++++++ staker/stakerapp.go | 143 +++++++++++++++++++++++++++----------------- 2 files changed, 115 insertions(+), 55 deletions(-) diff --git a/staker/commands.go b/staker/commands.go index 5d82bfc..33dcc2f 100644 --- a/staker/commands.go +++ b/staker/commands.go @@ -1,6 +1,7 @@ package staker import ( + staking "github.com/babylonlabs-io/babylon/btcstaking" cl "github.com/babylonlabs-io/btc-staker/babylonclient" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -8,6 +9,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" + notifier "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) @@ -131,3 +133,28 @@ func (req *stakingRequestCmd) EventID() chainhash.Hash { func (req *stakingRequestCmd) EventDesc() string { return "STAKING_REQUESTED_CMD" } + +type migrateStakingCmd struct { + stakerAddr btcutil.Address + notifierTx *notifier.TxConfirmation + parsedStakingTx *staking.ParsedV0StakingTx + pop *cl.BabylonPop + errChan chan error + successChanTxHash chan string +} + +func newMigrateStakingCmd( + stakerAddr btcutil.Address, + notifierTx *notifier.TxConfirmation, + parsedStakingTx *staking.ParsedV0StakingTx, + pop *cl.BabylonPop, +) *migrateStakingCmd { + return &migrateStakingCmd{ + stakerAddr: stakerAddr, + notifierTx: notifierTx, + parsedStakingTx: parsedStakingTx, + pop: pop, + errChan: make(chan error, 1), + successChanTxHash: make(chan string, 1), + } +} diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 941975f..aaf2f91 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -125,7 +125,7 @@ type App struct { m *metrics.StakerMetrics stakingRequestedCmdChan chan *stakingRequestCmd - migrateStakingCmd chan *stakingRequestCmd + migrateStakingCmd chan *migrateStakingCmd stakingTxBtcConfirmedEvChan chan *stakingTxBtcConfirmedEvent delegationSubmittedToBabylonEvChan chan *delegationSubmittedToBabylonEvent delegationActivatedPostApprovalEvChan chan *delegationActivatedPostApprovalEvent @@ -233,7 +233,7 @@ func NewStakerAppFromDeps( quit: make(chan struct{}), stakingRequestedCmdChan: make(chan *stakingRequestCmd), // channel to receive requests of transition of BTC staking tx to consumer BTC delegation - migrateStakingCmd: make(chan *stakingRequestCmd), + migrateStakingCmd: make(chan *migrateStakingCmd), // event for when transaction is confirmed on BTC stakingTxBtcConfirmedEvChan: make(chan *stakingTxBtcConfirmedEvent), @@ -521,6 +521,13 @@ func (app *App) SendPhase1Transaction( covenantPks []*secp256k1.PublicKey, covenantQuorum uint32, ) (btcDelegationTxHash string, err error) { + // check we are not shutting down + select { + case <-app.quit: + return "", nil + default: + } + parsedStakingTx, notifierTx, status, err := walletcontroller.StkTxV0ParsedWithBlock(app.wc, app.network, stkTxHash, tag, covenantPks, covenantQuorum) if err != nil { app.logger.WithError(err).Info("err getting tx details") @@ -531,70 +538,32 @@ func (app *App) SendPhase1Transaction( return "", err } - // check if the BTC tx is deep enough - // build the babylon delegation - // send delegation to babylon - pop, err := app.unlockAndCreatePop(stakerAddr) if err != nil { return "", err } - if err := app.txTracker.AddTransactionSentToBTC( - notifierTx.Tx, - uint32(parsedStakingTx.StakingOutputIdx), - parsedStakingTx.OpReturnData.StakingTime, - []*btcec.PublicKey{parsedStakingTx.OpReturnData.FinalityProviderPublicKey.PubKey}, - babylonPopToDBPop(pop), - stakerAddr, - ); err != nil { - app.logger.WithError(err).Info("err to set tx as sent on BTC") - return "", err - } - - stakingParams, err := app.babylonClient.Params() - if err != nil { - return "", err - } - - if err := checkConfirmationDepth(app.currentBestBlockHeight.Load(), notifierTx.BlockHeight, stakingParams.ConfirmationTimeBlocks); err != nil { - app.logger.WithError(err).Info("err to check confirmation depth of tx") - return "", err - } - - if err := app.txTracker.SetTxConfirmed(stkTxHash, notifierTx.BlockHash, notifierTx.BlockHeight); err != nil { - return "", err - } + req := newMigrateStakingCmd(stakerAddr, notifierTx, parsedStakingTx, pop) - req := newSendDelegationRequest( - stkTxHash, - app.newBtcInclusionInfo(notifierTx), - stakingParams.ConfirmationTimeBlocks, + utils.PushOrQuit[*migrateStakingCmd]( + app.migrateStakingCmd, + req, + app.quit, ) - ts, err := app.txTracker.GetTransaction(stkTxHash) - if err != nil { - app.logger.WithError(err).Errorf("Error getting transaction state for tx %s", stkTxHash) - return "", err - } - - delData, resp, err := app.sendDelegationToBabylonTaskWithRetry(req, stakerAddr, ts) - if err != nil { + select { + case reqErr := <-req.errChan: app.logger.WithFields(logrus.Fields{ - "btcTxHash": stakerAddr, - "err": err, - }).Debugf("Sending BTC delegation failed") - return "", err - } + "stakerAddress": stakerAddr, + "err": reqErr, + }).Debugf("Sending staking tx failed") - if err := app.txTracker.SetTxSentToBabylon(stkTxHash, delData.Ud.UnbondingTransaction, delData.Ud.UnbondingTxUnbondingTime); err != nil { - return "", err + return "", reqErr + case hash := <-req.successChanTxHash: + return hash, nil + case <-app.quit: + return "", nil } - - app.logger.WithFields(logrus.Fields{ - "consumerBtcDelegationTxHash": resp.TxHash, - }).Debugf("Sending BTC delegation was a sucess") - return resp.TxHash, nil } // TODO: We should also handle case when btc node or babylon node lost data and start from scratch @@ -1552,6 +1521,70 @@ func (app *App) handleStakingCommands() { ) } app.logStakingEventProcessed(cmd) + case cmd := <-app.migrateStakingCmd: + if err := app.txTracker.AddTransactionSentToBTC( + cmd.notifierTx.Tx, + uint32(cmd.parsedStakingTx.StakingOutputIdx), + cmd.parsedStakingTx.OpReturnData.StakingTime, + []*btcec.PublicKey{cmd.parsedStakingTx.OpReturnData.FinalityProviderPublicKey.PubKey}, + babylonPopToDBPop(cmd.pop), + cmd.stakerAddr, + ); err != nil { + app.logger.WithError(err).Info("err to set tx as sent on BTC") + cmd.errChan <- err + continue + } + + stakingParams, err := app.babylonClient.Params() + if err != nil { + cmd.errChan <- err + continue + } + + if err := checkConfirmationDepth(app.currentBestBlockHeight.Load(), cmd.notifierTx.BlockHeight, stakingParams.ConfirmationTimeBlocks); err != nil { + app.logger.WithError(err).Info("err to check confirmation depth of tx") + cmd.errChan <- err + continue + } + + stkTxHash := cmd.notifierTx.Tx.TxHash() + if err := app.txTracker.SetTxConfirmed(&stkTxHash, cmd.notifierTx.BlockHash, cmd.notifierTx.BlockHeight); err != nil { + cmd.errChan <- err + continue + } + + req := newSendDelegationRequest( + &stkTxHash, + app.newBtcInclusionInfo(cmd.notifierTx), + stakingParams.ConfirmationTimeBlocks, + ) + + ts, err := app.txTracker.GetTransaction(&stkTxHash) + if err != nil { + app.logger.WithError(err).Errorf("Error getting transaction state for tx %s", stkTxHash.String()) + cmd.errChan <- err + continue + } + + delData, resp, err := app.sendDelegationToBabylonTaskWithRetry(req, cmd.stakerAddr, ts) + if err != nil { + app.logger.WithFields(logrus.Fields{ + "btcTxHash": cmd.stakerAddr, + "err": err, + }).Debugf("Sending BTC delegation failed") + cmd.errChan <- err + continue + } + + if err := app.txTracker.SetTxSentToBabylon(&stkTxHash, delData.Ud.UnbondingTransaction, delData.Ud.UnbondingTxUnbondingTime); err != nil { + cmd.errChan <- err + continue + } + + app.logger.WithFields(logrus.Fields{ + "consumerBtcDelegationTxHash": resp.TxHash, + }).Debugf("Sending BTC delegation was a sucess") + cmd.successChanTxHash <- resp.TxHash case <-app.quit: return } From 0f0e989c1cd2070b915a4e4cd91962a116c5d8e9 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 18:39:00 -0300 Subject: [PATCH 24/38] chore: add todo --- staker/stakerapp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index aaf2f91..4930923 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -1535,6 +1535,7 @@ func (app *App) handleStakingCommands() { continue } + // TODO: modify to handle it as channel stakingParams, err := app.babylonClient.Params() if err != nil { cmd.errChan <- err From 8be6baf1206ecf4f37374108ead36ddc49626cc0 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 21:38:47 -0300 Subject: [PATCH 25/38] chore: fix lint and allow nonamedreturns --- .golangci.yml | 2 +- staker/babylontypes.go | 5 ----- staker/stakerapp.go | 3 +-- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 717f7ad..5872f4d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -39,7 +39,7 @@ linters: - nilerr # - nlreturn # Style wise I personally like this one, todo(lazar): unlax at somepoint, good practice - noctx - - nonamedreturns + # - nonamedreturns - nosprintfhostport - paralleltest - reassign diff --git a/staker/babylontypes.go b/staker/babylontypes.go index 94cb869..17758e7 100644 --- a/staker/babylontypes.go +++ b/staker/babylontypes.go @@ -34,11 +34,6 @@ type sendDelegationRequest struct { requiredInclusionBlockDepth uint32 } -type sendDelegationResponse struct { - txHashConsumerChain string - err error -} - func (app *App) buildOwnedDelegation( req *sendDelegationRequest, stakerAddress btcutil.Address, diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 4930923..edb13ec 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -913,7 +913,6 @@ func (app *App) waitForStakingTxConfirmation( "btcTxHash": txHash, "confLeft": u, }).Debugf("Staking transaction received confirmation") - } } } @@ -1584,7 +1583,7 @@ func (app *App) handleStakingCommands() { app.logger.WithFields(logrus.Fields{ "consumerBtcDelegationTxHash": resp.TxHash, - }).Debugf("Sending BTC delegation was a sucess") + }).Debugf("Sending BTC delegation was a success") cmd.successChanTxHash <- resp.TxHash case <-app.quit: return From 9958f45de8fd2bed543b4bb8c3f8540ad31595d2 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 21:44:20 -0300 Subject: [PATCH 26/38] chore: remove go routine of mine empty blocks --- itest/e2e_test.go | 2 +- staker/stakerapp.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/itest/e2e_test.go b/itest/e2e_test.go index c995f68..232c49f 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -967,7 +967,7 @@ func TestStakeFromPhase1(t *testing.T) { params, err := cl.Params() require.NoError(t, err) - go tm.mineNEmptyBlocks(t, params.ConfirmationTimeBlocks, true) + tm.mineNEmptyBlocks(t, params.ConfirmationTimeBlocks, true) tm.waitForStakingTxState(t, txHash, proto.TransactionState_SENT_TO_BABYLON) pend, err := tm.BabylonClient.QueryPendingBTCDelegations() diff --git a/staker/stakerapp.go b/staker/stakerapp.go index edb13ec..d1940ab 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -544,7 +544,6 @@ func (app *App) SendPhase1Transaction( } req := newMigrateStakingCmd(stakerAddr, notifierTx, parsedStakingTx, pop) - utils.PushOrQuit[*migrateStakingCmd]( app.migrateStakingCmd, req, @@ -1505,7 +1504,6 @@ func (app *App) handleStakingCommands() { } stakingTxHash, err := app.handleStakingCmd(cmd) - if err != nil { utils.PushOrQuit( cmd.errChan, @@ -1520,6 +1518,7 @@ func (app *App) handleStakingCommands() { ) } app.logStakingEventProcessed(cmd) + case cmd := <-app.migrateStakingCmd: if err := app.txTracker.AddTransactionSentToBTC( cmd.notifierTx.Tx, From 417d18fa8fa9f0562a4cc40447d1a3525afce105 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 22:17:39 -0300 Subject: [PATCH 27/38] chore: add new field to transaction tracked of tx hash of btc delegation --- itest/e2e_test.go | 2 +- itest/manager.go | 3 + proto/transaction.pb.go | 161 +++++++---------------- proto/transaction.proto | 1 + staker/events.go | 7 +- staker/stakerapp.go | 21 ++- stakerdb/trackedtranactionstore.go | 3 +- stakerdb/trackedtransactionstore_test.go | 3 +- 8 files changed, 77 insertions(+), 124 deletions(-) diff --git a/itest/e2e_test.go b/itest/e2e_test.go index 232c49f..f527d26 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -967,7 +967,7 @@ func TestStakeFromPhase1(t *testing.T) { params, err := cl.Params() require.NoError(t, err) - tm.mineNEmptyBlocks(t, params.ConfirmationTimeBlocks, true) + tm.mineNEmptyBlocks(t, params.ConfirmationTimeBlocks+1, true) tm.waitForStakingTxState(t, txHash, proto.TransactionState_SENT_TO_BABYLON) pend, err := tm.BabylonClient.QueryPendingBTCDelegations() diff --git a/itest/manager.go b/itest/manager.go index 2620676..6f0306b 100644 --- a/itest/manager.go +++ b/itest/manager.go @@ -1144,6 +1144,8 @@ func (tm *TestManager) insertCovenantSigForDelegation( btcutil.Amount(btcDel.TotalSat), regtestParams, ) + require.NoError(t, err) + slashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() require.NoError(t, err) @@ -1159,6 +1161,7 @@ func (tm *TestManager) insertCovenantSigForDelegation( // slash unbonding tx spends unbonding tx unbondingMsgTx, _, err := bbntypes.NewBTCTxFromHex(btcDel.UndelegationResponse.UnbondingTxHex) require.NoError(t, err) + unbondingInfo, err := staking.BuildUnbondingInfo( btcDel.BtcPk.MustToBTCPK(), fpBTCPKs, diff --git a/proto/transaction.pb.go b/proto/transaction.pb.go index 9870fd3..e8836bc 100644 --- a/proto/transaction.pb.go +++ b/proto/transaction.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.33.0 +// protoc-gen-go v1.35.1 // protoc v3.6.1 // source: transaction.proto @@ -101,11 +101,9 @@ type WatchedTxData struct { func (x *WatchedTxData) Reset() { *x = WatchedTxData{} - if protoimpl.UnsafeEnabled { - mi := &file_transaction_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_transaction_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *WatchedTxData) String() string { @@ -116,7 +114,7 @@ func (*WatchedTxData) ProtoMessage() {} func (x *WatchedTxData) ProtoReflect() protoreflect.Message { mi := &file_transaction_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -199,11 +197,9 @@ type BTCConfirmationInfo struct { func (x *BTCConfirmationInfo) Reset() { *x = BTCConfirmationInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_transaction_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_transaction_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BTCConfirmationInfo) String() string { @@ -214,7 +210,7 @@ func (*BTCConfirmationInfo) ProtoMessage() {} func (x *BTCConfirmationInfo) ProtoReflect() protoreflect.Message { mi := &file_transaction_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -254,11 +250,9 @@ type CovenantSig struct { func (x *CovenantSig) Reset() { *x = CovenantSig{} - if protoimpl.UnsafeEnabled { - mi := &file_transaction_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_transaction_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CovenantSig) String() string { @@ -269,7 +263,7 @@ func (*CovenantSig) ProtoMessage() {} func (x *CovenantSig) ProtoReflect() protoreflect.Message { mi := &file_transaction_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -313,11 +307,9 @@ type UnbondingTxData struct { func (x *UnbondingTxData) Reset() { *x = UnbondingTxData{} - if protoimpl.UnsafeEnabled { - mi := &file_transaction_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_transaction_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UnbondingTxData) String() string { @@ -328,7 +320,7 @@ func (*UnbondingTxData) ProtoMessage() {} func (x *UnbondingTxData) ProtoReflect() protoreflect.Message { mi := &file_transaction_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -393,16 +385,15 @@ type TrackedTransaction struct { State TransactionState `protobuf:"varint,10,opt,name=state,proto3,enum=proto.TransactionState" json:"state,omitempty"` Watched bool `protobuf:"varint,11,opt,name=watched,proto3" json:"watched,omitempty"` // this data is only filled if tracked transactions state is >= SENT_TO_BABYLON - UnbondingTxData *UnbondingTxData `protobuf:"bytes,12,opt,name=unbonding_tx_data,json=unbondingTxData,proto3" json:"unbonding_tx_data,omitempty"` + UnbondingTxData *UnbondingTxData `protobuf:"bytes,12,opt,name=unbonding_tx_data,json=unbondingTxData,proto3" json:"unbonding_tx_data,omitempty"` + BtcDelegationTxHash string `protobuf:"bytes,13,opt,name=btcDelegationTxHash,proto3" json:"btcDelegationTxHash,omitempty"` } func (x *TrackedTransaction) Reset() { *x = TrackedTransaction{} - if protoimpl.UnsafeEnabled { - mi := &file_transaction_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_transaction_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TrackedTransaction) String() string { @@ -413,7 +404,7 @@ func (*TrackedTransaction) ProtoMessage() {} func (x *TrackedTransaction) ProtoReflect() protoreflect.Message { mi := &file_transaction_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -512,6 +503,13 @@ func (x *TrackedTransaction) GetUnbondingTxData() *UnbondingTxData { return nil } +func (x *TrackedTransaction) GetBtcDelegationTxHash() string { + if x != nil { + return x.BtcDelegationTxHash + } + return "" +} + var File_transaction_proto protoreflect.FileDescriptor var file_transaction_proto_rawDesc = []byte{ @@ -574,7 +572,7 @@ var file_transaction_proto_rawDesc = []byte{ 0x42, 0x54, 0x43, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x1e, 0x75, 0x6e, 0x62, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x42, 0x74, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, - 0x6e, 0x66, 0x6f, 0x22, 0x84, 0x05, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x54, + 0x6e, 0x66, 0x6f, 0x22, 0xb6, 0x05, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x74, 0x72, 0x61, @@ -614,21 +612,24 @@ var file_transaction_proto_rawDesc = []byte{ 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x6e, 0x62, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0f, 0x75, 0x6e, 0x62, 0x6f, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x44, 0x61, 0x74, 0x61, 0x2a, 0xa5, 0x01, 0x0a, 0x10, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x0f, 0x0a, 0x0b, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, 0x42, 0x54, 0x43, 0x10, 0x00, - 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x4e, - 0x5f, 0x42, 0x54, 0x43, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x54, - 0x4f, 0x5f, 0x42, 0x41, 0x42, 0x59, 0x4c, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x56, - 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x45, 0x4c, - 0x45, 0x47, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x04, - 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x42, 0x4f, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x43, 0x4f, - 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x4e, 0x5f, 0x42, 0x54, 0x43, 0x10, 0x05, - 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x50, 0x45, 0x4e, 0x54, 0x5f, 0x4f, 0x4e, 0x5f, 0x42, 0x54, 0x43, - 0x10, 0x06, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x62, 0x61, 0x62, 0x79, 0x6c, 0x6f, 0x6e, 0x6c, 0x61, 0x62, 0x73, 0x2d, 0x69, 0x6f, 0x2f, - 0x62, 0x74, 0x63, 0x2d, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x44, 0x61, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x13, 0x62, 0x74, + 0x63, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x78, 0x48, 0x61, 0x73, + 0x68, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x62, 0x74, 0x63, 0x44, 0x65, 0x6c, 0x65, + 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x2a, 0xa5, 0x01, 0x0a, + 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, 0x42, 0x54, 0x43, + 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x5f, + 0x4f, 0x4e, 0x5f, 0x42, 0x54, 0x43, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x4e, 0x54, + 0x5f, 0x54, 0x4f, 0x5f, 0x42, 0x41, 0x42, 0x59, 0x4c, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x0c, 0x0a, + 0x08, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x44, + 0x45, 0x4c, 0x45, 0x47, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, + 0x10, 0x04, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x42, 0x4f, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, + 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x4e, 0x5f, 0x42, 0x54, 0x43, + 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x50, 0x45, 0x4e, 0x54, 0x5f, 0x4f, 0x4e, 0x5f, 0x42, + 0x54, 0x43, 0x10, 0x06, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x62, 0x61, 0x62, 0x79, 0x6c, 0x6f, 0x6e, 0x6c, 0x61, 0x62, 0x73, 0x2d, 0x69, + 0x6f, 0x2f, 0x62, 0x74, 0x63, 0x2d, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -645,7 +646,7 @@ func file_transaction_proto_rawDescGZIP() []byte { var file_transaction_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_transaction_proto_msgTypes = make([]protoimpl.MessageInfo, 5) -var file_transaction_proto_goTypes = []interface{}{ +var file_transaction_proto_goTypes = []any{ (TransactionState)(0), // 0: proto.TransactionState (*WatchedTxData)(nil), // 1: proto.WatchedTxData (*BTCConfirmationInfo)(nil), // 2: proto.BTCConfirmationInfo @@ -671,68 +672,6 @@ func file_transaction_proto_init() { if File_transaction_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_transaction_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WatchedTxData); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_transaction_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BTCConfirmationInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_transaction_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CovenantSig); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_transaction_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UnbondingTxData); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_transaction_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrackedTransaction); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/proto/transaction.proto b/proto/transaction.proto index 24266cd..d2115d1 100644 --- a/proto/transaction.proto +++ b/proto/transaction.proto @@ -67,4 +67,5 @@ message TrackedTransaction { bool watched = 11; // this data is only filled if tracked transactions state is >= SENT_TO_BABYLON UnbondingTxData unbonding_tx_data = 12; + string btcDelegationTxHash = 13; } diff --git a/staker/events.go b/staker/events.go index 49ad6fc..fa12cd1 100644 --- a/staker/events.go +++ b/staker/events.go @@ -40,9 +40,10 @@ func (event *stakingTxBtcConfirmedEvent) EventDesc() string { } type delegationSubmittedToBabylonEvent struct { - stakingTxHash chainhash.Hash - unbondingTx *wire.MsgTx - unbondingTime uint16 + stakingTxHash chainhash.Hash + btcDelegationTxHash string + unbondingTx *wire.MsgTx + unbondingTime uint16 } func (event *delegationSubmittedToBabylonEvent) EventID() chainhash.Hash { diff --git a/staker/stakerapp.go b/staker/stakerapp.go index d1940ab..185a744 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -1389,7 +1389,6 @@ func (app *App) handlePostApprovalCmd( bestBlockHeight := app.currentBestBlockHeight.Load() err := app.wc.UnlockWallet(defaultWalletUnlockTimeout) - if err != nil { return nil, err } @@ -1575,15 +1574,23 @@ func (app *App) handleStakingCommands() { continue } - if err := app.txTracker.SetTxSentToBabylon(&stkTxHash, delData.Ud.UnbondingTransaction, delData.Ud.UnbondingTxUnbondingTime); err != nil { - cmd.errChan <- err - continue + err = app.txTracker.SetTxSentToBabylon(&stkTxHash, resp.TxHash, delData.Ud.UnbondingTransaction, delData.Ud.UnbondingTxUnbondingTime) + if err != nil { + utils.PushOrQuit( + cmd.errChan, + err, + app.quit, + ) + } else { + utils.PushOrQuit( + cmd.successChanTxHash, + resp.TxHash, + app.quit, + ) } - app.logger.WithFields(logrus.Fields{ "consumerBtcDelegationTxHash": resp.TxHash, }).Debugf("Sending BTC delegation was a success") - cmd.successChanTxHash <- resp.TxHash case <-app.quit: return } @@ -1635,7 +1642,7 @@ func (app *App) handleStakingEvents() { case ev := <-app.delegationSubmittedToBabylonEvChan: app.logStakingEventReceived(ev) - if err := app.txTracker.SetTxSentToBabylon(&ev.stakingTxHash, ev.unbondingTx, ev.unbondingTime); err != nil { + if err := app.txTracker.SetTxSentToBabylon(&ev.stakingTxHash, ev.btcDelegationTxHash, ev.unbondingTx, ev.unbondingTime); err != nil { // TODO: handle this error somehow, it means we received confirmation for tx which we do not store // which is seems like programming error. Maybe panic? app.logger.Fatalf("Error setting state for tx %s: %s", ev.stakingTxHash, err) diff --git a/stakerdb/trackedtranactionstore.go b/stakerdb/trackedtranactionstore.go index 4b10f82..177b5ab 100644 --- a/stakerdb/trackedtranactionstore.go +++ b/stakerdb/trackedtranactionstore.go @@ -974,11 +974,11 @@ func (c *TrackedTransactionStore) SetTxConfirmed( func (c *TrackedTransactionStore) SetTxSentToBabylon( txHash *chainhash.Hash, + btcDelegationTxHash string, unbondingTx *wire.MsgTx, unbondingTime uint16, ) error { update, err := newInitialUnbondingTxData(unbondingTx, unbondingTime) - if err != nil { return err } @@ -990,6 +990,7 @@ func (c *TrackedTransactionStore) SetTxSentToBabylon( tx.State = proto.TransactionState_SENT_TO_BABYLON tx.UnbondingTxData = update + tx.BtcDelegationTxHash = btcDelegationTxHash return nil } diff --git a/stakerdb/trackedtransactionstore_test.go b/stakerdb/trackedtransactionstore_test.go index 1d546f0..6e00689 100644 --- a/stakerdb/trackedtransactionstore_test.go +++ b/stakerdb/trackedtransactionstore_test.go @@ -213,7 +213,7 @@ func TestStateTransitions(t *testing.T) { require.Equal(t, height, storedTx.StakingTxConfirmationInfo.Height) // Sent to Babylon - err = s.SetTxSentToBabylon(&txHash, tx.StakingTx, tx.StakingTime) + err = s.SetTxSentToBabylon(&txHash, hash.String(), tx.StakingTx, tx.StakingTime) require.NoError(t, err) storedTx, err = s.GetTransaction(&txHash) require.NoError(t, err) @@ -357,6 +357,7 @@ func FuzzQuerySpendableTx(f *testing.F) { txHash := storedTx.StakingTx.TxHash() err := s.SetTxSentToBabylon( &txHash, + txHash.String(), storedTx.StakingTx, storedTx.StakingTime, ) From 5eb25428c0ef3c799eecf791e7b2a8ab1bb1e4ca Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 22:57:31 -0300 Subject: [PATCH 28/38] tryfix: get btc delegation tx hash --- staker/stakerapp.go | 107 ++++++++++++++--------------- stakerdb/trackedtranactionstore.go | 18 ++--- 2 files changed, 63 insertions(+), 62 deletions(-) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 185a744..15278d5 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -678,7 +678,6 @@ func (app *App) checkTransactionsStatus() error { stakingTxHash := txHash delegationInfo, err := app.babylonClient.QueryDelegationInfo(stakingTxHash) - if err != nil && !errors.Is(err, cl.ErrDelegationNotFound) { return err } @@ -703,10 +702,8 @@ func (app *App) checkTransactionsStatus() error { } else { // transaction which is not on babylon, is already confirmed on btc chain // get all necessary info and send it to babylon - tx, stakerAddress := app.mustGetTransactionAndStakerAddress(stakingTxHash) details, status, err := app.wc.TxDetails(stakingTxHash, tx.StakingTx.TxOut[tx.StakingOutputIndex].PkScript) - if err != nil { // we got some communication err, return error and kill app startup return err @@ -1262,7 +1259,7 @@ func (app *App) sendDelegationToBabylonTask( ) { defer app.wg.Done() - delegationData, _, err := app.sendDelegationToBabylonTaskWithRetry(req, stakerAddress, storedTx) + delegationData, delegationTxResp, err := app.sendDelegationToBabylonTaskWithRetry(req, stakerAddress, storedTx) if err != nil { app.reportCriticialError( req.btcTxHash, @@ -1274,9 +1271,10 @@ func (app *App) sendDelegationToBabylonTask( // report success with the values we sent to Babylon ev := &delegationSubmittedToBabylonEvent{ - stakingTxHash: req.btcTxHash, - unbondingTx: delegationData.Ud.UnbondingTransaction, - unbondingTime: delegationData.Ud.UnbondingTxUnbondingTime, + stakingTxHash: req.btcTxHash, + btcDelegationTxHash: delegationTxResp.TxHash, + unbondingTx: delegationData.Ud.UnbondingTransaction, + unbondingTime: delegationData.Ud.UnbondingTxUnbondingTime, } utils.PushOrQuit[*delegationSubmittedToBabylonEvent]( @@ -1519,77 +1517,58 @@ func (app *App) handleStakingCommands() { app.logStakingEventProcessed(cmd) case cmd := <-app.migrateStakingCmd: - if err := app.txTracker.AddTransactionSentToBTC( - cmd.notifierTx.Tx, - uint32(cmd.parsedStakingTx.StakingOutputIdx), - cmd.parsedStakingTx.OpReturnData.StakingTime, - []*btcec.PublicKey{cmd.parsedStakingTx.OpReturnData.FinalityProviderPublicKey.PubKey}, - babylonPopToDBPop(cmd.pop), - cmd.stakerAddr, - ); err != nil { - app.logger.WithError(err).Info("err to set tx as sent on BTC") - cmd.errChan <- err - continue - } - - // TODO: modify to handle it as channel + stkTxHash := cmd.notifierTx.Tx.TxHash() stakingParams, err := app.babylonClient.Params() if err != nil { cmd.errChan <- err continue } - if err := checkConfirmationDepth(app.currentBestBlockHeight.Load(), cmd.notifierTx.BlockHeight, stakingParams.ConfirmationTimeBlocks); err != nil { - app.logger.WithError(err).Info("err to check confirmation depth of tx") - cmd.errChan <- err - continue - } - - stkTxHash := cmd.notifierTx.Tx.TxHash() - if err := app.txTracker.SetTxConfirmed(&stkTxHash, cmd.notifierTx.BlockHash, cmd.notifierTx.BlockHeight); err != nil { - cmd.errChan <- err - continue - } - - req := newSendDelegationRequest( + bestBlockHeight := app.currentBestBlockHeight.Load() + if err := app.waitForStakingTransactionConfirmation( &stkTxHash, - app.newBtcInclusionInfo(cmd.notifierTx), + cmd.parsedStakingTx.StakingOutput.PkScript, stakingParams.ConfirmationTimeBlocks, - ) - - ts, err := app.txTracker.GetTransaction(&stkTxHash) - if err != nil { - app.logger.WithError(err).Errorf("Error getting transaction state for tx %s", stkTxHash.String()) + bestBlockHeight, + ); err != nil { cmd.errChan <- err continue } - delData, resp, err := app.sendDelegationToBabylonTaskWithRetry(req, cmd.stakerAddr, ts) - if err != nil { - app.logger.WithFields(logrus.Fields{ - "btcTxHash": cmd.stakerAddr, - "err": err, - }).Debugf("Sending BTC delegation failed") + if err := app.txTracker.AddTransactionSentToBTC( + cmd.notifierTx.Tx, + uint32(cmd.parsedStakingTx.StakingOutputIdx), + cmd.parsedStakingTx.OpReturnData.StakingTime, + []*btcec.PublicKey{cmd.parsedStakingTx.OpReturnData.FinalityProviderPublicKey.PubKey}, + babylonPopToDBPop(cmd.pop), + cmd.stakerAddr, + ); err != nil { + app.logger.WithError(err).Info("err to set tx as sent on BTC") cmd.errChan <- err continue } - err = app.txTracker.SetTxSentToBabylon(&stkTxHash, resp.TxHash, delData.Ud.UnbondingTransaction, delData.Ud.UnbondingTxUnbondingTime) + // eventually tx is send to babylon + storedTx, err := app.waitForTrackedTransactionState(stkTxHash, proto.TransactionState_SENT_TO_BABYLON, time.Second, 20) if err != nil { utils.PushOrQuit( cmd.errChan, err, app.quit, ) - } else { - utils.PushOrQuit( - cmd.successChanTxHash, - resp.TxHash, - app.quit, - ) + app.logger.WithFields(logrus.Fields{ + "stakingTxHash": stkTxHash, + }).Debugf("BTC delegation waited for too long to become active, check the status manually") + continue } + + utils.PushOrQuit( + cmd.successChanTxHash, + storedTx.BtcDelegationTxHash, + app.quit, + ) app.logger.WithFields(logrus.Fields{ - "consumerBtcDelegationTxHash": resp.TxHash, + "consumerBtcDelegationTxHash": storedTx.BtcDelegationTxHash, }).Debugf("Sending BTC delegation was a success") case <-app.quit: return @@ -2053,6 +2032,26 @@ func (app *App) ListUnspentOutputs() ([]walletcontroller.Utxo, error) { return app.wc.ListOutputs(false) } +func (app *App) waitForTrackedTransactionState( + txHash chainhash.Hash, + minState proto.TransactionState, + interval time.Duration, + tries uint, +) (storedTx *stakerdb.StoredTransaction, err error) { + err = retry.Do(func() error { + storedTx, err = app.GetStoredTransaction(&txHash) + if err != nil { + return err + } + + if storedTx.State < minState { + return fmt.Errorf("txHash %s is at state %s, should be at least %s", txHash.String(), storedTx.State.String(), minState.String()) + } + return nil + }, retry.Attempts(tries), retry.Delay(interval)) + return storedTx, err +} + func (app *App) waitForSpendConfirmation(stakingTxHash chainhash.Hash, ev *notifier.ConfirmationEvent) { // check we are not shutting down select { diff --git a/stakerdb/trackedtranactionstore.go b/stakerdb/trackedtranactionstore.go index 177b5ab..c9da47d 100644 --- a/stakerdb/trackedtranactionstore.go +++ b/stakerdb/trackedtranactionstore.go @@ -125,10 +125,11 @@ type StoredTransaction struct { Pop *ProofOfPossession // Returning address as string, to avoid having to know how to decode address // which requires knowing the network we are on - StakerAddress string - State proto.TransactionState - Watched bool - UnbondingTxData *UnbondingStoreData + StakerAddress string + State proto.TransactionState + Watched bool + UnbondingTxData *UnbondingStoreData + BtcDelegationTxHash string } // StakingTxConfirmedOnBtc returns true only if staking transaction was sent and confirmed on bitcoin @@ -367,10 +368,11 @@ func protoTxToStoredTransaction(ttx *proto.TrackedTransaction) (*StoredTransacti BtcSigType: ttx.BtcSigType, BtcSigOverBabylonAddr: ttx.BtcSigOverBbnStakerAddr, }, - StakerAddress: ttx.StakerAddress, - State: ttx.State, - Watched: ttx.Watched, - UnbondingTxData: utd, + StakerAddress: ttx.StakerAddress, + State: ttx.State, + Watched: ttx.Watched, + UnbondingTxData: utd, + BtcDelegationTxHash: ttx.BtcDelegationTxHash, }, nil } From 28eae576b73cd67d5ce337e74726fc1067604698 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 22:59:36 -0300 Subject: [PATCH 29/38] fix: lint removed unused func --- staker/stakerapp.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 15278d5..fb19971 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -2341,16 +2341,3 @@ func (app *App) unlockAndCreatePop(stakerAddress btcutil.Address) (*cl.BabylonPo stakerAddress, ) } - -func checkConfirmationDepth(tipBlockHeight, txInclusionBlockHeight, confirmationTimeBlocks uint32) error { - if txInclusionBlockHeight >= tipBlockHeight { - return fmt.Errorf("inclusion block height: %d should be lower than current tip: %d", txInclusionBlockHeight, tipBlockHeight) - } - if (tipBlockHeight - txInclusionBlockHeight) < confirmationTimeBlocks { - return fmt.Errorf( - "BTC tx not deep enough, current tip: %d, tx inclusion height: %d, confirmations needed: %d", - tipBlockHeight, txInclusionBlockHeight, confirmationTimeBlocks, - ) - } - return nil -} From cce90a94bbd0b5448b73435972a757399e6b8a47 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 25 Nov 2024 23:16:29 -0300 Subject: [PATCH 30/38] chore: add goroutine to return the cmd request --- staker/stakerapp.go | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index fb19971..906b6f6 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -1548,28 +1548,30 @@ func (app *App) handleStakingCommands() { continue } - // eventually tx is send to babylon - storedTx, err := app.waitForTrackedTransactionState(stkTxHash, proto.TransactionState_SENT_TO_BABYLON, time.Second, 20) - if err != nil { + go func() { + // eventually tx is send to babylon and notifies the cmd request + storedTx, err := app.waitForTrackedTransactionState(stkTxHash, proto.TransactionState_SENT_TO_BABYLON, time.Second, 20) + if err != nil { + utils.PushOrQuit( + cmd.errChan, + err, + app.quit, + ) + app.logger.WithFields(logrus.Fields{ + "stakingTxHash": stkTxHash, + }).Debugf("BTC delegation waited for too long to become active, check the status manually") + return + } + utils.PushOrQuit( - cmd.errChan, - err, + cmd.successChanTxHash, + storedTx.BtcDelegationTxHash, app.quit, ) app.logger.WithFields(logrus.Fields{ - "stakingTxHash": stkTxHash, - }).Debugf("BTC delegation waited for too long to become active, check the status manually") - continue - } - - utils.PushOrQuit( - cmd.successChanTxHash, - storedTx.BtcDelegationTxHash, - app.quit, - ) - app.logger.WithFields(logrus.Fields{ - "consumerBtcDelegationTxHash": storedTx.BtcDelegationTxHash, - }).Debugf("Sending BTC delegation was a success") + "consumerBtcDelegationTxHash": storedTx.BtcDelegationTxHash, + }).Debugf("Sending BTC delegation was a success") + }() case <-app.quit: return } From 9f1f0ab7afa16b2fe4da330b273f2d3906d30571 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 27 Nov 2024 10:09:30 -0300 Subject: [PATCH 31/38] chore: address pr comment and simplified the stking tx migration --- itest/e2e_test.go | 2 + staker/stakerapp.go | 134 ++++++++++++++++++++++++++------------------ 2 files changed, 80 insertions(+), 56 deletions(-) diff --git a/itest/e2e_test.go b/itest/e2e_test.go index f527d26..7241cdb 100644 --- a/itest/e2e_test.go +++ b/itest/e2e_test.go @@ -895,6 +895,8 @@ func TestStakeFromPhase1(t *testing.T) { resFundRawStkTx, err := rpcBtc.FundRawTransaction(&tx, btcjson.FundRawTransactionOpts{ FeeRate: btcjson.Float64(0.02), + // by setting the ChangePosition to 1 we make sure that the staking output will be at index 0 + ChangePosition: btcjson.Int(1), }, btcjson.Bool(false)) require.NoError(t, err) require.NotNil(t, resFundRawStkTx) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 906b6f6..861ef96 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -1329,52 +1329,72 @@ func (app *App) handlePreApprovalCmd( stakingTx *wire.MsgTx, stakingOutputIdx uint32, ) (*chainhash.Hash, error) { + btcTxHash, _, err := app.handleSendDelegationRequest( + cmd.stakerAddress, + cmd.stakingTime, + cmd.requiredDepthOnBtcChain, + cmd.fpBtcPks, + cmd.pop, + stakingTx, + stakingOutputIdx, + nil, + ) + return btcTxHash, err +} + +func (app *App) handleSendDelegationRequest( + stakerAddress btcutil.Address, + stakingTime uint16, + requiredDepthOnBtcChain uint32, + fpBtcPks []*secp256k1.PublicKey, + pop *cl.BabylonPop, + stakingTx *wire.MsgTx, + stakingOutputIdx uint32, + inclusionInfo *inclusionInfo, +) (btcTxHash *chainhash.Hash, btcDelTxHash string, err error) { // just to pass to buildAndSendDelegation fakeStoredTx, err := stakerdb.CreateTrackedTransaction( stakingTx, stakingOutputIdx, - cmd.stakingTime, - cmd.fpBtcPks, - babylonPopToDBPop(cmd.pop), - cmd.stakerAddress, + stakingTime, + fpBtcPks, + babylonPopToDBPop(pop), + stakerAddress, ) - if err != nil { - return nil, err + return nil, btcDelTxHash, err } stakingTxHash := stakingTx.TxHash() - req := newSendDelegationRequest(&stakingTxHash, nil, cmd.requiredDepthOnBtcChain) - _, delegationData, err := app.buildAndSendDelegation( + req := newSendDelegationRequest(&stakingTxHash, inclusionInfo, requiredDepthOnBtcChain) + resp, delegationData, err := app.buildAndSendDelegation( req, - cmd.stakerAddress, + stakerAddress, fakeStoredTx, ) - if err != nil { - return nil, err + return nil, btcDelTxHash, err } err = app.txTracker.AddTransactionSentToBabylon( stakingTx, stakingOutputIdx, - cmd.stakingTime, - cmd.fpBtcPks, - babylonPopToDBPop(cmd.pop), - cmd.stakerAddress, + stakingTime, + fpBtcPks, + babylonPopToDBPop(pop), + stakerAddress, delegationData.Ud.UnbondingTransaction, delegationData.Ud.UnbondingTxUnbondingTime, ) - if err != nil { - return nil, err + return nil, btcDelTxHash, err } app.wg.Add(1) go app.checkForUnbondingTxSignaturesOnBabylon(&stakingTxHash) - return &stakingTxHash, nil + return &stakingTxHash, resp.TxHash, nil } func (app *App) handlePostApprovalCmd( @@ -1518,60 +1538,49 @@ func (app *App) handleStakingCommands() { case cmd := <-app.migrateStakingCmd: stkTxHash := cmd.notifierTx.Tx.TxHash() - stakingParams, err := app.babylonClient.Params() + stkParams, err := app.babylonClient.Params() if err != nil { cmd.errChan <- err continue } bestBlockHeight := app.currentBestBlockHeight.Load() - if err := app.waitForStakingTransactionConfirmation( - &stkTxHash, - cmd.parsedStakingTx.StakingOutput.PkScript, - stakingParams.ConfirmationTimeBlocks, - bestBlockHeight, - ); err != nil { + // check confirmation is deep enough + if err := checkConfirmationDepth(bestBlockHeight, cmd.notifierTx.BlockHeight, stkParams.ConfirmationTimeBlocks); err != nil { cmd.errChan <- err continue } - if err := app.txTracker.AddTransactionSentToBTC( - cmd.notifierTx.Tx, - uint32(cmd.parsedStakingTx.StakingOutputIdx), + _, btcDelTxHash, err := app.handleSendDelegationRequest( + cmd.stakerAddr, cmd.parsedStakingTx.OpReturnData.StakingTime, + stkParams.ConfirmationTimeBlocks, []*btcec.PublicKey{cmd.parsedStakingTx.OpReturnData.FinalityProviderPublicKey.PubKey}, - babylonPopToDBPop(cmd.pop), - cmd.stakerAddr, - ); err != nil { - app.logger.WithError(err).Info("err to set tx as sent on BTC") - cmd.errChan <- err - continue - } - - go func() { - // eventually tx is send to babylon and notifies the cmd request - storedTx, err := app.waitForTrackedTransactionState(stkTxHash, proto.TransactionState_SENT_TO_BABYLON, time.Second, 20) - if err != nil { - utils.PushOrQuit( - cmd.errChan, - err, - app.quit, - ) - app.logger.WithFields(logrus.Fields{ - "stakingTxHash": stkTxHash, - }).Debugf("BTC delegation waited for too long to become active, check the status manually") - return - } - + cmd.pop, + cmd.notifierTx.Tx, + uint32(cmd.parsedStakingTx.StakingOutputIdx), + app.newBtcInclusionInfo(cmd.notifierTx), + ) + if err != nil { utils.PushOrQuit( - cmd.successChanTxHash, - storedTx.BtcDelegationTxHash, + cmd.errChan, + err, app.quit, ) app.logger.WithFields(logrus.Fields{ - "consumerBtcDelegationTxHash": storedTx.BtcDelegationTxHash, - }).Debugf("Sending BTC delegation was a success") - }() + "stakingTxHash": stkTxHash, + }).Debugf("BTC delegation waited for too long to become active, check the status manually") + return + } + + utils.PushOrQuit( + cmd.successChanTxHash, + btcDelTxHash, + app.quit, + ) + app.logger.WithFields(logrus.Fields{ + "consumerBtcDelegationTxHash": btcDelTxHash, + }).Debugf("Sending BTC delegation was a success") case <-app.quit: return } @@ -2343,3 +2352,16 @@ func (app *App) unlockAndCreatePop(stakerAddress btcutil.Address) (*cl.BabylonPo stakerAddress, ) } + +func checkConfirmationDepth(tipBlockHeight, txInclusionBlockHeight, confirmationTimeBlocks uint32) error { + if txInclusionBlockHeight >= tipBlockHeight { + return fmt.Errorf("inclusion block height: %d should be lower than current tip: %d", txInclusionBlockHeight, tipBlockHeight) + } + if (tipBlockHeight - txInclusionBlockHeight) < confirmationTimeBlocks { + return fmt.Errorf( + "BTC tx not deep enough, current tip: %d, tx inclusion height: %d, confirmations needed: %d", + tipBlockHeight, txInclusionBlockHeight, confirmationTimeBlocks, + ) + } + return nil +} From d0a1c863f1391ff5f91d04e834a26933ff524b63 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 27 Nov 2024 10:12:53 -0300 Subject: [PATCH 32/38] chore: add btc del tx hash to delegation sent to babylon --- staker/stakerapp.go | 1 + stakerdb/trackedtranactionstore.go | 2 ++ stakerdb/trackedtransactionstore_test.go | 1 + 3 files changed, 4 insertions(+) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 861ef96..2e5108e 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -1386,6 +1386,7 @@ func (app *App) handleSendDelegationRequest( stakerAddress, delegationData.Ud.UnbondingTransaction, delegationData.Ud.UnbondingTxUnbondingTime, + resp.TxHash, ) if err != nil { return nil, btcDelTxHash, err diff --git a/stakerdb/trackedtranactionstore.go b/stakerdb/trackedtranactionstore.go index c9da47d..444923b 100644 --- a/stakerdb/trackedtranactionstore.go +++ b/stakerdb/trackedtranactionstore.go @@ -677,6 +677,7 @@ func (c *TrackedTransactionStore) AddTransactionSentToBabylon( stakerAddress btcutil.Address, unbondingTx *wire.MsgTx, unbondingTime uint16, + btcDelTxHash string, ) error { txHash := btcTx.TxHash() txHashBytes := txHash[:] @@ -720,6 +721,7 @@ func (c *TrackedTransactionStore) AddTransactionSentToBabylon( State: proto.TransactionState_SENT_TO_BABYLON, Watched: false, UnbondingTxData: update, + BtcDelegationTxHash: btcDelTxHash, } inputData, err := getInputData(btcTx) diff --git a/stakerdb/trackedtransactionstore_test.go b/stakerdb/trackedtransactionstore_test.go index 6e00689..e6b8b11 100644 --- a/stakerdb/trackedtransactionstore_test.go +++ b/stakerdb/trackedtransactionstore_test.go @@ -413,6 +413,7 @@ func FuzzTrackInputs(f *testing.F) { stakerAddr, randomUnbondingTx, unbodningTime, + "txHashBtcDelegation", ) require.NoError(t, err) } From 5e096b1eadc7ef7d7a3f0450deb7d11b323a929c Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 27 Nov 2024 10:14:42 -0300 Subject: [PATCH 33/38] fix: lint removed unused func --- staker/stakerapp.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 2e5108e..3236865 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -2044,26 +2044,6 @@ func (app *App) ListUnspentOutputs() ([]walletcontroller.Utxo, error) { return app.wc.ListOutputs(false) } -func (app *App) waitForTrackedTransactionState( - txHash chainhash.Hash, - minState proto.TransactionState, - interval time.Duration, - tries uint, -) (storedTx *stakerdb.StoredTransaction, err error) { - err = retry.Do(func() error { - storedTx, err = app.GetStoredTransaction(&txHash) - if err != nil { - return err - } - - if storedTx.State < minState { - return fmt.Errorf("txHash %s is at state %s, should be at least %s", txHash.String(), storedTx.State.String(), minState.String()) - } - return nil - }, retry.Attempts(tries), retry.Delay(interval)) - return storedTx, err -} - func (app *App) waitForSpendConfirmation(stakingTxHash chainhash.Hash, ev *notifier.ConfirmationEvent) { // check we are not shutting down select { From a97cd8c8ff17b6fe3148445651574b9a278ce506 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 28 Nov 2024 09:23:56 -0300 Subject: [PATCH 34/38] chore: removed txInclusionHeightFlag flag and getting the block height by querying the btc --- cmd/stakercli/daemon/daemoncommands.go | 15 +++++++------- staker/stakerapp.go | 20 +++++++++++++++++++ stakerservice/client/rpcclient.go | 16 +++++++++++++++ stakerservice/service.go | 27 ++++++++++++++++++++++++-- stakerservice/stakerdresponses.go | 7 +++++++ walletcontroller/client.go | 15 +++++++++----- walletcontroller/interface.go | 3 +++ 7 files changed, 88 insertions(+), 15 deletions(-) diff --git a/cmd/stakercli/daemon/daemoncommands.go b/cmd/stakercli/daemon/daemoncommands.go index f09bb54..c2f3fe3 100644 --- a/cmd/stakercli/daemon/daemoncommands.go +++ b/cmd/stakercli/daemon/daemoncommands.go @@ -161,11 +161,6 @@ var stakeFromPhase1Cmd = cli.Command{ Usage: "BTC address of the staker in hex", Required: true, }, - cli.Uint64Flag{ - Name: txInclusionHeightFlag, - Usage: "Expected BTC height at which transaction was included. This value is important to choose correct global parameters for transaction", - Required: true, - }, }, Action: stakeFromPhase1TxBTC, } @@ -399,10 +394,14 @@ func stakeFromPhase1TxBTC(ctx *cli.Context) error { return fmt.Errorf("error parsing file %s: %w", inputGlobalParamsFilePath, err) } - stakingTxInclusionHeight := ctx.Uint64(txInclusionHeightFlag) - paramsForHeight := globalParams.GetVersionedGlobalParamsByHeight(stakingTxInclusionHeight) + resp, err := client.BtcTxDetails(sctx, stakingTransactionHash) + if err != nil { + return fmt.Errorf("error to get btc tx and block data from staking tx %s: %w", stakingTransactionHash, err) + } + + paramsForHeight := globalParams.GetVersionedGlobalParamsByHeight(uint64(resp.Blk.Height)) if paramsForHeight == nil { - return fmt.Errorf("error getting param version from global params %s with height %d", inputGlobalParamsFilePath, stakingTxInclusionHeight) + return fmt.Errorf("error getting param version from global params %s with height %d", inputGlobalParamsFilePath, resp.Blk.Height) } stakerAddress := ctx.String(stakerAddressFlag) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 3236865..52ef828 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -24,6 +24,7 @@ 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" @@ -2334,6 +2335,25 @@ func (app *App) unlockAndCreatePop(stakerAddress btcutil.Address) (*cl.BabylonPo ) } +func (app *App) BtcTxAndBlock(txHash *chainhash.Hash) (*btcjson.TxRawResult, *btcjson.GetBlockHeaderVerboseResult, error) { + tx, err := app.wc.TxVerbose(txHash) + if err != nil { + return nil, nil, err + } + + blockHash, err := chainhash.NewHashFromStr(tx.BlockHash) + if err != nil { + return nil, nil, err + } + + blk, err := app.wc.BlockHeaderVerbose(blockHash) + if err != nil { + return nil, nil, err + } + + return tx, blk, nil +} + func checkConfirmationDepth(tipBlockHeight, txInclusionBlockHeight, confirmationTimeBlocks uint32) error { if txInclusionBlockHeight >= tipBlockHeight { return fmt.Errorf("inclusion block height: %d should be lower than current tip: %d", txInclusionBlockHeight, tipBlockHeight) diff --git a/stakerservice/client/rpcclient.go b/stakerservice/client/rpcclient.go index a4f4581..d42193c 100644 --- a/stakerservice/client/rpcclient.go +++ b/stakerservice/client/rpcclient.go @@ -110,6 +110,22 @@ func (c *StakerServiceJSONRPCClient) BtcDelegationFromBtcStakingTx( return result, nil } +func (c *StakerServiceJSONRPCClient) BtcTxDetails( + ctx context.Context, + txHash string, +) (*service.BtcTxAndBlockResponse, error) { + result := new(service.BtcTxAndBlockResponse) + + params := make(map[string]interface{}) + params["txHashStr"] = txHash + + _, err := c.client.Call(ctx, "btc_tx_blk_details", params, result) + if err != nil { + return nil, err + } + return result, nil +} + func parseCovenantsPubKeyToHex(pks ...*btcec.PublicKey) []string { pksHex := make([]string, len(pks)) for i, pk := range pks { diff --git a/stakerservice/service.go b/stakerservice/service.go index 6225414..ac3dc73 100644 --- a/stakerservice/service.go +++ b/stakerservice/service.go @@ -191,8 +191,30 @@ func parseCovenantPubKeyFromHex(pkStr string) (*btcec.PublicKey, error) { return pk, nil } -func (s *StakerService) stakingDetails(_ *rpctypes.Context, - stakingTxHash string) (*StakingDetails, error) { +func (s *StakerService) btcTxBlkDetails( + _ *rpctypes.Context, + txHashStr string, +) (*BtcTxAndBlockResponse, error) { + txHash, err := chainhash.NewHashFromStr(txHashStr) + if err != nil { + return nil, err + } + + tx, blk, err := s.staker.BtcTxAndBlock(txHash) + if err != nil { + return nil, err + } + + return &BtcTxAndBlockResponse{ + Tx: tx, + Blk: blk, + }, nil +} + +func (s *StakerService) stakingDetails( + _ *rpctypes.Context, + stakingTxHash string, +) (*StakingDetails, error) { txHash, err := chainhash.NewHashFromStr(stakingTxHash) if err != nil { return nil, err @@ -619,6 +641,7 @@ func (s *StakerService) GetRoutes() RoutesMap { "list_staking_transactions": rpc.NewRPCFunc(s.listStakingTransactions, "offset,limit"), "unbond_staking": rpc.NewRPCFunc(s.unbondStaking, "stakingTxHash"), "withdrawable_transactions": rpc.NewRPCFunc(s.withdrawableTransactions, "offset,limit"), + "btc_tx_blk_details": rpc.NewRPCFunc(s.btcTxBlkDetails, "txHashStr"), // watch api "watch_staking_tx": rpc.NewRPCFunc(s.watchStaking, "stakingTx,stakingTime,stakingValue,stakerBtcPk,fpBtcPks,slashingTx,slashingTxSig,stakerBabylonAddr,stakerAddress,stakerBtcSig,unbondingTx,slashUnbondingTx,slashUnbondingTxSig,unbondingTime,popType"), diff --git a/stakerservice/stakerdresponses.go b/stakerservice/stakerdresponses.go index a8e8486..ac52fbb 100644 --- a/stakerservice/stakerdresponses.go +++ b/stakerservice/stakerdresponses.go @@ -1,5 +1,7 @@ package stakerservice +import "github.com/btcsuite/btcd/btcjson" + type ResultHealth struct{} type ResultBtcDelegationFromBtcStakingTx struct { @@ -57,3 +59,8 @@ type WithdrawableTransactionsResponse struct { LastWithdrawableTransactionIndex string `json:"last_transaction_index"` TotalTransactionCount string `json:"total_transaction_count"` } + +type BtcTxAndBlockResponse struct { + Tx *btcjson.TxRawResult `json:"tx"` + Blk *btcjson.GetBlockHeaderVerboseResult `json:"blk"` +} diff --git a/walletcontroller/client.go b/walletcontroller/client.go index 8dc0833..acae62e 100644 --- a/walletcontroller/client.go +++ b/walletcontroller/client.go @@ -270,12 +270,17 @@ func (w *RPCWalletController) getTxDetails(req notifier.ConfRequest, msg string) // 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, err - } + return w.Client.GetRawTransaction(txHash) +} + +// TxVerbose returns the raw transaction verbose based on the transaction hash +func (w *RPCWalletController) TxVerbose(txHash *chainhash.Hash) (*btcjson.TxRawResult, error) { + return w.Client.GetRawTransactionVerbose(txHash) +} - return rawTx, nil +// BlockHeaderVerbose returns the block header data based on the block hash +func (w *RPCWalletController) BlockHeaderVerbose(blockHash *chainhash.Hash) (*btcjson.GetBlockHeaderVerboseResult, error) { + return w.Client.GetBlockHeaderVerbose(blockHash) } // Fetch info about transaction from mempool or blockchain, requires node to have enabled transaction index diff --git a/walletcontroller/interface.go b/walletcontroller/interface.go index 2dcd151..a57a351 100644 --- a/walletcontroller/interface.go +++ b/walletcontroller/interface.go @@ -4,6 +4,7 @@ import ( staking "github.com/babylonlabs-io/babylon/btcstaking" "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" @@ -71,6 +72,8 @@ type WalletController interface { ListOutputs(onlySpendable bool) ([]Utxo, error) TxDetails(txHash *chainhash.Hash, pkScript []byte) (*notifier.TxConfirmation, TxStatus, error) Tx(txHash *chainhash.Hash) (*btcutil.Tx, error) + TxVerbose(txHash *chainhash.Hash) (*btcjson.TxRawResult, error) + BlockHeaderVerbose(blockHash *chainhash.Hash) (*btcjson.GetBlockHeaderVerboseResult, error) SignBip322NativeSegwit(msg []byte, address btcutil.Address) (wire.TxWitness, error) // SignOneInputTaprootSpendingTransaction signs transactions with one taproot input that // uses script spending path. From 0a2eb546e889ee9955c5b2a9a69bd971bcde3417 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 28 Nov 2024 09:28:37 -0300 Subject: [PATCH 35/38] chore: rename BtcDelegationTxHash to BabylonBTCDelegationTxHash --- proto/transaction.pb.go | 47 +++++++++++++++--------------- proto/transaction.proto | 2 +- staker/events.go | 8 ++--- staker/stakerapp.go | 12 ++++---- stakerdb/trackedtranactionstore.go | 26 ++++++++--------- stakerservice/service.go | 4 +-- stakerservice/stakerdresponses.go | 2 +- 7 files changed, 51 insertions(+), 50 deletions(-) diff --git a/proto/transaction.pb.go b/proto/transaction.pb.go index e8836bc..5323319 100644 --- a/proto/transaction.pb.go +++ b/proto/transaction.pb.go @@ -385,8 +385,8 @@ type TrackedTransaction struct { State TransactionState `protobuf:"varint,10,opt,name=state,proto3,enum=proto.TransactionState" json:"state,omitempty"` Watched bool `protobuf:"varint,11,opt,name=watched,proto3" json:"watched,omitempty"` // this data is only filled if tracked transactions state is >= SENT_TO_BABYLON - UnbondingTxData *UnbondingTxData `protobuf:"bytes,12,opt,name=unbonding_tx_data,json=unbondingTxData,proto3" json:"unbonding_tx_data,omitempty"` - BtcDelegationTxHash string `protobuf:"bytes,13,opt,name=btcDelegationTxHash,proto3" json:"btcDelegationTxHash,omitempty"` + UnbondingTxData *UnbondingTxData `protobuf:"bytes,12,opt,name=unbonding_tx_data,json=unbondingTxData,proto3" json:"unbonding_tx_data,omitempty"` + BabylonBTCDelegationTxHash string `protobuf:"bytes,13,opt,name=babylonBTCDelegationTxHash,proto3" json:"babylonBTCDelegationTxHash,omitempty"` } func (x *TrackedTransaction) Reset() { @@ -503,9 +503,9 @@ func (x *TrackedTransaction) GetUnbondingTxData() *UnbondingTxData { return nil } -func (x *TrackedTransaction) GetBtcDelegationTxHash() string { +func (x *TrackedTransaction) GetBabylonBTCDelegationTxHash() string { if x != nil { - return x.BtcDelegationTxHash + return x.BabylonBTCDelegationTxHash } return "" } @@ -572,7 +572,7 @@ var file_transaction_proto_rawDesc = []byte{ 0x42, 0x54, 0x43, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x1e, 0x75, 0x6e, 0x62, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x42, 0x74, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, - 0x6e, 0x66, 0x6f, 0x22, 0xb6, 0x05, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x54, + 0x6e, 0x66, 0x6f, 0x22, 0xc4, 0x05, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x74, 0x72, 0x61, @@ -612,24 +612,25 @@ var file_transaction_proto_rawDesc = []byte{ 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x6e, 0x62, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0f, 0x75, 0x6e, 0x62, 0x6f, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x44, 0x61, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x13, 0x62, 0x74, - 0x63, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x78, 0x48, 0x61, 0x73, - 0x68, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x62, 0x74, 0x63, 0x44, 0x65, 0x6c, 0x65, - 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x2a, 0xa5, 0x01, 0x0a, - 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, 0x42, 0x54, 0x43, - 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x5f, - 0x4f, 0x4e, 0x5f, 0x42, 0x54, 0x43, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x4e, 0x54, - 0x5f, 0x54, 0x4f, 0x5f, 0x42, 0x41, 0x42, 0x59, 0x4c, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x0c, 0x0a, - 0x08, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x44, - 0x45, 0x4c, 0x45, 0x47, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, - 0x10, 0x04, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x42, 0x4f, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, - 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x4e, 0x5f, 0x42, 0x54, 0x43, - 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x50, 0x45, 0x4e, 0x54, 0x5f, 0x4f, 0x4e, 0x5f, 0x42, - 0x54, 0x43, 0x10, 0x06, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x62, 0x61, 0x62, 0x79, 0x6c, 0x6f, 0x6e, 0x6c, 0x61, 0x62, 0x73, 0x2d, 0x69, - 0x6f, 0x2f, 0x62, 0x74, 0x63, 0x2d, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x44, 0x61, 0x74, 0x61, 0x12, 0x3e, 0x0a, 0x1a, 0x62, 0x61, + 0x62, 0x79, 0x6c, 0x6f, 0x6e, 0x42, 0x54, 0x43, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, + 0x62, 0x61, 0x62, 0x79, 0x6c, 0x6f, 0x6e, 0x42, 0x54, 0x43, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x2a, 0xa5, 0x01, 0x0a, 0x10, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x0f, 0x0a, 0x0b, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, 0x42, 0x54, 0x43, 0x10, 0x00, + 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x4e, + 0x5f, 0x42, 0x54, 0x43, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x4e, 0x54, 0x5f, 0x54, + 0x4f, 0x5f, 0x42, 0x41, 0x42, 0x59, 0x4c, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x56, + 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x45, 0x4c, + 0x45, 0x47, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x04, + 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x42, 0x4f, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x43, 0x4f, + 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x4e, 0x5f, 0x42, 0x54, 0x43, 0x10, 0x05, + 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x50, 0x45, 0x4e, 0x54, 0x5f, 0x4f, 0x4e, 0x5f, 0x42, 0x54, 0x43, + 0x10, 0x06, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x62, 0x61, 0x62, 0x79, 0x6c, 0x6f, 0x6e, 0x6c, 0x61, 0x62, 0x73, 0x2d, 0x69, 0x6f, 0x2f, + 0x62, 0x74, 0x63, 0x2d, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/transaction.proto b/proto/transaction.proto index d2115d1..22ebd95 100644 --- a/proto/transaction.proto +++ b/proto/transaction.proto @@ -67,5 +67,5 @@ message TrackedTransaction { bool watched = 11; // this data is only filled if tracked transactions state is >= SENT_TO_BABYLON UnbondingTxData unbonding_tx_data = 12; - string btcDelegationTxHash = 13; + string babylonBTCDelegationTxHash = 13; } diff --git a/staker/events.go b/staker/events.go index fa12cd1..86e7dc2 100644 --- a/staker/events.go +++ b/staker/events.go @@ -40,10 +40,10 @@ func (event *stakingTxBtcConfirmedEvent) EventDesc() string { } type delegationSubmittedToBabylonEvent struct { - stakingTxHash chainhash.Hash - btcDelegationTxHash string - unbondingTx *wire.MsgTx - unbondingTime uint16 + stakingTxHash chainhash.Hash + babylonBTCDelegationTxHash string + unbondingTx *wire.MsgTx + unbondingTime uint16 } func (event *delegationSubmittedToBabylonEvent) EventID() chainhash.Hash { diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 52ef828..bc3bc9c 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -521,7 +521,7 @@ func (app *App) SendPhase1Transaction( tag []byte, covenantPks []*secp256k1.PublicKey, covenantQuorum uint32, -) (btcDelegationTxHash string, err error) { +) (babylonBTCDelegationTxHash string, err error) { // check we are not shutting down select { case <-app.quit: @@ -1272,10 +1272,10 @@ func (app *App) sendDelegationToBabylonTask( // report success with the values we sent to Babylon ev := &delegationSubmittedToBabylonEvent{ - stakingTxHash: req.btcTxHash, - btcDelegationTxHash: delegationTxResp.TxHash, - unbondingTx: delegationData.Ud.UnbondingTransaction, - unbondingTime: delegationData.Ud.UnbondingTxUnbondingTime, + stakingTxHash: req.btcTxHash, + babylonBTCDelegationTxHash: delegationTxResp.TxHash, + unbondingTx: delegationData.Ud.UnbondingTransaction, + unbondingTime: delegationData.Ud.UnbondingTxUnbondingTime, } utils.PushOrQuit[*delegationSubmittedToBabylonEvent]( @@ -1634,7 +1634,7 @@ func (app *App) handleStakingEvents() { case ev := <-app.delegationSubmittedToBabylonEvChan: app.logStakingEventReceived(ev) - if err := app.txTracker.SetTxSentToBabylon(&ev.stakingTxHash, ev.btcDelegationTxHash, ev.unbondingTx, ev.unbondingTime); err != nil { + if err := app.txTracker.SetTxSentToBabylon(&ev.stakingTxHash, ev.babylonBTCDelegationTxHash, ev.unbondingTx, ev.unbondingTime); err != nil { // TODO: handle this error somehow, it means we received confirmation for tx which we do not store // which is seems like programming error. Maybe panic? app.logger.Fatalf("Error setting state for tx %s: %s", ev.stakingTxHash, err) diff --git a/stakerdb/trackedtranactionstore.go b/stakerdb/trackedtranactionstore.go index 444923b..f5ddef5 100644 --- a/stakerdb/trackedtranactionstore.go +++ b/stakerdb/trackedtranactionstore.go @@ -125,11 +125,11 @@ type StoredTransaction struct { Pop *ProofOfPossession // Returning address as string, to avoid having to know how to decode address // which requires knowing the network we are on - StakerAddress string - State proto.TransactionState - Watched bool - UnbondingTxData *UnbondingStoreData - BtcDelegationTxHash string + StakerAddress string + State proto.TransactionState + Watched bool + UnbondingTxData *UnbondingStoreData + BabylonBTCDelegationTxHash string } // StakingTxConfirmedOnBtc returns true only if staking transaction was sent and confirmed on bitcoin @@ -368,11 +368,11 @@ func protoTxToStoredTransaction(ttx *proto.TrackedTransaction) (*StoredTransacti BtcSigType: ttx.BtcSigType, BtcSigOverBabylonAddr: ttx.BtcSigOverBbnStakerAddr, }, - StakerAddress: ttx.StakerAddress, - State: ttx.State, - Watched: ttx.Watched, - UnbondingTxData: utd, - BtcDelegationTxHash: ttx.BtcDelegationTxHash, + StakerAddress: ttx.StakerAddress, + State: ttx.State, + Watched: ttx.Watched, + UnbondingTxData: utd, + BabylonBTCDelegationTxHash: ttx.BabylonBTCDelegationTxHash, }, nil } @@ -721,7 +721,7 @@ func (c *TrackedTransactionStore) AddTransactionSentToBabylon( State: proto.TransactionState_SENT_TO_BABYLON, Watched: false, UnbondingTxData: update, - BtcDelegationTxHash: btcDelTxHash, + BabylonBTCDelegationTxHash: btcDelTxHash, } inputData, err := getInputData(btcTx) @@ -978,7 +978,7 @@ func (c *TrackedTransactionStore) SetTxConfirmed( func (c *TrackedTransactionStore) SetTxSentToBabylon( txHash *chainhash.Hash, - btcDelegationTxHash string, + babylonBTCDelegationTxHash string, unbondingTx *wire.MsgTx, unbondingTime uint16, ) error { @@ -994,7 +994,7 @@ func (c *TrackedTransactionStore) SetTxSentToBabylon( tx.State = proto.TransactionState_SENT_TO_BABYLON tx.UnbondingTxData = update - tx.BtcDelegationTxHash = btcDelegationTxHash + tx.BabylonBTCDelegationTxHash = babylonBTCDelegationTxHash return nil } diff --git a/stakerservice/service.go b/stakerservice/service.go index ac3dc73..78973c5 100644 --- a/stakerservice/service.go +++ b/stakerservice/service.go @@ -151,14 +151,14 @@ func (s *StakerService) btcDelegationFromBtcStakingTx( return nil, err } - btcDelegationTxHash, err := s.staker.SendPhase1Transaction(stakerAddr, stkTxHash, tag, covenantPks, covenantQuorum) + babylonBTCDelegationTxHash, err := s.staker.SendPhase1Transaction(stakerAddr, stkTxHash, tag, covenantPks, covenantQuorum) if err != nil { s.logger.WithError(err).Info("err to send phase 1 tx") return nil, err } return &ResultBtcDelegationFromBtcStakingTx{ - TxHashDelegationBTC: btcDelegationTxHash, + BabylonBTCDelegationTxHash: babylonBTCDelegationTxHash, }, nil } diff --git a/stakerservice/stakerdresponses.go b/stakerservice/stakerdresponses.go index ac52fbb..d8171aa 100644 --- a/stakerservice/stakerdresponses.go +++ b/stakerservice/stakerdresponses.go @@ -5,7 +5,7 @@ import "github.com/btcsuite/btcd/btcjson" type ResultHealth struct{} type ResultBtcDelegationFromBtcStakingTx struct { - TxHashDelegationBTC string `json:"tx_hash_delegation_btc"` + BabylonBTCDelegationTxHash string `json:"babylon_btc_delegation_tx_hash"` } type ResultStake struct { From 2c7964d955288efcb9e2a3606fa8dbb39fe5a21b Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 28 Nov 2024 09:31:18 -0300 Subject: [PATCH 36/38] chore: update error msg --- staker/stakerapp.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index bc3bc9c..06a9421 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -1566,12 +1566,12 @@ func (app *App) handleStakingCommands() { if err != nil { utils.PushOrQuit( cmd.errChan, - err, + fmt.Errorf("sending tx to babylon failed: %w", err), app.quit, ) app.logger.WithFields(logrus.Fields{ "stakingTxHash": stkTxHash, - }).Debugf("BTC delegation waited for too long to become active, check the status manually") + }).Errorf("BTC delegation transaction failed: %w", err) return } From 325cc3ebed3f1dbe52bda2093e0a097d18b6b360 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 28 Nov 2024 09:35:24 -0300 Subject: [PATCH 37/38] fix: lint --- staker/stakerapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 06a9421..79ffa46 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -1571,7 +1571,7 @@ func (app *App) handleStakingCommands() { ) app.logger.WithFields(logrus.Fields{ "stakingTxHash": stkTxHash, - }).Errorf("BTC delegation transaction failed: %w", err) + }).WithError(err).Error("BTC delegation transaction failed") return } From f63aab844efdca9f27d2d7f53bb86485fdcba00c Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Fri, 29 Nov 2024 00:11:47 -0300 Subject: [PATCH 38/38] chore: add back tx-inclusion-height as optional, if not set queries the tx hash --- cmd/stakercli/daemon/daemoncommands.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cmd/stakercli/daemon/daemoncommands.go b/cmd/stakercli/daemon/daemoncommands.go index c2f3fe3..67e38e7 100644 --- a/cmd/stakercli/daemon/daemoncommands.go +++ b/cmd/stakercli/daemon/daemoncommands.go @@ -161,6 +161,10 @@ var stakeFromPhase1Cmd = cli.Command{ Usage: "BTC address of the staker in hex", Required: true, }, + cli.Uint64Flag{ + Name: txInclusionHeightFlag, + Usage: "Expected BTC height at which transaction was included. This value is important to choose correct global parameters for transaction, if set doesn't query bitcoin to get the block height from txHash", + }, }, Action: stakeFromPhase1TxBTC, } @@ -394,14 +398,19 @@ func stakeFromPhase1TxBTC(ctx *cli.Context) error { return fmt.Errorf("error parsing file %s: %w", inputGlobalParamsFilePath, err) } - resp, err := client.BtcTxDetails(sctx, stakingTransactionHash) - if err != nil { - return fmt.Errorf("error to get btc tx and block data from staking tx %s: %w", stakingTransactionHash, err) + blockHeighTxInclusion := ctx.Uint64(txInclusionHeightFlag) + if blockHeighTxInclusion == 0 { + resp, err := client.BtcTxDetails(sctx, stakingTransactionHash) + if err != nil { + return fmt.Errorf("error to get btc tx and block data from staking tx %s: %w", stakingTransactionHash, err) + } + + blockHeighTxInclusion = uint64(resp.Blk.Height) } - paramsForHeight := globalParams.GetVersionedGlobalParamsByHeight(uint64(resp.Blk.Height)) + paramsForHeight := globalParams.GetVersionedGlobalParamsByHeight(blockHeighTxInclusion) if paramsForHeight == nil { - return fmt.Errorf("error getting param version from global params %s with height %d", inputGlobalParamsFilePath, resp.Blk.Height) + return fmt.Errorf("error getting param version from global params %s with height %d", inputGlobalParamsFilePath, blockHeighTxInclusion) } stakerAddress := ctx.String(stakerAddressFlag)