Skip to content

feat: add pk lookup endpoint #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,47 @@ const docTemplate = `{
}
}
},
"/v1/staker/pubkey-lookup": {
"get": {
"description": "Retrieves public keys for the given BTC addresses. This endpoint",
"produces": [
"application/json"
],
"parameters": [
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "csv",
"description": "List of BTC addresses to look up (up to 10), currently only supports Taproot and Native Segwit addresses",
"name": "address",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "A map of BTC addresses to their corresponding public keys (only addresses with delegations are returned)",
"schema": {
"$ref": "#/definitions/handlers.Result"
}
},
"400": {
"description": "Bad Request: Invalid input parameters",
"schema": {
"$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error"
}
}
}
}
},
"/v1/stats": {
"get": {
"description": "Fetches overall stats for babylon staking including tvl, total delegations, active tvl, active delegations and total stakers.",
Expand Down Expand Up @@ -511,6 +552,9 @@ const docTemplate = `{
"active_tvl": {
"type": "integer"
},
"pending_tvl": {
"type": "integer"
},
"total_delegations": {
"type": "integer"
},
Expand Down
44 changes: 44 additions & 0 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,47 @@
}
}
},
"/v1/staker/pubkey-lookup": {
"get": {
"description": "Retrieves public keys for the given BTC addresses. This endpoint",
"produces": [
"application/json"
],
"parameters": [
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "csv",
"description": "List of BTC addresses to look up (up to 10), currently only supports Taproot and Native Segwit addresses",
"name": "address",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "A map of BTC addresses to their corresponding public keys (only addresses with delegations are returned)",
"schema": {
"$ref": "#/definitions/handlers.Result"
}
},
"400": {
"description": "Bad Request: Invalid input parameters",
"schema": {
"$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error"
}
}
}
}
},
"/v1/stats": {
"get": {
"description": "Fetches overall stats for babylon staking including tvl, total delegations, active tvl, active delegations and total stakers.",
Expand Down Expand Up @@ -500,6 +541,9 @@
"active_tvl": {
"type": "integer"
},
"pending_tvl": {
"type": "integer"
},
"total_delegations": {
"type": "integer"
},
Expand Down
31 changes: 31 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ definitions:
type: integer
active_tvl:
type: integer
pending_tvl:
type: integer
total_delegations:
type: integer
total_stakers:
Expand Down Expand Up @@ -342,6 +344,35 @@ paths:
description: 'Error: Bad Request'
schema:
$ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error'
/v1/staker/pubkey-lookup:
get:
description: Retrieves public keys for the given BTC addresses. This endpoint
parameters:
- collectionFormat: csv
description: List of BTC addresses to look up (up to 10), currently only supports
Taproot and Native Segwit addresses
in: query
items:
type: string
name: address
required: true
type: array
produces:
- application/json
responses:
"200":
description: A map of BTC addresses to their corresponding public keys (only
addresses with delegations are returned)
schema:
$ref: '#/definitions/handlers.Result'
"400":
description: 'Bad Request: Invalid input parameters'
schema:
$ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/github_com_babylonlabs-io_staking-api-service_internal_types.Error'
/v1/stats:
get:
description: Fetches overall stats for babylon staking including tvl, total
Expand Down
35 changes: 34 additions & 1 deletion internal/api/handlers/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"context"
"fmt"
"net/http"

"github.com/babylonlabs-io/staking-api-service/internal/config"
Expand Down Expand Up @@ -103,11 +104,43 @@ func parseBtcAddressQuery(
http.StatusBadRequest, types.BadRequest, queryName+" is required",
)
}
err := utils.IsValidBtcAddress(address, netParam)
_, err := utils.CheckBtcAddressType(address, netParam)
if err != nil {
return "", types.NewErrorWithMsg(
http.StatusBadRequest, types.BadRequest, err.Error(),
)
}
return address, nil
}

func parseBtcAddressesQuery(
r *http.Request, queryName string, netParam *chaincfg.Params, limit int,
) ([]string, *types.Error) {
// Get all the values for the queryName
addresses := r.URL.Query()[queryName]
// Check if no addresses were provided
if len(addresses) == 0 {
return nil, types.NewErrorWithMsg(
http.StatusBadRequest, types.BadRequest, queryName+" is required",
)
}

// Check if the number of addresses exceeds the limit
if len(addresses) > limit {
return nil, types.NewErrorWithMsg(
http.StatusBadRequest, types.BadRequest, fmt.Sprintf("Maximum %d %s allowed", limit, queryName),
)
}

// Validate each address
for _, address := range addresses {
_, err := utils.CheckBtcAddressType(address, netParam)
if err != nil {
return nil, types.NewErrorWithMsg(
http.StatusBadRequest, types.BadRequest, err.Error(),
)
}
}

return addresses, nil
}
2 changes: 1 addition & 1 deletion internal/api/handlers/ordinals.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func parseRequestPayload(request *http.Request, maxUTXOs uint32, netParam *chain
}
}

if err := utils.IsValidBtcAddress(payload.Address, netParam); err != nil {
if _, err := utils.CheckBtcAddressType(payload.Address, netParam); err != nil {
return nil, types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, err.Error())
}
return &payload, nil
Expand Down
46 changes: 46 additions & 0 deletions internal/api/handlers/pubkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package handlers

import (
"net/http"

"github.com/babylonlabs-io/staking-api-service/internal/types"
)

const (
// MAX_NUM_PK_LOOKUP_ADDRESSES is the maximum number of addresses that can be queried
// in a single request. This limit helps prevent the URL from becoming too long
// and potentially being rejected by the server or browser. While this is a soft
// limit that can be increased if needed, we are setting it conservatively at 10
// to ensure compatibility. Given a URL length limit of 2048 characters and a
// worst-case scenario where each address is 64 characters long, we can support
// up to 28 addresses. However, we limit it to 10 for added safety.
MAX_NUM_PK_LOOKUP_ADDRESSES = 10
)

// GetPubKeys @Summary Get stakers' public keys
// @Description Retrieves public keys for the given BTC addresses. This endpoint
// only returns public keys for addresses that have associated delegations in
// 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
// @Param address query []string true "List of BTC addresses to look up (up to 10), currently only supports Taproot and Native Segwit addresses"
// @Success 200 {object} 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,
)
if err != nil {
return nil, err
}

// Get the public keys for the given addresses
addressToPkMapping, err := h.services.GetStakerPublicKeysByAddresses(request.Context(), addresses)
if err != nil {
return nil, err
}

return NewResult(addressToPkMapping), nil
}
2 changes: 2 additions & 0 deletions internal/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@ func (a *Server) SetupRoutes(r *chi.Mux) {
r.Post("/v1/ordinals/verify-utxos", registerHandler(handlers.VerifyUTXOs))
}

r.Get("/v1/staker/pubkey-lookup", registerHandler(handlers.GetPubKeys))

r.Get("/swagger/*", httpSwagger.WrapHandler)
}
15 changes: 15 additions & 0 deletions internal/db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ type DBClient interface {
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)
}

type DelegationFilter struct {
Expand Down
44 changes: 44 additions & 0 deletions internal/db/pk_address_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/babylonlabs-io/staking-api-service/internal/db/model"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)

Expand All @@ -23,3 +24,46 @@ func (db *Database) InsertPkAddressMappings(
}
return nil
}

func (db *Database) FindPkMappingsByTaprootAddress(
ctx context.Context, taprootAddresses []string,
) ([]*model.PkAddressMapping, error) {
client := db.Client.Database(db.DbName).Collection(model.PkAddressMappingsCollection)
filter := bson.M{"taproot": bson.M{"$in": taprootAddresses}}

addressMapping := []*model.PkAddressMapping{}
cursor, err := client.Find(ctx, filter)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &addressMapping); err != nil {
return nil, err
}
return addressMapping, nil
}

func (db *Database) FindPkMappingsByNativeSegwitAddress(
ctx context.Context, nativeSegwitAddresses []string,
) ([]*model.PkAddressMapping, error) {
client := db.Client.Database(db.DbName).Collection(
model.PkAddressMappingsCollection,
)
filter := bson.M{
"$or": []bson.M{
{"native_segwit_even": bson.M{"$in": nativeSegwitAddresses}},
{"native_segwit_odd": bson.M{"$in": nativeSegwitAddresses}},
},
}

addressMapping := []*model.PkAddressMapping{}
cursor, err := client.Find(ctx, filter)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &addressMapping); err != nil {
return nil, err
}
return addressMapping, nil
}
Loading
Loading