From 22ed334b18d97397959080d112bf24ee0d2dd622 Mon Sep 17 00:00:00 2001 From: Gurjot Singh <111540954+gusin13@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:49:35 -0400 Subject: [PATCH] feat: unbonding state transitions (#27) --- config/config-docker.yml | 2 + config/config-local.yml | 2 + go.mod | 2 +- go.sum | 3 +- internal/config/poller.go | 14 ++++- internal/db/interface.go | 26 ++++++++++ internal/db/model/setup.go | 2 + internal/db/model/timelock.go | 15 ++++++ internal/db/timelock.go | 73 ++++++++++++++++++++++++++ internal/services/delegation.go | 79 ++++++++++++++++++----------- internal/services/expiry-checker.go | 65 ++++++++++++++++++++++++ internal/services/service.go | 2 + internal/types/state.go | 14 ++--- internal/types/timelock_tx_type.go | 11 ++++ tests/mocks/mock_db_client.go | 66 ++++++++++++++++++++++++ 15 files changed, 336 insertions(+), 40 deletions(-) create mode 100644 internal/db/model/timelock.go create mode 100644 internal/db/timelock.go create mode 100644 internal/services/expiry-checker.go create mode 100644 internal/types/timelock_tx_type.go diff --git a/config/config-docker.yml b/config/config-docker.yml index 2f205e0..63f8d0d 100644 --- a/config/config-docker.yml +++ b/config/config-docker.yml @@ -14,6 +14,8 @@ bbn: timeout: 30s poller: param-polling-interval: 60s + expiry-checker-polling-interval: 10s + expired-delegations-limit: 100 queue: queue_user: user # can be replaced by values in .env file queue_password: password diff --git a/config/config-local.yml b/config/config-local.yml index 4aab66e..001ecb3 100644 --- a/config/config-local.yml +++ b/config/config-local.yml @@ -14,6 +14,8 @@ bbn: timeout: 30s poller: param-polling-interval: 10s + expiry-checker-polling-interval: 10s + expired-delegations-limit: 100 queue: queue_user: user # can be replaced by values in .env file queue_password: password diff --git a/go.mod b/go.mod index 680d157..75de9ac 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.3 require ( github.com/babylonlabs-io/babylon v0.12.1 github.com/babylonlabs-io/staking-queue-client v0.4.1 - github.com/btcsuite/btcd v0.24.2 + github.com/btcsuite/btcd v0.24.3-0.20241011125836-24eb815168f4 github.com/cometbft/cometbft v0.38.7 github.com/cosmos/cosmos-sdk v0.50.6 github.com/cosmos/gogoproto v1.7.0 diff --git a/go.sum b/go.sum index b9b981a..3d333d2 100644 --- a/go.sum +++ b/go.sum @@ -293,8 +293,9 @@ github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6Z github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= -github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd v0.24.3-0.20241011125836-24eb815168f4 h1:wks2KaK25+5rr3BBmD2euEhinRViLv3jJpYZImxwCnM= +github.com/btcsuite/btcd v0.24.3-0.20241011125836-24eb815168f4/go.mod h1:zHK7t7sw8XbsCkD64WePHE3r3k9/XoGAcf6mXV14c64= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= diff --git a/internal/config/poller.go b/internal/config/poller.go index d266062..e13b374 100644 --- a/internal/config/poller.go +++ b/internal/config/poller.go @@ -6,13 +6,23 @@ import ( ) type PollerConfig struct { - ParamPollingInterval time.Duration `mapstructure:"param-polling-interval"` + ParamPollingInterval time.Duration `mapstructure:"param-polling-interval"` + ExpiryCheckerPollingInterval time.Duration `mapstructure:"expiry-checker-polling-interval"` + ExpiredDelegationsLimit uint64 `mapstructure:"expired-delegations-limit"` } func (cfg *PollerConfig) Validate() error { - if cfg.ParamPollingInterval < 0 { + if cfg.ParamPollingInterval <= 0 { return errors.New("param-polling-interval must be positive") } + if cfg.ExpiryCheckerPollingInterval <= 0 { + return errors.New("expiry-checker-polling-interval must be positive") + } + + if cfg.ExpiredDelegationsLimit <= 0 { + return errors.New("expired-delegations-limit must be positive") + } + return nil } diff --git a/internal/db/interface.go b/internal/db/interface.go index 1edbded..51257da 100644 --- a/internal/db/interface.go +++ b/internal/db/interface.go @@ -113,4 +113,30 @@ type DbInterface interface { GetBTCDelegationByStakingTxHash( ctx context.Context, stakingTxHash string, ) (*model.BTCDelegationDetails, error) + /** + * SaveNewTimeLockExpire saves a new timelock expire to the database. + * If the timelock expire already exists, DuplicateKeyError will be returned. + * @param ctx The context + * @param stakingTxHashHex The staking tx hash hex + * @param expireHeight The expire height + * @param txType The transaction type + * @return An error if the operation failed + */ + SaveNewTimeLockExpire( + ctx context.Context, stakingTxHashHex string, expireHeight uint32, txType string, + ) error + /** + * FindExpiredDelegations finds the expired delegations. + * @param ctx The context + * @param btcTipHeight The BTC tip height + * @return The expired delegations or an error + */ + FindExpiredDelegations(ctx context.Context, btcTipHeight, limit uint64) ([]model.TimeLockDocument, error) + /** + * DeleteExpiredDelegation deletes an expired delegation. + * @param ctx The context + * @param id The ID of the expired delegation + * @return An error if the operation failed + */ + DeleteExpiredDelegation(ctx context.Context, stakingTxHashHex string) error } diff --git a/internal/db/model/setup.go b/internal/db/model/setup.go index 0a96ecf..a38e707 100644 --- a/internal/db/model/setup.go +++ b/internal/db/model/setup.go @@ -16,6 +16,7 @@ import ( const ( FinalityProviderDetailsCollection = "finality_provider_details" BTCDelegationDetailsCollection = "btc_delegation_details" + TimeLockCollection = "timelock" GlobalParamsCollection = "global_params" ) @@ -27,6 +28,7 @@ type index struct { var collections = map[string][]index{ FinalityProviderDetailsCollection: {{Indexes: map[string]int{}}}, BTCDelegationDetailsCollection: {{Indexes: map[string]int{}}}, + TimeLockCollection: {{Indexes: map[string]int{}}}, GlobalParamsCollection: {{Indexes: map[string]int{}}}, } diff --git a/internal/db/model/timelock.go b/internal/db/model/timelock.go new file mode 100644 index 0000000..f8c41f1 --- /dev/null +++ b/internal/db/model/timelock.go @@ -0,0 +1,15 @@ +package model + +type TimeLockDocument struct { + StakingTxHashHex string `bson:"_id"` // Primary key + ExpireHeight uint32 `bson:"expire_height"` + TxType string `bson:"tx_type"` +} + +func NewTimeLockDocument(stakingTxHashHex string, expireHeight uint32, txType string) *TimeLockDocument { + return &TimeLockDocument{ + StakingTxHashHex: stakingTxHashHex, + ExpireHeight: expireHeight, + TxType: txType, + } +} diff --git a/internal/db/timelock.go b/internal/db/timelock.go new file mode 100644 index 0000000..866a851 --- /dev/null +++ b/internal/db/timelock.go @@ -0,0 +1,73 @@ +package db + +import ( + "context" + "errors" + "fmt" + + "github.com/babylonlabs-io/babylon-staking-indexer/internal/db/model" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func (db *Database) SaveNewTimeLockExpire( + ctx context.Context, stakingTxHashHex string, + expireHeight uint32, txType string, +) error { + tlDoc := model.NewTimeLockDocument(stakingTxHashHex, expireHeight, txType) + _, err := db.client.Database(db.dbName). + Collection(model.TimeLockCollection). + InsertOne(ctx, tlDoc) + if err != nil { + var writeErr mongo.WriteException + if errors.As(err, &writeErr) { + for _, e := range writeErr.WriteErrors { + if mongo.IsDuplicateKeyError(e) { + return &DuplicateKeyError{ + Key: tlDoc.StakingTxHashHex, + Message: "timelock already exists", + } + } + } + } + return err + } + return nil +} + +func (db *Database) FindExpiredDelegations(ctx context.Context, btcTipHeight, limit uint64) ([]model.TimeLockDocument, error) { + client := db.client.Database(db.dbName).Collection(model.TimeLockCollection) + filter := bson.M{"expire_height": bson.M{"$lte": btcTipHeight}} + + opts := options.Find().SetLimit(int64(limit)) + cursor, err := client.Find(ctx, filter, opts) + if err != nil { + return nil, err + } + defer cursor.Close(ctx) + + var delegations []model.TimeLockDocument + if err = cursor.All(ctx, &delegations); err != nil { + return nil, err + } + + return delegations, nil +} + +func (db *Database) DeleteExpiredDelegation(ctx context.Context, stakingTxHashHex string) error { + client := db.client.Database(db.dbName).Collection(model.TimeLockCollection) + filter := bson.M{"_id": stakingTxHashHex} + + result, err := client.DeleteOne(ctx, filter) + if err != nil { + return fmt.Errorf("failed to delete expired delegation with stakingTxHashHex %v: %w", stakingTxHashHex, err) + } + + // Check if any document was deleted + if result.DeletedCount == 0 { + return fmt.Errorf("no expired delegation found with stakingTxHashHex %v", stakingTxHashHex) + } + + return nil +} diff --git a/internal/services/delegation.go b/internal/services/delegation.go index c7068df..a256478 100644 --- a/internal/services/delegation.go +++ b/internal/services/delegation.go @@ -34,9 +34,9 @@ func (s *Service) processNewBTCDelegationEvent( return err } - if err := s.db.SaveNewBTCDelegation( + if dbErr := s.db.SaveNewBTCDelegation( ctx, model.FromEventBTCDelegationCreated(newDelegation), - ); err != nil { + ); dbErr != nil { if db.IsDuplicateKeyError(err) { // BTC delegation already exists, ignore the event return nil @@ -44,7 +44,7 @@ func (s *Service) processNewBTCDelegationEvent( return types.NewError( http.StatusInternalServerError, types.InternalServiceError, - fmt.Errorf("failed to save new BTC delegation: %w", err), + fmt.Errorf("failed to save new BTC delegation: %w", dbErr), ) } @@ -65,13 +65,13 @@ func (s *Service) processCovenantQuorumReachedEvent( return err } - if err := s.db.UpdateBTCDelegationState( + if dbErr := s.db.UpdateBTCDelegationState( ctx, covenantQuorumReachedEvent.StakingTxHash, types.DelegationState(covenantQuorumReachedEvent.NewState), - ); err != nil { + ); dbErr != nil { return types.NewError( http.StatusInternalServerError, types.InternalServiceError, - fmt.Errorf("failed to update BTC delegation state: %w", err), + fmt.Errorf("failed to update BTC delegation state: %w", dbErr), ) } @@ -92,13 +92,13 @@ func (s *Service) processBTCDelegationInclusionProofReceivedEvent( return err } - if err := s.db.UpdateBTCDelegationDetails( + if dbErr := s.db.UpdateBTCDelegationDetails( ctx, inclusionProofEvent.StakingTxHash, model.FromEventBTCDelegationInclusionProofReceived(inclusionProofEvent), - ); err != nil { + ); dbErr != nil { return types.NewError( http.StatusInternalServerError, types.InternalServiceError, - fmt.Errorf("failed to update BTC delegation state: %w", err), + fmt.Errorf("failed to update BTC delegation state: %w", dbErr), ) } @@ -119,13 +119,16 @@ func (s *Service) processBTCDelegationUnbondedEarlyEvent( return err } - if err := s.db.UpdateBTCDelegationState( - ctx, unbondedEarlyEvent.StakingTxHash, types.DelegationState(unbondedEarlyEvent.NewState), - ); err != nil { + // TODO: save timelock expire, need to figure out what will be the expire height in this case. + // https://github.com/babylonlabs-io/babylon-staking-indexer/issues/28 + + if dbErr := s.db.UpdateBTCDelegationState( + ctx, unbondedEarlyEvent.StakingTxHash, types.StateUnbonding, + ); dbErr != nil { return types.NewError( http.StatusInternalServerError, types.InternalServiceError, - fmt.Errorf("failed to update BTC delegation state: %w", err), + fmt.Errorf("failed to update BTC delegation state: %w", dbErr), ) } @@ -146,13 +149,31 @@ func (s *Service) processBTCDelegationExpiredEvent( return err } - if err := s.db.UpdateBTCDelegationState( - ctx, expiredEvent.StakingTxHash, types.DelegationState(expiredEvent.NewState), - ); err != nil { + delegation, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, expiredEvent.StakingTxHash) + if dbErr != nil { + return types.NewError( + http.StatusInternalServerError, + types.InternalServiceError, + fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr), + ) + } + if dbErr := s.db.SaveNewTimeLockExpire( + ctx, delegation.StakingTxHashHex, delegation.EndHeight, types.ExpiredTxType.String(), + ); dbErr != nil { + return types.NewError( + http.StatusInternalServerError, + types.InternalServiceError, + fmt.Errorf("failed to save timelock expire: %w", dbErr), + ) + } + + if dbErr := s.db.UpdateBTCDelegationState( + ctx, expiredEvent.StakingTxHash, types.StateUnbonding, + ); dbErr != nil { return types.NewError( http.StatusInternalServerError, types.InternalServiceError, - fmt.Errorf("failed to update BTC delegation state: %w", err), + fmt.Errorf("failed to update BTC delegation state: %w", dbErr), ) } @@ -190,12 +211,12 @@ func (s *Service) validateCovenantQuorumReachedEvent(ctx context.Context, event } // Fetch the current delegation state from the database - delegation, err := s.db.GetBTCDelegationByStakingTxHash(ctx, event.StakingTxHash) - if err != nil { + delegation, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, event.StakingTxHash) + if dbErr != nil { return types.NewError( http.StatusInternalServerError, types.InternalServiceError, - fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", err), + fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr), ) } @@ -247,12 +268,12 @@ func (s *Service) validateBTCDelegationInclusionProofReceivedEvent(ctx context.C } // Fetch the current delegation state from the database - delegation, err := s.db.GetBTCDelegationByStakingTxHash(ctx, event.StakingTxHash) - if err != nil { + delegation, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, event.StakingTxHash) + if dbErr != nil { return types.NewError( http.StatusInternalServerError, types.InternalServiceError, - fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", err), + fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr), ) } @@ -312,12 +333,12 @@ func (s *Service) validateBTCDelegationUnbondedEarlyEvent(ctx context.Context, e } // Fetch the current delegation state from the database - delegation, err := s.db.GetBTCDelegationByStakingTxHash(ctx, event.StakingTxHash) - if err != nil { + delegation, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, event.StakingTxHash) + if dbErr != nil { return types.NewError( http.StatusInternalServerError, types.InternalServiceError, - fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", err), + fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr), ) } @@ -349,12 +370,12 @@ func (s *Service) validateBTCDelegationExpiredEvent(ctx context.Context, event * } // Fetch the current delegation state from the database - delegation, err := s.db.GetBTCDelegationByStakingTxHash(ctx, event.StakingTxHash) - if err != nil { + delegation, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, event.StakingTxHash) + if dbErr != nil { return types.NewError( http.StatusInternalServerError, types.InternalServiceError, - fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", err), + fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr), ) } diff --git a/internal/services/expiry-checker.go b/internal/services/expiry-checker.go new file mode 100644 index 0000000..83616e8 --- /dev/null +++ b/internal/services/expiry-checker.go @@ -0,0 +1,65 @@ +package services + +import ( + "context" + "fmt" + "net/http" + + "github.com/babylonlabs-io/babylon-staking-indexer/internal/types" + "github.com/babylonlabs-io/babylon-staking-indexer/internal/utils/poller" + "github.com/rs/zerolog/log" +) + +func (s *Service) StartExpiryChecker(ctx context.Context) { + expiryCheckerPoller := poller.NewPoller( + s.cfg.Poller.ExpiryCheckerPollingInterval, + s.checkExpiry, + ) + go expiryCheckerPoller.Start(ctx) +} + +func (s *Service) checkExpiry(ctx context.Context) *types.Error { + btcTip, err := s.btc.GetBlockCount() + if err != nil { + return types.NewInternalServiceError( + fmt.Errorf("failed to get BTC tip height: %w", err), + ) + } + + expiredDelegations, err := s.db.FindExpiredDelegations(ctx, uint64(btcTip), s.cfg.Poller.ExpiredDelegationsLimit) + if err != nil { + return types.NewInternalServiceError( + fmt.Errorf("failed to find expired delegations: %w", err), + ) + } + + for _, delegation := range expiredDelegations { + delegation, err := s.db.GetBTCDelegationByStakingTxHash(ctx, delegation.StakingTxHashHex) + if err != nil { + return types.NewError( + http.StatusInternalServerError, + types.InternalServiceError, + fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", err), + ) + } + + // TODO: consider eligibility for state transition here + // https://github.com/babylonlabs-io/babylon-staking-indexer/issues/29 + + if err := s.db.UpdateBTCDelegationState(ctx, delegation.StakingTxHashHex, types.StateWithdrawable); err != nil { + log.Error().Err(err).Msg("Error updating BTC delegation state to withdrawable") + return types.NewInternalServiceError( + fmt.Errorf("failed to update BTC delegation state to withdrawable: %w", err), + ) + } + + if err := s.db.DeleteExpiredDelegation(ctx, delegation.StakingTxHashHex); err != nil { + log.Error().Err(err).Msg("Error deleting expired delegation") + return types.NewInternalServiceError( + fmt.Errorf("failed to delete expired delegation: %w", err), + ) + } + } + + return nil +} diff --git a/internal/services/service.go b/internal/services/service.go index ac26459..55866f8 100644 --- a/internal/services/service.go +++ b/internal/services/service.go @@ -40,6 +40,8 @@ func NewService( func (s *Service) StartIndexerSync(ctx context.Context) { // Sync global parameters s.SyncGlobalParams(ctx) + // Start the expiry checker + s.StartExpiryChecker(ctx) // Start the bootstrap process s.BootstrapBbn(ctx) // Start the websocket event subscription process diff --git a/internal/types/state.go b/internal/types/state.go index cf5b645..ae26eb9 100644 --- a/internal/types/state.go +++ b/internal/types/state.go @@ -4,13 +4,13 @@ package types type DelegationState string const ( - StatePending DelegationState = "PENDING" - StateVerified DelegationState = "VERIFIED" - StateActive DelegationState = "ACTIVE" - StateUnbonding DelegationState = "UNBONDING" - StateWithdrawn DelegationState = "WITHDRAWN" - StateSlashed DelegationState = "SLASHED" - StateUnbonded DelegationState = "UNBONDED" + StatePending DelegationState = "PENDING" + StateVerified DelegationState = "VERIFIED" + StateActive DelegationState = "ACTIVE" + StateUnbonding DelegationState = "UNBONDING" + StateWithdrawable DelegationState = "WITHDRAWABLE" + StateWithdrawn DelegationState = "WITHDRAWN" + StateSlashed DelegationState = "SLASHED" ) func (s DelegationState) String() string { diff --git a/internal/types/timelock_tx_type.go b/internal/types/timelock_tx_type.go new file mode 100644 index 0000000..ca5af2e --- /dev/null +++ b/internal/types/timelock_tx_type.go @@ -0,0 +1,11 @@ +package types + +type TimeLockTxType string + +const ( + ExpiredTxType TimeLockTxType = "EXPIRED" +) + +func (t TimeLockTxType) String() string { + return string(t) +} diff --git a/tests/mocks/mock_db_client.go b/tests/mocks/mock_db_client.go index 4f657e1..18220bf 100644 --- a/tests/mocks/mock_db_client.go +++ b/tests/mocks/mock_db_client.go @@ -21,6 +21,54 @@ type DbInterface struct { mock.Mock } +// DeleteExpiredDelegation provides a mock function with given fields: ctx, stakingTxHashHex +func (_m *DbInterface) DeleteExpiredDelegation(ctx context.Context, stakingTxHashHex string) error { + ret := _m.Called(ctx, stakingTxHashHex) + + if len(ret) == 0 { + panic("no return value specified for DeleteExpiredDelegation") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, stakingTxHashHex) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// FindExpiredDelegations provides a mock function with given fields: ctx, btcTipHeight, limit +func (_m *DbInterface) FindExpiredDelegations(ctx context.Context, btcTipHeight uint64, limit uint64) ([]model.TimeLockDocument, error) { + ret := _m.Called(ctx, btcTipHeight, limit) + + if len(ret) == 0 { + panic("no return value specified for FindExpiredDelegations") + } + + var r0 []model.TimeLockDocument + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) ([]model.TimeLockDocument, error)); ok { + return rf(ctx, btcTipHeight, limit) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) []model.TimeLockDocument); ok { + r0 = rf(ctx, btcTipHeight, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]model.TimeLockDocument) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok { + r1 = rf(ctx, btcTipHeight, limit) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetBTCDelegationByStakingTxHash provides a mock function with given fields: ctx, stakingTxHash func (_m *DbInterface) GetBTCDelegationByStakingTxHash(ctx context.Context, stakingTxHash string) (*model.BTCDelegationDetails, error) { ret := _m.Called(ctx, stakingTxHash) @@ -153,6 +201,24 @@ func (_m *DbInterface) SaveNewFinalityProvider(ctx context.Context, fpDoc *model return r0 } +// SaveNewTimeLockExpire provides a mock function with given fields: ctx, stakingTxHashHex, expireHeight, txType +func (_m *DbInterface) SaveNewTimeLockExpire(ctx context.Context, stakingTxHashHex string, expireHeight uint32, txType string) error { + ret := _m.Called(ctx, stakingTxHashHex, expireHeight, txType) + + if len(ret) == 0 { + panic("no return value specified for SaveNewTimeLockExpire") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint32, string) error); ok { + r0 = rf(ctx, stakingTxHashHex, expireHeight, txType) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // SaveStakingParams provides a mock function with given fields: ctx, version, params func (_m *DbInterface) SaveStakingParams(ctx context.Context, version uint32, params *bbnclient.StakingParams) error { ret := _m.Called(ctx, version, params)