Skip to content
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

add ordinal endpoint and tests #207

Merged
merged 1 commit into from
Jul 24, 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
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ run-unprocessed-events-replay-local:
--replay

generate-mock-interface:
cd internal/db && mockery --name=DBClient --output=../../tests/mocks --outpkg=dbmock --filename=mock_db_client.go
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/clients/unisat && mockery --name=UnisatClientInterface --output=../../../tests/mocks --outpkg=mocks --filename=mock_unisat_client.go

tests:
./bin/local-startup.sh;
Expand Down
8 changes: 6 additions & 2 deletions cmd/staking-api-service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/babylonchain/staking-api-service/cmd/staking-api-service/cli"
"github.com/babylonchain/staking-api-service/cmd/staking-api-service/scripts"
"github.com/babylonchain/staking-api-service/internal/api"
"github.com/babylonchain/staking-api-service/internal/clients"
"github.com/babylonchain/staking-api-service/internal/config"
"github.com/babylonchain/staking-api-service/internal/db/model"
"github.com/babylonchain/staking-api-service/internal/observability/metrics"
Expand Down Expand Up @@ -58,12 +59,15 @@ func main() {
if err != nil {
log.Fatal().Err(err).Msg("error while setting up staking db model")
}
services, err := services.New(ctx, cfg, params, finalityProviders)

// initialize clients package which is used to interact with external services
clients := clients.New(cfg)
jrwbabylonlab marked this conversation as resolved.
Show resolved Hide resolved
services, err := services.New(ctx, cfg, params, finalityProviders, clients)
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)
queues := queue.New(cfg.Queue, services)

// Check if the replay flag is set
if cli.GetReplayFlag() {
Expand Down
13 changes: 12 additions & 1 deletion config/config-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ server:
write-timeout: 60s
read-timeout: 60s
idle-timeout: 60s
allowed-origins: [ "*" ]
allowed-origins: ["*"]
log-level: debug
btc-net: "mainnet"
max-content-length: 4096
Expand All @@ -27,3 +27,14 @@ queue:
metrics:
host: 0.0.0.0
port: 2112
assets:
max_utxos: 100
jeremy-babylonlabs marked this conversation as resolved.
Show resolved Hide resolved
jrwbabylonlab marked this conversation as resolved.
Show resolved Hide resolved
ordinals:
host: "http://ord-poc.devnet.babylonchain.io"
port: 8888
timeout: 1000
unisat:
host: "https://open-api.unisat.io"
limit: 100
timeout: 1000
token: "add your token as ASSETS_UNISAT_TOKEN in environment variables"
13 changes: 12 additions & 1 deletion config/config-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ server:
write-timeout: 60s
read-timeout: 60s
idle-timeout: 60s
allowed-origins: [ "*" ]
allowed-origins: ["*"]
log-level: debug
btc-net: "signet"
max-content-length: 4096
Expand All @@ -27,3 +27,14 @@ queue:
metrics:
host: 0.0.0.0
port: 2112
assets:
max_utxos: 100
ordinals:
host: "http://ord-poc.devnet.babylonchain.io"
port: 8888
timeout: 5000
unisat:
host: "https://open-api-testnet.unisat.io"
limit: 100
timeout: 5000
token: "add your token as ASSETS_UNISAT_TOKEN in .env"
8 changes: 6 additions & 2 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,14 +622,18 @@ const docTemplate = `{
"VALIDATION_ERROR",
"NOT_FOUND",
"BAD_REQUEST",
"FORBIDDEN"
"FORBIDDEN",
"UNPROCESSABLE_ENTITY",
"REQUEST_TIMEOUT"
],
"x-enum-varnames": [
"InternalServiceError",
"ValidationError",
"NotFound",
"BadRequest",
"Forbidden"
"Forbidden",
"UnprocessableEntity",
"RequestTimeout"
]
}
}
Expand Down
55 changes: 55 additions & 0 deletions docs/ordinals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Ordinals in API system

The Babylon Staking API allows for the option to deploy additional endpoints
that check whether a UTXO contains an inscription or not, with the aim to help
staking applications identify whether they should avoid spending a particular UTXO.
This is accomplished through a connection to the
[Ordinal Service](https://github.com/ordinals/ord) and
a connection to the Unisat API.
Due to Unisat being a payed service and applying rate limits,
the API initially tries to get the status of a UTXO through the Ordinals Service,
and if that fails, then contacts the Unisat API,
effectively using it as a back-up mechanism to handle downtime from the Ordinals Service.

NOTE: To enable the optional ordinal API endpoint, you will need to provide the
`ordinal` and `unisat` configurations under `assets`

## Ordinal service Client

WIP

## Unisat Service Client

You can find more information about Unisat's Ordinal/BRC-20/Runes related endpoints at:
https://docs.unisat.io/

In our service, we only utilize the following endpoint:
- `/v1/indexer/address/{{address}}/inscription-utxo-data`

### How to Use It

1. Log in via https://developer.unisat.io/account/login (create an account if you don't have one).
2. Copy the `API-Key`.
3. Set the key as an environment variable named `UNISAT_TOKEN`.
4. Configure the values for `unisat.host`, `limit`, `timeout`, etc. Refer to `config-docker.yml`.
5. Ensure you also set up the `ordinals` configuration, as this is a dependency.
6. Call the POST endpoint `/v1/ordinals/verify-utxos` as shown in the example below:
7. The calls to unisat will only be triggered if the ordinal service is not responding or returning errors
```POST
{
"utxos": [
{
"txid": "143c33b4ff4450a60648aec6b4d086639322cb093195226c641ae4f0ae33c3f5",
"vout": 2
},
{
"txid": "be3877c8dedd716f026cc77ef3f04f940b40b064d1928247cff5bb08ef1ba58e",
"vout": 0
},
{
"txid": "d7f65a37f59088b3b4e4bc119727daa0a0dd8435a645c49e6a665affc109539d",
"vout": 0
}
],
"address": "tb1pyqjxwcdv6pfcaj2l565ludclz2pwu2k5azs6uznz8kml74kkma6qm0gzlv"
}
8 changes: 6 additions & 2 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -611,14 +611,18 @@
"VALIDATION_ERROR",
"NOT_FOUND",
"BAD_REQUEST",
"FORBIDDEN"
"FORBIDDEN",
"UNPROCESSABLE_ENTITY",
"REQUEST_TIMEOUT"
],
"x-enum-varnames": [
"InternalServiceError",
"ValidationError",
"NotFound",
"BadRequest",
"Forbidden"
"Forbidden",
"UnprocessableEntity",
"RequestTimeout"
]
}
}
Expand Down
4 changes: 4 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,17 @@ definitions:
- NOT_FOUND
- BAD_REQUEST
- FORBIDDEN
- UNPROCESSABLE_ENTITY
- REQUEST_TIMEOUT
type: string
x-enum-varnames:
- InternalServiceError
- ValidationError
- NotFound
- BadRequest
- Forbidden
- UnprocessableEntity
- RequestTimeout
info:
contact: {}
paths:
Expand Down
57 changes: 57 additions & 0 deletions internal/api/handlers/ordinals.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package handlers

