Skip to content

Commit

Permalink
wait for tx k-deep
Browse files Browse the repository at this point in the history
  • Loading branch information
Lazar955 committed Nov 19, 2024
1 parent 1c249c6 commit 369100f
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 10 deletions.
83 changes: 76 additions & 7 deletions btcstaking-tracker/stakingeventwatcher/expected_babylon_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,31 @@ package stakingeventwatcher

import (
"context"
sdkerrors "cosmossdk.io/errors"
"fmt"

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

"cosmossdk.io/errors"
"github.com/avast/retry-go/v4"
btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types"
"github.com/babylonlabs-io/vigilante/config"
"github.com/cosmos/cosmos-sdk/client"
"strings"
"time"

"errors"
bbnclient "github.com/babylonlabs-io/babylon/client/client"
bbn "github.com/babylonlabs-io/babylon/types"
btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types"
btcstakingtypes "github.com/babylonlabs-io/babylon/x/btcstaking/types"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/cosmos/cosmos-sdk/types/query"
)

var (
ErrHeaderNotKnownToBabylon = errors.New("btc header not known to babylon")
ErrHeaderOnBabylonLCFork = errors.New("btc header is on babylon btc light client fork")
ErrBabylonBtcLightClientNotReady = errors.New("babylon btc light client is not ready to receive delegation")
)

