diff --git a/internal/db/delegation.go b/internal/db/delegation.go index 8d1cb87..4764abe 100644 --- a/internal/db/delegation.go +++ b/internal/db/delegation.go @@ -113,6 +113,25 @@ func (db *Database) UpdateBTCDelegationDetails( return nil } +func (db *Database) SaveBTCDelegationUnbondingCovenantSignature( + ctx context.Context, stakingTxHash string, covenantBtcPkHex string, signatureHex string, +) error { + filter := bson.M{"_id": stakingTxHash} + update := bson.M{ + "$push": bson.M{ + "covenant_signatures": bson.M{ + "covenant_btc_pk_hex": covenantBtcPkHex, + "signature_hex": signatureHex, + }, + }, + } + _, err := db.client.Database(db.dbName). + Collection(model.BTCDelegationDetailsCollection). + UpdateOne(ctx, filter, update) + + return err +} + func (db *Database) GetBTCDelegationByStakingTxHash( ctx context.Context, stakingTxHash string, ) (*model.BTCDelegationDetails, error) { diff --git a/internal/db/interface.go b/internal/db/interface.go index 22fdd1b..70fd648 100644 --- a/internal/db/interface.go +++ b/internal/db/interface.go @@ -100,6 +100,18 @@ type DbInterface interface { UpdateBTCDelegationState( ctx context.Context, stakingTxHash string, newState types.DelegationState, ) error + /** + * SaveBTCDelegationUnbondingCovenantSignature saves a BTC delegation + * unbonding covenant signature to the database. + * @param ctx The context + * @param stakingTxHash The staking tx hash + * @param covenantBtcPkHex The covenant BTC public key + * @param signatureHex The signature + * @return An error if the operation failed + */ + SaveBTCDelegationUnbondingCovenantSignature( + ctx context.Context, stakingTxHash string, covenantBtcPkHex string, signatureHex string, + ) error /** * GetBTCDelegationState retrieves the BTC delegation state. * @param ctx The context diff --git a/internal/db/model/delegation.go b/internal/db/model/delegation.go index cbd82fc..5af0857 100644 --- a/internal/db/model/delegation.go +++ b/internal/db/model/delegation.go @@ -10,19 +10,25 @@ import ( bbntypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" ) +type CovenantSignature struct { + CovenantBtcPkHex string `bson:"covenant_btc_pk_hex"` + SignatureHex string `bson:"signature_hex"` +} + type BTCDelegationDetails struct { - StakingTxHashHex string `bson:"_id"` // Primary key - StakingTxHex string `bson:"staking_tx_hex"` - StakingTime uint32 `bson:"staking_time"` - StakingOutputIdx uint32 `bson:"staking_output_idx"` - StakerBtcPkHex string `bson:"staker_btc_pk_hex"` - FinalityProviderBtcPksHex []string `bson:"finality_provider_btc_pks_hex"` - StartHeight uint32 `bson:"start_height"` - EndHeight uint32 `bson:"end_height"` - State types.DelegationState `bson:"state"` - ParamsVersion uint32 `bson:"params_version"` - UnbondingTime uint32 `bson:"unbonding_time"` - UnbondingTx string `bson:"unbonding_tx"` + StakingTxHashHex string `bson:"_id"` // Primary key + StakingTxHex string `bson:"staking_tx_hex"` + StakingTime uint32 `bson:"staking_time"` + StakingOutputIdx uint32 `bson:"staking_output_idx"` + StakerBtcPkHex string `bson:"staker_btc_pk_hex"` + FinalityProviderBtcPksHex []string `bson:"finality_provider_btc_pks_hex"` + StartHeight uint32 `bson:"start_height"` + EndHeight uint32 `bson:"end_height"` + State types.DelegationState `bson:"state"` + ParamsVersion uint32 `bson:"params_version"` + UnbondingTime uint32 `bson:"unbonding_time"` + UnbondingTx string `bson:"unbonding_tx"` + CovenantUnbondingSignatures []CovenantSignature `bson:"covenant_unbonding_signatures"` } func FromEventBTCDelegationCreated( @@ -74,18 +80,19 @@ func FromEventBTCDelegationCreated( } return &BTCDelegationDetails{ - StakingTxHashHex: stakingTx.TxHash().String(), - StakingTxHex: event.StakingTxHex, - StakingTime: uint32(stakingTime), - StakingOutputIdx: uint32(stakingOutputIdx), - StakerBtcPkHex: event.StakerBtcPkHex, - FinalityProviderBtcPksHex: event.FinalityProviderBtcPksHex, - ParamsVersion: uint32(paramsVersion), - UnbondingTime: uint32(unbondingTime), - UnbondingTx: event.UnbondingTx, - State: types.StatePending, // initial state will always be PENDING - StartHeight: uint32(0), // it should be set when the inclusion proof is received - EndHeight: uint32(0), // it should be set when the inclusion proof is received + StakingTxHashHex: stakingTx.TxHash().String(), + StakingTxHex: event.StakingTxHex, + StakingTime: uint32(stakingTime), + StakingOutputIdx: uint32(stakingOutputIdx), + StakerBtcPkHex: event.StakerBtcPkHex, + FinalityProviderBtcPksHex: event.FinalityProviderBtcPksHex, + ParamsVersion: uint32(paramsVersion), + UnbondingTime: uint32(unbondingTime), + UnbondingTx: event.UnbondingTx, + State: types.StatePending, // initial state will always be PENDING + StartHeight: uint32(0), // it should be set when the inclusion proof is received + EndHeight: uint32(0), // it should be set when the inclusion proof is received + CovenantUnbondingSignatures: []CovenantSignature{}, }, nil } diff --git a/internal/services/delegation.go b/internal/services/delegation.go index a71768f..51a050e 100644 --- a/internal/services/delegation.go +++ b/internal/services/delegation.go @@ -16,6 +16,7 @@ import ( const ( EventBTCDelegationCreated EventTypes = "babylon.btcstaking.v1.EventBTCDelegationCreated" EventCovenantQuorumReached EventTypes = "babylon.btcstaking.v1.EventCovenantQuorumReached" + EventCovenantSignatureReceived EventTypes = "babylon.btcstaking.v1.EventCovenantSignatureReceived" EventBTCDelegationInclusionProofReceived EventTypes = "babylon.btcstaking.v1.EventBTCDelegationInclusionProofReceived" EventBTCDelgationUnbondedEarly EventTypes = "babylon.btcstaking.v1.EventBTCDelgationUnbondedEarly" EventBTCDelegationExpired EventTypes = "babylon.btcstaking.v1.EventBTCDelegationExpired" @@ -64,6 +65,53 @@ func (s *Service) processNewBTCDelegationEvent( return nil } +func (s *Service) processCovenantSignatureReceivedEvent( + ctx context.Context, event abcitypes.Event, +) *types.Error { + covenantSignatureReceivedEvent, err := parseEvent[*bbntypes.EventCovenantSignatureReceived]( + EventCovenantSignatureReceived, event, + ) + if err != nil { + return err + } + stakingTxHash := covenantSignatureReceivedEvent.StakingTxHash + delegation, dbErr := s.db.GetBTCDelegationByStakingTxHash(ctx, stakingTxHash) + if dbErr != nil { + return types.NewError( + http.StatusInternalServerError, + types.InternalServiceError, + fmt.Errorf("failed to get BTC delegation by staking tx hash: %w", dbErr), + ) + } + // Check if the covenant signature already exists, if it does, ignore the event + for _, signature := range delegation.CovenantUnbondingSignatures { + if signature.CovenantBtcPkHex == covenantSignatureReceivedEvent.CovenantBtcPkHex { + return nil + } + } + // Breakdown the covenantSignatureReceivedEvent into individual fields + covenantBtcPkHex := covenantSignatureReceivedEvent.CovenantBtcPkHex + signatureHex := covenantSignatureReceivedEvent.CovenantUnbondingSignatureHex + + if dbErr := s.db.SaveBTCDelegationUnbondingCovenantSignature( + ctx, + stakingTxHash, + covenantBtcPkHex, + signatureHex, + ); dbErr != nil { + return types.NewError( + http.StatusInternalServerError, + types.InternalServiceError, + fmt.Errorf( + "failed to save BTC delegation unbonding covenant signature: %w for staking tx hash %s", + dbErr, stakingTxHash, + ), + ) + } + + return nil +} + func (s *Service) processCovenantQuorumReachedEvent( ctx context.Context, event abcitypes.Event, ) *types.Error { diff --git a/internal/services/events.go b/internal/services/events.go index ea9347a..7ce3e1c 100644 --- a/internal/services/events.go +++ b/internal/services/events.go @@ -64,6 +64,9 @@ func (s *Service) processEvent(ctx context.Context, event BbnEvent) *types.Error case EventCovenantQuorumReached: log.Debug().Msg("Processing covenant quorum reached event") err = s.processCovenantQuorumReachedEvent(ctx, bbnEvent) + case EventCovenantSignatureReceived: + log.Debug().Msg("Processing covenant signature received event") + err = s.processCovenantSignatureReceivedEvent(ctx, bbnEvent) case EventBTCDelegationInclusionProofReceived: log.Debug().Msg("Processing BTC delegation inclusion proof received event") err = s.processBTCDelegationInclusionProofReceivedEvent(ctx, bbnEvent)