diff --git a/babylonclient/babyloncontroller.go b/babylonclient/babyloncontroller.go index f634c8c..f26055d 100644 --- a/babylonclient/babyloncontroller.go +++ b/babylonclient/babyloncontroller.go @@ -133,15 +133,19 @@ func (bc *BabylonController) Stop() error { return bc.bbnClient.Stop() } -func (bc *BabylonController) Params() (*StakingParams, error) { - // TODO: it would probably be good to have separate methods for those - var bccParams *btcctypes.Params +func (bc *BabylonController) btccheckpointParamsWithRetry() (*BTCCheckpointParams, error) { + var bccParams *BTCCheckpointParams if err := retry.Do(func() error { response, err := bc.bbnClient.BTCCheckpointParams() if err != nil { return err } - bccParams = &response.Params + + bccParams = &BTCCheckpointParams{ + ConfirmationTimeBlocks: response.Params.BtcConfirmationDepth, + FinalizationTimeoutBlocks: response.Params.CheckpointFinalizationTimeout, + } + return nil }, RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) { bc.logger.WithFields(logrus.Fields{ @@ -153,6 +157,20 @@ func (bc *BabylonController) Params() (*StakingParams, error) { return nil, err } + return bccParams, nil +} + +func (bc *BabylonController) BTCCheckpointParams() (*BTCCheckpointParams, error) { + return bc.btccheckpointParamsWithRetry() +} + +func (bc *BabylonController) Params() (*StakingParams, error) { + bccParams, err := bc.btccheckpointParamsWithRetry() + + if err != nil { + return nil, err + } + var stakingTrackerParams *StakingTrackerResponse if err := retry.Do(func() error { trackerParams, err := bc.QueryStakingTracker() @@ -172,8 +190,8 @@ func (bc *BabylonController) Params() (*StakingParams, error) { } return &StakingParams{ - ConfirmationTimeBlocks: bccParams.BtcConfirmationDepth, - FinalizationTimeoutBlocks: bccParams.CheckpointFinalizationTimeout, + ConfirmationTimeBlocks: bccParams.ConfirmationTimeBlocks, + FinalizationTimeoutBlocks: bccParams.FinalizationTimeoutBlocks, SlashingPkScript: stakingTrackerParams.SlashingPkScript, CovenantPks: stakingTrackerParams.CovenantPks, MinSlashingTxFeeSat: stakingTrackerParams.MinSlashingFee, @@ -189,6 +207,71 @@ func (bc *BabylonController) Params() (*StakingParams, error) { }, nil } +func (bc *BabylonController) ParamsByBtcHeight(btcHeight uint32) (*StakingParams, error) { + bccParams, err := bc.btccheckpointParamsWithRetry() + + if err != nil { + return nil, err + } + + var stakingTrackerParams *StakingTrackerResponse + if err := retry.Do(func() error { + trackerParams, err := bc.QueryStakingTrackerByBtcHeight(btcHeight) + if err != nil { + return err + } + stakingTrackerParams = trackerParams + return nil + }, RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) { + bc.logger.WithFields(logrus.Fields{ + "attempt": n + 1, + "max_attempts": RtyAttNum, + "error": err, + }).Error("Failed to query babylon client for staking tracker params") + })); err != nil { + return nil, err + } + + return &StakingParams{ + ConfirmationTimeBlocks: bccParams.ConfirmationTimeBlocks, + FinalizationTimeoutBlocks: bccParams.FinalizationTimeoutBlocks, + SlashingPkScript: stakingTrackerParams.SlashingPkScript, + CovenantPks: stakingTrackerParams.CovenantPks, + MinSlashingTxFeeSat: stakingTrackerParams.MinSlashingFee, + SlashingRate: stakingTrackerParams.SlashingRate, + CovenantQuruomThreshold: stakingTrackerParams.CovenantQuruomThreshold, + UnbondingTime: stakingTrackerParams.UnbondingTime, + UnbondingFee: stakingTrackerParams.UnbondingFee, + MinStakingTime: stakingTrackerParams.MinStakingTime, + MaxStakingTime: stakingTrackerParams.MaxStakingTime, + MinStakingValue: stakingTrackerParams.MinStakingValue, + MaxStakingValue: stakingTrackerParams.MaxStakingValue, + AllowListExpirationHeight: stakingTrackerParams.AllowListExpirationHeight, + }, nil +} + +func (bc *BabylonController) StakingTrackerByBtcHeight(btcHeight uint32) (*StakingTrackerResponse, error) { + var stakingTrackerParams *StakingTrackerResponse + if err := retry.Do(func() error { + trackerParams, err := bc.QueryStakingTrackerByBtcHeight(btcHeight) + if err != nil { + return err + } + stakingTrackerParams = trackerParams + return nil + }, RtyAtt, RtyDel, RtyErr, retry.OnRetry(func(n uint, err error) { + bc.logger.WithFields(logrus.Fields{ + "attempt": n + 1, + "max_attempts": RtyAttNum, + "error": err, + }).Error("Failed to query babylon client for staking tracker params") + })); err != nil { + return nil, err + } + + return stakingTrackerParams, nil +} + func (bc *BabylonController) GetKeyAddress() sdk.AccAddress { // get key address, retrieves address based on key name which is configured in // cfg *stakercfg.BBNConfig. If this fails, it means we have misconfiguration problem @@ -444,33 +527,22 @@ func getQueryContext(timeout time.Duration) (context.Context, context.CancelFunc return ctx, cancel } -func (bc *BabylonController) QueryStakingTracker() (*StakingTrackerResponse, error) { - ctx, cancel := getQueryContext(bc.cfg.Timeout) - defer cancel() - - clientCtx := client.Context{Client: bc.bbnClient.RPCClient} - queryClient := btcstypes.NewQueryClient(clientCtx) - - response, err := queryClient.Params(ctx, &btcstypes.QueryParamsRequest{}) - if err != nil { - return nil, err - } - +func parseParams(params *btcstypes.Params) (*StakingTrackerResponse, error) { // check early that the covenant config makes sense, so that rest of the // code can assume that: // 1. covenant quorum is less or equal to number of covenant pks // 2. covenant pks are not empty - if len(response.Params.CovenantPks) == 0 { + if len(params.CovenantPks) == 0 { return nil, fmt.Errorf("empty list of covenant pks: %w", ErrInvalidValueReceivedFromBabylonNode) } - if int(response.Params.CovenantQuorum) > len(response.Params.CovenantPks) { + if int(params.CovenantQuorum) > len(params.CovenantPks) { return nil, fmt.Errorf("covenant quorum is bigger than number of covenant pks: %w", ErrInvalidValueReceivedFromBabylonNode) } var covenantPks []*btcec.PublicKey - for _, covenantPk := range response.Params.CovenantPks { + for _, covenantPk := range params.CovenantPks { covenantBtcPk, err := covenantPk.ToBTCPK() if err != nil { return nil, err @@ -478,50 +550,83 @@ func (bc *BabylonController) QueryStakingTracker() (*StakingTrackerResponse, err covenantPks = append(covenantPks, covenantBtcPk) } - unbondingTime := response.Params.UnbondingTimeBlocks + unbondingTime := params.UnbondingTimeBlocks if unbondingTime > math.MaxUint16 { return nil, fmt.Errorf("unbonding time is bigger than uint16: %w", ErrInvalidValueReceivedFromBabylonNode) } - minStakingTimeBlocksU32 := response.Params.MinStakingTimeBlocks + minStakingTimeBlocksU32 := params.MinStakingTimeBlocks if minStakingTimeBlocksU32 > math.MaxUint16 { return nil, fmt.Errorf("min staking time is bigger than uint16: %w", ErrInvalidValueReceivedFromBabylonNode) } - maxStakingTimeBlocksU32 := response.Params.MaxStakingTimeBlocks + maxStakingTimeBlocksU32 := params.MaxStakingTimeBlocks if maxStakingTimeBlocksU32 > math.MaxUint16 { return nil, fmt.Errorf("max staking time is bigger than uint16: %w", ErrInvalidValueReceivedFromBabylonNode) } - if response.Params.MinStakingValueSat < 0 { + if params.MinStakingValueSat < 0 { return nil, fmt.Errorf("min staking value is negative: %w", ErrInvalidValueReceivedFromBabylonNode) } - if response.Params.MaxStakingValueSat < 0 { + if params.MaxStakingValueSat < 0 { return nil, fmt.Errorf("max staking value is negative: %w", ErrInvalidValueReceivedFromBabylonNode) } - if response.Params.UnbondingFeeSat < 0 { + if params.UnbondingFeeSat < 0 { return nil, fmt.Errorf("unbonding fee is negative: %w", ErrInvalidValueReceivedFromBabylonNode) } return &StakingTrackerResponse{ - SlashingPkScript: response.Params.SlashingPkScript, - SlashingRate: response.Params.SlashingRate, - MinComissionRate: response.Params.MinCommissionRate, + SlashingPkScript: params.SlashingPkScript, + SlashingRate: params.SlashingRate, + MinComissionRate: params.MinCommissionRate, CovenantPks: covenantPks, - MinSlashingFee: btcutil.Amount(response.Params.MinSlashingTxFeeSat), - CovenantQuruomThreshold: response.Params.CovenantQuorum, + MinSlashingFee: btcutil.Amount(params.MinSlashingTxFeeSat), + CovenantQuruomThreshold: params.CovenantQuorum, UnbondingTime: uint16(unbondingTime), - UnbondingFee: btcutil.Amount(response.Params.UnbondingFeeSat), + UnbondingFee: btcutil.Amount(params.UnbondingFeeSat), MinStakingTime: uint16(minStakingTimeBlocksU32), MaxStakingTime: uint16(maxStakingTimeBlocksU32), - MinStakingValue: btcutil.Amount(response.Params.MinStakingValueSat), - MaxStakingValue: btcutil.Amount(response.Params.MaxStakingValueSat), - AllowListExpirationHeight: response.Params.AllowListExpirationHeight, + MinStakingValue: btcutil.Amount(params.MinStakingValueSat), + MaxStakingValue: btcutil.Amount(params.MaxStakingValueSat), + AllowListExpirationHeight: params.AllowListExpirationHeight, }, nil } +func (bc *BabylonController) QueryStakingTracker() (*StakingTrackerResponse, error) { + ctx, cancel := getQueryContext(bc.cfg.Timeout) + defer cancel() + + clientCtx := client.Context{Client: bc.bbnClient.RPCClient} + queryClient := btcstypes.NewQueryClient(clientCtx) + + response, err := queryClient.Params(ctx, &btcstypes.QueryParamsRequest{}) + if err != nil { + return nil, err + } + + return parseParams(&response.Params) +} + +func (bc *BabylonController) QueryStakingTrackerByBtcHeight(btcHeight uint32) (*StakingTrackerResponse, error) { + ctx, cancel := getQueryContext(bc.cfg.Timeout) + defer cancel() + + clientCtx := client.Context{Client: bc.bbnClient.RPCClient} + queryClient := btcstypes.NewQueryClient(clientCtx) + + response, err := queryClient.ParamsByBTCHeight(ctx, &btcstypes.QueryParamsByBTCHeightRequest{ + BtcHeight: btcHeight, + }) + + if err != nil { + return nil, err + } + + return parseParams(&response.Params) +} + func (bc *BabylonController) QueryFinalityProviders( limit uint64, offset uint64) (*FinalityProvidersClientResponse, error) { diff --git a/babylonclient/interface.go b/babylonclient/interface.go index 705e3ff..154464a 100644 --- a/babylonclient/interface.go +++ b/babylonclient/interface.go @@ -16,6 +16,13 @@ import ( pv "github.com/cosmos/relayer/v2/relayer/provider" ) +type BTCCheckpointParams struct { + // K-deep + ConfirmationTimeBlocks uint32 + // W-deep + FinalizationTimeoutBlocks uint32 +} + type StakingParams struct { // K-deep ConfirmationTimeBlocks uint32 @@ -68,7 +75,10 @@ type SingleKeyKeyring interface { type BabylonClient interface { SingleKeyKeyring + BTCCheckpointParams() (*BTCCheckpointParams, error) Params() (*StakingParams, error) + ParamsByBtcHeight(btcHeight uint32) (*StakingParams, error) + StakingTrackerByBtcHeight(btcHeight uint32) (*StakingTrackerResponse, error) Delegate(dg *DelegationData) (*pv.RelayerTxResponse, error) QueryFinalityProviders(limit uint64, offset uint64) (*FinalityProvidersClientResponse, error) QueryFinalityProvider(btcPubKey *btcec.PublicKey) (*FinalityProviderClientResponse, error) @@ -91,6 +101,21 @@ func (m *MockBabylonClient) Params() (*StakingParams, error) { return m.ClientParams, nil } +func (m *MockBabylonClient) ParamsByBtcHeight(btcHeight uint32) (*StakingParams, error) { + return m.ClientParams, nil +} + +func (m *MockBabylonClient) BTCCheckpointParams() (*BTCCheckpointParams, error) { + return &BTCCheckpointParams{ + ConfirmationTimeBlocks: m.ClientParams.ConfirmationTimeBlocks, + FinalizationTimeoutBlocks: m.ClientParams.FinalizationTimeoutBlocks, + }, nil +} + +func (m *MockBabylonClient) StakingTrackerByBtcHeight(btcHeight uint32) (*StakingTrackerResponse, error) { + return &StakingTrackerResponse{}, nil +} + func (m *MockBabylonClient) Sign(msg []byte) ([]byte, error) { sig, err := m.babylonKey.Sign(msg) diff --git a/staker/babylontypes.go b/staker/babylontypes.go index c208705..a88164e 100644 --- a/staker/babylontypes.go +++ b/staker/babylontypes.go @@ -21,9 +21,10 @@ import ( // retrieving data from babylon chain, sending data to babylon chain, queuing data to be send etc. type inclusionInfo struct { - txIndex uint32 - inclusionBlock *wire.MsgBlock - inclusionProof []byte + txIndex uint32 + inclusionBlock *wire.MsgBlock + inclusionBlockBtcHeight uint32 + inclusionProof []byte } type sendDelegationRequest struct { @@ -39,7 +40,7 @@ func (app *App) buildOwnedDelegation( stakerAddress btcutil.Address, storedTx *stakerdb.StoredTransaction, ) (*cl.DelegationData, error) { - externalData, err := app.retrieveExternalDelegationData(stakerAddress) + externalData, err := app.retrieveExternalDelegationData(stakerAddress, req.inclusionInfo) if err != nil { return nil, err } diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 79ffa46..4ecebe1 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -960,8 +960,9 @@ func (app *App) mustBuildInclusionProof( func (app *App) newBtcInclusionInfo(notifierTx *notifier.TxConfirmation) *inclusionInfo { return &inclusionInfo{ - txIndex: notifierTx.TxIndex, - inclusionBlock: notifierTx.Block, + txIndex: notifierTx.TxIndex, + inclusionBlock: notifierTx.Block, + inclusionBlockBtcHeight: notifierTx.BlockHeight, inclusionProof: app.mustBuildInclusionProof( notifierTx.Block, notifierTx.TxIndex, @@ -969,10 +970,29 @@ func (app *App) newBtcInclusionInfo(notifierTx *notifier.TxConfirmation) *inclus } } -func (app *App) retrieveExternalDelegationData(stakerAddress btcutil.Address) (*externalDelegationData, error) { - params, err := app.babylonClient.Params() - if err != nil { - return nil, err +func (app *App) retrieveExternalDelegationData( + stakerAddress btcutil.Address, + inclusionInfo *inclusionInfo, +) (*externalDelegationData, error) { + var params *cl.StakingParams + + if inclusionInfo == nil { + p, err := app.babylonClient.Params() + + if err != nil { + return nil, err + } + + params = p + + } else { + p, err := app.babylonClient.ParamsByBtcHeight(inclusionInfo.inclusionBlockBtcHeight) + + if err != nil { + return nil, err + } + + params = p } stakerPublicKey, err := app.wc.AddressPublicKey(stakerAddress) @@ -1540,7 +1560,8 @@ func (app *App) handleStakingCommands() { case cmd := <-app.migrateStakingCmd: stkTxHash := cmd.notifierTx.Tx.TxHash() - stkParams, err := app.babylonClient.Params() + + btcCheckpointParams, err := app.babylonClient.BTCCheckpointParams() if err != nil { cmd.errChan <- err continue @@ -1548,7 +1569,7 @@ func (app *App) handleStakingCommands() { bestBlockHeight := app.currentBestBlockHeight.Load() // check confirmation is deep enough - if err := checkConfirmationDepth(bestBlockHeight, cmd.notifierTx.BlockHeight, stkParams.ConfirmationTimeBlocks); err != nil { + if err := checkConfirmationDepth(bestBlockHeight, cmd.notifierTx.BlockHeight, btcCheckpointParams.ConfirmationTimeBlocks); err != nil { cmd.errChan <- err continue } @@ -1556,7 +1577,7 @@ func (app *App) handleStakingCommands() { _, btcDelTxHash, err := app.handleSendDelegationRequest( cmd.stakerAddr, cmd.parsedStakingTx.OpReturnData.StakingTime, - stkParams.ConfirmationTimeBlocks, + btcCheckpointParams.ConfirmationTimeBlocks, []*btcec.PublicKey{cmd.parsedStakingTx.OpReturnData.FinalityProviderPublicKey.PubKey}, cmd.pop, cmd.notifierTx.Tx, @@ -1616,9 +1637,10 @@ func (app *App) handleStakingEvents() { req := newSendDelegationRequest( &ev.stakingTxHash, &inclusionInfo{ - txIndex: ev.txIndex, - inclusionBlock: ev.inlusionBlock, - inclusionProof: proof, + txIndex: ev.txIndex, + inclusionBlock: ev.inlusionBlock, + inclusionBlockBtcHeight: ev.blockHeight, + inclusionProof: proof, }, ev.blockDepth, )