import (
"encoding/json"
"net/http"

"github.com/babylonchain/staking-api-service/internal/types"
"github.com/babylonchain/staking-api-service/internal/utils"
"github.com/btcsuite/btcd/chaincfg"
)

type VerifyUTXOsRequestPayload struct {
jrwbabylonlab marked this conversation as resolved.
Show resolved Hide resolved
Address string `json:"address"`
UTXOs []types.UTXOIdentifier `json:"utxos"`
}

func parseRequestPayload(request *http.Request, maxUTXOs uint32, netParam *chaincfg.Params) (*VerifyUTXOsRequestPayload, *types.Error) {
var payload VerifyUTXOsRequestPayload
if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
return nil, types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, "invalid input format")
}
utxos := payload.UTXOs
if len(utxos) == 0 {
jrwbabylonlab marked this conversation as resolved.
Show resolved Hide resolved
return nil, types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, "empty UTXO array")
}

if uint32(len(utxos)) > maxUTXOs {
return nil, types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, "too many UTXOs in the request")
}

for _, utxo := range utxos {
if !utils.IsValidTxHash(utxo.Txid) {
return nil, types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, "invalid UTXO txid")
} else if utxo.Vout < 0 {
return nil, types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, "invalid UTXO vout")
}
}

if err := utils.IsValidBtcAddress(payload.Address, netParam); err != nil {
return nil, types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, err.Error())
}
return &payload, nil
}

func (h *Handler) VerifyUTXOs(request *http.Request) (*Result, *types.Error) {
jeremy-babylonlabs marked this conversation as resolved.
Show resolved Hide resolved
inputs, err := parseRequestPayload(request, h.config.Assets.MaxUTXOs, h.config.Server.BTCNetParam)
if err != nil {
jeremy-babylonlabs marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
}

results, err := h.services.VerifyUTXOs(request.Context(), inputs.UTXOs, inputs.Address)
if err != nil {
return nil, err
}

return NewResult(results), nil
}
6 changes: 6 additions & 0 deletions internal/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,11 @@ func (a *Server) SetupRoutes(r *chi.Mux) {
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 {
jrwbabylonlab marked this conversation as resolved.
Show resolved Hide resolved
r.Post("/v1/ordinals/verify-utxos", registerHandler(handlers.VerifyUTXOs))
}

r.Get("/swagger/*", httpSwagger.WrapHandler)
}
4 changes: 3 additions & 1 deletion internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
type Server struct {
httpServer *http.Server
handlers *handlers.Handler
cfg *config.Config
jrwbabylonlab marked this conversation as resolved.
Show resolved Hide resolved
}

func New(
Expand Down Expand Up @@ -52,6 +53,7 @@ func New(
server := &Server{
httpServer: srv,
handlers: handlers,
cfg: cfg,
}
server.SetupRoutes(r)
return server, nil
Expand All @@ -60,4 +62,4 @@ func New(
func (a *Server) Start() error {
log.Info().Msgf("Starting server on %s", a.httpServer.Addr)
return a.httpServer.ListenAndServe()
}
}
Loading