Skip to content

Commit

Permalink
Merge pull request #41 from gaze-network/develop
Browse files Browse the repository at this point in the history
Release v0.3.0
  • Loading branch information
gazenw authored Jul 25, 2024
2 parents 60717ec + 7dcbd08 commit 1967895
Show file tree
Hide file tree
Showing 20 changed files with 610 additions and 183 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"ui.completion.usePlaceholders": false,
"ui.diagnostic.analyses": {
// https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md
// "fieldalignment": false,
"fieldalignment": false,
"nilness": true,
"shadow": false,
"unusedparams": true,
Expand Down
64 changes: 41 additions & 23 deletions modules/runes/api/httphandler/get_balances_by_address.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
package httphandler

import (
"slices"

"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common/errs"
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
"github.com/gaze-network/indexer-network/modules/runes/runes"
"github.com/gaze-network/uint128"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)

type getBalancesByAddressRequest struct {
type getBalancesRequest struct {
Wallet string `params:"wallet"`
Id string `query:"id"`
BlockHeight uint64 `query:"blockHeight"`
Limit int32 `query:"limit"`
Offset int32 `query:"offset"`
}

func (r getBalancesByAddressRequest) Validate() error {
const (
getBalancesMaxLimit = 5000
getBalancesDefaultLimit = 100
)

func (r getBalancesRequest) Validate() error {
var errList []error
if r.Wallet == "" {
errList = append(errList, errors.New("'wallet' is required"))
}
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
errList = append(errList, errors.New("'id' is not valid rune id or rune name"))
}
if r.Limit < 0 {
errList = append(errList, errors.New("'limit' must be non-negative"))
}
if r.Limit > getBalancesMaxLimit {
errList = append(errList, errors.Errorf("'limit' cannot exceed %d", getBalancesMaxLimit))
}
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
}

Expand All @@ -36,15 +48,15 @@ type balance struct {
Decimals uint8 `json:"decimals"`
}

type getBalancesByAddressResult struct {
type getBalancesResult struct {
List []balance `json:"list"`
BlockHeight uint64 `json:"blockHeight"`
}

type getBalancesByAddressResponse = HttpResponse[getBalancesByAddressResult]
type getBalancesResponse = HttpResponse[getBalancesResult]

func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
var req getBalancesByAddressRequest
func (h *HttpHandler) GetBalances(ctx *fiber.Ctx) (err error) {
var req getBalancesRequest
if err := ctx.ParamsParser(&req); err != nil {
return errors.WithStack(err)
}
Expand All @@ -54,6 +66,9 @@ func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
if err := req.Validate(); err != nil {
return errors.WithStack(err)
}
if req.Limit == 0 {
req.Limit = getBalancesDefaultLimit
}

pkScript, ok := resolvePkScript(h.network, req.Wallet)
if !ok {
Expand All @@ -64,49 +79,52 @@ func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
if blockHeight == 0 {
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
if err != nil {
if errors.Is(err, errs.NotFound) {
return errs.NewPublicError("latest block not found")
}
return errors.Wrap(err, "error during GetLatestBlock")
}
blockHeight = uint64(blockHeader.Height)
}

balances, err := h.usecase.GetBalancesByPkScript(ctx.UserContext(), pkScript, blockHeight)
balances, err := h.usecase.GetBalancesByPkScript(ctx.UserContext(), pkScript, blockHeight, req.Limit, req.Offset)
if err != nil {
if errors.Is(err, errs.NotFound) {
return errs.NewPublicError("balances not found")
}
return errors.Wrap(err, "error during GetBalancesByPkScript")
}

runeId, ok := h.resolveRuneId(ctx.UserContext(), req.Id)
if ok {
// filter out balances that don't match the requested rune id
for key := range balances {
if key != runeId {
delete(balances, key)
}
}
balances = lo.Filter(balances, func(b *entity.Balance, _ int) bool {
return b.RuneId == runeId
})
}

balanceRuneIds := lo.Keys(balances)
balanceRuneIds := lo.Map(balances, func(b *entity.Balance, _ int) runes.RuneId {
return b.RuneId
})
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx.UserContext(), balanceRuneIds)
if err != nil {
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
}

balanceList := make([]balance, 0, len(balances))
for id, b := range balances {
runeEntry := runeEntries[id]
for _, b := range balances {
runeEntry := runeEntries[b.RuneId]
balanceList = append(balanceList, balance{
Amount: b.Amount,
Id: id,
Id: b.RuneId,
Name: runeEntry.SpacedRune,
Symbol: string(runeEntry.Symbol),
Decimals: runeEntry.Divisibility,
})
}
slices.SortFunc(balanceList, func(i, j balance) int {
return j.Amount.Cmp(i.Amount)
})

resp := getBalancesByAddressResponse{
Result: &getBalancesByAddressResult{
resp := getBalancesResponse{
Result: &getBalancesResult{
BlockHeight: blockHeight,
List: balanceList,
},
Expand Down
85 changes: 56 additions & 29 deletions modules/runes/api/httphandler/get_balances_by_address_batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package httphandler
import (
"context"
"fmt"
"slices"

"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common/errs"
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
"github.com/gaze-network/indexer-network/modules/runes/runes"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
"golang.org/x/sync/errgroup"
Expand All @@ -16,33 +17,49 @@ type getBalanceQuery struct {
Wallet string `json:"wallet"`
Id string `json:"id"`
BlockHeight uint64 `json:"blockHeight"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}

type getBalancesByAddressBatchRequest struct {
type getBalancesBatchRequest struct {
Queries []getBalanceQuery `json:"queries"`
}

func (r getBalancesByAddressBatchRequest) Validate() error {
const getBalancesBatchMaxQueries = 100

func (r getBalancesBatchRequest) Validate() error {
var errList []error
for _, query := range r.Queries {
if len(r.Queries) == 0 {
errList = append(errList, errors.New("at least one query is required"))
}
if len(r.Queries) > getBalancesBatchMaxQueries {
errList = append(errList, errors.Errorf("cannot exceed %d queries", getBalancesBatchMaxQueries))
}
for i, query := range r.Queries {
if query.Wallet == "" {
errList = append(errList, errors.Errorf("queries[%d]: 'wallet' is required"))
errList = append(errList, errors.Errorf("queries[%d]: 'wallet' is required", i))
}
if query.Id != "" && !isRuneIdOrRuneName(query.Id) {
errList = append(errList, errors.Errorf("queries[%d]: 'id' is not valid rune id or rune name"))
errList = append(errList, errors.Errorf("queries[%d]: 'id' is not valid rune id or rune name", i))
}
if query.Limit < 0 {
errList = append(errList, errors.Errorf("queries[%d]: 'limit' must be non-negative", i))
}
if query.Limit > getBalancesMaxLimit {
errList = append(errList, errors.Errorf("queries[%d]: 'limit' cannot exceed %d", i, getBalancesMaxLimit))
}
}
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
}

type getBalancesByAddressBatchResult struct {
List []*getBalancesByAddressResult `json:"list"`
type getBalancesBatchResult struct {
List []*getBalancesResult `json:"list"`
}

type getBalancesByAddressBatchResponse = HttpResponse[getBalancesByAddressBatchResult]
type getBalancesBatchResponse = HttpResponse[getBalancesBatchResult]

func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
var req getBalancesByAddressBatchRequest
func (h *HttpHandler) GetBalancesBatch(ctx *fiber.Ctx) (err error) {
var req getBalancesBatchRequest
if err := ctx.BodyParser(&req); err != nil {
return errors.WithStack(err)
}
Expand All @@ -53,11 +70,14 @@ func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
var latestBlockHeight uint64
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
if err != nil {
if errors.Is(err, errs.NotFound) {
return errs.NewPublicError("latest block not found")
}
return errors.Wrap(err, "error during GetLatestBlock")
}
latestBlockHeight = uint64(blockHeader.Height)

processQuery := func(ctx context.Context, query getBalanceQuery, queryIndex int) (*getBalancesByAddressResult, error) {
processQuery := func(ctx context.Context, query getBalanceQuery, queryIndex int) (*getBalancesResult, error) {
pkScript, ok := resolvePkScript(h.network, query.Wallet)
if !ok {
return nil, errs.NewPublicError(fmt.Sprintf("unable to resolve pkscript from \"queries[%d].wallet\"", queryIndex))
Expand All @@ -68,50 +88,57 @@ func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
blockHeight = latestBlockHeight
}

balances, err := h.usecase.GetBalancesByPkScript(ctx, pkScript, blockHeight)
if query.Limit == 0 {
query.Limit = getBalancesMaxLimit
}

balances, err := h.usecase.GetBalancesByPkScript(ctx, pkScript, blockHeight, query.Limit, query.Offset)
if err != nil {
if errors.Is(err, errs.NotFound) {
return nil, errs.NewPublicError("balances not found")
}
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
}

runeId, ok := h.resolveRuneId(ctx, query.Id)
if ok {
// filter out balances that don't match the requested rune id
for key := range balances {
if key != runeId {
delete(balances, key)
}
}
balances = lo.Filter(balances, func(b *entity.Balance, _ int) bool {
return b.RuneId == runeId
})
}

balanceRuneIds := lo.Keys(balances)
balanceRuneIds := lo.Map(balances, func(b *entity.Balance, _ int) runes.RuneId {
return b.RuneId
})
runeEntries, err := h.usecase.GetRuneEntryByRuneIdBatch(ctx, balanceRuneIds)
if err != nil {
if errors.Is(err, errs.NotFound) {
return nil, errs.NewPublicError("rune not found")
}
return nil, errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
}

balanceList := make([]balance, 0, len(balances))
for id, b := range balances {
runeEntry := runeEntries[id]
for _, b := range balances {
runeEntry := runeEntries[b.RuneId]
balanceList = append(balanceList, balance{
Amount: b.Amount,
Id: id,
Id: b.RuneId,
Name: runeEntry.SpacedRune,
Symbol: string(runeEntry.Symbol),
Decimals: runeEntry.Divisibility,
})
}
slices.SortFunc(balanceList, func(i, j balance) int {
return j.Amount.Cmp(i.Amount)
})

result := getBalancesByAddressResult{
result := getBalancesResult{
BlockHeight: blockHeight,
List: balanceList,
}
return &result, nil
}

results := make([]*getBalancesByAddressResult, len(req.Queries))
results := make([]*getBalancesResult, len(req.Queries))
eg, ectx := errgroup.WithContext(ctx.UserContext())
for i, query := range req.Queries {
i := i
Expand All @@ -129,8 +156,8 @@ func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
return errors.WithStack(err)
}

resp := getBalancesByAddressBatchResponse{
Result: &getBalancesByAddressBatchResult{
resp := getBalancesBatchResponse{
Result: &getBalancesBatchResult{
List: results,
},
}
Expand Down
Loading

0 comments on commit 1967895

Please sign in to comment.