Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new command phase1 stake to BTC delegation #90

Merged
merged 42 commits into from
Nov 29, 2024
Merged
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
580adf8
chore: init command draft
RafilxTenfen Oct 16, 2024
db1692a
chore: add get tx details from tx hash only
RafilxTenfen Oct 16, 2024
d76b933
chore: add initial impl to btc staking from phase 1
RafilxTenfen Nov 5, 2024
7a53e09
Merge branch 'main' of github.com:babylonlabs-io/btc-staker into rafi…
RafilxTenfen Nov 18, 2024
ea40b9f
feat: add cli to create babylon create btc delegation from staking tx
RafilxTenfen Nov 20, 2024
0374d0b
chore: add #90 to changelog
RafilxTenfen Nov 20, 2024
b41aa7a
chore: add stake command to CLI
RafilxTenfen Nov 20, 2024
90d7a95
fix: lint
RafilxTenfen Nov 20, 2024
0fe2886
chore: move cli and manager to separated file
RafilxTenfen Nov 20, 2024
2021de6
chore: manager split btc data
RafilxTenfen Nov 20, 2024
17ebc5d
chore: split func to generate covenant pk
RafilxTenfen Nov 20, 2024
35ec159
chore: add staking of phase 1 test
RafilxTenfen Nov 20, 2024
81b947b
fix: lint
RafilxTenfen Nov 21, 2024
7c745dd
trytest: stuck at including finality provider to running chain
RafilxTenfen Nov 21, 2024
323cdcf
chore: add last err to debug
RafilxTenfen Nov 21, 2024
f57a446
pass json serializable params
KonradStaniec Nov 22, 2024
916d27d
chore: refactory send transaction to phase 1
RafilxTenfen Nov 22, 2024
5dfe472
Merge branch 'rafilx/migrate-stk-to-phase2' of github.com:babylonlabs…
RafilxTenfen Nov 22, 2024
75c02d3
Merge branch 'main' of github.com:babylonlabs-io/btc-staker into rafi…
RafilxTenfen Nov 22, 2024
fdc155e
chore: fix comment
RafilxTenfen Nov 22, 2024
15cb74d
chore: address PR comments
RafilxTenfen Nov 25, 2024
ffff690
chore: rollback rand
RafilxTenfen Nov 25, 2024
503c8a5
chore: use rand previous created
RafilxTenfen Nov 25, 2024
4e7844f
Merge branch 'main' of github.com:babylonlabs-io/btc-staker into rafi…
RafilxTenfen Nov 25, 2024
86b1da9
chore: add possible responde of BTC delegation to consumer
RafilxTenfen Nov 25, 2024
cb95513
chore: try to refactory to receive BTC delegation tx hash
RafilxTenfen Nov 25, 2024
6bea332
chore: moved to use command
RafilxTenfen Nov 25, 2024
0f0e989
chore: add todo
RafilxTenfen Nov 25, 2024
8be6baf
chore: fix lint and allow nonamedreturns
RafilxTenfen Nov 26, 2024
9958f45
chore: remove go routine of mine empty blocks
RafilxTenfen Nov 26, 2024
417d18f
chore: add new field to transaction tracked of tx hash of btc delegation
RafilxTenfen Nov 26, 2024
5eb2542
tryfix: get btc delegation tx hash
RafilxTenfen Nov 26, 2024
28eae57
fix: lint removed unused func
RafilxTenfen Nov 26, 2024
cce90a9
chore: add goroutine to return the cmd request
RafilxTenfen Nov 26, 2024
9f1f0ab
chore: address pr comment and simplified the stking tx migration
RafilxTenfen Nov 27, 2024
d0a1c86
chore: add btc del tx hash to delegation sent to babylon
RafilxTenfen Nov 27, 2024
5e096b1
fix: lint removed unused func
RafilxTenfen Nov 27, 2024
a97cd8c
chore: removed txInclusionHeightFlag flag and getting the block heigh…
RafilxTenfen Nov 28, 2024
0a2eb54
chore: rename BtcDelegationTxHash to BabylonBTCDelegationTxHash
RafilxTenfen Nov 28, 2024
2c7964d
chore: update error msg
RafilxTenfen Nov 28, 2024
325cc3e
fix: lint
RafilxTenfen Nov 28, 2024
f63aab8
chore: add back tx-inclusion-height as optional, if not set queries t…
RafilxTenfen Nov 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: refactory send transaction to phase 1
RafilxTenfen committed Nov 22, 2024
commit 916d27d3df655b59a3c7f4ae005c77e0fabab2fe
19 changes: 18 additions & 1 deletion cmd/stakercli/daemon/daemoncommands.go
Original file line number Diff line number Diff line change
@@ -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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wodner whether this param is necessary i.e we have staking tx hash, we can retrieve staking transaction from the btc chain and check its height and based on that chose the param.

Non-blocking comment it can be tackled as future improvement imo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had modified to get the Block Height from the tx hash in the CLI command, but that also added one more call to service, it was done this way to avoid sending the whole global params to the service app

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testing with CLI was not that nice, brought it back the flag, but let if as optional, if not set it will query the bitcoin to get the block height from the tx

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
}

37 changes: 32 additions & 5 deletions itest/e2e_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
75 changes: 39 additions & 36 deletions staker/stakerapp.go
Original file line number Diff line number Diff line change
@@ -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)
22 changes: 20 additions & 2 deletions stakerservice/client/rpcclient.go
Original file line number Diff line number Diff line change
@@ -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)

64 changes: 33 additions & 31 deletions stakerservice/service.go
Original file line number Diff line number Diff line change
@@ -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,59 +129,64 @@ 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 {
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)
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"),