From 25888bea6edf4856295eba84c4d13f6c92005733 Mon Sep 17 00:00:00 2001 From: Crypto Minion <154598612+jrwbabylonlab@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:23:58 +1100 Subject: [PATCH] feat: phase-1 delegatoin to check if FP has been transitioned (#151) * feat: phase-1 delegatoin to return with FP status * feat: accept list of states --- docs/docs.go | 118 +++++++------- docs/swagger.json | 118 +++++++------- docs/swagger.yaml | 100 ++++++------ .../indexer/db/client/finality_provider.go | 153 ++---------------- internal/indexer/db/client/interface.go | 4 +- .../shared/api/handlers/handler/handler.go | 25 +-- internal/shared/services/services.go | 2 +- internal/v1/api/handlers/staker.go | 1 - internal/v1/service/delegation.go | 44 ++++- internal/v1/service/interface.go | 2 +- internal/v2/api/handlers/finality_provider.go | 31 ++-- internal/v2/queue/client/message.go | 4 +- internal/v2/queue/handler/handler.go | 7 - internal/v2/service/finality_provider.go | 65 +++----- internal/v2/service/interface.go | 7 +- internal/v2/service/service.go | 17 +- internal/v2/service/stats.go | 32 ++-- 17 files changed, 312 insertions(+), 418 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 41e6c5ed..265268cb 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -181,7 +181,6 @@ const docTemplate = `{ "tags": [ "v1" ], - "deprecated": true, "parameters": [ { "type": "string", @@ -502,7 +501,7 @@ const docTemplate = `{ }, "/v2/finality-providers": { "get": { - "description": "Fetches finality providers with optional filtering and pagination", + "description": "Fetches finality providers with its stats, currently does not support pagination", "produces": [ "application/json" ], @@ -510,45 +509,27 @@ const docTemplate = `{ "v2" ], "summary": "List Finality Providers", - "parameters": [ - { - "type": "string", - "description": "Pagination key to fetch the next page", - "name": "pagination_key", - "in": "query" - }, - { - "enum": [ - "active", - "standby" - ], - "type": "string", - "description": "Filter by state", - "name": "state", - "in": "query" - } - ], "responses": { "200": { - "description": "List of finality providers and pagination token", + "description": "List of finality providers with its stats", "schema": { - "$ref": "#/definitions/handler.PublicResponse-array_v2service_FinalityProviderPublic" + "$ref": "#/definitions/handler.PublicResponse-array_v2service_FinalityProviderStatsPublic" } }, "400": { - "description": "Error: Bad Request", + "description": "Invalid parameters or malformed request", "schema": { "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } }, "404": { - "description": "Error: Not Found", + "description": "No finality providers found", "schema": { "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } }, "500": { - "description": "Error: Internal Server Error", + "description": "Internal server error occurred", "schema": { "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } @@ -707,13 +688,13 @@ const docTemplate = `{ } } }, - "handler.PublicResponse-array_v2service_FinalityProviderPublic": { + "handler.PublicResponse-array_v2service_FinalityProviderStatsPublic": { "type": "object", "properties": { "data": { "type": "array", "items": { - "$ref": "#/definitions/v2service.FinalityProviderPublic" + "$ref": "#/definitions/v2service.FinalityProviderStatsPublic" } }, "pagination": { @@ -893,27 +874,6 @@ const docTemplate = `{ } } }, - "indexertypes.DelegationState": { - "type": "string", - "enum": [ - "PENDING", - "VERIFIED", - "ACTIVE", - "UNBONDING", - "WITHDRAWABLE", - "WITHDRAWN", - "SLASHED" - ], - "x-enum-varnames": [ - "StatePending", - "StateVerified", - "StateActive", - "StateUnbonding", - "StateWithdrawable", - "StateWithdrawn", - "StateSlashed" - ] - }, "types.ErrorCode": { "type": "string", "enum": [ @@ -1006,6 +966,9 @@ const docTemplate = `{ "is_overflow": { "type": "boolean" }, + "is_slashed": { + "type": "boolean" + }, "staker_pk_hex": { "type": "string" }, @@ -1213,9 +1176,18 @@ const docTemplate = `{ "v2service.DelegationStaking": { "type": "object", "properties": { + "bbn_inception_height": { + "type": "integer" + }, + "bbn_inception_time": { + "type": "integer" + }, "end_height": { "type": "integer" }, + "slashing_tx_hex": { + "type": "string" + }, "staking_amount": { "type": "integer" }, @@ -1242,6 +1214,9 @@ const docTemplate = `{ "$ref": "#/definitions/v2service.CovenantSignature" } }, + "slashing_tx_hex": { + "type": "string" + }, "unbonding_time": { "type": "integer" }, @@ -1250,7 +1225,7 @@ const docTemplate = `{ } } }, - "v2service.FinalityProviderPublic": { + "v2service.FinalityProviderStatsPublic": { "type": "object", "properties": { "active_delegations": { @@ -1270,12 +1245,6 @@ const docTemplate = `{ }, "state": { "$ref": "#/definitions/types.FinalityProviderQueryingState" - }, - "total_delegations": { - "type": "integer" - }, - "total_tvl": { - "type": "integer" } } }, @@ -1350,7 +1319,7 @@ const docTemplate = `{ "type": "string" }, "state": { - "$ref": "#/definitions/indexertypes.DelegationState" + "$ref": "#/definitions/v2types.DelegationState" } } }, @@ -1379,6 +1348,43 @@ const docTemplate = `{ "type": "integer" } } + }, + "v2types.DelegationState": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "ACTIVE", + "TIMELOCK_UNBONDING", + "EARLY_UNBONDING", + "TIMELOCK_WITHDRAWABLE", + "EARLY_UNBONDING_WITHDRAWABLE", + "TIMELOCK_SLASHING_WITHDRAWABLE", + "EARLY_UNBONDING_SLASHING_WITHDRAWABLE", + "TIMELOCK_WITHDRAWN", + "EARLY_UNBONDING_WITHDRAWN", + "TIMELOCK_SLASHING_WITHDRAWN", + "EARLY_UNBONDING_SLASHING_WITHDRAWN", + "TIMELOCK_SLASHED", + "EARLY_UNBONDING_SLASHED" + ], + "x-enum-varnames": [ + "StatePending", + "StateVerified", + "StateActive", + "StateTimelockUnbonding", + "StateEarlyUnbonding", + "StateTimelockWithdrawable", + "StateEarlyUnbondingWithdrawable", + "StateTimelockSlashingWithdrawable", + "StateEarlyUnbondingSlashingWithdrawable", + "StateTimelockWithdrawn", + "StateEarlyUnbondingWithdrawn", + "StateTimelockSlashingWithdrawn", + "StateEarlyUnbondingSlashingWithdrawn", + "StateTimelockSlashed", + "StateEarlyUnbondingSlashed" + ] } } }` diff --git a/docs/swagger.json b/docs/swagger.json index 01be11ea..1e741de5 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -173,7 +173,6 @@ "tags": [ "v1" ], - "deprecated": true, "parameters": [ { "type": "string", @@ -494,7 +493,7 @@ }, "/v2/finality-providers": { "get": { - "description": "Fetches finality providers with optional filtering and pagination", + "description": "Fetches finality providers with its stats, currently does not support pagination", "produces": [ "application/json" ], @@ -502,45 +501,27 @@ "v2" ], "summary": "List Finality Providers", - "parameters": [ - { - "type": "string", - "description": "Pagination key to fetch the next page", - "name": "pagination_key", - "in": "query" - }, - { - "enum": [ - "active", - "standby" - ], - "type": "string", - "description": "Filter by state", - "name": "state", - "in": "query" - } - ], "responses": { "200": { - "description": "List of finality providers and pagination token", + "description": "List of finality providers with its stats", "schema": { - "$ref": "#/definitions/handler.PublicResponse-array_v2service_FinalityProviderPublic" + "$ref": "#/definitions/handler.PublicResponse-array_v2service_FinalityProviderStatsPublic" } }, "400": { - "description": "Error: Bad Request", + "description": "Invalid parameters or malformed request", "schema": { "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } }, "404": { - "description": "Error: Not Found", + "description": "No finality providers found", "schema": { "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } }, "500": { - "description": "Error: Internal Server Error", + "description": "Internal server error occurred", "schema": { "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } @@ -699,13 +680,13 @@ } } }, - "handler.PublicResponse-array_v2service_FinalityProviderPublic": { + "handler.PublicResponse-array_v2service_FinalityProviderStatsPublic": { "type": "object", "properties": { "data": { "type": "array", "items": { - "$ref": "#/definitions/v2service.FinalityProviderPublic" + "$ref": "#/definitions/v2service.FinalityProviderStatsPublic" } }, "pagination": { @@ -885,27 +866,6 @@ } } }, - "indexertypes.DelegationState": { - "type": "string", - "enum": [ - "PENDING", - "VERIFIED", - "ACTIVE", - "UNBONDING", - "WITHDRAWABLE", - "WITHDRAWN", - "SLASHED" - ], - "x-enum-varnames": [ - "StatePending", - "StateVerified", - "StateActive", - "StateUnbonding", - "StateWithdrawable", - "StateWithdrawn", - "StateSlashed" - ] - }, "types.ErrorCode": { "type": "string", "enum": [ @@ -998,6 +958,9 @@ "is_overflow": { "type": "boolean" }, + "is_slashed": { + "type": "boolean" + }, "staker_pk_hex": { "type": "string" }, @@ -1205,9 +1168,18 @@ "v2service.DelegationStaking": { "type": "object", "properties": { + "bbn_inception_height": { + "type": "integer" + }, + "bbn_inception_time": { + "type": "integer" + }, "end_height": { "type": "integer" }, + "slashing_tx_hex": { + "type": "string" + }, "staking_amount": { "type": "integer" }, @@ -1234,6 +1206,9 @@ "$ref": "#/definitions/v2service.CovenantSignature" } }, + "slashing_tx_hex": { + "type": "string" + }, "unbonding_time": { "type": "integer" }, @@ -1242,7 +1217,7 @@ } } }, - "v2service.FinalityProviderPublic": { + "v2service.FinalityProviderStatsPublic": { "type": "object", "properties": { "active_delegations": { @@ -1262,12 +1237,6 @@ }, "state": { "$ref": "#/definitions/types.FinalityProviderQueryingState" - }, - "total_delegations": { - "type": "integer" - }, - "total_tvl": { - "type": "integer" } } }, @@ -1342,7 +1311,7 @@ "type": "string" }, "state": { - "$ref": "#/definitions/indexertypes.DelegationState" + "$ref": "#/definitions/v2types.DelegationState" } } }, @@ -1371,6 +1340,43 @@ "type": "integer" } } + }, + "v2types.DelegationState": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "ACTIVE", + "TIMELOCK_UNBONDING", + "EARLY_UNBONDING", + "TIMELOCK_WITHDRAWABLE", + "EARLY_UNBONDING_WITHDRAWABLE", + "TIMELOCK_SLASHING_WITHDRAWABLE", + "EARLY_UNBONDING_SLASHING_WITHDRAWABLE", + "TIMELOCK_WITHDRAWN", + "EARLY_UNBONDING_WITHDRAWN", + "TIMELOCK_SLASHING_WITHDRAWN", + "EARLY_UNBONDING_SLASHING_WITHDRAWN", + "TIMELOCK_SLASHED", + "EARLY_UNBONDING_SLASHED" + ], + "x-enum-varnames": [ + "StatePending", + "StateVerified", + "StateActive", + "StateTimelockUnbonding", + "StateEarlyUnbonding", + "StateTimelockWithdrawable", + "StateEarlyUnbondingWithdrawable", + "StateTimelockSlashingWithdrawable", + "StateEarlyUnbondingSlashingWithdrawable", + "StateTimelockWithdrawn", + "StateEarlyUnbondingWithdrawn", + "StateTimelockSlashingWithdrawn", + "StateEarlyUnbondingSlashingWithdrawn", + "StateTimelockSlashed", + "StateEarlyUnbondingSlashed" + ] } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 3c42abbf..6a2a6bb0 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -34,11 +34,11 @@ definitions: pagination: $ref: '#/definitions/handler.paginationResponse' type: object - handler.PublicResponse-array_v2service_FinalityProviderPublic: + handler.PublicResponse-array_v2service_FinalityProviderStatsPublic: properties: data: items: - $ref: '#/definitions/v2service.FinalityProviderPublic' + $ref: '#/definitions/v2service.FinalityProviderStatsPublic' type: array pagination: $ref: '#/definitions/handler.paginationResponse' @@ -154,24 +154,6 @@ definitions: version: type: integer type: object - indexertypes.DelegationState: - enum: - - PENDING - - VERIFIED - - ACTIVE - - UNBONDING - - WITHDRAWABLE - - WITHDRAWN - - SLASHED - type: string - x-enum-varnames: - - StatePending - - StateVerified - - StateActive - - StateUnbonding - - StateWithdrawable - - StateWithdrawn - - StateSlashed types.ErrorCode: enum: - INTERNAL_SERVICE_ERROR @@ -237,6 +219,8 @@ definitions: type: boolean is_overflow: type: boolean + is_slashed: + type: boolean staker_pk_hex: type: string staking_tx: @@ -372,8 +356,14 @@ definitions: type: object v2service.DelegationStaking: properties: + bbn_inception_height: + type: integer + bbn_inception_time: + type: integer end_height: type: integer + slashing_tx_hex: + type: string staking_amount: type: integer staking_time: @@ -391,12 +381,14 @@ definitions: items: $ref: '#/definitions/v2service.CovenantSignature' type: array + slashing_tx_hex: + type: string unbonding_time: type: integer unbonding_tx: type: string type: object - v2service.FinalityProviderPublic: + v2service.FinalityProviderStatsPublic: properties: active_delegations: type: integer @@ -410,10 +402,6 @@ definitions: $ref: '#/definitions/types.FinalityProviderDescription' state: $ref: '#/definitions/types.FinalityProviderQueryingState' - total_delegations: - type: integer - total_tvl: - type: integer type: object v2service.OverallStatsPublic: properties: @@ -462,7 +450,7 @@ definitions: staker_btc_pk_hex: type: string state: - $ref: '#/definitions/indexertypes.DelegationState' + $ref: '#/definitions/v2types.DelegationState' type: object v2service.StakerStatsPublic: properties: @@ -481,6 +469,40 @@ definitions: withdrawable_tvl: type: integer type: object + v2types.DelegationState: + enum: + - PENDING + - VERIFIED + - ACTIVE + - TIMELOCK_UNBONDING + - EARLY_UNBONDING + - TIMELOCK_WITHDRAWABLE + - EARLY_UNBONDING_WITHDRAWABLE + - TIMELOCK_SLASHING_WITHDRAWABLE + - EARLY_UNBONDING_SLASHING_WITHDRAWABLE + - TIMELOCK_WITHDRAWN + - EARLY_UNBONDING_WITHDRAWN + - TIMELOCK_SLASHING_WITHDRAWN + - EARLY_UNBONDING_SLASHING_WITHDRAWN + - TIMELOCK_SLASHED + - EARLY_UNBONDING_SLASHED + type: string + x-enum-varnames: + - StatePending + - StateVerified + - StateActive + - StateTimelockUnbonding + - StateEarlyUnbonding + - StateTimelockWithdrawable + - StateEarlyUnbondingWithdrawable + - StateTimelockSlashingWithdrawable + - StateEarlyUnbondingSlashingWithdrawable + - StateTimelockWithdrawn + - StateEarlyUnbondingWithdrawn + - StateTimelockSlashingWithdrawn + - StateEarlyUnbondingSlashingWithdrawn + - StateTimelockSlashed + - StateEarlyUnbondingSlashed info: contact: email: contact@babylonlabs.io @@ -599,7 +621,6 @@ paths: - v1 /v1/staker/delegations: get: - deprecated: true description: Retrieves delegations for a given staker parameters: - description: Staker BTC Public Key @@ -825,36 +846,25 @@ paths: - v2 /v2/finality-providers: get: - description: Fetches finality providers with optional filtering and pagination - parameters: - - description: Pagination key to fetch the next page - in: query - name: pagination_key - type: string - - description: Filter by state - enum: - - active - - standby - in: query - name: state - type: string + description: Fetches finality providers with its stats, currently does not support + pagination produces: - application/json responses: "200": - description: List of finality providers and pagination token + description: List of finality providers with its stats schema: - $ref: '#/definitions/handler.PublicResponse-array_v2service_FinalityProviderPublic' + $ref: '#/definitions/handler.PublicResponse-array_v2service_FinalityProviderStatsPublic' "400": - description: 'Error: Bad Request' + description: Invalid parameters or malformed request schema: $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' "404": - description: 'Error: Not Found' + description: No finality providers found schema: $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' "500": - description: 'Error: Internal Server Error' + description: Internal server error occurred schema: $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' summary: List Finality Providers diff --git a/internal/indexer/db/client/finality_provider.go b/internal/indexer/db/client/finality_provider.go index 496df7e2..0aa2075a 100644 --- a/internal/indexer/db/client/finality_provider.go +++ b/internal/indexer/db/client/finality_provider.go @@ -4,11 +4,7 @@ import ( "context" indexerdbmodel "github.com/babylonlabs-io/staking-api-service/internal/indexer/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/shared/db" - dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo/options" ) // GetFinalityProviderByPk retrieves a single finality provider by their primary key @@ -16,13 +12,12 @@ func (indexerdbclient *IndexerDatabase) GetFinalityProviderByPk( ctx context.Context, fpPk string, ) (*indexerdbmodel.IndexerFinalityProviderDetails, error) { - client := indexerdbclient.Client.Database(indexerdbclient.DbName).Collection(indexerdbmodel.FinalityProviderDetailsCollection) - - filter := bson.M{} - filter = indexerdbclient.applyFpPkFilter(filter, fpPk) + client := indexerdbclient.Client.Database( + indexerdbclient.DbName, + ).Collection(indexerdbmodel.FinalityProviderDetailsCollection) var result indexerdbmodel.IndexerFinalityProviderDetails - err := client.FindOne(ctx, filter).Decode(&result) + err := client.FindOne(ctx, bson.M{"_id": fpPk}).Decode(&result) if err != nil { return nil, err } @@ -33,139 +28,21 @@ func (indexerdbclient *IndexerDatabase) GetFinalityProviderByPk( // GetFinalityProviders retrieves finality providers filtered by state func (indexerdbclient *IndexerDatabase) GetFinalityProviders( ctx context.Context, - state types.FinalityProviderQueryingState, - paginationToken string, -) (*db.DbResultMap[indexerdbmodel.IndexerFinalityProviderDetails], error) { - client := indexerdbclient.Client.Database(indexerdbclient.DbName).Collection(indexerdbmodel.FinalityProviderDetailsCollection) - - filter := bson.M{} - filter = indexerdbclient.applyStateFilter(filter, state) - filter = indexerdbclient.applyPaginationFilter(filter, paginationToken) - - options := options.Find().SetSort(bson.D{ - {Key: "commission", Value: 1}, - {Key: "_id", Value: 1}, - }) - - return db.FindWithPagination( - ctx, client, filter, options, indexerdbclient.Cfg.MaxPaginationLimit, - indexerdbmodel.BuildFinalityProviderPaginationToken, - ) -} - -// SearchFinalityProviders performs a text search across finality providers -func (indexerdbclient *IndexerDatabase) SearchFinalityProviders( - ctx context.Context, - searchQuery string, - paginationToken string, -) (*db.DbResultMap[indexerdbmodel.IndexerFinalityProviderDetails], error) { - client := indexerdbclient.Client.Database(indexerdbclient.DbName).Collection(indexerdbmodel.FinalityProviderDetailsCollection) - - filter := indexerdbclient.applySearchFilter(bson.M{}, searchQuery) - filter = indexerdbclient.applyPaginationFilter(filter, paginationToken) - - options := options.Find().SetSort(bson.D{ - {Key: "commission", Value: 1}, - {Key: "_id", Value: 1}, - }) - - return db.FindWithPagination( - ctx, client, filter, options, indexerdbclient.Cfg.MaxPaginationLimit, - indexerdbmodel.BuildFinalityProviderPaginationToken, - ) -} - -func (indexerdbclient *IndexerDatabase) applyFpPkFilter(filter bson.M, fpPk string) bson.M { - if fpPk != "" { - filter["_id"] = fpPk - } - return filter -} - -func (indexerdbclient *IndexerDatabase) applyMonikerFilters(filter bson.M, moniker string) bson.M { - if moniker != "" { - filter["description.moniker"] = moniker - } - return filter -} - -func (indexerdbclient *IndexerDatabase) applyStateFilter(filter bson.M, state types.FinalityProviderQueryingState) bson.M { - if state == types.FinalityProviderStateActive { - filter["state"] = indexerdbmodel.FinalityProviderStatus_FINALITY_PROVIDER_STATUS_ACTIVE - } else if state == types.FinalityProviderStateStandby { - filter["state"] = bson.M{ - "$in": []indexerdbmodel.FinalityProviderState{ - indexerdbmodel.FinalityProviderStatus_FINALITY_PROVIDER_STATUS_INACTIVE, - indexerdbmodel.FinalityProviderStatus_FINALITY_PROVIDER_STATUS_JAILED, - indexerdbmodel.FinalityProviderStatus_FINALITY_PROVIDER_STATUS_SLASHED, - }, - } - } - return filter -} - -func (indexerdbclient *IndexerDatabase) applySearchFilter(filter bson.M, searchQuery string) bson.M { - if searchQuery == "" { - return filter - } - - searchFilter := bson.M{ - "$or": []bson.M{ - { - "_id": bson.M{ - "$regex": searchQuery, - "$options": "i", // case-insensitive - }, - }, - { - "description.moniker": bson.M{ - "$regex": searchQuery, - "$options": "i", // case-insensitive - }, - }, - }, - } +) ([]*indexerdbmodel.IndexerFinalityProviderDetails, error) { + client := indexerdbclient.Client.Database( + indexerdbclient.DbName, + ).Collection(indexerdbmodel.FinalityProviderDetailsCollection) - if len(filter) > 0 { - return bson.M{ - "$and": []bson.M{ - filter, - searchFilter, - }, - } - } - return searchFilter -} - -func (indexerdbclient *IndexerDatabase) applyPaginationFilter(filter bson.M, paginationToken string) bson.M { - if paginationToken == "" { - return filter - } - - decodedToken, err := dbmodel.DecodePaginationToken[indexerdbmodel.IndexerFinalityProviderPagination](paginationToken) + cursor, err := client.Find(ctx, bson.M{}) if err != nil { - return filter + return nil, err } + defer cursor.Close(ctx) - paginationFilter := bson.M{ - "$or": []bson.M{ - { - "commission": decodedToken.Commission, - "_id": bson.M{"$gt": decodedToken.BtcPk}, - }, - { - "commission": bson.M{"$gt": decodedToken.Commission}, - }, - }, + var results []*indexerdbmodel.IndexerFinalityProviderDetails + if err := cursor.All(ctx, &results); err != nil { + return nil, err } - if len(filter) > 0 { - return bson.M{ - "$and": []bson.M{ - filter, - paginationFilter, - }, - } - } - return paginationFilter + return results, nil } diff --git a/internal/indexer/db/client/interface.go b/internal/indexer/db/client/interface.go index bba600e8..385ff40d 100644 --- a/internal/indexer/db/client/interface.go +++ b/internal/indexer/db/client/interface.go @@ -6,7 +6,6 @@ import ( indexerdbmodel "github.com/babylonlabs-io/staking-api-service/internal/indexer/db/model" indexertypes "github.com/babylonlabs-io/staking-api-service/internal/indexer/types" "github.com/babylonlabs-io/staking-api-service/internal/shared/db" - "github.com/babylonlabs-io/staking-api-service/internal/shared/types" ) type IndexerDBClient interface { @@ -15,8 +14,7 @@ type IndexerDBClient interface { GetBbnStakingParams(ctx context.Context) ([]*indexertypes.BbnStakingParams, error) GetBtcCheckpointParams(ctx context.Context) ([]*indexertypes.BtcCheckpointParams, error) // Finality Providers - GetFinalityProviders(ctx context.Context, state types.FinalityProviderQueryingState, paginationToken string) (*db.DbResultMap[indexerdbmodel.IndexerFinalityProviderDetails], error) - SearchFinalityProviders(ctx context.Context, searchQuery string, paginationToken string) (*db.DbResultMap[indexerdbmodel.IndexerFinalityProviderDetails], error) + GetFinalityProviders(ctx context.Context) ([]*indexerdbmodel.IndexerFinalityProviderDetails, error) GetFinalityProviderByPk(ctx context.Context, fpPk string) (*indexerdbmodel.IndexerFinalityProviderDetails, error) // Staker Delegations GetDelegation(ctx context.Context, stakingTxHashHex string) (*indexerdbmodel.IndexerDelegationDetails, error) diff --git a/internal/shared/api/handlers/handler/handler.go b/internal/shared/api/handlers/handler/handler.go index d51094c5..9a1a8a81 100644 --- a/internal/shared/api/handlers/handler/handler.go +++ b/internal/shared/api/handlers/handler/handler.go @@ -155,18 +155,23 @@ func ParseBtcAddressesQuery( // If the state is not provided, it returns an empty string func ParseStateFilterQuery( r *http.Request, queryName string, -) (types.DelegationState, *types.Error) { - state := r.URL.Query().Get(queryName) - if state == "" { - return "", nil +) ([]types.DelegationState, *types.Error) { + states := r.URL.Query()[queryName] + if len(states) == 0 { + return nil, nil } - stateEnum, err := types.FromStringToDelegationState(state) - if err != nil { - return "", types.NewErrorWithMsg( - http.StatusBadRequest, types.BadRequest, err.Error(), - ) + + var stateEnums []types.DelegationState + for _, state := range states { + stateEnum, err := types.FromStringToDelegationState(state) + if err != nil { + return nil, types.NewErrorWithMsg( + http.StatusBadRequest, types.BadRequest, err.Error(), + ) + } + stateEnums = append(stateEnums, stateEnum) } - return stateEnum, nil + return stateEnums, nil } func ParseFPSearchQuery(r *http.Request, queryName string, isOptional bool) (string, *types.Error) { diff --git a/internal/shared/services/services.go b/internal/shared/services/services.go index bfc59211..c29c1a76 100644 --- a/internal/shared/services/services.go +++ b/internal/shared/services/services.go @@ -34,7 +34,7 @@ func New( if err != nil { return nil, err } - v2Service, err := v2service.New(ctx, cfg, globalParams, finalityProviders, clients, dbClients) + v2Service, err := v2service.New(ctx, cfg, clients, dbClients) if err != nil { return nil, err } diff --git a/internal/v1/api/handlers/staker.go b/internal/v1/api/handlers/staker.go index 198b8bb9..b75f944b 100644 --- a/internal/v1/api/handlers/staker.go +++ b/internal/v1/api/handlers/staker.go @@ -17,7 +17,6 @@ type DelegationCheckPublicResponse struct { // @Description Retrieves delegations for a given staker // @Produce json // @Tags v1 -// @Deprecated // @Param staker_btc_pk query string true "Staker BTC Public Key" // @Param state query types.DelegationState false "Filter by state" // @Param pagination_key query string false "Pagination key to fetch the next page of delegations" diff --git a/internal/v1/service/delegation.go b/internal/v1/service/delegation.go index cc460ac4..ddd66b26 100644 --- a/internal/v1/service/delegation.go +++ b/internal/v1/service/delegation.go @@ -4,6 +4,7 @@ import ( "context" "net/http" + indexerdbmodel "github.com/babylonlabs-io/staking-api-service/internal/indexer/db/model" "github.com/babylonlabs-io/staking-api-service/internal/shared/db" "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" @@ -30,16 +31,17 @@ type DelegationPublic struct { UnbondingTx *TransactionPublic `json:"unbonding_tx,omitempty"` IsOverflow bool `json:"is_overflow"` IsEligibleForTransition bool `json:"is_eligible_for_transition"` + IsSlashed bool `json:"is_slashed"` } func (s *V1Service) DelegationsByStakerPk( ctx context.Context, stakerPk string, - state types.DelegationState, pageToken string, + states []types.DelegationState, pageToken string, ) ([]*DelegationPublic, string, *types.Error) { filter := &v1dbclient.DelegationFilter{} - if state != "" { + if len(states) > 0 { filter = &v1dbclient.DelegationFilter{ - States: []types.DelegationState{state}, + States: states, } } @@ -59,8 +61,15 @@ func (s *V1Service) DelegationsByStakerPk( return nil, "", types.NewInternalServiceError(err) } + // Get list of all finality providers in phase-2 + transitionedFps, err := s.Service.DbClients.IndexerDBClient.GetFinalityProviders(ctx) + if err != nil { + log.Ctx(ctx).Error().Err(err).Msg("Failed to get finality providers") + return nil, "", types.NewInternalServiceError(err) + } + for _, d := range resultMap.Data { - delegations = append(delegations, s.FromDelegationDocument(&d, bbnHeight)) + delegations = append(delegations, s.FromDelegationDocument(&d, bbnHeight, transitionedFps)) } return delegations, resultMap.PaginationToken, nil } @@ -117,7 +126,15 @@ func (s *V1Service) GetDelegation(ctx context.Context, txHashHex string) (*Deleg log.Ctx(ctx).Error().Err(err).Msg("Failed to get last processed BBN height") return nil, types.NewInternalServiceError(err) } - return s.FromDelegationDocument(delegation, bbnHeight), nil + + // Get list of all finality providers in phase-2 + transitionedFps, err := s.Service.DbClients.IndexerDBClient.GetFinalityProviders(ctx) + if err != nil { + log.Ctx(ctx).Error().Err(err).Msg("Failed to get finality providers") + return nil, types.NewInternalServiceError(err) + } + + return s.FromDelegationDocument(delegation, bbnHeight, transitionedFps), nil } func (s *V1Service) CheckStakerHasActiveDelegationByPk( @@ -137,6 +154,18 @@ func (s *V1Service) CheckStakerHasActiveDelegationByPk( return hasDelegation, nil } +// This method checks if the finality provider is slashed and whether it is in the transitioned list +func (s *V1Service) checkFpStatus( + fpPk string, transitionedFps []*indexerdbmodel.IndexerFinalityProviderDetails, +) (bool, bool) { + for _, fp := range transitionedFps { + if fp.BtcPk == fpPk { + return true, fp.State == indexerdbmodel.FinalityProviderStatus_FINALITY_PROVIDER_STATUS_SLASHED + } + } + return false, false +} + func (s *V1Service) isEligibleForTransition( delegation *v1model.DelegationDocument, bbnHeight uint64, ) bool { @@ -164,7 +193,9 @@ func (s *V1Service) isEligibleForTransition( func (s *V1Service) FromDelegationDocument( d *v1model.DelegationDocument, bbnHeight uint64, + transitionedFps []*indexerdbmodel.IndexerFinalityProviderDetails, ) *DelegationPublic { + isTransitioned, isSlashed := s.checkFpStatus(d.FinalityProviderPkHex, transitionedFps) delPublic := &DelegationPublic{ StakingTxHashHex: d.StakingTxHashHex, StakerPkHex: d.StakerPkHex, @@ -179,7 +210,8 @@ func (s *V1Service) FromDelegationDocument( TimeLock: d.StakingTx.TimeLock, }, IsOverflow: d.IsOverflow, - IsEligibleForTransition: s.isEligibleForTransition(d, bbnHeight), + IsEligibleForTransition: isTransitioned && !isSlashed && s.isEligibleForTransition(d, bbnHeight), + IsSlashed: isSlashed, } // Add unbonding transaction if it exists diff --git a/internal/v1/service/interface.go b/internal/v1/service/interface.go index 2206c477..0ccee805 100644 --- a/internal/v1/service/interface.go +++ b/internal/v1/service/interface.go @@ -10,7 +10,7 @@ import ( type V1ServiceProvider interface { service.SharedServiceProvider // Delegation - DelegationsByStakerPk(ctx context.Context, stakerPk string, state types.DelegationState, pageToken string) ([]*DelegationPublic, string, *types.Error) + DelegationsByStakerPk(ctx context.Context, stakerPk string, states []types.DelegationState, pageToken string) ([]*DelegationPublic, string, *types.Error) SaveActiveStakingDelegation(ctx context.Context, txHashHex, stakerPkHex, finalityProviderPkHex string, value, startHeight uint64, stakingTimestamp int64, timeLock, stakingOutputIndex uint64, stakingTxHex string, isOverflow bool) *types.Error IsDelegationPresent(ctx context.Context, txHashHex string) (bool, *types.Error) GetDelegation(ctx context.Context, txHashHex string) (*DelegationPublic, *types.Error) diff --git a/internal/v2/api/handlers/finality_provider.go b/internal/v2/api/handlers/finality_provider.go index 8b926db5..176bd702 100644 --- a/internal/v2/api/handlers/finality_provider.go +++ b/internal/v2/api/handlers/finality_provider.go @@ -7,34 +7,23 @@ import ( "github.com/babylonlabs-io/staking-api-service/internal/shared/types" ) -// GetFinalityProviders gets a list of finality providers with optional filters +// GetFinalityProviders gets a list of finality providers with its stats // @Summary List Finality Providers -// @Description Fetches finality providers with optional filtering and pagination +// @Description Fetches finality providers with its stats, currently does not support pagination +// the response contains a field for pagination token, but it's not used yet +// this is for the future when we will support pagination // @Produce json // @Tags v2 -// @Param pagination_key query string false "Pagination key to fetch the next page" -// @Param state query string false "Filter by state" Enums(active, standby) -// @Success 200 {object} handler.PublicResponse[[]v2service.FinalityProviderPublic]{array} "List of finality providers and pagination token" -// @Failure 400 {object} types.Error "Error: Bad Request" -// @Failure 404 {object} types.Error "Error: Not Found" -// @Failure 500 {object} types.Error "Error: Internal Server Error" +// @Success 200 {object} handler.PublicResponse[[]v2service.FinalityProviderStatsPublic] "List of finality providers with its stats" +// @Failure 400 {object} types.Error "Invalid parameters or malformed request" +// @Failure 404 {object} types.Error "No finality providers found" +// @Failure 500 {object} types.Error "Internal server error occurred" // @Router /v2/finality-providers [get] func (h *V2Handler) GetFinalityProviders(request *http.Request) (*handler.Result, *types.Error) { - state, err := handler.ParseFPStateQuery(request, true) - if err != nil { - return nil, err - } - - paginationKey, err := handler.ParsePaginationQuery(request) - if err != nil { - return nil, err - } - - // Get all finality providers with optional state filter - providers, paginationToken, err := h.Service.GetFinalityProviders(request.Context(), state, paginationKey) + providers, err := h.Service.GetFinalityProvidersWithStats(request.Context()) if err != nil { return nil, err } - return handler.NewResultWithPagination(providers, paginationToken), nil + return handler.NewResultWithPagination(providers, ""), nil } diff --git a/internal/v2/queue/client/message.go b/internal/v2/queue/client/message.go index 2982cdae..a5a41573 100644 --- a/internal/v2/queue/client/message.go +++ b/internal/v2/queue/client/message.go @@ -11,14 +11,14 @@ func (q *V2QueueClient) StartReceivingMessages() { log.Printf("Starting to receive messages from verified staking queue") queueclient.StartQueueMessageProcessing( q.VerifiedStakingEventQueueClient, - q.Handler.VerifiedStakingHandler, q.Handler.HandleUnprocessedMessage, + q.Handler.VerifiedStakingHandler, nil, q.MaxRetryAttempts, q.ProcessingTimeout, ) log.Printf("Starting to receive messages from pending staking queue") queueclient.StartQueueMessageProcessing( q.PendingStakingEventQueueClient, - q.Handler.PendingStakingHandler, q.Handler.HandleUnprocessedMessage, + q.Handler.PendingStakingHandler, nil, q.MaxRetryAttempts, q.ProcessingTimeout, ) diff --git a/internal/v2/queue/handler/handler.go b/internal/v2/queue/handler/handler.go index c86fdd91..e8507883 100644 --- a/internal/v2/queue/handler/handler.go +++ b/internal/v2/queue/handler/handler.go @@ -1,10 +1,7 @@ package v2queuehandler import ( - "context" - queuehandler "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/handler" - "github.com/babylonlabs-io/staking-api-service/internal/shared/types" v2service "github.com/babylonlabs-io/staking-api-service/internal/v2/service" ) @@ -19,7 +16,3 @@ func New(queueHandler *queuehandler.QueueHandler, service v2service.V2ServicePro Service: service, } } - -func (qh *V2QueueHandler) HandleUnprocessedMessage(ctx context.Context, messageBody, receipt string) *types.Error { - return qh.Service.SaveUnprocessableMessages(ctx, messageBody, receipt) -} diff --git a/internal/v2/service/finality_provider.go b/internal/v2/service/finality_provider.go index d9fa091c..7d9dbc12 100644 --- a/internal/v2/service/finality_provider.go +++ b/internal/v2/service/finality_provider.go @@ -10,67 +10,52 @@ import ( "github.com/rs/zerolog/log" ) -type FinalityProviderPublic struct { +type FinalityProviderStatsPublic struct { BtcPk string `json:"btc_pk"` State types.FinalityProviderQueryingState `json:"state"` Description types.FinalityProviderDescription `json:"description"` Commission string `json:"commission"` ActiveTvl int64 `json:"active_tvl"` - TotalTvl int64 `json:"total_tvl"` ActiveDelegations int64 `json:"active_delegations"` - TotalDelegations int64 `json:"total_delegations"` } -type FinalityProvidersPublic struct { - FinalityProviders []FinalityProviderPublic `json:"finality_providers"` +type FinalityProvidersStatsPublic struct { + FinalityProviders []FinalityProviderStatsPublic `json:"finality_providers"` } -func mapToFinalityProviderPublic(provider indexerdbmodel.IndexerFinalityProviderDetails) *FinalityProviderPublic { - return &FinalityProviderPublic{ - BtcPk: provider.BtcPk, - State: types.FinalityProviderQueryingState(provider.State), - Description: types.FinalityProviderDescription(provider.Description), - Commission: provider.Commission, - // TODO: add active_tvl, total_tvl, active_delegations, total_delegations from statistic data field +func mapToFinalityProviderStatsPublic(provider indexerdbmodel.IndexerFinalityProviderDetails) *FinalityProviderStatsPublic { + return &FinalityProviderStatsPublic{ + BtcPk: provider.BtcPk, + State: types.FinalityProviderQueryingState(provider.State), + Description: types.FinalityProviderDescription(provider.Description), + Commission: provider.Commission, ActiveTvl: 0, - TotalTvl: 0, ActiveDelegations: 0, - TotalDelegations: 0, } } -// GetFinalityProviders gets a list of finality providers with optional filters -func (s *V2Service) GetFinalityProviders(ctx context.Context, state types.FinalityProviderQueryingState, paginationKey string) ([]*FinalityProviderPublic, string, *types.Error) { - resultMap, err := s.DbClients.IndexerDBClient.GetFinalityProviders(ctx, state, paginationKey) +// GetFinalityProviders gets a list of finality providers with stats +func (s *V2Service) GetFinalityProvidersWithStats( + ctx context.Context, +) ([]*FinalityProviderStatsPublic, *types.Error) { + fps, err := s.DbClients.IndexerDBClient.GetFinalityProviders(ctx) if err != nil { if db.IsNotFoundError(err) { log.Ctx(ctx).Warn().Err(err).Msg("Finality providers not found") - return nil, "", types.NewErrorWithMsg(http.StatusNotFound, types.NotFound, "finality providers not found, please retry") + return nil, types.NewErrorWithMsg( + http.StatusNotFound, types.NotFound, "finality providers not found, please retry", + ) } - return nil, "", types.NewErrorWithMsg(http.StatusInternalServerError, types.InternalServiceError, "failed to get finality providers") + return nil, types.NewErrorWithMsg( + http.StatusInternalServerError, types.InternalServiceError, "failed to get finality providers", + ) } - providersPublic := make([]*FinalityProviderPublic, 0, len(resultMap.Data)) - for _, provider := range resultMap.Data { - providersPublic = append(providersPublic, mapToFinalityProviderPublic(provider)) - } - return providersPublic, resultMap.PaginationToken, nil -} - -// SearchFinalityProviders searches for finality providers with optional filters -func (s *V2Service) SearchFinalityProviders(ctx context.Context, searchQuery string, paginationKey string) ([]*FinalityProviderPublic, string, *types.Error) { - resultMap, err := s.DbClients.IndexerDBClient.SearchFinalityProviders(ctx, searchQuery, paginationKey) - if err != nil { - if db.IsNotFoundError(err) { - log.Ctx(ctx).Warn().Err(err).Str("searchQuery", searchQuery).Msg("Finality providers not found") - return nil, "", types.NewErrorWithMsg(http.StatusNotFound, types.NotFound, "finality providers not found, please retry") - } - return nil, "", types.NewErrorWithMsg(http.StatusInternalServerError, types.InternalServiceError, "failed to search finality providers") - } + // TODO: Call the FP stats service to get the stats for compose the response + providersPublic := make([]*FinalityProviderStatsPublic, 0, len(fps)) - providersPublic := make([]*FinalityProviderPublic, 0, len(resultMap.Data)) - for _, provider := range resultMap.Data { - providersPublic = append(providersPublic, mapToFinalityProviderPublic(provider)) + for _, provider := range fps { + providersPublic = append(providersPublic, mapToFinalityProviderStatsPublic(*provider)) } - return providersPublic, resultMap.PaginationToken, nil + return providersPublic, nil } diff --git a/internal/v2/service/interface.go b/internal/v2/service/interface.go index 006d544e..577de2e3 100644 --- a/internal/v2/service/interface.go +++ b/internal/v2/service/interface.go @@ -3,14 +3,13 @@ package v2service import ( "context" - "github.com/babylonlabs-io/staking-api-service/internal/shared/services/service" "github.com/babylonlabs-io/staking-api-service/internal/shared/types" ) type V2ServiceProvider interface { - service.SharedServiceProvider - GetFinalityProviders(ctx context.Context, state types.FinalityProviderQueryingState, paginationKey string) ([]*FinalityProviderPublic, string, *types.Error) - SearchFinalityProviders(ctx context.Context, searchQuery string, paginationKey string) ([]*FinalityProviderPublic, string, *types.Error) + GetFinalityProvidersWithStats(ctx context.Context) ( + []*FinalityProviderStatsPublic, *types.Error, + ) GetParams(ctx context.Context) (*ParamsPublic, *types.Error) GetDelegation(ctx context.Context, stakingTxHashHex string) (*StakerDelegationPublic, *types.Error) GetDelegations(ctx context.Context, stakerPKHex string, paginationKey string) ([]*StakerDelegationPublic, string, *types.Error) diff --git a/internal/v2/service/service.go b/internal/v2/service/service.go index 4d264bd1..eaf0df0b 100644 --- a/internal/v2/service/service.go +++ b/internal/v2/service/service.go @@ -3,31 +3,26 @@ package v2service import ( "context" - service "github.com/babylonlabs-io/staking-api-service/internal/shared/services/service" "github.com/babylonlabs-io/staking-api-service/internal/shared/config" dbclients "github.com/babylonlabs-io/staking-api-service/internal/shared/db/clients" "github.com/babylonlabs-io/staking-api-service/internal/shared/http/clients" - "github.com/babylonlabs-io/staking-api-service/internal/shared/types" ) type V2Service struct { - *service.Service + DbClients *dbclients.DbClients + Clients *clients.Clients + Cfg *config.Config } func New( ctx context.Context, cfg *config.Config, - globalParams *types.GlobalParams, - finalityProviders []types.FinalityProviderDetails, clients *clients.Clients, dbClients *dbclients.DbClients, ) (*V2Service, error) { - service, err := service.New(ctx, cfg, globalParams, finalityProviders, clients, dbClients) - if err != nil { - return nil, err - } - return &V2Service{ - service, + DbClients: dbClients, + Clients: clients, + Cfg: cfg, }, nil } diff --git a/internal/v2/service/stats.go b/internal/v2/service/stats.go index b13abb68..94b4715e 100644 --- a/internal/v2/service/stats.go +++ b/internal/v2/service/stats.go @@ -20,35 +20,35 @@ type OverallStatsPublic struct { } type StakerStatsPublic struct { - StakerPkHex string `json:"_id"` - ActiveTvl int64 `json:"active_tvl"` - WithdrawableTvl int64 `json:"withdrawable_tvl"` - SlashedTvl int64 `json:"slashed_tvl"` - ActiveDelegations uint32 `json:"active_delegations"` - WithdrawableDelegations uint32 `json:"withdrawable_delegations"` - SlashedDelegations uint32 `json:"slashed_delegations"` + StakerPkHex string `json:"_id"` + ActiveTvl int64 `json:"active_tvl"` + WithdrawableTvl int64 `json:"withdrawable_tvl"` + SlashedTvl int64 `json:"slashed_tvl"` + ActiveDelegations uint32 `json:"active_delegations"` + WithdrawableDelegations uint32 `json:"withdrawable_delegations"` + SlashedDelegations uint32 `json:"slashed_delegations"` } func (s *V2Service) GetStakerStats(ctx context.Context, stakerPKHex string) (*StakerStatsPublic, *types.Error) { - stakerStats, err := s.Service.DbClients.V2DBClient.GetStakerStats(ctx, stakerPKHex) + stakerStats, err := s.DbClients.V2DBClient.GetStakerStats(ctx, stakerPKHex) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("error while fetching staker stats") return nil, types.NewInternalServiceError(err) } return &StakerStatsPublic{ - StakerPkHex: stakerStats.StakerPkHex, - ActiveTvl: stakerStats.ActiveTvl, - WithdrawableTvl: stakerStats.WithdrawableTvl, - SlashedTvl: stakerStats.SlashedTvl, - ActiveDelegations: stakerStats.ActiveDelegations, - WithdrawableDelegations: stakerStats.WithdrawableDelegations, - SlashedDelegations: stakerStats.SlashedDelegations, + StakerPkHex: stakerStats.StakerPkHex, + ActiveTvl: stakerStats.ActiveTvl, + WithdrawableTvl: stakerStats.WithdrawableTvl, + SlashedTvl: stakerStats.SlashedTvl, + ActiveDelegations: stakerStats.ActiveDelegations, + WithdrawableDelegations: stakerStats.WithdrawableDelegations, + SlashedDelegations: stakerStats.SlashedDelegations, }, nil } func (s *V2Service) GetOverallStats(ctx context.Context) (*OverallStatsPublic, *types.Error) { - overallStats, err := s.Service.DbClients.V2DBClient.GetOverallStats(ctx) + overallStats, err := s.DbClients.V2DBClient.GetOverallStats(ctx) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("error while fetching overall stats") return nil, types.NewInternalServiceError(err)