diff --git a/Makefile b/Makefile index a2d6de6d..2b5229b1 100644 --- a/Makefile +++ b/Makefile @@ -57,8 +57,10 @@ run-unprocessed-events-replay-local: --replay generate-mock-interface: - cd internal/db && mockery --name=DBClient --output=../../tests/mocks --outpkg=mocks --filename=mock_db_client.go - cd internal/clients/ordinals && mockery --name=OrdinalsClientInterface --output=../../../tests/mocks --outpkg=mocks --filename=mock_ordinal_client.go + cd internal/shared/db/client && mockery --name=DBClient --output=../../../../tests/mocks --outpkg=mocks --filename=mock_db_client.go + cd internal/v1/db/client && mockery --name=V1DBClient --output=../../../../tests/mocks --outpkg=mocks --filename=mock_v1_db_client.go + cd internal/v2/db/client && mockery --name=V2DBClient --output=../../../../tests/mocks --outpkg=mocks --filename=mock_v2_db_client.go + cd internal/shared/http/clients/ordinals && mockery --name=OrdinalsClient --output=../../../../../tests/mocks --outpkg=mocks --filename=mock_ordinal_client.go test: ./bin/local-startup.sh; @@ -66,4 +68,4 @@ test: build-swagger: - swag init --parseDependency --parseInternal -d cmd/staking-api-service,internal/api,internal/types \ No newline at end of file + swag init --parseDependency --parseInternal -d cmd/staking-api-service,internal/shared/api,internal/shared/types,internal/v1/api/handlers,internal/v2/api/handlers \ No newline at end of file diff --git a/cmd/staking-api-service/main.go b/cmd/staking-api-service/main.go index 7de71abd..d0e66d3b 100644 --- a/cmd/staking-api-service/main.go +++ b/cmd/staking-api-service/main.go @@ -6,15 +6,16 @@ import ( "github.com/babylonlabs-io/staking-api-service/cmd/staking-api-service/cli" "github.com/babylonlabs-io/staking-api-service/cmd/staking-api-service/scripts" - "github.com/babylonlabs-io/staking-api-service/internal/api" - "github.com/babylonlabs-io/staking-api-service/internal/clients" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/observability/healthcheck" - "github.com/babylonlabs-io/staking-api-service/internal/observability/metrics" - "github.com/babylonlabs-io/staking-api-service/internal/queue" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + dbclients "github.com/babylonlabs-io/staking-api-service/internal/shared/db/clients" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/http/clients" + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/healthcheck" + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/metrics" + queueclients "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/clients" + "github.com/babylonlabs-io/staking-api-service/internal/shared/services" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "github.com/joho/godotenv" "github.com/rs/zerolog/log" ) @@ -63,24 +64,32 @@ func main() { metricsPort := cfg.Metrics.GetMetricsPort() metrics.Init(metricsPort) - err = model.Setup(ctx, cfg) + err = dbmodel.Setup(ctx, cfg) if err != nil { log.Fatal().Err(err).Msg("error while setting up staking db model") } // initialize clients package which is used to interact with external services clients := clients.New(cfg) - services, err := services.New(ctx, cfg, params, finalityProviders, clients) + + dbClients, err := dbclients.New(ctx, cfg) + if err != nil { + log.Fatal().Err(err).Msg("error while setting up staking db clients") + } + + services, err := services.New(ctx, cfg, params, finalityProviders, clients, dbClients) if err != nil { log.Fatal().Err(err).Msg("error while setting up staking services layer") } + // Start the event queue processing - queues := queue.New(cfg.Queue, services) + queueClients := queueclients.New(ctx, cfg.Queue, services) // Check if the scripts flag is set if cli.GetReplayFlag() { log.Info().Msg("Replay flag is set. Starting replay of unprocessable messages.") - err := scripts.ReplayUnprocessableMessages(ctx, cfg, queues, services.DbClient) + + err := scripts.ReplayUnprocessableMessages(ctx, cfg, queueClients, dbClients.SharedDBClient) if err != nil { log.Fatal().Err(err).Msg("error while replaying unprocessable messages") } @@ -94,9 +103,9 @@ func main() { return } - queues.StartReceivingMessages() + queueClients.StartReceivingMessages() - healthcheckErr := healthcheck.StartHealthCheckCron(ctx, queues, cfg.Server.HealthCheckInterval) + healthcheckErr := healthcheck.StartHealthCheckCron(ctx, queueClients, cfg.Server.HealthCheckInterval) if healthcheckErr != nil { log.Fatal().Err(healthcheckErr).Msg("error while starting health check cron") } diff --git a/cmd/staking-api-service/scripts/pubkey_address_backfill.go b/cmd/staking-api-service/scripts/pubkey_address_backfill.go index 9052a2e6..e95e1314 100644 --- a/cmd/staking-api-service/scripts/pubkey_address_backfill.go +++ b/cmd/staking-api-service/scripts/pubkey_address_backfill.go @@ -4,21 +4,26 @@ import ( "context" "fmt" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + dbclient "github.com/babylonlabs-io/staking-api-service/internal/shared/db/client" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" + v1dbclient "github.com/babylonlabs-io/staking-api-service/internal/v1/db/client" "github.com/rs/zerolog/log" ) func BackfillPubkeyAddressesMappings(ctx context.Context, cfg *config.Config) error { - dbClient, err := db.New(ctx, cfg.Db) + client, err := dbclient.NewMongoClient(ctx, cfg.Db) + if err != nil { + return fmt.Errorf("failed to create db client: %w", err) + } + v1dbClient, err := v1dbclient.New(ctx, client, cfg.Db) if err != nil { return fmt.Errorf("failed to create db client: %w", err) } pageToken := "" var count int for { - result, err := dbClient.ScanDelegationsPaginated(ctx, pageToken) + result, err := v1dbClient.ScanDelegationsPaginated(ctx, pageToken) if err != nil { return fmt.Errorf("failed to scan delegations: %w", err) } @@ -29,7 +34,7 @@ func BackfillPubkeyAddressesMappings(ctx context.Context, cfg *config.Config) er if err != nil { return fmt.Errorf("failed to derive btc addresses: %w", err) } - if err := dbClient.InsertPkAddressMappings( + if err := v1dbClient.InsertPkAddressMappings( ctx, delegation.StakerPkHex, addresses.Taproot, addresses.NativeSegwitOdd, addresses.NativeSegwitEven, ); err != nil { diff --git a/cmd/staking-api-service/scripts/replay_unprocessed_messages.go b/cmd/staking-api-service/scripts/replay_unprocessed_messages.go index effb09c6..42cec480 100644 --- a/cmd/staking-api-service/scripts/replay_unprocessed_messages.go +++ b/cmd/staking-api-service/scripts/replay_unprocessed_messages.go @@ -6,9 +6,9 @@ import ( "errors" "fmt" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/queue" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + dbclient "github.com/babylonlabs-io/staking-api-service/internal/shared/db/client" + queueclients "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/clients" queueClient "github.com/babylonlabs-io/staking-queue-client/client" "github.com/rs/zerolog/log" ) @@ -17,9 +17,7 @@ type GenericEvent struct { EventType queueClient.EventType `json:"event_type"` } -func ReplayUnprocessableMessages(ctx context.Context, cfg *config.Config, queues *queue.Queues, db db.DBClient) (err error) { - fmt.Println("Starting to replay unprocessable messages...") - +func ReplayUnprocessableMessages(ctx context.Context, cfg *config.Config, queues *queueclients.QueueClients, db dbclient.DBClient) (err error) { // Fetch unprocessable messages unprocessableMessages, err := db.FindUnprocessableMessages(ctx) if err != nil { @@ -30,15 +28,12 @@ func ReplayUnprocessableMessages(ctx context.Context, cfg *config.Config, queues messageCount := len(unprocessableMessages) // Inform the user of the number of unprocessable messages - fmt.Printf("There are %d unprocessable messages.\n", messageCount) if messageCount == 0 { return errors.New("no unprocessable messages to replay") } // Process each unprocessable message - for i, msg := range unprocessableMessages { - fmt.Printf("Processing message %d/%d: %s\n", i+1, messageCount, msg.MessageBody) - + for _, msg := range unprocessableMessages { var genericEvent GenericEvent if err := json.Unmarshal([]byte(msg.MessageBody), &genericEvent); err != nil { return errors.New("failed to unmarshal event message") @@ -53,34 +48,28 @@ func ReplayUnprocessableMessages(ctx context.Context, cfg *config.Config, queues if err := db.DeleteUnprocessableMessage(ctx, msg.Receipt); err != nil { return errors.New("failed to delete unprocessable message") } - - fmt.Printf("Message %d/%d processed and deleted successfully.\n", i+1, messageCount) } log.Info().Msg("Reprocessing of unprocessable messages completed.") - fmt.Println("Reprocessing of unprocessable messages completed.") return } // processEventMessage processes the event message based on its EventType. -func processEventMessage(ctx context.Context, queues *queue.Queues, event GenericEvent, messageBody string) error { - fmt.Printf("Sending message to the queue for event type: %v\n", event.EventType) - +func processEventMessage(ctx context.Context, queues *queueclients.QueueClients, event GenericEvent, messageBody string) error { switch event.EventType { case queueClient.ActiveStakingEventType: - return queues.ActiveStakingQueueClient.SendMessage(ctx, messageBody) + return queues.V1QueueClient.ActiveStakingQueueClient.SendMessage(ctx, messageBody) case queueClient.UnbondingStakingEventType: - return queues.UnbondingStakingQueueClient.SendMessage(ctx, messageBody) + return queues.V1QueueClient.UnbondingStakingQueueClient.SendMessage(ctx, messageBody) case queueClient.WithdrawStakingEventType: - return queues.WithdrawStakingQueueClient.SendMessage(ctx, messageBody) + return queues.V1QueueClient.WithdrawStakingQueueClient.SendMessage(ctx, messageBody) case queueClient.ExpiredStakingEventType: - return queues.ExpiredStakingQueueClient.SendMessage(ctx, messageBody) + return queues.V1QueueClient.ExpiredStakingQueueClient.SendMessage(ctx, messageBody) case queueClient.StatsEventType: - return queues.StatsQueueClient.SendMessage(ctx, messageBody) + return queues.V1QueueClient.StatsQueueClient.SendMessage(ctx, messageBody) case queueClient.BtcInfoEventType: - return queues.BtcInfoQueueClient.SendMessage(ctx, messageBody) + return queues.V1QueueClient.BtcInfoQueueClient.SendMessage(ctx, messageBody) default: - fmt.Printf("Error: unknown event type: %v\n", event.EventType) return fmt.Errorf("unknown event type: %v", event.EventType) } } diff --git a/docs/docs.go b/docs/docs.go index f44ec4a9..8b54d880 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -27,6 +27,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "shared" + ], "summary": "Health check endpoint", "responses": { "200": { @@ -44,6 +47,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "parameters": [ { "type": "string", @@ -57,13 +63,13 @@ const docTemplate = `{ "200": { "description": "Delegation", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-services_DelegationPublic" + "$ref": "#/definitions/handler.PublicResponse-v1service_DelegationPublic" } }, "400": { "description": "Error: Bad Request", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -75,6 +81,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Get Active Finality Providers", "parameters": [ { @@ -94,7 +103,7 @@ const docTemplate = `{ "200": { "description": "A list of finality providers sorted by ActiveTvl in descending order", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-array_services_FpDetailsPublic" + "$ref": "#/definitions/handler.PublicResponse-array_v1service_FpDetailsPublic" } } } @@ -106,12 +115,15 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Get Babylon global parameters", "responses": { "200": { "description": "Global parameters", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-services_GlobalParamsPublic" + "$ref": "#/definitions/handler.PublicResponse-v1service_GlobalParamsPublic" } } } @@ -123,6 +135,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "parameters": [ { "type": "string", @@ -145,13 +160,13 @@ const docTemplate = `{ "200": { "description": "Delegation check result", "schema": { - "$ref": "#/definitions/handlers.DelegationCheckPublicResponse" + "$ref": "#/definitions/v1handlers.DelegationCheckPublicResponse" } }, "400": { "description": "Error: Bad Request", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -163,6 +178,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "parameters": [ { "type": "string", @@ -195,13 +213,13 @@ const docTemplate = `{ "200": { "description": "List of delegations and pagination token", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-array_services_DelegationPublic" + "$ref": "#/definitions/handler.PublicResponse-array_v1service_DelegationPublic" } }, "400": { "description": "Error: Bad Request", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -213,6 +231,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "parameters": [ { "type": "array", @@ -230,19 +251,19 @@ const docTemplate = `{ "200": { "description": "A map of BTC addresses to their corresponding public keys (only addresses with delegations are returned)", "schema": { - "$ref": "#/definitions/handlers.Result" + "$ref": "#/definitions/handler.Result" } }, "400": { "description": "Bad Request: Invalid input parameters", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } }, "500": { "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -254,12 +275,15 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Get Overall Stats", "responses": { "200": { "description": "Overall stats for babylon staking", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-services_OverallStatsPublic" + "$ref": "#/definitions/handler.PublicResponse-v1service_OverallStatsPublic" } } } @@ -271,6 +295,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Get Staker Stats", "parameters": [ { @@ -290,13 +317,13 @@ const docTemplate = `{ "200": { "description": "List of top stakers by active tvl", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-array_services_StakerStatsPublic" + "$ref": "#/definitions/handler.PublicResponse-array_v1service_StakerStatsPublic" } }, "400": { "description": "Error: Bad Request", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -311,6 +338,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Unbond delegation", "parameters": [ { @@ -319,7 +349,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.UnbondDelegationRequestPayload" + "$ref": "#/definitions/v1handlers.UnbondDelegationRequestPayload" } } ], @@ -330,7 +360,7 @@ const docTemplate = `{ "400": { "description": "Invalid request payload", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -342,6 +372,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Check unbonding eligibility", "parameters": [ { @@ -359,7 +392,174 @@ const docTemplate = `{ "400": { "description": "Missing or invalid 'staking_tx_hash_hex' query parameter", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" + } + } + } + } + }, + "/v2/finality-providers": { + "get": { + "description": "Fetches finality providers including their public keys, active tvl, total tvl, descriptions, commission, active delegations and total delegations etc", + "produces": [ + "application/json" + ], + "tags": [ + "v2" + ], + "summary": "Get Finality Providers", + "parameters": [ + { + "type": "string", + "description": "Pagination key to fetch the next page of finality providers", + "name": "pagination_key", + "in": "query" + }, + { + "type": "string", + "description": "Filter by finality provider public key", + "name": "finality_provider_pk", + "in": "query" + }, + { + "enum": [ + "active_tvl", + "name", + "commission" + ], + "type": "string", + "description": "Sort by field", + "name": "sort_by", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "description": "Order", + "name": "order", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of finality providers and pagination token", + "schema": { + "$ref": "#/definitions/handler.PublicResponse-array_v2service_FinalityProviderPublic" + } + }, + "400": { + "description": "Error: Bad Request", + "schema": { + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" + } + } + } + } + }, + "/v2/global-params": { + "get": { + "description": "Fetches global parameters for babylon chain and BTC chain", + "produces": [ + "application/json" + ], + "tags": [ + "v2" + ], + "summary": "Get Global Parameters", + "responses": { + "200": { + "description": "Global parameters", + "schema": { + "$ref": "#/definitions/handler.PublicResponse-v2service_GlobalParamsPublic" + } + } + } + } + }, + "/v2/staker/delegations": { + "get": { + "description": "Fetches staker delegations for babylon staking including tvl, total delegations, active tvl, active delegations and total stakers.", + "produces": [ + "application/json" + ], + "tags": [ + "v2" + ], + "summary": "Get Staker Delegations", + "parameters": [ + { + "type": "string", + "description": "Staking transaction hash in hex format", + "name": "staking_tx_hash_hex", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Pagination key to fetch the next page of delegations", + "name": "pagination_key", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of staker delegations and pagination token", + "schema": { + "$ref": "#/definitions/handler.PublicResponse-array_v2service_StakerDelegationPublic" + } + }, + "400": { + "description": "Error: Bad Request", + "schema": { + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" + } + } + } + } + }, + "/v2/staker/stats": { + "get": { + "description": "Fetches staker stats for babylon staking including active tvl, total tvl, active delegations and total delegations.", + "produces": [ + "application/json" + ], + "tags": [ + "v2" + ], + "summary": "Get Staker Stats", + "responses": { + "200": { + "description": "Staker stats", + "schema": { + "$ref": "#/definitions/handler.PublicResponse-v2service_StakerStatsPublic" + } + } + } + } + }, + "/v2/stats": { + "get": { + "description": "Overall system stats", + "produces": [ + "application/json" + ], + "tags": [ + "v2" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.PublicResponse-v2service_OverallStatsPublic" + } + }, + "400": { + "description": "Error: Bad Request", + "schema": { + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -367,7 +567,7 @@ const docTemplate = `{ } }, "definitions": { - "github_com_babylonlabs-io_staking-api-service_internal_types.Error": { + "github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error": { "type": "object", "properties": { "err": {}, @@ -379,93 +579,154 @@ const docTemplate = `{ } } }, - "handlers.DelegationCheckPublicResponse": { + "github_com_babylonlabs-io_staking-api-service_internal_shared_types.TransactionInfo": { "type": "object", "properties": { - "code": { + "output_index": { "type": "integer" }, + "tx_hex": { + "type": "string" + } + } + }, + "handler.PublicResponse-array_v1service_DelegationPublic": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v1service.DelegationPublic" + } + }, + "pagination": { + "$ref": "#/definitions/handler.paginationResponse" + } + } + }, + "handler.PublicResponse-array_v1service_FpDetailsPublic": { + "type": "object", + "properties": { "data": { - "type": "boolean" + "type": "array", + "items": { + "$ref": "#/definitions/v1service.FpDetailsPublic" + } + }, + "pagination": { + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-array_services_DelegationPublic": { + "handler.PublicResponse-array_v1service_StakerStatsPublic": { "type": "object", "properties": { "data": { "type": "array", "items": { - "$ref": "#/definitions/services.DelegationPublic" + "$ref": "#/definitions/v1service.StakerStatsPublic" } }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-array_services_FpDetailsPublic": { + "handler.PublicResponse-array_v2service_FinalityProviderPublic": { "type": "object", "properties": { "data": { "type": "array", "items": { - "$ref": "#/definitions/services.FpDetailsPublic" + "$ref": "#/definitions/v2service.FinalityProviderPublic" } }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-array_services_StakerStatsPublic": { + "handler.PublicResponse-array_v2service_StakerDelegationPublic": { "type": "object", "properties": { "data": { "type": "array", "items": { - "$ref": "#/definitions/services.StakerStatsPublic" + "$ref": "#/definitions/v2service.StakerDelegationPublic" } }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" + } + } + }, + "handler.PublicResponse-v1service_DelegationPublic": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/v1service.DelegationPublic" + }, + "pagination": { + "$ref": "#/definitions/handler.paginationResponse" + } + } + }, + "handler.PublicResponse-v1service_GlobalParamsPublic": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/v1service.GlobalParamsPublic" + }, + "pagination": { + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-services_DelegationPublic": { + "handler.PublicResponse-v1service_OverallStatsPublic": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/services.DelegationPublic" + "$ref": "#/definitions/v1service.OverallStatsPublic" }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-services_GlobalParamsPublic": { + "handler.PublicResponse-v2service_GlobalParamsPublic": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/services.GlobalParamsPublic" + "$ref": "#/definitions/v2service.GlobalParamsPublic" }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-services_OverallStatsPublic": { + "handler.PublicResponse-v2service_OverallStatsPublic": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/services.OverallStatsPublic" + "$ref": "#/definitions/v2service.OverallStatsPublic" }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.Result": { + "handler.PublicResponse-v2service_StakerStatsPublic": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/v2service.StakerStatsPublic" + }, + "pagination": { + "$ref": "#/definitions/handler.paginationResponse" + } + } + }, + "handler.Result": { "type": "object", "properties": { "data": {}, @@ -474,32 +735,159 @@ const docTemplate = `{ } } }, - "handlers.UnbondDelegationRequestPayload": { + "handler.paginationResponse": { "type": "object", "properties": { - "staker_signed_signature_hex": { + "next_key": { "type": "string" + } + } + }, + "types.BTCParams": { + "type": "object", + "properties": { + "btc_confirmation_depth": { + "type": "integer" }, - "staking_tx_hash_hex": { + "version": { + "type": "integer" + } + } + }, + "types.BabylonParams": { + "type": "object", + "properties": { + "covenant_pks": { + "type": "array", + "items": { + "type": "string" + } + }, + "covenant_quorum": { + "type": "integer" + }, + "delegation_creation_base_gas_fee": { + "type": "integer" + }, + "max_active_finality_providers": { + "type": "integer" + }, + "max_staking_amount": { + "type": "integer" + }, + "max_staking_time": { + "type": "integer" + }, + "min_commission_rate": { + "type": "number" + }, + "min_slashing_tx_fee": { + "type": "integer" + }, + "min_staking_amount": { + "type": "integer" + }, + "min_staking_time": { + "type": "integer" + }, + "min_unbonding_time": { + "type": "integer" + }, + "slashing_pk_script": { "type": "string" }, - "unbonding_tx_hash_hex": { + "slashing_rate": { + "type": "number" + }, + "unbonding_fee": { + "type": "integer" + }, + "version": { + "type": "integer" + } + } + }, + "types.ErrorCode": { + "type": "string", + "enum": [ + "INTERNAL_SERVICE_ERROR", + "VALIDATION_ERROR", + "NOT_FOUND", + "BAD_REQUEST", + "FORBIDDEN", + "UNPROCESSABLE_ENTITY", + "REQUEST_TIMEOUT" + ], + "x-enum-varnames": [ + "InternalServiceError", + "ValidationError", + "NotFound", + "BadRequest", + "Forbidden", + "UnprocessableEntity", + "RequestTimeout" + ] + }, + "types.FinalityProviderDescription": { + "type": "object", + "properties": { + "details": { "type": "string" }, - "unbonding_tx_hex": { + "identity": { + "type": "string" + }, + "moniker": { + "type": "string" + }, + "security_contact": { + "type": "string" + }, + "website": { "type": "string" } } }, - "handlers.paginationResponse": { + "types.FinalityProviderState": { + "type": "string", + "enum": [ + "active", + "standby" + ], + "x-enum-varnames": [ + "FinalityProviderStateActive", + "FinalityProviderStateStandby" + ] + }, + "v1handlers.DelegationCheckPublicResponse": { "type": "object", "properties": { - "next_key": { + "code": { + "type": "integer" + }, + "data": { + "type": "boolean" + } + } + }, + "v1handlers.UnbondDelegationRequestPayload": { + "type": "object", + "properties": { + "staker_signed_signature_hex": { + "type": "string" + }, + "staking_tx_hash_hex": { + "type": "string" + }, + "unbonding_tx_hash_hex": { + "type": "string" + }, + "unbonding_tx_hex": { "type": "string" } } }, - "services.DelegationPublic": { + "v1service.DelegationPublic": { "type": "object", "properties": { "finality_provider_pk_hex": { @@ -512,7 +900,7 @@ const docTemplate = `{ "type": "string" }, "staking_tx": { - "$ref": "#/definitions/services.TransactionPublic" + "$ref": "#/definitions/v1service.TransactionPublic" }, "staking_tx_hash_hex": { "type": "string" @@ -524,11 +912,11 @@ const docTemplate = `{ "type": "string" }, "unbonding_tx": { - "$ref": "#/definitions/services.TransactionPublic" + "$ref": "#/definitions/v1service.TransactionPublic" } } }, - "services.FpDescriptionPublic": { + "v1service.FpDescriptionPublic": { "type": "object", "properties": { "details": { @@ -548,7 +936,7 @@ const docTemplate = `{ } } }, - "services.FpDetailsPublic": { + "v1service.FpDetailsPublic": { "type": "object", "properties": { "active_delegations": { @@ -564,7 +952,7 @@ const docTemplate = `{ "type": "string" }, "description": { - "$ref": "#/definitions/services.FpDescriptionPublic" + "$ref": "#/definitions/v1service.FpDescriptionPublic" }, "total_delegations": { "type": "integer" @@ -574,18 +962,18 @@ const docTemplate = `{ } } }, - "services.GlobalParamsPublic": { + "v1service.GlobalParamsPublic": { "type": "object", "properties": { "versions": { "type": "array", "items": { - "$ref": "#/definitions/services.VersionedGlobalParamsPublic" + "$ref": "#/definitions/v1service.VersionedGlobalParamsPublic" } } } }, - "services.OverallStatsPublic": { + "v1service.OverallStatsPublic": { "type": "object", "properties": { "active_delegations": { @@ -611,7 +999,7 @@ const docTemplate = `{ } } }, - "services.StakerStatsPublic": { + "v1service.StakerStatsPublic": { "type": "object", "properties": { "active_delegations": { @@ -631,7 +1019,7 @@ const docTemplate = `{ } } }, - "services.TransactionPublic": { + "v1service.TransactionPublic": { "type": "object", "properties": { "output_index": { @@ -651,7 +1039,7 @@ const docTemplate = `{ } } }, - "services.VersionedGlobalParamsPublic": { + "v1service.VersionedGlobalParamsPublic": { "type": "object", "properties": { "activation_height": { @@ -701,26 +1089,135 @@ const docTemplate = `{ } } }, - "types.ErrorCode": { - "type": "string", - "enum": [ - "INTERNAL_SERVICE_ERROR", - "VALIDATION_ERROR", - "NOT_FOUND", - "BAD_REQUEST", - "FORBIDDEN", - "UNPROCESSABLE_ENTITY", - "REQUEST_TIMEOUT" - ], - "x-enum-varnames": [ - "InternalServiceError", - "ValidationError", - "NotFound", - "BadRequest", - "Forbidden", - "UnprocessableEntity", - "RequestTimeout" - ] + "v2service.FinalityProviderPublic": { + "type": "object", + "properties": { + "active_delegations": { + "type": "integer" + }, + "active_tvl": { + "type": "integer" + }, + "btc_pk": { + "type": "string" + }, + "commission": { + "type": "string" + }, + "description": { + "$ref": "#/definitions/types.FinalityProviderDescription" + }, + "state": { + "$ref": "#/definitions/types.FinalityProviderState" + }, + "total_delegations": { + "type": "integer" + }, + "total_tvl": { + "type": "integer" + } + } + }, + "v2service.GlobalParamsPublic": { + "type": "object", + "properties": { + "babylon": { + "type": "array", + "items": { + "$ref": "#/definitions/types.BabylonParams" + } + }, + "btc": { + "type": "array", + "items": { + "$ref": "#/definitions/types.BTCParams" + } + } + } + }, + "v2service.OverallStatsPublic": { + "type": "object", + "properties": { + "active_delegations": { + "type": "integer" + }, + "active_finality_providers": { + "type": "integer" + }, + "active_stakers": { + "type": "integer" + }, + "active_tvl": { + "type": "integer" + }, + "total_delegations": { + "type": "integer" + }, + "total_finality_providers": { + "type": "integer" + }, + "total_stakers": { + "type": "integer" + }, + "total_tvl": { + "type": "integer" + } + } + }, + "v2service.StakerDelegationPublic": { + "type": "object", + "properties": { + "finality_provider_pk_hex": { + "type": "string" + }, + "staker_pk_hex": { + "type": "string" + }, + "staking_start_height": { + "type": "integer" + }, + "staking_tx": { + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.TransactionInfo" + }, + "staking_tx_hash_hex": { + "type": "string" + }, + "staking_value": { + "type": "integer" + }, + "state": { + "type": "string" + }, + "timelock": { + "type": "integer" + }, + "unbonding_start_height": { + "type": "integer" + }, + "unbonding_tx": { + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.TransactionInfo" + } + } + }, + "v2service.StakerStatsPublic": { + "type": "object", + "properties": { + "active_delegations": { + "type": "integer" + }, + "active_tvl": { + "type": "integer" + }, + "staker_pk_hex": { + "type": "string" + }, + "total_delegations": { + "type": "integer" + }, + "total_tvl": { + "type": "integer" + } + } } } }` diff --git a/docs/swagger.json b/docs/swagger.json index b903030b..03dfa2d9 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -19,6 +19,9 @@ "produces": [ "application/json" ], + "tags": [ + "shared" + ], "summary": "Health check endpoint", "responses": { "200": { @@ -36,6 +39,9 @@ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "parameters": [ { "type": "string", @@ -49,13 +55,13 @@ "200": { "description": "Delegation", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-services_DelegationPublic" + "$ref": "#/definitions/handler.PublicResponse-v1service_DelegationPublic" } }, "400": { "description": "Error: Bad Request", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -67,6 +73,9 @@ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Get Active Finality Providers", "parameters": [ { @@ -86,7 +95,7 @@ "200": { "description": "A list of finality providers sorted by ActiveTvl in descending order", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-array_services_FpDetailsPublic" + "$ref": "#/definitions/handler.PublicResponse-array_v1service_FpDetailsPublic" } } } @@ -98,12 +107,15 @@ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Get Babylon global parameters", "responses": { "200": { "description": "Global parameters", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-services_GlobalParamsPublic" + "$ref": "#/definitions/handler.PublicResponse-v1service_GlobalParamsPublic" } } } @@ -115,6 +127,9 @@ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "parameters": [ { "type": "string", @@ -137,13 +152,13 @@ "200": { "description": "Delegation check result", "schema": { - "$ref": "#/definitions/handlers.DelegationCheckPublicResponse" + "$ref": "#/definitions/v1handlers.DelegationCheckPublicResponse" } }, "400": { "description": "Error: Bad Request", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -155,6 +170,9 @@ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "parameters": [ { "type": "string", @@ -187,13 +205,13 @@ "200": { "description": "List of delegations and pagination token", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-array_services_DelegationPublic" + "$ref": "#/definitions/handler.PublicResponse-array_v1service_DelegationPublic" } }, "400": { "description": "Error: Bad Request", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -205,6 +223,9 @@ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "parameters": [ { "type": "array", @@ -222,19 +243,19 @@ "200": { "description": "A map of BTC addresses to their corresponding public keys (only addresses with delegations are returned)", "schema": { - "$ref": "#/definitions/handlers.Result" + "$ref": "#/definitions/handler.Result" } }, "400": { "description": "Bad Request: Invalid input parameters", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } }, "500": { "description": "Internal Server Error", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -246,12 +267,15 @@ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Get Overall Stats", "responses": { "200": { "description": "Overall stats for babylon staking", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-services_OverallStatsPublic" + "$ref": "#/definitions/handler.PublicResponse-v1service_OverallStatsPublic" } } } @@ -263,6 +287,9 @@ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Get Staker Stats", "parameters": [ { @@ -282,13 +309,13 @@ "200": { "description": "List of top stakers by active tvl", "schema": { - "$ref": "#/definitions/handlers.PublicResponse-array_services_StakerStatsPublic" + "$ref": "#/definitions/handler.PublicResponse-array_v1service_StakerStatsPublic" } }, "400": { "description": "Error: Bad Request", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -303,6 +330,9 @@ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Unbond delegation", "parameters": [ { @@ -311,7 +341,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.UnbondDelegationRequestPayload" + "$ref": "#/definitions/v1handlers.UnbondDelegationRequestPayload" } } ], @@ -322,7 +352,7 @@ "400": { "description": "Invalid request payload", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -334,6 +364,9 @@ "produces": [ "application/json" ], + "tags": [ + "v1" + ], "summary": "Check unbonding eligibility", "parameters": [ { @@ -351,7 +384,174 @@ "400": { "description": "Missing or invalid 'staking_tx_hash_hex' query parameter", "schema": { - "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error" + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" + } + } + } + } + }, + "/v2/finality-providers": { + "get": { + "description": "Fetches finality providers including their public keys, active tvl, total tvl, descriptions, commission, active delegations and total delegations etc", + "produces": [ + "application/json" + ], + "tags": [ + "v2" + ], + "summary": "Get Finality Providers", + "parameters": [ + { + "type": "string", + "description": "Pagination key to fetch the next page of finality providers", + "name": "pagination_key", + "in": "query" + }, + { + "type": "string", + "description": "Filter by finality provider public key", + "name": "finality_provider_pk", + "in": "query" + }, + { + "enum": [ + "active_tvl", + "name", + "commission" + ], + "type": "string", + "description": "Sort by field", + "name": "sort_by", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "description": "Order", + "name": "order", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of finality providers and pagination token", + "schema": { + "$ref": "#/definitions/handler.PublicResponse-array_v2service_FinalityProviderPublic" + } + }, + "400": { + "description": "Error: Bad Request", + "schema": { + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" + } + } + } + } + }, + "/v2/global-params": { + "get": { + "description": "Fetches global parameters for babylon chain and BTC chain", + "produces": [ + "application/json" + ], + "tags": [ + "v2" + ], + "summary": "Get Global Parameters", + "responses": { + "200": { + "description": "Global parameters", + "schema": { + "$ref": "#/definitions/handler.PublicResponse-v2service_GlobalParamsPublic" + } + } + } + } + }, + "/v2/staker/delegations": { + "get": { + "description": "Fetches staker delegations for babylon staking including tvl, total delegations, active tvl, active delegations and total stakers.", + "produces": [ + "application/json" + ], + "tags": [ + "v2" + ], + "summary": "Get Staker Delegations", + "parameters": [ + { + "type": "string", + "description": "Staking transaction hash in hex format", + "name": "staking_tx_hash_hex", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Pagination key to fetch the next page of delegations", + "name": "pagination_key", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List of staker delegations and pagination token", + "schema": { + "$ref": "#/definitions/handler.PublicResponse-array_v2service_StakerDelegationPublic" + } + }, + "400": { + "description": "Error: Bad Request", + "schema": { + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" + } + } + } + } + }, + "/v2/staker/stats": { + "get": { + "description": "Fetches staker stats for babylon staking including active tvl, total tvl, active delegations and total delegations.", + "produces": [ + "application/json" + ], + "tags": [ + "v2" + ], + "summary": "Get Staker Stats", + "responses": { + "200": { + "description": "Staker stats", + "schema": { + "$ref": "#/definitions/handler.PublicResponse-v2service_StakerStatsPublic" + } + } + } + } + }, + "/v2/stats": { + "get": { + "description": "Overall system stats", + "produces": [ + "application/json" + ], + "tags": [ + "v2" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.PublicResponse-v2service_OverallStatsPublic" + } + }, + "400": { + "description": "Error: Bad Request", + "schema": { + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error" } } } @@ -359,7 +559,7 @@ } }, "definitions": { - "github_com_babylonlabs-io_staking-api-service_internal_types.Error": { + "github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error": { "type": "object", "properties": { "err": {}, @@ -371,93 +571,154 @@ } } }, - "handlers.DelegationCheckPublicResponse": { + "github_com_babylonlabs-io_staking-api-service_internal_shared_types.TransactionInfo": { "type": "object", "properties": { - "code": { + "output_index": { "type": "integer" }, + "tx_hex": { + "type": "string" + } + } + }, + "handler.PublicResponse-array_v1service_DelegationPublic": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v1service.DelegationPublic" + } + }, + "pagination": { + "$ref": "#/definitions/handler.paginationResponse" + } + } + }, + "handler.PublicResponse-array_v1service_FpDetailsPublic": { + "type": "object", + "properties": { "data": { - "type": "boolean" + "type": "array", + "items": { + "$ref": "#/definitions/v1service.FpDetailsPublic" + } + }, + "pagination": { + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-array_services_DelegationPublic": { + "handler.PublicResponse-array_v1service_StakerStatsPublic": { "type": "object", "properties": { "data": { "type": "array", "items": { - "$ref": "#/definitions/services.DelegationPublic" + "$ref": "#/definitions/v1service.StakerStatsPublic" } }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-array_services_FpDetailsPublic": { + "handler.PublicResponse-array_v2service_FinalityProviderPublic": { "type": "object", "properties": { "data": { "type": "array", "items": { - "$ref": "#/definitions/services.FpDetailsPublic" + "$ref": "#/definitions/v2service.FinalityProviderPublic" } }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-array_services_StakerStatsPublic": { + "handler.PublicResponse-array_v2service_StakerDelegationPublic": { "type": "object", "properties": { "data": { "type": "array", "items": { - "$ref": "#/definitions/services.StakerStatsPublic" + "$ref": "#/definitions/v2service.StakerDelegationPublic" } }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" + } + } + }, + "handler.PublicResponse-v1service_DelegationPublic": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/v1service.DelegationPublic" + }, + "pagination": { + "$ref": "#/definitions/handler.paginationResponse" + } + } + }, + "handler.PublicResponse-v1service_GlobalParamsPublic": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/v1service.GlobalParamsPublic" + }, + "pagination": { + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-services_DelegationPublic": { + "handler.PublicResponse-v1service_OverallStatsPublic": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/services.DelegationPublic" + "$ref": "#/definitions/v1service.OverallStatsPublic" }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-services_GlobalParamsPublic": { + "handler.PublicResponse-v2service_GlobalParamsPublic": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/services.GlobalParamsPublic" + "$ref": "#/definitions/v2service.GlobalParamsPublic" }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.PublicResponse-services_OverallStatsPublic": { + "handler.PublicResponse-v2service_OverallStatsPublic": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/services.OverallStatsPublic" + "$ref": "#/definitions/v2service.OverallStatsPublic" }, "pagination": { - "$ref": "#/definitions/handlers.paginationResponse" + "$ref": "#/definitions/handler.paginationResponse" } } }, - "handlers.Result": { + "handler.PublicResponse-v2service_StakerStatsPublic": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/v2service.StakerStatsPublic" + }, + "pagination": { + "$ref": "#/definitions/handler.paginationResponse" + } + } + }, + "handler.Result": { "type": "object", "properties": { "data": {}, @@ -466,32 +727,159 @@ } } }, - "handlers.UnbondDelegationRequestPayload": { + "handler.paginationResponse": { "type": "object", "properties": { - "staker_signed_signature_hex": { + "next_key": { "type": "string" + } + } + }, + "types.BTCParams": { + "type": "object", + "properties": { + "btc_confirmation_depth": { + "type": "integer" }, - "staking_tx_hash_hex": { + "version": { + "type": "integer" + } + } + }, + "types.BabylonParams": { + "type": "object", + "properties": { + "covenant_pks": { + "type": "array", + "items": { + "type": "string" + } + }, + "covenant_quorum": { + "type": "integer" + }, + "delegation_creation_base_gas_fee": { + "type": "integer" + }, + "max_active_finality_providers": { + "type": "integer" + }, + "max_staking_amount": { + "type": "integer" + }, + "max_staking_time": { + "type": "integer" + }, + "min_commission_rate": { + "type": "number" + }, + "min_slashing_tx_fee": { + "type": "integer" + }, + "min_staking_amount": { + "type": "integer" + }, + "min_staking_time": { + "type": "integer" + }, + "min_unbonding_time": { + "type": "integer" + }, + "slashing_pk_script": { "type": "string" }, - "unbonding_tx_hash_hex": { + "slashing_rate": { + "type": "number" + }, + "unbonding_fee": { + "type": "integer" + }, + "version": { + "type": "integer" + } + } + }, + "types.ErrorCode": { + "type": "string", + "enum": [ + "INTERNAL_SERVICE_ERROR", + "VALIDATION_ERROR", + "NOT_FOUND", + "BAD_REQUEST", + "FORBIDDEN", + "UNPROCESSABLE_ENTITY", + "REQUEST_TIMEOUT" + ], + "x-enum-varnames": [ + "InternalServiceError", + "ValidationError", + "NotFound", + "BadRequest", + "Forbidden", + "UnprocessableEntity", + "RequestTimeout" + ] + }, + "types.FinalityProviderDescription": { + "type": "object", + "properties": { + "details": { "type": "string" }, - "unbonding_tx_hex": { + "identity": { + "type": "string" + }, + "moniker": { + "type": "string" + }, + "security_contact": { + "type": "string" + }, + "website": { "type": "string" } } }, - "handlers.paginationResponse": { + "types.FinalityProviderState": { + "type": "string", + "enum": [ + "active", + "standby" + ], + "x-enum-varnames": [ + "FinalityProviderStateActive", + "FinalityProviderStateStandby" + ] + }, + "v1handlers.DelegationCheckPublicResponse": { "type": "object", "properties": { - "next_key": { + "code": { + "type": "integer" + }, + "data": { + "type": "boolean" + } + } + }, + "v1handlers.UnbondDelegationRequestPayload": { + "type": "object", + "properties": { + "staker_signed_signature_hex": { + "type": "string" + }, + "staking_tx_hash_hex": { + "type": "string" + }, + "unbonding_tx_hash_hex": { + "type": "string" + }, + "unbonding_tx_hex": { "type": "string" } } }, - "services.DelegationPublic": { + "v1service.DelegationPublic": { "type": "object", "properties": { "finality_provider_pk_hex": { @@ -504,7 +892,7 @@ "type": "string" }, "staking_tx": { - "$ref": "#/definitions/services.TransactionPublic" + "$ref": "#/definitions/v1service.TransactionPublic" }, "staking_tx_hash_hex": { "type": "string" @@ -516,11 +904,11 @@ "type": "string" }, "unbonding_tx": { - "$ref": "#/definitions/services.TransactionPublic" + "$ref": "#/definitions/v1service.TransactionPublic" } } }, - "services.FpDescriptionPublic": { + "v1service.FpDescriptionPublic": { "type": "object", "properties": { "details": { @@ -540,7 +928,7 @@ } } }, - "services.FpDetailsPublic": { + "v1service.FpDetailsPublic": { "type": "object", "properties": { "active_delegations": { @@ -556,7 +944,7 @@ "type": "string" }, "description": { - "$ref": "#/definitions/services.FpDescriptionPublic" + "$ref": "#/definitions/v1service.FpDescriptionPublic" }, "total_delegations": { "type": "integer" @@ -566,18 +954,18 @@ } } }, - "services.GlobalParamsPublic": { + "v1service.GlobalParamsPublic": { "type": "object", "properties": { "versions": { "type": "array", "items": { - "$ref": "#/definitions/services.VersionedGlobalParamsPublic" + "$ref": "#/definitions/v1service.VersionedGlobalParamsPublic" } } } }, - "services.OverallStatsPublic": { + "v1service.OverallStatsPublic": { "type": "object", "properties": { "active_delegations": { @@ -603,7 +991,7 @@ } } }, - "services.StakerStatsPublic": { + "v1service.StakerStatsPublic": { "type": "object", "properties": { "active_delegations": { @@ -623,7 +1011,7 @@ } } }, - "services.TransactionPublic": { + "v1service.TransactionPublic": { "type": "object", "properties": { "output_index": { @@ -643,7 +1031,7 @@ } } }, - "services.VersionedGlobalParamsPublic": { + "v1service.VersionedGlobalParamsPublic": { "type": "object", "properties": { "activation_height": { @@ -693,26 +1081,135 @@ } } }, - "types.ErrorCode": { - "type": "string", - "enum": [ - "INTERNAL_SERVICE_ERROR", - "VALIDATION_ERROR", - "NOT_FOUND", - "BAD_REQUEST", - "FORBIDDEN", - "UNPROCESSABLE_ENTITY", - "REQUEST_TIMEOUT" - ], - "x-enum-varnames": [ - "InternalServiceError", - "ValidationError", - "NotFound", - "BadRequest", - "Forbidden", - "UnprocessableEntity", - "RequestTimeout" - ] + "v2service.FinalityProviderPublic": { + "type": "object", + "properties": { + "active_delegations": { + "type": "integer" + }, + "active_tvl": { + "type": "integer" + }, + "btc_pk": { + "type": "string" + }, + "commission": { + "type": "string" + }, + "description": { + "$ref": "#/definitions/types.FinalityProviderDescription" + }, + "state": { + "$ref": "#/definitions/types.FinalityProviderState" + }, + "total_delegations": { + "type": "integer" + }, + "total_tvl": { + "type": "integer" + } + } + }, + "v2service.GlobalParamsPublic": { + "type": "object", + "properties": { + "babylon": { + "type": "array", + "items": { + "$ref": "#/definitions/types.BabylonParams" + } + }, + "btc": { + "type": "array", + "items": { + "$ref": "#/definitions/types.BTCParams" + } + } + } + }, + "v2service.OverallStatsPublic": { + "type": "object", + "properties": { + "active_delegations": { + "type": "integer" + }, + "active_finality_providers": { + "type": "integer" + }, + "active_stakers": { + "type": "integer" + }, + "active_tvl": { + "type": "integer" + }, + "total_delegations": { + "type": "integer" + }, + "total_finality_providers": { + "type": "integer" + }, + "total_stakers": { + "type": "integer" + }, + "total_tvl": { + "type": "integer" + } + } + }, + "v2service.StakerDelegationPublic": { + "type": "object", + "properties": { + "finality_provider_pk_hex": { + "type": "string" + }, + "staker_pk_hex": { + "type": "string" + }, + "staking_start_height": { + "type": "integer" + }, + "staking_tx": { + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.TransactionInfo" + }, + "staking_tx_hash_hex": { + "type": "string" + }, + "staking_value": { + "type": "integer" + }, + "state": { + "type": "string" + }, + "timelock": { + "type": "integer" + }, + "unbonding_start_height": { + "type": "integer" + }, + "unbonding_tx": { + "$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.TransactionInfo" + } + } + }, + "v2service.StakerStatsPublic": { + "type": "object", + "properties": { + "active_delegations": { + "type": "integer" + }, + "active_tvl": { + "type": "integer" + }, + "staker_pk_hex": { + "type": "string" + }, + "total_delegations": { + "type": "integer" + }, + "total_tvl": { + "type": "integer" + } + } } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 8ec56d27..571b9b3e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,5 +1,5 @@ definitions: - github_com_babylonlabs-io_staking-api-service_internal_types.Error: + github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error: properties: err: {} errorCode: @@ -7,68 +7,200 @@ definitions: statusCode: type: integer type: object - handlers.DelegationCheckPublicResponse: + github_com_babylonlabs-io_staking-api-service_internal_shared_types.TransactionInfo: properties: - code: + output_index: type: integer + tx_hex: + type: string + type: object + handler.PublicResponse-array_v1service_DelegationPublic: + properties: data: - type: boolean + items: + $ref: '#/definitions/v1service.DelegationPublic' + type: array + pagination: + $ref: '#/definitions/handler.paginationResponse' + type: object + handler.PublicResponse-array_v1service_FpDetailsPublic: + properties: + data: + items: + $ref: '#/definitions/v1service.FpDetailsPublic' + type: array + pagination: + $ref: '#/definitions/handler.paginationResponse' type: object - handlers.PublicResponse-array_services_DelegationPublic: + handler.PublicResponse-array_v1service_StakerStatsPublic: properties: data: items: - $ref: '#/definitions/services.DelegationPublic' + $ref: '#/definitions/v1service.StakerStatsPublic' type: array pagination: - $ref: '#/definitions/handlers.paginationResponse' + $ref: '#/definitions/handler.paginationResponse' type: object - handlers.PublicResponse-array_services_FpDetailsPublic: + handler.PublicResponse-array_v2service_FinalityProviderPublic: properties: data: items: - $ref: '#/definitions/services.FpDetailsPublic' + $ref: '#/definitions/v2service.FinalityProviderPublic' type: array pagination: - $ref: '#/definitions/handlers.paginationResponse' + $ref: '#/definitions/handler.paginationResponse' type: object - handlers.PublicResponse-array_services_StakerStatsPublic: + handler.PublicResponse-array_v2service_StakerDelegationPublic: properties: data: items: - $ref: '#/definitions/services.StakerStatsPublic' + $ref: '#/definitions/v2service.StakerDelegationPublic' type: array pagination: - $ref: '#/definitions/handlers.paginationResponse' + $ref: '#/definitions/handler.paginationResponse' type: object - handlers.PublicResponse-services_DelegationPublic: + handler.PublicResponse-v1service_DelegationPublic: properties: data: - $ref: '#/definitions/services.DelegationPublic' + $ref: '#/definitions/v1service.DelegationPublic' pagination: - $ref: '#/definitions/handlers.paginationResponse' + $ref: '#/definitions/handler.paginationResponse' type: object - handlers.PublicResponse-services_GlobalParamsPublic: + handler.PublicResponse-v1service_GlobalParamsPublic: properties: data: - $ref: '#/definitions/services.GlobalParamsPublic' + $ref: '#/definitions/v1service.GlobalParamsPublic' pagination: - $ref: '#/definitions/handlers.paginationResponse' + $ref: '#/definitions/handler.paginationResponse' type: object - handlers.PublicResponse-services_OverallStatsPublic: + handler.PublicResponse-v1service_OverallStatsPublic: properties: data: - $ref: '#/definitions/services.OverallStatsPublic' + $ref: '#/definitions/v1service.OverallStatsPublic' pagination: - $ref: '#/definitions/handlers.paginationResponse' + $ref: '#/definitions/handler.paginationResponse' type: object - handlers.Result: + handler.PublicResponse-v2service_GlobalParamsPublic: + properties: + data: + $ref: '#/definitions/v2service.GlobalParamsPublic' + pagination: + $ref: '#/definitions/handler.paginationResponse' + type: object + handler.PublicResponse-v2service_OverallStatsPublic: + properties: + data: + $ref: '#/definitions/v2service.OverallStatsPublic' + pagination: + $ref: '#/definitions/handler.paginationResponse' + type: object + handler.PublicResponse-v2service_StakerStatsPublic: + properties: + data: + $ref: '#/definitions/v2service.StakerStatsPublic' + pagination: + $ref: '#/definitions/handler.paginationResponse' + type: object + handler.Result: properties: data: {} status: type: integer type: object - handlers.UnbondDelegationRequestPayload: + handler.paginationResponse: + properties: + next_key: + type: string + type: object + types.BTCParams: + properties: + btc_confirmation_depth: + type: integer + version: + type: integer + type: object + types.BabylonParams: + properties: + covenant_pks: + items: + type: string + type: array + covenant_quorum: + type: integer + delegation_creation_base_gas_fee: + type: integer + max_active_finality_providers: + type: integer + max_staking_amount: + type: integer + max_staking_time: + type: integer + min_commission_rate: + type: number + min_slashing_tx_fee: + type: integer + min_staking_amount: + type: integer + min_staking_time: + type: integer + min_unbonding_time: + type: integer + slashing_pk_script: + type: string + slashing_rate: + type: number + unbonding_fee: + type: integer + version: + type: integer + type: object + types.ErrorCode: + enum: + - INTERNAL_SERVICE_ERROR + - VALIDATION_ERROR + - NOT_FOUND + - BAD_REQUEST + - FORBIDDEN + - UNPROCESSABLE_ENTITY + - REQUEST_TIMEOUT + type: string + x-enum-varnames: + - InternalServiceError + - ValidationError + - NotFound + - BadRequest + - Forbidden + - UnprocessableEntity + - RequestTimeout + types.FinalityProviderDescription: + properties: + details: + type: string + identity: + type: string + moniker: + type: string + security_contact: + type: string + website: + type: string + type: object + types.FinalityProviderState: + enum: + - active + - standby + type: string + x-enum-varnames: + - FinalityProviderStateActive + - FinalityProviderStateStandby + v1handlers.DelegationCheckPublicResponse: + properties: + code: + type: integer + data: + type: boolean + type: object + v1handlers.UnbondDelegationRequestPayload: properties: staker_signed_signature_hex: type: string @@ -79,12 +211,7 @@ definitions: unbonding_tx_hex: type: string type: object - handlers.paginationResponse: - properties: - next_key: - type: string - type: object - services.DelegationPublic: + v1service.DelegationPublic: properties: finality_provider_pk_hex: type: string @@ -93,7 +220,7 @@ definitions: staker_pk_hex: type: string staking_tx: - $ref: '#/definitions/services.TransactionPublic' + $ref: '#/definitions/v1service.TransactionPublic' staking_tx_hash_hex: type: string staking_value: @@ -101,9 +228,9 @@ definitions: state: type: string unbonding_tx: - $ref: '#/definitions/services.TransactionPublic' + $ref: '#/definitions/v1service.TransactionPublic' type: object - services.FpDescriptionPublic: + v1service.FpDescriptionPublic: properties: details: type: string @@ -116,7 +243,7 @@ definitions: website: type: string type: object - services.FpDetailsPublic: + v1service.FpDetailsPublic: properties: active_delegations: type: integer @@ -127,20 +254,20 @@ definitions: commission: type: string description: - $ref: '#/definitions/services.FpDescriptionPublic' + $ref: '#/definitions/v1service.FpDescriptionPublic' total_delegations: type: integer total_tvl: type: integer type: object - services.GlobalParamsPublic: + v1service.GlobalParamsPublic: properties: versions: items: - $ref: '#/definitions/services.VersionedGlobalParamsPublic' + $ref: '#/definitions/v1service.VersionedGlobalParamsPublic' type: array type: object - services.OverallStatsPublic: + v1service.OverallStatsPublic: properties: active_delegations: type: integer @@ -157,7 +284,7 @@ definitions: unconfirmed_tvl: type: integer type: object - services.StakerStatsPublic: + v1service.StakerStatsPublic: properties: active_delegations: type: integer @@ -170,7 +297,7 @@ definitions: total_tvl: type: integer type: object - services.TransactionPublic: + v1service.TransactionPublic: properties: output_index: type: integer @@ -183,7 +310,7 @@ definitions: tx_hex: type: string type: object - services.VersionedGlobalParamsPublic: + v1service.VersionedGlobalParamsPublic: properties: activation_height: type: integer @@ -216,24 +343,91 @@ definitions: version: type: integer type: object - types.ErrorCode: - enum: - - INTERNAL_SERVICE_ERROR - - VALIDATION_ERROR - - NOT_FOUND - - BAD_REQUEST - - FORBIDDEN - - UNPROCESSABLE_ENTITY - - REQUEST_TIMEOUT - type: string - x-enum-varnames: - - InternalServiceError - - ValidationError - - NotFound - - BadRequest - - Forbidden - - UnprocessableEntity - - RequestTimeout + v2service.FinalityProviderPublic: + properties: + active_delegations: + type: integer + active_tvl: + type: integer + btc_pk: + type: string + commission: + type: string + description: + $ref: '#/definitions/types.FinalityProviderDescription' + state: + $ref: '#/definitions/types.FinalityProviderState' + total_delegations: + type: integer + total_tvl: + type: integer + type: object + v2service.GlobalParamsPublic: + properties: + babylon: + items: + $ref: '#/definitions/types.BabylonParams' + type: array + btc: + items: + $ref: '#/definitions/types.BTCParams' + type: array + type: object + v2service.OverallStatsPublic: + properties: + active_delegations: + type: integer + active_finality_providers: + type: integer + active_stakers: + type: integer + active_tvl: + type: integer + total_delegations: + type: integer + total_finality_providers: + type: integer + total_stakers: + type: integer + total_tvl: + type: integer + type: object + v2service.StakerDelegationPublic: + properties: + finality_provider_pk_hex: + type: string + staker_pk_hex: + type: string + staking_start_height: + type: integer + staking_tx: + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.TransactionInfo' + staking_tx_hash_hex: + type: string + staking_value: + type: integer + state: + type: string + timelock: + type: integer + unbonding_start_height: + type: integer + unbonding_tx: + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.TransactionInfo' + type: object + v2service.StakerStatsPublic: + properties: + active_delegations: + type: integer + active_tvl: + type: integer + staker_pk_hex: + type: string + total_delegations: + type: integer + total_tvl: + type: integer + type: object info: contact: email: contact@babylonlabs.io @@ -257,6 +451,8 @@ paths: schema: type: string summary: Health check endpoint + tags: + - shared /v1/delegation: get: description: Retrieves a delegation by a given transaction hash @@ -272,11 +468,13 @@ paths: "200": description: Delegation schema: - $ref: '#/definitions/handlers.PublicResponse-services_DelegationPublic' + $ref: '#/definitions/handler.PublicResponse-v1service_DelegationPublic' "400": description: 'Error: Bad Request' schema: - $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error' + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' + tags: + - v1 /v1/finality-providers: get: description: Fetches details of all active finality providers sorted by their @@ -297,8 +495,10 @@ paths: description: A list of finality providers sorted by ActiveTvl in descending order schema: - $ref: '#/definitions/handlers.PublicResponse-array_services_FpDetailsPublic' + $ref: '#/definitions/handler.PublicResponse-array_v1service_FpDetailsPublic' summary: Get Active Finality Providers + tags: + - v1 /v1/global-params: get: description: Retrieves the global parameters for Babylon, including finality @@ -309,8 +509,10 @@ paths: "200": description: Global parameters schema: - $ref: '#/definitions/handlers.PublicResponse-services_GlobalParamsPublic' + $ref: '#/definitions/handler.PublicResponse-v1service_GlobalParamsPublic' summary: Get Babylon global parameters + tags: + - v1 /v1/staker/delegation/check: get: description: |- @@ -335,11 +537,13 @@ paths: "200": description: Delegation check result schema: - $ref: '#/definitions/handlers.DelegationCheckPublicResponse' + $ref: '#/definitions/v1handlers.DelegationCheckPublicResponse' "400": description: 'Error: Bad Request' schema: - $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error' + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' + tags: + - v1 /v1/staker/delegations: get: description: Retrieves delegations for a given staker @@ -369,11 +573,13 @@ paths: "200": description: List of delegations and pagination token schema: - $ref: '#/definitions/handlers.PublicResponse-array_services_DelegationPublic' + $ref: '#/definitions/handler.PublicResponse-array_v1service_DelegationPublic' "400": description: 'Error: Bad Request' schema: - $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error' + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' + tags: + - v1 /v1/staker/pubkey-lookup: get: description: Retrieves public keys for the given BTC addresses. This endpoint @@ -394,15 +600,17 @@ paths: description: A map of BTC addresses to their corresponding public keys (only addresses with delegations are returned) schema: - $ref: '#/definitions/handlers.Result' + $ref: '#/definitions/handler.Result' "400": description: 'Bad Request: Invalid input parameters' schema: - $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error' + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' "500": description: Internal Server Error schema: - $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error' + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' + tags: + - v1 /v1/stats: get: description: Fetches overall stats for babylon staking including tvl, total @@ -413,8 +621,10 @@ paths: "200": description: Overall stats for babylon staking schema: - $ref: '#/definitions/handlers.PublicResponse-services_OverallStatsPublic' + $ref: '#/definitions/handler.PublicResponse-v1service_OverallStatsPublic' summary: Get Overall Stats + tags: + - v1 /v1/stats/staker: get: description: |- @@ -436,12 +646,14 @@ paths: "200": description: List of top stakers by active tvl schema: - $ref: '#/definitions/handlers.PublicResponse-array_services_StakerStatsPublic' + $ref: '#/definitions/handler.PublicResponse-array_v1service_StakerStatsPublic' "400": description: 'Error: Bad Request' schema: - $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error' + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' summary: Get Staker Stats + tags: + - v1 /v1/unbonding: post: consumes: @@ -454,7 +666,7 @@ paths: name: payload required: true schema: - $ref: '#/definitions/handlers.UnbondDelegationRequestPayload' + $ref: '#/definitions/v1handlers.UnbondDelegationRequestPayload' produces: - application/json responses: @@ -463,8 +675,10 @@ paths: "400": description: Invalid request payload schema: - $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error' + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' summary: Unbond delegation + tags: + - v1 /v1/unbonding/eligibility: get: description: Checks if a delegation identified by its staking transaction hash @@ -483,6 +697,122 @@ paths: "400": description: Missing or invalid 'staking_tx_hash_hex' query parameter schema: - $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error' + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' summary: Check unbonding eligibility + tags: + - v1 + /v2/finality-providers: + get: + description: Fetches finality providers including their public keys, active + tvl, total tvl, descriptions, commission, active delegations and total delegations + etc + parameters: + - description: Pagination key to fetch the next page of finality providers + in: query + name: pagination_key + type: string + - description: Filter by finality provider public key + in: query + name: finality_provider_pk + type: string + - description: Sort by field + enum: + - active_tvl + - name + - commission + in: query + name: sort_by + type: string + - description: Order + enum: + - asc + - desc + in: query + name: order + type: string + produces: + - application/json + responses: + "200": + description: List of finality providers and pagination token + schema: + $ref: '#/definitions/handler.PublicResponse-array_v2service_FinalityProviderPublic' + "400": + description: 'Error: Bad Request' + schema: + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' + summary: Get Finality Providers + tags: + - v2 + /v2/global-params: + get: + description: Fetches global parameters for babylon chain and BTC chain + produces: + - application/json + responses: + "200": + description: Global parameters + schema: + $ref: '#/definitions/handler.PublicResponse-v2service_GlobalParamsPublic' + summary: Get Global Parameters + tags: + - v2 + /v2/staker/delegations: + get: + description: Fetches staker delegations for babylon staking including tvl, total + delegations, active tvl, active delegations and total stakers. + parameters: + - description: Staking transaction hash in hex format + in: query + name: staking_tx_hash_hex + required: true + type: string + - description: Pagination key to fetch the next page of delegations + in: query + name: pagination_key + type: string + produces: + - application/json + responses: + "200": + description: List of staker delegations and pagination token + schema: + $ref: '#/definitions/handler.PublicResponse-array_v2service_StakerDelegationPublic' + "400": + description: 'Error: Bad Request' + schema: + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' + summary: Get Staker Delegations + tags: + - v2 + /v2/staker/stats: + get: + description: Fetches staker stats for babylon staking including active tvl, + total tvl, active delegations and total delegations. + produces: + - application/json + responses: + "200": + description: Staker stats + schema: + $ref: '#/definitions/handler.PublicResponse-v2service_StakerStatsPublic' + summary: Get Staker Stats + tags: + - v2 + /v2/stats: + get: + description: Overall system stats + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.PublicResponse-v2service_OverallStatsPublic' + "400": + description: 'Error: Bad Request' + schema: + $ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_shared_types.Error' + tags: + - v2 swagger: "2.0" diff --git a/internal/api/handlers/delegation.go b/internal/api/handlers/delegation.go deleted file mode 100644 index 1dbd9b40..00000000 --- a/internal/api/handlers/delegation.go +++ /dev/null @@ -1,28 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" -) - -// GetDelegationByTxHash @Summary Get a delegation -// @Description Retrieves a delegation by a given transaction hash -// @Produce json -// @Param staking_tx_hash_hex query string true "Staking transaction hash in hex format" -// @Success 200 {object} PublicResponse[services.DelegationPublic] "Delegation" -// @Failure 400 {object} types.Error "Error: Bad Request" -// @Router /v1/delegation [get] -func (h *Handler) GetDelegationByTxHash(request *http.Request) (*Result, *types.Error) { - stakingTxHash, err := parseTxHashQuery(request, "staking_tx_hash_hex") - if err != nil { - return nil, err - } - delegation, err := h.services.GetDelegation(request.Context(), stakingTxHash) - if err != nil { - return nil, err - } - - return NewResult(services.FromDelegationDocument(delegation)), nil -} diff --git a/internal/api/handlers/finality_provider.go b/internal/api/handlers/finality_provider.go deleted file mode 100644 index 2c0106cd..00000000 --- a/internal/api/handlers/finality_provider.go +++ /dev/null @@ -1,45 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" -) - -// GetFinalityProviders gets active finality providers sorted by ActiveTvl. -// @Summary Get Active Finality Providers -// @Description Fetches details of all active finality providers sorted by their active total value locked (ActiveTvl) in descending order. -// @Produce json -// @Param fp_btc_pk query string false "Public key of the finality provider to fetch" -// @Param pagination_key query string false "Pagination key to fetch the next page of finality providers" -// @Success 200 {object} PublicResponse[[]services.FpDetailsPublic] "A list of finality providers sorted by ActiveTvl in descending order" -// @Router /v1/finality-providers [get] -func (h *Handler) GetFinalityProviders(request *http.Request) (*Result, *types.Error) { - fpPk, err := parsePublicKeyQuery(request, "fp_btc_pk", true) - if err != nil { - return nil, err - } - if fpPk != "" { - var result []*services.FpDetailsPublic - fp, err := h.services.GetFinalityProvider(request.Context(), fpPk) - if err != nil { - return nil, err - } - if fp != nil { - result = append(result, fp) - } - - return NewResult(result), nil - } - - paginationKey, err := parsePaginationQuery(request) - if err != nil { - return nil, err - } - fps, paginationToken, err := h.services.GetFinalityProviders(request.Context(), paginationKey) - if err != nil { - return nil, err - } - return NewResultWithPagination(fps, paginationToken), nil -} diff --git a/internal/api/handlers/params.go b/internal/api/handlers/params.go deleted file mode 100644 index 7a94b529..00000000 --- a/internal/api/handlers/params.go +++ /dev/null @@ -1,18 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/babylonlabs-io/staking-api-service/internal/types" -) - -// GetBabylonGlobalParams godoc -// @Summary Get Babylon global parameters -// @Description Retrieves the global parameters for Babylon, including finality provider details. -// @Produce json -// @Success 200 {object} PublicResponse[services.GlobalParamsPublic] "Global parameters" -// @Router /v1/global-params [get] -func (h *Handler) GetBabylonGlobalParams(request *http.Request) (*Result, *types.Error) { - params := h.services.GetGlobalParamsPublic() - return NewResult(params), nil -} diff --git a/internal/api/routes.go b/internal/api/routes.go deleted file mode 100644 index 2380debf..00000000 --- a/internal/api/routes.go +++ /dev/null @@ -1,32 +0,0 @@ -package api - -import ( - _ "github.com/babylonlabs-io/staking-api-service/docs" - "github.com/go-chi/chi" - httpSwagger "github.com/swaggo/http-swagger" -) - -func (a *Server) SetupRoutes(r *chi.Mux) { - handlers := a.handlers - r.Get("/healthcheck", registerHandler(handlers.HealthCheck)) - - r.Get("/v1/staker/delegations", registerHandler(handlers.GetStakerDelegations)) - r.Post("/v1/unbonding", registerHandler(handlers.UnbondDelegation)) - r.Get("/v1/unbonding/eligibility", registerHandler(handlers.GetUnbondingEligibility)) - r.Get("/v1/global-params", registerHandler(handlers.GetBabylonGlobalParams)) - r.Get("/v1/finality-providers", registerHandler(handlers.GetFinalityProviders)) - r.Get("/v1/stats", registerHandler(handlers.GetOverallStats)) - r.Get("/v1/stats/staker", registerHandler(handlers.GetStakersStats)) - r.Get("/v1/staker/delegation/check", registerHandler(handlers.CheckStakerDelegationExist)) - r.Get("/v1/delegation", registerHandler(handlers.GetDelegationByTxHash)) - - // Only register these routes if the asset has been configured - // The endpoints are used to check ordinals within the UTXOs - if a.cfg.Assets != nil { - r.Post("/v1/ordinals/verify-utxos", registerHandler(handlers.VerifyUTXOs)) - } - - r.Get("/v1/staker/pubkey-lookup", registerHandler(handlers.GetPubKeys)) - - r.Get("/swagger/*", httpSwagger.WrapHandler) -} diff --git a/internal/clients/clients.go b/internal/clients/clients.go deleted file mode 100644 index 2e042428..00000000 --- a/internal/clients/clients.go +++ /dev/null @@ -1,22 +0,0 @@ -package clients - -import ( - "github.com/babylonlabs-io/staking-api-service/internal/clients/ordinals" - "github.com/babylonlabs-io/staking-api-service/internal/config" -) - -type Clients struct { - Ordinals ordinals.OrdinalsClientInterface -} - -func New(cfg *config.Config) *Clients { - var ordinalsClient *ordinals.OrdinalsClient - // If the assets config is set, create the ordinal related clients - if cfg.Assets != nil { - ordinalsClient = ordinals.NewOrdinalsClient(cfg.Assets.Ordinals) - } - - return &Clients{ - Ordinals: ordinalsClient, - } -} diff --git a/internal/db/timelock.go b/internal/db/timelock.go deleted file mode 100644 index 14627716..00000000 --- a/internal/db/timelock.go +++ /dev/null @@ -1,27 +0,0 @@ -package db - -import ( - "context" - - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" -) - -func (db *Database) SaveTimeLockExpireCheck( - ctx context.Context, stakingTxHashHex string, - expireHeight uint64, txType string, -) error { - client := db.Client.Database(db.DbName).Collection(model.TimeLockCollection) - document := model.NewTimeLockDocument(stakingTxHashHex, expireHeight, txType) - _, err := client.InsertOne(ctx, document) - if err != nil { - return err - } - return nil -} - -func (db *Database) TransitionToUnbondedState( - ctx context.Context, stakingTxHashHex string, eligiblePreviousState []types.DelegationState, -) error { - return db.transitionState(ctx, stakingTxHashHex, types.Unbonded.ToString(), eligiblePreviousState, nil) -} diff --git a/internal/db/withdraw.go b/internal/db/withdraw.go deleted file mode 100644 index c18b5aa1..00000000 --- a/internal/db/withdraw.go +++ /dev/null @@ -1,19 +0,0 @@ -package db - -import ( - "context" - - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" -) - -func (db *Database) TransitionToWithdrawnState(ctx context.Context, txHashHex string) error { - err := db.transitionState( - ctx, txHashHex, types.Withdrawn.ToString(), - utils.QualifiedStatesToWithdraw(), nil, - ) - if err != nil { - return err - } - return nil -} diff --git a/internal/queue/queue.go b/internal/queue/queue.go deleted file mode 100644 index 5ddcd1db..00000000 --- a/internal/queue/queue.go +++ /dev/null @@ -1,286 +0,0 @@ -package queue - -import ( - "context" - "fmt" - "net/http" - "strings" - "time" - - "github.com/babylonlabs-io/staking-api-service/internal/observability/metrics" - "github.com/babylonlabs-io/staking-api-service/internal/observability/tracing" - "github.com/babylonlabs-io/staking-api-service/internal/queue/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-queue-client/client" - queueConfig "github.com/babylonlabs-io/staking-queue-client/config" - "github.com/rs/zerolog/log" -) - -type Queues struct { - Handlers *handlers.QueueHandler - processingTimeout time.Duration - maxRetryAttempts int32 - ActiveStakingQueueClient client.QueueClient - ExpiredStakingQueueClient client.QueueClient - UnbondingStakingQueueClient client.QueueClient - WithdrawStakingQueueClient client.QueueClient - StatsQueueClient client.QueueClient - BtcInfoQueueClient client.QueueClient -} - -func New(cfg *queueConfig.QueueConfig, service *services.Services) *Queues { - activeStakingQueueClient, err := client.NewQueueClient( - cfg, client.ActiveStakingQueueName, - ) - if err != nil { - log.Fatal().Err(err).Msg("error while creating ActiveStakingQueueClient") - } - - expiredStakingQueueClient, err := client.NewQueueClient( - cfg, client.ExpiredStakingQueueName, - ) - if err != nil { - log.Fatal().Err(err).Msg("error while creating ExpiredStakingQueueClient") - } - - unbondingStakingQueueClient, err := client.NewQueueClient( - cfg, client.UnbondingStakingQueueName, - ) - if err != nil { - log.Fatal().Err(err).Msg("error while creating UnbondingStakingQueueClient") - } - - withdrawStakingQueueClient, err := client.NewQueueClient( - cfg, client.WithdrawStakingQueueName, - ) - if err != nil { - log.Fatal().Err(err).Msg("error while creating WithdrawStakingQueueClient") - } - - statsQueueClient, err := client.NewQueueClient( - cfg, client.StakingStatsQueueName, - ) - if err != nil { - log.Fatal().Err(err).Msg("error while creating StatsQueueClient") - } - - btcInfoQueueClient, err := client.NewQueueClient( - cfg, client.BtcInfoQueueName, - ) - if err != nil { - log.Fatal().Err(err).Msg("error while creating BtcInfoQueueClient") - } - - handlers := handlers.NewQueueHandler(service, statsQueueClient.SendMessage) - return &Queues{ - Handlers: handlers, - processingTimeout: time.Duration(cfg.QueueProcessingTimeout) * time.Second, - maxRetryAttempts: cfg.MsgMaxRetryAttempts, - ActiveStakingQueueClient: activeStakingQueueClient, - ExpiredStakingQueueClient: expiredStakingQueueClient, - UnbondingStakingQueueClient: unbondingStakingQueueClient, - WithdrawStakingQueueClient: withdrawStakingQueueClient, - StatsQueueClient: statsQueueClient, - BtcInfoQueueClient: btcInfoQueueClient, - } -} - -// Start all message processing -func (q *Queues) StartReceivingMessages() { - // start processing messages from the active staking queue - startQueueMessageProcessing( - q.ActiveStakingQueueClient, - q.Handlers.ActiveStakingHandler, q.Handlers.HandleUnprocessedMessage, - q.maxRetryAttempts, q.processingTimeout, - ) - startQueueMessageProcessing( - q.ExpiredStakingQueueClient, - q.Handlers.ExpiredStakingHandler, q.Handlers.HandleUnprocessedMessage, - q.maxRetryAttempts, q.processingTimeout, - ) - startQueueMessageProcessing( - q.UnbondingStakingQueueClient, - q.Handlers.UnbondingStakingHandler, q.Handlers.HandleUnprocessedMessage, - q.maxRetryAttempts, q.processingTimeout, - ) - startQueueMessageProcessing( - q.WithdrawStakingQueueClient, - q.Handlers.WithdrawStakingHandler, q.Handlers.HandleUnprocessedMessage, - q.maxRetryAttempts, q.processingTimeout, - ) - startQueueMessageProcessing( - q.StatsQueueClient, - q.Handlers.StatsHandler, q.Handlers.HandleUnprocessedMessage, - q.maxRetryAttempts, q.processingTimeout, - ) - startQueueMessageProcessing( - q.BtcInfoQueueClient, - q.Handlers.BtcInfoHandler, q.Handlers.HandleUnprocessedMessage, - q.maxRetryAttempts, q.processingTimeout, - ) - // ...add more queues here -} - -// Turn off all message processing -func (q *Queues) StopReceivingMessages() { - activeQueueErr := q.ActiveStakingQueueClient.Stop() - if activeQueueErr != nil { - log.Error().Err(activeQueueErr). - Str("queueName", q.ActiveStakingQueueClient.GetQueueName()). - Msg("error while stopping queue") - } - expiredQueueErr := q.ExpiredStakingQueueClient.Stop() - if expiredQueueErr != nil { - log.Error().Err(expiredQueueErr). - Str("queueName", q.ExpiredStakingQueueClient.GetQueueName()). - Msg("error while stopping queue") - } - unbondingQueueErr := q.UnbondingStakingQueueClient.Stop() - if unbondingQueueErr != nil { - log.Error().Err(unbondingQueueErr). - Str("queueName", q.UnbondingStakingQueueClient.GetQueueName()). - Msg("error while stopping queue") - } - withdrawnQueueErr := q.WithdrawStakingQueueClient.Stop() - if withdrawnQueueErr != nil { - log.Error().Err(withdrawnQueueErr). - Str("queueName", q.WithdrawStakingQueueClient.GetQueueName()). - Msg("error while stopping queue") - } - statsQueueErr := q.StatsQueueClient.Stop() - if statsQueueErr != nil { - log.Error().Err(statsQueueErr). - Str("queueName", q.StatsQueueClient.GetQueueName()). - Msg("error while stopping queue") - } - btcInfoQueueErr := q.BtcInfoQueueClient.Stop() - if btcInfoQueueErr != nil { - log.Error().Err(btcInfoQueueErr). - Str("queueName", q.BtcInfoQueueClient.GetQueueName()). - Msg("error while stopping queue") - } - // ...add more queues here -} - -func startQueueMessageProcessing( - queueClient client.QueueClient, - handler handlers.MessageHandler, unprocessableHandler handlers.UnprocessableMessageHandler, - maxRetryAttempts int32, processingTimeout time.Duration, -) { - messagesChan, err := queueClient.ReceiveMessages() - log.Info().Str("queueName", queueClient.GetQueueName()).Msg("start receiving messages from queue") - if err != nil { - log.Fatal().Err(err).Str("queueName", queueClient.GetQueueName()).Msg("error setting up message channel from queue") - } - - go func() { - for message := range messagesChan { - attempts := message.GetRetryAttempts() - // For each message, create a new context with a deadline or timeout - ctx, cancel := context.WithTimeout(context.Background(), processingTimeout) - ctx = attachLoggerContext(ctx, message, queueClient) - // Attach the tracingInfo for the message processing - _, err := tracing.WrapWithSpan[any](ctx, "message_processing", func() (any, *types.Error) { - timer := metrics.StartEventProcessingDurationTimer(queueClient.GetQueueName(), attempts) - // Process the message - err := handler(ctx, message.Body) - if err != nil { - timer(err.StatusCode) - } else { - timer(http.StatusOK) - } - return nil, err - }) - if err != nil { - recordErrorLog(err) - // We will retry the message if it has not exceeded the max retry attempts - // otherwise, we will dump the message into db for manual inspection and remove from the queue - if attempts > maxRetryAttempts { - log.Ctx(ctx).Error().Err(err). - Msg("exceeded retry attempts, message will be dumped into db for manual inspection") - metrics.RecordUnprocessableEntity(queueClient.GetQueueName()) - saveUnprocessableMsgErr := unprocessableHandler(ctx, message.Body, message.Receipt) - if saveUnprocessableMsgErr != nil { - log.Ctx(ctx).Error().Err(saveUnprocessableMsgErr). - Msg("error while saving unprocessable message") - metrics.RecordQueueOperationFailure("unprocessableHandler", queueClient.GetQueueName()) - cancel() - continue - } - } else { - log.Ctx(ctx).Error().Err(err). - Msg("error while processing message from queue, will be requeued") - reQueueErr := queueClient.ReQueueMessage(ctx, message) - if reQueueErr != nil { - log.Ctx(ctx).Error().Err(reQueueErr). - Msg("error while requeuing message") - metrics.RecordQueueOperationFailure("reQueueMessage", queueClient.GetQueueName()) - } - cancel() - continue - } - } - - delErr := queueClient.DeleteMessage(message.Receipt) - if delErr != nil { - log.Ctx(ctx).Error().Err(delErr). - Msg("error while deleting message from queue") - metrics.RecordQueueOperationFailure("deleteMessage", queueClient.GetQueueName()) - } - - tracingInfo := ctx.Value(tracing.TracingInfoKey) - logEvent := log.Ctx(ctx).Debug() - if tracingInfo != nil { - logEvent = logEvent.Interface("tracingInfo", tracingInfo) - } - logEvent.Msg("message processed successfully") - cancel() - } - log.Info().Str("queueName", queueClient.GetQueueName()).Msg("stopped receiving messages from queue") - }() -} - -func attachLoggerContext(ctx context.Context, message client.QueueMessage, queueClient client.QueueClient) context.Context { - ctx = tracing.AttachTracingIntoContext(ctx) - - traceId := ctx.Value(tracing.TraceIdKey) - return log.With(). - Str("receipt", message.Receipt). - Str("queueName", queueClient.GetQueueName()). - Interface("traceId", traceId). - Logger().WithContext(ctx) -} - -func recordErrorLog(err *types.Error) { - if err.StatusCode >= http.StatusInternalServerError { - log.Error().Err(err).Msg("event processing failed with 5xx error") - } else { - log.Warn().Err(err).Msg("event processing failed with 4xx error") - } -} - -func (q *Queues) IsConnectionHealthy() error { - var errorMessages []string - - ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second) - defer cancel() - - checkQueue := func(name string, client client.QueueClient) { - if err := client.Ping(ctx); err != nil { - errorMessages = append(errorMessages, fmt.Sprintf("%s is not healthy: %v", name, err)) - } - } - - checkQueue("ActiveStakingQueueClient", q.ActiveStakingQueueClient) - checkQueue("ExpiredStakingQueueClient", q.ExpiredStakingQueueClient) - checkQueue("UnbondingStakingQueueClient", q.UnbondingStakingQueueClient) - checkQueue("WithdrawStakingQueueClient", q.WithdrawStakingQueueClient) - checkQueue("StatsQueueClient", q.StatsQueueClient) - checkQueue("BtcInfoQueueClient", q.BtcInfoQueueClient) - - if len(errorMessages) > 0 { - return fmt.Errorf(strings.Join(errorMessages, "; ")) - } - return nil -} diff --git a/internal/services/services.go b/internal/services/services.go deleted file mode 100644 index 781a7385..00000000 --- a/internal/services/services.go +++ /dev/null @@ -1,58 +0,0 @@ -package services - -import ( - "context" - "net/http" - - "github.com/rs/zerolog/log" - - "github.com/babylonlabs-io/staking-api-service/internal/clients" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/types" -) - -// Service layer contains the business logic and is used to interact with -// the database and other external clients (if any). -type Services struct { - DbClient db.DBClient - Clients *clients.Clients - cfg *config.Config - params *types.GlobalParams - finalityProviders []types.FinalityProviderDetails -} - -func New( - ctx context.Context, - cfg *config.Config, - globalParams *types.GlobalParams, - finalityProviders []types.FinalityProviderDetails, - clients *clients.Clients, -) (*Services, error) { - dbClient, err := db.New(ctx, cfg.Db) - if err != nil { - log.Ctx(ctx).Fatal().Err(err).Msg("error while creating db client") - return nil, err - } - return &Services{ - DbClient: dbClient, - Clients: clients, - cfg: cfg, - params: globalParams, - finalityProviders: finalityProviders, - }, nil -} - -// DoHealthCheck checks the health of the services by ping the database. -func (s *Services) DoHealthCheck(ctx context.Context) error { - return s.DbClient.Ping(ctx) -} - -func (s *Services) SaveUnprocessableMessages(ctx context.Context, messageBody, receipt string) *types.Error { - err := s.DbClient.SaveUnprocessableMessage(ctx, messageBody, receipt) - if err != nil { - log.Ctx(ctx).Error().Err(err).Msg("error while saving unprocessable message") - return types.NewErrorWithMsg(http.StatusInternalServerError, types.InternalServiceError, "error while saving unprocessable message") - } - return nil -} diff --git a/internal/api/handlers/handler.go b/internal/shared/api/handlers/handler/handler.go similarity index 80% rename from internal/api/handlers/handler.go rename to internal/shared/api/handlers/handler/handler.go index b0eb5f9e..9b20d8a2 100644 --- a/internal/api/handlers/handler.go +++ b/internal/shared/api/handlers/handler/handler.go @@ -1,20 +1,24 @@ -package handlers +package handler import ( "context" "fmt" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/services/service" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" "github.com/btcsuite/btcd/chaincfg" ) type Handler struct { - config *config.Config - services *services.Services + Config *config.Config + Service service.SharedServiceProvider +} + +func New(ctx context.Context, config *config.Config, service service.SharedServiceProvider) (*Handler, error) { + return &Handler{Config: config, Service: service}, nil } type ResultOptions struct { @@ -46,16 +50,7 @@ func NewResult[T any](data T) *Result { return &Result{Data: res, Status: http.StatusOK} } -func New( - ctx context.Context, cfg *config.Config, services *services.Services, -) (*Handler, error) { - return &Handler{ - config: cfg, - services: services, - }, nil -} - -func parsePaginationQuery(r *http.Request) (string, *types.Error) { +func ParsePaginationQuery(r *http.Request) (string, *types.Error) { pageKey := r.URL.Query().Get("pagination_key") if pageKey == "" { return "", nil @@ -68,7 +63,7 @@ func parsePaginationQuery(r *http.Request) (string, *types.Error) { return pageKey, nil } -func parsePublicKeyQuery(r *http.Request, queryName string, isOptional bool) (string, *types.Error) { +func ParsePublicKeyQuery(r *http.Request, queryName string, isOptional bool) (string, *types.Error) { pkHex := r.URL.Query().Get(queryName) if pkHex == "" { if isOptional { @@ -87,7 +82,7 @@ func parsePublicKeyQuery(r *http.Request, queryName string, isOptional bool) (st return pkHex, nil } -func parseTxHashQuery(r *http.Request, queryName string) (string, *types.Error) { +func ParseTxHashQuery(r *http.Request, queryName string) (string, *types.Error) { txHashHex := r.URL.Query().Get(queryName) if txHashHex == "" { return "", types.NewErrorWithMsg( @@ -102,7 +97,7 @@ func parseTxHashQuery(r *http.Request, queryName string) (string, *types.Error) return txHashHex, nil } -func parseBtcAddressQuery( +func ParseBtcAddressQuery( r *http.Request, queryName string, netParam *chaincfg.Params, ) (string, *types.Error) { address := r.URL.Query().Get(queryName) @@ -120,7 +115,7 @@ func parseBtcAddressQuery( return address, nil } -func parseBtcAddressesQuery( +func ParseBtcAddressesQuery( r *http.Request, queryName string, netParam *chaincfg.Params, limit int, ) ([]string, *types.Error) { // Get all the values for the queryName @@ -154,9 +149,9 @@ func parseBtcAddressesQuery( return addresses, nil } -// parseStateFilterQuery parses the state filter query and returns the state enum +// ParseStateFilterQuery parses the state filter query and returns the state enum // If the state is not provided, it returns an empty string -func parseStateFilterQuery( +func ParseStateFilterQuery( r *http.Request, queryName string, ) (types.DelegationState, *types.Error) { state := r.URL.Query().Get(queryName) diff --git a/internal/api/handlers/health.go b/internal/shared/api/handlers/handler/health.go similarity index 63% rename from internal/api/handlers/health.go rename to internal/shared/api/handlers/handler/health.go index e84ab993..35b26c08 100644 --- a/internal/api/handlers/health.go +++ b/internal/shared/api/handlers/handler/health.go @@ -1,19 +1,20 @@ -package handlers +package handler import ( "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" ) // HealthCheck godoc // @Summary Health check endpoint // @Description Health check the service, including ping database connection // @Produce json -// @Success 200 {string} PublicResponse[string] "Server is up and running" +// @Tags shared +// @Success 200 {string} handler.PublicResponse[string] "Server is up and running" // @Router /healthcheck [get] func (h *Handler) HealthCheck(request *http.Request) (*Result, *types.Error) { - err := h.services.DoHealthCheck(request.Context()) + err := h.Service.DoHealthCheck(request.Context()) if err != nil { return nil, types.NewInternalServiceError(err) } diff --git a/internal/api/handlers/ordinals.go b/internal/shared/api/handlers/handler/ordinals.go similarity index 81% rename from internal/api/handlers/ordinals.go rename to internal/shared/api/handlers/handler/ordinals.go index 81f002c5..71e67506 100644 --- a/internal/api/handlers/ordinals.go +++ b/internal/shared/api/handlers/handler/ordinals.go @@ -1,11 +1,11 @@ -package handlers +package handler import ( "encoding/json" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" "github.com/btcsuite/btcd/chaincfg" ) @@ -43,12 +43,12 @@ func parseRequestPayload(request *http.Request, maxUTXOs uint32, netParam *chain } func (h *Handler) VerifyUTXOs(request *http.Request) (*Result, *types.Error) { - inputs, err := parseRequestPayload(request, h.config.Assets.MaxUTXOs, h.config.Server.BTCNetParam) + inputs, err := parseRequestPayload(request, h.Config.Assets.MaxUTXOs, h.Config.Server.BTCNetParam) if err != nil { return nil, err } - results, err := h.services.VerifyUTXOs(request.Context(), inputs.UTXOs, inputs.Address) + results, err := h.Service.VerifyUTXOs(request.Context(), inputs.UTXOs, inputs.Address) if err != nil { return nil, err } diff --git a/internal/shared/api/handlers/handlers.go b/internal/shared/api/handlers/handlers.go new file mode 100644 index 00000000..8a1fa391 --- /dev/null +++ b/internal/shared/api/handlers/handlers.go @@ -0,0 +1,38 @@ +package handlers + +import ( + "context" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/services" + v1handler "github.com/babylonlabs-io/staking-api-service/internal/v1/api/handlers" + v2handler "github.com/babylonlabs-io/staking-api-service/internal/v2/api/handlers" +) + +type Handlers struct { + SharedHandler *handler.Handler + V1Handler *v1handler.V1Handler + V2Handler *v2handler.V2Handler +} + +func New(ctx context.Context, config *config.Config, services *services.Services) (*Handlers, error) { + sharedHandler, err := handler.New(ctx, config, services.SharedService) + if err != nil { + return nil, err + } + v1Handler, err := v1handler.New(ctx, sharedHandler, services.V1Service) + if err != nil { + return nil, err + } + v2Handler, err := v2handler.New(ctx, sharedHandler, services.V2Service) + if err != nil { + return nil, err + } + + return &Handlers{ + SharedHandler: sharedHandler, + V1Handler: v1Handler, + V2Handler: v2Handler, + }, nil +} diff --git a/internal/api/http_response.go b/internal/shared/api/http_response.go similarity index 86% rename from internal/api/http_response.go rename to internal/shared/api/http_response.go index c055e685..e87e6248 100644 --- a/internal/api/http_response.go +++ b/internal/shared/api/http_response.go @@ -4,11 +4,10 @@ import ( "encoding/json" "net/http" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/metrics" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" logger "github.com/rs/zerolog" - - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/observability/metrics" - "github.com/babylonlabs-io/staking-api-service/internal/types" ) type ErrorResponse struct { @@ -27,7 +26,7 @@ func (e *ErrorResponse) Error() string { return e.Message } -func registerHandler(handlerFunc func(*http.Request) (*handlers.Result, *types.Error)) func(http.ResponseWriter, *http.Request) { +func registerHandler(handlerFunc func(*http.Request) (*handler.Result, *types.Error)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { // Set up metrics recording for the endpoint timer := metrics.StartHttpRequestDurationTimer(r.URL.Path) diff --git a/internal/api/middlewares/content_length.go b/internal/shared/api/middlewares/content_length.go similarity index 91% rename from internal/api/middlewares/content_length.go rename to internal/shared/api/middlewares/content_length.go index fd32e0aa..e1f8412e 100644 --- a/internal/api/middlewares/content_length.go +++ b/internal/shared/api/middlewares/content_length.go @@ -3,7 +3,7 @@ package middlewares import ( "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" ) var methodsToCheck = map[string]struct{}{ diff --git a/internal/api/middlewares/cors.go b/internal/shared/api/middlewares/cors.go similarity index 96% rename from internal/api/middlewares/cors.go rename to internal/shared/api/middlewares/cors.go index eb01b6ff..90235c2c 100644 --- a/internal/api/middlewares/cors.go +++ b/internal/shared/api/middlewares/cors.go @@ -3,7 +3,7 @@ package middlewares import ( "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" "github.com/rs/cors" ) diff --git a/internal/api/middlewares/logging.go b/internal/shared/api/middlewares/logging.go similarity index 93% rename from internal/api/middlewares/logging.go rename to internal/shared/api/middlewares/logging.go index dc2c44dc..3b287511 100644 --- a/internal/api/middlewares/logging.go +++ b/internal/shared/api/middlewares/logging.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/babylonlabs-io/staking-api-service/internal/observability/tracing" + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/tracing" "github.com/rs/zerolog/log" ) diff --git a/internal/api/middlewares/security.go b/internal/shared/api/middlewares/security.go similarity index 100% rename from internal/api/middlewares/security.go rename to internal/shared/api/middlewares/security.go diff --git a/internal/api/middlewares/tracing.go b/internal/shared/api/middlewares/tracing.go similarity index 76% rename from internal/api/middlewares/tracing.go rename to internal/shared/api/middlewares/tracing.go index e55d181d..0a9f29e1 100644 --- a/internal/api/middlewares/tracing.go +++ b/internal/shared/api/middlewares/tracing.go @@ -3,7 +3,7 @@ package middlewares import ( "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/observability/tracing" + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/tracing" ) func TracingMiddleware(next http.Handler) http.Handler { diff --git a/internal/shared/api/routes.go b/internal/shared/api/routes.go new file mode 100644 index 00000000..3dcb541a --- /dev/null +++ b/internal/shared/api/routes.go @@ -0,0 +1,43 @@ +package api + +import ( + _ "github.com/babylonlabs-io/staking-api-service/docs" + "github.com/go-chi/chi" + httpSwagger "github.com/swaggo/http-swagger" +) + +func (a *Server) SetupRoutes(r *chi.Mux) { + handlers := a.handlers + // Extend on the healthcheck endpoint here + r.Get("/healthcheck", registerHandler(handlers.SharedHandler.HealthCheck)) + + r.Get("/v1/staker/delegations", registerHandler(handlers.V1Handler.GetStakerDelegations)) + r.Post("/v1/unbonding", registerHandler(handlers.V1Handler.UnbondDelegation)) + r.Get("/v1/unbonding/eligibility", registerHandler(handlers.V1Handler.GetUnbondingEligibility)) + r.Get("/v1/global-params", registerHandler(handlers.V1Handler.GetBabylonGlobalParams)) + r.Get("/v1/finality-providers", registerHandler(handlers.V1Handler.GetFinalityProviders)) + r.Get("/v1/stats", registerHandler(handlers.V1Handler.GetOverallStats)) + r.Get("/v1/stats/staker", registerHandler(handlers.V1Handler.GetStakersStats)) + r.Get("/v1/staker/delegation/check", registerHandler(handlers.V1Handler.CheckStakerDelegationExist)) + r.Get("/v1/delegation", registerHandler(handlers.V1Handler.GetDelegationByTxHash)) + + // Only register these routes if the asset has been configured + // The endpoints are used to check ordinals within the UTXOs + // Don't deprecate this endpoint + if a.cfg.Assets != nil { + r.Post("/v1/ordinals/verify-utxos", registerHandler(handlers.SharedHandler.VerifyUTXOs)) + } + + // Don't deprecate this endpoint + r.Get("/v1/staker/pubkey-lookup", registerHandler(handlers.V1Handler.GetPubKeys)) + + r.Get("/swagger/*", httpSwagger.WrapHandler) + + // V2 API + // TODO: Implement the handlers for the V2 API + r.Get("/v2/stats", registerHandler(handlers.V2Handler.GetStats)) + r.Get("/v2/finality-providers", registerHandler(handlers.V2Handler.GetFinalityProviders)) + r.Get("/v2/global-params", registerHandler(handlers.V2Handler.GetGlobalParams)) + r.Get("/v2/staker/delegations", registerHandler(handlers.V2Handler.GetStakerDelegations)) + r.Get("/v2/staker/stats", registerHandler(handlers.V2Handler.GetStakerStats)) +} diff --git a/internal/api/server.go b/internal/shared/api/server.go similarity index 76% rename from internal/api/server.go rename to internal/shared/api/server.go index c3752562..70ae997c 100644 --- a/internal/api/server.go +++ b/internal/shared/api/server.go @@ -5,10 +5,10 @@ import ( "fmt" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/api/middlewares" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/services" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/middlewares" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/services" "github.com/go-chi/chi" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -16,7 +16,7 @@ import ( type Server struct { httpServer *http.Server - handlers *handlers.Handler + handlers *handlers.Handlers cfg *config.Config } @@ -45,6 +45,10 @@ func New( Handler: r, } + if err != nil { + log.Fatal().Err(err).Msg("error while setting up handlers") + } + handlers, err := handlers.New(ctx, cfg, services) if err != nil { log.Fatal().Err(err).Msg("error while setting up handlers") diff --git a/internal/config/assets.go b/internal/shared/config/assets.go similarity index 100% rename from internal/config/assets.go rename to internal/shared/config/assets.go diff --git a/internal/config/config.go b/internal/shared/config/config.go similarity index 100% rename from internal/config/config.go rename to internal/shared/config/config.go diff --git a/internal/config/db.go b/internal/shared/config/db.go similarity index 100% rename from internal/config/db.go rename to internal/shared/config/db.go diff --git a/internal/config/metrics.go b/internal/shared/config/metrics.go similarity index 100% rename from internal/config/metrics.go rename to internal/shared/config/metrics.go diff --git a/internal/config/ordinals.go b/internal/shared/config/ordinals.go similarity index 100% rename from internal/config/ordinals.go rename to internal/shared/config/ordinals.go diff --git a/internal/config/server.go b/internal/shared/config/server.go similarity index 96% rename from internal/config/server.go rename to internal/shared/config/server.go index cc047d22..98a3145a 100644 --- a/internal/config/server.go +++ b/internal/shared/config/server.go @@ -6,10 +6,9 @@ import ( "net" "time" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" "github.com/btcsuite/btcd/chaincfg" "github.com/rs/zerolog" - - "github.com/babylonlabs-io/staking-api-service/internal/utils" ) type ServerConfig struct { diff --git a/internal/db/README.md b/internal/shared/db/README.md similarity index 100% rename from internal/db/README.md rename to internal/shared/db/README.md diff --git a/internal/shared/db/client/db_client.go b/internal/shared/db/client/db_client.go new file mode 100644 index 00000000..3a8f3de6 --- /dev/null +++ b/internal/shared/db/client/db_client.go @@ -0,0 +1,40 @@ +package dbclient + +import ( + "context" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type Database struct { + DbName string + Client *mongo.Client + Cfg *config.DbConfig +} + +func NewMongoClient(ctx context.Context, cfg *config.DbConfig) (*mongo.Client, error) { + credential := options.Credential{ + Username: cfg.Username, + Password: cfg.Password, + } + clientOps := options.Client().ApplyURI(cfg.Address).SetAuth(credential) + return mongo.Connect(ctx, clientOps) +} + +func (db *Database) Ping(ctx context.Context) error { + err := db.Client.Ping(ctx, nil) + if err != nil { + return err + } + return nil +} + +func New(ctx context.Context, client *mongo.Client, cfg *config.DbConfig) (*Database, error) { + return &Database{ + DbName: cfg.DbName, + Client: client, + Cfg: cfg, + }, nil +} diff --git a/internal/shared/db/client/interface.go b/internal/shared/db/client/interface.go new file mode 100644 index 00000000..e1605714 --- /dev/null +++ b/internal/shared/db/client/interface.go @@ -0,0 +1,34 @@ +package dbclient + +import ( + "context" + + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" +) + +type DBClient interface { + Ping(ctx context.Context) error + // InsertPkAddressMappings inserts the btc public key and + // its corresponding btc addresses into the database. + InsertPkAddressMappings( + ctx context.Context, stakerPkHex, taproot, nativeSigwitOdd, nativeSigwitEven string, + ) error + // FindPkMappingsByTaprootAddress finds the PK address mappings by taproot address. + // The returned slice addressMapping will only contain documents for addresses + // that were found in the database. If some addresses do not have a matching + // document, those addresses will simply be absent from the result. + FindPkMappingsByTaprootAddress( + ctx context.Context, taprootAddresses []string, + ) ([]*dbmodel.PkAddressMapping, error) + // FindPkMappingsByNativeSegwitAddress finds the PK address mappings by native + // segwit address. The returned slice addressMapping will only contain + // documents for addresses that were found in the database. + // If some addresses do not have a matching document, those addresses will + // simply be absent from the result. + FindPkMappingsByNativeSegwitAddress( + ctx context.Context, nativeSegwitAddresses []string, + ) ([]*dbmodel.PkAddressMapping, error) + SaveUnprocessableMessage(ctx context.Context, messageBody, receipt string) error + FindUnprocessableMessages(ctx context.Context) ([]dbmodel.UnprocessableMessageDocument, error) + DeleteUnprocessableMessage(ctx context.Context, Receipt interface{}) error +} diff --git a/internal/db/pk_address_mapping.go b/internal/shared/db/client/pk_address_mapping.go similarity index 72% rename from internal/db/pk_address_mapping.go rename to internal/shared/db/client/pk_address_mapping.go index 3de990ee..bebc83d0 100644 --- a/internal/db/pk_address_mapping.go +++ b/internal/shared/db/client/pk_address_mapping.go @@ -1,9 +1,9 @@ -package db +package dbclient import ( "context" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) @@ -11,8 +11,8 @@ import ( func (db *Database) InsertPkAddressMappings( ctx context.Context, pkHex, taproot, nativeSigwitOdd, nativeSigwitEven string, ) error { - client := db.Client.Database(db.DbName).Collection(model.PkAddressMappingsCollection) - addressMapping := &model.PkAddressMapping{ + client := db.Client.Database(db.DbName).Collection(dbmodel.V1PkAddressMappingsCollection) + addressMapping := &dbmodel.PkAddressMapping{ PkHex: pkHex, Taproot: taproot, NativeSegwitOdd: nativeSigwitOdd, @@ -27,11 +27,11 @@ func (db *Database) InsertPkAddressMappings( func (db *Database) FindPkMappingsByTaprootAddress( ctx context.Context, taprootAddresses []string, -) ([]*model.PkAddressMapping, error) { - client := db.Client.Database(db.DbName).Collection(model.PkAddressMappingsCollection) +) ([]*dbmodel.PkAddressMapping, error) { + client := db.Client.Database(db.DbName).Collection(dbmodel.V1PkAddressMappingsCollection) filter := bson.M{"taproot": bson.M{"$in": taprootAddresses}} - addressMapping := []*model.PkAddressMapping{} + addressMapping := []*dbmodel.PkAddressMapping{} cursor, err := client.Find(ctx, filter) if err != nil { return nil, err @@ -45,9 +45,9 @@ func (db *Database) FindPkMappingsByTaprootAddress( func (db *Database) FindPkMappingsByNativeSegwitAddress( ctx context.Context, nativeSegwitAddresses []string, -) ([]*model.PkAddressMapping, error) { +) ([]*dbmodel.PkAddressMapping, error) { client := db.Client.Database(db.DbName).Collection( - model.PkAddressMappingsCollection, + dbmodel.V1PkAddressMappingsCollection, ) filter := bson.M{ "$or": []bson.M{ @@ -56,7 +56,7 @@ func (db *Database) FindPkMappingsByNativeSegwitAddress( }, } - addressMapping := []*model.PkAddressMapping{} + addressMapping := []*dbmodel.PkAddressMapping{} cursor, err := client.Find(ctx, filter) if err != nil { return nil, err diff --git a/internal/db/unprocessable_message.go b/internal/shared/db/client/unprocessable_message.go similarity index 65% rename from internal/db/unprocessable_message.go rename to internal/shared/db/client/unprocessable_message.go index 13787f51..57d5bde9 100644 --- a/internal/db/unprocessable_message.go +++ b/internal/shared/db/client/unprocessable_message.go @@ -1,17 +1,17 @@ -package db +package dbclient import ( "context" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) func (db *Database) SaveUnprocessableMessage(ctx context.Context, messageBody, receipt string) error { - unprocessableMsgClient := db.Client.Database(db.DbName).Collection(model.UnprocessableMsgCollection) + unprocessableMsgClient := db.Client.Database(db.DbName).Collection(dbmodel.V1UnprocessableMsgCollection) - _, err := unprocessableMsgClient.InsertOne(ctx, model.NewUnprocessableMessageDocument(messageBody, receipt)) + _, err := unprocessableMsgClient.InsertOne(ctx, dbmodel.NewUnprocessableMessageDocument(messageBody, receipt)) if err != nil { return err } @@ -19,8 +19,8 @@ func (db *Database) SaveUnprocessableMessage(ctx context.Context, messageBody, r return nil } -func (db *Database) FindUnprocessableMessages(ctx context.Context) ([]model.UnprocessableMessageDocument, error) { - client := db.Client.Database(db.DbName).Collection(model.UnprocessableMsgCollection) +func (db *Database) FindUnprocessableMessages(ctx context.Context) ([]dbmodel.UnprocessableMessageDocument, error) { + client := db.Client.Database(db.DbName).Collection(dbmodel.V1UnprocessableMsgCollection) filter := bson.M{} options := options.FindOptions{} @@ -30,7 +30,7 @@ func (db *Database) FindUnprocessableMessages(ctx context.Context) ([]model.Unpr } defer cursor.Close(ctx) - var unprocessableMessages []model.UnprocessableMessageDocument + var unprocessableMessages []dbmodel.UnprocessableMessageDocument if err = cursor.All(ctx, &unprocessableMessages); err != nil { return nil, err } @@ -39,7 +39,7 @@ func (db *Database) FindUnprocessableMessages(ctx context.Context) ([]model.Unpr } func (db *Database) DeleteUnprocessableMessage(ctx context.Context, Receipt interface{}) error { - unprocessableMsgClient := db.Client.Database(db.DbName).Collection(model.UnprocessableMsgCollection) + unprocessableMsgClient := db.Client.Database(db.DbName).Collection(dbmodel.V1UnprocessableMsgCollection) filter := bson.M{"receipt": Receipt} _, err := unprocessableMsgClient.DeleteOne(ctx, filter) return err diff --git a/internal/shared/db/clients/db_clients.go b/internal/shared/db/clients/db_clients.go new file mode 100644 index 00000000..bde64af5 --- /dev/null +++ b/internal/shared/db/clients/db_clients.go @@ -0,0 +1,51 @@ +package dbclients + +import ( + "context" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + dbclient "github.com/babylonlabs-io/staking-api-service/internal/shared/db/client" + v1dbclient "github.com/babylonlabs-io/staking-api-service/internal/v1/db/client" + v2dbclient "github.com/babylonlabs-io/staking-api-service/internal/v2/db/client" + "github.com/rs/zerolog/log" + "go.mongodb.org/mongo-driver/mongo" +) + +type DbClients struct { + MongoClient *mongo.Client + SharedDBClient dbclient.DBClient + V1DBClient v1dbclient.V1DBClient + V2DBClient v2dbclient.V2DBClient +} + +func New(ctx context.Context, cfg *config.Config) (*DbClients, error) { + mongoClient, err := dbclient.NewMongoClient(ctx, cfg.Db) + if err != nil { + return nil, err + } + + dbClient, err := dbclient.New(ctx, mongoClient, cfg.Db) + if err != nil { + return nil, err + } + + v1dbClient, err := v1dbclient.New(ctx, mongoClient, cfg.Db) + if err != nil { + log.Ctx(ctx).Fatal().Err(err).Msg("error while creating v1 db client") + return nil, err + } + v2dbClient, err := v2dbclient.New(ctx, mongoClient, cfg.Db) + if err != nil { + log.Ctx(ctx).Fatal().Err(err).Msg("error while creating v2 db client") + return nil, err + } + + dbClients := DbClients{ + MongoClient: mongoClient, + SharedDBClient: dbClient, + V1DBClient: v1dbClient, + V2DBClient: v2dbClient, + } + + return &dbClients, nil +} diff --git a/internal/db/error.go b/internal/shared/db/error.go similarity index 100% rename from internal/db/error.go rename to internal/shared/db/error.go diff --git a/internal/db/model/pagination.go b/internal/shared/db/model/pagination.go similarity index 97% rename from internal/db/model/pagination.go rename to internal/shared/db/model/pagination.go index 2f93f651..3867d99e 100644 --- a/internal/db/model/pagination.go +++ b/internal/shared/db/model/pagination.go @@ -1,4 +1,4 @@ -package model +package dbmodel import ( "encoding/base64" diff --git a/internal/db/model/pk_address_mapping.go b/internal/shared/db/model/pk_address_mapping.go similarity index 93% rename from internal/db/model/pk_address_mapping.go rename to internal/shared/db/model/pk_address_mapping.go index 55b60bee..be6ee9ce 100644 --- a/internal/db/model/pk_address_mapping.go +++ b/internal/shared/db/model/pk_address_mapping.go @@ -1,4 +1,4 @@ -package model +package dbmodel type PkAddressMapping struct { PkHex string `bson:"_id"` diff --git a/internal/db/model/setup.go b/internal/shared/db/model/setup.go similarity index 62% rename from internal/db/model/setup.go rename to internal/shared/db/model/setup.go index f5ea00d6..2a00d014 100644 --- a/internal/db/model/setup.go +++ b/internal/shared/db/model/setup.go @@ -1,11 +1,11 @@ -package model +package dbmodel import ( "context" "fmt" "time" - "github.com/babylonlabs-io/staking-api-service/internal/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" "github.com/rs/zerolog/log" "go.mongodb.org/mongo-driver/bson" @@ -14,16 +14,20 @@ import ( ) const ( - StatsLockCollection = "stats_lock" - OverallStatsCollection = "overall_stats" - FinalityProviderStatsCollection = "finality_providers_stats" - StakerStatsCollection = "staker_stats" - DelegationCollection = "delegations" - TimeLockCollection = "timelock_queue" - UnbondingCollection = "unbonding_queue" - BtcInfoCollection = "btc_info" - UnprocessableMsgCollection = "unprocessable_messages" - PkAddressMappingsCollection = "pk_address_mappings" + // V1 + V1StatsLockCollection = "stats_lock" + V1OverallStatsCollection = "overall_stats" + V1FinalityProviderStatsCollection = "finality_providers_stats" + V1StakerStatsCollection = "staker_stats" + V1DelegationCollection = "delegations" + V1TimeLockCollection = "timelock_queue" + V1UnbondingCollection = "unbonding_queue" + V1BtcInfoCollection = "btc_info" + V1UnprocessableMsgCollection = "unprocessable_messages" + V1PkAddressMappingsCollection = "pk_address_mappings" + // V2 + V2StakerStatsCollection = "v2_staker_stats" + V2FinalityProviderStatsCollection = "v2_finality_providers_stats" ) type index struct { @@ -32,22 +36,24 @@ type index struct { } var collections = map[string][]index{ - StatsLockCollection: {{Indexes: map[string]int{}}}, - OverallStatsCollection: {{Indexes: map[string]int{}}}, - FinalityProviderStatsCollection: {{Indexes: map[string]int{"active_tvl": -1}, Unique: false}}, - StakerStatsCollection: {{Indexes: map[string]int{"active_tvl": -1}, Unique: false}}, - DelegationCollection: { + V1StatsLockCollection: {{Indexes: map[string]int{}}}, + V1OverallStatsCollection: {{Indexes: map[string]int{}}}, + V1FinalityProviderStatsCollection: {{Indexes: map[string]int{"active_tvl": -1}, Unique: false}}, + V1StakerStatsCollection: {{Indexes: map[string]int{"active_tvl": -1}, Unique: false}}, + V1DelegationCollection: { {Indexes: map[string]int{"staker_pk_hex": 1, "staking_tx.start_height": -1, "_id": 1}, Unique: false}, }, - TimeLockCollection: {{Indexes: map[string]int{"expire_height": 1}, Unique: false}}, - UnbondingCollection: {{Indexes: map[string]int{"unbonding_tx_hash_hex": 1}, Unique: true}}, - UnprocessableMsgCollection: {{Indexes: map[string]int{}}}, - BtcInfoCollection: {{Indexes: map[string]int{}}}, - PkAddressMappingsCollection: { + V1TimeLockCollection: {{Indexes: map[string]int{"expire_height": 1}, Unique: false}}, + V1UnbondingCollection: {{Indexes: map[string]int{"unbonding_tx_hash_hex": 1}, Unique: true}}, + V1UnprocessableMsgCollection: {{Indexes: map[string]int{}}}, + V1BtcInfoCollection: {{Indexes: map[string]int{}}}, + V1PkAddressMappingsCollection: { {Indexes: map[string]int{"taproot": 1}, Unique: true}, {Indexes: map[string]int{"native_segwit_odd": 1}, Unique: true}, {Indexes: map[string]int{"native_segwit_even": 1}, Unique: true}, }, + V2StakerStatsCollection: {{Indexes: map[string]int{}}}, + V2FinalityProviderStatsCollection: {{Indexes: map[string]int{"active_tvl": -1}, Unique: false}}, } func Setup(ctx context.Context, cfg *config.Config) error { diff --git a/internal/db/model/unprocessable_message.go b/internal/shared/db/model/unprocessable_message.go similarity index 95% rename from internal/db/model/unprocessable_message.go rename to internal/shared/db/model/unprocessable_message.go index a25b1de9..a1f64710 100644 --- a/internal/db/model/unprocessable_message.go +++ b/internal/shared/db/model/unprocessable_message.go @@ -1,4 +1,4 @@ -package model +package dbmodel type UnprocessableMessageDocument struct { MessageBody string `bson:"message_body"` diff --git a/internal/db/dbclient.go b/internal/shared/db/pagination.go similarity index 72% rename from internal/db/dbclient.go rename to internal/shared/db/pagination.go index 6fa25030..ad4d14cc 100644 --- a/internal/db/dbclient.go +++ b/internal/shared/db/pagination.go @@ -3,49 +3,16 @@ package db import ( "context" - "github.com/babylonlabs-io/staking-api-service/internal/config" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) -type Database struct { - DbName string - Client *mongo.Client - cfg *config.DbConfig -} - type DbResultMap[T any] struct { Data []T `json:"data"` PaginationToken string `json:"paginationToken"` } -func New(ctx context.Context, cfg *config.DbConfig) (*Database, error) { - credential := options.Credential{ - Username: cfg.Username, - Password: cfg.Password, - } - clientOps := options.Client().ApplyURI(cfg.Address).SetAuth(credential) - client, err := mongo.Connect(ctx, clientOps) - if err != nil { - return nil, err - } - - return &Database{ - DbName: cfg.DbName, - Client: client, - cfg: cfg, - }, nil -} - -func (db *Database) Ping(ctx context.Context) error { - err := db.Client.Ping(ctx, nil) - if err != nil { - return err - } - return nil -} - /* Builds the result map with a pagination token. If the result length exceeds the maximum limit, it returns the map with a token. @@ -74,7 +41,7 @@ func toResultMapWithPaginationToken[T any](paginationLimit int64, result []T, pa } // Finds documents in the collection with pagination in returned results. -func findWithPagination[T any]( +func FindWithPagination[T any]( ctx context.Context, client *mongo.Collection, filter bson.M, options *options.FindOptions, limit int64, paginationKeyBuilder func(T) (string, error), diff --git a/internal/clients/base/base_client.go b/internal/shared/http/client/http_client.go similarity index 87% rename from internal/clients/base/base_client.go rename to internal/shared/http/client/http_client.go index 2852317b..d74444c2 100644 --- a/internal/clients/base/base_client.go +++ b/internal/shared/http/client/http_client.go @@ -1,4 +1,4 @@ -package baseclient +package client import ( "bytes" @@ -8,20 +8,14 @@ import ( "net/http" "time" - "github.com/babylonlabs-io/staking-api-service/internal/observability/metrics" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/metrics" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "github.com/rs/zerolog/log" ) var ALLOWED_METHODS = []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"} -type BaseClient interface { - GetBaseURL() string - GetDefaultRequestTimeout() int - GetHttpClient() *http.Client -} - -type BaseClientOptions struct { +type HttpClientOptions struct { Timeout int Path string TemplatePath string // Metrics purpose @@ -38,7 +32,7 @@ func isAllowedMethod(method string) bool { } func sendRequest[I any, R any]( - ctx context.Context, client BaseClient, method string, opts *BaseClientOptions, input *I, + ctx context.Context, client HttpClient, method string, opts *HttpClientOptions, input *I, ) (*R, *types.Error) { if !isAllowedMethod(method) { return nil, types.NewInternalServiceError(fmt.Errorf("method %s is not allowed", method)) @@ -122,7 +116,7 @@ func sendRequest[I any, R any]( } func SendRequest[I any, R any]( - ctx context.Context, client BaseClient, method string, opts *BaseClientOptions, input *I, + ctx context.Context, client HttpClient, method string, opts *HttpClientOptions, input *I, ) (*R, *types.Error) { timer := metrics.StartClientRequestDurationTimer( client.GetBaseURL(), method, opts.TemplatePath, diff --git a/internal/shared/http/client/interface.go b/internal/shared/http/client/interface.go new file mode 100644 index 00000000..0d91691b --- /dev/null +++ b/internal/shared/http/client/interface.go @@ -0,0 +1,9 @@ +package client + +import "net/http" + +type HttpClient interface { + GetBaseURL() string + GetDefaultRequestTimeout() int + GetHttpClient() *http.Client +} diff --git a/internal/shared/http/clients/http_clients.go b/internal/shared/http/clients/http_clients.go new file mode 100644 index 00000000..d7f1f0a5 --- /dev/null +++ b/internal/shared/http/clients/http_clients.go @@ -0,0 +1,22 @@ +package clients + +import ( + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/http/clients/ordinals" +) + +type Clients struct { + Ordinals ordinals.OrdinalsClient +} + +func New(cfg *config.Config) *Clients { + var ordinalsClient ordinals.OrdinalsClient + // If the assets config is set, create the ordinal related clients + if cfg.Assets != nil { + ordinalsClient = ordinals.New(cfg.Assets.Ordinals) + } + + return &Clients{ + Ordinals: ordinalsClient, + } +} diff --git a/internal/clients/ordinals/interface.go b/internal/shared/http/clients/ordinals/interface.go similarity index 80% rename from internal/clients/ordinals/interface.go rename to internal/shared/http/clients/ordinals/interface.go index 322cfcdb..47c48820 100644 --- a/internal/clients/ordinals/interface.go +++ b/internal/shared/http/clients/ordinals/interface.go @@ -4,10 +4,10 @@ import ( "context" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" ) -type OrdinalsClientInterface interface { +type OrdinalsClient interface { GetBaseURL() string GetDefaultRequestTimeout() int GetHttpClient() *http.Client diff --git a/internal/clients/ordinals/ordinals.go b/internal/shared/http/clients/ordinals/ordinals.go similarity index 71% rename from internal/clients/ordinals/ordinals.go rename to internal/shared/http/clients/ordinals/ordinals.go index fafac289..e7ba3aea 100644 --- a/internal/clients/ordinals/ordinals.go +++ b/internal/shared/http/clients/ordinals/ordinals.go @@ -6,9 +6,9 @@ import ( "fmt" "net/http" - baseclient "github.com/babylonlabs-io/staking-api-service/internal/clients/base" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/http/client" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" ) type OrdinalsOutputResponse struct { @@ -17,13 +17,13 @@ type OrdinalsOutputResponse struct { Runes json.RawMessage `json:"runes"` } -type OrdinalsClient struct { +type Ordinals struct { config *config.OrdinalsConfig defaultHeaders map[string]string httpClient *http.Client } -func NewOrdinalsClient(config *config.OrdinalsConfig) *OrdinalsClient { +func New(config *config.OrdinalsConfig) *Ordinals { // Client is disabled if config is nil if config == nil { return nil @@ -33,7 +33,7 @@ func NewOrdinalsClient(config *config.OrdinalsConfig) *OrdinalsClient { "Content-Type": "application/json", "Accept": "application/json", } - return &OrdinalsClient{ + return &Ordinals{ config, headers, httpClient, @@ -41,23 +41,23 @@ func NewOrdinalsClient(config *config.OrdinalsConfig) *OrdinalsClient { } // Necessary for the BaseClient interface -func (c *OrdinalsClient) GetBaseURL() string { +func (c *Ordinals) GetBaseURL() string { return fmt.Sprintf("%s:%s", c.config.Host, c.config.Port) } -func (c *OrdinalsClient) GetDefaultRequestTimeout() int { +func (c *Ordinals) GetDefaultRequestTimeout() int { return c.config.Timeout } -func (c *OrdinalsClient) GetHttpClient() *http.Client { +func (c *Ordinals) GetHttpClient() *http.Client { return c.httpClient } -func (c *OrdinalsClient) FetchUTXOInfos( +func (c *Ordinals) FetchUTXOInfos( ctx context.Context, utxos []types.UTXOIdentifier, ) ([]OrdinalsOutputResponse, *types.Error) { path := "/outputs" - opts := &baseclient.BaseClientOptions{ + opts := &client.HttpClientOptions{ Path: path, TemplatePath: path, Headers: c.defaultHeaders, @@ -68,7 +68,7 @@ func (c *OrdinalsClient) FetchUTXOInfos( txHashVouts = append(txHashVouts, fmt.Sprintf("%s:%d", utxo.Txid, utxo.Vout)) } - outputsResponse, err := baseclient.SendRequest[[]string, []OrdinalsOutputResponse]( + outputsResponse, err := client.SendRequest[[]string, []OrdinalsOutputResponse]( ctx, c, http.MethodPost, opts, &txHashVouts, ) if err != nil { diff --git a/internal/observability/healthcheck/healthcheck.go b/internal/shared/observability/healthcheck/healthcheck.go similarity index 62% rename from internal/observability/healthcheck/healthcheck.go rename to internal/shared/observability/healthcheck/healthcheck.go index f41eda2c..903e9ef5 100644 --- a/internal/observability/healthcheck/healthcheck.go +++ b/internal/shared/observability/healthcheck/healthcheck.go @@ -4,8 +4,9 @@ import ( "context" "fmt" - "github.com/babylonlabs-io/staking-api-service/internal/observability/metrics" - "github.com/babylonlabs-io/staking-api-service/internal/queue" + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/metrics" + queueclient "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/client" + queueclients "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/clients" "github.com/robfig/cron/v3" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -17,7 +18,7 @@ func SetLogger(customLogger zerolog.Logger) { logger = customLogger } -func StartHealthCheckCron(ctx context.Context, queues *queue.Queues, cronTime int) error { +func StartHealthCheckCron(ctx context.Context, queueClients *queueclients.QueueClients, cronTime int) error { c := cron.New() logger.Info().Msg("Initiated Health Check Cron") @@ -28,7 +29,7 @@ func StartHealthCheckCron(ctx context.Context, queues *queue.Queues, cronTime in cronSpec := fmt.Sprintf("@every %ds", cronTime) _, err := c.AddFunc(cronSpec, func() { - queueHealthCheck(queues) + queueHealthCheck(queueClients.V1QueueClient) }) if err != nil { @@ -46,8 +47,8 @@ func StartHealthCheckCron(ctx context.Context, queues *queue.Queues, cronTime in return nil } -func queueHealthCheck(queues *queue.Queues) { - if err := queues.IsConnectionHealthy(); err != nil { +func queueHealthCheck(queueClient queueclient.QueueClient) { + if err := queueClient.IsConnectionHealthy(); err != nil { logger.Error().Err(err).Msg("One or more queue connections are not healthy.") // Record service unavailable in metrics metrics.RecordServiceCrash("queue") diff --git a/internal/observability/metrics/metrics.go b/internal/shared/observability/metrics/metrics.go similarity index 100% rename from internal/observability/metrics/metrics.go rename to internal/shared/observability/metrics/metrics.go diff --git a/internal/observability/tracing/tracing.go b/internal/shared/observability/tracing/tracing.go similarity index 94% rename from internal/observability/tracing/tracing.go rename to internal/shared/observability/tracing/tracing.go index be557646..cc8b11a3 100644 --- a/internal/observability/tracing/tracing.go +++ b/internal/shared/observability/tracing/tracing.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "github.com/google/uuid" "github.com/rs/zerolog/log" ) diff --git a/internal/queue/handlers/REAME.md b/internal/shared/queue/REAME.md similarity index 100% rename from internal/queue/handlers/REAME.md rename to internal/shared/queue/REAME.md diff --git a/internal/shared/queue/client/client.go b/internal/shared/queue/client/client.go new file mode 100644 index 00000000..506099d1 --- /dev/null +++ b/internal/shared/queue/client/client.go @@ -0,0 +1,55 @@ +package queueclient + +import ( + "context" + "net/http" + "time" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/tracing" + "github.com/babylonlabs-io/staking-api-service/internal/shared/services" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + + "github.com/babylonlabs-io/staking-queue-client/client" + queueConfig "github.com/babylonlabs-io/staking-queue-client/config" + "github.com/rs/zerolog/log" +) + +type Queue struct { + ProcessingTimeout time.Duration + MaxRetryAttempts int32 + StatsQueueClient client.QueueClient +} + +func New(ctx context.Context, cfg *queueConfig.QueueConfig, service *services.Services) *Queue { + statsQueueClient, err := client.NewQueueClient( + cfg, client.StakingStatsQueueName, + ) + if err != nil { + log.Fatal().Err(err).Msg("error while creating StatsQueueClient") + } + + return &Queue{ + ProcessingTimeout: time.Duration(cfg.QueueProcessingTimeout) * time.Second, + MaxRetryAttempts: cfg.MsgMaxRetryAttempts, + StatsQueueClient: statsQueueClient, + } +} + +func attachLoggerContext(ctx context.Context, message client.QueueMessage, queueClient client.QueueClient) context.Context { + ctx = tracing.AttachTracingIntoContext(ctx) + + traceId := ctx.Value(tracing.TraceIdKey) + return log.With(). + Str("receipt", message.Receipt). + Str("queueName", queueClient.GetQueueName()). + Interface("traceId", traceId). + Logger().WithContext(ctx) +} + +func recordErrorLog(err *types.Error) { + if err.StatusCode >= http.StatusInternalServerError { + log.Error().Err(err).Msg("event processing failed with 5xx error") + } else { + log.Warn().Err(err).Msg("event processing failed with 4xx error") + } +} diff --git a/internal/shared/queue/client/interface.go b/internal/shared/queue/client/interface.go new file mode 100644 index 00000000..d6a1efb5 --- /dev/null +++ b/internal/shared/queue/client/interface.go @@ -0,0 +1,7 @@ +package queueclient + +type QueueClient interface { + StartReceivingMessages() + StopReceivingMessages() + IsConnectionHealthy() error +} diff --git a/internal/shared/queue/client/message.go b/internal/shared/queue/client/message.go new file mode 100644 index 00000000..9c878790 --- /dev/null +++ b/internal/shared/queue/client/message.go @@ -0,0 +1,92 @@ +package queueclient + +import ( + "context" + "net/http" + "time" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/metrics" + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/tracing" + queuehandler "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-queue-client/client" + "github.com/rs/zerolog/log" +) + +func StartQueueMessageProcessing( + queueClient client.QueueClient, + handler queuehandler.MessageHandler, unprocessableHandler queuehandler.UnprocessableMessageHandler, + maxRetryAttempts int32, processingTimeout time.Duration, +) { + messagesChan, err := queueClient.ReceiveMessages() + log.Info().Str("queueName", queueClient.GetQueueName()).Msg("start receiving messages from queue") + if err != nil { + log.Fatal().Err(err).Str("queueName", queueClient.GetQueueName()).Msg("error setting up message channel from queue") + } + + go func() { + for message := range messagesChan { + attempts := message.GetRetryAttempts() + // For each message, create a new context with a deadline or timeout + ctx, cancel := context.WithTimeout(context.Background(), processingTimeout) + ctx = attachLoggerContext(ctx, message, queueClient) + // Attach the tracingInfo for the message processing + _, err := tracing.WrapWithSpan[any](ctx, "message_processing", func() (any, *types.Error) { + timer := metrics.StartEventProcessingDurationTimer(queueClient.GetQueueName(), attempts) + // Process the message + err := handler(ctx, message.Body) + if err != nil { + timer(err.StatusCode) + } else { + timer(http.StatusOK) + } + return nil, err + }) + if err != nil { + recordErrorLog(err) + // We will retry the message if it has not exceeded the max retry attempts + // otherwise, we will dump the message into db for manual inspection and remove from the queue + if attempts > maxRetryAttempts { + log.Ctx(ctx).Error().Err(err). + Msg("exceeded retry attempts, message will be dumped into db for manual inspection") + metrics.RecordUnprocessableEntity(queueClient.GetQueueName()) + saveUnprocessableMsgErr := unprocessableHandler(ctx, message.Body, message.Receipt) + if saveUnprocessableMsgErr != nil { + log.Ctx(ctx).Error().Err(saveUnprocessableMsgErr). + Msg("error while saving unprocessable message") + metrics.RecordQueueOperationFailure("unprocessableHandler", queueClient.GetQueueName()) + cancel() + continue + } + } else { + log.Ctx(ctx).Error().Err(err). + Msg("error while processing message from queue, will be requeued") + reQueueErr := queueClient.ReQueueMessage(ctx, message) + if reQueueErr != nil { + log.Ctx(ctx).Error().Err(reQueueErr). + Msg("error while requeuing message") + metrics.RecordQueueOperationFailure("reQueueMessage", queueClient.GetQueueName()) + } + cancel() + continue + } + } + + delErr := queueClient.DeleteMessage(message.Receipt) + if delErr != nil { + log.Ctx(ctx).Error().Err(delErr). + Msg("error while deleting message from queue") + metrics.RecordQueueOperationFailure("deleteMessage", queueClient.GetQueueName()) + } + + tracingInfo := ctx.Value(tracing.TracingInfoKey) + logEvent := log.Ctx(ctx).Debug() + if tracingInfo != nil { + logEvent = logEvent.Interface("tracingInfo", tracingInfo) + } + logEvent.Msg("message processed successfully") + cancel() + } + log.Info().Str("queueName", queueClient.GetQueueName()).Msg("stopped receiving messages from queue") + }() +} diff --git a/internal/shared/queue/clients/clients.go b/internal/shared/queue/clients/clients.go new file mode 100644 index 00000000..f5d2fa47 --- /dev/null +++ b/internal/shared/queue/clients/clients.go @@ -0,0 +1,38 @@ +package queueclients + +import ( + "context" + + queueclient "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/client" + queuehandler "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/handler" + queuehandlers "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/handlers" + "github.com/babylonlabs-io/staking-api-service/internal/shared/services" + v1queueclient "github.com/babylonlabs-io/staking-api-service/internal/v1/queue/client" + v2queueclient "github.com/babylonlabs-io/staking-api-service/internal/v2/queue/client" + queueConfig "github.com/babylonlabs-io/staking-queue-client/config" + "github.com/rs/zerolog/log" +) + +type QueueClients struct { + V1QueueClient *v1queueclient.V1QueueClient + V2QueueClient *v2queueclient.V2QueueClient +} + +func New(ctx context.Context, cfg *queueConfig.QueueConfig, services *services.Services) *QueueClients { + queueClient := queueclient.New(ctx, cfg, services) + queueHandler := queuehandler.New(queueClient.StatsQueueClient.SendMessage) + queueHandlers, err := queuehandlers.New(services, queueHandler) + if err != nil { + log.Fatal().Err(err).Msg("error while setting up queue handlers") + } + v1QueueClient := v1queueclient.New(cfg, queueHandlers.V1QueueHandler, queueClient) + return &QueueClients{ + V1QueueClient: v1QueueClient, + } +} + +func (q *QueueClients) StartReceivingMessages() { + log.Printf("Starting to receive messages from queue clients") + q.V1QueueClient.StartReceivingMessages() + q.V2QueueClient.StartReceivingMessages() +} diff --git a/internal/queue/handlers/handler.go b/internal/shared/queue/handler/handler.go similarity index 58% rename from internal/queue/handlers/handler.go rename to internal/shared/queue/handler/handler.go index f1002efb..1f92b055 100644 --- a/internal/queue/handlers/handler.go +++ b/internal/shared/queue/handler/handler.go @@ -1,45 +1,39 @@ -package handlers +package queuehandler import ( "context" "encoding/json" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-queue-client/client" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + queueclient "github.com/babylonlabs-io/staking-queue-client/client" "github.com/rs/zerolog/log" ) type QueueHandler struct { - Services *services.Services emitStatsEvent func(ctx context.Context, messageBody string) error } type MessageHandler func(ctx context.Context, messageBody string) *types.Error type UnprocessableMessageHandler func(ctx context.Context, messageBody, receipt string) *types.Error -func NewQueueHandler( - services *services.Services, - emitStats func(ctx context.Context, messageBody string) error, +func New( + emitStatsEvent func(ctx context.Context, messageBody string) error, ) *QueueHandler { return &QueueHandler{ - Services: services, - emitStatsEvent: emitStats, + emitStatsEvent: emitStatsEvent, } } -func (qh *QueueHandler) HandleUnprocessedMessage(ctx context.Context, messageBody, receipt string) *types.Error { - return qh.Services.SaveUnprocessableMessages(ctx, messageBody, receipt) -} - -func (qh *QueueHandler) EmitStatsEvent(ctx context.Context, statsEvent client.StatsEvent) *types.Error { +func (qh *QueueHandler) EmitStatsEvent(ctx context.Context, statsEvent queueclient.StatsEvent) *types.Error { jsonData, err := json.Marshal(statsEvent) if err != nil { log.Ctx(ctx).Err(err).Msg("Failed to marshal the stats event") return types.NewError(http.StatusBadRequest, types.BadRequest, err) } + err = qh.emitStatsEvent(ctx, string(jsonData)) + if err != nil { log.Ctx(ctx).Err(err).Msg("Failed to emit the stats event") return types.NewError(http.StatusInternalServerError, types.InternalServiceError, err) diff --git a/internal/shared/queue/handlers/handlers.go b/internal/shared/queue/handlers/handlers.go new file mode 100644 index 00000000..f79931aa --- /dev/null +++ b/internal/shared/queue/handlers/handlers.go @@ -0,0 +1,19 @@ +package queuehandlers + +import ( + queuehandler "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/services" + v1queuehandler "github.com/babylonlabs-io/staking-api-service/internal/v1/queue/handler" + v2queuehandler "github.com/babylonlabs-io/staking-api-service/internal/v2/queue/handler" +) + +type QueueHandlers struct { + V1QueueHandler *v1queuehandler.V1QueueHandler + V2QueueHandler *v2queuehandler.V2QueueHandler +} + +func New(services *services.Services, queueHandler *queuehandler.QueueHandler) (*QueueHandlers, error) { + return &QueueHandlers{ + V1QueueHandler: v1queuehandler.New(queueHandler, services.V1Service), + }, nil +} diff --git a/internal/shared/services/service/interface.go b/internal/shared/services/service/interface.go new file mode 100644 index 00000000..92ef661a --- /dev/null +++ b/internal/shared/services/service/interface.go @@ -0,0 +1,13 @@ +package service + +import ( + "context" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" +) + +type SharedServiceProvider interface { + DoHealthCheck(ctx context.Context) error + VerifyUTXOs(ctx context.Context, utxos []types.UTXOIdentifier, address string) ([]*SafeUTXOPublic, *types.Error) + SaveUnprocessableMessages(ctx context.Context, messages string, receipt string) *types.Error +} diff --git a/internal/services/ordinals.go b/internal/shared/services/service/ordinals.go similarity index 90% rename from internal/services/ordinals.go rename to internal/shared/services/service/ordinals.go index 7f1557c7..42127918 100644 --- a/internal/services/ordinals.go +++ b/internal/shared/services/service/ordinals.go @@ -1,10 +1,10 @@ -package services +package service import ( "context" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "github.com/rs/zerolog/log" ) @@ -14,7 +14,7 @@ type SafeUTXOPublic struct { Inscription bool `json:"inscription"` } -func (s *Services) VerifyUTXOs( +func (s *Service) VerifyUTXOs( ctx context.Context, utxos []types.UTXOIdentifier, address string, ) ([]*SafeUTXOPublic, *types.Error) { result, err := s.verifyViaOrdinalService(ctx, utxos) @@ -27,7 +27,7 @@ func (s *Services) VerifyUTXOs( return result, nil } -func (s *Services) verifyViaOrdinalService( +func (s *Service) verifyViaOrdinalService( ctx context.Context, utxos []types.UTXOIdentifier, ) ([]*SafeUTXOPublic, *types.Error) { var results []*SafeUTXOPublic diff --git a/internal/shared/services/service/service.go b/internal/shared/services/service/service.go new file mode 100644 index 00000000..30168751 --- /dev/null +++ b/internal/shared/services/service/service.go @@ -0,0 +1,56 @@ +package service + +import ( + "context" + "net/http" + + "github.com/rs/zerolog/log" + + "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" +) + +// Services layer contains the business logic and is used to interact with +// the database and other external clients (if any). +type Service struct { + DbClients *dbclients.DbClients + Clients *clients.Clients + Cfg *config.Config + Params *types.GlobalParams + FinalityProviders []types.FinalityProviderDetails +} + +func New( + ctx context.Context, + cfg *config.Config, + globalParams *types.GlobalParams, + finalityProviders []types.FinalityProviderDetails, + clients *clients.Clients, + dbClients *dbclients.DbClients, +) (*Service, error) { + return &Service{ + DbClients: dbClients, + Clients: clients, + Cfg: cfg, + Params: globalParams, + FinalityProviders: finalityProviders, + }, nil +} + +// DoHealthCheck checks the health of the services by ping the database. +func (s *Service) DoHealthCheck(ctx context.Context) error { + return s.DbClients.V1DBClient.Ping(ctx) + + // TODO: extend on this to ping indexer db +} + +func (s *Service) SaveUnprocessableMessages(ctx context.Context, messageBody, receipt string) *types.Error { + err := s.DbClients.V1DBClient.SaveUnprocessableMessage(ctx, messageBody, receipt) + if err != nil { + log.Ctx(ctx).Error().Err(err).Msg("error while saving unprocessable message") + return types.NewErrorWithMsg(http.StatusInternalServerError, types.InternalServiceError, "error while saving unprocessable message") + } + return nil +} diff --git a/internal/shared/services/services.go b/internal/shared/services/services.go new file mode 100644 index 00000000..bfc59211 --- /dev/null +++ b/internal/shared/services/services.go @@ -0,0 +1,49 @@ +package services + +import ( + "context" + + "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/services/service" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" + v2service "github.com/babylonlabs-io/staking-api-service/internal/v2/service" +) + +type Services struct { + SharedService service.SharedServiceProvider + V1Service v1service.V1ServiceProvider + V2Service v2service.V2ServiceProvider +} + +func New( + ctx context.Context, + cfg *config.Config, + globalParams *types.GlobalParams, + finalityProviders []types.FinalityProviderDetails, + clients *clients.Clients, + dbClients *dbclients.DbClients, +) (*Services, error) { + service, err := service.New(ctx, cfg, globalParams, finalityProviders, clients, dbClients) + if err != nil { + return nil, err + } + v1Service, err := v1service.New(ctx, cfg, globalParams, finalityProviders, clients, dbClients) + if err != nil { + return nil, err + } + v2Service, err := v2service.New(ctx, cfg, globalParams, finalityProviders, clients, dbClients) + if err != nil { + return nil, err + } + + services := Services{ + SharedService: service, + V1Service: v1Service, + V2Service: v2Service, + } + + return &services, nil +} diff --git a/internal/types/delegation.go b/internal/shared/types/delegation.go similarity index 100% rename from internal/types/delegation.go rename to internal/shared/types/delegation.go diff --git a/internal/types/error.go b/internal/shared/types/error.go similarity index 100% rename from internal/types/error.go rename to internal/shared/types/error.go diff --git a/internal/types/finality_providers.go b/internal/shared/types/finality_providers.go similarity index 90% rename from internal/types/finality_providers.go rename to internal/shared/types/finality_providers.go index 8ee30c9f..deb5bf9a 100644 --- a/internal/types/finality_providers.go +++ b/internal/shared/types/finality_providers.go @@ -6,6 +6,13 @@ import ( "path/filepath" ) +type FinalityProviderState string + +const ( + FinalityProviderStateActive FinalityProviderState = "active" + FinalityProviderStateStandby FinalityProviderState = "standby" +) + type FinalityProviderDescription struct { Moniker string `json:"moniker"` Identity string `json:"identity"` diff --git a/internal/shared/types/global_params.go b/internal/shared/types/global_params.go new file mode 100644 index 00000000..0a241009 --- /dev/null +++ b/internal/shared/types/global_params.go @@ -0,0 +1,74 @@ +package types + +import ( + "encoding/hex" + "encoding/json" + "os" + "path/filepath" + + "github.com/babylonlabs-io/networks/parameters/parser" + "github.com/btcsuite/btcd/btcec/v2" +) + +type VersionedGlobalParams = parser.VersionedGlobalParams + +type GlobalParams = parser.GlobalParams + +type BabylonParams struct { + Version int `json:"version"` + CovenantPKs []string `json:"covenant_pks"` + CovenantQuorum int `json:"covenant_quorum"` + MaxStakingAmount int64 `json:"max_staking_amount"` + MinStakingAmount int64 `json:"min_staking_amount"` + MaxStakingTime int64 `json:"max_staking_time"` + MinStakingTime int64 `json:"min_staking_time"` + SlashingPKScript string `json:"slashing_pk_script"` + MinSlashingTxFee int64 `json:"min_slashing_tx_fee"` + SlashingRate float64 `json:"slashing_rate"` + MinUnbondingTime int64 `json:"min_unbonding_time"` + UnbondingFee int64 `json:"unbonding_fee"` + MinCommissionRate float64 `json:"min_commission_rate"` + MaxActiveFinalityProviders int `json:"max_active_finality_providers"` + DelegationCreationBaseGasFee int64 `json:"delegation_creation_base_gas_fee"` +} + +type BTCParams struct { + Version int `json:"version"` + BTCConfirmationDepth int `json:"btc_confirmation_depth"` +} + +func NewGlobalParams(path string) (*GlobalParams, error) { + data, err := os.ReadFile(filepath.Clean(path)) + if err != nil { + return nil, err + } + + var globalParams GlobalParams + err = json.Unmarshal(data, &globalParams) + if err != nil { + return nil, err + } + + _, err = parser.ParseGlobalParams(&globalParams) + if err != nil { + return nil, err + } + + return &globalParams, nil +} + +// parseCovenantPubKeyFromHex parses public key string to btc public key +// the input should be 33 bytes +func parseCovenantPubKeyFromHex(pkStr string) (*btcec.PublicKey, error) { + pkBytes, err := hex.DecodeString(pkStr) + if err != nil { + return nil, err + } + + pk, err := btcec.ParsePubKey(pkBytes) + if err != nil { + return nil, err + } + + return pk, nil +} diff --git a/internal/shared/types/staker.go b/internal/shared/types/staker.go new file mode 100644 index 00000000..3253acbc --- /dev/null +++ b/internal/shared/types/staker.go @@ -0,0 +1,6 @@ +package types + +type TransactionInfo struct { + TxHex string `json:"tx_hex"` + OutputIndex int `json:"output_index"` +} diff --git a/internal/types/transaction_type.go b/internal/shared/types/transaction_type.go similarity index 100% rename from internal/types/transaction_type.go rename to internal/shared/types/transaction_type.go diff --git a/internal/types/utxo.go b/internal/shared/types/utxo.go similarity index 100% rename from internal/types/utxo.go rename to internal/shared/types/utxo.go diff --git a/internal/utils/btc.go b/internal/shared/utils/btc.go similarity index 99% rename from internal/utils/btc.go rename to internal/shared/utils/btc.go index 9ef6b442..4a3a9fe8 100644 --- a/internal/utils/btc.go +++ b/internal/shared/utils/btc.go @@ -8,14 +8,13 @@ import ( "github.com/babylonlabs-io/babylon/btcstaking" "github.com/babylonlabs-io/babylon/crypto/bip322" bbntypes "github.com/babylonlabs-io/babylon/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - - "github.com/babylonlabs-io/staking-api-service/internal/types" ) const PublickKeyWithNoCoordinatesSize = 32 diff --git a/internal/shared/utils/datagen/datagen.go b/internal/shared/utils/datagen/datagen.go new file mode 100644 index 00000000..5b04488b --- /dev/null +++ b/internal/shared/utils/datagen/datagen.go @@ -0,0 +1,215 @@ +package datagen + +import ( + "bytes" + "encoding/hex" + "fmt" + "log" + "math/rand" + "time" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +func GenRandomByteArray(r *rand.Rand, length uint64) []byte { + newHeaderBytes := make([]byte, length) + r.Read(newHeaderBytes) + return newHeaderBytes +} + +func RandomPk() (string, error) { + fpPirvKey, err := btcec.NewPrivateKey() + if err != nil { + return "", err + } + fpPk := fpPirvKey.PubKey() + return hex.EncodeToString(schnorr.SerializePubKey(fpPk)), nil +} + +func GeneratePks(numOfKeys int) []string { + var pks []string + for i := 0; i < numOfKeys; i++ { + k, err := RandomPk() + if err != nil { + log.Fatalf("Failed to generate random pk: %v", err) + } + pks = append(pks, k) + } + return pks +} + +// RandomPostiveFloat64 generates a random float64 value greater than 0. +func RandomPostiveFloat64(r *rand.Rand) float64 { + for { + f := r.Float64() // Generate a random float64 + if f > 0 { + return f + } + // If f is 0 (extremely rare), regenerate + } +} + +// RandomPositiveInt generates a random positive integer from 1 to max. +func RandomPositiveInt(r *rand.Rand, max int) int { + // Generate a random number from 1 to max (inclusive) + return r.Intn(max) + 1 +} + +// RandomString generates a random alphanumeric string of length n. +func RandomString(r *rand.Rand, n int) string { + result := make([]byte, n) + letterLen := len(letters) + for i := range result { + num := r.Int() % letterLen + result[i] = letters[num] + } + return string(result) +} + +// RandomAmount generates a random BTC amount from 0.1 to 10000 +// the returned value is in satoshis +func RandomAmount(r *rand.Rand) int64 { + // Generate a random value range from 0.1 to 10000 BTC + randomBTC := r.Float64()*(9999.9-0.1) + 0.1 + // convert to satoshi + return int64(randomBTC*1e8) + 1 +} + +// GenerateRandomTx generates a random transaction with random values for each field. +func GenerateRandomTx( + r *rand.Rand, + options *struct{ DisableRbf bool }, +) (*wire.MsgTx, string, error) { + sequence := r.Uint32() + if options != nil && options.DisableRbf { + sequence = wire.MaxTxInSequenceNum + } + tx := &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.HashH(GenRandomByteArray(r, 10)), + Index: r.Uint32(), + }, + SignatureScript: []byte{}, + Sequence: sequence, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: int64(r.Int31()), + PkScript: GenRandomByteArray(r, 80), + }, + }, + LockTime: 0, + } + var buf bytes.Buffer + if err := tx.Serialize(&buf); err != nil { + return nil, "", err + } + txHex := hex.EncodeToString(buf.Bytes()) + + return tx, txHex, nil +} + +// GenerateRandomTxWithOutput generates a random transaction with random values +// for each field. +func RandomBytes(r *rand.Rand, n uint64) ([]byte, string) { + randomBytes := GenRandomByteArray(r, n) + return randomBytes, hex.EncodeToString(randomBytes) +} + +// GenerateRandomTimestamp generates a random timestamp before the specified timestamp. +// If beforeTimestamp is 0, then the current time is used. +func GenerateRandomTimestamp(afterTimestamp, beforeTimestamp int64) int64 { + timeNow := time.Now().Unix() + if beforeTimestamp == 0 && afterTimestamp == 0 { + return timeNow + } + if beforeTimestamp == 0 { + return afterTimestamp + rand.Int63n(timeNow-afterTimestamp) + } else if afterTimestamp == 0 { + // Generate a reasonable timestamp between 1 second to 6 months in the past + sixMonthsInSeconds := int64(6 * 30 * 24 * 60 * 60) + return beforeTimestamp - rand.Int63n(sixMonthsInSeconds) + } + return afterTimestamp + rand.Int63n(beforeTimestamp-afterTimestamp) +} + +// GenerateRandomFinalityProviderDetail generates a random number of finality providers +func GenerateRandomFinalityProviderDetail(r *rand.Rand, numOfFps uint64) []types.FinalityProviderDetails { + var finalityProviders []types.FinalityProviderDetails + + for i := uint64(0); i < numOfFps; i++ { + fpPkInHex, err := RandomPk() + if err != nil { + log.Fatalf("failed to generate random public key: %v", err) + } + + randomStr := RandomString(r, 10) + finalityProviders = append(finalityProviders, types.FinalityProviderDetails{ + Description: types.FinalityProviderDescription{ + Moniker: "Moniker" + randomStr, + Identity: "Identity" + randomStr, + Website: "Website" + randomStr, + SecurityContact: "SecurityContact" + randomStr, + Details: "Details" + randomStr, + }, + Commission: fmt.Sprintf("%f", RandomPostiveFloat64(r)), + BtcPk: fpPkInHex, + }) + } + return finalityProviders +} + +func RandomFinalityProviderState(r *rand.Rand) types.FinalityProviderState { + states := []types.FinalityProviderState{types.FinalityProviderStateActive, types.FinalityProviderStateStandby} + return states[r.Intn(len(states))] +} + +func GenerateRandomBabylonParams(r *rand.Rand) types.BabylonParams { + return types.BabylonParams{ + Version: r.Intn(10), + CovenantPKs: GeneratePks(r.Intn(10)), + CovenantQuorum: r.Intn(10), + MaxStakingAmount: int64(r.Intn(1000000000000000000)), + MinStakingAmount: int64(r.Intn(1000000000000000000)), + MaxStakingTime: int64(r.Intn(1000000000000000000)), + MinStakingTime: int64(r.Intn(1000000000000000000)), + SlashingPKScript: RandomString(r, 10), + MinSlashingTxFee: int64(r.Intn(1000000000000000000)), + SlashingRate: RandomPostiveFloat64(r), + MinUnbondingTime: int64(r.Intn(1000000000000000000)), + UnbondingFee: int64(r.Intn(1000000000000000000)), + MinCommissionRate: RandomPostiveFloat64(r), + MaxActiveFinalityProviders: r.Intn(10), + DelegationCreationBaseGasFee: int64(r.Intn(1000000000000000000)), + } +} + +func GenerateRandomBTCParams(r *rand.Rand) types.BTCParams { + return types.BTCParams{ + Version: r.Intn(10), + BTCConfirmationDepth: r.Intn(10), + } +} + +func RandomDelegationState(r *rand.Rand) types.DelegationState { + states := []types.DelegationState{types.Active, types.UnbondingRequested, types.Unbonding, types.Unbonded, types.Withdrawn} + return states[r.Intn(len(states))] +} + +func RandomTransactionInfo(r *rand.Rand) types.TransactionInfo { + _, txHex, _ := GenerateRandomTx(r, nil) + return types.TransactionInfo{ + TxHex: txHex, + OutputIndex: r.Intn(100), + } +} diff --git a/internal/utils/state_transition.go b/internal/shared/utils/state_transition.go similarity index 95% rename from internal/utils/state_transition.go rename to internal/shared/utils/state_transition.go index 1d6985d7..6f46f849 100644 --- a/internal/utils/state_transition.go +++ b/internal/shared/utils/state_transition.go @@ -1,8 +1,6 @@ package utils -import ( - "github.com/babylonlabs-io/staking-api-service/internal/types" -) +import "github.com/babylonlabs-io/staking-api-service/internal/shared/types" // QualifiedStatesToUnbondingRequest returns the qualified exisitng states to transition to "unbonding_request" func QualifiedStatesToUnbondingRequest() []types.DelegationState { diff --git a/internal/utils/timestamp.go b/internal/shared/utils/timestamp.go similarity index 100% rename from internal/utils/timestamp.go rename to internal/shared/utils/timestamp.go diff --git a/internal/utils/utils.go b/internal/shared/utils/utils.go similarity index 100% rename from internal/utils/utils.go rename to internal/shared/utils/utils.go diff --git a/internal/utils/validation.go b/internal/shared/utils/validation.go similarity index 100% rename from internal/utils/validation.go rename to internal/shared/utils/validation.go diff --git a/internal/types/global_params.go b/internal/types/global_params.go deleted file mode 100644 index 0e5bffa6..00000000 --- a/internal/types/global_params.go +++ /dev/null @@ -1,51 +0,0 @@ -package types - -import ( - "encoding/hex" - "encoding/json" - "os" - "path/filepath" - - "github.com/babylonlabs-io/networks/parameters/parser" - "github.com/btcsuite/btcd/btcec/v2" -) - -type VersionedGlobalParams = parser.VersionedGlobalParams - -type GlobalParams = parser.GlobalParams - -func NewGlobalParams(path string) (*GlobalParams, error) { - data, err := os.ReadFile(filepath.Clean(path)) - if err != nil { - return nil, err - } - - var globalParams GlobalParams - err = json.Unmarshal(data, &globalParams) - if err != nil { - return nil, err - } - - _, err = parser.ParseGlobalParams(&globalParams) - if err != nil { - return nil, err - } - - return &globalParams, nil -} - -// parseCovenantPubKeyFromHex parses public key string to btc public key -// the input should be 33 bytes -func parseCovenantPubKeyFromHex(pkStr string) (*btcec.PublicKey, error) { - pkBytes, err := hex.DecodeString(pkStr) - if err != nil { - return nil, err - } - - pk, err := btcec.ParsePubKey(pkBytes) - if err != nil { - return nil, err - } - - return pk, nil -} diff --git a/internal/v1/api/handlers/delegation.go b/internal/v1/api/handlers/delegation.go new file mode 100644 index 00000000..af23a976 --- /dev/null +++ b/internal/v1/api/handlers/delegation.go @@ -0,0 +1,30 @@ +package v1handlers + +import ( + "net/http" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" +) + +// GetDelegationByTxHash @Summary Get a delegation +// @Description Retrieves a delegation by a given transaction hash +// @Produce json +// @Tags v1 +// @Param staking_tx_hash_hex query string true "Staking transaction hash in hex format" +// @Success 200 {object} handler.PublicResponse[v1service.DelegationPublic] "Delegation" +// @Failure 400 {object} types.Error "Error: Bad Request" +// @Router /v1/delegation [get] +func (h *V1Handler) GetDelegationByTxHash(request *http.Request) (*handler.Result, *types.Error) { + stakingTxHash, err := handler.ParseTxHashQuery(request, "staking_tx_hash_hex") + if err != nil { + return nil, err + } + delegation, err := h.Service.GetDelegation(request.Context(), stakingTxHash) + if err != nil { + return nil, err + } + + return handler.NewResult(v1service.FromDelegationDocument(delegation)), nil +} diff --git a/internal/v1/api/handlers/finality_provider.go b/internal/v1/api/handlers/finality_provider.go new file mode 100644 index 00000000..240e5f42 --- /dev/null +++ b/internal/v1/api/handlers/finality_provider.go @@ -0,0 +1,47 @@ +package v1handlers + +import ( + "net/http" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" +) + +// GetFinalityProviders gets active finality providers sorted by ActiveTvl. +// @Summary Get Active Finality Providers +// @Description Fetches details of all active finality providers sorted by their active total value locked (ActiveTvl) in descending order. +// @Produce json +// @Tags v1 +// @Param fp_btc_pk query string false "Public key of the finality provider to fetch" +// @Param pagination_key query string false "Pagination key to fetch the next page of finality providers" +// @Success 200 {object} handler.PublicResponse[[]v1service.FpDetailsPublic] "A list of finality providers sorted by ActiveTvl in descending order" +// @Router /v1/finality-providers [get] +func (h *V1Handler) GetFinalityProviders(request *http.Request) (*handler.Result, *types.Error) { + fpPk, err := handler.ParsePublicKeyQuery(request, "fp_btc_pk", true) + if err != nil { + return nil, err + } + if fpPk != "" { + var result []*v1service.FpDetailsPublic + fp, err := h.Service.GetFinalityProvider(request.Context(), fpPk) + if err != nil { + return nil, err + } + if fp != nil { + result = append(result, fp) + } + + return handler.NewResult(result), nil + } + + paginationKey, err := handler.ParsePaginationQuery(request) + if err != nil { + return nil, err + } + fps, paginationToken, err := h.Service.GetFinalityProviders(request.Context(), paginationKey) + if err != nil { + return nil, err + } + return handler.NewResultWithPagination(fps, paginationToken), nil +} diff --git a/internal/v1/api/handlers/handler.go b/internal/v1/api/handlers/handler.go new file mode 100644 index 00000000..c5ddaa61 --- /dev/null +++ b/internal/v1/api/handlers/handler.go @@ -0,0 +1,22 @@ +package v1handlers + +import ( + "context" + + handler "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" +) + +type V1Handler struct { + *handler.Handler + Service v1service.V1ServiceProvider +} + +func New( + ctx context.Context, handler *handler.Handler, v1Service v1service.V1ServiceProvider, +) (*V1Handler, error) { + return &V1Handler{ + Handler: handler, + Service: v1Service, + }, nil +} diff --git a/internal/v1/api/handlers/params.go b/internal/v1/api/handlers/params.go new file mode 100644 index 00000000..0e675186 --- /dev/null +++ b/internal/v1/api/handlers/params.go @@ -0,0 +1,20 @@ +package v1handlers + +import ( + "net/http" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" +) + +// GetBabylonGlobalParams godoc +// @Summary Get Babylon global parameters +// @Description Retrieves the global parameters for Babylon, including finality provider details. +// @Produce json +// @Tags v1 +// @Success 200 {object} handler.PublicResponse[v1service.GlobalParamsPublic] "Global parameters" +// @Router /v1/global-params [get] +func (h *V1Handler) GetBabylonGlobalParams(request *http.Request) (*handler.Result, *types.Error) { + params := h.Service.GetGlobalParamsPublic() + return handler.NewResult(params), nil +} diff --git a/internal/api/handlers/pubkey.go b/internal/v1/api/handlers/pubkey.go similarity index 67% rename from internal/api/handlers/pubkey.go rename to internal/v1/api/handlers/pubkey.go index 94278281..7abef87f 100644 --- a/internal/api/handlers/pubkey.go +++ b/internal/v1/api/handlers/pubkey.go @@ -1,9 +1,10 @@ -package handlers +package v1handlers import ( "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" ) const ( @@ -23,24 +24,25 @@ const ( // the system. If an address has no associated delegation, it will not be // included in the response. Supports both Taproot and Native Segwit addresses. // @Produce json +// @Tags v1 // @Param address query []string true "List of BTC addresses to look up (up to 10), currently only supports Taproot and Native Segwit addresses" collectionFormat(multi) -// @Success 200 {object} Result[map[string]string] "A map of BTC addresses to their corresponding public keys (only addresses with delegations are returned)" +// @Success 200 {object} handler.Result[map[string]string] "A map of BTC addresses to their corresponding public keys (only addresses with delegations are returned)" // @Failure 400 {object} types.Error "Bad Request: Invalid input parameters" // @Failure 500 {object} types.Error "Internal Server Error" // @Router /v1/staker/pubkey-lookup [get] -func (h *Handler) GetPubKeys(request *http.Request) (*Result, *types.Error) { - addresses, err := parseBtcAddressesQuery( - request, "address", h.config.Server.BTCNetParam, MAX_NUM_PK_LOOKUP_ADDRESSES, +func (h *V1Handler) GetPubKeys(request *http.Request) (*handler.Result, *types.Error) { + addresses, err := handler.ParseBtcAddressesQuery( + request, "address", h.Handler.Config.Server.BTCNetParam, MAX_NUM_PK_LOOKUP_ADDRESSES, ) if err != nil { return nil, err } // Get the public keys for the given addresses - addressToPkMapping, err := h.services.GetStakerPublicKeysByAddresses(request.Context(), addresses) + addressToPkMapping, err := h.Service.GetStakerPublicKeysByAddresses(request.Context(), addresses) if err != nil { return nil, err } - return NewResult(addressToPkMapping), nil + return handler.NewResult(addressToPkMapping), nil } diff --git a/internal/api/handlers/staker.go b/internal/v1/api/handlers/staker.go similarity index 65% rename from internal/api/handlers/staker.go rename to internal/v1/api/handlers/staker.go index e1fce9bb..198b8bb9 100644 --- a/internal/api/handlers/staker.go +++ b/internal/v1/api/handlers/staker.go @@ -1,10 +1,11 @@ -package handlers +package v1handlers import ( "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" ) type DelegationCheckPublicResponse struct { @@ -15,33 +16,35 @@ type DelegationCheckPublicResponse struct { // GetStakerDelegations @Summary Get staker delegations // @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" -// @Success 200 {object} PublicResponse[[]services.DelegationPublic]{array} "List of delegations and pagination token" +// @Success 200 {object} handler.PublicResponse[[]v1service.DelegationPublic]{array} "List of delegations and pagination token" // @Failure 400 {object} types.Error "Error: Bad Request" // @Router /v1/staker/delegations [get] -func (h *Handler) GetStakerDelegations(request *http.Request) (*Result, *types.Error) { - stakerBtcPk, err := parsePublicKeyQuery(request, "staker_btc_pk", false) +func (h *V1Handler) GetStakerDelegations(request *http.Request) (*handler.Result, *types.Error) { + stakerBtcPk, err := handler.ParsePublicKeyQuery(request, "staker_btc_pk", false) if err != nil { return nil, err } - paginationKey, err := parsePaginationQuery(request) + paginationKey, err := handler.ParsePaginationQuery(request) if err != nil { return nil, err } - stateFilter, err := parseStateFilterQuery(request, "state") + stateFilter, err := handler.ParseStateFilterQuery(request, "state") if err != nil { return nil, err } - delegations, newPaginationKey, err := h.services.DelegationsByStakerPk( + delegations, newPaginationKey, err := h.Service.DelegationsByStakerPk( request.Context(), stakerBtcPk, stateFilter, paginationKey, ) if err != nil { return nil, err } - return NewResultWithPagination(delegations, newPaginationKey), nil + return handler.NewResultWithPagination(delegations, newPaginationKey), nil } // CheckStakerDelegationExist @Summary Check if a staker has an active delegation @@ -49,13 +52,14 @@ func (h *Handler) GetStakerDelegations(request *http.Request) (*Result, *types.E // @Description Optionally, you can provide a timeframe to check if the delegation is active within the provided timeframe // @Description The available timeframe is "today" which checks after UTC 12AM of the current day // @Produce json +// @Tags v1 // @Param address query string true "Staker BTC address in Taproot/Native Segwit format" // @Param timeframe query string false "Check if the delegation is active within the provided timeframe" Enums(today) // @Success 200 {object} DelegationCheckPublicResponse "Delegation check result" // @Failure 400 {object} types.Error "Error: Bad Request" // @Router /v1/staker/delegation/check [get] -func (h *Handler) CheckStakerDelegationExist(request *http.Request) (*Result, *types.Error) { - address, err := parseBtcAddressQuery(request, "address", h.config.Server.BTCNetParam) +func (h *V1Handler) CheckStakerDelegationExist(request *http.Request) (*handler.Result, *types.Error) { + address, err := handler.ParseBtcAddressQuery(request, "address", h.Handler.Config.Server.BTCNetParam) if err != nil { return nil, err } @@ -65,7 +69,7 @@ func (h *Handler) CheckStakerDelegationExist(request *http.Request) (*Result, *t return nil, err } - addressToPkMapping, err := h.services.GetStakerPublicKeysByAddresses(request.Context(), []string{address}) + addressToPkMapping, err := h.Service.GetStakerPublicKeysByAddresses(request.Context(), []string{address}) if err != nil { return nil, err } @@ -73,7 +77,7 @@ func (h *Handler) CheckStakerDelegationExist(request *http.Request) (*Result, *t return buildDelegationCheckResponse(false), nil } - exist, err := h.services.CheckStakerHasActiveDelegationByPk( + exist, err := h.Service.CheckStakerHasActiveDelegationByPk( request.Context(), addressToPkMapping[address], afterTimestamp, ) if err != nil { @@ -83,8 +87,8 @@ func (h *Handler) CheckStakerDelegationExist(request *http.Request) (*Result, *t return buildDelegationCheckResponse(exist), nil } -func buildDelegationCheckResponse(exist bool) *Result { - return &Result{ +func buildDelegationCheckResponse(exist bool) *handler.Result { + return &handler.Result{ Data: &DelegationCheckPublicResponse{ Data: exist, Code: 0, }, diff --git a/internal/api/handlers/stats.go b/internal/v1/api/handlers/stats.go similarity index 53% rename from internal/api/handlers/stats.go rename to internal/v1/api/handlers/stats.go index d346f466..e8739407 100644 --- a/internal/api/handlers/stats.go +++ b/internal/v1/api/handlers/stats.go @@ -1,25 +1,27 @@ -package handlers +package v1handlers import ( "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" ) // GetOverallStats gets overall stats for babylon staking // @Summary Get Overall Stats // @Description Fetches overall stats for babylon staking including tvl, total delegations, active tvl, active delegations and total stakers. // @Produce json -// @Success 200 {object} PublicResponse[services.OverallStatsPublic] "Overall stats for babylon staking" +// @Tags v1 +// @Success 200 {object} handler.PublicResponse[v1service.OverallStatsPublic] "Overall stats for babylon staking" // @Router /v1/stats [get] -func (h *Handler) GetOverallStats(request *http.Request) (*Result, *types.Error) { - stats, err := h.services.GetOverallStats(request.Context()) +func (h *V1Handler) GetOverallStats(request *http.Request) (*handler.Result, *types.Error) { + stats, err := h.Service.GetOverallStats(request.Context()) if err != nil { return nil, err } - return NewResult(stats), nil + return handler.NewResult(stats), nil } // GetStakersStats gets staker stats for babylon staking @@ -28,20 +30,21 @@ func (h *Handler) GetOverallStats(request *http.Request) (*Result, *types.Error) // @Description If staker_btc_pk query parameter is provided, it will return stats for the specific staker. // @Description Otherwise, it will return the top stakers ranked by active tvl. // @Produce json +// @Tags v1 // @Param staker_btc_pk query string false "Public key of the staker to fetch" // @Param pagination_key query string false "Pagination key to fetch the next page of top stakers" -// @Success 200 {object} PublicResponse[[]services.StakerStatsPublic]{array} "List of top stakers by active tvl" +// @Success 200 {object} handler.PublicResponse[[]v1service.StakerStatsPublic]{array} "List of top stakers by active tvl" // @Failure 400 {object} types.Error "Error: Bad Request" // @Router /v1/stats/staker [get] -func (h *Handler) GetStakersStats(request *http.Request) (*Result, *types.Error) { +func (h *V1Handler) GetStakersStats(request *http.Request) (*handler.Result, *types.Error) { // Check if the request is for a specific staker - stakerPk, err := parsePublicKeyQuery(request, "staker_btc_pk", true) + stakerPk, err := handler.ParsePublicKeyQuery(request, "staker_btc_pk", true) if err != nil { return nil, err } if stakerPk != "" { - var result []services.StakerStatsPublic - stakerStats, err := h.services.GetStakerStats(request.Context(), stakerPk) + var result []v1service.StakerStatsPublic + stakerStats, err := h.Service.GetStakerStats(request.Context(), stakerPk) if err != nil { return nil, err } @@ -49,18 +52,18 @@ func (h *Handler) GetStakersStats(request *http.Request) (*Result, *types.Error) result = append(result, *stakerStats) } - return NewResult(result), nil + return handler.NewResult(result), nil } // Otherwise, fetch the top stakers ranked by active tvl - paginationKey, err := parsePaginationQuery(request) + paginationKey, err := handler.ParsePaginationQuery(request) if err != nil { return nil, err } - topStakerStats, paginationToken, err := h.services.GetTopStakersByActiveTvl(request.Context(), paginationKey) + topStakerStats, paginationToken, err := h.Service.GetTopStakersByActiveTvl(request.Context(), paginationKey) if err != nil { return nil, err } - return NewResultWithPagination(topStakerStats, paginationToken), nil + return handler.NewResultWithPagination(topStakerStats, paginationToken), nil } diff --git a/internal/api/handlers/unbonding.go b/internal/v1/api/handlers/unbonding.go similarity index 77% rename from internal/api/handlers/unbonding.go rename to internal/v1/api/handlers/unbonding.go index b87f8520..a385228b 100644 --- a/internal/api/handlers/unbonding.go +++ b/internal/v1/api/handlers/unbonding.go @@ -1,11 +1,12 @@ -package handlers +package v1handlers import ( "encoding/json" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" ) type UnbondDelegationRequestPayload struct { @@ -51,16 +52,17 @@ func parseUnbondDelegationRequestPayload(request *http.Request) (*UnbondDelegati // @Description Unbonds a delegation by processing the provided transaction details. This is an async operation. // @Accept json // @Produce json +// @Tags v1 // @Param payload body UnbondDelegationRequestPayload true "Unbonding Request Payload" // @Success 202 "Request accepted and will be processed asynchronously" // @Failure 400 {object} types.Error "Invalid request payload" // @Router /v1/unbonding [post] -func (h *Handler) UnbondDelegation(request *http.Request) (*Result, *types.Error) { +func (h *V1Handler) UnbondDelegation(request *http.Request) (*handler.Result, *types.Error) { payload, err := parseUnbondDelegationRequestPayload(request) if err != nil { return nil, err } - unbondErr := h.services.UnbondDelegation( + unbondErr := h.Service.UnbondDelegation( request.Context(), payload.StakingTxHashHex, payload.UnbondingTxHashHex, payload.UnbondingTxHex, payload.StakerSignedSignatureHex, @@ -69,26 +71,27 @@ func (h *Handler) UnbondDelegation(request *http.Request) (*Result, *types.Error return nil, unbondErr } - return &Result{Status: http.StatusAccepted}, nil + return &handler.Result{Status: http.StatusAccepted}, nil } // GetUnbondingEligibility godoc // @Summary Check unbonding eligibility // @Description Checks if a delegation identified by its staking transaction hash is eligible for unbonding. // @Produce json +// @Tags v1 // @Param staking_tx_hash_hex query string true "Staking Transaction Hash Hex" // @Success 200 "The delegation is eligible for unbonding" // @Failure 400 {object} types.Error "Missing or invalid 'staking_tx_hash_hex' query parameter" // @Router /v1/unbonding/eligibility [get] -func (h *Handler) GetUnbondingEligibility(request *http.Request) (*Result, *types.Error) { - stakingTxHashHex, err := parseTxHashQuery(request, "staking_tx_hash_hex") +func (h *V1Handler) GetUnbondingEligibility(request *http.Request) (*handler.Result, *types.Error) { + stakingTxHashHex, err := handler.ParseTxHashQuery(request, "staking_tx_hash_hex") if err != nil { return nil, err } - err = h.services.IsEligibleForUnbondingRequest(request.Context(), stakingTxHashHex) + err = h.Service.IsEligibleForUnbondingRequest(request.Context(), stakingTxHashHex) if err != nil { return nil, err } - return &Result{Status: http.StatusOK}, nil + return &handler.Result{Status: http.StatusOK}, nil } diff --git a/internal/db/btc_info.go b/internal/v1/db/client/btc_info.go similarity index 54% rename from internal/db/btc_info.go rename to internal/v1/db/client/btc_info.go index 7590d068..f2678fc3 100644 --- a/internal/db/btc_info.go +++ b/internal/v1/db/client/btc_info.go @@ -1,20 +1,22 @@ -package db +package v1dbclient import ( "context" "errors" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/db" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) -func (db *Database) UpsertLatestBtcInfo( +func (v1dbclient *V1Database) UpsertLatestBtcInfo( ctx context.Context, height uint64, confirmedTvl, unconfirmedTvl uint64, ) error { - client := db.Client.Database(db.DbName).Collection(model.BtcInfoCollection) + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1BtcInfoCollection) // Start a session - session, sessionErr := db.Client.StartSession() + session, sessionErr := v1dbclient.Client.StartSession() if sessionErr != nil { return sessionErr } @@ -22,14 +24,14 @@ func (db *Database) UpsertLatestBtcInfo( transactionWork := func(sessCtx mongo.SessionContext) (interface{}, error) { // Check for existing document - var existingInfo model.BtcInfo - findErr := client.FindOne(sessCtx, bson.M{"_id": model.LatestBtcInfoId}).Decode(&existingInfo) + var existingInfo v1dbmodel.BtcInfo + findErr := client.FindOne(sessCtx, bson.M{"_id": v1dbmodel.LatestBtcInfoId}).Decode(&existingInfo) if findErr != nil && findErr != mongo.ErrNoDocuments { return nil, findErr } - btcInfo := &model.BtcInfo{ - ID: model.LatestBtcInfoId, + btcInfo := &v1dbmodel.BtcInfo{ + ID: v1dbmodel.LatestBtcInfoId, BtcHeight: height, ConfirmedTvl: confirmedTvl, UnconfirmedTvl: unconfirmedTvl, @@ -46,7 +48,7 @@ func (db *Database) UpsertLatestBtcInfo( // If document exists and the incoming height is greater, update the document if existingInfo.BtcHeight < height { _, updateErr := client.UpdateOne( - sessCtx, bson.M{"_id": model.LatestBtcInfoId}, + sessCtx, bson.M{"_id": v1dbmodel.LatestBtcInfoId}, bson.M{"$set": btcInfo}, ) if updateErr != nil { @@ -61,14 +63,14 @@ func (db *Database) UpsertLatestBtcInfo( return txErr } -func (db *Database) GetLatestBtcInfo(ctx context.Context) (*model.BtcInfo, error) { - client := db.Client.Database(db.DbName).Collection(model.BtcInfoCollection) - var btcInfo model.BtcInfo - err := client.FindOne(ctx, bson.M{"_id": model.LatestBtcInfoId}).Decode(&btcInfo) +func (v1dbclient *V1Database) GetLatestBtcInfo(ctx context.Context) (*v1dbmodel.BtcInfo, error) { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1BtcInfoCollection) + var btcInfo v1dbmodel.BtcInfo + err := client.FindOne(ctx, bson.M{"_id": v1dbmodel.LatestBtcInfoId}).Decode(&btcInfo) if err != nil { if errors.Is(err, mongo.ErrNoDocuments) { - return nil, &NotFoundError{ - Key: model.LatestBtcInfoId, + return nil, &db.NotFoundError{ + Key: v1dbmodel.LatestBtcInfoId, Message: "Latest Btc info not found", } } diff --git a/internal/v1/db/client/db_client.go b/internal/v1/db/client/db_client.go new file mode 100644 index 00000000..e2ccfc23 --- /dev/null +++ b/internal/v1/db/client/db_client.go @@ -0,0 +1,23 @@ +package v1dbclient + +import ( + "context" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/db/client" + "go.mongodb.org/mongo-driver/mongo" +) + +type V1Database struct { + *dbclient.Database +} + +func New(ctx context.Context, client *mongo.Client, cfg *config.DbConfig) (*V1Database, error) { + return &V1Database{ + Database: &dbclient.Database{ + DbName: cfg.DbName, + Client: client, + Cfg: cfg, + }, + }, nil +} diff --git a/internal/db/delegation.go b/internal/v1/db/client/delegation.go similarity index 66% rename from internal/db/delegation.go rename to internal/v1/db/client/delegation.go index 84637e49..83ad8881 100644 --- a/internal/db/delegation.go +++ b/internal/v1/db/client/delegation.go @@ -1,4 +1,4 @@ -package db +package v1dbclient import ( "context" @@ -9,23 +9,25 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "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" + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" ) -func (db *Database) SaveActiveStakingDelegation( +func (v1dbclient *V1Database) SaveActiveStakingDelegation( ctx context.Context, stakingTxHashHex, stakerPkHex, fpPkHex string, stakingTxHex string, amount, startHeight, timelock, outputIndex uint64, startTimestamp int64, isOverflow bool, ) error { - client := db.Client.Database(db.DbName).Collection(model.DelegationCollection) - document := model.DelegationDocument{ + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1DelegationCollection) + document := v1dbmodel.DelegationDocument{ StakingTxHashHex: stakingTxHashHex, // Primary key of db collection StakerPkHex: stakerPkHex, FinalityProviderPkHex: fpPkHex, StakingValue: amount, State: types.Active, - StakingTx: &model.TimelockTransaction{ + StakingTx: &v1dbmodel.TimelockTransaction{ TxHex: stakingTxHex, OutputIndex: outputIndex, StartTimestamp: startTimestamp, @@ -41,7 +43,7 @@ func (db *Database) SaveActiveStakingDelegation( for _, e := range writeErr.WriteErrors { if mongo.IsDuplicateKeyError(e) { // Return the custom error type so that we can return 4xx errors to client - return &DuplicateKeyError{ + return &db.DuplicateKeyError{ Key: stakingTxHashHex, Message: "Delegation already exists", } @@ -55,14 +57,14 @@ func (db *Database) SaveActiveStakingDelegation( // CheckDelegationExistByStakerPk checks if a staker has any // delegation in the specified states by the staker's public key -func (db *Database) CheckDelegationExistByStakerPk( +func (v1dbclient *V1Database) CheckDelegationExistByStakerPk( ctx context.Context, stakerPk string, extraFilter *DelegationFilter, ) (bool, error) { - client := db.Client.Database(db.DbName).Collection(model.DelegationCollection) + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1DelegationCollection) filter := buildAdditionalDelegationFilter( bson.M{"staker_pk_hex": stakerPk}, extraFilter, ) - var delegation model.DelegationDocument + var delegation v1dbmodel.DelegationDocument err := client.FindOne(ctx, filter).Decode(&delegation) if err != nil { if err == mongo.ErrNoDocuments { @@ -73,11 +75,11 @@ func (db *Database) CheckDelegationExistByStakerPk( return true, nil } -func (db *Database) FindDelegationsByStakerPk( +func (v1dbclient *V1Database) FindDelegationsByStakerPk( ctx context.Context, stakerPk string, extraFilter *DelegationFilter, paginationToken string, -) (*DbResultMap[model.DelegationDocument], error) { - client := db.Client.Database(db.DbName).Collection(model.DelegationCollection) +) (*db.DbResultMap[v1dbmodel.DelegationDocument], error) { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1DelegationCollection) filter := bson.M{"staker_pk_hex": stakerPk} filter = buildAdditionalDelegationFilter(filter, extraFilter) @@ -88,9 +90,9 @@ func (db *Database) FindDelegationsByStakerPk( // Decode the pagination token first if it exist if paginationToken != "" { - decodedToken, err := model.DecodePaginationToken[model.DelegationByStakerPagination](paginationToken) + decodedToken, err := dbmodel.DecodePaginationToken[v1dbmodel.DelegationByStakerPagination](paginationToken) if err != nil { - return nil, &InvalidPaginationTokenError{ + return nil, &db.InvalidPaginationTokenError{ Message: "Invalid pagination token", } } @@ -102,22 +104,22 @@ func (db *Database) FindDelegationsByStakerPk( } } - return findWithPagination( - ctx, client, filter, options, db.cfg.MaxPaginationLimit, - model.BuildDelegationByStakerPaginationToken, + return db.FindWithPagination( + ctx, client, filter, options, v1dbclient.Cfg.MaxPaginationLimit, + v1dbmodel.BuildDelegationByStakerPaginationToken, ) } // SaveUnbondingTx saves the unbonding transaction details for a staking transaction // It returns an NotFoundError if the staking transaction is not found -func (db *Database) FindDelegationByTxHashHex(ctx context.Context, stakingTxHashHex string) (*model.DelegationDocument, error) { - client := db.Client.Database(db.DbName).Collection(model.DelegationCollection) +func (v1dbclient *V1Database) FindDelegationByTxHashHex(ctx context.Context, stakingTxHashHex string) (*v1dbmodel.DelegationDocument, error) { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1DelegationCollection) filter := bson.M{"_id": stakingTxHashHex} - var delegation model.DelegationDocument + var delegation v1dbmodel.DelegationDocument err := client.FindOne(ctx, filter).Decode(&delegation) if err != nil { if errors.Is(err, mongo.ErrNoDocuments) { - return nil, &NotFoundError{ + return nil, &db.NotFoundError{ Key: stakingTxHashHex, Message: "Delegation not found", } @@ -127,20 +129,20 @@ func (db *Database) FindDelegationByTxHashHex(ctx context.Context, stakingTxHash return &delegation, nil } -func (db *Database) ScanDelegationsPaginated( +func (v1dbclient *V1Database) ScanDelegationsPaginated( ctx context.Context, paginationToken string, -) (*DbResultMap[model.DelegationDocument], error) { - client := db.Client.Database(db.DbName).Collection(model.DelegationCollection) +) (*db.DbResultMap[v1dbmodel.DelegationDocument], error) { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1DelegationCollection) filter := bson.M{} options := options.Find() options.SetSort(bson.M{"_id": 1}) // Decode the pagination token if it exists if paginationToken != "" { decodedToken, err := - model.DecodePaginationToken[model.DelegationScanPagination](paginationToken) + dbmodel.DecodePaginationToken[v1dbmodel.DelegationScanPagination](paginationToken) if err != nil { - return nil, &InvalidPaginationTokenError{ + return nil, &db.InvalidPaginationTokenError{ Message: "Invalid pagination token", } } @@ -148,19 +150,19 @@ func (db *Database) ScanDelegationsPaginated( } // Perform the paginated query and return the results - return findWithPagination( - ctx, client, filter, options, db.cfg.MaxPaginationLimit, - model.BuildDelegationScanPaginationToken, + return db.FindWithPagination( + ctx, client, filter, options, v1dbclient.Cfg.MaxPaginationLimit, + v1dbmodel.BuildDelegationScanPaginationToken, ) } // TransitionState updates the state of a staking transaction to a new state // It returns an NotFoundError if the staking transaction is not found or not in the eligible state to transition -func (db *Database) transitionState( +func (v1dbclient *V1Database) transitionState( ctx context.Context, stakingTxHashHex, newState string, eligiblePreviousState []types.DelegationState, additionalUpdates map[string]interface{}, ) error { - client := db.Client.Database(db.DbName).Collection(model.DelegationCollection) + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1DelegationCollection) filter := bson.M{"_id": stakingTxHashHex, "state": bson.M{"$in": eligiblePreviousState}} update := bson.M{"$set": bson.M{"state": newState}} for field, value := range additionalUpdates { @@ -170,7 +172,7 @@ func (db *Database) transitionState( _, err := client.UpdateOne(ctx, filter, update) if err != nil { if errors.Is(err, mongo.ErrNoDocuments) { - return &NotFoundError{ + return &db.NotFoundError{ Key: stakingTxHashHex, Message: "Delegation not found or not in eligible state to transition", } diff --git a/internal/db/interface.go b/internal/v1/db/client/interface.go similarity index 60% rename from internal/db/interface.go rename to internal/v1/db/client/interface.go index 258654b3..163dff1f 100644 --- a/internal/db/interface.go +++ b/internal/v1/db/client/interface.go @@ -1,14 +1,16 @@ -package db +package v1dbclient import ( "context" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/db" + dbclient "github.com/babylonlabs-io/staking-api-service/internal/shared/db/client" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" ) -type DBClient interface { - Ping(ctx context.Context) error +type V1DBClient interface { + dbclient.DBClient SaveActiveStakingDelegation( ctx context.Context, stakingTxHashHex, stakerPkHex, fpPkHex string, stakingTxHex string, amount, startHeight, timelock, outputIndex uint64, @@ -23,15 +25,12 @@ type DBClient interface { FindDelegationsByStakerPk( ctx context.Context, stakerPk string, extraFilter *DelegationFilter, paginationToken string, - ) (*DbResultMap[model.DelegationDocument], error) + ) (*db.DbResultMap[v1dbmodel.DelegationDocument], error) SaveUnbondingTx( ctx context.Context, stakingTxHashHex, unbondingTxHashHex, txHex, signatureHex string, ) error - FindDelegationByTxHashHex(ctx context.Context, txHashHex string) (*model.DelegationDocument, error) + FindDelegationByTxHashHex(ctx context.Context, txHashHex string) (*v1dbmodel.DelegationDocument, error) SaveTimeLockExpireCheck(ctx context.Context, stakingTxHashHex string, expireHeight uint64, txType string) error - SaveUnprocessableMessage(ctx context.Context, messageBody, receipt string) error - FindUnprocessableMessages(ctx context.Context) ([]model.UnprocessableMessageDocument, error) - DeleteUnprocessableMessage(ctx context.Context, Receipt interface{}) error TransitionToUnbondedState( ctx context.Context, stakingTxHashHex string, eligiblePreviousState []types.DelegationState, ) error @@ -41,69 +40,49 @@ type DBClient interface { TransitionToWithdrawnState(ctx context.Context, txHashHex string) error GetOrCreateStatsLock( ctx context.Context, stakingTxHashHex string, state string, - ) (*model.StatsLockDocument, error) + ) (*v1dbmodel.StatsLockDocument, error) SubtractOverallStats( ctx context.Context, stakingTxHashHex, stakerPkHex string, amount uint64, ) error IncrementOverallStats( ctx context.Context, stakingTxHashHex, stakerPkHex string, amount uint64, ) error - GetOverallStats(ctx context.Context) (*model.OverallStatsDocument, error) + GetOverallStats(ctx context.Context) (*v1dbmodel.OverallStatsDocument, error) IncrementFinalityProviderStats( ctx context.Context, stakingTxHashHex, fpPkHex string, amount uint64, ) error SubtractFinalityProviderStats( ctx context.Context, stakingTxHashHex, fpPkHex string, amount uint64, ) error - FindFinalityProviderStats(ctx context.Context, paginationToken string) (*DbResultMap[*model.FinalityProviderStatsDocument], error) + FindFinalityProviderStats(ctx context.Context, paginationToken string) (*db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument], error) FindFinalityProviderStatsByFinalityProviderPkHex( ctx context.Context, finalityProviderPkHex []string, - ) ([]*model.FinalityProviderStatsDocument, error) + ) ([]*v1dbmodel.FinalityProviderStatsDocument, error) IncrementStakerStats( ctx context.Context, stakingTxHashHex, stakerPkHex string, amount uint64, ) error SubtractStakerStats( ctx context.Context, stakingTxHashHex, stakerPkHex string, amount uint64, ) error - FindTopStakersByTvl(ctx context.Context, paginationToken string) (*DbResultMap[*model.StakerStatsDocument], error) + FindTopStakersByTvl(ctx context.Context, paginationToken string) (*db.DbResultMap[*v1dbmodel.StakerStatsDocument], error) // GetStakerStats fetches the staker stats by the staker's public key. GetStakerStats( ctx context.Context, stakerPkHex string, - ) (*model.StakerStatsDocument, error) + ) (*v1dbmodel.StakerStatsDocument, error) UpsertLatestBtcInfo( ctx context.Context, height uint64, confirmedTvl uint64, unconfirmedTvl uint64, ) error - GetLatestBtcInfo(ctx context.Context) (*model.BtcInfo, error) + GetLatestBtcInfo(ctx context.Context) (*v1dbmodel.BtcInfo, error) CheckDelegationExistByStakerPk( ctx context.Context, address string, extraFilter *DelegationFilter, ) (bool, error) - // InsertPkAddressMappings inserts the btc public key and - // its corresponding btc addresses into the database. - InsertPkAddressMappings( - ctx context.Context, stakerPkHex, taproot, nativeSigwitOdd, nativeSigwitEven string, - ) error - // FindPkMappingsByTaprootAddress finds the PK address mappings by taproot address. - // The returned slice addressMapping will only contain documents for addresses - // that were found in the database. If some addresses do not have a matching - // document, those addresses will simply be absent from the result. - FindPkMappingsByTaprootAddress( - ctx context.Context, taprootAddresses []string, - ) ([]*model.PkAddressMapping, error) - // FindPkMappingsByNativeSegwitAddress finds the PK address mappings by native - // segwit address. The returned slice addressMapping will only contain - // documents for addresses that were found in the database. - // If some addresses do not have a matching document, those addresses will - // simply be absent from the result. - FindPkMappingsByNativeSegwitAddress( - ctx context.Context, nativeSegwitAddresses []string, - ) ([]*model.PkAddressMapping, error) // ScanDelegationsPaginated scans the delegation collection in a paginated way // without applying any filters or sorting, ensuring that all existing items // are eventually fetched. ScanDelegationsPaginated( ctx context.Context, paginationToken string, - ) (*DbResultMap[model.DelegationDocument], error) + ) (*db.DbResultMap[v1dbmodel.DelegationDocument], error) } type DelegationFilter struct { diff --git a/internal/db/stats.go b/internal/v1/db/client/stats.go similarity index 65% rename from internal/db/stats.go rename to internal/v1/db/client/stats.go index 4063ddae..79bd8f09 100644 --- a/internal/db/stats.go +++ b/internal/v1/db/client/stats.go @@ -1,4 +1,4 @@ -package db +package v1dbclient import ( "context" @@ -7,8 +7,10 @@ import ( "fmt" "math/big" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "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" + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" @@ -18,16 +20,16 @@ import ( // GetOrCreateStatsLock fetches the lock status for each stats type for the given staking tx hash. // If the document does not exist, it will create a new document with the default values // Refer to the README.md in this directory for more information on the stats lock -func (db *Database) GetOrCreateStatsLock( +func (db *V1Database) GetOrCreateStatsLock( ctx context.Context, stakingTxHashHex string, txType string, -) (*model.StatsLockDocument, error) { - client := db.Client.Database(db.DbName).Collection(model.StatsLockCollection) +) (*v1dbmodel.StatsLockDocument, error) { + client := db.Client.Database(db.DbName).Collection(dbmodel.V1StatsLockCollection) id := constructStatsLockId(stakingTxHashHex, txType) filter := bson.M{"_id": id} // Define the default document to be inserted if not found // This setOnInsert will only be applied if the document is not found update := bson.M{ - "$setOnInsert": model.NewStatsLockDocument( + "$setOnInsert": v1dbmodel.NewStatsLockDocument( id, false, false, @@ -36,7 +38,7 @@ func (db *Database) GetOrCreateStatsLock( } opts := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After) - var result model.StatsLockDocument + var result v1dbmodel.StatsLockDocument err := client.FindOneAndUpdate(ctx, filter, update, opts).Decode(&result) if err != nil { return nil, err @@ -47,14 +49,14 @@ func (db *Database) GetOrCreateStatsLock( // IncrementOverallStats increments the overall stats for the given staking tx hash. // This method is idempotent, only the first call will be processed. Otherwise it will return a notFoundError for duplicates // Refer to the README.md in this directory for more information on the sharding logic -func (db *Database) IncrementOverallStats( +func (v1dbclient *V1Database) IncrementOverallStats( ctx context.Context, stakingTxHashHex, stakerPkHex string, amount uint64, ) error { - overallStatsClient := db.Client.Database(db.DbName).Collection(model.OverallStatsCollection) - stakerStatsClient := db.Client.Database(db.DbName).Collection(model.StakerStatsCollection) + overallStatsClient := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1OverallStatsCollection) + stakerStatsClient := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1StakerStatsCollection) // Start a session - session, sessionErr := db.Client.StartSession() + session, sessionErr := v1dbclient.Client.StartSession() if sessionErr != nil { return sessionErr } @@ -70,7 +72,7 @@ func (db *Database) IncrementOverallStats( } // Define the work to be done in the transaction transactionWork := func(sessCtx mongo.SessionContext) (interface{}, error) { - err := db.updateStatsLockByFieldName(sessCtx, stakingTxHashHex, types.Active.ToString(), "overall_stats") + err := v1dbclient.updateStatsLockByFieldName(sessCtx, stakingTxHashHex, types.Active.ToString(), "overall_stats") if err != nil { return nil, err } @@ -78,7 +80,7 @@ func (db *Database) IncrementOverallStats( // The order of the overall stats and staker stats update is important. // The staker stats colleciton will need to be processed first to determine if the staker is new // If the staker stats is the first delegation for the staker, we need to increment the total stakers - var stakerStats model.StakerStatsDocument + var stakerStats v1dbmodel.StakerStatsDocument stakerStatsFilter := bson.M{"_id": stakerPkHex} stakerErr := stakerStatsClient.FindOne(ctx, stakerStatsFilter).Decode(&stakerStats) if stakerErr != nil { @@ -87,7 +89,7 @@ func (db *Database) IncrementOverallStats( if stakerStats.TotalDelegations == 1 { upsertUpdate["$inc"].(bson.M)["total_stakers"] = 1 } - shardId, err := db.generateOverallStatsId() + shardId, err := v1dbclient.generateOverallStatsId() if err != nil { return nil, err } @@ -113,7 +115,7 @@ func (db *Database) IncrementOverallStats( // SubtractOverallStats decrements the overall stats for the given staking tx hash // This method is idempotent, only the first call will be processed. Otherwise it will return a notFoundError for duplicates // Refer to the README.md in this directory for more information on the sharding logic -func (db *Database) SubtractOverallStats( +func (v1dbclient *V1Database) SubtractOverallStats( ctx context.Context, stakingTxHashHex, stakerPkHex string, amount uint64, ) error { upsertUpdate := bson.M{ @@ -122,10 +124,10 @@ func (db *Database) SubtractOverallStats( "active_delegations": -1, }, } - overallStatsClient := db.Client.Database(db.DbName).Collection(model.OverallStatsCollection) + overallStatsClient := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1OverallStatsCollection) // Start a session - session, sessionErr := db.Client.StartSession() + session, sessionErr := v1dbclient.Client.StartSession() if sessionErr != nil { return sessionErr } @@ -133,11 +135,11 @@ func (db *Database) SubtractOverallStats( // Define the work to be done in the transaction transactionWork := func(sessCtx mongo.SessionContext) (interface{}, error) { - err := db.updateStatsLockByFieldName(sessCtx, stakingTxHashHex, types.Unbonded.ToString(), "overall_stats") + err := v1dbclient.updateStatsLockByFieldName(sessCtx, stakingTxHashHex, types.Unbonded.ToString(), "overall_stats") if err != nil { return nil, err } - shardId, err := db.generateOverallStatsId() + shardId, err := v1dbclient.generateOverallStatsId() if err != nil { return nil, err } @@ -162,14 +164,14 @@ func (db *Database) SubtractOverallStats( // GetOverallStats fetches the overall stats from all the shards and sums them up // Refer to the README.md in this directory for more information on the sharding logic -func (db *Database) GetOverallStats(ctx context.Context) (*model.OverallStatsDocument, error) { +func (v1dbclient *V1Database) GetOverallStats(ctx context.Context) (*v1dbmodel.OverallStatsDocument, error) { // The collection is sharded by the _id field, so we need to query all the shards var shardsId []string - for i := 0; i < int(db.cfg.LogicalShardCount); i++ { + for i := 0; i < int(v1dbclient.Cfg.LogicalShardCount); i++ { shardsId = append(shardsId, fmt.Sprintf("%d", i)) } - client := db.Client.Database(db.DbName).Collection(model.OverallStatsCollection) + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1OverallStatsCollection) filter := bson.M{"_id": bson.M{"$in": shardsId}} cursor, err := client.Find(ctx, filter) if err != nil { @@ -177,13 +179,13 @@ func (db *Database) GetOverallStats(ctx context.Context) (*model.OverallStatsDoc } defer cursor.Close(ctx) - var overallStats []model.OverallStatsDocument + var overallStats []v1dbmodel.OverallStatsDocument if err = cursor.All(ctx, &overallStats); err != nil { return nil, err } // Sum up the stats for the overall stats - var result model.OverallStatsDocument + var result v1dbmodel.OverallStatsDocument for _, stats := range overallStats { result.ActiveTvl += stats.ActiveTvl result.TotalTvl += stats.TotalTvl @@ -198,8 +200,8 @@ func (db *Database) GetOverallStats(ctx context.Context) (*model.OverallStatsDoc // Generate the id for the overall stats document. Id is a random number ranged from 0-LogicalShardCount-1 // It's a logical shard to avoid locking the same field during concurrent writes // The sharding number should never be reduced after roll out -func (db *Database) generateOverallStatsId() (string, error) { - max := big.NewInt(int64(db.cfg.LogicalShardCount)) +func (v1dbclient *V1Database) generateOverallStatsId() (string, error) { + max := big.NewInt(int64(v1dbclient.Cfg.LogicalShardCount)) // Generate a secure random number within the range [0, max) n, err := rand.Int(rand.Reader, max) if err != nil { @@ -209,8 +211,8 @@ func (db *Database) generateOverallStatsId() (string, error) { return fmt.Sprint(n), nil } -func (db *Database) updateStatsLockByFieldName(ctx context.Context, stakingTxHashHex, state string, fieldName string) error { - statsLockClient := db.Client.Database(db.DbName).Collection(model.StatsLockCollection) +func (v1dbclient *V1Database) updateStatsLockByFieldName(ctx context.Context, stakingTxHashHex, state string, fieldName string) error { + statsLockClient := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1StatsLockCollection) filter := bson.M{"_id": constructStatsLockId(stakingTxHashHex, state), fieldName: false} update := bson.M{"$set": bson.M{fieldName: true}} result, err := statsLockClient.UpdateOne(ctx, filter, update) @@ -218,7 +220,7 @@ func (db *Database) updateStatsLockByFieldName(ctx context.Context, stakingTxHas return err } if result.MatchedCount == 0 { - return &NotFoundError{ + return &db.NotFoundError{ Key: stakingTxHashHex, Message: "document already processed or does not exist", } @@ -233,7 +235,7 @@ func constructStatsLockId(stakingTxHashHex, state string) string { // IncrementFinalityProviderStats increments the finality provider stats for the given staking tx hash // This method is idempotent, only the first call will be processed. Otherwise it will return a notFoundError for duplicates // Refer to the README.md in this directory for more information on the sharding logic -func (db *Database) IncrementFinalityProviderStats( +func (v1dbclient *V1Database) IncrementFinalityProviderStats( ctx context.Context, stakingTxHashHex, fpPkHex string, amount uint64, ) error { upsertUpdate := bson.M{ @@ -244,13 +246,13 @@ func (db *Database) IncrementFinalityProviderStats( "total_delegations": 1, }, } - return db.updateFinalityProviderStats(ctx, types.Active.ToString(), stakingTxHashHex, fpPkHex, upsertUpdate) + return v1dbclient.updateFinalityProviderStats(ctx, types.Active.ToString(), stakingTxHashHex, fpPkHex, upsertUpdate) } // SubtractFinalityProviderStats decrements the finality provider stats for the given provider pk hex // This method is idempotent, only the first call will be processed. Otherwise it will return a notFoundError for duplicates // Refer to the README.md in this directory for more information on the sharding logic -func (db *Database) SubtractFinalityProviderStats( +func (v1dbclient *V1Database) SubtractFinalityProviderStats( ctx context.Context, stakingTxHashHex, fpPkHex string, amount uint64, ) error { upsertUpdate := bson.M{ @@ -259,20 +261,20 @@ func (db *Database) SubtractFinalityProviderStats( "active_delegations": -1, }, } - return db.updateFinalityProviderStats(ctx, types.Unbonded.ToString(), stakingTxHashHex, fpPkHex, upsertUpdate) + return v1dbclient.updateFinalityProviderStats(ctx, types.Unbonded.ToString(), stakingTxHashHex, fpPkHex, upsertUpdate) } // FindFinalityProviderStats fetches the finality provider stats from the database -func (db *Database) FindFinalityProviderStats(ctx context.Context, paginationToken string) (*DbResultMap[*model.FinalityProviderStatsDocument], error) { - client := db.Client.Database(db.DbName).Collection(model.FinalityProviderStatsCollection) +func (v1dbclient *V1Database) FindFinalityProviderStats(ctx context.Context, paginationToken string) (*db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument], error) { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1FinalityProviderStatsCollection) options := options.Find().SetSort(bson.D{{Key: "active_tvl", Value: -1}}) // Sorting in descending order var filter bson.M // Decode the pagination token first if it exist if paginationToken != "" { - decodedToken, err := model.DecodePaginationToken[model.FinalityProviderStatsPagination](paginationToken) + decodedToken, err := dbmodel.DecodePaginationToken[v1dbmodel.FinalityProviderStatsPagination](paginationToken) if err != nil { - return nil, &InvalidPaginationTokenError{ + return nil, &db.InvalidPaginationTokenError{ Message: "Invalid pagination token", } } @@ -284,16 +286,16 @@ func (db *Database) FindFinalityProviderStats(ctx context.Context, paginationTok } } - return findWithPagination( - ctx, client, filter, options, db.cfg.MaxPaginationLimit, - model.BuildFinalityProviderStatsPaginationToken, + return db.FindWithPagination( + ctx, client, filter, options, v1dbclient.Cfg.MaxPaginationLimit, + v1dbmodel.BuildFinalityProviderStatsPaginationToken, ) } -func (db *Database) FindFinalityProviderStatsByFinalityProviderPkHex( +func (v1dbclient *V1Database) FindFinalityProviderStatsByFinalityProviderPkHex( ctx context.Context, finalityProviderPkHex []string, -) ([]*model.FinalityProviderStatsDocument, error) { - client := db.Client.Database(db.DbName).Collection(model.FinalityProviderStatsCollection) +) ([]*v1dbmodel.FinalityProviderStatsDocument, error) { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1FinalityProviderStatsCollection) filter := bson.M{"_id": bson.M{"$in": finalityProviderPkHex}} cursor, err := client.Find(ctx, filter) if err != nil { @@ -301,7 +303,7 @@ func (db *Database) FindFinalityProviderStatsByFinalityProviderPkHex( } defer cursor.Close(ctx) - var finalityProviders []*model.FinalityProviderStatsDocument + var finalityProviders []*v1dbmodel.FinalityProviderStatsDocument if err = cursor.All(ctx, &finalityProviders); err != nil { return nil, err } @@ -309,18 +311,18 @@ func (db *Database) FindFinalityProviderStatsByFinalityProviderPkHex( return finalityProviders, nil } -func (db *Database) updateFinalityProviderStats(ctx context.Context, state, stakingTxHashHex, fpPkHex string, upsertUpdate primitive.M) error { - client := db.Client.Database(db.DbName).Collection(model.FinalityProviderStatsCollection) +func (v1dbclient *V1Database) updateFinalityProviderStats(ctx context.Context, state, stakingTxHashHex, fpPkHex string, upsertUpdate primitive.M) error { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1FinalityProviderStatsCollection) // Start a session - session, sessionErr := db.Client.StartSession() + session, sessionErr := v1dbclient.Client.StartSession() if sessionErr != nil { return sessionErr } defer session.EndSession(ctx) transactionWork := func(sessCtx mongo.SessionContext) (interface{}, error) { - err := db.updateStatsLockByFieldName(sessCtx, stakingTxHashHex, state, "finality_provider_stats") + err := v1dbclient.updateStatsLockByFieldName(sessCtx, stakingTxHashHex, state, "finality_provider_stats") if err != nil { return nil, err } @@ -345,7 +347,7 @@ func (db *Database) updateFinalityProviderStats(ctx context.Context, state, stak // IncrementStakerStats increments the staker stats for the given staking tx hash // This method is idempotent, only the first call will be processed. Otherwise it will return a notFoundError for duplicates -func (db *Database) IncrementStakerStats( +func (v1dbclient *V1Database) IncrementStakerStats( ctx context.Context, stakingTxHashHex, stakerPkHex string, amount uint64, ) error { upsertUpdate := bson.M{ @@ -356,12 +358,12 @@ func (db *Database) IncrementStakerStats( "total_delegations": 1, }, } - return db.updateStakerStats(ctx, types.Active.ToString(), stakingTxHashHex, stakerPkHex, upsertUpdate) + return v1dbclient.updateStakerStats(ctx, types.Active.ToString(), stakingTxHashHex, stakerPkHex, upsertUpdate) } // SubtractStakerStats decrements the staker stats for the given staking tx hash // This method is idempotent, only the first call will be processed. Otherwise it will return a notFoundError for duplicates -func (db *Database) SubtractStakerStats( +func (v1dbclient *V1Database) SubtractStakerStats( ctx context.Context, stakingTxHashHex, stakerPkHex string, amount uint64, ) error { upsertUpdate := bson.M{ @@ -370,21 +372,21 @@ func (db *Database) SubtractStakerStats( "active_delegations": -1, }, } - return db.updateStakerStats(ctx, types.Unbonded.ToString(), stakingTxHashHex, stakerPkHex, upsertUpdate) + return v1dbclient.updateStakerStats(ctx, types.Unbonded.ToString(), stakingTxHashHex, stakerPkHex, upsertUpdate) } -func (db *Database) updateStakerStats(ctx context.Context, state, stakingTxHashHex, stakerPkHex string, upsertUpdate primitive.M) error { - client := db.Client.Database(db.DbName).Collection(model.StakerStatsCollection) +func (v1dbclient *V1Database) updateStakerStats(ctx context.Context, state, stakingTxHashHex, stakerPkHex string, upsertUpdate primitive.M) error { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1StakerStatsCollection) // Start a session - session, sessionErr := db.Client.StartSession() + session, sessionErr := v1dbclient.Client.StartSession() if sessionErr != nil { return sessionErr } defer session.EndSession(ctx) transactionWork := func(sessCtx mongo.SessionContext) (interface{}, error) { - err := db.updateStatsLockByFieldName(sessCtx, stakingTxHashHex, state, "staker_stats") + err := v1dbclient.updateStatsLockByFieldName(sessCtx, stakingTxHashHex, state, "staker_stats") if err != nil { return nil, err } @@ -403,16 +405,16 @@ func (db *Database) updateStakerStats(ctx context.Context, state, stakingTxHashH return txErr } -func (db *Database) FindTopStakersByTvl(ctx context.Context, paginationToken string) (*DbResultMap[*model.StakerStatsDocument], error) { - client := db.Client.Database(db.DbName).Collection(model.StakerStatsCollection) +func (v1dbclient *V1Database) FindTopStakersByTvl(ctx context.Context, paginationToken string) (*db.DbResultMap[*v1dbmodel.StakerStatsDocument], error) { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1StakerStatsCollection) opts := options.Find().SetSort(bson.D{{Key: "active_tvl", Value: -1}}) var filter bson.M // Decode the pagination token first if it exist if paginationToken != "" { - decodedToken, err := model.DecodePaginationToken[model.StakerStatsByStakerPagination](paginationToken) + decodedToken, err := dbmodel.DecodePaginationToken[v1dbmodel.StakerStatsByStakerPagination](paginationToken) if err != nil { - return nil, &InvalidPaginationTokenError{ + return nil, &db.InvalidPaginationTokenError{ Message: "Invalid pagination token", } } @@ -424,23 +426,23 @@ func (db *Database) FindTopStakersByTvl(ctx context.Context, paginationToken str } } - return findWithPagination( - ctx, client, filter, opts, db.cfg.MaxPaginationLimit, - model.BuildStakerStatsByStakerPaginationToken, + return db.FindWithPagination( + ctx, client, filter, opts, v1dbclient.Cfg.MaxPaginationLimit, + v1dbmodel.BuildStakerStatsByStakerPaginationToken, ) } -func (db *Database) GetStakerStats( +func (v1dbclient *V1Database) GetStakerStats( ctx context.Context, stakerPkHex string, -) (*model.StakerStatsDocument, error) { - client := db.Client.Database(db.DbName).Collection(model.StakerStatsCollection) +) (*v1dbmodel.StakerStatsDocument, error) { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1StakerStatsCollection) filter := bson.M{"_id": stakerPkHex} - var result model.StakerStatsDocument + var result v1dbmodel.StakerStatsDocument err := client.FindOne(ctx, filter).Decode(&result) if err != nil { // If the document is not found, return nil if errors.Is(err, mongo.ErrNoDocuments) { - return nil, &NotFoundError{ + return nil, &db.NotFoundError{ Key: stakerPkHex, Message: "Staker stats not found", } diff --git a/internal/v1/db/client/timelock.go b/internal/v1/db/client/timelock.go new file mode 100644 index 00000000..630acf65 --- /dev/null +++ b/internal/v1/db/client/timelock.go @@ -0,0 +1,28 @@ +package v1dbclient + +import ( + "context" + + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" +) + +func (v1dbclient *V1Database) SaveTimeLockExpireCheck( + ctx context.Context, stakingTxHashHex string, + expireHeight uint64, txType string, +) error { + client := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1TimeLockCollection) + document := v1dbmodel.NewTimeLockDocument(stakingTxHashHex, expireHeight, txType) + _, err := client.InsertOne(ctx, document) + if err != nil { + return err + } + return nil +} + +func (v1dbclient *V1Database) TransitionToUnbondedState( + ctx context.Context, stakingTxHashHex string, eligiblePreviousState []types.DelegationState, +) error { + return v1dbclient.transitionState(ctx, stakingTxHashHex, types.Unbonded.ToString(), eligiblePreviousState, nil) +} diff --git a/internal/db/unbonding.go b/internal/v1/db/client/unbonding.go similarity index 72% rename from internal/db/unbonding.go rename to internal/v1/db/client/unbonding.go index 3f37a162..8af8419b 100644 --- a/internal/db/unbonding.go +++ b/internal/v1/db/client/unbonding.go @@ -1,24 +1,26 @@ -package db +package v1dbclient import ( "context" "errors" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "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" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) -func (db *Database) SaveUnbondingTx( +func (v1dbclient *V1Database) SaveUnbondingTx( ctx context.Context, stakingTxHashHex, txHashHex, txHex, signatureHex string, ) error { - delegationClient := db.Client.Database(db.DbName).Collection(model.DelegationCollection) - unbondingClient := db.Client.Database(db.DbName).Collection(model.UnbondingCollection) + delegationClient := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1DelegationCollection) + unbondingClient := v1dbclient.Client.Database(v1dbclient.DbName).Collection(dbmodel.V1UnbondingCollection) // Start a session - session, err := db.Client.StartSession() + session, err := v1dbclient.Client.StartSession() if err != nil { return err } @@ -31,11 +33,11 @@ func (db *Database) SaveUnbondingTx( "_id": stakingTxHashHex, "state": types.Active, } - var delegationDocument model.DelegationDocument + var delegationDocument v1dbmodel.DelegationDocument err = delegationClient.FindOne(sessCtx, delegationFilter).Decode(&delegationDocument) if err != nil { if err == mongo.ErrNoDocuments { - return nil, &NotFoundError{ + return nil, &db.NotFoundError{ Key: stakingTxHashHex, Message: "no active delegation found for unbonding request", } @@ -50,18 +52,18 @@ func (db *Database) SaveUnbondingTx( } if result.MatchedCount == 0 { - return nil, &NotFoundError{ + return nil, &db.NotFoundError{ Key: stakingTxHashHex, Message: "delegation not found or not eligible for unbonding", } } // Insert the unbonding transaction document - unbondingDocument := model.UnbondingDocument{ + unbondingDocument := v1dbmodel.UnbondingDocument{ StakerPkHex: delegationDocument.StakerPkHex, FinalityPkHex: delegationDocument.FinalityProviderPkHex, UnbondingTxSigHex: signatureHex, - State: model.UnbondingInitialState, + State: v1dbmodel.UnbondingInitialState, UnbondingTxHashHex: txHashHex, UnbondingTxHex: txHex, StakingTxHex: delegationDocument.StakingTx.TxHex, @@ -76,7 +78,7 @@ func (db *Database) SaveUnbondingTx( if errors.As(err, &writeErr) { for _, e := range writeErr.WriteErrors { if mongo.IsDuplicateKeyError(e) { - return nil, &DuplicateKeyError{ + return nil, &db.DuplicateKeyError{ Key: txHashHex, Message: "unbonding transaction already exists", } @@ -100,11 +102,11 @@ func (db *Database) SaveUnbondingTx( // Change the state to `unbonding` and save the unbondingTx data // Return not found error if the stakingTxHashHex is not found or the existing state is not eligible for unbonding -func (db *Database) TransitionToUnbondingState( +func (v1dbclient *V1Database) TransitionToUnbondingState( ctx context.Context, txHashHex string, startHeight, timelock, outputIndex uint64, txHex string, startTimestamp int64, ) error { unbondingTxMap := make(map[string]interface{}) - unbondingTxMap["unbonding_tx"] = model.TimelockTransaction{ + unbondingTxMap["unbonding_tx"] = v1dbmodel.TimelockTransaction{ TxHex: txHex, OutputIndex: outputIndex, StartTimestamp: startTimestamp, @@ -112,7 +114,7 @@ func (db *Database) TransitionToUnbondingState( TimeLock: timelock, } - err := db.transitionState( + err := v1dbclient.transitionState( ctx, txHashHex, types.Unbonding.ToString(), utils.QualifiedStatesToUnbonding(), unbondingTxMap, ) diff --git a/internal/v1/db/client/withdraw.go b/internal/v1/db/client/withdraw.go new file mode 100644 index 00000000..344386bf --- /dev/null +++ b/internal/v1/db/client/withdraw.go @@ -0,0 +1,19 @@ +package v1dbclient + +import ( + "context" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" +) + +func (v1dbclient *V1Database) TransitionToWithdrawnState(ctx context.Context, txHashHex string) error { + err := v1dbclient.transitionState( + ctx, txHashHex, types.Withdrawn.ToString(), + utils.QualifiedStatesToWithdraw(), nil, + ) + if err != nil { + return err + } + return nil +} diff --git a/internal/db/model/btc_info.go b/internal/v1/db/model/btc_info.go similarity index 92% rename from internal/db/model/btc_info.go rename to internal/v1/db/model/btc_info.go index 05680a5e..ce86fa77 100644 --- a/internal/db/model/btc_info.go +++ b/internal/v1/db/model/btc_info.go @@ -1,4 +1,4 @@ -package model +package v1dbmodel const LatestBtcInfoId = "latest" diff --git a/internal/db/model/delegation.go b/internal/v1/db/model/delegation.go similarity index 85% rename from internal/db/model/delegation.go rename to internal/v1/db/model/delegation.go index febf839f..c24e3710 100644 --- a/internal/db/model/delegation.go +++ b/internal/v1/db/model/delegation.go @@ -1,7 +1,8 @@ -package model +package v1dbmodel import ( - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" ) type TimelockTransaction struct { @@ -33,7 +34,7 @@ func BuildDelegationByStakerPaginationToken(d DelegationDocument) (string, error StakingTxHashHex: d.StakingTxHashHex, StakingStartHeight: d.StakingTx.StartHeight, } - token, err := GetPaginationToken(page) + token, err := dbmodel.GetPaginationToken(page) if err != nil { return "", err } @@ -48,7 +49,7 @@ func BuildDelegationScanPaginationToken(d DelegationDocument) (string, error) { page := &DelegationScanPagination{ StakingTxHashHex: d.StakingTxHashHex, } - token, err := GetPaginationToken(page) + token, err := dbmodel.GetPaginationToken(page) if err != nil { return "", err } diff --git a/internal/db/model/stats.go b/internal/v1/db/model/stats.go similarity index 93% rename from internal/db/model/stats.go rename to internal/v1/db/model/stats.go index 936333de..465b939f 100644 --- a/internal/db/model/stats.go +++ b/internal/v1/db/model/stats.go @@ -1,4 +1,6 @@ -package model +package v1dbmodel + +import dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" // StatsLockDocument represents the document in the stats lock collection // It's used as a lock to prevent concurrent stats calculation for the same staking tx hash @@ -49,7 +51,7 @@ func BuildFinalityProviderStatsPaginationToken(d *FinalityProviderStatsDocument) ActiveTvl: d.ActiveTvl, FinalityProviderPkHex: d.FinalityProviderPkHex, } - token, err := GetPaginationToken(page) + token, err := dbmodel.GetPaginationToken(page) if err != nil { return "", err } @@ -76,7 +78,7 @@ func BuildStakerStatsByStakerPaginationToken(d *StakerStatsDocument) (string, er StakerPkHex: d.StakerPkHex, ActiveTvl: d.ActiveTvl, } - token, err := GetPaginationToken(page) + token, err := dbmodel.GetPaginationToken(page) if err != nil { return "", err } diff --git a/internal/db/model/timelock.go b/internal/v1/db/model/timelock.go similarity index 95% rename from internal/db/model/timelock.go rename to internal/v1/db/model/timelock.go index 2800b31e..97a4cf82 100644 --- a/internal/db/model/timelock.go +++ b/internal/v1/db/model/timelock.go @@ -1,4 +1,4 @@ -package model +package v1dbmodel type TimeLockDocument struct { StakingTxHashHex string `bson:"staking_tx_hash_hex"` diff --git a/internal/db/model/unbonding.go b/internal/v1/db/model/unbonding.go similarity index 97% rename from internal/db/model/unbonding.go rename to internal/v1/db/model/unbonding.go index 9ece15f9..7f4e68ab 100644 --- a/internal/db/model/unbonding.go +++ b/internal/v1/db/model/unbonding.go @@ -1,4 +1,4 @@ -package model +package v1dbmodel const ( UnbondingInitialState = "INSERTED" diff --git a/internal/v1/queue/client/client.go b/internal/v1/queue/client/client.go new file mode 100644 index 00000000..2618bfc5 --- /dev/null +++ b/internal/v1/queue/client/client.go @@ -0,0 +1,63 @@ +package v1queueclient + +import ( + queueclient "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/client" + v1queuehandler "github.com/babylonlabs-io/staking-api-service/internal/v1/queue/handler" + client "github.com/babylonlabs-io/staking-queue-client/client" + queueConfig "github.com/babylonlabs-io/staking-queue-client/config" + "github.com/rs/zerolog/log" +) + +type V1QueueClient struct { + *queueclient.Queue + Handler *v1queuehandler.V1QueueHandler + ActiveStakingQueueClient client.QueueClient + ExpiredStakingQueueClient client.QueueClient + UnbondingStakingQueueClient client.QueueClient + WithdrawStakingQueueClient client.QueueClient + BtcInfoQueueClient client.QueueClient +} + +func New(cfg *queueConfig.QueueConfig, handler *v1queuehandler.V1QueueHandler, queueClient *queueclient.Queue) *V1QueueClient { + activeStakingQueueClient, err := client.NewQueueClient( + cfg, client.ActiveStakingQueueName, + ) + if err != nil { + log.Fatal().Err(err).Msg("error while creating ActiveStakingQueueClient") + } + + expiredStakingQueueClient, err := client.NewQueueClient( + cfg, client.ExpiredStakingQueueName, + ) + if err != nil { + log.Fatal().Err(err).Msg("error while creating ExpiredStakingQueueClient") + } + + unbondingStakingQueueClient, err := client.NewQueueClient( + cfg, client.UnbondingStakingQueueName, + ) + if err != nil { + log.Fatal().Err(err).Msg("error while creating UnbondingStakingQueueClient") + } + withdrawStakingQueueClient, err := client.NewQueueClient( + cfg, client.WithdrawStakingQueueName, + ) + if err != nil { + log.Fatal().Err(err).Msg("error while creating WithdrawStakingQueueClient") + } + btcInfoQueueClient, err := client.NewQueueClient( + cfg, client.BtcInfoQueueName, + ) + if err != nil { + log.Fatal().Err(err).Msg("error while creating BtcInfoQueueClient") + } + return &V1QueueClient{ + Queue: queueClient, + Handler: handler, + ActiveStakingQueueClient: activeStakingQueueClient, + ExpiredStakingQueueClient: expiredStakingQueueClient, + UnbondingStakingQueueClient: unbondingStakingQueueClient, + WithdrawStakingQueueClient: withdrawStakingQueueClient, + BtcInfoQueueClient: btcInfoQueueClient, + } +} diff --git a/internal/v1/queue/client/health.go b/internal/v1/queue/client/health.go new file mode 100644 index 00000000..eb1d5399 --- /dev/null +++ b/internal/v1/queue/client/health.go @@ -0,0 +1,35 @@ +package v1queueclient + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/babylonlabs-io/staking-queue-client/client" +) + +func (q *V1QueueClient) IsConnectionHealthy() error { + var errorMessages []string + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + checkQueue := func(name string, client client.QueueClient) { + if err := client.Ping(ctx); err != nil { + errorMessages = append(errorMessages, fmt.Sprintf("%s is not healthy: %v", name, err)) + } + } + + checkQueue("ActiveStakingQueueClient", q.ActiveStakingQueueClient) + checkQueue("ExpiredStakingQueueClient", q.ExpiredStakingQueueClient) + checkQueue("UnbondingStakingQueueClient", q.UnbondingStakingQueueClient) + checkQueue("WithdrawStakingQueueClient", q.WithdrawStakingQueueClient) + checkQueue("StatsQueueClient", q.StatsQueueClient) + checkQueue("BtcInfoQueueClient", q.BtcInfoQueueClient) + + if len(errorMessages) > 0 { + return fmt.Errorf(strings.Join(errorMessages, "; ")) + } + return nil +} diff --git a/internal/v1/queue/client/message.go b/internal/v1/queue/client/message.go new file mode 100644 index 00000000..a65c1ae7 --- /dev/null +++ b/internal/v1/queue/client/message.go @@ -0,0 +1,88 @@ +package v1queueclient + +import ( + queueclient "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/client" + "github.com/rs/zerolog/log" +) + +func (q *V1QueueClient) StartReceivingMessages() { + log.Printf("Starting to receive messages from v1 queues") + // start processing messages from the active staking queue + queueclient.StartQueueMessageProcessing( + q.ActiveStakingQueueClient, + q.Handler.ActiveStakingHandler, q.Handler.HandleUnprocessedMessage, + q.MaxRetryAttempts, q.ProcessingTimeout, + ) + log.Printf("Starting to receive messages from expired staking queue") + queueclient.StartQueueMessageProcessing( + q.ExpiredStakingQueueClient, + q.Handler.ExpiredStakingHandler, q.Handler.HandleUnprocessedMessage, + q.MaxRetryAttempts, q.ProcessingTimeout, + ) + log.Printf("Starting to receive messages from unbonding staking queue") + queueclient.StartQueueMessageProcessing( + q.UnbondingStakingQueueClient, + q.Handler.UnbondingStakingHandler, q.Handler.HandleUnprocessedMessage, + q.MaxRetryAttempts, q.ProcessingTimeout, + ) + log.Printf("Starting to receive messages from withdraw staking queue") + queueclient.StartQueueMessageProcessing( + q.WithdrawStakingQueueClient, + q.Handler.WithdrawStakingHandler, q.Handler.HandleUnprocessedMessage, + q.MaxRetryAttempts, q.ProcessingTimeout, + ) + log.Printf("Starting to receive messages from stats queue") + queueclient.StartQueueMessageProcessing( + q.StatsQueueClient, + q.Handler.StatsHandler, q.Handler.HandleUnprocessedMessage, + q.MaxRetryAttempts, q.ProcessingTimeout, + ) + log.Printf("Starting to receive messages from btc info queue") + queueclient.StartQueueMessageProcessing( + q.BtcInfoQueueClient, + q.Handler.BtcInfoHandler, q.Handler.HandleUnprocessedMessage, + q.MaxRetryAttempts, q.ProcessingTimeout, + ) + // ...add more queues here +} + +// Turn off all message processing +func (q *V1QueueClient) StopReceivingMessages() { + activeQueueErr := q.ActiveStakingQueueClient.Stop() + if activeQueueErr != nil { + log.Error().Err(activeQueueErr). + Str("queueName", q.ActiveStakingQueueClient.GetQueueName()). + Msg("error while stopping queue") + } + expiredQueueErr := q.ExpiredStakingQueueClient.Stop() + if expiredQueueErr != nil { + log.Error().Err(expiredQueueErr). + Str("queueName", q.ExpiredStakingQueueClient.GetQueueName()). + Msg("error while stopping queue") + } + unbondingQueueErr := q.UnbondingStakingQueueClient.Stop() + if unbondingQueueErr != nil { + log.Error().Err(unbondingQueueErr). + Str("queueName", q.UnbondingStakingQueueClient.GetQueueName()). + Msg("error while stopping queue") + } + withdrawnQueueErr := q.WithdrawStakingQueueClient.Stop() + if withdrawnQueueErr != nil { + log.Error().Err(withdrawnQueueErr). + Str("queueName", q.WithdrawStakingQueueClient.GetQueueName()). + Msg("error while stopping queue") + } + statsQueueErr := q.StatsQueueClient.Stop() + if statsQueueErr != nil { + log.Error().Err(statsQueueErr). + Str("queueName", q.StatsQueueClient.GetQueueName()). + Msg("error while stopping queue") + } + btcInfoQueueErr := q.BtcInfoQueueClient.Stop() + if btcInfoQueueErr != nil { + log.Error().Err(btcInfoQueueErr). + Str("queueName", q.BtcInfoQueueClient.GetQueueName()). + Msg("error while stopping queue") + } + // ...add more queues here +} diff --git a/internal/queue/handlers/active_staking.go b/internal/v1/queue/handler/active_staking.go similarity index 86% rename from internal/queue/handlers/active_staking.go rename to internal/v1/queue/handler/active_staking.go index d50845a1..373eb397 100644 --- a/internal/queue/handlers/active_staking.go +++ b/internal/v1/queue/handler/active_staking.go @@ -1,11 +1,11 @@ -package handlers +package v1queuehandler import ( "context" "encoding/json" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" queueClient "github.com/babylonlabs-io/staking-queue-client/client" "github.com/rs/zerolog/log" ) @@ -13,7 +13,7 @@ import ( // ActiveStakingHandler handles the active staking event // This handler is designed to be idempotent, capable of handling duplicate messages gracefully. // It can also resume from the next step if a previous step fails, ensuring robustness in the event processing workflow. -func (h *QueueHandler) ActiveStakingHandler(ctx context.Context, messageBody string) *types.Error { +func (h *V1QueueHandler) ActiveStakingHandler(ctx context.Context, messageBody string) *types.Error { // Parse the message body into ActiveStakingEvent var activeStakingEvent queueClient.ActiveStakingEvent err := json.Unmarshal([]byte(messageBody), &activeStakingEvent) @@ -23,7 +23,7 @@ func (h *QueueHandler) ActiveStakingHandler(ctx context.Context, messageBody str } // Check if delegation already exists - exist, delError := h.Services.IsDelegationPresent(ctx, activeStakingEvent.StakingTxHashHex) + exist, delError := h.Service.IsDelegationPresent(ctx, activeStakingEvent.StakingTxHashHex) if delError != nil { return delError } @@ -49,7 +49,7 @@ func (h *QueueHandler) ActiveStakingHandler(ctx context.Context, messageBody str } // Perform the async timelock expire check - expireCheckError := h.Services.ProcessExpireCheck( + expireCheckError := h.Service.ProcessExpireCheck( ctx, activeStakingEvent.StakingTxHashHex, activeStakingEvent.StakingStartHeight, activeStakingEvent.StakingTimeLock, @@ -61,7 +61,7 @@ func (h *QueueHandler) ActiveStakingHandler(ctx context.Context, messageBody str // Save the active staking delegation. This is the final step in the active staking event processing // Please refer to the README.md for the details on the active staking event processing workflow - saveErr := h.Services.SaveActiveStakingDelegation( + saveErr := h.Service.SaveActiveStakingDelegation( ctx, activeStakingEvent.StakingTxHashHex, activeStakingEvent.StakerPkHex, activeStakingEvent.FinalityProviderPkHex, activeStakingEvent.StakingValue, activeStakingEvent.StakingStartHeight, activeStakingEvent.StakingStartTimestamp, diff --git a/internal/queue/handlers/btc_info.go b/internal/v1/queue/handler/btc_info.go similarity index 73% rename from internal/queue/handlers/btc_info.go rename to internal/v1/queue/handler/btc_info.go index 7aa053e1..a6bc9de3 100644 --- a/internal/queue/handlers/btc_info.go +++ b/internal/v1/queue/handler/btc_info.go @@ -1,16 +1,16 @@ -package handlers +package v1queuehandler import ( "context" "encoding/json" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" queueClient "github.com/babylonlabs-io/staking-queue-client/client" "github.com/rs/zerolog/log" ) -func (h *QueueHandler) BtcInfoHandler(ctx context.Context, messageBody string) *types.Error { +func (h *V1QueueHandler) BtcInfoHandler(ctx context.Context, messageBody string) *types.Error { var btcInfo queueClient.BtcInfoEvent err := json.Unmarshal([]byte(messageBody), &btcInfo) if err != nil { @@ -18,7 +18,7 @@ func (h *QueueHandler) BtcInfoHandler(ctx context.Context, messageBody string) * return types.NewError(http.StatusBadRequest, types.BadRequest, err) } - statsErr := h.Services.ProcessBtcInfoStats( + statsErr := h.Service.ProcessBtcInfoStats( ctx, btcInfo.Height, btcInfo.ConfirmedTvl, btcInfo.UnconfirmedTvl, ) if statsErr != nil { diff --git a/internal/queue/handlers/expired_staking.go b/internal/v1/queue/handler/expired_staking.go similarity index 75% rename from internal/queue/handlers/expired_staking.go rename to internal/v1/queue/handler/expired_staking.go index f1a681f7..af1fd1ee 100644 --- a/internal/queue/handlers/expired_staking.go +++ b/internal/v1/queue/handler/expired_staking.go @@ -1,4 +1,4 @@ -package handlers +package v1queuehandler import ( "context" @@ -8,11 +8,11 @@ import ( queueClient "github.com/babylonlabs-io/staking-queue-client/client" "github.com/rs/zerolog/log" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" ) -func (h *QueueHandler) ExpiredStakingHandler(ctx context.Context, messageBody string) *types.Error { +func (h *V1QueueHandler) ExpiredStakingHandler(ctx context.Context, messageBody string) *types.Error { var expiredStakingEvent queueClient.ExpiredStakingEvent err := json.Unmarshal([]byte(messageBody), &expiredStakingEvent) if err != nil { @@ -21,7 +21,7 @@ func (h *QueueHandler) ExpiredStakingHandler(ctx context.Context, messageBody st } // Check if the delegation is in the right state to process the unbonded(timelock expire) event - del, delErr := h.Services.GetDelegation(ctx, expiredStakingEvent.StakingTxHashHex) + del, delErr := h.Service.GetDelegation(ctx, expiredStakingEvent.StakingTxHashHex) // Requeue if found any error. Including not found error if delErr != nil { return delErr @@ -39,7 +39,7 @@ func (h *QueueHandler) ExpiredStakingHandler(ctx context.Context, messageBody st return types.NewError(http.StatusBadRequest, types.BadRequest, err) } - transitionErr := h.Services.TransitionToUnbondedState(ctx, txType, expiredStakingEvent.StakingTxHashHex) + transitionErr := h.Service.TransitionToUnbondedState(ctx, txType, expiredStakingEvent.StakingTxHashHex) if transitionErr != nil { return transitionErr } diff --git a/internal/v1/queue/handler/handler.go b/internal/v1/queue/handler/handler.go new file mode 100644 index 00000000..33273c54 --- /dev/null +++ b/internal/v1/queue/handler/handler.go @@ -0,0 +1,25 @@ +package v1queuehandler + +import ( + "context" + + queuehandler "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" +) + +type V1QueueHandler struct { + *queuehandler.QueueHandler + Service v1service.V1ServiceProvider +} + +func New(queueHandler *queuehandler.QueueHandler, service v1service.V1ServiceProvider) *V1QueueHandler { + return &V1QueueHandler{ + QueueHandler: queueHandler, + Service: service, + } +} + +func (qh *V1QueueHandler) HandleUnprocessedMessage(ctx context.Context, messageBody, receipt string) *types.Error { + return qh.Service.SaveUnprocessableMessages(ctx, messageBody, receipt) +} diff --git a/internal/queue/handlers/stats.go b/internal/v1/queue/handler/stats.go similarity index 84% rename from internal/queue/handlers/stats.go rename to internal/v1/queue/handler/stats.go index f14e8c74..690e1b72 100644 --- a/internal/queue/handlers/stats.go +++ b/internal/v1/queue/handler/stats.go @@ -1,11 +1,11 @@ -package handlers +package v1queuehandler import ( "context" "encoding/json" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" queueClient "github.com/babylonlabs-io/staking-queue-client/client" "github.com/rs/zerolog/log" ) @@ -20,7 +20,7 @@ import ( // // If any step fails, it logs the error and returns a corresponding error response. // which will be sent back to the message queue for later retry -func (h *QueueHandler) StatsHandler(ctx context.Context, messageBody string) *types.Error { +func (h *V1QueueHandler) StatsHandler(ctx context.Context, messageBody string) *types.Error { var statsEvent queueClient.StatsEvent err := json.Unmarshal([]byte(messageBody), &statsEvent) if err != nil { @@ -40,7 +40,7 @@ func (h *QueueHandler) StatsHandler(ctx context.Context, messageBody string) *ty isOverflow := statsEvent.IsOverflow if statsEvent.SchemaVersion < 1 { // Look up the overflow status from the database - overflow, overflowErr := h.Services.GetDelegation(ctx, statsEvent.GetStakingTxHashHex()) + overflow, overflowErr := h.Service.GetDelegation(ctx, statsEvent.GetStakingTxHashHex()) if overflowErr != nil { log.Ctx(ctx).Error().Err(overflowErr).Msg("Failed to get overflow status") return overflowErr @@ -57,7 +57,7 @@ func (h *QueueHandler) StatsHandler(ctx context.Context, messageBody string) *ty // Perform the stats calculation only if the event is not an overflow event if !isOverflow { // Perform the stats calculation - statsErr := h.Services.ProcessStakingStatsCalculation( + statsErr := h.Service.ProcessStakingStatsCalculation( ctx, statsEvent.StakingTxHashHex, statsEvent.StakerPkHex, statsEvent.FinalityProviderPkHex, @@ -75,11 +75,11 @@ func (h *QueueHandler) StatsHandler(ctx context.Context, messageBody string) *ty // Convert the staker's public key into corresponding BTC addresses for // database lookup. This is performed only for active delegation events to // prevent duplicated database writes. -func (h *QueueHandler) performAddressLookupConversion(ctx context.Context, stakerPkHex string, state types.DelegationState) *types.Error { +func (h *V1QueueHandler) performAddressLookupConversion(ctx context.Context, stakerPkHex string, state types.DelegationState) *types.Error { // Perform the address lookup conversion only for active delegation events // to prevent duplicated database writes if state == types.Active { - addErr := h.Services.ProcessAndSaveBtcAddresses(ctx, stakerPkHex) + addErr := h.Service.ProcessAndSaveBtcAddresses(ctx, stakerPkHex) if addErr != nil { log.Ctx(ctx).Error().Err(addErr).Msg("Failed to process and save btc addresses") return addErr diff --git a/internal/queue/handlers/unbonding.go b/internal/v1/queue/handler/unbonding.go similarity index 84% rename from internal/queue/handlers/unbonding.go rename to internal/v1/queue/handler/unbonding.go index 517bb5c3..6e6225f5 100644 --- a/internal/queue/handlers/unbonding.go +++ b/internal/v1/queue/handler/unbonding.go @@ -1,17 +1,17 @@ -package handlers +package v1queuehandler import ( "context" "encoding/json" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" queueClient "github.com/babylonlabs-io/staking-queue-client/client" "github.com/rs/zerolog/log" ) -func (h *QueueHandler) UnbondingStakingHandler(ctx context.Context, messageBody string) *types.Error { +func (h *V1QueueHandler) UnbondingStakingHandler(ctx context.Context, messageBody string) *types.Error { var unbondingStakingEvent queueClient.UnbondingStakingEvent err := json.Unmarshal([]byte(messageBody), &unbondingStakingEvent) if err != nil { @@ -20,7 +20,7 @@ func (h *QueueHandler) UnbondingStakingHandler(ctx context.Context, messageBody } // Check if the delegation is in the right state to process the unbonding event - del, delErr := h.Services.GetDelegation(ctx, unbondingStakingEvent.StakingTxHashHex) + del, delErr := h.Service.GetDelegation(ctx, unbondingStakingEvent.StakingTxHashHex) // Requeue if found any error. Including not found error if delErr != nil { return delErr @@ -33,7 +33,7 @@ func (h *QueueHandler) UnbondingStakingHandler(ctx context.Context, messageBody return nil } - expireCheckErr := h.Services.ProcessExpireCheck( + expireCheckErr := h.Service.ProcessExpireCheck( ctx, unbondingStakingEvent.StakingTxHashHex, unbondingStakingEvent.UnbondingStartHeight, unbondingStakingEvent.UnbondingTimeLock, @@ -62,7 +62,7 @@ func (h *QueueHandler) UnbondingStakingHandler(ctx context.Context, messageBody // Save the unbonding staking delegation. This is the final step in the unbonding staking event processing // Please refer to the README.md for the details on the unbonding staking event processing workflow - transitionErr := h.Services.TransitionToUnbondingState( + transitionErr := h.Service.TransitionToUnbondingState( ctx, unbondingStakingEvent.StakingTxHashHex, unbondingStakingEvent.UnbondingStartHeight, unbondingStakingEvent.UnbondingTimeLock, unbondingStakingEvent.UnbondingOutputIndex, unbondingStakingEvent.UnbondingTxHex, unbondingStakingEvent.UnbondingStartTimestamp, diff --git a/internal/queue/handlers/withdraw.go b/internal/v1/queue/handler/withdraw.go similarity index 81% rename from internal/queue/handlers/withdraw.go rename to internal/v1/queue/handler/withdraw.go index 195c2fbb..ed15a765 100644 --- a/internal/queue/handlers/withdraw.go +++ b/internal/v1/queue/handler/withdraw.go @@ -1,17 +1,17 @@ -package handlers +package v1queuehandler import ( "context" "encoding/json" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" queueClient "github.com/babylonlabs-io/staking-queue-client/client" "github.com/rs/zerolog/log" ) -func (h *QueueHandler) WithdrawStakingHandler(ctx context.Context, messageBody string) *types.Error { +func (h *V1QueueHandler) WithdrawStakingHandler(ctx context.Context, messageBody string) *types.Error { var withdrawnStakingEvent queueClient.WithdrawStakingEvent err := json.Unmarshal([]byte(messageBody), &withdrawnStakingEvent) if err != nil { @@ -20,7 +20,7 @@ func (h *QueueHandler) WithdrawStakingHandler(ctx context.Context, messageBody s } // Check if the delegation is in the right state to process the withdrawn event. - del, delErr := h.Services.GetDelegation(ctx, withdrawnStakingEvent.StakingTxHashHex) + del, delErr := h.Service.GetDelegation(ctx, withdrawnStakingEvent.StakingTxHashHex) // Requeue if found any error. Including not found error if delErr != nil { return delErr @@ -46,7 +46,7 @@ func (h *QueueHandler) WithdrawStakingHandler(ctx context.Context, messageBody s // Transition to withdrawn state // Please refer to the README.md for the details on the event processing workflow - transitionErr := h.Services.TransitionToWithdrawnState( + transitionErr := h.Service.TransitionToWithdrawnState( ctx, withdrawnStakingEvent.StakingTxHashHex, ) if transitionErr != nil { diff --git a/internal/services/delegation.go b/internal/v1/service/delegation.go similarity index 76% rename from internal/services/delegation.go rename to internal/v1/service/delegation.go index 278732cd..986cfd0e 100644 --- a/internal/services/delegation.go +++ b/internal/v1/service/delegation.go @@ -1,13 +1,14 @@ -package services +package v1service import ( "context" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "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" + v1dbclient "github.com/babylonlabs-io/staking-api-service/internal/v1/db/client" + v1model "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" "github.com/rs/zerolog/log" ) @@ -30,7 +31,7 @@ type DelegationPublic struct { IsOverflow bool `json:"is_overflow"` } -func FromDelegationDocument(d *model.DelegationDocument) DelegationPublic { +func FromDelegationDocument(d *v1model.DelegationDocument) DelegationPublic { delPublic := DelegationPublic{ StakingTxHashHex: d.StakingTxHashHex, StakerPkHex: d.StakerPkHex, @@ -60,18 +61,18 @@ func FromDelegationDocument(d *model.DelegationDocument) DelegationPublic { return delPublic } -func (s *Services) DelegationsByStakerPk( +func (s *V1Service) DelegationsByStakerPk( ctx context.Context, stakerPk string, state types.DelegationState, pageToken string, ) ([]DelegationPublic, string, *types.Error) { - filter := &db.DelegationFilter{} + filter := &v1dbclient.DelegationFilter{} if state != "" { - filter = &db.DelegationFilter{ + filter = &v1dbclient.DelegationFilter{ States: []types.DelegationState{state}, } } - resultMap, err := s.DbClient.FindDelegationsByStakerPk(ctx, stakerPk, filter, pageToken) + resultMap, err := s.Service.DbClients.V1DBClient.FindDelegationsByStakerPk(ctx, stakerPk, filter, pageToken) if err != nil { if db.IsInvalidPaginationTokenError(err) { log.Ctx(ctx).Warn().Err(err).Msg("Invalid pagination token when fetching delegations by staker pk") @@ -88,12 +89,12 @@ func (s *Services) DelegationsByStakerPk( } // SaveActiveStakingDelegation saves the active staking delegation to the database. -func (s *Services) SaveActiveStakingDelegation( +func (s *V1Service) SaveActiveStakingDelegation( ctx context.Context, txHashHex, stakerPkHex, finalityProviderPkHex string, value, startHeight uint64, stakingTimestamp int64, timeLock, stakingOutputIndex uint64, stakingTxHex string, isOverflow bool, ) *types.Error { - err := s.DbClient.SaveActiveStakingDelegation( + err := s.Service.DbClients.V1DBClient.SaveActiveStakingDelegation( ctx, txHashHex, stakerPkHex, finalityProviderPkHex, stakingTxHex, value, startHeight, timeLock, stakingOutputIndex, stakingTimestamp, isOverflow, ) @@ -108,8 +109,8 @@ func (s *Services) SaveActiveStakingDelegation( return nil } -func (s *Services) IsDelegationPresent(ctx context.Context, txHashHex string) (bool, *types.Error) { - delegation, err := s.DbClient.FindDelegationByTxHashHex(ctx, txHashHex) +func (s *V1Service) IsDelegationPresent(ctx context.Context, txHashHex string) (bool, *types.Error) { + delegation, err := s.Service.DbClients.V1DBClient.FindDelegationByTxHashHex(ctx, txHashHex) if err != nil { if db.IsNotFoundError(err) { return false, nil @@ -124,8 +125,8 @@ func (s *Services) IsDelegationPresent(ctx context.Context, txHashHex string) (b return false, nil } -func (s *Services) GetDelegation(ctx context.Context, txHashHex string) (*model.DelegationDocument, *types.Error) { - delegation, err := s.DbClient.FindDelegationByTxHashHex(ctx, txHashHex) +func (s *V1Service) GetDelegation(ctx context.Context, txHashHex string) (*v1model.DelegationDocument, *types.Error) { + delegation, err := s.Service.DbClients.V1DBClient.FindDelegationByTxHashHex(ctx, txHashHex) if err != nil { if db.IsNotFoundError(err) { log.Ctx(ctx).Warn().Err(err).Str("stakingTxHash", txHashHex).Msg("Staking delegation not found") @@ -137,14 +138,14 @@ func (s *Services) GetDelegation(ctx context.Context, txHashHex string) (*model. return delegation, nil } -func (s *Services) CheckStakerHasActiveDelegationByPk( +func (s *V1Service) CheckStakerHasActiveDelegationByPk( ctx context.Context, stakerPk string, afterTimestamp int64, ) (bool, *types.Error) { - filter := &db.DelegationFilter{ + filter := &v1dbclient.DelegationFilter{ States: []types.DelegationState{types.Active}, AfterTimestamp: afterTimestamp, } - hasDelegation, err := s.DbClient.CheckDelegationExistByStakerPk( + hasDelegation, err := s.Service.DbClients.V1DBClient.CheckDelegationExistByStakerPk( ctx, stakerPk, filter, ) if err != nil { diff --git a/internal/services/finality_provider.go b/internal/v1/service/finality_provider.go similarity index 88% rename from internal/services/finality_provider.go rename to internal/v1/service/finality_provider.go index 292287b8..dde5a220 100644 --- a/internal/services/finality_provider.go +++ b/internal/v1/service/finality_provider.go @@ -1,12 +1,12 @@ -package services +package v1service import ( "context" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/db" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1model "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" "github.com/rs/zerolog/log" ) @@ -44,9 +44,9 @@ type FpParamsPublic struct { // GetFinalityProvidersFromGlobalParams returns the finality providers from the global params. // Those FP are treated as "active" finality providers. -func (s *Services) GetFinalityProvidersFromGlobalParams() []*FpParamsPublic { +func (s *V1Service) GetFinalityProvidersFromGlobalParams() []*FpParamsPublic { var fpDetails []*FpParamsPublic - for _, finalityProvider := range s.finalityProviders { + for _, finalityProvider := range s.Service.FinalityProviders { description := &FpDescriptionPublic{ Moniker: finalityProvider.Description.Moniker, Identity: finalityProvider.Description.Identity, @@ -63,11 +63,11 @@ func (s *Services) GetFinalityProvidersFromGlobalParams() []*FpParamsPublic { return fpDetails } -func (s *Services) GetFinalityProvider( +func (s *V1Service) GetFinalityProvider( ctx context.Context, fpPkHex string, ) (*FpDetailsPublic, *types.Error) { fpStatsByPks, err := - s.DbClient.FindFinalityProviderStatsByFinalityProviderPkHex( + s.Service.DbClients.V1DBClient.FindFinalityProviderStatsByFinalityProviderPkHex( ctx, []string{fpPkHex}, ) if err != nil { @@ -132,7 +132,7 @@ func (s *Services) GetFinalityProvider( }, nil } -func (s *Services) GetFinalityProviders(ctx context.Context, page string) ([]*FpDetailsPublic, string, *types.Error) { +func (s *V1Service) GetFinalityProviders(ctx context.Context, page string) ([]*FpDetailsPublic, string, *types.Error) { fpParams := s.GetFinalityProvidersFromGlobalParams() if len(fpParams) == 0 { log.Ctx(ctx).Error().Msg("No finality providers found from global params") @@ -144,7 +144,7 @@ func (s *Services) GetFinalityProviders(ctx context.Context, page string) ([]*Fp fpParamsMap[fp.BtcPk] = fp } - resultMap, err := s.DbClient.FindFinalityProviderStats(ctx, page) + resultMap, err := s.Service.DbClients.V1DBClient.FindFinalityProviderStats(ctx, page) if err != nil { if db.IsInvalidPaginationTokenError(err) { log.Ctx(ctx).Warn().Err(err).Msg("Invalid pagination token when fetching finality providers") @@ -194,7 +194,7 @@ func (s *Services) GetFinalityProviders(ctx context.Context, page string) ([]*Fp } // If there are no more pages to fetch, make sure all the finality providers from global params are included if resultMap.PaginationToken == "" { - fpsNotInUse, err := s.findRegisteredFinalityProvidersNotInUse(ctx, fpParams) + fpsNotInUse, err := s.FindRegisteredFinalityProvidersNotInUse(ctx, fpParams) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("Error while fetching finality providers not in use") return nil, "", types.NewError(http.StatusInternalServerError, types.InternalServiceError, err) @@ -206,18 +206,18 @@ func (s *Services) GetFinalityProviders(ctx context.Context, page string) ([]*Fp return finalityProviderDetailsPublic, resultMap.PaginationToken, nil } -func (s *Services) findRegisteredFinalityProvidersNotInUse( +func (s *V1Service) FindRegisteredFinalityProvidersNotInUse( ctx context.Context, fpParams []*FpParamsPublic, ) ([]*FpDetailsPublic, error) { var finalityProvidersPkHex []string for _, fp := range fpParams { finalityProvidersPkHex = append(finalityProvidersPkHex, fp.BtcPk) } - fpStatsByPks, err := s.DbClient.FindFinalityProviderStatsByFinalityProviderPkHex(ctx, finalityProvidersPkHex) + fpStatsByPks, err := s.Service.DbClients.V1DBClient.FindFinalityProviderStatsByFinalityProviderPkHex(ctx, finalityProvidersPkHex) if err != nil { return nil, err } - fpStatsByPksMap := make(map[string]*model.FinalityProviderStatsDocument) + fpStatsByPksMap := make(map[string]*v1model.FinalityProviderStatsDocument) for _, fpStat := range fpStatsByPks { fpStatsByPksMap[fpStat.FinalityProviderPkHex] = fpStat } diff --git a/internal/v1/service/interface.go b/internal/v1/service/interface.go new file mode 100644 index 00000000..c80b115c --- /dev/null +++ b/internal/v1/service/interface.go @@ -0,0 +1,43 @@ +package v1service + +import ( + "context" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/services/service" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1model "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" +) + +type V1ServiceProvider interface { + service.SharedServiceProvider + // Delegation + DelegationsByStakerPk(ctx context.Context, stakerPk string, state 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) (*v1model.DelegationDocument, *types.Error) + CheckStakerHasActiveDelegationByPk(ctx context.Context, stakerPkHex string, afterTimestamp int64) (bool, *types.Error) + TransitionToUnbondingState(ctx context.Context, txHashHex string, startHeight, timelock, outputIndex uint64, txHex string, startTimestamp int64) *types.Error + TransitionToWithdrawnState(ctx context.Context, txHashHex string) *types.Error + UnbondDelegation(ctx context.Context, stakingTxHashHex, unbondingTxHashHex, unbondingTxHex, signatureHex string) *types.Error + IsEligibleForUnbondingRequest(ctx context.Context, stakingTxHashHex string) *types.Error + // Finality Provider + GetFinalityProvidersFromGlobalParams() []*FpParamsPublic + GetFinalityProvider(ctx context.Context, finalityProviderPkHex string) (*FpDetailsPublic, *types.Error) + GetFinalityProviders(ctx context.Context, pageToken string) ([]*FpDetailsPublic, string, *types.Error) + FindRegisteredFinalityProvidersNotInUse(ctx context.Context, fpParams []*FpParamsPublic) ([]*FpDetailsPublic, error) + // Global Params + GetGlobalParamsPublic() *GlobalParamsPublic + GetVersionedGlobalParamsByHeight(height uint64) *types.VersionedGlobalParams + // Staker + ProcessAndSaveBtcAddresses(ctx context.Context, stakerPkHex string) *types.Error + GetStakerPublicKeysByAddresses(ctx context.Context, addresses []string) (map[string]string, *types.Error) + // Stats + ProcessStakingStatsCalculation(ctx context.Context, stakingTxHashHex, stakerPkHex, fpPkHex string, state types.DelegationState, amount uint64) *types.Error + GetOverallStats(ctx context.Context) (*OverallStatsPublic, *types.Error) + GetStakerStats(ctx context.Context, stakerPkHex string) (*StakerStatsPublic, *types.Error) + GetTopStakersByActiveTvl(ctx context.Context, pageToken string) ([]StakerStatsPublic, string, *types.Error) + ProcessBtcInfoStats(ctx context.Context, btcHeight uint64, confirmedTvl uint64, unconfirmedTvl uint64) *types.Error + // Timelock + ProcessExpireCheck(ctx context.Context, stakingTxHashHex string, startHeight, timelock uint64, txType types.StakingTxType) *types.Error + TransitionToUnbondedState(ctx context.Context, stakingType types.StakingTxType, stakingTxHashHex string) *types.Error +} diff --git a/internal/services/params.go b/internal/v1/service/params.go similarity index 83% rename from internal/services/params.go rename to internal/v1/service/params.go index 81dac3df..a3d45173 100644 --- a/internal/services/params.go +++ b/internal/v1/service/params.go @@ -1,7 +1,7 @@ -package services +package v1service import ( - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" ) type VersionedGlobalParamsPublic struct { @@ -25,9 +25,9 @@ type GlobalParamsPublic struct { Versions []VersionedGlobalParamsPublic `json:"versions"` } -func (s *Services) GetGlobalParamsPublic() *GlobalParamsPublic { +func (s *V1Service) GetGlobalParamsPublic() *GlobalParamsPublic { var versionedParams []VersionedGlobalParamsPublic - for _, version := range s.params.Versions { + for _, version := range s.Service.Params.Versions { versionedParams = append(versionedParams, VersionedGlobalParamsPublic{ Version: version.Version, ActivationHeight: version.ActivationHeight, @@ -52,12 +52,12 @@ func (s *Services) GetGlobalParamsPublic() *GlobalParamsPublic { // GetVersionedGlobalParamsByHeight returns the versioned global params // for a particular bitcoin height -func (s *Services) GetVersionedGlobalParamsByHeight(height uint64) *types.VersionedGlobalParams { +func (s *V1Service) GetVersionedGlobalParamsByHeight(height uint64) *types.VersionedGlobalParams { // Iterate the list in reverse (i.e. decreasing ActivationHeight) // and identify the first element that has an activation height below // the specified BTC height. - for i := len(s.params.Versions) - 1; i >= 0; i-- { - paramsVersion := s.params.Versions[i] + for i := len(s.Service.Params.Versions) - 1; i >= 0; i-- { + paramsVersion := s.Service.Params.Versions[i] if paramsVersion.ActivationHeight <= height { return paramsVersion } diff --git a/internal/v1/service/service.go b/internal/v1/service/service.go new file mode 100644 index 00000000..c948f134 --- /dev/null +++ b/internal/v1/service/service.go @@ -0,0 +1,33 @@ +package v1service + +import ( + "context" + + "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 V1Service struct { + *service.Service +} + +func New( + ctx context.Context, + cfg *config.Config, + globalParams *types.GlobalParams, + finalityProviders []types.FinalityProviderDetails, + clients *clients.Clients, + dbClients *dbclients.DbClients, +) (*V1Service, error) { + service, err := service.New(ctx, cfg, globalParams, finalityProviders, clients, dbClients) + if err != nil { + return nil, err + } + + return &V1Service{ + service, + }, nil +} diff --git a/internal/services/staker.go b/internal/v1/service/staker.go similarity index 81% rename from internal/services/staker.go rename to internal/v1/service/staker.go index 4992316d..3fa6861e 100644 --- a/internal/services/staker.go +++ b/internal/v1/service/staker.go @@ -1,12 +1,12 @@ -package services +package v1service import ( "context" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" "github.com/rs/zerolog/log" ) @@ -16,12 +16,12 @@ type PublicKeyAddressMappingPublic struct { } // Given the staker public key, transform into multiple btc addresses and save them in the db. -func (s *Services) ProcessAndSaveBtcAddresses( +func (s *V1Service) ProcessAndSaveBtcAddresses( ctx context.Context, stakerPkHex string, ) *types.Error { // Prepare the btc addresses addresses, err := utils.DeriveAddressesFromNoCoordPk( - stakerPkHex, s.cfg.Server.BTCNetParam, + stakerPkHex, s.Service.Cfg.Server.BTCNetParam, ) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("Failed to derive addresses from staker pk") @@ -32,7 +32,7 @@ func (s *Services) ProcessAndSaveBtcAddresses( } // Try to save the btc addresses, ignore if they already exist - err = s.DbClient.InsertPkAddressMappings( + err = s.Service.DbClients.V1DBClient.InsertPkAddressMappings( ctx, stakerPkHex, addresses.Taproot, addresses.NativeSegwitOdd, addresses.NativeSegwitEven, ) @@ -48,13 +48,14 @@ func (s *Services) ProcessAndSaveBtcAddresses( // first categorizing the addresses, then querying the database for the // corresponding public keys. The results are returned as a map where the keys // are the addresses and the values are the corresponding public keys. -func (s *Services) GetStakerPublicKeysByAddresses( +// TODO: extract this to a common function in util +func (s *V1Service) GetStakerPublicKeysByAddresses( ctx context.Context, addresses []string, ) (map[string]string, *types.Error) { // Split the addresses into taproot and native segwit var taprootAddresses, nativeSegwitAddresses []string for _, addr := range addresses { - addressType, err := utils.CheckBtcAddressType(addr, s.cfg.Server.BTCNetParam) + addressType, err := utils.CheckBtcAddressType(addr, s.Service.Cfg.Server.BTCNetParam) if err != nil { return nil, types.NewErrorWithMsg( http.StatusBadRequest, types.BadRequest, "invalid btc address", @@ -75,7 +76,7 @@ func (s *Services) GetStakerPublicKeysByAddresses( addressPkMapping := make(map[string]string) // Get the public keys from the db by taproot addresses if len(taprootAddresses) > 0 { - mappings, err := s.DbClient.FindPkMappingsByTaprootAddress( + mappings, err := s.Service.DbClients.V1DBClient.FindPkMappingsByTaprootAddress( ctx, taprootAddresses, ) if err != nil { @@ -89,7 +90,7 @@ func (s *Services) GetStakerPublicKeysByAddresses( } // Get the public keys from the db by native segwit addresses if len(nativeSegwitAddresses) > 0 { - mappings, err := s.DbClient.FindPkMappingsByNativeSegwitAddress( + mappings, err := s.Service.DbClients.V1DBClient.FindPkMappingsByNativeSegwitAddress( ctx, nativeSegwitAddresses, ) if err != nil { @@ -113,7 +114,7 @@ func (s *Services) GetStakerPublicKeysByAddresses( // It checks both the "NativeSegwitEven" and "NativeSegwitOdd" fields to find a // match. func findPublicKeyByNativeSegwitAddress( - providedNativeSegwitAddress string, mappings []*model.PkAddressMapping, + providedNativeSegwitAddress string, mappings []*dbmodel.PkAddressMapping, ) string { for _, mapping := range mappings { if mapping.NativeSegwitEven == providedNativeSegwitAddress || diff --git a/internal/services/stats.go b/internal/v1/service/stats.go similarity index 85% rename from internal/services/stats.go rename to internal/v1/service/stats.go index 7d4a9a34..54517c83 100644 --- a/internal/services/stats.go +++ b/internal/v1/service/stats.go @@ -1,12 +1,12 @@ -package services +package v1service import ( "context" "fmt" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/db" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "github.com/rs/zerolog/log" ) @@ -30,12 +30,12 @@ type StakerStatsPublic struct { // ProcessStakingStatsCalculation calculates the staking stats and updates the database. // This method tolerates duplicated calls, only the first call will be processed. -func (s *Services) ProcessStakingStatsCalculation( +func (s *V1Service) ProcessStakingStatsCalculation( ctx context.Context, stakingTxHashHex, stakerPkHex, fpPkHex string, state types.DelegationState, amount uint64, ) *types.Error { // Fetch existing or initialize the stats lock document if not exist - statsLockDocument, err := s.DbClient.GetOrCreateStatsLock( + statsLockDocument, err := s.Service.DbClients.V1DBClient.GetOrCreateStatsLock( ctx, stakingTxHashHex, state.ToString(), ) if err != nil { @@ -47,7 +47,7 @@ func (s *Services) ProcessStakingStatsCalculation( case types.Active: // Add to the finality stats if !statsLockDocument.FinalityProviderStats { - err = s.DbClient.IncrementFinalityProviderStats( + err = s.Service.DbClients.V1DBClient.IncrementFinalityProviderStats( ctx, stakingTxHashHex, fpPkHex, amount, ) if err != nil { @@ -70,7 +70,7 @@ func (s *Services) ProcessStakingStatsCalculation( Msg("error while processing and saving btc addresses") return types.NewInternalServiceError(addressConversionErr) } - err = s.DbClient.IncrementStakerStats( + err = s.Service.DbClients.V1DBClient.IncrementStakerStats( ctx, stakingTxHashHex, stakerPkHex, amount, ) if err != nil { @@ -86,7 +86,7 @@ func (s *Services) ProcessStakingStatsCalculation( // The overall stats should be the last to be updated as it has dependency // on staker stats. if !statsLockDocument.OverallStats { - err = s.DbClient.IncrementOverallStats( + err = s.Service.DbClients.V1DBClient.IncrementOverallStats( ctx, stakingTxHashHex, stakerPkHex, amount, ) if err != nil { @@ -102,7 +102,7 @@ func (s *Services) ProcessStakingStatsCalculation( case types.Unbonded: // Subtract from the finality stats if !statsLockDocument.FinalityProviderStats { - err = s.DbClient.SubtractFinalityProviderStats( + err = s.Service.DbClients.V1DBClient.SubtractFinalityProviderStats( ctx, stakingTxHashHex, fpPkHex, amount, ) if err != nil { @@ -115,7 +115,7 @@ func (s *Services) ProcessStakingStatsCalculation( } } if !statsLockDocument.StakerStats { - err = s.DbClient.SubtractStakerStats( + err = s.Service.DbClients.V1DBClient.SubtractStakerStats( ctx, stakingTxHashHex, stakerPkHex, amount, ) if err != nil { @@ -131,7 +131,7 @@ func (s *Services) ProcessStakingStatsCalculation( // The overall stats should be the last to be updated as it has dependency // on staker stats. if !statsLockDocument.OverallStats { - err = s.DbClient.SubtractOverallStats( + err = s.Service.DbClients.V1DBClient.SubtractOverallStats( ctx, stakingTxHashHex, stakerPkHex, amount, ) if err != nil { @@ -153,10 +153,10 @@ func (s *Services) ProcessStakingStatsCalculation( return nil } -func (s *Services) GetOverallStats( +func (s *V1Service) GetOverallStats( ctx context.Context, ) (*OverallStatsPublic, *types.Error) { - stats, err := s.DbClient.GetOverallStats(ctx) + stats, err := s.Service.DbClients.V1DBClient.GetOverallStats(ctx) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("error while fetching overall stats") return nil, types.NewInternalServiceError(err) @@ -166,7 +166,7 @@ func (s *Services) GetOverallStats( confirmedTvl := uint64(0) pendingTvl := uint64(0) - btcInfo, err := s.DbClient.GetLatestBtcInfo(ctx) + btcInfo, err := s.Service.DbClients.V1DBClient.GetLatestBtcInfo(ctx) if err != nil { // Handle missing BTC information, which may occur during initial setup. // Default the unconfirmed TVL to 0; this will be updated automatically @@ -195,10 +195,10 @@ func (s *Services) GetOverallStats( }, nil } -func (s *Services) GetStakerStats( +func (s *V1Service) GetStakerStats( ctx context.Context, stakerPkHex string, ) (*StakerStatsPublic, *types.Error) { - stats, err := s.DbClient.GetStakerStats(ctx, stakerPkHex) + stats, err := s.Service.DbClients.V1DBClient.GetStakerStats(ctx, stakerPkHex) if err != nil { // Not found error is not an error, return nil if db.IsNotFoundError(err) { @@ -217,10 +217,10 @@ func (s *Services) GetStakerStats( }, nil } -func (s *Services) GetTopStakersByActiveTvl( +func (s *V1Service) GetTopStakersByActiveTvl( ctx context.Context, pageToken string, ) ([]StakerStatsPublic, string, *types.Error) { - resultMap, err := s.DbClient.FindTopStakersByTvl(ctx, pageToken) + resultMap, err := s.Service.DbClients.V1DBClient.FindTopStakersByTvl(ctx, pageToken) if err != nil { if db.IsInvalidPaginationTokenError(err) { log.Ctx(ctx).Warn().Err(err). @@ -244,10 +244,10 @@ func (s *Services) GetTopStakersByActiveTvl( return topStakersStats, resultMap.PaginationToken, nil } -func (s *Services) ProcessBtcInfoStats( +func (s *V1Service) ProcessBtcInfoStats( ctx context.Context, btcHeight uint64, confirmedTvl uint64, unconfirmedTvl uint64, ) *types.Error { - err := s.DbClient.UpsertLatestBtcInfo(ctx, btcHeight, confirmedTvl, unconfirmedTvl) + err := s.Service.DbClients.V1DBClient.UpsertLatestBtcInfo(ctx, btcHeight, confirmedTvl, unconfirmedTvl) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("error while upserting latest btc info") return types.NewInternalServiceError(err) diff --git a/internal/services/timelock.go b/internal/v1/service/timelock.go similarity index 74% rename from internal/services/timelock.go rename to internal/v1/service/timelock.go index 2667e59b..f289e599 100644 --- a/internal/services/timelock.go +++ b/internal/v1/service/timelock.go @@ -1,23 +1,23 @@ -package services +package v1service import ( "context" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "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" "github.com/rs/zerolog/log" ) // ProcessExpireCheck checks if the staking delegation has expired and updates the database. // This method tolerate duplicated calls on the same stakingTxHashHex. -func (s *Services) ProcessExpireCheck( +func (s *V1Service) ProcessExpireCheck( ctx context.Context, stakingTxHashHex string, startHeight, timelock uint64, txType types.StakingTxType, ) *types.Error { expireHeight := startHeight + timelock - err := s.DbClient.SaveTimeLockExpireCheck( + err := s.Service.DbClients.V1DBClient.SaveTimeLockExpireCheck( ctx, stakingTxHashHex, expireHeight, txType.ToString(), ) if err != nil { @@ -29,10 +29,10 @@ func (s *Services) ProcessExpireCheck( // TransitionToUnbondedState transitions the staking delegation to unbonded state. // It returns true if the delegation is found and successfully transitioned to unbonded state. -func (s *Services) TransitionToUnbondedState( +func (s *V1Service) TransitionToUnbondedState( ctx context.Context, stakingType types.StakingTxType, stakingTxHashHex string, ) *types.Error { - err := s.DbClient.TransitionToUnbondedState(ctx, stakingTxHashHex, utils.QualifiedStatesToUnbonded(stakingType)) + err := s.Service.DbClients.V1DBClient.TransitionToUnbondedState(ctx, stakingTxHashHex, utils.QualifiedStatesToUnbonded(stakingType)) if err != nil { // If the delegation is not found, we can ignore the error, it just means the delegation is not in a state that we can transition to unbonded if db.IsNotFoundError(err) { diff --git a/internal/services/unbonding.go b/internal/v1/service/unbonding.go similarity index 82% rename from internal/services/unbonding.go rename to internal/v1/service/unbonding.go index 7d2131be..cdf29516 100644 --- a/internal/services/unbonding.go +++ b/internal/v1/service/unbonding.go @@ -1,28 +1,27 @@ -package services +package v1service import ( "context" "fmt" "net/http" + "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" "github.com/rs/zerolog/log" - - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" ) // UnbondDelegation verifies the unbonding request and saves the unbonding tx into the DB. // It returns an error if the delegation is not eligible for unbonding or if the unbonding request is invalid. // If successful, it will change the delegation state to `unbonding_requested` -func (s *Services) UnbondDelegation( +func (s *V1Service) UnbondDelegation( ctx context.Context, stakingTxHashHex, unbondingTxHashHex, unbondingTxHex, signatureHex string) *types.Error { // 1. check the delegation is eligible for unbonding - delegationDoc, err := s.DbClient.FindDelegationByTxHashHex(ctx, stakingTxHashHex) + delegationDoc, err := s.Service.DbClients.V1DBClient.FindDelegationByTxHashHex(ctx, stakingTxHashHex) if err != nil { if ok := db.IsNotFoundError(err); ok { log.Warn().Err(err).Msg("delegation not found, hence not eligible for unbonding") @@ -61,7 +60,7 @@ func (s *Services) UnbondDelegation( delegationDoc.StakingTx.OutputIndex, delegationDoc.StakingValue, paramsVersion, - s.cfg.Server.BTCNetParam, + s.Service.Cfg.Server.BTCNetParam, ); err != nil { log.Ctx(ctx).Warn().Err(err).Msg(fmt.Sprintf("unbonding request did not pass unbonding request verification, staking tx hash: %s, unbonding tx hash: %s", delegationDoc.StakingTxHashHex, unbondingTxHashHex)) @@ -69,7 +68,7 @@ func (s *Services) UnbondDelegation( } // 3. save unbonding tx into DB - err = s.DbClient.SaveUnbondingTx(ctx, stakingTxHashHex, unbondingTxHashHex, unbondingTxHex, signatureHex) + err = s.Service.DbClients.V1DBClient.SaveUnbondingTx(ctx, stakingTxHashHex, unbondingTxHashHex, unbondingTxHex, signatureHex) if err != nil { if ok := db.IsDuplicateKeyError(err); ok { log.Ctx(ctx).Warn().Err(err).Msg("unbonding request already been submitted into the system") @@ -84,8 +83,8 @@ func (s *Services) UnbondDelegation( return nil } -func (s *Services) IsEligibleForUnbondingRequest(ctx context.Context, stakingTxHashHex string) *types.Error { - delegationDoc, err := s.DbClient.FindDelegationByTxHashHex(ctx, stakingTxHashHex) +func (s *V1Service) IsEligibleForUnbondingRequest(ctx context.Context, stakingTxHashHex string) *types.Error { + delegationDoc, err := s.Service.DbClients.V1DBClient.FindDelegationByTxHashHex(ctx, stakingTxHashHex) if err != nil { if ok := db.IsNotFoundError(err); ok { log.Ctx(ctx).Warn().Err(err).Msg("delegation not found, hence not eligible for unbonding") @@ -104,12 +103,12 @@ func (s *Services) IsEligibleForUnbondingRequest(ctx context.Context, stakingTxH // TransitionToUnbondingState process the actual confirmed unbonding tx by updating the delegation state to `unbonding` // It returns true if the delegation is found and successfully transitioned to unbonding state. -func (s *Services) TransitionToUnbondingState( +func (s *V1Service) TransitionToUnbondingState( ctx context.Context, stakingTxHashHex string, unbondingStartHeight, unbondingTimelock, unbondingOutputIndex uint64, unbondingTxHex string, unbondingStartTimestamp int64, ) *types.Error { - err := s.DbClient.TransitionToUnbondingState(ctx, stakingTxHashHex, unbondingStartHeight, unbondingTimelock, unbondingOutputIndex, unbondingTxHex, unbondingStartTimestamp) + err := s.Service.DbClients.V1DBClient.TransitionToUnbondingState(ctx, stakingTxHashHex, unbondingStartHeight, unbondingTimelock, unbondingOutputIndex, unbondingTxHex, unbondingStartTimestamp) if err != nil { if ok := db.IsNotFoundError(err); ok { log.Ctx(ctx).Warn().Str("stakingTxHashHex", stakingTxHashHex).Err(err).Msg("delegation not found or no longer eligible for unbonding") diff --git a/internal/services/withdrawn.go b/internal/v1/service/withdrawn.go similarity index 69% rename from internal/services/withdrawn.go rename to internal/v1/service/withdrawn.go index 0ce92625..34d682bb 100644 --- a/internal/services/withdrawn.go +++ b/internal/v1/service/withdrawn.go @@ -1,18 +1,18 @@ -package services +package v1service import ( "context" "net/http" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/db" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "github.com/rs/zerolog/log" ) -func (s *Services) TransitionToWithdrawnState( +func (s *V1Service) TransitionToWithdrawnState( ctx context.Context, stakingTxHashHex string, ) *types.Error { - err := s.DbClient.TransitionToWithdrawnState(ctx, stakingTxHashHex) + err := s.Service.DbClients.V1DBClient.TransitionToWithdrawnState(ctx, stakingTxHashHex) if err != nil { if ok := db.IsNotFoundError(err); ok { log.Ctx(ctx).Warn().Str("stakingTxHashHex", stakingTxHashHex).Err(err).Msg("delegation not found or no longer eligible for withdraw") diff --git a/internal/v2/api/handlers/finality_provider.go b/internal/v2/api/handlers/finality_provider.go new file mode 100644 index 00000000..51c6be92 --- /dev/null +++ b/internal/v2/api/handlers/finality_provider.go @@ -0,0 +1,32 @@ +package v2handlers + +import ( + "net/http" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" +) + +// GetFinalityProviders gets finality providers +// @Summary Get Finality Providers +// @Description Fetches finality providers including their public keys, active tvl, total tvl, descriptions, commission, active delegations and total delegations etc +// @Produce json +// @Tags v2 +// @Param pagination_key query string false "Pagination key to fetch the next page of finality providers" +// @Param finality_provider_pk query string false "Filter by finality provider public key" +// @Param sort_by query string false "Sort by field" Enums(active_tvl, name, commission) +// @Param order query string false "Order" Enums(asc, desc) +// @Success 200 {object} handler.PublicResponse[[]v2service.FinalityProviderPublic]{array} "List of finality providers and pagination token" +// @Failure 400 {object} types.Error "Error: Bad Request" +// @Router /v2/finality-providers [get] +func (h *V2Handler) GetFinalityProviders(request *http.Request) (*handler.Result, *types.Error) { + paginationKey, err := handler.ParsePaginationQuery(request) + if err != nil { + return nil, err + } + providers, paginationToken, err := h.Service.GetFinalityProviders(request.Context(), paginationKey) + if err != nil { + return nil, err + } + return handler.NewResultWithPagination(providers, paginationToken), nil +} diff --git a/internal/v2/api/handlers/handler.go b/internal/v2/api/handlers/handler.go new file mode 100644 index 00000000..5d884e8e --- /dev/null +++ b/internal/v2/api/handlers/handler.go @@ -0,0 +1,22 @@ +package v2handlers + +import ( + "context" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + v2service "github.com/babylonlabs-io/staking-api-service/internal/v2/service" +) + +type V2Handler struct { + *handler.Handler + Service v2service.V2ServiceProvider +} + +func New( + ctx context.Context, handler *handler.Handler, v2Service v2service.V2ServiceProvider, +) (*V2Handler, error) { + return &V2Handler{ + Handler: handler, + Service: v2Service, + }, nil +} diff --git a/internal/v2/api/handlers/params.go b/internal/v2/api/handlers/params.go new file mode 100644 index 00000000..53a4788e --- /dev/null +++ b/internal/v2/api/handlers/params.go @@ -0,0 +1,23 @@ +package v2handlers + +import ( + "net/http" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" +) + +// GetGlobalParams gets global parameters +// @Summary Get Global Parameters +// @Description Fetches global parameters for babylon chain and BTC chain +// @Produce json +// @Tags v2 +// @Success 200 {object} handler.PublicResponse[v2service.GlobalParamsPublic] "Global parameters" +// @Router /v2/global-params [get] +func (h *V2Handler) GetGlobalParams(request *http.Request) (*handler.Result, *types.Error) { + params, err := h.Service.GetGlobalParams(request.Context()) + if err != nil { + return nil, err + } + return handler.NewResult(params), nil +} diff --git a/internal/v2/api/handlers/staker.go b/internal/v2/api/handlers/staker.go new file mode 100644 index 00000000..8e7bb6ae --- /dev/null +++ b/internal/v2/api/handlers/staker.go @@ -0,0 +1,49 @@ +package v2handlers + +import ( + "net/http" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" +) + +// GetStakerDelegations gets staker delegations for babylon staking +// @Summary Get Staker Delegations +// @Description Fetches staker delegations for babylon staking including tvl, total delegations, active tvl, active delegations and total stakers. +// @Produce json +// @Tags v2 +// @Param staking_tx_hash_hex query string true "Staking transaction hash in hex format" +// @Param pagination_key query string false "Pagination key to fetch the next page of delegations" +// @Success 200 {object} handler.PublicResponse[[]v2service.StakerDelegationPublic]{array} "List of staker delegations and pagination token" +// @Failure 400 {object} types.Error "Error: Bad Request" +// @Router /v2/staker/delegations [get] +func (h *V2Handler) GetStakerDelegations(request *http.Request) (*handler.Result, *types.Error) { + paginationKey, err := handler.ParsePaginationQuery(request) + if err != nil { + return nil, err + } + delegations, paginationToken, err := h.Service.GetStakerDelegations(request.Context(), paginationKey) + if err != nil { + return nil, err + } + return handler.NewResultWithPagination(delegations, paginationToken), nil +} + +// GetStakerStats gets staker stats for babylon staking +// @Summary Get Staker Stats +// @Description Fetches staker stats for babylon staking including active tvl, total tvl, active delegations and total delegations. +// @Produce json +// @Tags v2 +// @Success 200 {object} handler.PublicResponse[v2service.StakerStatsPublic] "Staker stats" +// @Router /v2/staker/stats [get] +func (h *V2Handler) GetStakerStats(request *http.Request) (*handler.Result, *types.Error) { + stakerPKHex := request.URL.Query().Get("staker_pk_hex") + if stakerPKHex == "" { + return nil, types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, "staker_pk_hex is required") + } + stats, err := h.Service.GetStakerStats(request.Context(), stakerPKHex) + if err != nil { + return nil, err + } + return handler.NewResult(stats), nil +} diff --git a/internal/v2/api/handlers/stats.go b/internal/v2/api/handlers/stats.go new file mode 100644 index 00000000..560993d3 --- /dev/null +++ b/internal/v2/api/handlers/stats.go @@ -0,0 +1,23 @@ +package v2handlers + +import ( + "net/http" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" +) + +// GetStats @Summary Get overall system stats +// @Description Overall system stats +// @Produce json +// @Tags v2 +// @Success 200 {object} handler.PublicResponse[v2service.OverallStatsPublic] "" +// @Failure 400 {object} types.Error "Error: Bad Request" +// @Router /v2/stats [get] +func (h *V2Handler) GetStats(request *http.Request) (*handler.Result, *types.Error) { + stats, err := h.Service.GetOverallStats(request.Context()) + if err != nil { + return nil, err + } + return handler.NewResult(stats), nil +} diff --git a/internal/v2/db/client/db_client.go b/internal/v2/db/client/db_client.go new file mode 100644 index 00000000..249c4e6d --- /dev/null +++ b/internal/v2/db/client/db_client.go @@ -0,0 +1,23 @@ +package v2dbclient + +import ( + "context" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + dbclient "github.com/babylonlabs-io/staking-api-service/internal/shared/db/client" + "go.mongodb.org/mongo-driver/mongo" +) + +type V2Database struct { + *dbclient.Database +} + +func New(ctx context.Context, client *mongo.Client, cfg *config.DbConfig) (*V2Database, error) { + return &V2Database{ + Database: &dbclient.Database{ + DbName: cfg.DbName, + Client: client, + Cfg: cfg, + }, + }, nil +} diff --git a/internal/v2/db/client/interface.go b/internal/v2/db/client/interface.go new file mode 100644 index 00000000..02bd61c4 --- /dev/null +++ b/internal/v2/db/client/interface.go @@ -0,0 +1,9 @@ +package v2dbclient + +import ( + dbclient "github.com/babylonlabs-io/staking-api-service/internal/shared/db/client" +) + +type V2DBClient interface { + dbclient.DBClient +} diff --git a/internal/v2/db/model/finality_provider.go b/internal/v2/db/model/finality_provider.go new file mode 100644 index 00000000..c9f584ef --- /dev/null +++ b/internal/v2/db/model/finality_provider.go @@ -0,0 +1,10 @@ +package v2dbmodel + +type FinalityProviderDocument struct { + ID string `bson:"_id"` + ActiveTVL int64 `bson:"active_tvl"` + TotalTVL int64 `bson:"total_tvl"` + ActiveDelegations int64 `bson:"active_delegations"` + TotalDelegations int64 `bson:"total_delegations"` +} + diff --git a/internal/v2/db/model/staker.go b/internal/v2/db/model/staker.go new file mode 100644 index 00000000..eb865585 --- /dev/null +++ b/internal/v2/db/model/staker.go @@ -0,0 +1,13 @@ +package v2dbmodel + +type StakerDocument struct { + ID string `bson:"_id"` + Addresses map[string]string `bson:"addresses"` + ActiveTVL int64 `bson:"active_tvl"` + WithdrawableTVL int64 `bson:"withdrawable_tvl"` + SlashedTVL int64 `bson:"slashed_tvl"` + TotalActiveDelegations int64 `bson:"total_active_delegations"` + TotalWithdrawableDelegations int64 `bson:"total_withdrawable_delegations"` + TotalSlashedDelegations int64 `bson:"total_slashed_delegations"` +} + diff --git a/internal/v2/queue/client/client.go b/internal/v2/queue/client/client.go new file mode 100644 index 00000000..9e1baa26 --- /dev/null +++ b/internal/v2/queue/client/client.go @@ -0,0 +1,19 @@ +package v2queueclient + +import ( + queueclient "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/client" + v2queuehandler "github.com/babylonlabs-io/staking-api-service/internal/v2/queue/handler" + queueConfig "github.com/babylonlabs-io/staking-queue-client/config" +) + +type V2QueueClient struct { + *queueclient.Queue + Handler *v2queuehandler.V2QueueHandler +} + +func New(cfg *queueConfig.QueueConfig, handler *v2queuehandler.V2QueueHandler, queueClient *queueclient.Queue) *V2QueueClient { + return &V2QueueClient{ + Queue: queueClient, + Handler: handler, + } +} diff --git a/internal/v2/queue/client/health.go b/internal/v2/queue/client/health.go new file mode 100644 index 00000000..c1a06579 --- /dev/null +++ b/internal/v2/queue/client/health.go @@ -0,0 +1,5 @@ +package v2queueclient + +func (q *V2QueueClient) IsConnectionHealthy() error { + return nil +} diff --git a/internal/v2/queue/client/message.go b/internal/v2/queue/client/message.go new file mode 100644 index 00000000..66a061f8 --- /dev/null +++ b/internal/v2/queue/client/message.go @@ -0,0 +1,13 @@ +package v2queueclient + +import ( + "github.com/rs/zerolog/log" +) + +func (q *V2QueueClient) StartReceivingMessages() { + log.Printf("Starting to receive messages from v2 queues") +} + +// Turn off all message processing +func (q *V2QueueClient) StopReceivingMessages() { +} diff --git a/internal/v2/queue/handler/handler.go b/internal/v2/queue/handler/handler.go new file mode 100644 index 00000000..1ec32c51 --- /dev/null +++ b/internal/v2/queue/handler/handler.go @@ -0,0 +1,25 @@ +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" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" +) + +type V2QueueHandler struct { + *queuehandler.QueueHandler + Service v1service.V1ServiceProvider +} + +func New(queueHandler *queuehandler.QueueHandler, service v1service.V1ServiceProvider) *V2QueueHandler { + return &V2QueueHandler{ + QueueHandler: queueHandler, + 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 new file mode 100644 index 00000000..c14bad63 --- /dev/null +++ b/internal/v2/service/finality_provider.go @@ -0,0 +1,47 @@ +package v2service + +import ( + "context" + "math/rand" + "time" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils/datagen" +) + +type FinalityProviderPublic struct { + BtcPK string `json:"btc_pk"` + State types.FinalityProviderState `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"` +} + +func (s *V2Service) GetFinalityProviders(ctx context.Context, paginationKey string) ([]FinalityProviderPublic, string, *types.Error) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + // random number of providers between 1 and 10 + numProviders := datagen.RandomPositiveInt(r, 10) + providers := datagen.GenerateRandomFinalityProviderDetail(r, uint64(numProviders)) + publicProviders := make([]FinalityProviderPublic, len(providers)) + for i, provider := range providers { + publicProviders[i] = FinalityProviderPublic{ + BtcPK: datagen.GeneratePks(1)[0], + State: datagen.RandomFinalityProviderState(r), + Description: provider.Description, + Commission: provider.Commission, + ActiveTVL: int64(datagen.RandomPositiveInt(r, 1000000000000000000)), + TotalTVL: int64(datagen.RandomPositiveInt(r, 1000000000000000000)), + ActiveDelegations: int64(datagen.RandomPositiveInt(r, 100)), + TotalDelegations: int64(datagen.RandomPositiveInt(r, 100)), + } + } + + return publicProviders, "", nil +} diff --git a/internal/v2/service/global_params.go b/internal/v2/service/global_params.go new file mode 100644 index 00000000..96954f3a --- /dev/null +++ b/internal/v2/service/global_params.go @@ -0,0 +1,25 @@ +package v2service + +import ( + "context" + "math/rand" + "time" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils/datagen" +) + +type GlobalParamsPublic struct { + Babylon []types.BabylonParams `json:"babylon"` + BTC []types.BTCParams `json:"btc"` +} + +func (s *V2Service) GetGlobalParams(ctx context.Context) (GlobalParamsPublic, *types.Error) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + babylonParams := datagen.GenerateRandomBabylonParams(r) + btcParams := datagen.GenerateRandomBTCParams(r) + return GlobalParamsPublic{ + Babylon: []types.BabylonParams{babylonParams}, + BTC: []types.BTCParams{btcParams}, + }, nil +} diff --git a/internal/v2/service/interface.go b/internal/v2/service/interface.go new file mode 100644 index 00000000..432bc8af --- /dev/null +++ b/internal/v2/service/interface.go @@ -0,0 +1,17 @@ +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, paginationKey string) ([]FinalityProviderPublic, string, *types.Error) + GetGlobalParams(ctx context.Context) (GlobalParamsPublic, *types.Error) + GetOverallStats(ctx context.Context) (OverallStatsPublic, *types.Error) + GetStakerDelegations(ctx context.Context, paginationKey string) ([]StakerDelegationPublic, string, *types.Error) + GetStakerStats(ctx context.Context, stakerPKHex string) (StakerStatsPublic, *types.Error) +} diff --git a/internal/v2/service/service.go b/internal/v2/service/service.go new file mode 100644 index 00000000..4d264bd1 --- /dev/null +++ b/internal/v2/service/service.go @@ -0,0 +1,33 @@ +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 +} + +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, + }, nil +} diff --git a/internal/v2/service/staker.go b/internal/v2/service/staker.go new file mode 100644 index 00000000..1700a76b --- /dev/null +++ b/internal/v2/service/staker.go @@ -0,0 +1,69 @@ +package v2service + +import ( + "context" + "math/rand" + "time" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils/datagen" +) + +type StakerDelegationPublic struct { + StakingTxHashHex string `json:"staking_tx_hash_hex"` + StakerPKHex string `json:"staker_pk_hex"` + FinalityProviderPKHex string `json:"finality_provider_pk_hex"` + StakingStartHeight int64 `json:"staking_start_height"` + UnbondingStartHeight int64 `json:"unbonding_start_height"` + Timelock int64 `json:"timelock"` + StakingValue int64 `json:"staking_value"` + State string `json:"state"` + StakingTx types.TransactionInfo `json:"staking_tx"` + UnbondingTx types.TransactionInfo `json:"unbonding_tx"` +} + +type StakerStatsPublic struct { + StakerPKHex string `json:"staker_pk_hex"` + ActiveTVL int64 `json:"active_tvl"` + TotalTVL int64 `json:"total_tvl"` + ActiveDelegations int64 `json:"active_delegations"` + TotalDelegations int64 `json:"total_delegations"` +} + +func (s *V2Service) GetStakerDelegations(ctx context.Context, paginationKey string) ([]StakerDelegationPublic, string, *types.Error) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + // random positive int + numStakerDelegations := datagen.RandomPositiveInt(r, 10) + stakerDelegationPublics := []StakerDelegationPublic{} + for i := 0; i < numStakerDelegations; i++ { + _, stakingTxHash, _ := datagen.GenerateRandomTx(r, nil) + stakerPkHex, _ := datagen.RandomPk() + fpPkHex, _ := datagen.RandomPk() + stakerDelegation := &StakerDelegationPublic{ + StakingTxHashHex: stakingTxHash, + StakerPKHex: stakerPkHex, + FinalityProviderPKHex: fpPkHex, + StakingStartHeight: int64(datagen.RandomPositiveInt(r, 1000000)), + UnbondingStartHeight: int64(datagen.RandomPositiveInt(r, 1000000)), + Timelock: int64(datagen.RandomPositiveInt(r, 1000000)), + StakingValue: datagen.RandomAmount(r), + State: types.Active.ToString(), + StakingTx: datagen.RandomTransactionInfo(r), + UnbondingTx: datagen.RandomTransactionInfo(r), + } + stakerDelegationPublics = append(stakerDelegationPublics, *stakerDelegation) + } + return stakerDelegationPublics, "", nil +} + +func (s *V2Service) GetStakerStats(ctx context.Context, stakerPKHex string) (StakerStatsPublic, *types.Error) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + stakerStats := StakerStatsPublic{ + StakerPKHex: stakerPKHex, + ActiveTVL: int64(datagen.RandomPositiveInt(r, 1000000)), + TotalTVL: int64(datagen.RandomPositiveInt(r, 1000000)), + ActiveDelegations: int64(datagen.RandomPositiveInt(r, 100)), + TotalDelegations: int64(datagen.RandomPositiveInt(r, 100)), + } + return stakerStats, nil +} diff --git a/internal/v2/service/stats.go b/internal/v2/service/stats.go new file mode 100644 index 00000000..62175b5e --- /dev/null +++ b/internal/v2/service/stats.go @@ -0,0 +1,36 @@ +package v2service + +import ( + "context" + "math/rand" + "time" + + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils/datagen" +) + +type OverallStatsPublic struct { + ActiveTVL int64 `json:"active_tvl"` + TotalTVL int64 `json:"total_tvl"` + ActiveDelegations int64 `json:"active_delegations"` + TotalDelegations int64 `json:"total_delegations"` + ActiveStakers int64 `json:"active_stakers"` + TotalStakers int64 `json:"total_stakers"` + ActiveFinalityProviders int64 `json:"active_finality_providers"` + TotalFinalityProviders int64 `json:"total_finality_providers"` +} + +func (s *V2Service) GetOverallStats(ctx context.Context) (OverallStatsPublic, *types.Error) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + overallStats := OverallStatsPublic{ + ActiveTVL: int64(datagen.RandomPositiveInt(r, 1000000)), + TotalTVL: int64(datagen.RandomPositiveInt(r, 1000000)), + ActiveDelegations: int64(datagen.RandomPositiveInt(r, 100)), + TotalDelegations: int64(datagen.RandomPositiveInt(r, 100)), + ActiveStakers: int64(datagen.RandomPositiveInt(r, 100)), + TotalStakers: int64(datagen.RandomPositiveInt(r, 100)), + ActiveFinalityProviders: int64(datagen.RandomPositiveInt(r, 100)), + TotalFinalityProviders: int64(datagen.RandomPositiveInt(r, 100)), + } + return overallStats, nil +} diff --git a/tests/integration_test/active_staking_test.go b/tests/integration_test/active_staking_test.go index e75b2596..596a1d76 100644 --- a/tests/integration_test/active_staking_test.go +++ b/tests/integration_test/active_staking_test.go @@ -12,9 +12,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" + handler "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" ) const ( @@ -26,9 +26,9 @@ func TestUnbondActiveStaking(t *testing.T) { expiredStakingEvent := client.NewExpiredStakingEvent(activeStakingEvent[0].StakingTxHashHex, types.ActiveTxType.ToString()) testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeStakingEvent) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvent) time.Sleep(2 * time.Second) - sendTestMessage(testServer.Queues.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredStakingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredStakingEvent}) time.Sleep(2 * time.Second) // Test the API @@ -44,7 +44,7 @@ func TestUnbondActiveStaking(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var response handlers.PublicResponse[[]services.DelegationPublic] + var response handler.PublicResponse[[]v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -58,9 +58,9 @@ func TestUnbondActiveStakingShouldTolerateOutOfOrder(t *testing.T) { expiredStakingEvent := client.NewExpiredStakingEvent(activeStakingEvent[0].StakingTxHashHex, types.ActiveTxType.ToString()) testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredStakingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredStakingEvent}) time.Sleep(2 * time.Second) - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeStakingEvent) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvent) time.Sleep(10 * time.Second) // Test the API @@ -76,7 +76,7 @@ func TestUnbondActiveStakingShouldTolerateOutOfOrder(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var response handlers.PublicResponse[[]services.DelegationPublic] + var response handler.PublicResponse[[]v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -90,7 +90,7 @@ func TestShouldNotUnbondIfNotActiveState(t *testing.T) { expiredStakingEvent := client.NewExpiredStakingEvent(activeStakingEvent.StakingTxHashHex, types.ActiveTxType.ToString()) testServer := setupTestServer(t, nil) defer testServer.Close() - err := sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) + err := sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) require.NoError(t, err) time.Sleep(2 * time.Second) @@ -107,7 +107,7 @@ func TestShouldNotUnbondIfNotActiveState(t *testing.T) { // Check that the status code is HTTP 202 assert.Equal(t, http.StatusAccepted, resp.StatusCode, "expected HTTP 202 Accepted status") - sendTestMessage(testServer.Queues.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredStakingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredStakingEvent}) time.Sleep(2 * time.Second) // Test the API @@ -123,7 +123,7 @@ func TestShouldNotUnbondIfNotActiveState(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var response handlers.PublicResponse[[]services.DelegationPublic] + var response handler.PublicResponse[[]v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err, "unmarshalling response body should not fail") diff --git a/tests/integration_test/address_lookup_test.go b/tests/integration_test/address_lookup_test.go index 02fd9084..6d34def2 100644 --- a/tests/integration_test/address_lookup_test.go +++ b/tests/integration_test/address_lookup_test.go @@ -8,10 +8,11 @@ import ( "testing" "time" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/stretchr/testify/assert" ) @@ -36,7 +37,7 @@ func FuzzTestPkAddressesMapping(f *testing.F) { testServer := setupTestServer(t, nil) defer testServer.Close() sendTestMessage( - testServer.Queues.ActiveStakingQueueClient, activeStakingEvents, + testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvents, ) time.Sleep(5 * time.Second) @@ -125,13 +126,13 @@ func TestPkAddressMappingWorksForOlderStatsEventVersion(t *testing.T) { assert.NoError(t, err, "failed to generate random public key") fpPk, err := testutils.RandomPk() assert.NoError(t, err, "failed to generate random public key") - del := &model.DelegationDocument{ + del := &v1dbmodel.DelegationDocument{ StakingTxHashHex: txHash, StakerPkHex: stakerPk, FinalityProviderPkHex: fpPk, StakingValue: uint64(testutils.RandomAmount(r)), State: types.Active, - StakingTx: &model.TimelockTransaction{ + StakingTx: &v1dbmodel.TimelockTransaction{ TxHex: tx.TxHash().String(), OutputIndex: uint64(tx.TxOut[0].Value), StartTimestamp: time.Now().Unix(), @@ -149,16 +150,16 @@ func TestPkAddressMappingWorksForOlderStatsEventVersion(t *testing.T) { State: string(del.State), } testutils.InjectDbDocument( - testServer.Config, model.DelegationCollection, del, + testServer.Config, dbmodel.V1DelegationCollection, del, ) sendTestMessage( - testServer.Queues.StatsQueueClient, []event{*oldStatsMsg}, + testServer.Queues.V1QueueClient.StatsQueueClient, []event{*oldStatsMsg}, ) time.Sleep(5 * time.Second) // inspect the items in the database - pkAddresses, err := testutils.InspectDbDocuments[model.PkAddressMapping]( - testServer.Config, model.PkAddressMappingsCollection, + pkAddresses, err := testutils.InspectDbDocuments[dbmodel.PkAddressMapping]( + testServer.Config, dbmodel.V1PkAddressMappingsCollection, ) assert.NoError(t, err, "failed to inspect the items in the database") assert.Equal(t, 1, len(pkAddresses), "expected only one item in the database") @@ -205,7 +206,7 @@ func performLookupRequest( bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err) - var response handlers.PublicResponse[map[string]string] + var response handler.PublicResponse[map[string]string] err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err) return response.Data diff --git a/tests/integration_test/assets_checking_test.go b/tests/integration_test/assets_checking_test.go index dd031537..1a4fdf46 100644 --- a/tests/integration_test/assets_checking_test.go +++ b/tests/integration_test/assets_checking_test.go @@ -7,14 +7,14 @@ import ( "net/http" "testing" - "github.com/babylonlabs-io/staking-api-service/internal/api" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/clients" - "github.com/babylonlabs-io/staking-api-service/internal/clients/ordinals" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/http/clients" + "github.com/babylonlabs-io/staking-api-service/internal/shared/http/clients/ordinals" + "github.com/babylonlabs-io/staking-api-service/internal/shared/services/service" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" "github.com/babylonlabs-io/staking-api-service/tests/mocks" "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/btcsuite/btcd/chaincfg" @@ -63,7 +63,7 @@ func FuzzSuccessfullyVerifyUTXOsAssetsViaOrdinalService(f *testing.F) { mockedOrdinalResponse := createOrdinalServiceResponse(t, r, payload.UTXOs, txidsWithAsset) - mockOrdinal := new(mocks.OrdinalsClientInterface) + mockOrdinal := new(mocks.OrdinalsClient) mockOrdinal.On("FetchUTXOInfos", mock.Anything, mock.Anything).Return(mockedOrdinalResponse, nil) mockedClients := &clients.Clients{ Ordinals: mockOrdinal, @@ -80,7 +80,7 @@ func FuzzSuccessfullyVerifyUTXOsAssetsViaOrdinalService(f *testing.F) { assert.Equal(t, http.StatusOK, resp.StatusCode) // decode the response body - var response handlers.PublicResponse[[]services.SafeUTXOPublic] + var response handler.PublicResponse[[]service.SafeUTXOPublic] err = json.NewDecoder(resp.Body).Decode(&response) if err != nil { t.Fatalf("Failed to decode response body: %v", err) @@ -226,7 +226,7 @@ func createOrdinalServiceResponse(t *testing.T, r *rand.Rand, utxos []types.UTXO return responses } -func createPayload(t *testing.T, r *rand.Rand, netParam *chaincfg.Params, size int) handlers.VerifyUTXOsRequestPayload { +func createPayload(t *testing.T, r *rand.Rand, netParam *chaincfg.Params, size int) handler.VerifyUTXOsRequestPayload { var utxos []types.UTXOIdentifier for i := 0; i < size; i++ { @@ -248,7 +248,7 @@ func createPayload(t *testing.T, r *rand.Rand, netParam *chaincfg.Params, size i if err != nil { t.Fatalf("Failed to generate taproot address from pk: %v", err) } - return handlers.VerifyUTXOsRequestPayload{ + return handler.VerifyUTXOsRequestPayload{ UTXOs: utxos, Address: addresses.Taproot, } diff --git a/tests/integration_test/delegation_test.go b/tests/integration_test/delegation_test.go index 75351b3f..75aa9201 100644 --- a/tests/integration_test/delegation_test.go +++ b/tests/integration_test/delegation_test.go @@ -11,9 +11,9 @@ import ( "github.com/babylonlabs-io/staking-queue-client/client" "github.com/stretchr/testify/assert" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" + handler "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" "github.com/babylonlabs-io/staking-api-service/tests/testutils" ) @@ -35,9 +35,9 @@ func TestGetDelegationByTxHashHex(t *testing.T) { expiredStakingEvent := client.NewExpiredStakingEvent(activeStakingEvent[0].StakingTxHashHex, types.ActiveTxType.ToString()) testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeStakingEvent) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvent) time.Sleep(2 * time.Second) - sendTestMessage(testServer.Queues.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredStakingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredStakingEvent}) time.Sleep(2 * time.Second) // Test the API @@ -53,7 +53,7 @@ func TestGetDelegationByTxHashHex(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var response handlers.PublicResponse[services.DelegationPublic] + var response handler.PublicResponse[v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err, "unmarshalling response body should not fail") diff --git a/tests/integration_test/finality_provider_test.go b/tests/integration_test/finality_provider_test.go index 25fb1bb9..a42cfeab 100644 --- a/tests/integration_test/finality_provider_test.go +++ b/tests/integration_test/finality_provider_test.go @@ -9,16 +9,18 @@ import ( "testing" "time" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" + handler "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/db" + dbclients "github.com/babylonlabs-io/staking-api-service/internal/shared/db/clients" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" testmock "github.com/babylonlabs-io/staking-api-service/tests/mocks" "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "go.mongodb.org/mongo-driver/mongo" ) const ( @@ -29,7 +31,7 @@ func shouldGetFinalityProvidersSuccessfully(t *testing.T, testServer *TestServer url := testServer.Server.URL + finalityProvidersPath defer testServer.Close() - responseBody := fetchSuccessfulResponse[[]services.FpDetailsPublic](t, url) + responseBody := fetchSuccessfulResponse[[]v1service.FpDetailsPublic](t, url) result := responseBody.Data assert.Equal(t, "Babylon Foundation 2", result[2].Description.Moniker) assert.Equal(t, "0.060000000000000000", result[1].Commission) @@ -50,30 +52,41 @@ func TestGetFinalityProvidersSuccessfully(t *testing.T) { } func TestGetFinalityProviderShouldNotFailInCaseOfDbFailure(t *testing.T) { - mockDB := new(testmock.DBClient) - mockDB.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(nil, errors.New("just an error")) - - testServer := setupTestServer(t, &TestServerDependency{MockDbClient: mockDB}) + mockV1DBClient := new(testmock.V1DBClient) + mockV1DBClient.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(nil, errors.New("just an error")) + mockMongoClient := &mongo.Client{} + testServer := setupTestServer(t, &TestServerDependency{MockDbClients: dbclients.DbClients{ + MongoClient: mockMongoClient, + V1DBClient: mockV1DBClient, + }}) shouldGetFinalityProvidersSuccessfully(t, testServer) } func TestGetFinalityProviderShouldReturnFallbackToGlobalParams(t *testing.T) { - mockedResultMap := &db.DbResultMap[*model.FinalityProviderStatsDocument]{ - Data: []*model.FinalityProviderStatsDocument{}, + mockedResultMap := &db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument]{ + Data: []*v1dbmodel.FinalityProviderStatsDocument{}, PaginationToken: "", } - mockDB := new(testmock.DBClient) - mockDB.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(mockedResultMap, nil) - - testServer := setupTestServer(t, &TestServerDependency{MockDbClient: mockDB}) + mockV1DBClient := new(testmock.V1DBClient) + mockV1DBClient.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(mockedResultMap, nil) + mockMongoClient := &mongo.Client{} + + testServer := setupTestServer(t, &TestServerDependency{MockDbClients: dbclients.DbClients{ + MongoClient: mockMongoClient, + V1DBClient: mockV1DBClient, + }}) shouldGetFinalityProvidersSuccessfully(t, testServer) } func TestGetFinalityProviderReturn4xxErrorIfPageTokenInvalid(t *testing.T) { - mockDB := new(testmock.DBClient) - mockDB.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(nil, &db.InvalidPaginationTokenError{}) - - testServer := setupTestServer(t, &TestServerDependency{MockDbClient: mockDB}) + mockV1DBClient := new(testmock.V1DBClient) + mockV1DBClient.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(nil, &db.InvalidPaginationTokenError{}) + mockMongoClient := &mongo.Client{} + + testServer := setupTestServer(t, &TestServerDependency{MockDbClients: dbclients.DbClients{ + MongoClient: mockMongoClient, + V1DBClient: mockV1DBClient, + }}) url := testServer.Server.URL + finalityProvidersPath defer testServer.Close() // Make a GET request to the finality providers endpoint @@ -102,18 +115,23 @@ func FuzzGetFinalityProviderShouldReturnAllRegisteredFps(f *testing.F) { r := rand.New(rand.NewSource(seed)) fpParams, registeredFpsStats, notRegisteredFpsStats := setUpFinalityProvidersStatsDataSet(t, r, nil) - mockDB := new(testmock.DBClient) - mockDB.On("FindFinalityProviderStatsByFinalityProviderPkHex", + mockV1DBClient := new(testmock.V1DBClient) + mockV1DBClient.On("FindFinalityProviderStatsByFinalityProviderPkHex", mock.Anything, mock.Anything, ).Return(registeredFpsStats, nil) - mockedFinalityProviderStats := &db.DbResultMap[*model.FinalityProviderStatsDocument]{ + mockedFinalityProviderStats := &db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument]{ Data: append(registeredFpsStats, notRegisteredFpsStats...), PaginationToken: "", } - mockDB.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(mockedFinalityProviderStats, nil) + mockV1DBClient.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(mockedFinalityProviderStats, nil) - testServer := setupTestServer(t, &TestServerDependency{MockDbClient: mockDB, MockedFinalityProviders: fpParams}) + mockMongoClient := &mongo.Client{} + + testServer := setupTestServer(t, &TestServerDependency{MockDbClients: dbclients.DbClients{ + MongoClient: mockMongoClient, + V1DBClient: mockV1DBClient, + }, MockedFinalityProviders: fpParams}) url := testServer.Server.URL + finalityProvidersPath defer testServer.Close() @@ -129,7 +147,7 @@ func FuzzGetFinalityProviderShouldReturnAllRegisteredFps(f *testing.F) { bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var responseBody handlers.PublicResponse[[]services.FpDetailsPublic] + var responseBody handler.PublicResponse[[]v1service.FpDetailsPublic] err = json.Unmarshal(bodyBytes, &responseBody) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -151,7 +169,7 @@ func FuzzGetFinalityProviderShouldReturnAllRegisteredFps(f *testing.F) { } assert.Equal(t, len(fpParams)+len(notRegisteredFpsStats), len(result)) - resultMap := make(map[string]services.FpDetailsPublic) + resultMap := make(map[string]v1service.FpDetailsPublic) for _, fp := range result { resultMap[fp.BtcPk] = fp } @@ -198,11 +216,11 @@ func FuzzTestGetFinalityProviderWithPaginationResponse(f *testing.F) { testServer := setupTestServer(t, &TestServerDependency{ConfigOverrides: cfg}) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeStakingEvents) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvents) time.Sleep(10 * time.Second) var paginationKey string - var allDataCollected []services.FpDetailsPublic + var allDataCollected []v1service.FpDetailsPublic var atLeastOnePage bool // Test the API for { @@ -212,7 +230,7 @@ func FuzzTestGetFinalityProviderWithPaginationResponse(f *testing.F) { assert.Equal(t, http.StatusOK, resp.StatusCode, "expected HTTP 200 OK status") bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var response handlers.PublicResponse[[]services.FpDetailsPublic] + var response handler.PublicResponse[[]v1service.FpDetailsPublic] err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -240,20 +258,24 @@ func FuzzGetFinalityProviderShouldNotReturnRegisteredFpWithoutStakingForPaginate r := rand.New(rand.NewSource(seed)) fpParams, registeredFpsStats, notRegisteredFpsStats := setUpFinalityProvidersStatsDataSet(t, r, nil) - mockDB := new(testmock.DBClient) - mockDB.On("FindFinalityProviderStatsByFinalityProviderPkHex", + mockV1DBClient := new(testmock.V1DBClient) + mockV1DBClient.On("FindFinalityProviderStatsByFinalityProviderPkHex", mock.Anything, mock.Anything, ).Return(registeredFpsStats, nil) registeredWithoutStakeFpsStats := registeredFpsStats[:len(registeredFpsStats)-testutils.RandomPositiveInt(r, len(registeredFpsStats))] - mockedFinalityProviderStats := &db.DbResultMap[*model.FinalityProviderStatsDocument]{ + mockedFinalityProviderStats := &db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument]{ Data: append(registeredWithoutStakeFpsStats, notRegisteredFpsStats...), PaginationToken: "abcd", } - mockDB.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(mockedFinalityProviderStats, nil) + mockV1DBClient.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(mockedFinalityProviderStats, nil) + mockMongoClient := &mongo.Client{} - testServer := setupTestServer(t, &TestServerDependency{MockDbClient: mockDB, MockedFinalityProviders: fpParams}) + testServer := setupTestServer(t, &TestServerDependency{MockDbClients: dbclients.DbClients{ + MongoClient: mockMongoClient, + V1DBClient: mockV1DBClient, + }, MockedFinalityProviders: fpParams}) url := testServer.Server.URL + finalityProvidersPath defer testServer.Close() @@ -269,7 +291,7 @@ func FuzzGetFinalityProviderShouldNotReturnRegisteredFpWithoutStakingForPaginate bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var responseBody handlers.PublicResponse[[]services.FpDetailsPublic] + var responseBody handler.PublicResponse[[]v1service.FpDetailsPublic] err = json.Unmarshal(bodyBytes, &responseBody) assert.NoError(t, err, "unmarshalling response body should not fail") result := responseBody.Data @@ -299,21 +321,25 @@ func FuzzShouldNotReturnDefaultFpFromParamsWhenPageTokenIsPresent(f *testing.F) } fpParams, registeredFpsStats, _ := setUpFinalityProvidersStatsDataSet(t, r, opts) - mockDB := new(testmock.DBClient) + mockV1DBClient := new(testmock.V1DBClient) // Mock the response for the registered finality providers numOfFpNotHaveStats := testutils.RandomPositiveInt(r, int(opts.NumOfRegisterFps)) - mockDB.On("FindFinalityProviderStatsByFinalityProviderPkHex", + mockV1DBClient.On("FindFinalityProviderStatsByFinalityProviderPkHex", mock.Anything, mock.Anything, ).Return(registeredFpsStats[:len(registeredFpsStats)-numOfFpNotHaveStats], nil) // We are mocking the last page of the response where there is no more data to fetch - mockedFinalityProviderStats := &db.DbResultMap[*model.FinalityProviderStatsDocument]{ - Data: []*model.FinalityProviderStatsDocument{}, + mockedFinalityProviderStats := &db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument]{ + Data: []*v1dbmodel.FinalityProviderStatsDocument{}, PaginationToken: "", } - mockDB.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(mockedFinalityProviderStats, nil) + mockV1DBClient.On("FindFinalityProviderStats", mock.Anything, mock.Anything).Return(mockedFinalityProviderStats, nil) - testServer := setupTestServer(t, &TestServerDependency{MockDbClient: mockDB, MockedFinalityProviders: fpParams}) + mockMongoClient := &mongo.Client{} + testServer := setupTestServer(t, &TestServerDependency{MockDbClients: dbclients.DbClients{ + MongoClient: mockMongoClient, + V1DBClient: mockV1DBClient, + }, MockedFinalityProviders: fpParams}) url := testServer.Server.URL + finalityProvidersPath + "?pagination_key=abcd" defer testServer.Close() @@ -322,7 +348,7 @@ func FuzzShouldNotReturnDefaultFpFromParamsWhenPageTokenIsPresent(f *testing.F) assert.NoError(t, err, "making GET request to finality providers endpoint should not fail") bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var response handlers.PublicResponse[[]services.FpDetailsPublic] + var response handler.PublicResponse[[]v1service.FpDetailsPublic] err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err, "unmarshalling response body should not fail") assert.Equal(t, numOfFpNotHaveStats, len(response.Data)) @@ -335,17 +361,21 @@ func FuzzGetFinalityProvider(f *testing.F) { r := rand.New(rand.NewSource(seed)) fpParams, registeredFpsStats, notRegisteredFpsStats := setUpFinalityProvidersStatsDataSet(t, r, nil) // Manually force a single value for the finality provider to be used in db mocking - fpStats := []*model.FinalityProviderStatsDocument{registeredFpsStats[0]} + fpStats := []*v1dbmodel.FinalityProviderStatsDocument{registeredFpsStats[0]} - mockDB := new(testmock.DBClient) - mockDB.On("FindFinalityProviderStatsByFinalityProviderPkHex", + mockV1DBClient := new(testmock.V1DBClient) + mockV1DBClient.On("FindFinalityProviderStatsByFinalityProviderPkHex", mock.Anything, mock.Anything, ).Return(fpStats, nil) + mockMongoClient := &mongo.Client{} - testServer := setupTestServer(t, &TestServerDependency{MockDbClient: mockDB, MockedFinalityProviders: fpParams}) + testServer := setupTestServer(t, &TestServerDependency{MockDbClients: dbclients.DbClients{ + MongoClient: mockMongoClient, + V1DBClient: mockV1DBClient, + }, MockedFinalityProviders: fpParams}) url := testServer.Server.URL + finalityProvidersPath + "?fp_btc_pk=" + fpParams[0].BtcPk // Make a GET request to the finality providers endpoint - respBody := fetchSuccessfulResponse[[]services.FpDetailsPublic](t, url) + respBody := fetchSuccessfulResponse[[]v1service.FpDetailsPublic](t, url) result := respBody.Data assert.Equal(t, 1, len(result)) assert.Equal(t, fpParams[0].Description.Moniker, result[0].Description.Moniker) @@ -358,19 +388,23 @@ func FuzzGetFinalityProvider(f *testing.F) { testServer.Close() // Test the API with a non-existent finality provider from notRegisteredFpsStats - fpStats = []*model.FinalityProviderStatsDocument{notRegisteredFpsStats[0]} - mockDB = new(testmock.DBClient) - mockDB.On("FindFinalityProviderStatsByFinalityProviderPkHex", + fpStats = []*v1dbmodel.FinalityProviderStatsDocument{notRegisteredFpsStats[0]} + mockV1DBClient = new(testmock.V1DBClient) + mockV1DBClient.On("FindFinalityProviderStatsByFinalityProviderPkHex", mock.Anything, mock.Anything, ).Return(fpStats, nil) testServer = setupTestServer(t, &TestServerDependency{ - MockDbClient: mockDB, MockedFinalityProviders: fpParams, + MockDbClients: dbclients.DbClients{ + MongoClient: mockMongoClient, + V1DBClient: mockV1DBClient, + }, + MockedFinalityProviders: fpParams, }) notRegisteredFp := notRegisteredFpsStats[0] url = testServer.Server.URL + finalityProvidersPath + "?fp_btc_pk=" + notRegisteredFp.FinalityProviderPkHex - respBody = fetchSuccessfulResponse[[]services.FpDetailsPublic](t, url) + respBody = fetchSuccessfulResponse[[]v1service.FpDetailsPublic](t, url) result = respBody.Data assert.Equal(t, 1, len(result)) assert.Equal(t, "", result[0].Description.Moniker) @@ -387,14 +421,14 @@ func FuzzGetFinalityProvider(f *testing.F) { defer testServer.Close() assert.NoError(t, err, "generating random public key should not fail") url = testServer.Server.URL + finalityProvidersPath + "?fp_btc_pk=" + randomPk - respBody = fetchSuccessfulResponse[[]services.FpDetailsPublic](t, url) + respBody = fetchSuccessfulResponse[[]v1service.FpDetailsPublic](t, url) result = respBody.Data assert.Equal(t, 0, len(result)) }) } -func generateFinalityProviderStatsDocument(r *rand.Rand, pk string) *model.FinalityProviderStatsDocument { - return &model.FinalityProviderStatsDocument{ +func generateFinalityProviderStatsDocument(r *rand.Rand, pk string) *v1dbmodel.FinalityProviderStatsDocument { + return &v1dbmodel.FinalityProviderStatsDocument{ FinalityProviderPkHex: pk, ActiveTvl: testutils.RandomAmount(r), TotalTvl: testutils.RandomAmount(r), @@ -408,7 +442,7 @@ type SetupFpStatsDataSetOpts struct { NumOfNotRegisteredFps int } -func setUpFinalityProvidersStatsDataSet(t *testing.T, r *rand.Rand, opts *SetupFpStatsDataSetOpts) ([]types.FinalityProviderDetails, []*model.FinalityProviderStatsDocument, []*model.FinalityProviderStatsDocument) { +func setUpFinalityProvidersStatsDataSet(t *testing.T, r *rand.Rand, opts *SetupFpStatsDataSetOpts) ([]types.FinalityProviderDetails, []*v1dbmodel.FinalityProviderStatsDocument, []*v1dbmodel.FinalityProviderStatsDocument) { numOfRegisterFps := testutils.RandomPositiveInt(r, 10) numOfNotRegisteredFps := testutils.RandomPositiveInt(r, 10) if opts != nil { @@ -418,13 +452,13 @@ func setUpFinalityProvidersStatsDataSet(t *testing.T, r *rand.Rand, opts *SetupF fpParams := testutils.GenerateRandomFinalityProviderDetail(r, uint64(numOfRegisterFps)) // Generate a set of registered finality providers - var registeredFpsStats []*model.FinalityProviderStatsDocument + var registeredFpsStats []*v1dbmodel.FinalityProviderStatsDocument for i := 0; i < numOfRegisterFps; i++ { fpStats := generateFinalityProviderStatsDocument(r, fpParams[i].BtcPk) registeredFpsStats = append(registeredFpsStats, fpStats) } - var notRegisteredFpsStats []*model.FinalityProviderStatsDocument + var notRegisteredFpsStats []*v1dbmodel.FinalityProviderStatsDocument for i := 0; i < numOfNotRegisteredFps; i++ { fpNotRegisteredPk, err := testutils.RandomPk() assert.NoError(t, err, "generating random public key should not fail") diff --git a/tests/integration_test/global_params_test.go b/tests/integration_test/global_params_test.go index 41140f7d..1a7ab4fa 100644 --- a/tests/integration_test/global_params_test.go +++ b/tests/integration_test/global_params_test.go @@ -6,10 +6,9 @@ import ( "net/http" "testing" + handler "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" "github.com/stretchr/testify/assert" - - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/services" ) const ( @@ -34,7 +33,7 @@ func TestGlobalParams(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var responseBody handlers.PublicResponse[services.GlobalParamsPublic] + var responseBody handler.PublicResponse[v1service.GlobalParamsPublic] err = json.Unmarshal(bodyBytes, &responseBody) assert.NoError(t, err, "unmarshalling response body should not fail") diff --git a/tests/integration_test/healthcheck_test.go b/tests/integration_test/healthcheck_test.go index 105d1307..6bfb753c 100644 --- a/tests/integration_test/healthcheck_test.go +++ b/tests/integration_test/healthcheck_test.go @@ -6,9 +6,11 @@ import ( "net/http" "testing" + dbclients "github.com/babylonlabs-io/staking-api-service/internal/shared/db/clients" testmock "github.com/babylonlabs-io/staking-api-service/tests/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "go.mongodb.org/mongo-driver/mongo" ) const ( @@ -43,10 +45,14 @@ func TestHealthCheck(t *testing.T) { // Test the db connection error case func TestHealthCheckDBError(t *testing.T) { - mockDB := new(testmock.DBClient) - mockDB.On("Ping", mock.Anything).Return(io.EOF) // Expect db error + mockV1DBClient := new(testmock.V1DBClient) + mockV1DBClient.On("Ping", mock.Anything).Return(io.EOF) // Expect db error + mockMongoClient := &mongo.Client{} - testServer := setupTestServer(t, &TestServerDependency{MockDbClient: mockDB}) + testServer := setupTestServer(t, &TestServerDependency{MockDbClients: dbclients.DbClients{ + MongoClient: mockMongoClient, + V1DBClient: mockV1DBClient, + }}) defer testServer.Close() @@ -124,4 +130,4 @@ func TestSecurityHeaders(t *testing.T) { assert.Equal(t, "DENY", resp.Header.Get("X-Frame-Options"), "expected X-Frame-Options to be DENY") assert.Equal(t, "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://stackpath.bootstrap.com; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://stackpath.bootstrap.com; img-src 'self' data: https://cdnjs.cloudflare.com https://stackpath.bootstrap.com; font-src 'self' https://cdnjs.cloudflare.com https://stackpath.bootstrap.com; object-src 'none'; frame-ancestors 'self'; form-action 'self'; block-all-mixed-content; base-uri 'self';", resp.Header.Get("Content-Security-Policy"), "expected Swagger Content-Security-Policy") assert.Equal(t, "strict-origin-when-cross-origin", resp.Header.Get("Referrer-Policy"), "expected Referrer-Policy to be strict-origin-when-cross-origin") -} \ No newline at end of file +} diff --git a/tests/integration_test/replay_unprocessed_messages_test.go b/tests/integration_test/replay_unprocessed_messages_test.go index 022c0321..1680d5a5 100644 --- a/tests/integration_test/replay_unprocessed_messages_test.go +++ b/tests/integration_test/replay_unprocessed_messages_test.go @@ -9,8 +9,9 @@ import ( "time" "github.com/babylonlabs-io/staking-api-service/cmd/staking-api-service/scripts" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + dbclient "github.com/babylonlabs-io/staking-api-service/internal/shared/db/client" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/babylonlabs-io/staking-queue-client/client" "github.com/stretchr/testify/assert" @@ -32,12 +33,15 @@ func TestReplayUnprocessableMessages(t *testing.T) { doc := string(data) testutils.InjectDbDocument( - testServer.Config, - model.UnprocessableMsgCollection, - model.NewUnprocessableMessageDocument(doc, "receipt"), + testServer.Config, dbmodel.V1UnprocessableMsgCollection, + dbmodel.NewUnprocessableMessageDocument(doc, "receipt"), ) - db := testutils.DirectDbConnection(testServer.Config) - scripts.ReplayUnprocessableMessages(ctx, testServer.Config, testServer.Queues, db) + dbClients, _ := testutils.DirectDbConnection(testServer.Config) + defer dbClients.MongoClient.Disconnect(ctx) + dbclient, err := dbclient.New(ctx, dbClients.MongoClient, testServer.Config.Db) + assert.NoError(t, err, "creating db client should not return an error") + + scripts.ReplayUnprocessableMessages(ctx, testServer.Config, testServer.Queues, dbclient) time.Sleep(3 * time.Second) @@ -51,7 +55,7 @@ func TestReplayUnprocessableMessages(t *testing.T) { body, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var responseJSON handlers.PublicResponse[[]client.ActiveStakingEvent] + var responseJSON handler.PublicResponse[[]client.ActiveStakingEvent] err = json.Unmarshal(body, &responseJSON) assert.NoError(t, err, "unmarshal response JSON should not return an error") diff --git a/tests/integration_test/setup.go b/tests/integration_test/setup.go index ab23e895..82940c61 100644 --- a/tests/integration_test/setup.go +++ b/tests/integration_test/setup.go @@ -19,19 +19,20 @@ import ( "github.com/rabbitmq/amqp091-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/mongo" queueConfig "github.com/babylonlabs-io/staking-queue-client/config" - "github.com/babylonlabs-io/staking-api-service/internal/api" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/api/middlewares" - "github.com/babylonlabs-io/staking-api-service/internal/clients" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/observability/metrics" - "github.com/babylonlabs-io/staking-api-service/internal/queue" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api/middlewares" + "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/observability/metrics" + queueclients "github.com/babylonlabs-io/staking-api-service/internal/shared/queue/clients" + "github.com/babylonlabs-io/staking-api-service/internal/shared/services" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "github.com/babylonlabs-io/staking-api-service/tests/testutils" ) @@ -39,7 +40,7 @@ var setUpDbIndex = false type TestServerDependency struct { ConfigOverrides *config.Config - MockDbClient db.DBClient + MockDbClients dbclients.DbClients PreInjectEventsHandler func(queueClient client.QueueClient) error MockedFinalityProviders []types.FinalityProviderDetails MockedGlobalParams *types.GlobalParams @@ -48,23 +49,23 @@ type TestServerDependency struct { type TestServer struct { Server *httptest.Server - Queues *queue.Queues + Queues *queueclients.QueueClients Conn *amqp091.Connection channel *amqp091.Channel Config *config.Config - Db *db.Database + Db *mongo.Client } func (ts *TestServer) Close() { ts.Server.Close() - ts.Queues.StopReceivingMessages() + ts.Queues.V1QueueClient.StopReceivingMessages() if err := ts.Conn.Close(); err != nil { log.Fatalf("failed to close connection in test: %v", err) } if err := ts.channel.Close(); err != nil { log.Fatalf("failed to close channel in test: %v", err) } - if err := ts.Db.Client.Disconnect(context.Background()); err != nil { + if err := ts.Db.Disconnect(context.Background()); err != nil { log.Fatalf("failed to close db connection in test: %v", err) } } @@ -115,16 +116,16 @@ func setupTestServer(t *testing.T, dep *TestServerDependency) *TestServer { c = clients.New(cfg) } - services, err := services.New(context.Background(), cfg, params, fps, c) - if err != nil { - t.Fatalf("Failed to initialize services: %v", err) + // setup test db + dbClients := testutils.SetupTestDB(*cfg) + + if dep != nil && (dep.MockDbClients.V1DBClient != nil || dep.MockDbClients.V2DBClient != nil || dep.MockDbClients.MongoClient != nil) { + dbClients = &dep.MockDbClients } - if dep != nil && dep.MockDbClient != nil { - services.DbClient = dep.MockDbClient - } else { - // This means we are using real database, we not mocking anything - testutils.SetupTestDB(*cfg) + services, err := services.New(context.Background(), cfg, params, fps, c, dbClients) + if err != nil { + t.Fatalf("Failed to initialize services: %v", err) } apiServer, err := api.New(context.Background(), cfg, services) @@ -148,20 +149,17 @@ func setupTestServer(t *testing.T, dep *TestServerDependency) *TestServer { // Create an httptest server server := httptest.NewServer(r) - // setup direct db connection - dbConnection := testutils.DirectDbConnection(cfg) - return &TestServer{ Server: server, Queues: queues, Conn: conn, channel: ch, Config: cfg, - Db: dbConnection, + Db: dbClients.MongoClient, } } -func setUpTestQueue(cfg *queueConfig.QueueConfig, service *services.Services) (*queue.Queues, *amqp091.Connection, *amqp091.Channel, error) { +func setUpTestQueue(cfg *queueConfig.QueueConfig, services *services.Services) (*queueclients.QueueClients, *amqp091.Connection, *amqp091.Channel, error) { amqpURI := fmt.Sprintf("amqp://%s:%s@%s", cfg.QueueUser, cfg.QueuePassword, cfg.Url) conn, err := amqp091.Dial(amqpURI) if err != nil { @@ -192,10 +190,10 @@ func setUpTestQueue(cfg *queueConfig.QueueConfig, service *services.Services) (* } // Start the actual queue processing in our codebase - queues := queue.New(cfg, service) - queues.StartReceivingMessages() + queueClients := queueclients.New(context.Background(), cfg, services) + queueClients.StartReceivingMessages() - return queues, conn, ch, nil + return queueClients, conn, ch, nil } // inspectQueueMessageCount inspects the number of messages in the given queue. @@ -274,7 +272,7 @@ func attachRandomSeedsToFuzzer(f *testing.F, numOfSeeds int) { bbndatagen.AddRandomSeedsToFuzzer(f, uint(numOfSeeds)) } -func fetchSuccessfulResponse[T any](t *testing.T, url string) handlers.PublicResponse[T] { +func fetchSuccessfulResponse[T any](t *testing.T, url string) handler.PublicResponse[T] { // Make a GET request to the finality providers endpoint resp, err := http.Get(url) assert.NoError(t, err) @@ -287,7 +285,7 @@ func fetchSuccessfulResponse[T any](t *testing.T, url string) handlers.PublicRes bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var responseBody handlers.PublicResponse[T] + var responseBody handler.PublicResponse[T] err = json.Unmarshal(bodyBytes, &responseBody) assert.NoError(t, err, "unmarshalling response body should not fail") return responseBody diff --git a/tests/integration_test/staker_test.go b/tests/integration_test/staker_test.go index 02db97fe..9717830a 100644 --- a/tests/integration_test/staker_test.go +++ b/tests/integration_test/staker_test.go @@ -9,12 +9,13 @@ import ( "testing" "time" - "github.com/babylonlabs-io/staking-api-service/internal/api" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api" + handler "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" + v1handlers "github.com/babylonlabs-io/staking-api-service/internal/v1/api/handlers" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/babylonlabs-io/staking-queue-client/client" "github.com/stretchr/testify/assert" @@ -54,7 +55,7 @@ func FuzzTestStakerDelegationsWithPaginationResponse(f *testing.F) { } sendTestMessage( - testServer.Queues.ActiveStakingQueueClient, + testServer.Queues.V1QueueClient.ActiveStakingQueueClient, append(activeStakingEventsByStaker1, activeStakingEventsByStaker2...), ) time.Sleep(5 * time.Second) @@ -111,7 +112,7 @@ func TestActiveStakingFetchedByStakerPkWithInvalidPaginationKey(t *testing.T) { }) testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeStakingEvent) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvent) // Wait for 2 seconds to make sure the message is processed time.Sleep(2 * time.Second) @@ -180,7 +181,7 @@ func FuzzCheckStakerActiveDelegations(f *testing.F) { testServer := setupTestServer(t, nil) defer testServer.Close() sendTestMessage( - testServer.Queues.ActiveStakingQueueClient, activeStakingEvents, + testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvents, ) time.Sleep(5 * time.Second) @@ -222,7 +223,7 @@ func FuzzCheckStakerActiveDelegations(f *testing.F) { ) unbondingEvents = append(unbondingEvents, unbondingEvent) } - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, unbondingEvents) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, unbondingEvents) time.Sleep(5 * time.Second) isExist = fetchCheckStakerActiveDelegations(t, testServer, addresses.Taproot, "") @@ -245,7 +246,7 @@ func FuzzCheckStakerActiveDelegationsForToday(f *testing.F) { testServer := setupTestServer(t, nil) defer testServer.Close() sendTestMessage( - testServer.Queues.ActiveStakingQueueClient, activeStakingEvents, + testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvents, ) time.Sleep(3 * time.Second) @@ -270,7 +271,7 @@ func FuzzCheckStakerActiveDelegationsForToday(f *testing.F) { } activeStakingEvents = testutils.GenerateRandomActiveStakingEvents(r, opts) sendTestMessage( - testServer.Queues.ActiveStakingQueueClient, activeStakingEvents, + testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvents, ) time.Sleep(3 * time.Second) @@ -295,7 +296,7 @@ func TestGetDelegationReturnEmptySliceWhenNoDelegation(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var response handlers.PublicResponse[[]services.DelegationPublic] + var response handler.PublicResponse[[]v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -320,7 +321,7 @@ func FuzzStakerDelegationsFilterByState(f *testing.F) { ) sendTestMessage( - testServer.Queues.ActiveStakingQueueClient, + testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEventsByStaker, ) time.Sleep(5 * time.Second) @@ -391,7 +392,7 @@ func fetchCheckStakerActiveDelegations( bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var response handlers.DelegationCheckPublicResponse + var response v1handlers.DelegationCheckPublicResponse err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -402,20 +403,20 @@ func fetchCheckStakerActiveDelegations( func fetchStakerDelegations( t *testing.T, testServer *TestServer, stakerPk string, stateFilter types.DelegationState, -) []services.DelegationPublic { +) []v1service.DelegationPublic { url := testServer.Server.URL + stakerDelegations + "?staker_btc_pk=" + stakerPk if stateFilter != "" { url += "&state=" + stateFilter.ToString() } var paginationKey string - var allDataCollected []services.DelegationPublic + var allDataCollected []v1service.DelegationPublic for { resp, err := http.Get(url + "&pagination_key=" + paginationKey) assert.NoError(t, err, "making GET request to delegations by staker pk should not fail") assert.Equal(t, http.StatusOK, resp.StatusCode, "expected HTTP 200 OK status") bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var response handlers.PublicResponse[[]services.DelegationPublic] + var response handler.PublicResponse[[]v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -441,7 +442,7 @@ func updateDelegationState( filter := bson.M{"_id": txId} update := bson.M{"state": state.ToString()} err := testutils.UpdateDbDocument( - testServer.Db, testServer.Config, model.DelegationCollection, + testServer.Db, testServer.Config, dbmodel.V1DelegationCollection, filter, update, ) assert.NoError(t, err) diff --git a/tests/integration_test/stats_test.go b/tests/integration_test/stats_test.go index 1bc312a6..c54e6641 100644 --- a/tests/integration_test/stats_test.go +++ b/tests/integration_test/stats_test.go @@ -10,10 +10,11 @@ import ( "testing" "time" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/services" + handler "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/babylonlabs-io/staking-queue-client/client" "github.com/stretchr/testify/assert" @@ -42,14 +43,14 @@ func TestStatsShouldBeShardedInDb(t *testing.T) { } testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeStakingEvent) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvent) time.Sleep(2 * time.Second) - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, unbondingEvents) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, unbondingEvents) time.Sleep(2 * time.Second) // directly read from the db to check that we have more than 2 records in the overall stats collection - results, err := testutils.InspectDbDocuments[model.OverallStatsDocument]( - testServer.Config, model.OverallStatsCollection, + results, err := testutils.InspectDbDocuments[v1dbmodel.OverallStatsDocument]( + testServer.Config, dbmodel.V1OverallStatsCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -80,7 +81,7 @@ func TestShouldSkipStatsCalculationForOverflowedStakingEvent(t *testing.T) { testServer := setupTestServer(t, nil) defer testServer.Close() - err := sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) + err := sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) require.NoError(t, err) time.Sleep(2 * time.Second) @@ -96,7 +97,9 @@ func TestShouldSkipStatsCalculationForOverflowedStakingEvent(t *testing.T) { defer resp.Body.Close() // Let's inspect what's stored in the database - results, err := testutils.InspectDbDocuments[model.UnbondingDocument](testServer.Config, model.UnbondingCollection) + results, err := testutils.InspectDbDocuments[v1dbmodel.UnbondingDocument]( + testServer.Config, dbmodel.V1UnbondingCollection, + ) assert.NoError(t, err, "failed to inspect DB documents") assert.Equal(t, 1, len(results), "expected 1 document in the DB") @@ -115,12 +118,12 @@ func TestShouldSkipStatsCalculationForOverflowedStakingEvent(t *testing.T) { UnbondingOutputIndex: 1, } - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) time.Sleep(2 * time.Second) // directly read from the db to check that we only have 1 shard in the overall stats collection - stats, err := testutils.InspectDbDocuments[model.OverallStatsDocument]( - testServer.Config, model.OverallStatsCollection, + stats, err := testutils.InspectDbDocuments[v1dbmodel.OverallStatsDocument]( + testServer.Config, dbmodel.V1OverallStatsCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -146,15 +149,15 @@ func TestShouldNotPerformStatsCalculationForUnbondingTxWhenDelegationIsOverflowe )) testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeStakingEvent) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvent) time.Sleep(2 * time.Second) - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, unbondingEvents) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, unbondingEvents) time.Sleep(2 * time.Second) // directly read from the db to check that we have more than 2 records in the // overall stats collection - results, err := testutils.InspectDbDocuments[model.OverallStatsDocument]( - testServer.Config, model.OverallStatsCollection, + results, err := testutils.InspectDbDocuments[v1dbmodel.OverallStatsDocument]( + testServer.Config, dbmodel.V1OverallStatsCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -190,7 +193,7 @@ func TestStatsEndpoints(t *testing.T) { activeStakingEvent := getTestActiveStakingEvent() testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) time.Sleep(2 * time.Second) // Test the finality endpoint first @@ -240,7 +243,7 @@ func TestStatsEndpoints(t *testing.T) { activeStakingEvent.StakingTxHex, // mocked data, it doesn't matter in stats calculation activeStakingEvent.StakingTxHashHex, // mocked data, it doesn't matter in stats calculation ) - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) time.Sleep(2 * time.Second) // Make a GET request to the finality providers endpoint @@ -277,7 +280,7 @@ func TestStatsEndpoints(t *testing.T) { // Send two new active events, it will increment the stats activeEvents := buildActiveStakingEvent(t, 2) - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeEvents) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeEvents) time.Sleep(2 * time.Second) // Make a GET request to the finality providers endpoint @@ -312,7 +315,7 @@ func TestStatsEndpoints(t *testing.T) { ConfirmedTvl: 90, UnconfirmedTvl: 100, } - sendTestMessage(testServer.Queues.BtcInfoQueueClient, []*client.BtcInfoEvent{btcInfoEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.BtcInfoQueueClient, []*client.BtcInfoEvent{btcInfoEvent}) time.Sleep(2 * time.Second) @@ -341,7 +344,7 @@ func FuzzReturnStakerStatsByStakerPk(f *testing.F) { Stakers: testutils.GeneratePks(10), EnforceNotOverflow: true, }) - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, events) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, events) time.Sleep(10 * time.Second) // Find the unique staker pks @@ -391,7 +394,7 @@ func FuzzStatsEndpointReturnHighestUnconfirmedTvlFromEvents(f *testing.F) { highestHeightEvent = btcInfoEvent } } - sendTestMessage(testServer.Queues.BtcInfoQueueClient, messages) + sendTestMessage(testServer.Queues.V1QueueClient.BtcInfoQueueClient, messages) time.Sleep(5 * time.Second) overallStats = fetchOverallStatsEndpoint(t, testServer) @@ -428,14 +431,14 @@ func FuzzTestTopStakersWithPaginationResponse(f *testing.F) { testServer := setupTestServer(t, &TestServerDependency{ConfigOverrides: cfg}) defer testServer.Close() sendTestMessage( - testServer.Queues.ActiveStakingQueueClient, + testServer.Queues.V1QueueClient.ActiveStakingQueueClient, events, ) time.Sleep(5 * time.Second) // Test the API url := testServer.Server.URL + topStakerStatsPath var paginationKey string - var allDataCollected []services.StakerStatsPublic + var allDataCollected []v1service.StakerStatsPublic var numOfRequestsToFetchAllResults int for { numOfRequestsToFetchAllResults++ @@ -444,7 +447,7 @@ func FuzzTestTopStakersWithPaginationResponse(f *testing.F) { assert.Equal(t, http.StatusOK, resp.StatusCode, "expected HTTP 200 OK status") bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var response handlers.PublicResponse[[]services.StakerStatsPublic] + var response handler.PublicResponse[[]v1service.StakerStatsPublic] err = json.Unmarshal(bodyBytes, &response) assert.NoError(t, err, "unmarshalling response body should not fail") // Check that the response body is as expected @@ -466,7 +469,7 @@ func FuzzTestTopStakersWithPaginationResponse(f *testing.F) { }) } -func fetchFinalityEndpoint(t *testing.T, testServer *TestServer) []services.FpDetailsPublic { +func fetchFinalityEndpoint(t *testing.T, testServer *TestServer) []v1service.FpDetailsPublic { url := testServer.Server.URL + finalityProvidersPath // Make a GET request to the finality providers endpoint resp, err := http.Get(url) @@ -480,14 +483,14 @@ func fetchFinalityEndpoint(t *testing.T, testServer *TestServer) []services.FpDe bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var responseBody handlers.PublicResponse[[]services.FpDetailsPublic] + var responseBody handler.PublicResponse[[]v1service.FpDetailsPublic] err = json.Unmarshal(bodyBytes, &responseBody) assert.NoError(t, err, "unmarshalling response body should not fail") return responseBody.Data } -func fetchOverallStatsEndpoint(t *testing.T, testServer *TestServer) services.OverallStatsPublic { +func fetchOverallStatsEndpoint(t *testing.T, testServer *TestServer) v1service.OverallStatsPublic { url := testServer.Server.URL + overallStatsEndpoint // Make a GET request to the stats endpoint resp, err := http.Get(url) @@ -501,14 +504,14 @@ func fetchOverallStatsEndpoint(t *testing.T, testServer *TestServer) services.Ov bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var responseBody handlers.PublicResponse[services.OverallStatsPublic] + var responseBody handler.PublicResponse[v1service.OverallStatsPublic] err = json.Unmarshal(bodyBytes, &responseBody) assert.NoError(t, err, "unmarshalling response body should not fail") return responseBody.Data } -func fetchStakerStatsEndpoint(t *testing.T, testServer *TestServer, stakerPk string) []services.StakerStatsPublic { +func fetchStakerStatsEndpoint(t *testing.T, testServer *TestServer, stakerPk string) []v1service.StakerStatsPublic { url := testServer.Server.URL + topStakerStatsPath if stakerPk != "" { url += "?staker_btc_pk=" + stakerPk @@ -523,7 +526,7 @@ func fetchStakerStatsEndpoint(t *testing.T, testServer *TestServer, stakerPk str bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var responseBody handlers.PublicResponse[[]services.StakerStatsPublic] + var responseBody handler.PublicResponse[[]v1service.StakerStatsPublic] err = json.Unmarshal(bodyBytes, &responseBody) assert.NoError(t, err, "unmarshalling response body should not fail") diff --git a/tests/integration_test/timelock_test.go b/tests/integration_test/timelock_test.go index 989e55b4..4c605ca0 100644 --- a/tests/integration_test/timelock_test.go +++ b/tests/integration_test/timelock_test.go @@ -4,7 +4,8 @@ import ( "testing" "time" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + v1model "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/stretchr/testify/assert" ) @@ -14,13 +15,13 @@ func TestSaveTimelock(t *testing.T) { activeStakingEvent := buildActiveStakingEvent(t, 1) testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeStakingEvent) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvent) // Wait for 2 seconds to make sure the message is processed time.Sleep(2 * time.Second) // Check from DB if the data is saved - results, err := testutils.InspectDbDocuments[model.TimeLockDocument]( - testServer.Config, model.TimeLockCollection, + results, err := testutils.InspectDbDocuments[v1model.TimeLockDocument]( + testServer.Config, dbmodel.V1TimeLockCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -39,15 +40,15 @@ func TestNotSaveExpireCheckIfAlreadyProcessed(t *testing.T) { activeStakingEvent := buildActiveStakingEvent(t, 1) testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeStakingEvent) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvent) // Send again - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, activeStakingEvent) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, activeStakingEvent) // Wait for 2 seconds to make sure the message is processed time.Sleep(5 * time.Second) // Check from DB if the data is saved - results, err := testutils.InspectDbDocuments[model.TimeLockDocument]( - testServer.Config, model.TimeLockCollection, + results, err := testutils.InspectDbDocuments[v1model.TimeLockDocument]( + testServer.Config, dbmodel.V1TimeLockCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -62,11 +63,11 @@ func TestNotSaveExpireCheckIfAlreadyProcessed(t *testing.T) { // Now, let's inject the same data but with different expireHeight eventWithDifferentExpireHeight := buildActiveStakingEvent(t, 1) - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, eventWithDifferentExpireHeight) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, eventWithDifferentExpireHeight) time.Sleep(2 * time.Second) - results, err = testutils.InspectDbDocuments[model.TimeLockDocument]( - testServer.Config, model.TimeLockCollection, + results, err = testutils.InspectDbDocuments[v1model.TimeLockDocument]( + testServer.Config, dbmodel.V1TimeLockCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) diff --git a/tests/integration_test/unbonding_test.go b/tests/integration_test/unbonding_test.go index 17845908..2732ed1c 100644 --- a/tests/integration_test/unbonding_test.go +++ b/tests/integration_test/unbonding_test.go @@ -9,19 +9,22 @@ import ( "testing" "time" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api" + handler "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers/handler" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + dbclients "github.com/babylonlabs-io/staking-api-service/internal/shared/db/clients" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1handlers "github.com/babylonlabs-io/staking-api-service/internal/v1/api/handlers" + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" + v1service "github.com/babylonlabs-io/staking-api-service/internal/v1/service" + testmock "github.com/babylonlabs-io/staking-api-service/tests/mocks" + "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/babylonlabs-io/staking-queue-client/client" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - - "github.com/babylonlabs-io/staking-api-service/internal/api" - "github.com/babylonlabs-io/staking-api-service/internal/api/handlers" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/services" - "github.com/babylonlabs-io/staking-api-service/internal/types" - testmock "github.com/babylonlabs-io/staking-api-service/tests/mocks" - "github.com/babylonlabs-io/staking-api-service/tests/testutils" + "go.mongodb.org/mongo-driver/mongo" ) const ( @@ -34,7 +37,7 @@ func TestUnbondingRequest(t *testing.T) { testServer := setupTestServer(t, nil) defer testServer.Close() - err := sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) + err := sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) require.NoError(t, err) time.Sleep(2 * time.Second) @@ -106,7 +109,7 @@ func TestUnbondingRequest(t *testing.T) { bodyBytes, err = io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var getStakerDelegationResponse handlers.PublicResponse[[]services.DelegationPublic] + var getStakerDelegationResponse handler.PublicResponse[[]v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &getStakerDelegationResponse) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -115,8 +118,8 @@ func TestUnbondingRequest(t *testing.T) { assert.Equal(t, types.UnbondingRequested.ToString(), getStakerDelegationResponse.Data[0].State, "state should be unbonding requested") // Let's inspect what's stored in the database - results, err := testutils.InspectDbDocuments[model.UnbondingDocument]( - testServer.Config, model.UnbondingCollection, + results, err := testutils.InspectDbDocuments[v1dbmodel.UnbondingDocument]( + testServer.Config, dbmodel.V1UnbondingCollection, ) assert.NoError(t, err, "failed to inspect DB documents") @@ -180,8 +183,8 @@ func getTestActiveStakingEvent() *client.ActiveStakingEvent { } } -func getTestUnbondDelegationRequestPayload(stakingTxHashHex string) handlers.UnbondDelegationRequestPayload { - return handlers.UnbondDelegationRequestPayload{ +func getTestUnbondDelegationRequestPayload(stakingTxHashHex string) v1handlers.UnbondDelegationRequestPayload { + return v1handlers.UnbondDelegationRequestPayload{ StakingTxHashHex: stakingTxHashHex, UnbondingTxHashHex: "17aad3b01a9fa134f0e6374b9ad1b049376a9bdbd889afc369dcb8074bbdf1b3", UnbondingTxHex: "02000000000101378058b8745137ceb641141ef0609e75477960e5f6986455d3c59c7a7798cb060100000000ffffffff01905f010000000000225120ba296d88e83fd2faf5864ddda74ac8d0df6a7d76b39d5ef4c0751117a26cfd3104403c3d32c844ff751de59190ffc57427794450ba97b0d6ead43d53d865b34021abb52a6370382cfc46416f42f28d32704e504f84720d8458b5d51fee69d28cf94940728ab06ac8ab2f14b4c60cacb0e8932760f1559df93dd9053327e72cec53fc9749d5abaa4a96758b7f3588b3ad80e4c157233f1e689a37bea3ccaa9e50ba153ece2091fef91f0f00d010d6c918acb9197db6cd33d99ee617090fa8cb61fa2a31405aad2057349e985e742d5131e1e2b227b5170f6350ac2e2feb72254fcc25b3cee21a18ac2059d3532148a597a2d05c0395bf5f7176044b1cd312f37701a9b4d0aad70bc5a4ba20a5c60c2188e833d39d0fa798ab3f69aa12ed3dd2f3bad659effa252782de3c31ba20c8ccb03c379e452f10c81232b41a1ca8b63d0baf8387e57d302c987e5abb8527ba20ffeaec52a9b407b355ef6967a7ffc15fd6c3fe07de2844d61550475e7a5233e5ba539c61c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0957294d847c7353393803c6a2c03d6b777bb2cbb69aa4c85291d2d9bb981bb59cc2f4c8bba33b96629a6e942aad0c9815a4e5e7322f63e521e762371b71e3c1300000000", @@ -194,7 +197,7 @@ func TestProcessUnbondingStakingEvent(t *testing.T) { testServer := setupTestServer(t, nil) defer testServer.Close() - err := sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []*client.ActiveStakingEvent{activeStakingEvent}) + err := sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []*client.ActiveStakingEvent{activeStakingEvent}) require.NoError(t, err) time.Sleep(2 * time.Second) @@ -210,8 +213,8 @@ func TestProcessUnbondingStakingEvent(t *testing.T) { defer resp.Body.Close() // Let's inspect what's stored in the database - results, err := testutils.InspectDbDocuments[model.UnbondingDocument]( - testServer.Config, model.UnbondingCollection, + results, err := testutils.InspectDbDocuments[v1dbmodel.UnbondingDocument]( + testServer.Config, dbmodel.V1UnbondingCollection, ) assert.NoError(t, err, "failed to inspect DB documents") @@ -231,7 +234,7 @@ func TestProcessUnbondingStakingEvent(t *testing.T) { UnbondingOutputIndex: 1, } - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) time.Sleep(2 * time.Second) // Let's GET the delegation from API @@ -246,7 +249,7 @@ func TestProcessUnbondingStakingEvent(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var getStakerDelegationResponse handlers.PublicResponse[[]services.DelegationPublic] + var getStakerDelegationResponse handler.PublicResponse[[]v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &getStakerDelegationResponse) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -262,8 +265,8 @@ func TestProcessUnbondingStakingEvent(t *testing.T) { assert.NoError(t, err, "expected timestamp to be in RFC3339 format") // Let's also fetch the DB to make sure the expired check is processed - timeLockResults, err := testutils.InspectDbDocuments[model.TimeLockDocument]( - testServer.Config, model.TimeLockCollection, + timeLockResults, err := testutils.InspectDbDocuments[v1dbmodel.TimeLockDocument]( + testServer.Config, dbmodel.V1TimeLockCollection, ) assert.NoError(t, err, "failed to inspect DB documents") @@ -282,7 +285,7 @@ func TestProcessUnbondingStakingEventDuringBootstrap(t *testing.T) { testServer := setupTestServer(t, nil) defer testServer.Close() - err := sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []*client.ActiveStakingEvent{activeStakingEvent}) + err := sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []*client.ActiveStakingEvent{activeStakingEvent}) require.NoError(t, err) time.Sleep(2 * time.Second) @@ -302,7 +305,7 @@ func TestProcessUnbondingStakingEventDuringBootstrap(t *testing.T) { UnbondingOutputIndex: 1, } - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) time.Sleep(2 * time.Second) // Let's GET the delegation from API @@ -318,7 +321,7 @@ func TestProcessUnbondingStakingEventDuringBootstrap(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var getStakerDelegationResponse handlers.PublicResponse[[]services.DelegationPublic] + var getStakerDelegationResponse handler.PublicResponse[[]v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &getStakerDelegationResponse) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -331,8 +334,8 @@ func TestProcessUnbondingStakingEventDuringBootstrap(t *testing.T) { assert.Equal(t, unbondingEvent.UnbondingTxHex, getStakerDelegationResponse.Data[0].UnbondingTx.TxHex, "expected unbonding tx to match") // Let's also fetch the DB to make sure the expired check is processed - timeLockResults, err := testutils.InspectDbDocuments[model.TimeLockDocument]( - testServer.Config, model.TimeLockCollection, + timeLockResults, err := testutils.InspectDbDocuments[v1dbmodel.TimeLockDocument]( + testServer.Config, dbmodel.V1TimeLockCollection, ) assert.NoError(t, err, "failed to inspect DB documents") @@ -351,7 +354,7 @@ func TestShouldIgnoreOutdatedUnbondingEvent(t *testing.T) { testServer := setupTestServer(t, nil) defer testServer.Close() - err := sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []*client.ActiveStakingEvent{activeStakingEvent}) + err := sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []*client.ActiveStakingEvent{activeStakingEvent}) require.NoError(t, err) time.Sleep(2 * time.Second) @@ -368,7 +371,7 @@ func TestShouldIgnoreOutdatedUnbondingEvent(t *testing.T) { UnbondingOutputIndex: 1, } - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) time.Sleep(2 * time.Second) // Let's GET the delegation from API @@ -384,7 +387,7 @@ func TestShouldIgnoreOutdatedUnbondingEvent(t *testing.T) { bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var getStakerDelegationResponse handlers.PublicResponse[[]services.DelegationPublic] + var getStakerDelegationResponse handler.PublicResponse[[]v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &getStakerDelegationResponse) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -397,8 +400,8 @@ func TestShouldIgnoreOutdatedUnbondingEvent(t *testing.T) { assert.Equal(t, unbondingEvent.UnbondingTxHex, getStakerDelegationResponse.Data[0].UnbondingTx.TxHex, "expected unbonding tx to match") // Let's also fetch the DB to make sure the expired check is processed - timeLockResults, err := testutils.InspectDbDocuments[model.TimeLockDocument]( - testServer.Config, model.TimeLockCollection, + timeLockResults, err := testutils.InspectDbDocuments[v1dbmodel.TimeLockDocument]( + testServer.Config, dbmodel.V1TimeLockCollection, ) assert.NoError(t, err, "failed to inspect DB documents") @@ -412,12 +415,12 @@ func TestShouldIgnoreOutdatedUnbondingEvent(t *testing.T) { assert.Equal(t, types.UnbondingTxType.ToString(), timeLockResults[1].TxType) // Let's send an outdated unbonding event - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) time.Sleep(2 * time.Second) // Fetch from the expire checker to make sure we only processed the unbonding event once - timeLockResults, err = testutils.InspectDbDocuments[model.TimeLockDocument]( - testServer.Config, model.TimeLockCollection, + timeLockResults, err = testutils.InspectDbDocuments[v1dbmodel.TimeLockDocument]( + testServer.Config, dbmodel.V1TimeLockCollection, ) assert.NoError(t, err, "failed to inspect DB documents") @@ -443,17 +446,17 @@ func TestProcessUnbondingStakingEventShouldTolerateEventMsgOutOfOrder(t *testing UnbondingOutputIndex: 1, } - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) time.Sleep(2 * time.Second) // Check DB, there should be no unbonding document - results, err := testutils.InspectDbDocuments[model.UnbondingDocument]( - testServer.Config, model.UnbondingCollection, + results, err := testutils.InspectDbDocuments[v1dbmodel.UnbondingDocument]( + testServer.Config, dbmodel.V1UnbondingCollection, ) assert.NoError(t, err, "failed to inspect DB documents") assert.Empty(t, results, "expected no unbonding document in the DB") // Send the active event - err = sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []*client.ActiveStakingEvent{activeStakingEvent}) + err = sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []*client.ActiveStakingEvent{activeStakingEvent}) require.NoError(t, err) time.Sleep(10 * time.Second) @@ -471,7 +474,7 @@ func TestProcessUnbondingStakingEventShouldTolerateEventMsgOutOfOrder(t *testing bodyBytes, err := io.ReadAll(resp.Body) assert.NoError(t, err, "reading response body should not fail") - var getStakerDelegationResponse handlers.PublicResponse[[]services.DelegationPublic] + var getStakerDelegationResponse handler.PublicResponse[[]v1service.DelegationPublic] err = json.Unmarshal(bodyBytes, &getStakerDelegationResponse) assert.NoError(t, err, "unmarshalling response body should not fail") @@ -483,8 +486,8 @@ func TestProcessUnbondingStakingEventShouldTolerateEventMsgOutOfOrder(t *testing assert.Equal(t, unbondingEvent.UnbondingTxHex, getStakerDelegationResponse.Data[0].UnbondingTx.TxHex, "expected unbonding tx to match") // Let's also fetch the DB to make sure the expired check is processed - timeLockResults, err := testutils.InspectDbDocuments[model.TimeLockDocument]( - testServer.Config, model.TimeLockCollection, + timeLockResults, err := testutils.InspectDbDocuments[v1dbmodel.TimeLockDocument]( + testServer.Config, dbmodel.V1TimeLockCollection, ) assert.NoError(t, err, "failed to inspect DB documents") @@ -500,17 +503,21 @@ func TestProcessUnbondingStakingEventShouldTolerateEventMsgOutOfOrder(t *testing func TestUnbondingRequestValidation(t *testing.T) { r := rand.New(rand.NewSource(time.Now().UnixNano())) - mockDB := new(testmock.DBClient) - mockDB.On("FindDelegationByTxHashHex", mock.Anything, mock.Anything).Return( - &model.DelegationDocument{ + mockV1DBClient := new(testmock.V1DBClient) + mockV1DBClient.On("FindDelegationByTxHashHex", mock.Anything, mock.Anything).Return( + &v1dbmodel.DelegationDocument{ State: "active", - StakingTx: &model.TimelockTransaction{ + StakingTx: &v1dbmodel.TimelockTransaction{ StartHeight: 300, }, }, nil, ) - testServer := setupTestServer(t, &TestServerDependency{MockDbClient: mockDB}) + mockMongoClient := &mongo.Client{} + testServer := setupTestServer(t, &TestServerDependency{MockDbClients: dbclients.DbClients{ + MongoClient: mockMongoClient, + V1DBClient: mockV1DBClient, + }}) defer testServer.Close() unbondingUrl := testServer.Server.URL + unbondingPath @@ -524,7 +531,7 @@ func TestUnbondingRequestValidation(t *testing.T) { ) _, fakeSig := testutils.RandomBytes(r, 64) - payload := handlers.UnbondDelegationRequestPayload{ + payload := v1handlers.UnbondDelegationRequestPayload{ StakingTxHashHex: stakingTxHashHex, // unbondingTxHashHex does not match the hash of unbondingTxHex as it is // generated randomly @@ -554,7 +561,7 @@ func TestUnbondingRequestValidation(t *testing.T) { unbondingTxHashHex = unbondingTx.TxHash().String() _, fakeSig = testutils.RandomBytes(r, 64) - payload = handlers.UnbondDelegationRequestPayload{ + payload = v1handlers.UnbondDelegationRequestPayload{ StakingTxHashHex: stakingTxHashHex, // unbondingTxHashHex does not match the hash of unbondingTxHex as it is // generated randomly diff --git a/tests/integration_test/unprocessable_message_test.go b/tests/integration_test/unprocessable_message_test.go index 48f67088..5693df42 100644 --- a/tests/integration_test/unprocessable_message_test.go +++ b/tests/integration_test/unprocessable_message_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/babylonlabs-io/staking-queue-client/client" "github.com/stretchr/testify/assert" @@ -13,13 +13,13 @@ import ( func TestUnprocessableMessageShouldBeStoredInDB(t *testing.T) { testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage[string](testServer.Queues.ActiveStakingQueueClient, []string{"a rubbish message"}) + sendTestMessage[string](testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []string{"a rubbish message"}) // In test, we retry 3 times. (config is 2, but counting start from 0) time.Sleep(20 * time.Second) // Fetch from DB and check - docs, err := testutils.InspectDbDocuments[model.UnprocessableMessageDocument]( - testServer.Config, model.UnprocessableMsgCollection, + docs, err := testutils.InspectDbDocuments[dbmodel.UnprocessableMessageDocument]( + testServer.Config, dbmodel.V1UnprocessableMsgCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) diff --git a/tests/integration_test/withdraw_staking_test.go b/tests/integration_test/withdraw_staking_test.go index 1c08e67e..f81b5831 100644 --- a/tests/integration_test/withdraw_staking_test.go +++ b/tests/integration_test/withdraw_staking_test.go @@ -7,8 +7,9 @@ import ( "testing" "time" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + v1model "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/babylonlabs-io/staking-queue-client/client" "github.com/stretchr/testify/assert" @@ -18,14 +19,14 @@ func TestWithdrawFromActiveStaking(t *testing.T) { activeStakingEvent := getTestActiveStakingEvent() testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) // Wait for 2 seconds to make sure the message is processed time.Sleep(2 * time.Second) // Check from DB that this delegatin exist and has the state of active - results, err := testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err := testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -43,12 +44,12 @@ func TestWithdrawFromActiveStaking(t *testing.T) { TxType: types.ActiveTxType.ToString(), } - sendTestMessage(testServer.Queues.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredEvent}) time.Sleep(2 * time.Second) // Check from DB that this delegatin is in "unbonded" state - results, err = testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err = testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -65,12 +66,12 @@ func TestWithdrawFromActiveStaking(t *testing.T) { StakingTxHashHex: activeStakingEvent.StakingTxHashHex, } - sendTestMessage(testServer.Queues.WithdrawStakingQueueClient, []client.WithdrawStakingEvent{withdrawEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.WithdrawStakingQueueClient, []client.WithdrawStakingEvent{withdrawEvent}) time.Sleep(2 * time.Second) // Check the DB, now it shall be "withdrawn" state - results, err = testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err = testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -86,14 +87,14 @@ func TestWithdrawFromStakingHasUnbondingRequested(t *testing.T) { activeStakingEvent := getTestActiveStakingEvent() testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) // Wait for 2 seconds to make sure the message is processed time.Sleep(2 * time.Second) // Check from DB that this delegatin exist and has the state of active - results, err := testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err := testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -126,7 +127,7 @@ func TestWithdrawFromStakingHasUnbondingRequested(t *testing.T) { UnbondingOutputIndex: 1, } - sendTestMessage(testServer.Queues.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.UnbondingStakingQueueClient, []client.UnbondingStakingEvent{unbondingEvent}) time.Sleep(2 * time.Second) // Send the timelock expire event so that the state change to "unbonded" @@ -136,12 +137,12 @@ func TestWithdrawFromStakingHasUnbondingRequested(t *testing.T) { TxType: types.UnbondingTxType.ToString(), } - sendTestMessage(testServer.Queues.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredEvent}) time.Sleep(2 * time.Second) // Check from DB that this delegatin is in "unbonded" state - results, err = testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err = testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -158,12 +159,12 @@ func TestWithdrawFromStakingHasUnbondingRequested(t *testing.T) { StakingTxHashHex: activeStakingEvent.StakingTxHashHex, } - sendTestMessage(testServer.Queues.WithdrawStakingQueueClient, []client.WithdrawStakingEvent{withdrawEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.WithdrawStakingQueueClient, []client.WithdrawStakingEvent{withdrawEvent}) time.Sleep(2 * time.Second) // Check the DB, now it shall be "withdrawn" state - results, err = testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err = testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -179,14 +180,14 @@ func TestProcessWithdrawStakingEventShouldTolerateEventMsgOutOfOrder(t *testing. activeStakingEvent := getTestActiveStakingEvent() testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) // Wait for 2 seconds to make sure the message is processed time.Sleep(2 * time.Second) // Check from DB that this delegatin exist and has the state of active - results, err := testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err := testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -203,12 +204,12 @@ func TestProcessWithdrawStakingEventShouldTolerateEventMsgOutOfOrder(t *testing. StakingTxHashHex: activeStakingEvent.StakingTxHashHex, } - sendTestMessage(testServer.Queues.WithdrawStakingQueueClient, []client.WithdrawStakingEvent{withdrawEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.WithdrawStakingQueueClient, []client.WithdrawStakingEvent{withdrawEvent}) time.Sleep(2 * time.Second) // Check the DB, it should still be "active" state as the withdraw event will be requeued - results, err = testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err = testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -223,12 +224,12 @@ func TestProcessWithdrawStakingEventShouldTolerateEventMsgOutOfOrder(t *testing. TxType: types.ActiveTxType.ToString(), } - sendTestMessage(testServer.Queues.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredEvent}) time.Sleep(10 * time.Second) // Check the DB after a while, now it shall be "withdrawn" state - results, err = testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err = testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -244,7 +245,7 @@ func TestShouldIgnoreWithdrawnEventIfAlreadyWithdrawn(t *testing.T) { activeStakingEvent := getTestActiveStakingEvent() testServer := setupTestServer(t, nil) defer testServer.Close() - sendTestMessage(testServer.Queues.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ActiveStakingQueueClient, []client.ActiveStakingEvent{*activeStakingEvent}) // Wait for 2 seconds to make sure the message is processed time.Sleep(2 * time.Second) @@ -255,7 +256,7 @@ func TestShouldIgnoreWithdrawnEventIfAlreadyWithdrawn(t *testing.T) { TxType: types.ActiveTxType.ToString(), } - sendTestMessage(testServer.Queues.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.ExpiredStakingQueueClient, []client.ExpiredStakingEvent{expiredEvent}) time.Sleep(10 * time.Second) // Send the withdraw event before timelock expire event which would change the state to unbonded @@ -264,12 +265,12 @@ func TestShouldIgnoreWithdrawnEventIfAlreadyWithdrawn(t *testing.T) { StakingTxHashHex: activeStakingEvent.StakingTxHashHex, } - sendTestMessage(testServer.Queues.WithdrawStakingQueueClient, []client.WithdrawStakingEvent{withdrawEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.WithdrawStakingQueueClient, []client.WithdrawStakingEvent{withdrawEvent}) time.Sleep(2 * time.Second) // Check the DB after a while, now it shall be "withdrawn" state - results, err := testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err := testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) @@ -281,12 +282,12 @@ func TestShouldIgnoreWithdrawnEventIfAlreadyWithdrawn(t *testing.T) { assert.Equal(t, types.Withdrawn, results[0].State, "expected state to be unbonded") // Send again the withdraw event, it should be ignored - sendTestMessage(testServer.Queues.WithdrawStakingQueueClient, []client.WithdrawStakingEvent{withdrawEvent}) + sendTestMessage(testServer.Queues.V1QueueClient.WithdrawStakingQueueClient, []client.WithdrawStakingEvent{withdrawEvent}) time.Sleep(2 * time.Second) // Check the DB, nothing should be changed. - results, err = testutils.InspectDbDocuments[model.DelegationDocument]( - testServer.Config, model.DelegationCollection, + results, err = testutils.InspectDbDocuments[v1model.DelegationDocument]( + testServer.Config, dbmodel.V1DelegationCollection, ) if err != nil { t.Fatalf("Failed to inspect DB documents: %v", err) diff --git a/tests/mocks/mock_db_client.go b/tests/mocks/mock_db_client.go index 1b071521..2911d21c 100644 --- a/tests/mocks/mock_db_client.go +++ b/tests/mocks/mock_db_client.go @@ -1,16 +1,13 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks import ( context "context" - db "github.com/babylonlabs-io/staking-api-service/internal/db" - mock "github.com/stretchr/testify/mock" - - model "github.com/babylonlabs-io/staking-api-service/internal/db/model" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" - types "github.com/babylonlabs-io/staking-api-service/internal/types" + mock "github.com/stretchr/testify/mock" ) // DBClient is an autogenerated mock type for the DBClient type @@ -18,34 +15,6 @@ type DBClient struct { mock.Mock } -// CheckDelegationExistByStakerPk provides a mock function with given fields: ctx, address, extraFilter -func (_m *DBClient) CheckDelegationExistByStakerPk(ctx context.Context, address string, extraFilter *db.DelegationFilter) (bool, error) { - ret := _m.Called(ctx, address, extraFilter) - - if len(ret) == 0 { - panic("no return value specified for CheckDelegationExistByStakerPk") - } - - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, *db.DelegationFilter) (bool, error)); ok { - return rf(ctx, address, extraFilter) - } - if rf, ok := ret.Get(0).(func(context.Context, string, *db.DelegationFilter) bool); ok { - r0 = rf(ctx, address, extraFilter) - } else { - r0 = ret.Get(0).(bool) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, *db.DelegationFilter) error); ok { - r1 = rf(ctx, address, extraFilter) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // DeleteUnprocessableMessage provides a mock function with given fields: ctx, Receipt func (_m *DBClient) DeleteUnprocessableMessage(ctx context.Context, Receipt interface{}) error { ret := _m.Called(ctx, Receipt) @@ -64,144 +33,24 @@ func (_m *DBClient) DeleteUnprocessableMessage(ctx context.Context, Receipt inte return r0 } -// FindDelegationByTxHashHex provides a mock function with given fields: ctx, txHashHex -func (_m *DBClient) FindDelegationByTxHashHex(ctx context.Context, txHashHex string) (*model.DelegationDocument, error) { - ret := _m.Called(ctx, txHashHex) - - if len(ret) == 0 { - panic("no return value specified for FindDelegationByTxHashHex") - } - - var r0 *model.DelegationDocument - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*model.DelegationDocument, error)); ok { - return rf(ctx, txHashHex) - } - if rf, ok := ret.Get(0).(func(context.Context, string) *model.DelegationDocument); ok { - r0 = rf(ctx, txHashHex) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.DelegationDocument) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, txHashHex) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// FindDelegationsByStakerPk provides a mock function with given fields: ctx, stakerPk, extraFilter, paginationToken -func (_m *DBClient) FindDelegationsByStakerPk(ctx context.Context, stakerPk string, extraFilter *db.DelegationFilter, paginationToken string) (*db.DbResultMap[model.DelegationDocument], error) { - ret := _m.Called(ctx, stakerPk, extraFilter, paginationToken) - - if len(ret) == 0 { - panic("no return value specified for FindDelegationsByStakerPk") - } - - var r0 *db.DbResultMap[model.DelegationDocument] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, *db.DelegationFilter, string) (*db.DbResultMap[model.DelegationDocument], error)); ok { - return rf(ctx, stakerPk, extraFilter, paginationToken) - } - if rf, ok := ret.Get(0).(func(context.Context, string, *db.DelegationFilter, string) *db.DbResultMap[model.DelegationDocument]); ok { - r0 = rf(ctx, stakerPk, extraFilter, paginationToken) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*db.DbResultMap[model.DelegationDocument]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, *db.DelegationFilter, string) error); ok { - r1 = rf(ctx, stakerPk, extraFilter, paginationToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// FindFinalityProviderStats provides a mock function with given fields: ctx, paginationToken -func (_m *DBClient) FindFinalityProviderStats(ctx context.Context, paginationToken string) (*db.DbResultMap[*model.FinalityProviderStatsDocument], error) { - ret := _m.Called(ctx, paginationToken) - - if len(ret) == 0 { - panic("no return value specified for FindFinalityProviderStats") - } - - var r0 *db.DbResultMap[*model.FinalityProviderStatsDocument] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*db.DbResultMap[*model.FinalityProviderStatsDocument], error)); ok { - return rf(ctx, paginationToken) - } - if rf, ok := ret.Get(0).(func(context.Context, string) *db.DbResultMap[*model.FinalityProviderStatsDocument]); ok { - r0 = rf(ctx, paginationToken) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*db.DbResultMap[*model.FinalityProviderStatsDocument]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, paginationToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// FindFinalityProviderStatsByFinalityProviderPkHex provides a mock function with given fields: ctx, finalityProviderPkHex -func (_m *DBClient) FindFinalityProviderStatsByFinalityProviderPkHex(ctx context.Context, finalityProviderPkHex []string) ([]*model.FinalityProviderStatsDocument, error) { - ret := _m.Called(ctx, finalityProviderPkHex) - - if len(ret) == 0 { - panic("no return value specified for FindFinalityProviderStatsByFinalityProviderPkHex") - } - - var r0 []*model.FinalityProviderStatsDocument - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*model.FinalityProviderStatsDocument, error)); ok { - return rf(ctx, finalityProviderPkHex) - } - if rf, ok := ret.Get(0).(func(context.Context, []string) []*model.FinalityProviderStatsDocument); ok { - r0 = rf(ctx, finalityProviderPkHex) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*model.FinalityProviderStatsDocument) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { - r1 = rf(ctx, finalityProviderPkHex) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // FindPkMappingsByNativeSegwitAddress provides a mock function with given fields: ctx, nativeSegwitAddresses -func (_m *DBClient) FindPkMappingsByNativeSegwitAddress(ctx context.Context, nativeSegwitAddresses []string) ([]*model.PkAddressMapping, error) { +func (_m *DBClient) FindPkMappingsByNativeSegwitAddress(ctx context.Context, nativeSegwitAddresses []string) ([]*dbmodel.PkAddressMapping, error) { ret := _m.Called(ctx, nativeSegwitAddresses) if len(ret) == 0 { panic("no return value specified for FindPkMappingsByNativeSegwitAddress") } - var r0 []*model.PkAddressMapping + var r0 []*dbmodel.PkAddressMapping var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*model.PkAddressMapping, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*dbmodel.PkAddressMapping, error)); ok { return rf(ctx, nativeSegwitAddresses) } - if rf, ok := ret.Get(0).(func(context.Context, []string) []*model.PkAddressMapping); ok { + if rf, ok := ret.Get(0).(func(context.Context, []string) []*dbmodel.PkAddressMapping); ok { r0 = rf(ctx, nativeSegwitAddresses) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*model.PkAddressMapping) + r0 = ret.Get(0).([]*dbmodel.PkAddressMapping) } } @@ -215,23 +64,23 @@ func (_m *DBClient) FindPkMappingsByNativeSegwitAddress(ctx context.Context, nat } // FindPkMappingsByTaprootAddress provides a mock function with given fields: ctx, taprootAddresses -func (_m *DBClient) FindPkMappingsByTaprootAddress(ctx context.Context, taprootAddresses []string) ([]*model.PkAddressMapping, error) { +func (_m *DBClient) FindPkMappingsByTaprootAddress(ctx context.Context, taprootAddresses []string) ([]*dbmodel.PkAddressMapping, error) { ret := _m.Called(ctx, taprootAddresses) if len(ret) == 0 { panic("no return value specified for FindPkMappingsByTaprootAddress") } - var r0 []*model.PkAddressMapping + var r0 []*dbmodel.PkAddressMapping var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*model.PkAddressMapping, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*dbmodel.PkAddressMapping, error)); ok { return rf(ctx, taprootAddresses) } - if rf, ok := ret.Get(0).(func(context.Context, []string) []*model.PkAddressMapping); ok { + if rf, ok := ret.Get(0).(func(context.Context, []string) []*dbmodel.PkAddressMapping); ok { r0 = rf(ctx, taprootAddresses) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]*model.PkAddressMapping) + r0 = ret.Get(0).([]*dbmodel.PkAddressMapping) } } @@ -244,144 +93,24 @@ func (_m *DBClient) FindPkMappingsByTaprootAddress(ctx context.Context, taprootA return r0, r1 } -// FindTopStakersByTvl provides a mock function with given fields: ctx, paginationToken -func (_m *DBClient) FindTopStakersByTvl(ctx context.Context, paginationToken string) (*db.DbResultMap[*model.StakerStatsDocument], error) { - ret := _m.Called(ctx, paginationToken) - - if len(ret) == 0 { - panic("no return value specified for FindTopStakersByTvl") - } - - var r0 *db.DbResultMap[*model.StakerStatsDocument] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*db.DbResultMap[*model.StakerStatsDocument], error)); ok { - return rf(ctx, paginationToken) - } - if rf, ok := ret.Get(0).(func(context.Context, string) *db.DbResultMap[*model.StakerStatsDocument]); ok { - r0 = rf(ctx, paginationToken) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*db.DbResultMap[*model.StakerStatsDocument]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, paginationToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // FindUnprocessableMessages provides a mock function with given fields: ctx -func (_m *DBClient) FindUnprocessableMessages(ctx context.Context) ([]model.UnprocessableMessageDocument, error) { +func (_m *DBClient) FindUnprocessableMessages(ctx context.Context) ([]dbmodel.UnprocessableMessageDocument, error) { ret := _m.Called(ctx) if len(ret) == 0 { panic("no return value specified for FindUnprocessableMessages") } - var r0 []model.UnprocessableMessageDocument - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) ([]model.UnprocessableMessageDocument, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) []model.UnprocessableMessageDocument); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]model.UnprocessableMessageDocument) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetLatestBtcInfo provides a mock function with given fields: ctx -func (_m *DBClient) GetLatestBtcInfo(ctx context.Context) (*model.BtcInfo, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetLatestBtcInfo") - } - - var r0 *model.BtcInfo - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*model.BtcInfo, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) *model.BtcInfo); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.BtcInfo) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetOrCreateStatsLock provides a mock function with given fields: ctx, stakingTxHashHex, state -func (_m *DBClient) GetOrCreateStatsLock(ctx context.Context, stakingTxHashHex string, state string) (*model.StatsLockDocument, error) { - ret := _m.Called(ctx, stakingTxHashHex, state) - - if len(ret) == 0 { - panic("no return value specified for GetOrCreateStatsLock") - } - - var r0 *model.StatsLockDocument - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.StatsLockDocument, error)); ok { - return rf(ctx, stakingTxHashHex, state) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) *model.StatsLockDocument); ok { - r0 = rf(ctx, stakingTxHashHex, state) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.StatsLockDocument) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, stakingTxHashHex, state) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetOverallStats provides a mock function with given fields: ctx -func (_m *DBClient) GetOverallStats(ctx context.Context) (*model.OverallStatsDocument, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetOverallStats") - } - - var r0 *model.OverallStatsDocument + var r0 []dbmodel.UnprocessableMessageDocument var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*model.OverallStatsDocument, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context) ([]dbmodel.UnprocessableMessageDocument, error)); ok { return rf(ctx) } - if rf, ok := ret.Get(0).(func(context.Context) *model.OverallStatsDocument); ok { + if rf, ok := ret.Get(0).(func(context.Context) []dbmodel.UnprocessableMessageDocument); ok { r0 = rf(ctx) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.OverallStatsDocument) + r0 = ret.Get(0).([]dbmodel.UnprocessableMessageDocument) } } @@ -394,90 +123,6 @@ func (_m *DBClient) GetOverallStats(ctx context.Context) (*model.OverallStatsDoc return r0, r1 } -// GetStakerStats provides a mock function with given fields: ctx, stakerPkHex -func (_m *DBClient) GetStakerStats(ctx context.Context, stakerPkHex string) (*model.StakerStatsDocument, error) { - ret := _m.Called(ctx, stakerPkHex) - - if len(ret) == 0 { - panic("no return value specified for GetStakerStats") - } - - var r0 *model.StakerStatsDocument - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*model.StakerStatsDocument, error)); ok { - return rf(ctx, stakerPkHex) - } - if rf, ok := ret.Get(0).(func(context.Context, string) *model.StakerStatsDocument); ok { - r0 = rf(ctx, stakerPkHex) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*model.StakerStatsDocument) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, stakerPkHex) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// IncrementFinalityProviderStats provides a mock function with given fields: ctx, stakingTxHashHex, fpPkHex, amount -func (_m *DBClient) IncrementFinalityProviderStats(ctx context.Context, stakingTxHashHex string, fpPkHex string, amount uint64) error { - ret := _m.Called(ctx, stakingTxHashHex, fpPkHex, amount) - - if len(ret) == 0 { - panic("no return value specified for IncrementFinalityProviderStats") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { - r0 = rf(ctx, stakingTxHashHex, fpPkHex, amount) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// IncrementOverallStats provides a mock function with given fields: ctx, stakingTxHashHex, stakerPkHex, amount -func (_m *DBClient) IncrementOverallStats(ctx context.Context, stakingTxHashHex string, stakerPkHex string, amount uint64) error { - ret := _m.Called(ctx, stakingTxHashHex, stakerPkHex, amount) - - if len(ret) == 0 { - panic("no return value specified for IncrementOverallStats") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { - r0 = rf(ctx, stakingTxHashHex, stakerPkHex, amount) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// IncrementStakerStats provides a mock function with given fields: ctx, stakingTxHashHex, stakerPkHex, amount -func (_m *DBClient) IncrementStakerStats(ctx context.Context, stakingTxHashHex string, stakerPkHex string, amount uint64) error { - ret := _m.Called(ctx, stakingTxHashHex, stakerPkHex, amount) - - if len(ret) == 0 { - panic("no return value specified for IncrementStakerStats") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { - r0 = rf(ctx, stakingTxHashHex, stakerPkHex, amount) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // InsertPkAddressMappings provides a mock function with given fields: ctx, stakerPkHex, taproot, nativeSigwitOdd, nativeSigwitEven func (_m *DBClient) InsertPkAddressMappings(ctx context.Context, stakerPkHex string, taproot string, nativeSigwitOdd string, nativeSigwitEven string) error { ret := _m.Called(ctx, stakerPkHex, taproot, nativeSigwitOdd, nativeSigwitEven) @@ -514,60 +159,6 @@ func (_m *DBClient) Ping(ctx context.Context) error { return r0 } -// SaveActiveStakingDelegation provides a mock function with given fields: ctx, stakingTxHashHex, stakerPkHex, fpPkHex, stakingTxHex, amount, startHeight, timelock, outputIndex, startTimestamp, isOverflow -func (_m *DBClient) SaveActiveStakingDelegation(ctx context.Context, stakingTxHashHex string, stakerPkHex string, fpPkHex string, stakingTxHex string, amount uint64, startHeight uint64, timelock uint64, outputIndex uint64, startTimestamp int64, isOverflow bool) error { - ret := _m.Called(ctx, stakingTxHashHex, stakerPkHex, fpPkHex, stakingTxHex, amount, startHeight, timelock, outputIndex, startTimestamp, isOverflow) - - if len(ret) == 0 { - panic("no return value specified for SaveActiveStakingDelegation") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, uint64, uint64, uint64, uint64, int64, bool) error); ok { - r0 = rf(ctx, stakingTxHashHex, stakerPkHex, fpPkHex, stakingTxHex, amount, startHeight, timelock, outputIndex, startTimestamp, isOverflow) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SaveTimeLockExpireCheck provides a mock function with given fields: ctx, stakingTxHashHex, expireHeight, txType -func (_m *DBClient) SaveTimeLockExpireCheck(ctx context.Context, stakingTxHashHex string, expireHeight uint64, txType string) error { - ret := _m.Called(ctx, stakingTxHashHex, expireHeight, txType) - - if len(ret) == 0 { - panic("no return value specified for SaveTimeLockExpireCheck") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, uint64, string) error); ok { - r0 = rf(ctx, stakingTxHashHex, expireHeight, txType) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SaveUnbondingTx provides a mock function with given fields: ctx, stakingTxHashHex, unbondingTxHashHex, txHex, signatureHex -func (_m *DBClient) SaveUnbondingTx(ctx context.Context, stakingTxHashHex string, unbondingTxHashHex string, txHex string, signatureHex string) error { - ret := _m.Called(ctx, stakingTxHashHex, unbondingTxHashHex, txHex, signatureHex) - - if len(ret) == 0 { - panic("no return value specified for SaveUnbondingTx") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { - r0 = rf(ctx, stakingTxHashHex, unbondingTxHashHex, txHex, signatureHex) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // SaveUnprocessableMessage provides a mock function with given fields: ctx, messageBody, receipt func (_m *DBClient) SaveUnprocessableMessage(ctx context.Context, messageBody string, receipt string) error { ret := _m.Called(ctx, messageBody, receipt) @@ -586,162 +177,6 @@ func (_m *DBClient) SaveUnprocessableMessage(ctx context.Context, messageBody st return r0 } -// ScanDelegationsPaginated provides a mock function with given fields: ctx, paginationToken -func (_m *DBClient) ScanDelegationsPaginated(ctx context.Context, paginationToken string) (*db.DbResultMap[model.DelegationDocument], error) { - ret := _m.Called(ctx, paginationToken) - - if len(ret) == 0 { - panic("no return value specified for ScanDelegationsPaginated") - } - - var r0 *db.DbResultMap[model.DelegationDocument] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (*db.DbResultMap[model.DelegationDocument], error)); ok { - return rf(ctx, paginationToken) - } - if rf, ok := ret.Get(0).(func(context.Context, string) *db.DbResultMap[model.DelegationDocument]); ok { - r0 = rf(ctx, paginationToken) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*db.DbResultMap[model.DelegationDocument]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, paginationToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// SubtractFinalityProviderStats provides a mock function with given fields: ctx, stakingTxHashHex, fpPkHex, amount -func (_m *DBClient) SubtractFinalityProviderStats(ctx context.Context, stakingTxHashHex string, fpPkHex string, amount uint64) error { - ret := _m.Called(ctx, stakingTxHashHex, fpPkHex, amount) - - if len(ret) == 0 { - panic("no return value specified for SubtractFinalityProviderStats") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { - r0 = rf(ctx, stakingTxHashHex, fpPkHex, amount) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SubtractOverallStats provides a mock function with given fields: ctx, stakingTxHashHex, stakerPkHex, amount -func (_m *DBClient) SubtractOverallStats(ctx context.Context, stakingTxHashHex string, stakerPkHex string, amount uint64) error { - ret := _m.Called(ctx, stakingTxHashHex, stakerPkHex, amount) - - if len(ret) == 0 { - panic("no return value specified for SubtractOverallStats") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { - r0 = rf(ctx, stakingTxHashHex, stakerPkHex, amount) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SubtractStakerStats provides a mock function with given fields: ctx, stakingTxHashHex, stakerPkHex, amount -func (_m *DBClient) SubtractStakerStats(ctx context.Context, stakingTxHashHex string, stakerPkHex string, amount uint64) error { - ret := _m.Called(ctx, stakingTxHashHex, stakerPkHex, amount) - - if len(ret) == 0 { - panic("no return value specified for SubtractStakerStats") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { - r0 = rf(ctx, stakingTxHashHex, stakerPkHex, amount) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// TransitionToUnbondedState provides a mock function with given fields: ctx, stakingTxHashHex, eligiblePreviousState -func (_m *DBClient) TransitionToUnbondedState(ctx context.Context, stakingTxHashHex string, eligiblePreviousState []types.DelegationState) error { - ret := _m.Called(ctx, stakingTxHashHex, eligiblePreviousState) - - if len(ret) == 0 { - panic("no return value specified for TransitionToUnbondedState") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, []types.DelegationState) error); ok { - r0 = rf(ctx, stakingTxHashHex, eligiblePreviousState) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// TransitionToUnbondingState provides a mock function with given fields: ctx, txHashHex, startHeight, timelock, outputIndex, txHex, startTimestamp -func (_m *DBClient) TransitionToUnbondingState(ctx context.Context, txHashHex string, startHeight uint64, timelock uint64, outputIndex uint64, txHex string, startTimestamp int64) error { - ret := _m.Called(ctx, txHashHex, startHeight, timelock, outputIndex, txHex, startTimestamp) - - if len(ret) == 0 { - panic("no return value specified for TransitionToUnbondingState") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, uint64, string, int64) error); ok { - r0 = rf(ctx, txHashHex, startHeight, timelock, outputIndex, txHex, startTimestamp) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// TransitionToWithdrawnState provides a mock function with given fields: ctx, txHashHex -func (_m *DBClient) TransitionToWithdrawnState(ctx context.Context, txHashHex string) error { - ret := _m.Called(ctx, txHashHex) - - if len(ret) == 0 { - panic("no return value specified for TransitionToWithdrawnState") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, txHashHex) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// UpsertLatestBtcInfo provides a mock function with given fields: ctx, height, confirmedTvl, unconfirmedTvl -func (_m *DBClient) UpsertLatestBtcInfo(ctx context.Context, height uint64, confirmedTvl uint64, unconfirmedTvl uint64) error { - ret := _m.Called(ctx, height, confirmedTvl, unconfirmedTvl) - - if len(ret) == 0 { - panic("no return value specified for UpsertLatestBtcInfo") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, uint64) error); ok { - r0 = rf(ctx, height, confirmedTvl, unconfirmedTvl) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // NewDBClient creates a new instance of DBClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewDBClient(t interface { diff --git a/tests/mocks/mock_ordinal_client.go b/tests/mocks/mock_ordinal_client.go index 79346c37..1c21cfef 100644 --- a/tests/mocks/mock_ordinal_client.go +++ b/tests/mocks/mock_ordinal_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package mocks @@ -8,18 +8,18 @@ import ( mock "github.com/stretchr/testify/mock" - ordinals "github.com/babylonlabs-io/staking-api-service/internal/clients/ordinals" + ordinals "github.com/babylonlabs-io/staking-api-service/internal/shared/http/clients/ordinals" - types "github.com/babylonlabs-io/staking-api-service/internal/types" + types "github.com/babylonlabs-io/staking-api-service/internal/shared/types" ) -// OrdinalsClientInterface is an autogenerated mock type for the OrdinalsClientInterface type -type OrdinalsClientInterface struct { +// OrdinalsClient is an autogenerated mock type for the OrdinalsClient type +type OrdinalsClient struct { mock.Mock } // FetchUTXOInfos provides a mock function with given fields: ctx, utxos -func (_m *OrdinalsClientInterface) FetchUTXOInfos(ctx context.Context, utxos []types.UTXOIdentifier) ([]ordinals.OrdinalsOutputResponse, *types.Error) { +func (_m *OrdinalsClient) FetchUTXOInfos(ctx context.Context, utxos []types.UTXOIdentifier) ([]ordinals.OrdinalsOutputResponse, *types.Error) { ret := _m.Called(ctx, utxos) if len(ret) == 0 { @@ -51,7 +51,7 @@ func (_m *OrdinalsClientInterface) FetchUTXOInfos(ctx context.Context, utxos []t } // GetBaseURL provides a mock function with given fields: -func (_m *OrdinalsClientInterface) GetBaseURL() string { +func (_m *OrdinalsClient) GetBaseURL() string { ret := _m.Called() if len(ret) == 0 { @@ -69,7 +69,7 @@ func (_m *OrdinalsClientInterface) GetBaseURL() string { } // GetDefaultRequestTimeout provides a mock function with given fields: -func (_m *OrdinalsClientInterface) GetDefaultRequestTimeout() int { +func (_m *OrdinalsClient) GetDefaultRequestTimeout() int { ret := _m.Called() if len(ret) == 0 { @@ -87,7 +87,7 @@ func (_m *OrdinalsClientInterface) GetDefaultRequestTimeout() int { } // GetHttpClient provides a mock function with given fields: -func (_m *OrdinalsClientInterface) GetHttpClient() *http.Client { +func (_m *OrdinalsClient) GetHttpClient() *http.Client { ret := _m.Called() if len(ret) == 0 { @@ -106,13 +106,13 @@ func (_m *OrdinalsClientInterface) GetHttpClient() *http.Client { return r0 } -// NewOrdinalsClientInterface creates a new instance of OrdinalsClientInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewOrdinalsClient creates a new instance of OrdinalsClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewOrdinalsClientInterface(t interface { +func NewOrdinalsClient(t interface { mock.TestingT Cleanup(func()) -}) *OrdinalsClientInterface { - mock := &OrdinalsClientInterface{} +}) *OrdinalsClient { + mock := &OrdinalsClient{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/tests/mocks/mock_v1_db_client.go b/tests/mocks/mock_v1_db_client.go new file mode 100644 index 00000000..f7492dd9 --- /dev/null +++ b/tests/mocks/mock_v1_db_client.go @@ -0,0 +1,761 @@ +// Code generated by mockery v2.44.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + db "github.com/babylonlabs-io/staking-api-service/internal/shared/db" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + + mock "github.com/stretchr/testify/mock" + + types "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + + v1dbclient "github.com/babylonlabs-io/staking-api-service/internal/v1/db/client" + + v1dbmodel "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" +) + +// V1DBClient is an autogenerated mock type for the V1DBClient type +type V1DBClient struct { + mock.Mock +} + +// CheckDelegationExistByStakerPk provides a mock function with given fields: ctx, address, extraFilter +func (_m *V1DBClient) CheckDelegationExistByStakerPk(ctx context.Context, address string, extraFilter *v1dbclient.DelegationFilter) (bool, error) { + ret := _m.Called(ctx, address, extraFilter) + + if len(ret) == 0 { + panic("no return value specified for CheckDelegationExistByStakerPk") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *v1dbclient.DelegationFilter) (bool, error)); ok { + return rf(ctx, address, extraFilter) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *v1dbclient.DelegationFilter) bool); ok { + r0 = rf(ctx, address, extraFilter) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *v1dbclient.DelegationFilter) error); ok { + r1 = rf(ctx, address, extraFilter) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteUnprocessableMessage provides a mock function with given fields: ctx, Receipt +func (_m *V1DBClient) DeleteUnprocessableMessage(ctx context.Context, Receipt interface{}) error { + ret := _m.Called(ctx, Receipt) + + if len(ret) == 0 { + panic("no return value specified for DeleteUnprocessableMessage") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, interface{}) error); ok { + r0 = rf(ctx, Receipt) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// FindDelegationByTxHashHex provides a mock function with given fields: ctx, txHashHex +func (_m *V1DBClient) FindDelegationByTxHashHex(ctx context.Context, txHashHex string) (*v1dbmodel.DelegationDocument, error) { + ret := _m.Called(ctx, txHashHex) + + if len(ret) == 0 { + panic("no return value specified for FindDelegationByTxHashHex") + } + + var r0 *v1dbmodel.DelegationDocument + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*v1dbmodel.DelegationDocument, error)); ok { + return rf(ctx, txHashHex) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *v1dbmodel.DelegationDocument); ok { + r0 = rf(ctx, txHashHex) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1dbmodel.DelegationDocument) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, txHashHex) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindDelegationsByStakerPk provides a mock function with given fields: ctx, stakerPk, extraFilter, paginationToken +func (_m *V1DBClient) FindDelegationsByStakerPk(ctx context.Context, stakerPk string, extraFilter *v1dbclient.DelegationFilter, paginationToken string) (*db.DbResultMap[v1dbmodel.DelegationDocument], error) { + ret := _m.Called(ctx, stakerPk, extraFilter, paginationToken) + + if len(ret) == 0 { + panic("no return value specified for FindDelegationsByStakerPk") + } + + var r0 *db.DbResultMap[v1dbmodel.DelegationDocument] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *v1dbclient.DelegationFilter, string) (*db.DbResultMap[v1dbmodel.DelegationDocument], error)); ok { + return rf(ctx, stakerPk, extraFilter, paginationToken) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *v1dbclient.DelegationFilter, string) *db.DbResultMap[v1dbmodel.DelegationDocument]); ok { + r0 = rf(ctx, stakerPk, extraFilter, paginationToken) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*db.DbResultMap[v1dbmodel.DelegationDocument]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *v1dbclient.DelegationFilter, string) error); ok { + r1 = rf(ctx, stakerPk, extraFilter, paginationToken) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindFinalityProviderStats provides a mock function with given fields: ctx, paginationToken +func (_m *V1DBClient) FindFinalityProviderStats(ctx context.Context, paginationToken string) (*db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument], error) { + ret := _m.Called(ctx, paginationToken) + + if len(ret) == 0 { + panic("no return value specified for FindFinalityProviderStats") + } + + var r0 *db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument], error)); ok { + return rf(ctx, paginationToken) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument]); ok { + r0 = rf(ctx, paginationToken) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*db.DbResultMap[*v1dbmodel.FinalityProviderStatsDocument]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, paginationToken) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindFinalityProviderStatsByFinalityProviderPkHex provides a mock function with given fields: ctx, finalityProviderPkHex +func (_m *V1DBClient) FindFinalityProviderStatsByFinalityProviderPkHex(ctx context.Context, finalityProviderPkHex []string) ([]*v1dbmodel.FinalityProviderStatsDocument, error) { + ret := _m.Called(ctx, finalityProviderPkHex) + + if len(ret) == 0 { + panic("no return value specified for FindFinalityProviderStatsByFinalityProviderPkHex") + } + + var r0 []*v1dbmodel.FinalityProviderStatsDocument + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*v1dbmodel.FinalityProviderStatsDocument, error)); ok { + return rf(ctx, finalityProviderPkHex) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []*v1dbmodel.FinalityProviderStatsDocument); ok { + r0 = rf(ctx, finalityProviderPkHex) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*v1dbmodel.FinalityProviderStatsDocument) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { + r1 = rf(ctx, finalityProviderPkHex) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindPkMappingsByNativeSegwitAddress provides a mock function with given fields: ctx, nativeSegwitAddresses +func (_m *V1DBClient) FindPkMappingsByNativeSegwitAddress(ctx context.Context, nativeSegwitAddresses []string) ([]*dbmodel.PkAddressMapping, error) { + ret := _m.Called(ctx, nativeSegwitAddresses) + + if len(ret) == 0 { + panic("no return value specified for FindPkMappingsByNativeSegwitAddress") + } + + var r0 []*dbmodel.PkAddressMapping + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*dbmodel.PkAddressMapping, error)); ok { + return rf(ctx, nativeSegwitAddresses) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []*dbmodel.PkAddressMapping); ok { + r0 = rf(ctx, nativeSegwitAddresses) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*dbmodel.PkAddressMapping) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { + r1 = rf(ctx, nativeSegwitAddresses) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindPkMappingsByTaprootAddress provides a mock function with given fields: ctx, taprootAddresses +func (_m *V1DBClient) FindPkMappingsByTaprootAddress(ctx context.Context, taprootAddresses []string) ([]*dbmodel.PkAddressMapping, error) { + ret := _m.Called(ctx, taprootAddresses) + + if len(ret) == 0 { + panic("no return value specified for FindPkMappingsByTaprootAddress") + } + + var r0 []*dbmodel.PkAddressMapping + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*dbmodel.PkAddressMapping, error)); ok { + return rf(ctx, taprootAddresses) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []*dbmodel.PkAddressMapping); ok { + r0 = rf(ctx, taprootAddresses) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*dbmodel.PkAddressMapping) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { + r1 = rf(ctx, taprootAddresses) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindTopStakersByTvl provides a mock function with given fields: ctx, paginationToken +func (_m *V1DBClient) FindTopStakersByTvl(ctx context.Context, paginationToken string) (*db.DbResultMap[*v1dbmodel.StakerStatsDocument], error) { + ret := _m.Called(ctx, paginationToken) + + if len(ret) == 0 { + panic("no return value specified for FindTopStakersByTvl") + } + + var r0 *db.DbResultMap[*v1dbmodel.StakerStatsDocument] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*db.DbResultMap[*v1dbmodel.StakerStatsDocument], error)); ok { + return rf(ctx, paginationToken) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *db.DbResultMap[*v1dbmodel.StakerStatsDocument]); ok { + r0 = rf(ctx, paginationToken) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*db.DbResultMap[*v1dbmodel.StakerStatsDocument]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, paginationToken) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindUnprocessableMessages provides a mock function with given fields: ctx +func (_m *V1DBClient) FindUnprocessableMessages(ctx context.Context) ([]dbmodel.UnprocessableMessageDocument, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for FindUnprocessableMessages") + } + + var r0 []dbmodel.UnprocessableMessageDocument + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]dbmodel.UnprocessableMessageDocument, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []dbmodel.UnprocessableMessageDocument); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]dbmodel.UnprocessableMessageDocument) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLatestBtcInfo provides a mock function with given fields: ctx +func (_m *V1DBClient) GetLatestBtcInfo(ctx context.Context) (*v1dbmodel.BtcInfo, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetLatestBtcInfo") + } + + var r0 *v1dbmodel.BtcInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*v1dbmodel.BtcInfo, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *v1dbmodel.BtcInfo); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1dbmodel.BtcInfo) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetOrCreateStatsLock provides a mock function with given fields: ctx, stakingTxHashHex, state +func (_m *V1DBClient) GetOrCreateStatsLock(ctx context.Context, stakingTxHashHex string, state string) (*v1dbmodel.StatsLockDocument, error) { + ret := _m.Called(ctx, stakingTxHashHex, state) + + if len(ret) == 0 { + panic("no return value specified for GetOrCreateStatsLock") + } + + var r0 *v1dbmodel.StatsLockDocument + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*v1dbmodel.StatsLockDocument, error)); ok { + return rf(ctx, stakingTxHashHex, state) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *v1dbmodel.StatsLockDocument); ok { + r0 = rf(ctx, stakingTxHashHex, state) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1dbmodel.StatsLockDocument) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, stakingTxHashHex, state) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetOverallStats provides a mock function with given fields: ctx +func (_m *V1DBClient) GetOverallStats(ctx context.Context) (*v1dbmodel.OverallStatsDocument, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetOverallStats") + } + + var r0 *v1dbmodel.OverallStatsDocument + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*v1dbmodel.OverallStatsDocument, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *v1dbmodel.OverallStatsDocument); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1dbmodel.OverallStatsDocument) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetStakerStats provides a mock function with given fields: ctx, stakerPkHex +func (_m *V1DBClient) GetStakerStats(ctx context.Context, stakerPkHex string) (*v1dbmodel.StakerStatsDocument, error) { + ret := _m.Called(ctx, stakerPkHex) + + if len(ret) == 0 { + panic("no return value specified for GetStakerStats") + } + + var r0 *v1dbmodel.StakerStatsDocument + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*v1dbmodel.StakerStatsDocument, error)); ok { + return rf(ctx, stakerPkHex) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *v1dbmodel.StakerStatsDocument); ok { + r0 = rf(ctx, stakerPkHex) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1dbmodel.StakerStatsDocument) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, stakerPkHex) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IncrementFinalityProviderStats provides a mock function with given fields: ctx, stakingTxHashHex, fpPkHex, amount +func (_m *V1DBClient) IncrementFinalityProviderStats(ctx context.Context, stakingTxHashHex string, fpPkHex string, amount uint64) error { + ret := _m.Called(ctx, stakingTxHashHex, fpPkHex, amount) + + if len(ret) == 0 { + panic("no return value specified for IncrementFinalityProviderStats") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { + r0 = rf(ctx, stakingTxHashHex, fpPkHex, amount) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IncrementOverallStats provides a mock function with given fields: ctx, stakingTxHashHex, stakerPkHex, amount +func (_m *V1DBClient) IncrementOverallStats(ctx context.Context, stakingTxHashHex string, stakerPkHex string, amount uint64) error { + ret := _m.Called(ctx, stakingTxHashHex, stakerPkHex, amount) + + if len(ret) == 0 { + panic("no return value specified for IncrementOverallStats") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { + r0 = rf(ctx, stakingTxHashHex, stakerPkHex, amount) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IncrementStakerStats provides a mock function with given fields: ctx, stakingTxHashHex, stakerPkHex, amount +func (_m *V1DBClient) IncrementStakerStats(ctx context.Context, stakingTxHashHex string, stakerPkHex string, amount uint64) error { + ret := _m.Called(ctx, stakingTxHashHex, stakerPkHex, amount) + + if len(ret) == 0 { + panic("no return value specified for IncrementStakerStats") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { + r0 = rf(ctx, stakingTxHashHex, stakerPkHex, amount) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InsertPkAddressMappings provides a mock function with given fields: ctx, stakerPkHex, taproot, nativeSigwitOdd, nativeSigwitEven +func (_m *V1DBClient) InsertPkAddressMappings(ctx context.Context, stakerPkHex string, taproot string, nativeSigwitOdd string, nativeSigwitEven string) error { + ret := _m.Called(ctx, stakerPkHex, taproot, nativeSigwitOdd, nativeSigwitEven) + + if len(ret) == 0 { + panic("no return value specified for InsertPkAddressMappings") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { + r0 = rf(ctx, stakerPkHex, taproot, nativeSigwitOdd, nativeSigwitEven) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Ping provides a mock function with given fields: ctx +func (_m *V1DBClient) Ping(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Ping") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SaveActiveStakingDelegation provides a mock function with given fields: ctx, stakingTxHashHex, stakerPkHex, fpPkHex, stakingTxHex, amount, startHeight, timelock, outputIndex, startTimestamp, isOverflow +func (_m *V1DBClient) SaveActiveStakingDelegation(ctx context.Context, stakingTxHashHex string, stakerPkHex string, fpPkHex string, stakingTxHex string, amount uint64, startHeight uint64, timelock uint64, outputIndex uint64, startTimestamp int64, isOverflow bool) error { + ret := _m.Called(ctx, stakingTxHashHex, stakerPkHex, fpPkHex, stakingTxHex, amount, startHeight, timelock, outputIndex, startTimestamp, isOverflow) + + if len(ret) == 0 { + panic("no return value specified for SaveActiveStakingDelegation") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, uint64, uint64, uint64, uint64, int64, bool) error); ok { + r0 = rf(ctx, stakingTxHashHex, stakerPkHex, fpPkHex, stakingTxHex, amount, startHeight, timelock, outputIndex, startTimestamp, isOverflow) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SaveTimeLockExpireCheck provides a mock function with given fields: ctx, stakingTxHashHex, expireHeight, txType +func (_m *V1DBClient) SaveTimeLockExpireCheck(ctx context.Context, stakingTxHashHex string, expireHeight uint64, txType string) error { + ret := _m.Called(ctx, stakingTxHashHex, expireHeight, txType) + + if len(ret) == 0 { + panic("no return value specified for SaveTimeLockExpireCheck") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, string) error); ok { + r0 = rf(ctx, stakingTxHashHex, expireHeight, txType) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SaveUnbondingTx provides a mock function with given fields: ctx, stakingTxHashHex, unbondingTxHashHex, txHex, signatureHex +func (_m *V1DBClient) SaveUnbondingTx(ctx context.Context, stakingTxHashHex string, unbondingTxHashHex string, txHex string, signatureHex string) error { + ret := _m.Called(ctx, stakingTxHashHex, unbondingTxHashHex, txHex, signatureHex) + + if len(ret) == 0 { + panic("no return value specified for SaveUnbondingTx") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { + r0 = rf(ctx, stakingTxHashHex, unbondingTxHashHex, txHex, signatureHex) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SaveUnprocessableMessage provides a mock function with given fields: ctx, messageBody, receipt +func (_m *V1DBClient) SaveUnprocessableMessage(ctx context.Context, messageBody string, receipt string) error { + ret := _m.Called(ctx, messageBody, receipt) + + if len(ret) == 0 { + panic("no return value specified for SaveUnprocessableMessage") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, messageBody, receipt) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ScanDelegationsPaginated provides a mock function with given fields: ctx, paginationToken +func (_m *V1DBClient) ScanDelegationsPaginated(ctx context.Context, paginationToken string) (*db.DbResultMap[v1dbmodel.DelegationDocument], error) { + ret := _m.Called(ctx, paginationToken) + + if len(ret) == 0 { + panic("no return value specified for ScanDelegationsPaginated") + } + + var r0 *db.DbResultMap[v1dbmodel.DelegationDocument] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*db.DbResultMap[v1dbmodel.DelegationDocument], error)); ok { + return rf(ctx, paginationToken) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *db.DbResultMap[v1dbmodel.DelegationDocument]); ok { + r0 = rf(ctx, paginationToken) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*db.DbResultMap[v1dbmodel.DelegationDocument]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, paginationToken) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SubtractFinalityProviderStats provides a mock function with given fields: ctx, stakingTxHashHex, fpPkHex, amount +func (_m *V1DBClient) SubtractFinalityProviderStats(ctx context.Context, stakingTxHashHex string, fpPkHex string, amount uint64) error { + ret := _m.Called(ctx, stakingTxHashHex, fpPkHex, amount) + + if len(ret) == 0 { + panic("no return value specified for SubtractFinalityProviderStats") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { + r0 = rf(ctx, stakingTxHashHex, fpPkHex, amount) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SubtractOverallStats provides a mock function with given fields: ctx, stakingTxHashHex, stakerPkHex, amount +func (_m *V1DBClient) SubtractOverallStats(ctx context.Context, stakingTxHashHex string, stakerPkHex string, amount uint64) error { + ret := _m.Called(ctx, stakingTxHashHex, stakerPkHex, amount) + + if len(ret) == 0 { + panic("no return value specified for SubtractOverallStats") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { + r0 = rf(ctx, stakingTxHashHex, stakerPkHex, amount) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SubtractStakerStats provides a mock function with given fields: ctx, stakingTxHashHex, stakerPkHex, amount +func (_m *V1DBClient) SubtractStakerStats(ctx context.Context, stakingTxHashHex string, stakerPkHex string, amount uint64) error { + ret := _m.Called(ctx, stakingTxHashHex, stakerPkHex, amount) + + if len(ret) == 0 { + panic("no return value specified for SubtractStakerStats") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) error); ok { + r0 = rf(ctx, stakingTxHashHex, stakerPkHex, amount) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TransitionToUnbondedState provides a mock function with given fields: ctx, stakingTxHashHex, eligiblePreviousState +func (_m *V1DBClient) TransitionToUnbondedState(ctx context.Context, stakingTxHashHex string, eligiblePreviousState []types.DelegationState) error { + ret := _m.Called(ctx, stakingTxHashHex, eligiblePreviousState) + + if len(ret) == 0 { + panic("no return value specified for TransitionToUnbondedState") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, []types.DelegationState) error); ok { + r0 = rf(ctx, stakingTxHashHex, eligiblePreviousState) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TransitionToUnbondingState provides a mock function with given fields: ctx, txHashHex, startHeight, timelock, outputIndex, txHex, startTimestamp +func (_m *V1DBClient) TransitionToUnbondingState(ctx context.Context, txHashHex string, startHeight uint64, timelock uint64, outputIndex uint64, txHex string, startTimestamp int64) error { + ret := _m.Called(ctx, txHashHex, startHeight, timelock, outputIndex, txHex, startTimestamp) + + if len(ret) == 0 { + panic("no return value specified for TransitionToUnbondingState") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64, uint64, string, int64) error); ok { + r0 = rf(ctx, txHashHex, startHeight, timelock, outputIndex, txHex, startTimestamp) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TransitionToWithdrawnState provides a mock function with given fields: ctx, txHashHex +func (_m *V1DBClient) TransitionToWithdrawnState(ctx context.Context, txHashHex string) error { + ret := _m.Called(ctx, txHashHex) + + if len(ret) == 0 { + panic("no return value specified for TransitionToWithdrawnState") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, txHashHex) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpsertLatestBtcInfo provides a mock function with given fields: ctx, height, confirmedTvl, unconfirmedTvl +func (_m *V1DBClient) UpsertLatestBtcInfo(ctx context.Context, height uint64, confirmedTvl uint64, unconfirmedTvl uint64) error { + ret := _m.Called(ctx, height, confirmedTvl, unconfirmedTvl) + + if len(ret) == 0 { + panic("no return value specified for UpsertLatestBtcInfo") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, uint64) error); ok { + r0 = rf(ctx, height, confirmedTvl, unconfirmedTvl) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewV1DBClient creates a new instance of V1DBClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewV1DBClient(t interface { + mock.TestingT + Cleanup(func()) +}) *V1DBClient { + mock := &V1DBClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/mocks/mock_v2_db_client.go b/tests/mocks/mock_v2_db_client.go new file mode 100644 index 00000000..f6c57c26 --- /dev/null +++ b/tests/mocks/mock_v2_db_client.go @@ -0,0 +1,191 @@ +// Code generated by mockery v2.44.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + mock "github.com/stretchr/testify/mock" +) + +// V2DBClient is an autogenerated mock type for the V2DBClient type +type V2DBClient struct { + mock.Mock +} + +// DeleteUnprocessableMessage provides a mock function with given fields: ctx, Receipt +func (_m *V2DBClient) DeleteUnprocessableMessage(ctx context.Context, Receipt interface{}) error { + ret := _m.Called(ctx, Receipt) + + if len(ret) == 0 { + panic("no return value specified for DeleteUnprocessableMessage") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, interface{}) error); ok { + r0 = rf(ctx, Receipt) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// FindPkMappingsByNativeSegwitAddress provides a mock function with given fields: ctx, nativeSegwitAddresses +func (_m *V2DBClient) FindPkMappingsByNativeSegwitAddress(ctx context.Context, nativeSegwitAddresses []string) ([]*dbmodel.PkAddressMapping, error) { + ret := _m.Called(ctx, nativeSegwitAddresses) + + if len(ret) == 0 { + panic("no return value specified for FindPkMappingsByNativeSegwitAddress") + } + + var r0 []*dbmodel.PkAddressMapping + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*dbmodel.PkAddressMapping, error)); ok { + return rf(ctx, nativeSegwitAddresses) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []*dbmodel.PkAddressMapping); ok { + r0 = rf(ctx, nativeSegwitAddresses) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*dbmodel.PkAddressMapping) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { + r1 = rf(ctx, nativeSegwitAddresses) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindPkMappingsByTaprootAddress provides a mock function with given fields: ctx, taprootAddresses +func (_m *V2DBClient) FindPkMappingsByTaprootAddress(ctx context.Context, taprootAddresses []string) ([]*dbmodel.PkAddressMapping, error) { + ret := _m.Called(ctx, taprootAddresses) + + if len(ret) == 0 { + panic("no return value specified for FindPkMappingsByTaprootAddress") + } + + var r0 []*dbmodel.PkAddressMapping + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*dbmodel.PkAddressMapping, error)); ok { + return rf(ctx, taprootAddresses) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) []*dbmodel.PkAddressMapping); ok { + r0 = rf(ctx, taprootAddresses) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*dbmodel.PkAddressMapping) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { + r1 = rf(ctx, taprootAddresses) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindUnprocessableMessages provides a mock function with given fields: ctx +func (_m *V2DBClient) FindUnprocessableMessages(ctx context.Context) ([]dbmodel.UnprocessableMessageDocument, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for FindUnprocessableMessages") + } + + var r0 []dbmodel.UnprocessableMessageDocument + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]dbmodel.UnprocessableMessageDocument, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []dbmodel.UnprocessableMessageDocument); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]dbmodel.UnprocessableMessageDocument) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InsertPkAddressMappings provides a mock function with given fields: ctx, stakerPkHex, taproot, nativeSigwitOdd, nativeSigwitEven +func (_m *V2DBClient) InsertPkAddressMappings(ctx context.Context, stakerPkHex string, taproot string, nativeSigwitOdd string, nativeSigwitEven string) error { + ret := _m.Called(ctx, stakerPkHex, taproot, nativeSigwitOdd, nativeSigwitEven) + + if len(ret) == 0 { + panic("no return value specified for InsertPkAddressMappings") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok { + r0 = rf(ctx, stakerPkHex, taproot, nativeSigwitOdd, nativeSigwitEven) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Ping provides a mock function with given fields: ctx +func (_m *V2DBClient) Ping(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Ping") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SaveUnprocessableMessage provides a mock function with given fields: ctx, messageBody, receipt +func (_m *V2DBClient) SaveUnprocessableMessage(ctx context.Context, messageBody string, receipt string) error { + ret := _m.Called(ctx, messageBody, receipt) + + if len(ret) == 0 { + panic("no return value specified for SaveUnprocessableMessage") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, messageBody, receipt) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewV2DBClient creates a new instance of V2DBClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewV2DBClient(t interface { + mock.TestingT + Cleanup(func()) +}) *V2DBClient { + mock := &V2DBClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/scripts_test/pubkey_address_backfil_test.go b/tests/scripts_test/pubkey_address_backfil_test.go index 4b9bb8aa..22d61e18 100644 --- a/tests/scripts_test/pubkey_address_backfil_test.go +++ b/tests/scripts_test/pubkey_address_backfil_test.go @@ -7,30 +7,31 @@ import ( "time" "github.com/babylonlabs-io/staking-api-service/cmd/staking-api-service/scripts" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/types" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" + v1model "github.com/babylonlabs-io/staking-api-service/internal/v1/db/model" "github.com/babylonlabs-io/staking-api-service/tests/testutils" "github.com/stretchr/testify/assert" ) -func createNewDelegationDocuments(cfg *config.Config, numOfDocs int) []*model.DelegationDocument { +func createNewDelegationDocuments(cfg *config.Config, numOfDocs int) []*v1model.DelegationDocument { r := rand.New(rand.NewSource(time.Now().UnixNano())) - var delegationDocuments []*model.DelegationDocument + var delegationDocuments []*v1model.DelegationDocument opts := &testutils.TestActiveEventGeneratorOpts{ NumOfEvents: 1, // a single event to make sure it's always unique } for i := 0; i < numOfDocs; i++ { activeStakingEvenets := testutils.GenerateRandomActiveStakingEvents(r, opts) for _, event := range activeStakingEvenets { - doc := &model.DelegationDocument{ + doc := &v1model.DelegationDocument{ StakingTxHashHex: event.StakingTxHashHex, StakerPkHex: event.StakerPkHex, FinalityProviderPkHex: event.FinalityProviderPkHex, StakingValue: event.StakingValue, State: types.Active, - StakingTx: &model.TimelockTransaction{ + StakingTx: &v1model.TimelockTransaction{ TxHex: event.StakingTxHex, OutputIndex: event.StakingOutputIndex, StartTimestamp: event.StakingStartTimestamp, @@ -54,9 +55,7 @@ func TestBackfillAddressesBasedOnPubKeys(t *testing.T) { docs := createNewDelegationDocuments(cfg, int(cfg.Db.MaxPaginationLimit)+1) for _, doc := range docs { testutils.InjectDbDocument( - cfg, - model.DelegationCollection, - doc, + cfg, dbmodel.V1DelegationCollection, doc, ) } @@ -65,9 +64,8 @@ func TestBackfillAddressesBasedOnPubKeys(t *testing.T) { err := scripts.BackfillPubkeyAddressesMappings(ctx, cfg) assert.Nil(t, err) // check if the data is inserted - results, err := testutils.InspectDbDocuments[model.PkAddressMapping]( - cfg, - model.PkAddressMappingsCollection, + results, err := testutils.InspectDbDocuments[dbmodel.PkAddressMapping]( + cfg, dbmodel.V1PkAddressMappingsCollection, ) assert.Nil(t, err) // find the num of unique staker pks from the docs @@ -94,9 +92,8 @@ func TestBackfillAddressesBasedOnPubKeys(t *testing.T) { // change the existing data err = scripts.BackfillPubkeyAddressesMappings(ctx, cfg) assert.Nil(t, err) - results2, err := testutils.InspectDbDocuments[model.PkAddressMapping]( - cfg, - model.PkAddressMappingsCollection, + results2, err := testutils.InspectDbDocuments[dbmodel.PkAddressMapping]( + cfg, dbmodel.V1PkAddressMappingsCollection, ) assert.Nil(t, err) assert.Equal(t, len(results), len(results2)) diff --git a/tests/testutils/database.go b/tests/testutils/database.go index 402aee45..a9e5f5cf 100644 --- a/tests/testutils/database.go +++ b/tests/testutils/database.go @@ -4,9 +4,12 @@ import ( "context" "log" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/db" - "github.com/babylonlabs-io/staking-api-service/internal/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + dbclient "github.com/babylonlabs-io/staking-api-service/internal/shared/db/client" + dbclients "github.com/babylonlabs-io/staking-api-service/internal/shared/db/clients" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + v1dbclient "github.com/babylonlabs-io/staking-api-service/internal/v1/db/client" + v2dbclient "github.com/babylonlabs-io/staking-api-service/internal/v2/db/client" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -14,46 +17,60 @@ import ( var setUpDbIndex = false -func DirectDbConnection(cfg *config.Config) *db.Database { - client, err := mongo.Connect( +func DirectDbConnection(cfg *config.Config) (*dbclients.DbClients, string) { + mongoClient, err := mongo.Connect( context.TODO(), options.Client().ApplyURI(cfg.Db.Address), ) if err != nil { log.Fatal(err) } - return &db.Database{ - DbName: cfg.Db.DbName, - Client: client, + dbClient, err := dbclient.New(context.TODO(), mongoClient, cfg.Db) + if err != nil { + log.Fatal(err) + } + v1dbClient, err := v1dbclient.New(context.TODO(), mongoClient, cfg.Db) + if err != nil { + log.Fatal(err) + } + v2dbClient, err := v2dbclient.New(context.TODO(), mongoClient, cfg.Db) + if err != nil { + log.Fatal(err) } + return &dbclients.DbClients{ + MongoClient: mongoClient, + SharedDBClient: dbClient, + V1DBClient: v1dbClient, + V2DBClient: v2dbClient, + }, cfg.Db.DbName } // SetupTestDB connects to MongoDB and purges all collections. -func SetupTestDB(cfg config.Config) *db.Database { +func SetupTestDB(cfg config.Config) *dbclients.DbClients { // Connect to MongoDB - db := DirectDbConnection(&cfg) + dbClients, dbName := DirectDbConnection(&cfg) // Purge all collections in the test database // Setup the db index only once for all tests if !setUpDbIndex { - err := model.Setup(context.Background(), &cfg) + err := dbmodel.Setup(context.Background(), &cfg) if err != nil { log.Fatal("Failed to setup database:", err) } setUpDbIndex = true } - if err := PurgeAllCollections(context.TODO(), db.Client, cfg.Db.DbName); err != nil { + if err := PurgeAllCollections(context.TODO(), dbClients.MongoClient, dbName); err != nil { log.Fatal("Failed to purge database:", err) } - return db + return dbClients } // InjectDbDocument inserts a single document into the specified collection. func InjectDbDocument[T any]( cfg *config.Config, collectionName string, doc T, ) { - connection := DirectDbConnection(cfg) - defer connection.Client.Disconnect(context.Background()) - collection := connection.Client.Database(connection.DbName). + connection, dbName := DirectDbConnection(cfg) + defer connection.MongoClient.Disconnect(context.Background()) + collection := connection.MongoClient.Database(dbName). Collection(collectionName) _, err := collection.InsertOne(context.Background(), doc) @@ -66,8 +83,8 @@ func InjectDbDocument[T any]( func InspectDbDocuments[T any]( cfg *config.Config, collectionName string, ) ([]T, error) { - connection := DirectDbConnection(cfg) - collection := connection.Client.Database(connection.DbName). + connection, dbName := DirectDbConnection(cfg) + collection := connection.MongoClient.Database(dbName). Collection(collectionName) cursor, err := collection.Find(context.Background(), bson.D{}) @@ -75,7 +92,7 @@ func InspectDbDocuments[T any]( return nil, err } defer cursor.Close(context.Background()) - defer connection.Client.Disconnect(context.Background()) + defer connection.MongoClient.Disconnect(context.Background()) var results []T for cursor.Next(context.Background()) { @@ -93,10 +110,10 @@ func InspectDbDocuments[T any]( // UpdateDbDocument updates a document in the specified collection based on the // provided filter and update data. func UpdateDbDocument( - connection *db.Database, cfg *config.Config, collectionName string, + connection *mongo.Client, cfg *config.Config, collectionName string, filter bson.M, update bson.M, ) error { - collection := connection.Client.Database(connection.DbName). + collection := connection.Database(cfg.Db.DbName). Collection(collectionName) // Perform the update operation diff --git a/tests/testutils/datagen.go b/tests/testutils/datagen.go index 0290085c..376d2b71 100644 --- a/tests/testutils/datagen.go +++ b/tests/testutils/datagen.go @@ -9,8 +9,8 @@ import ( "time" bbndatagen "github.com/babylonlabs-io/babylon/testutil/datagen" - "github.com/babylonlabs-io/staking-api-service/internal/config" - "github.com/babylonlabs-io/staking-api-service/internal/types" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" "github.com/babylonlabs-io/staking-queue-client/client" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -183,6 +183,11 @@ func GenerateRandomFinalityProviderDetail(r *rand.Rand, numOfFps uint64) []types return finalityProviders } +func RandomFinalityProviderState(r *rand.Rand) types.FinalityProviderState { + states := []types.FinalityProviderState{types.FinalityProviderStateActive, types.FinalityProviderStateStandby} + return states[r.Intn(len(states))] +} + // GenerateRandomActiveStakingEvents generates a random number of active staking events // with random values for each field. // default to max 11 events, 11 finality providers, and 11 stakers @@ -243,3 +248,43 @@ func GenerateRandomActiveStakingEvents( } return activeStakingEvents } + +func GenerateRandomBabylonParams(r *rand.Rand) types.BabylonParams { + return types.BabylonParams{ + Version: r.Intn(10), + CovenantPKs: GeneratePks(r.Intn(10)), + CovenantQuorum: r.Intn(10), + MaxStakingAmount: int64(r.Intn(1000000000000000000)), + MinStakingAmount: int64(r.Intn(1000000000000000000)), + MaxStakingTime: int64(r.Intn(1000000000000000000)), + MinStakingTime: int64(r.Intn(1000000000000000000)), + SlashingPKScript: RandomString(r, 10), + MinSlashingTxFee: int64(r.Intn(1000000000000000000)), + SlashingRate: RandomPostiveFloat64(r), + MinUnbondingTime: int64(r.Intn(1000000000000000000)), + UnbondingFee: int64(r.Intn(1000000000000000000)), + MinCommissionRate: RandomPostiveFloat64(r), + MaxActiveFinalityProviders: r.Intn(10), + DelegationCreationBaseGasFee: int64(r.Intn(1000000000000000000)), + } +} + +func GenerateRandomBTCParams(r *rand.Rand) types.BTCParams { + return types.BTCParams{ + Version: r.Intn(10), + BTCConfirmationDepth: r.Intn(10), + } +} + +func RandomDelegationState(r *rand.Rand) types.DelegationState { + states := []types.DelegationState{types.Active, types.UnbondingRequested, types.Unbonding, types.Unbonded, types.Withdrawn} + return states[r.Intn(len(states))] +} + +func RandomTransactionInfo(r *rand.Rand) types.TransactionInfo { + _, txHex, _ := GenerateRandomTx(r, nil) + return types.TransactionInfo{ + TxHex: txHex, + OutputIndex: r.Intn(100), + } +} diff --git a/tests/unit_test/utils/btc_test.go b/tests/unit_test/utils/btc_test.go index aedd7c1b..f66f1e64 100644 --- a/tests/unit_test/utils/btc_test.go +++ b/tests/unit_test/utils/btc_test.go @@ -3,7 +3,7 @@ package utilstest import ( "testing" - "github.com/babylonlabs-io/staking-api-service/internal/utils" + "github.com/babylonlabs-io/staking-api-service/internal/shared/utils" "github.com/btcsuite/btcd/chaincfg" "github.com/stretchr/testify/assert" )