Skip to content

Commit

Permalink
feat(SQS): queries and state validations scripts (osmosis-labs#7043)
Browse files Browse the repository at this point in the history
* feat(SQS): queries and state validations scripts

* lint
  • Loading branch information
p0mvn authored Dec 10, 2023
1 parent 26aac7d commit 004b371
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 32 deletions.
13 changes: 12 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,25 @@ sqs-load-test-ui:
sqs-profile:
go tool pprof -http=:8080 http://localhost:9092/debug/pprof/profile?seconds=15

# Validates that SQS concentrated liquidity pool state is
# consistent with the state of the chain.
sqs-validate-cl-state:
ingest/sqs/scripts/validate-cl-state.sh "http://localhost:9092"

# Compares the quotes betwen SQS and chain over pool 1136
# which is concentrated.
sqs-quote-compare:
ingest/sqs/scripts/quote.sh "http://localhost:9092"

# Updates go tests with the latest mainnet state
# Make sure that the node is running locally
sqs-update-mainnet-state:
curl -X POST "http:/localhost:9092/store-state"
curl -X POST "http:/localhost:9092/router/store-state"
mv pools.json ingest/sqs/router/usecase/routertesting/parsing/pools.json
mv taker_fees.json ingest/sqs/router/usecase/routertesting/parsing/taker_fees.json



###############################################################################
### Release ###
###############################################################################
Expand Down
5 changes: 5 additions & 0 deletions ingest/sqs/domain/mocks/pools_usecase_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ func (pm *PoolsUsecaseMock) GetTickModelMap(ctx context.Context, poolIDs []uint6
return pm.TickModelMap, nil
}

// GetPool implements mvc.PoolsUsecase.
func (pm *PoolsUsecaseMock) GetPool(ctx context.Context, poolID uint64) (domain.PoolI, error) {
panic("unimplemented")
}

var _ mvc.PoolsUsecase = &PoolsUsecaseMock{}
2 changes: 2 additions & 0 deletions ingest/sqs/domain/mvc/pools.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ type PoolsUsecase interface {
GetRoutesFromCandidates(ctx context.Context, candidateRoutes route.CandidateRoutes, takerFeeMap domain.TakerFeeMap, tokenInDenom, tokenOutDenom string) ([]route.RouteImpl, error)

GetTickModelMap(ctx context.Context, poolIDs []uint64) (map[uint64]domain.TickModel, error)
// GetPool returns the pool with the given ID.
GetPool(ctx context.Context, poolID uint64) (domain.PoolI, error)
}
2 changes: 2 additions & 0 deletions ingest/sqs/domain/mvc/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ type RouterUsecase interface {
GetCustomQuote(ctx context.Context, tokenIn sdk.Coin, tokenOutDenom string, poolIDs []uint64) (domain.Quote, error)
// GetCandidateRoutes returns the candidate routes for the given tokenIn and tokenOutDenom.
GetCandidateRoutes(ctx context.Context, tokenInDenom, tokenOutDenom string) (route.CandidateRoutes, error)
// GetTakerFee returns the taker fee for all token pairs in a pool.
GetTakerFee(ctx context.Context, poolID uint64) ([]domain.TakerFeeForPair, error)
// StoreRoutes stores all router state in the files locally. Used for debugging.
StoreRouterStateFiles(ctx context.Context) error
}
16 changes: 10 additions & 6 deletions ingest/sqs/domain/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func (tfm TakerFeeMap) Has(denom0, denom1 string) bool {
// GetTakerFee returns the taker fee for the given denoms.
// It sorts the denoms lexicographically before looking up the taker fee.
// Returns error if the taker fee is not found.
func (tfm TakerFeeMap) GetTakerFee(denom0, denom1 string) (osmomath.Dec, error) {
func (tfm TakerFeeMap) GetTakerFee(denom0, denom1 string) osmomath.Dec {
// Ensure increasing lexicographic order.
if denom1 < denom0 {
denom0, denom1 = denom1, denom0
Expand All @@ -160,13 +160,10 @@ func (tfm TakerFeeMap) GetTakerFee(denom0, denom1 string) (osmomath.Dec, error)
takerFee, found := tfm[DenomPair{Denom0: denom0, Denom1: denom1}]

if !found {
return osmomath.Dec{}, TakerFeeNotFoundForDenomPairError{
Denom0: denom0,
Denom1: denom1,
}
return DefaultTakerFee
}

return takerFee, nil
return takerFee
}

// SetTakerFee sets the taker fee for the given denoms.
Expand All @@ -180,4 +177,11 @@ func (tfm TakerFeeMap) SetTakerFee(denom0, denom1 string, takerFee osmomath.Dec)
tfm[DenomPair{Denom0: denom0, Denom1: denom1}] = takerFee
}

// TakerFeeForPair represents the taker fee for a pair of tokens
type TakerFeeForPair struct {
Denom0 string
Denom1 string
TakerFee osmomath.Dec
}

var DefaultTakerFee = osmomath.MustNewDecFromStr("0.001000000000000000")
43 changes: 43 additions & 0 deletions ingest/sqs/pools/delivery/http/pools_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package http

import (
"net/http"
"strconv"

"github.com/labstack/echo"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -33,6 +34,8 @@ func NewPoolsHandler(e *echo.Echo, us mvc.PoolsUsecase) {
}

e.GET(formatPoolsResource("/all"), handler.GetAllPools)
e.GET(formatPoolsResource("/:id"), handler.GetPool)
e.GET(formatPoolsResource("/ticks/:id"), handler.GetConcentratedPoolTicks)
}

// GetAllPools will fetch all supported pool types by the Osmosis
Expand All @@ -48,6 +51,46 @@ func (a *PoolsHandler) GetAllPools(c echo.Context) error {
return c.JSON(http.StatusOK, pools)
}

// GetPool will fetch a pool by its id
func (a *PoolsHandler) GetPool(c echo.Context) error {
ctx := c.Request().Context()

idStr := c.Param("id")
poolID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return c.JSON(http.StatusBadRequest, ResponseError{Message: err.Error()})
}

pools, err := a.PUsecase.GetPool(ctx, poolID)
if err != nil {
return c.JSON(getStatusCode(err), ResponseError{Message: err.Error()})
}

return c.JSON(http.StatusOK, pools)
}

func (a *PoolsHandler) GetConcentratedPoolTicks(c echo.Context) error {
ctx := c.Request().Context()

idStr := c.Param("id")
poolID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return c.JSON(http.StatusBadRequest, ResponseError{Message: err.Error()})
}

pools, err := a.PUsecase.GetTickModelMap(ctx, []uint64{poolID})
if err != nil {
return c.JSON(getStatusCode(err), ResponseError{Message: err.Error()})
}

tickModel, ok := pools[poolID]
if !ok {
return c.JSON(http.StatusNotFound, ResponseError{Message: "tick model not found for given pool"})
}

return c.JSON(http.StatusOK, tickModel)
}

func getStatusCode(err error) int {
if err == nil {
return http.StatusOK
Expand Down
39 changes: 19 additions & 20 deletions ingest/sqs/pools/usecase/pools_usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package usecase

import (
"context"
"errors"
"time"

"github.com/osmosis-labs/osmosis/v21/ingest/sqs/domain"
Expand Down Expand Up @@ -78,25 +77,7 @@ func (p *poolsUseCase) GetRoutesFromCandidates(ctx context.Context, candidateRou
}

// Get taker fee
takerFee, err := takerFeeMap.GetTakerFee(previousTokenOutDenom, candidatePool.TokenOutDenom)
if err != nil {
denom0 := previousTokenOutDenom
denom1 := candidatePool.TokenOutDenom
if denom1 < denom0 {
denom0, denom1 = denom1, denom0
}

// If taker fee is not found, set default
if errors.Is(err, domain.TakerFeeNotFoundForDenomPairError{
Denom0: denom0,
Denom1: denom1,
}) {
// TODO: make this more dynamic instead of hardcoding
takerFee = domain.DefaultTakerFee
} else {
return nil, err
}
}
takerFee := takerFeeMap.GetTakerFee(previousTokenOutDenom, candidatePool.TokenOutDenom)

if pool.GetType() == poolmanagertypes.Concentrated {
// Get tick model for concentrated pool
Expand Down Expand Up @@ -136,3 +117,21 @@ func (p *poolsUseCase) GetTickModelMap(ctx context.Context, poolIDs []uint64) (m

return tickModelMap, nil
}

// GetPool implements mvc.PoolsUsecase.
func (p *poolsUseCase) GetPool(ctx context.Context, poolID uint64) (domain.PoolI, error) {
pools, err := p.poolsRepository.GetPools(ctx, map[uint64]struct {
}{
poolID: {},
})

if err != nil {
return nil, err
}

pool, ok := pools[poolID]
if !ok {
return nil, domain.PoolNotFoundError{PoolID: poolID}
}
return pool, nil
}
18 changes: 18 additions & 0 deletions ingest/sqs/router/delivery/http/router_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func NewRouterHandler(e *echo.Echo, us mvc.RouterUsecase, logger log.Logger) {
e.GET(formatRouterResource("/single-quote"), handler.GetBestSingleRouteQuote)
e.GET(formatRouterResource("/routes"), handler.GetCandidateRoutes)
e.GET(formatRouterResource("/custom-quote"), handler.GetCustomQuote)
e.GET(formatRouterResource("/taker-fee-pool/:id"), handler.GetTakerFee)
e.POST(formatRouterResource("/store-state"), handler.StoreRouterStateInFiles)
}

Expand Down Expand Up @@ -148,6 +149,23 @@ func (a *RouterHandler) GetCandidateRoutes(c echo.Context) error {
return nil
}

func (a *RouterHandler) GetTakerFee(c echo.Context) error {
ctx := c.Request().Context()

idStr := c.Param("id")
poolID, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
return c.JSON(http.StatusBadRequest, ResponseError{Message: err.Error()})
}

takerFees, err := a.RUsecase.GetTakerFee(ctx, poolID)
if err != nil {
return c.JSON(getStatusCode(err), ResponseError{Message: err.Error()})
}

return c.JSON(http.StatusOK, takerFees)
}

// TODO: authentication for the endpoint and enable only in dev mode.
func (a *RouterHandler) StoreRouterStateInFiles(c echo.Context) error {
ctx := c.Request().Context()
Expand Down
34 changes: 34 additions & 0 deletions ingest/sqs/router/usecase/router_usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,40 @@ func (r *routerUseCaseImpl) GetCandidateRoutes(ctx context.Context, tokenInDenom
return routes, nil
}

// GetTakerFee implements mvc.RouterUsecase.
func (r *routerUseCaseImpl) GetTakerFee(ctx context.Context, poolID uint64) ([]domain.TakerFeeForPair, error) {
takerFees, err := r.routerRepository.GetAllTakerFees(ctx)
if err != nil {
return []domain.TakerFeeForPair{}, err
}

pool, err := r.poolsUsecase.GetPool(ctx, poolID)
if err != nil {
return []domain.TakerFeeForPair{}, err
}

poolDenoms := pool.GetPoolDenoms()

result := make([]domain.TakerFeeForPair, 0)

for i := range poolDenoms {
for j := i + 1; j < len(poolDenoms); j++ {
denom0 := poolDenoms[i]
denom1 := poolDenoms[j]

takerFee := takerFees.GetTakerFee(denom0, denom1)

result = append(result, domain.TakerFeeForPair{
Denom0: denom0,
Denom1: denom1,
TakerFee: takerFee,
})
}
}

return result, nil
}

// initializeRouter initializes the router per configuration defined on the use case
// Returns error if:
// - there is an error retrieving pools from the store
Expand Down
57 changes: 57 additions & 0 deletions ingest/sqs/scripts/healthcheck.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/bin/bash

validate_http_code() {
local http_status=$1
local check_url=$2
# Check if the HTTP status code is 200
if [ "$http_status" -eq 200 ]; then
echo "Status check successful (HTTP 200 OK) " $check_url
else
echo "Health check failed with HTTP $http_status"
error_message=$(curl -s -o /dev/null -w "%{stderr}" $check_url)
echo "Error message: $error_message"
exit 1
fi
}

# This script performs a health check on the node by making a GET request to the health check URL
# and checking if the HTTP status code is 200. If the HTTP status code is not 200, the script
# will exit with a non-zero exit code and print the error message.
perform_health_check() {
local url=$1

health_check_url=$url/healthcheck

# Make a GET request to the health check URL and capture the HTTP status code and error message
code_str=$(curl --write-out '%{http_code}' --silent --output /dev/null $health_check_url)

validate_http_code $code_str $health_check_url
}

# This script performs a status check on the node by making a POST request to the status check URL via grpc
# and checking if the HTTP status code is 200. If the HTTP status code is not 200, the script
# will exit with a non-zero exit code and print the error message.
# It also prints height and whether a node is syncing or not.
perform_status_check() {
local status_check_url=$1

full_status_url=$status_check_url/status

code_str=$(curl --write-out '%{http_code}' --silent --output /dev/null $full_status_url)

validate_http_code $code_str $full_status_url

# Make a POST request to the health check URL and capture the full response
full_response=$(curl -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"method": "status",
"id": 1
}' $status_check_url)

# Extract the status code using jq
block_height=$(echo "$full_response" | jq .result.sync_info.latest_block_height)
echo "Height" $block_height

is_syncing=$(echo "$full_response" | jq .result.sync_info.catching_up)
echo "Is Synching" $is_syncing
}
14 changes: 14 additions & 0 deletions ingest/sqs/scripts/quote.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

url=$1

# This script compares single hop quotes by running them against SQS and chain directly.

chain_amount_out=$(osmosisd q poolmanager estimate-swap-exact-amount-in 1136 1000000ibc/C140AFD542AE77BD7DCC83F13FDD8C5E5BB8C4929785E6EC2F4C636F98F17901 --swap-route-pool-ids 1136 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2)

sqs_res=$(curl "$url/router/custom-quote?tokenIn=1000000ibc/C140AFD542AE77BD7DCC83F13FDD8C5E5BB8C4929785E6EC2F4C636F98F17901&tokenOutDenom=ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2&poolIDs=1136")

sqs_amount_out=$(echo $sqs_res | jq .amount_out)

echo "chain_amount_out: $chain_amount_out"
echo "sqs_amount_out: $sqs_amount_out"
Loading

0 comments on commit 004b371

Please sign in to comment.