type Delegation struct {
StakingTx *wire.MsgTx
StakingOutputIdx uint32
Expand All @@ -23,6 +35,10 @@ type Delegation struct {
HasProof bool
}

type BabylonParams struct {
ConfirmationTimeBlocks uint32 // K-deep
}

type BabylonNodeAdapter interface {
DelegationsByStatus(status btcstakingtypes.BTCDelegationStatus, offset uint64, limit uint64) ([]Delegation, error)
IsDelegationActive(stakingTxHash chainhash.Hash) (bool, error)
Expand All @@ -31,17 +47,21 @@ type BabylonNodeAdapter interface {
inclusionProof *btcstakingtypes.InclusionProof) error
BtcClientTipHeight() (uint32, error)
ActivateDelegation(ctx context.Context, stakingTxHash chainhash.Hash, proof *btcctypes.BTCSpvProof) error
QueryHeaderDepth(headerHash *chainhash.Hash) (uint32, error)
Params() (*BabylonParams, error)
}

type BabylonClientAdapter struct {
babylonClient *bbnclient.Client
cfg *config.BTCStakingTrackerConfig
}

var _ BabylonNodeAdapter = (*BabylonClientAdapter)(nil)

func NewBabylonClientAdapter(babylonClient *bbnclient.Client) *BabylonClientAdapter {
func NewBabylonClientAdapter(babylonClient *bbnclient.Client, cfg *config.BTCStakingTrackerConfig) *BabylonClientAdapter {
return &BabylonClientAdapter{
babylonClient: babylonClient,
cfg: cfg,
}
}

Expand Down Expand Up @@ -127,7 +147,7 @@ func (bca *BabylonClientAdapter) ReportUnbonding(
StakeSpendingTxInclusionProof: inclusionProof,
}

resp, err := bca.babylonClient.ReliablySendMsg(ctx, &msg, []*errors.Error{}, []*errors.Error{})
resp, err := bca.babylonClient.ReliablySendMsg(ctx, &msg, []*sdkerrors.Error{}, []*sdkerrors.Error{})
if err != nil && resp != nil {
return fmt.Errorf("msg MsgBTCUndelegate failed exeuction with code %d and error %w", resp.Code, err)
}
Expand Down Expand Up @@ -161,7 +181,7 @@ func (bca *BabylonClientAdapter) ActivateDelegation(
StakingTxInclusionProof: btcstakingtypes.NewInclusionProofFromSpvProof(proof),
}

resp, err := bca.babylonClient.ReliablySendMsg(ctx, &msg, []*errors.Error{}, []*errors.Error{})
resp, err := bca.babylonClient.ReliablySendMsg(ctx, &msg, []*sdkerrors.Error{}, []*sdkerrors.Error{})

if err != nil && resp != nil {
return fmt.Errorf("msg MsgAddBTCDelegationInclusionProof failed exeuction with code %d and error %w", resp.Code, err)
Expand All @@ -173,3 +193,52 @@ func (bca *BabylonClientAdapter) ActivateDelegation(

return nil
}

func (bca *BabylonClientAdapter) QueryHeaderDepth(headerHash *chainhash.Hash) (uint32, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

clientCtx := client.Context{Client: bca.babylonClient.RPCClient}
queryClient := btclctypes.NewQueryClient(clientCtx)

var response *btclctypes.QueryHeaderDepthResponse
if err := retry.Do(func() error {
depthResponse, err := queryClient.HeaderDepth(ctx, &btclctypes.QueryHeaderDepthRequest{Hash: headerHash.String()})
if err != nil {
return err
}
response = depthResponse
return nil
},
retry.Attempts(5),
retry.MaxDelay(bca.cfg.RetrySubmitUnbondingTxInterval),
retry.MaxJitter(bca.cfg.RetryJitter),
retry.LastErrorOnly(true)); err != nil {
if strings.Contains(err.Error(), btclctypes.ErrHeaderDoesNotExist.Error()) {
return 0, fmt.Errorf("%s: %w", err.Error(), ErrHeaderNotKnownToBabylon)
}
return 0, err
}

return response.Depth, nil
}

func (bca *BabylonClientAdapter) Params() (*BabylonParams, error) {
var bccParams *btcctypes.Params
if err := retry.Do(func() error {
response, err := bca.babylonClient.BTCCheckpointParams()
if err != nil {
return err
}
bccParams = &response.Params

return nil
}, retry.Attempts(5),
retry.MaxDelay(bca.cfg.RetrySubmitUnbondingTxInterval),
retry.MaxJitter(bca.cfg.RetryJitter),
retry.LastErrorOnly(true)); err != nil {
return nil, err
}

return &BabylonParams{ConfirmationTimeBlocks: bccParams.BtcConfirmationDepth}, nil
}
30 changes: 30 additions & 0 deletions btcstaking-tracker/stakingeventwatcher/mock_babylon_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 53 additions & 2 deletions btcstaking-tracker/stakingeventwatcher/stakingeventwatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,12 @@ func (sew *StakingEventWatcher) handlerVerifiedDelegations() {
// checkBtcForStakingTx gets a snapshot of current Delegations in cache
// checks if staking tx is in BTC, generates a proof and invokes sending of MsgAddBTCDelegationInclusionProof
func (sew *StakingEventWatcher) checkBtcForStakingTx() {
params, err := sew.babylonNodeAdapter.Params()
if err != nil {
sew.logger.Errorf("error getting tx params %v", err)
return
}

for del := range sew.pendingTracker.DelegationsIter() {
if del.ActivationInProgress {
continue
Expand All @@ -602,13 +608,17 @@ func (sew *StakingEventWatcher) checkBtcForStakingTx() {
continue
}

go sew.activateBtcDelegation(txHash, proof)
go sew.activateBtcDelegation(txHash, proof, details.Block.BlockHash(), params.ConfirmationTimeBlocks)
}
}

// activateBtcDelegation invokes bbn client and send MsgAddBTCDelegationInclusionProof
func (sew *StakingEventWatcher) activateBtcDelegation(
stakingTxHash chainhash.Hash, proof *btcctypes.BTCSpvProof) {
stakingTxHash chainhash.Hash,
proof *btcctypes.BTCSpvProof,
inclusionBlockHash chainhash.Hash,
requiredDepth uint32,
) {
ctx, cancel := sew.quitContext()
defer cancel()

Expand All @@ -618,6 +628,8 @@ func (sew *StakingEventWatcher) activateBtcDelegation(
sew.logger.Debugf("skipping tx %s is not in pending tracker, err: %v", stakingTxHash, err)
}

sew.waitForRequiredDepth(ctx, stakingTxHash, &inclusionBlockHash, requiredDepth)

_ = retry.Do(func() error {
verified, err := sew.babylonNodeAdapter.IsDelegationVerified(stakingTxHash)
if err != nil {
Expand Down Expand Up @@ -652,6 +664,45 @@ func (sew *StakingEventWatcher) activateBtcDelegation(
)
}

func (sew *StakingEventWatcher) waitForRequiredDepth(
ctx context.Context,
stakingTxHash chainhash.Hash,
inclusionBlockHash *chainhash.Hash,
requiredDepth uint32,
) {
var depth uint32
_ = retry.Do(func() error {
var err error
depth, err = sew.babylonNodeAdapter.QueryHeaderDepth(inclusionBlockHash)
if err != nil {
// If the header is not known to babylon, or it is on LCFork, then most probably
// lc is not up to date, we should retry sending delegation after some time.
if errors.Is(err, ErrHeaderNotKnownToBabylon) || errors.Is(err, ErrHeaderOnBabylonLCFork) {
return fmt.Errorf("btc light client error %s: %w", err.Error(), ErrBabylonBtcLightClientNotReady)
}

return fmt.Errorf("error while getting delegation data: %w", err)
}

if depth < requiredDepth {
return fmt.Errorf("btc lc not ready, required depth: %d, current depth: %d: %w",
requiredDepth, depth, ErrBabylonBtcLightClientNotReady)
}

return nil
},
retry.Context(ctx),
retryForever,
fixedDelyTypeWithJitter,
retry.MaxDelay(sew.cfg.RetrySubmitUnbondingTxInterval),
retry.MaxJitter(sew.cfg.RetryJitter),
retry.OnRetry(func(n uint, err error) {
sew.logger.Debugf("waiting for staking tx: %s to be k-deep. Current[%d], required[%d]. "+
"Attempt: %d. Err: %v", stakingTxHash, depth, requiredDepth, n, err)
}),
)
}

func (sew *StakingEventWatcher) latency(method string) func() {
startTime := time.Now()
return func() {
Expand Down
2 changes: 1 addition & 1 deletion btcstaking-tracker/tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func NewBTCStakingTracker(
logger := parentLogger.With(zap.String("module", "btcstaking-tracker"))

// watcher routine
babylonAdapter := uw.NewBabylonClientAdapter(bbnClient)
babylonAdapter := uw.NewBabylonClientAdapter(bbnClient, cfg)
watcher := uw.NewStakingEventWatcher(
btcNotifier,
btcClient,
Expand Down

0 comments on commit 369100f

Please sign in to comment.