Compare commits

...

15 Commits
v0.5.4 ... main

Author SHA1 Message Date
Gaze
58f8497997 feat: add code to errs.PublicError 2024-11-26 14:39:27 +07:00
Gaze
920f7fe07b chore: go mod tidy 2024-11-22 14:23:53 +07:00
Gaze
0cb66232ef feat: add bip322 pkg 2024-11-22 14:22:07 +07:00
gazenw
4074548b3e Merge pull request #74 from gaze-network/develop
Release 0.7.0
2024-10-31 14:18:59 +07:00
gazenw
c5c9a7bdeb feat: add get Runes info batch api (#73)
* fix: make existing handlers use new total holders usecase

* fix: error msg

* feat: add get token info batch

* feat: add includeHoldersCount in get tokens api

* refactor: extract response mapping

* fix: rename new field and add holdersCount to extend

* fix: query params array

* fix: error msg

* fix: struct tags

* fix: remove error

* feat: add default value to additional fields
2024-10-31 14:14:58 +07:00
gazenw
58334dd3e4 Merge pull request #72 from gaze-network/develop
feat: add etching tx hash for runes info api
2024-10-26 20:56:01 +07:00
Gaze
cffe378beb feat: add etching tx hash for runes info api 2024-10-25 16:22:24 +07:00
gazenw
9a7ee49228 Merge pull request #71 from gaze-network/develop
Release v0.6.0
2024-10-17 14:35:19 +07:00
gazenw
9739f61067 feat: implement batch insert using multirow inserts (#70)
* feat: add new batch inserts

* fix: migration

* fix: add casting to unnest with patch

* fix: add UTC() to timestamp mappers

* chore: unused imports

* chore: remove unnecessary comments
2024-10-17 14:34:04 +07:00
gazenw
f1267b387e Merge pull request #69 from gaze-network/develop
Release v0.5.6
2024-10-15 17:59:34 +07:00
gazenw
8883c24c77 fix: only call VerifyStates if not api only (#68) 2024-10-15 17:58:51 +07:00
gazenw
e9ce8df01a Merge pull request #67 from gaze-network/develop
Release v0.5.5
2024-10-11 14:25:05 +07:00
Gaze
3ff73a99f8 Merge branch 'main' into develop 2024-10-11 14:24:36 +07:00
gazenw
96afdfd255 fix: order get ongoing tokens by mint transactions (#66) 2024-10-11 14:23:31 +07:00
gazenw
c49e39be97 feat: optimize flush speed for Runes (#65)
* feat: switch to batchexec for faster inserts

* feat: add time taken log

* feat: add process time log

* feat: add event logs
2024-10-11 14:21:26 +07:00
27 changed files with 1756 additions and 489 deletions

View File

@@ -11,6 +11,7 @@ import (
type PublicError struct {
err error
message string
code string // code is optional, it can be used to identify the error type
}
func (p PublicError) Error() string {
@@ -21,6 +22,10 @@ func (p PublicError) Message() string {
return p.message
}
func (p PublicError) Code() string {
return p.code
}
func (p PublicError) Unwrap() error {
return p.err
}
@@ -29,6 +34,10 @@ func NewPublicError(message string) error {
return withstack.WithStackDepth(&PublicError{err: errors.New(message), message: message}, 1)
}
func NewPublicErrorWithCode(message string, code string) error {
return withstack.WithStackDepth(&PublicError{err: errors.New(message), message: message, code: code}, 1)
}
func WithPublicMessage(err error, prefix string) error {
if err == nil {
return nil
@@ -41,3 +50,16 @@ func WithPublicMessage(err error, prefix string) error {
}
return withstack.WithStackDepth(&PublicError{err: err, message: message}, 1)
}
func WithPublicMessageCode(err error, prefix string, code string) error {
if err == nil {
return nil
}
var message string
if prefix != "" {
message = fmt.Sprintf("%s: %s", prefix, err.Error())
} else {
message = err.Error()
}
return withstack.WithStackDepth(&PublicError{err: err, message: message, code: code}, 1)
}

2
go.sum
View File

@@ -99,6 +99,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -232,6 +233,7 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=

View File

@@ -3,6 +3,7 @@ package httphandler
import (
"bytes"
"encoding/hex"
"fmt"
"net/url"
"slices"
@@ -90,7 +91,7 @@ func (h *HttpHandler) GetHolders(ctx *fiber.Ctx) (err error) {
var ok bool
runeId, ok = h.resolveRuneId(ctx.UserContext(), req.Id)
if !ok {
return errs.NewPublicError("unable to resolve rune id from \"id\"")
return errs.NewPublicError(fmt.Sprintf("unable to resolve rune id \"%s\" from \"id\"", req.Id))
}
}

View File

@@ -1,12 +1,12 @@
package httphandler
import (
"fmt"
"net/url"
"slices"
"strings"
"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"
@@ -14,8 +14,10 @@ import (
)
type getTokenInfoRequest struct {
Id string `params:"id"`
BlockHeight uint64 `query:"blockHeight"`
Id string `params:"id"`
BlockHeight uint64 `query:"blockHeight"`
AdditionalFieldsRaw string `query:"additionalFields"` // comma-separated list of additional fields
AdditionalFields []string
}
func (r *getTokenInfoRequest) Validate() error {
@@ -28,6 +30,13 @@ func (r *getTokenInfoRequest) Validate() error {
if !isRuneIdOrRuneName(r.Id) {
errList = append(errList, errors.Errorf("id '%s' is not valid rune id or rune name", r.Id))
}
if r.AdditionalFieldsRaw == "" {
// temporarily set default value for backward compatibility
r.AdditionalFieldsRaw = "holdersCount" // TODO: remove this default value after all clients are updated
}
r.AdditionalFields = strings.Split(r.AdditionalFieldsRaw, ",")
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
}
@@ -41,17 +50,19 @@ type entryTerms struct {
}
type entry struct {
Divisibility uint8 `json:"divisibility"`
Premine uint128.Uint128 `json:"premine"`
Rune runes.Rune `json:"rune"`
Spacers uint32 `json:"spacers"`
Symbol string `json:"symbol"`
Terms entryTerms `json:"terms"`
Turbo bool `json:"turbo"`
Divisibility uint8 `json:"divisibility"`
Premine uint128.Uint128 `json:"premine"`
Rune runes.Rune `json:"rune"`
Spacers uint32 `json:"spacers"`
Symbol string `json:"symbol"`
Terms entryTerms `json:"terms"`
Turbo bool `json:"turbo"`
EtchingTxHash string `json:"etchingTxHash"`
}
type tokenInfoExtend struct {
Entry entry `json:"entry"`
HoldersCount *int64 `json:"holdersCount,omitempty"`
Entry entry `json:"entry"`
}
type getTokenInfoResult struct {
@@ -67,7 +78,7 @@ type getTokenInfoResult struct {
DeployedAtHeight uint64 `json:"deployedAtHeight"`
CompletedAt *int64 `json:"completedAt"` // unix timestamp
CompletedAtHeight *uint64 `json:"completedAtHeight"`
HoldersCount int `json:"holdersCount"`
HoldersCount int64 `json:"holdersCount"` // deprecated // TODO: remove later
Extend tokenInfoExtend `json:"extend"`
}
@@ -102,7 +113,7 @@ func (h *HttpHandler) GetTokenInfo(ctx *fiber.Ctx) (err error) {
var ok bool
runeId, ok = h.resolveRuneId(ctx.UserContext(), req.Id)
if !ok {
return errs.NewPublicError("unable to resolve rune id from \"id\"")
return errs.NewPublicError(fmt.Sprintf("unable to resolve rune id \"%s\" from \"id\"", req.Id))
}
}
@@ -111,70 +122,78 @@ func (h *HttpHandler) GetTokenInfo(ctx *fiber.Ctx) (err error) {
if errors.Is(err, errs.NotFound) {
return errs.NewPublicError("rune not found")
}
return errors.Wrap(err, "error during GetTokenInfoByHeight")
return errors.Wrap(err, "error during GetRuneEntryByRuneIdAndHeight")
}
holdingBalances, err := h.usecase.GetBalancesByRuneId(ctx.UserContext(), runeId, blockHeight, -1, 0) // get all balances
if err != nil {
if errors.Is(err, errs.NotFound) {
return errs.NewPublicError("rune not found")
var holdersCountPtr *int64
if lo.Contains(req.AdditionalFields, "holdersCount") {
holdersCount, err := h.usecase.GetTotalHoldersByRuneId(ctx.UserContext(), runeId, blockHeight)
if err != nil {
if errors.Is(err, errs.NotFound) {
return errs.NewPublicError("rune not found")
}
return errors.Wrap(err, "error during GetBalancesByRuneId")
}
return errors.Wrap(err, "error during GetBalancesByRuneId")
holdersCountPtr = &holdersCount
}
holdingBalances = lo.Filter(holdingBalances, func(b *entity.Balance, _ int) bool {
return !b.Amount.IsZero()
})
// sort by amount descending
slices.SortFunc(holdingBalances, func(i, j *entity.Balance) int {
return j.Amount.Cmp(i.Amount)
})
totalSupply, err := runeEntry.Supply()
result, err := createTokenInfoResult(runeEntry, holdersCountPtr)
if err != nil {
return errors.Wrap(err, "cannot get total supply of rune")
return errors.Wrap(err, "error during createTokenInfoResult")
}
mintedAmount, err := runeEntry.MintedAmount()
if err != nil {
return errors.Wrap(err, "cannot get minted amount of rune")
}
circulatingSupply := mintedAmount.Sub(runeEntry.BurnedAmount)
terms := lo.FromPtr(runeEntry.Terms)
resp := getTokenInfoResponse{
Result: &getTokenInfoResult{
Id: runeId,
Name: runeEntry.SpacedRune,
Symbol: string(runeEntry.Symbol),
TotalSupply: totalSupply,
CirculatingSupply: circulatingSupply,
MintedAmount: mintedAmount,
BurnedAmount: runeEntry.BurnedAmount,
Decimals: runeEntry.Divisibility,
DeployedAt: runeEntry.EtchedAt.Unix(),
DeployedAtHeight: runeEntry.EtchingBlock,
CompletedAt: lo.Ternary(runeEntry.CompletedAt.IsZero(), nil, lo.ToPtr(runeEntry.CompletedAt.Unix())),
CompletedAtHeight: runeEntry.CompletedAtHeight,
HoldersCount: len(holdingBalances),
Extend: tokenInfoExtend{
Entry: entry{
Divisibility: runeEntry.Divisibility,
Premine: runeEntry.Premine,
Rune: runeEntry.SpacedRune.Rune,
Spacers: runeEntry.SpacedRune.Spacers,
Symbol: string(runeEntry.Symbol),
Terms: entryTerms{
Amount: lo.FromPtr(terms.Amount),
Cap: lo.FromPtr(terms.Cap),
HeightStart: terms.HeightStart,
HeightEnd: terms.HeightEnd,
OffsetStart: terms.OffsetStart,
OffsetEnd: terms.OffsetEnd,
},
Turbo: runeEntry.Turbo,
},
},
},
Result: result,
}
return errors.WithStack(ctx.JSON(resp))
}
func createTokenInfoResult(runeEntry *runes.RuneEntry, holdersCount *int64) (*getTokenInfoResult, error) {
totalSupply, err := runeEntry.Supply()
if err != nil {
return nil, errors.Wrap(err, "cannot get total supply of rune")
}
mintedAmount, err := runeEntry.MintedAmount()
if err != nil {
return nil, errors.Wrap(err, "cannot get minted amount of rune")
}
circulatingSupply := mintedAmount.Sub(runeEntry.BurnedAmount)
terms := lo.FromPtr(runeEntry.Terms)
return &getTokenInfoResult{
Id: runeEntry.RuneId,
Name: runeEntry.SpacedRune,
Symbol: string(runeEntry.Symbol),
TotalSupply: totalSupply,
CirculatingSupply: circulatingSupply,
MintedAmount: mintedAmount,
BurnedAmount: runeEntry.BurnedAmount,
Decimals: runeEntry.Divisibility,
DeployedAt: runeEntry.EtchedAt.Unix(),
DeployedAtHeight: runeEntry.EtchingBlock,
CompletedAt: lo.Ternary(runeEntry.CompletedAt.IsZero(), nil, lo.ToPtr(runeEntry.CompletedAt.Unix())),
CompletedAtHeight: runeEntry.CompletedAtHeight,
HoldersCount: lo.FromPtr(holdersCount),
Extend: tokenInfoExtend{
HoldersCount: holdersCount,
Entry: entry{
Divisibility: runeEntry.Divisibility,
Premine: runeEntry.Premine,
Rune: runeEntry.SpacedRune.Rune,
Spacers: runeEntry.SpacedRune.Spacers,
Symbol: string(runeEntry.Symbol),
Terms: entryTerms{
Amount: lo.FromPtr(terms.Amount),
Cap: lo.FromPtr(terms.Cap),
HeightStart: terms.HeightStart,
HeightEnd: terms.HeightEnd,
OffsetStart: terms.OffsetStart,
OffsetEnd: terms.OffsetEnd,
},
Turbo: runeEntry.Turbo,
EtchingTxHash: runeEntry.EtchingTxHash.String(),
},
},
}, nil
}

View File

@@ -0,0 +1,118 @@
package httphandler
import (
"fmt"
"net/url"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common/errs"
"github.com/gaze-network/indexer-network/modules/runes/runes"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
type getTokenInfoBatchRequest struct {
Ids []string `json:"ids"`
BlockHeight uint64 `json:"blockHeight"`
AdditionalFields []string `json:"additionalFields"`
}
const getTokenInfoBatchMaxQueries = 100
func (r *getTokenInfoBatchRequest) Validate() error {
var errList []error
if len(r.Ids) == 0 {
errList = append(errList, errors.New("ids cannot be empty"))
}
if len(r.Ids) > getTokenInfoBatchMaxQueries {
errList = append(errList, errors.Errorf("cannot query more than %d ids", getTokenInfoBatchMaxQueries))
}
for i := range r.Ids {
id, err := url.QueryUnescape(r.Ids[i])
if err != nil {
return errors.WithStack(err)
}
r.Ids[i] = id
if !isRuneIdOrRuneName(r.Ids[i]) {
errList = append(errList, errors.Errorf("ids[%d]: id '%s' is not valid rune id or rune name", i, r.Ids[i]))
}
}
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
}
type getTokenInfoBatchResult struct {
List []*getTokenInfoResult `json:"list"`
}
type getTokenInfoBatchResponse = HttpResponse[getTokenInfoBatchResult]
func (h *HttpHandler) GetTokenInfoBatch(ctx *fiber.Ctx) (err error) {
var req getTokenInfoBatchRequest
if err := ctx.BodyParser(&req); err != nil {
return errors.WithStack(err)
}
if err := req.Validate(); err != nil {
return errors.WithStack(err)
}
blockHeight := req.BlockHeight
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)
}
runeIds := make([]runes.RuneId, 0)
for i, id := range req.Ids {
runeId, ok := h.resolveRuneId(ctx.UserContext(), id)
if !ok {
return errs.NewPublicError(fmt.Sprintf("unable to resolve rune id \"%s\" from \"ids[%d]\"", id, i))
}
runeIds = append(runeIds, runeId)
}
runeEntries, err := h.usecase.GetRuneEntryByRuneIdAndHeightBatch(ctx.UserContext(), runeIds, blockHeight)
if err != nil {
return errors.Wrap(err, "error during GetRuneEntryByRuneIdAndHeightBatch")
}
holdersCounts := make(map[runes.RuneId]int64)
if lo.Contains(req.AdditionalFields, "holdersCount") {
holdersCounts, err = h.usecase.GetTotalHoldersByRuneIds(ctx.UserContext(), runeIds, blockHeight)
if err != nil {
return errors.Wrap(err, "error during GetBalancesByRuneId")
}
}
results := make([]*getTokenInfoResult, 0, len(runeIds))
for _, runeId := range runeIds {
runeEntry, ok := runeEntries[runeId]
if !ok {
return errs.NewPublicError(fmt.Sprintf("rune not found: %s", runeId))
}
var holdersCount *int64
if lo.Contains(req.AdditionalFields, "holdersCount") {
holdersCount = lo.ToPtr(holdersCounts[runeId])
}
result, err := createTokenInfoResult(runeEntry, holdersCount)
if err != nil {
return errors.Wrap(err, "error during createTokenInfoResult")
}
results = append(results, result)
}
resp := getTokenInfoBatchResponse{
Result: &getTokenInfoBatchResult{
List: results,
},
}
return errors.WithStack(ctx.JSON(resp))
}

View File

@@ -32,22 +32,31 @@ func (s GetTokensScope) IsValid() bool {
type getTokensRequest struct {
paginationRequest
Search string `query:"search"`
BlockHeight uint64 `query:"blockHeight"`
Scope GetTokensScope `query:"scope"`
Search string `query:"search"`
BlockHeight uint64 `query:"blockHeight"`
Scope GetTokensScope `query:"scope"`
AdditionalFieldsRaw string `query:"additionalFields"` // comma-separated list of additional fields
AdditionalFields []string
}
func (req getTokensRequest) Validate() error {
func (r *getTokensRequest) Validate() error {
var errList []error
if err := req.paginationRequest.Validate(); err != nil {
if err := r.paginationRequest.Validate(); err != nil {
errList = append(errList, err)
}
if req.Limit > getTokensMaxLimit {
if r.Limit > getTokensMaxLimit {
errList = append(errList, errors.Errorf("limit must be less than or equal to 1000"))
}
if req.Scope != "" && !req.Scope.IsValid() {
errList = append(errList, errors.Errorf("invalid scope: %s", req.Scope))
if r.Scope != "" && !r.Scope.IsValid() {
errList = append(errList, errors.Errorf("invalid scope: %s", r.Scope))
}
if r.AdditionalFieldsRaw == "" {
// temporarily set default value for backward compatibility
r.AdditionalFieldsRaw = "holdersCount" // TODO: remove this default value after all clients are updated
}
r.AdditionalFields = strings.Split(r.AdditionalFieldsRaw, ",")
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
}
@@ -62,7 +71,7 @@ func (req *getTokensRequest) ParseDefault() error {
}
type getTokensResult struct {
List []getTokenInfoResult `json:"list"`
List []*getTokenInfoResult `json:"list"`
}
type getTokensResponse = HttpResponse[getTokensResult]
@@ -111,62 +120,31 @@ func (h *HttpHandler) GetTokens(ctx *fiber.Ctx) (err error) {
}
runeIds := lo.Map(entries, func(item *runes.RuneEntry, _ int) runes.RuneId { return item.RuneId })
totalHolders, err := h.usecase.GetTotalHoldersByRuneIds(ctx.UserContext(), runeIds, blockHeight)
if err != nil {
return errors.Wrap(err, "error during GetTotalHoldersByRuneIds")
holdersCounts := make(map[runes.RuneId]int64)
if lo.Contains(req.AdditionalFields, "holdersCount") {
holdersCounts, err = h.usecase.GetTotalHoldersByRuneIds(ctx.UserContext(), runeIds, blockHeight)
if err != nil {
return errors.Wrap(err, "error during GetTotalHoldersByRuneIds")
}
}
result := make([]getTokenInfoResult, 0, len(entries))
results := make([]*getTokenInfoResult, 0, len(entries))
for _, ent := range entries {
totalSupply, err := ent.Supply()
if err != nil {
return errors.Wrap(err, "cannot get total supply of rune")
var holdersCount *int64
if lo.Contains(req.AdditionalFields, "holdersCount") {
holdersCount = lo.ToPtr(holdersCounts[ent.RuneId])
}
mintedAmount, err := ent.MintedAmount()
result, err := createTokenInfoResult(ent, holdersCount)
if err != nil {
return errors.Wrap(err, "cannot get minted amount of rune")
return errors.Wrap(err, "error during createTokenInfoResult")
}
circulatingSupply := mintedAmount.Sub(ent.BurnedAmount)
terms := lo.FromPtr(ent.Terms)
result = append(result, getTokenInfoResult{
Id: ent.RuneId,
Name: ent.SpacedRune,
Symbol: string(ent.Symbol),
TotalSupply: totalSupply,
CirculatingSupply: circulatingSupply,
MintedAmount: mintedAmount,
BurnedAmount: ent.BurnedAmount,
Decimals: ent.Divisibility,
DeployedAt: ent.EtchedAt.Unix(),
DeployedAtHeight: ent.EtchingBlock,
CompletedAt: lo.Ternary(ent.CompletedAt.IsZero(), nil, lo.ToPtr(ent.CompletedAt.Unix())),
CompletedAtHeight: ent.CompletedAtHeight,
HoldersCount: int(totalHolders[ent.RuneId]),
Extend: tokenInfoExtend{
Entry: entry{
Divisibility: ent.Divisibility,
Premine: ent.Premine,
Rune: ent.SpacedRune.Rune,
Spacers: ent.SpacedRune.Spacers,
Symbol: string(ent.Symbol),
Terms: entryTerms{
Amount: lo.FromPtr(terms.Amount),
Cap: lo.FromPtr(terms.Cap),
HeightStart: terms.HeightStart,
HeightEnd: terms.HeightEnd,
OffsetStart: terms.OffsetStart,
OffsetEnd: terms.OffsetEnd,
},
Turbo: ent.Turbo,
},
},
})
results = append(results, result)
}
return errors.WithStack(ctx.JSON(getTokensResponse{
Result: &getTokensResult{
List: result,
List: results,
},
}))
}

View File

@@ -152,7 +152,7 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
var ok bool
runeId, ok = h.resolveRuneId(ctx.UserContext(), req.Id)
if !ok {
return errs.NewPublicError("unable to resolve rune id from \"id\"")
return errs.NewPublicError(fmt.Sprintf("unable to resolve rune id \"%s\" from \"id\"", req.Id))
}
}

View File

@@ -12,6 +12,7 @@ func (h *HttpHandler) Mount(router fiber.Router) error {
r.Get("/transactions", h.GetTransactions)
r.Get("/transactions/hash/:hash", h.GetTransactionByHash)
r.Get("/holders/:id", h.GetHolders)
r.Post("/info/batch", h.GetTokenInfoBatch)
r.Get("/info/:id", h.GetTokenInfo)
r.Get("/utxos/wallet/:wallet", h.GetUTXOs)
r.Post("/utxos/output/batch", h.GetUTXOsOutputByLocationBatch)

View File

@@ -1,6 +1,6 @@
BEGIN;
CREATE EXTENSION pg_trgm;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- Indexer Client Information
CREATE TABLE IF NOT EXISTS "runes_indexer_stats" (

View File

@@ -0,0 +1,104 @@
-- name: BatchCreateRunesBalances :exec
INSERT INTO runes_balances ("pkscript", "block_height", "rune_id", "amount")
VALUES(
unnest(@pkscript_arr::TEXT[]),
unnest(@block_height_arr::INT[]),
unnest(@rune_id_arr::TEXT[]),
unnest(@amount_arr::DECIMAL[])
);
-- name: BatchCreateRuneEntries :exec
INSERT INTO runes_entries ("rune_id", "rune", "number", "spacers", "premine", "symbol", "divisibility", "terms", "terms_amount", "terms_cap", "terms_height_start", "terms_height_end", "terms_offset_start", "terms_offset_end", "turbo", "etching_block", "etching_tx_hash", "etched_at")
VALUES(
unnest(@rune_id_arr::TEXT[]),
unnest(@rune_arr::TEXT[]),
unnest(@number_arr::BIGINT[]),
unnest(@spacers_arr::INT[]),
unnest(@premine_arr::DECIMAL[]),
unnest(@symbol_arr::INT[]),
unnest(@divisibility_arr::SMALLINT[]),
unnest(@terms_arr::BOOLEAN[]),
unnest(@terms_amount_arr::DECIMAL[]),
unnest(@terms_cap_arr::DECIMAL[]),
unnest(@terms_height_start_arr::INT[]), -- nullable (need patch)
unnest(@terms_height_end_arr::INT[]), -- nullable (need patch)
unnest(@terms_offset_start_arr::INT[]), -- nullable (need patch)
unnest(@terms_offset_end_arr::INT[]), -- nullable (need patch)
unnest(@turbo_arr::BOOLEAN[]),
unnest(@etching_block_arr::INT[]),
unnest(@etching_tx_hash_arr::TEXT[]),
unnest(@etched_at_arr::TIMESTAMP[])
);
-- name: BatchCreateRuneEntryStates :exec
INSERT INTO runes_entry_states ("rune_id", "block_height", "mints", "burned_amount", "completed_at", "completed_at_height")
VALUES(
unnest(@rune_id_arr::TEXT[]),
unnest(@block_height_arr::INT[]),
unnest(@mints_arr::DECIMAL[]),
unnest(@burned_amount_arr::DECIMAL[]),
unnest(@completed_at_arr::TIMESTAMP[]),
unnest(@completed_at_height_arr::INT[]) -- nullable (need patch)
);
-- name: BatchCreateRunesOutpointBalances :exec
INSERT INTO runes_outpoint_balances ("rune_id", "pkscript", "tx_hash", "tx_idx", "amount", "block_height", "spent_height")
VALUES(
unnest(@rune_id_arr::TEXT[]),
unnest(@pkscript_arr::TEXT[]),
unnest(@tx_hash_arr::TEXT[]),
unnest(@tx_idx_arr::INT[]),
unnest(@amount_arr::DECIMAL[]),
unnest(@block_height_arr::INT[]),
unnest(@spent_height_arr::INT[]) -- nullable (need patch)
);
-- name: BatchSpendOutpointBalances :exec
UPDATE runes_outpoint_balances
SET "spent_height" = @spent_height::INT
FROM (
SELECT
unnest(@tx_hash_arr::TEXT[]) AS tx_hash,
unnest(@tx_idx_arr::INT[]) AS tx_idx
) AS input
WHERE "runes_outpoint_balances"."tx_hash" = "input"."tx_hash" AND "runes_outpoint_balances"."tx_idx" = "input"."tx_idx";
-- name: BatchCreateRunestones :exec
INSERT INTO runes_runestones ("tx_hash", "block_height", "etching", "etching_divisibility", "etching_premine", "etching_rune", "etching_spacers", "etching_symbol", "etching_terms", "etching_terms_amount", "etching_terms_cap", "etching_terms_height_start", "etching_terms_height_end", "etching_terms_offset_start", "etching_terms_offset_end", "etching_turbo", "edicts", "mint", "pointer", "cenotaph", "flaws")
VALUES(
unnest(@tx_hash_arr::TEXT[]),
unnest(@block_height_arr::INT[]),
unnest(@etching_arr::BOOLEAN[]),
unnest(@etching_divisibility_arr::SMALLINT[]), -- nullable (need patch)
unnest(@etching_premine_arr::DECIMAL[]),
unnest(@etching_rune_arr::TEXT[]), -- nullable (need patch)
unnest(@etching_spacers_arr::INT[]), -- nullable (need patch)
unnest(@etching_symbol_arr::INT[]), -- nullable (need patch)
unnest(@etching_terms_arr::BOOLEAN[]), -- nullable (need patch)
unnest(@etching_terms_amount_arr::DECIMAL[]),
unnest(@etching_terms_cap_arr::DECIMAL[]),
unnest(@etching_terms_height_start_arr::INT[]), -- nullable (need patch)
unnest(@etching_terms_height_end_arr::INT[]), -- nullable (need patch)
unnest(@etching_terms_offset_start_arr::INT[]), -- nullable (need patch)
unnest(@etching_terms_offset_end_arr::INT[]), -- nullable (need patch)
unnest(@etching_turbo_arr::BOOLEAN[]), -- nullable (need patch)
unnest(@edicts_arr::JSONB[]),
unnest(@mint_arr::TEXT[]), -- nullable (need patch)
unnest(@pointer_arr::INT[]), -- nullable (need patch)
unnest(@cenotaph_arr::BOOLEAN[]),
unnest(@flaws_arr::INT[])
);
-- name: BatchCreateRuneTransactions :exec
INSERT INTO runes_transactions ("hash", "block_height", "index", "timestamp", "inputs", "outputs", "mints", "burns", "rune_etched")
VALUES (
unnest(@hash_arr::TEXT[]),
unnest(@block_height_arr::INT[]),
unnest(@index_arr::INT[]),
unnest(@timestamp_arr::TIMESTAMP[]),
unnest(@inputs_arr::JSONB[]),
unnest(@outputs_arr::JSONB[]),
unnest(@mints_arr::JSONB[]),
unnest(@burns_arr::JSONB[]),
unnest(@rune_etched_arr::BOOLEAN[])
);

View File

@@ -103,8 +103,7 @@ SELECT * FROM runes_entries
@search::text = '' OR
runes_entries.rune ILIKE '%' || @search::text || '%'
)
ORDER BY (COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(states.mints, 0)) /
(COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(runes_entries.terms_cap, 0))::float DESC
ORDER BY states.mints DESC
LIMIT @_limit OFFSET @_offset;
-- name: GetRuneIdFromRune :one
@@ -151,13 +150,13 @@ INSERT INTO runes_transactions (hash, block_height, index, timestamp, inputs, ou
INSERT INTO runes_runestones (tx_hash, block_height, etching, etching_divisibility, etching_premine, etching_rune, etching_spacers, etching_symbol, etching_terms, etching_terms_amount, etching_terms_cap, etching_terms_height_start, etching_terms_height_end, etching_terms_offset_start, etching_terms_offset_end, etching_turbo, edicts, mint, pointer, cenotaph, flaws)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21);
-- name: CreateOutPointBalances :batchexec
-- name: CreateOutPointBalance :exec
INSERT INTO runes_outpoint_balances (rune_id, pkscript, tx_hash, tx_idx, amount, block_height, spent_height) VALUES ($1, $2, $3, $4, $5, $6, $7);
-- name: SpendOutPointBalances :exec
-- name: SpendOutPointBalance :exec
UPDATE runes_outpoint_balances SET spent_height = $1 WHERE tx_hash = $2 AND tx_idx = $3;
-- name: CreateRuneBalanceAtBlock :batchexec
-- name: CreateRuneBalance :exec
INSERT INTO runes_balances (pkscript, block_height, rune_id, amount) VALUES ($1, $2, $3, $4);
-- name: GetLatestIndexedBlock :one

View File

@@ -8,7 +8,6 @@ import (
"github.com/gaze-network/indexer-network/core/types"
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
"github.com/gaze-network/indexer-network/modules/runes/runes"
"github.com/gaze-network/uint128"
)
type RunesDataGateway interface {
@@ -65,12 +64,12 @@ type RunesReaderDataGateway interface {
}
type RunesWriterDataGateway interface {
CreateRuneEntry(ctx context.Context, entry *runes.RuneEntry, blockHeight uint64) error
CreateRuneEntryState(ctx context.Context, entry *runes.RuneEntry, blockHeight uint64) error
CreateRuneEntries(ctx context.Context, entries []*runes.RuneEntry) error
CreateRuneEntryStates(ctx context.Context, entries []*runes.RuneEntry, blockHeight uint64) error
CreateOutPointBalances(ctx context.Context, outPointBalances []*entity.OutPointBalance) error
SpendOutPointBalances(ctx context.Context, outPoint wire.OutPoint, blockHeight uint64) error
CreateRuneBalances(ctx context.Context, params []CreateRuneBalancesParams) error
CreateRuneTransaction(ctx context.Context, tx *entity.RuneTransaction) error
SpendOutPointBalancesBatch(ctx context.Context, outPoints []wire.OutPoint, blockHeight uint64) error
CreateRuneBalances(ctx context.Context, params []*entity.Balance) error
CreateRuneTransactions(ctx context.Context, txs []*entity.RuneTransaction) error
CreateIndexedBlock(ctx context.Context, block *entity.IndexedBlock) error
// TODO: collapse these into a single function (ResetStateToHeight)?
@@ -83,10 +82,3 @@ type RunesWriterDataGateway interface {
UnspendOutPointBalancesSinceHeight(ctx context.Context, height uint64) error
DeleteRuneBalancesSinceHeight(ctx context.Context, height uint64) error
}
type CreateRuneBalancesParams struct {
PkScript []byte
RuneId runes.RuneId
Balance uint128.Uint128
BlockHeight uint64
}

View File

@@ -145,7 +145,10 @@ func (p *Processor) ensureGenesisRune(ctx context.Context, network common.Networ
EtchingTxHash: genesisRuneConfig.EtchingTxHash,
EtchedAt: genesisRuneConfig.EtchedAt,
}
if err := p.runesDg.CreateRuneEntry(ctx, runeEntry, genesisRuneConfig.RuneId.BlockHeight); err != nil {
if err := p.runesDg.CreateRuneEntries(ctx, []*runes.RuneEntry{runeEntry}); err != nil {
return errors.Wrap(err, "failed to create genesis rune entry")
}
if err := p.runesDg.CreateRuneEntryStates(ctx, []*runes.RuneEntry{runeEntry}, genesisRuneConfig.RuneId.BlockHeight); err != nil {
return errors.Wrap(err, "failed to create genesis rune entry")
}
}

View File

@@ -14,7 +14,6 @@ import (
"github.com/gaze-network/indexer-network/common/errs"
"github.com/gaze-network/indexer-network/core/types"
"github.com/gaze-network/indexer-network/modules/runes/constants"
"github.com/gaze-network/indexer-network/modules/runes/datagateway"
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
"github.com/gaze-network/indexer-network/modules/runes/runes"
"github.com/gaze-network/indexer-network/pkg/logger"
@@ -27,19 +26,26 @@ import (
func (p *Processor) Process(ctx context.Context, blocks []*types.Block) error {
for _, block := range blocks {
ctx := logger.WithContext(ctx, slog.Int64("height", block.Header.Height))
logger.DebugContext(ctx, "Processing new block", slog.Int("txs", len(block.Transactions)))
logger.InfoContext(ctx, "Processing new block",
slogx.String("event", "runes_processor_processing_block"),
slog.Int("txs", len(block.Transactions)),
)
start := time.Now()
for _, tx := range block.Transactions {
if err := p.processTx(ctx, tx, block.Header); err != nil {
return errors.Wrap(err, "failed to process tx")
}
}
timeTakenToProcess := time.Since(start)
logger.InfoContext(ctx, "Processed block",
slogx.String("event", "runes_processor_processed_block"),
slog.Duration("time_taken", timeTakenToProcess),
)
if err := p.flushBlock(ctx, block.Header); err != nil {
return errors.Wrap(err, "failed to flush block")
}
logger.DebugContext(ctx, "Inserted new block")
}
return nil
}
@@ -669,6 +675,7 @@ func (p *Processor) getRunesBalancesAtOutPoint(ctx context.Context, outPoint wir
}
func (p *Processor) flushBlock(ctx context.Context, blockHeader types.BlockHeader) error {
start := time.Now()
runesDgTx, err := p.runesDg.BeginRunesTx(ctx)
if err != nil {
return errors.Wrap(err, "failed to begin runes tx")
@@ -715,78 +722,82 @@ func (p *Processor) flushBlock(ctx context.Context, blockHeader types.BlockHeade
return errors.Wrap(err, "failed to create indexed block")
}
// flush new rune entries
{
for _, runeEntry := range p.newRuneEntries {
if err := runesDgTx.CreateRuneEntry(ctx, runeEntry, uint64(blockHeader.Height)); err != nil {
return errors.Wrap(err, "failed to create rune entry")
}
}
p.newRuneEntries = make(map[runes.RuneId]*runes.RuneEntry)
newRuneEntries := lo.Values(p.newRuneEntries)
if err := runesDgTx.CreateRuneEntries(ctx, newRuneEntries); err != nil {
return errors.Wrap(err, "failed to create rune entry")
}
p.newRuneEntries = make(map[runes.RuneId]*runes.RuneEntry)
// flush new rune entry states
{
for _, runeEntry := range p.newRuneEntryStates {
if err := runesDgTx.CreateRuneEntryState(ctx, runeEntry, uint64(blockHeader.Height)); err != nil {
return errors.Wrap(err, "failed to create rune entry state")
}
}
p.newRuneEntryStates = make(map[runes.RuneId]*runes.RuneEntry)
newRuneEntryStates := lo.Values(p.newRuneEntryStates)
if err := runesDgTx.CreateRuneEntryStates(ctx, newRuneEntryStates, uint64(blockHeader.Height)); err != nil {
return errors.Wrap(err, "failed to create rune entry state")
}
p.newRuneEntryStates = make(map[runes.RuneId]*runes.RuneEntry)
// flush new outpoint balances
{
newBalances := make([]*entity.OutPointBalance, 0)
for _, balances := range p.newOutPointBalances {
newBalances = append(newBalances, balances...)
}
if err := runesDgTx.CreateOutPointBalances(ctx, newBalances); err != nil {
return errors.Wrap(err, "failed to create outpoint balances")
}
p.newOutPointBalances = make(map[wire.OutPoint][]*entity.OutPointBalance)
newOutpointBalances := make([]*entity.OutPointBalance, 0)
for _, balances := range p.newOutPointBalances {
newOutpointBalances = append(newOutpointBalances, balances...)
}
if err := runesDgTx.CreateOutPointBalances(ctx, newOutpointBalances); err != nil {
return errors.Wrap(err, "failed to create outpoint balances")
}
p.newOutPointBalances = make(map[wire.OutPoint][]*entity.OutPointBalance)
// flush new spend outpoints
{
for _, outPoint := range p.newSpendOutPoints {
if err := runesDgTx.SpendOutPointBalances(ctx, outPoint, uint64(blockHeader.Height)); err != nil {
return errors.Wrap(err, "failed to create spend outpoint")
}
}
p.newSpendOutPoints = make([]wire.OutPoint, 0)
newSpendOutPoints := p.newSpendOutPoints
if err := runesDgTx.SpendOutPointBalancesBatch(ctx, newSpendOutPoints, uint64(blockHeader.Height)); err != nil {
return errors.Wrap(err, "failed to create spend outpoint")
}
// flush new balances
{
params := make([]datagateway.CreateRuneBalancesParams, 0)
for pkScriptStr, balances := range p.newBalances {
pkScript, err := hex.DecodeString(pkScriptStr)
if err != nil {
return errors.Wrap(err, "failed to decode pk script")
}
for runeId, balance := range balances {
params = append(params, datagateway.CreateRuneBalancesParams{
PkScript: pkScript,
RuneId: runeId,
Balance: balance,
BlockHeight: uint64(blockHeader.Height),
})
}
p.newSpendOutPoints = make([]wire.OutPoint, 0)
// flush new newBalances
newBalances := make([]*entity.Balance, 0)
for pkScriptStr, balances := range p.newBalances {
pkScript, err := hex.DecodeString(pkScriptStr)
if err != nil {
return errors.Wrap(err, "failed to decode pk script")
}
if err := runesDgTx.CreateRuneBalances(ctx, params); err != nil {
return errors.Wrap(err, "failed to create balances at block")
for runeId, balance := range balances {
newBalances = append(newBalances, &entity.Balance{
PkScript: pkScript,
RuneId: runeId,
Amount: balance,
BlockHeight: uint64(blockHeader.Height),
})
}
p.newBalances = make(map[string]map[runes.RuneId]uint128.Uint128)
}
if err := runesDgTx.CreateRuneBalances(ctx, newBalances); err != nil {
return errors.Wrap(err, "failed to create balances at block")
}
p.newBalances = make(map[string]map[runes.RuneId]uint128.Uint128)
// flush new rune transactions
{
for _, runeTx := range p.newRuneTxs {
if err := runesDgTx.CreateRuneTransaction(ctx, runeTx); err != nil {
return errors.Wrap(err, "failed to create rune transaction")
}
}
p.newRuneTxs = make([]*entity.RuneTransaction, 0)
newRuneTxs := p.newRuneTxs
if err := runesDgTx.CreateRuneTransactions(ctx, newRuneTxs); err != nil {
return errors.Wrap(err, "failed to create rune transaction")
}
p.newRuneTxs = make([]*entity.RuneTransaction, 0)
if err := runesDgTx.Commit(ctx); err != nil {
return errors.Wrap(err, "failed to commit runes tx")
}
timeTaken := time.Since(start)
logger.InfoContext(ctx, "Flushed block",
slogx.String("event", "runes_processor_flushed_block"),
slog.Int64("height", blockHeader.Height),
slog.String("hash", blockHeader.Hash.String()),
slog.String("event_hash", hex.EncodeToString(eventHash[:])),
slog.String("cumulative_event_hash", hex.EncodeToString(cumulativeEventHash[:])),
slog.Int("new_rune_entries", len(newRuneEntries)),
slog.Int("new_rune_entry_states", len(newRuneEntryStates)),
slog.Int("new_outpoint_balances", len(newOutpointBalances)),
slog.Int("new_spend_outpoints", len(newSpendOutPoints)),
slog.Int("new_balances", len(newBalances)),
slog.Int("new_rune_txs", len(newRuneTxs)),
slogx.Duration("time_taken", timeTaken),
)
// submit event to reporting system
if p.reportingClient != nil {

View File

@@ -1,130 +0,0 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: batch.go
package gen
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)
var (
ErrBatchAlreadyClosed = errors.New("batch already closed")
)
const createOutPointBalances = `-- name: CreateOutPointBalances :batchexec
INSERT INTO runes_outpoint_balances (rune_id, pkscript, tx_hash, tx_idx, amount, block_height, spent_height) VALUES ($1, $2, $3, $4, $5, $6, $7)
`
type CreateOutPointBalancesBatchResults struct {
br pgx.BatchResults
tot int
closed bool
}
type CreateOutPointBalancesParams struct {
RuneID string
Pkscript string
TxHash string
TxIdx int32
Amount pgtype.Numeric
BlockHeight int32
SpentHeight pgtype.Int4
}
func (q *Queries) CreateOutPointBalances(ctx context.Context, arg []CreateOutPointBalancesParams) *CreateOutPointBalancesBatchResults {
batch := &pgx.Batch{}
for _, a := range arg {
vals := []interface{}{
a.RuneID,
a.Pkscript,
a.TxHash,
a.TxIdx,
a.Amount,
a.BlockHeight,
a.SpentHeight,
}
batch.Queue(createOutPointBalances, vals...)
}
br := q.db.SendBatch(ctx, batch)
return &CreateOutPointBalancesBatchResults{br, len(arg), false}
}
func (b *CreateOutPointBalancesBatchResults) Exec(f func(int, error)) {
defer b.br.Close()
for t := 0; t < b.tot; t++ {
if b.closed {
if f != nil {
f(t, ErrBatchAlreadyClosed)
}
continue
}
_, err := b.br.Exec()
if f != nil {
f(t, err)
}
}
}
func (b *CreateOutPointBalancesBatchResults) Close() error {
b.closed = true
return b.br.Close()
}
const createRuneBalanceAtBlock = `-- name: CreateRuneBalanceAtBlock :batchexec
INSERT INTO runes_balances (pkscript, block_height, rune_id, amount) VALUES ($1, $2, $3, $4)
`
type CreateRuneBalanceAtBlockBatchResults struct {
br pgx.BatchResults
tot int
closed bool
}
type CreateRuneBalanceAtBlockParams struct {
Pkscript string
BlockHeight int32
RuneID string
Amount pgtype.Numeric
}
func (q *Queries) CreateRuneBalanceAtBlock(ctx context.Context, arg []CreateRuneBalanceAtBlockParams) *CreateRuneBalanceAtBlockBatchResults {
batch := &pgx.Batch{}
for _, a := range arg {
vals := []interface{}{
a.Pkscript,
a.BlockHeight,
a.RuneID,
a.Amount,
}
batch.Queue(createRuneBalanceAtBlock, vals...)
}
br := q.db.SendBatch(ctx, batch)
return &CreateRuneBalanceAtBlockBatchResults{br, len(arg), false}
}
func (b *CreateRuneBalanceAtBlockBatchResults) Exec(f func(int, error)) {
defer b.br.Close()
for t := 0; t < b.tot; t++ {
if b.closed {
if f != nil {
f(t, ErrBatchAlreadyClosed)
}
continue
}
_, err := b.br.Exec()
if f != nil {
f(t, err)
}
}
}
func (b *CreateRuneBalanceAtBlockBatchResults) Close() error {
b.closed = true
return b.br.Close()
}

View File

@@ -0,0 +1,319 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: batch.sql
package gen
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const batchCreateRuneEntries = `-- name: BatchCreateRuneEntries :exec
INSERT INTO runes_entries ("rune_id", "rune", "number", "spacers", "premine", "symbol", "divisibility", "terms", "terms_amount", "terms_cap", "terms_height_start", "terms_height_end", "terms_offset_start", "terms_offset_end", "turbo", "etching_block", "etching_tx_hash", "etched_at")
VALUES(
unnest($1::TEXT[]),
unnest($2::TEXT[]),
unnest($3::BIGINT[]),
unnest($4::INT[]),
unnest($5::DECIMAL[]),
unnest($6::INT[]),
unnest($7::SMALLINT[]),
unnest($8::BOOLEAN[]),
unnest($9::DECIMAL[]),
unnest($10::DECIMAL[]),
unnest($11::INT[]), -- nullable (need patch)
unnest($12::INT[]), -- nullable (need patch)
unnest($13::INT[]), -- nullable (need patch)
unnest($14::INT[]), -- nullable (need patch)
unnest($15::BOOLEAN[]),
unnest($16::INT[]),
unnest($17::TEXT[]),
unnest($18::TIMESTAMP[])
)
`
type BatchCreateRuneEntriesParams struct {
RuneIDArr []string
RuneArr []string
NumberArr []int64
SpacersArr []int32
PremineArr []pgtype.Numeric
SymbolArr []int32
DivisibilityArr []int16
TermsArr []bool
TermsAmountArr []pgtype.Numeric
TermsCapArr []pgtype.Numeric
TermsHeightStartArr []int32
TermsHeightEndArr []int32
TermsOffsetStartArr []int32
TermsOffsetEndArr []int32
TurboArr []bool
EtchingBlockArr []int32
EtchingTxHashArr []string
EtchedAtArr []pgtype.Timestamp
}
func (q *Queries) BatchCreateRuneEntries(ctx context.Context, arg BatchCreateRuneEntriesParams) error {
_, err := q.db.Exec(ctx, batchCreateRuneEntries,
arg.RuneIDArr,
arg.RuneArr,
arg.NumberArr,
arg.SpacersArr,
arg.PremineArr,
arg.SymbolArr,
arg.DivisibilityArr,
arg.TermsArr,
arg.TermsAmountArr,
arg.TermsCapArr,
arg.TermsHeightStartArr,
arg.TermsHeightEndArr,
arg.TermsOffsetStartArr,
arg.TermsOffsetEndArr,
arg.TurboArr,
arg.EtchingBlockArr,
arg.EtchingTxHashArr,
arg.EtchedAtArr,
)
return err
}
const batchCreateRuneEntryStates = `-- name: BatchCreateRuneEntryStates :exec
INSERT INTO runes_entry_states ("rune_id", "block_height", "mints", "burned_amount", "completed_at", "completed_at_height")
VALUES(
unnest($1::TEXT[]),
unnest($2::INT[]),
unnest($3::DECIMAL[]),
unnest($4::DECIMAL[]),
unnest($5::TIMESTAMP[]),
unnest($6::INT[]) -- nullable (need patch)
)
`
type BatchCreateRuneEntryStatesParams struct {
RuneIDArr []string
BlockHeightArr []int32
MintsArr []pgtype.Numeric
BurnedAmountArr []pgtype.Numeric
CompletedAtArr []pgtype.Timestamp
CompletedAtHeightArr []int32
}
func (q *Queries) BatchCreateRuneEntryStates(ctx context.Context, arg BatchCreateRuneEntryStatesParams) error {
_, err := q.db.Exec(ctx, batchCreateRuneEntryStates,
arg.RuneIDArr,
arg.BlockHeightArr,
arg.MintsArr,
arg.BurnedAmountArr,
arg.CompletedAtArr,
arg.CompletedAtHeightArr,
)
return err
}
const batchCreateRuneTransactions = `-- name: BatchCreateRuneTransactions :exec
INSERT INTO runes_transactions ("hash", "block_height", "index", "timestamp", "inputs", "outputs", "mints", "burns", "rune_etched")
VALUES (
unnest($1::TEXT[]),
unnest($2::INT[]),
unnest($3::INT[]),
unnest($4::TIMESTAMP[]),
unnest($5::JSONB[]),
unnest($6::JSONB[]),
unnest($7::JSONB[]),
unnest($8::JSONB[]),
unnest($9::BOOLEAN[])
)
`
type BatchCreateRuneTransactionsParams struct {
HashArr []string
BlockHeightArr []int32
IndexArr []int32
TimestampArr []pgtype.Timestamp
InputsArr [][]byte
OutputsArr [][]byte
MintsArr [][]byte
BurnsArr [][]byte
RuneEtchedArr []bool
}
func (q *Queries) BatchCreateRuneTransactions(ctx context.Context, arg BatchCreateRuneTransactionsParams) error {
_, err := q.db.Exec(ctx, batchCreateRuneTransactions,
arg.HashArr,
arg.BlockHeightArr,
arg.IndexArr,
arg.TimestampArr,
arg.InputsArr,
arg.OutputsArr,
arg.MintsArr,
arg.BurnsArr,
arg.RuneEtchedArr,
)
return err
}
const batchCreateRunesBalances = `-- name: BatchCreateRunesBalances :exec
INSERT INTO runes_balances ("pkscript", "block_height", "rune_id", "amount")
VALUES(
unnest($1::TEXT[]),
unnest($2::INT[]),
unnest($3::TEXT[]),
unnest($4::DECIMAL[])
)
`
type BatchCreateRunesBalancesParams struct {
PkscriptArr []string
BlockHeightArr []int32
RuneIDArr []string
AmountArr []pgtype.Numeric
}
func (q *Queries) BatchCreateRunesBalances(ctx context.Context, arg BatchCreateRunesBalancesParams) error {
_, err := q.db.Exec(ctx, batchCreateRunesBalances,
arg.PkscriptArr,
arg.BlockHeightArr,
arg.RuneIDArr,
arg.AmountArr,
)
return err
}
const batchCreateRunesOutpointBalances = `-- name: BatchCreateRunesOutpointBalances :exec
INSERT INTO runes_outpoint_balances ("rune_id", "pkscript", "tx_hash", "tx_idx", "amount", "block_height", "spent_height")
VALUES(
unnest($1::TEXT[]),
unnest($2::TEXT[]),
unnest($3::TEXT[]),
unnest($4::INT[]),
unnest($5::DECIMAL[]),
unnest($6::INT[]),
unnest($7::INT[]) -- nullable (need patch)
)
`
type BatchCreateRunesOutpointBalancesParams struct {
RuneIDArr []string
PkscriptArr []string
TxHashArr []string
TxIdxArr []int32
AmountArr []pgtype.Numeric
BlockHeightArr []int32
SpentHeightArr []int32
}
func (q *Queries) BatchCreateRunesOutpointBalances(ctx context.Context, arg BatchCreateRunesOutpointBalancesParams) error {
_, err := q.db.Exec(ctx, batchCreateRunesOutpointBalances,
arg.RuneIDArr,
arg.PkscriptArr,
arg.TxHashArr,
arg.TxIdxArr,
arg.AmountArr,
arg.BlockHeightArr,
arg.SpentHeightArr,
)
return err
}
const batchCreateRunestones = `-- name: BatchCreateRunestones :exec
INSERT INTO runes_runestones ("tx_hash", "block_height", "etching", "etching_divisibility", "etching_premine", "etching_rune", "etching_spacers", "etching_symbol", "etching_terms", "etching_terms_amount", "etching_terms_cap", "etching_terms_height_start", "etching_terms_height_end", "etching_terms_offset_start", "etching_terms_offset_end", "etching_turbo", "edicts", "mint", "pointer", "cenotaph", "flaws")
VALUES(
unnest($1::TEXT[]),
unnest($2::INT[]),
unnest($3::BOOLEAN[]),
unnest($4::SMALLINT[]), -- nullable (need patch)
unnest($5::DECIMAL[]),
unnest($6::TEXT[]), -- nullable (need patch)
unnest($7::INT[]), -- nullable (need patch)
unnest($8::INT[]), -- nullable (need patch)
unnest($9::BOOLEAN[]), -- nullable (need patch)
unnest($10::DECIMAL[]),
unnest($11::DECIMAL[]),
unnest($12::INT[]), -- nullable (need patch)
unnest($13::INT[]), -- nullable (need patch)
unnest($14::INT[]), -- nullable (need patch)
unnest($15::INT[]), -- nullable (need patch)
unnest($16::BOOLEAN[]), -- nullable (need patch)
unnest($17::JSONB[]),
unnest($18::TEXT[]), -- nullable (need patch)
unnest($19::INT[]), -- nullable (need patch)
unnest($20::BOOLEAN[]),
unnest($21::INT[])
)
`
type BatchCreateRunestonesParams struct {
TxHashArr []string
BlockHeightArr []int32
EtchingArr []bool
EtchingDivisibilityArr []int16
EtchingPremineArr []pgtype.Numeric
EtchingRuneArr []string
EtchingSpacersArr []int32
EtchingSymbolArr []int32
EtchingTermsArr []bool
EtchingTermsAmountArr []pgtype.Numeric
EtchingTermsCapArr []pgtype.Numeric
EtchingTermsHeightStartArr []int32
EtchingTermsHeightEndArr []int32
EtchingTermsOffsetStartArr []int32
EtchingTermsOffsetEndArr []int32
EtchingTurboArr []bool
EdictsArr [][]byte
MintArr []string
PointerArr []int32
CenotaphArr []bool
FlawsArr []int32
}
func (q *Queries) BatchCreateRunestones(ctx context.Context, arg BatchCreateRunestonesParams) error {
_, err := q.db.Exec(ctx, batchCreateRunestones,
arg.TxHashArr,
arg.BlockHeightArr,
arg.EtchingArr,
arg.EtchingDivisibilityArr,
arg.EtchingPremineArr,
arg.EtchingRuneArr,
arg.EtchingSpacersArr,
arg.EtchingSymbolArr,
arg.EtchingTermsArr,
arg.EtchingTermsAmountArr,
arg.EtchingTermsCapArr,
arg.EtchingTermsHeightStartArr,
arg.EtchingTermsHeightEndArr,
arg.EtchingTermsOffsetStartArr,
arg.EtchingTermsOffsetEndArr,
arg.EtchingTurboArr,
arg.EdictsArr,
arg.MintArr,
arg.PointerArr,
arg.CenotaphArr,
arg.FlawsArr,
)
return err
}
const batchSpendOutpointBalances = `-- name: BatchSpendOutpointBalances :exec
UPDATE runes_outpoint_balances
SET "spent_height" = $1::INT
FROM (
SELECT
unnest($2::TEXT[]) AS tx_hash,
unnest($3::INT[]) AS tx_idx
) AS input
WHERE "runes_outpoint_balances"."tx_hash" = "input"."tx_hash" AND "runes_outpoint_balances"."tx_idx" = "input"."tx_idx"
`
type BatchSpendOutpointBalancesParams struct {
SpentHeight int32
TxHashArr []string
TxIdxArr []int32
}
func (q *Queries) BatchSpendOutpointBalances(ctx context.Context, arg BatchSpendOutpointBalancesParams) error {
_, err := q.db.Exec(ctx, batchSpendOutpointBalances, arg.SpentHeight, arg.TxHashArr, arg.TxIdxArr)
return err
}

View File

@@ -0,0 +1,118 @@
package gen
import (
"context"
"github.com/cockroachdb/errors"
"github.com/jackc/pgx/v5/pgtype"
)
type BatchCreateRuneEntriesPatchedParams struct {
BatchCreateRuneEntriesParams
TermsHeightStartArr []pgtype.Int4
TermsHeightEndArr []pgtype.Int4
TermsOffsetStartArr []pgtype.Int4
TermsOffsetEndArr []pgtype.Int4
}
func (q *Queries) BatchCreateRuneEntriesPatched(ctx context.Context, arg BatchCreateRuneEntriesPatchedParams) error {
_, err := q.db.Exec(ctx, batchCreateRuneEntries,
arg.RuneIDArr,
arg.RuneArr,
arg.NumberArr,
arg.SpacersArr,
arg.PremineArr,
arg.SymbolArr,
arg.DivisibilityArr,
arg.TermsArr,
arg.TermsAmountArr,
arg.TermsCapArr,
arg.TermsHeightStartArr,
arg.TermsHeightEndArr,
arg.TermsOffsetStartArr,
arg.TermsOffsetEndArr,
arg.TurboArr,
arg.EtchingBlockArr,
arg.EtchingTxHashArr,
arg.EtchedAtArr,
)
return errors.WithStack(err)
}
type BatchCreateRuneEntryStatesPatchedParams struct {
BatchCreateRuneEntryStatesParams
CompletedAtHeightArr []pgtype.Int4
}
func (q *Queries) BatchCreateRuneEntryStatesPatched(ctx context.Context, arg BatchCreateRuneEntryStatesPatchedParams) error {
_, err := q.db.Exec(ctx, batchCreateRuneEntryStates,
arg.RuneIDArr,
arg.BlockHeightArr,
arg.MintsArr,
arg.BurnedAmountArr,
arg.CompletedAtArr,
arg.CompletedAtHeightArr,
)
return errors.WithStack(err)
}
type BatchCreateRunesOutpointBalancesPatchedParams struct {
BatchCreateRunesOutpointBalancesParams
SpentHeightArr []pgtype.Int4
}
func (q *Queries) BatchCreateRunesOutpointBalancesPatched(ctx context.Context, arg BatchCreateRunesOutpointBalancesPatchedParams) error {
_, err := q.db.Exec(ctx, batchCreateRunesOutpointBalances,
arg.RuneIDArr,
arg.PkscriptArr,
arg.TxHashArr,
arg.TxIdxArr,
arg.AmountArr,
arg.BlockHeightArr,
arg.SpentHeightArr,
)
return errors.WithStack(err)
}
type BatchCreateRunestonesPatchedParams struct {
BatchCreateRunestonesParams
EtchingDivisibilityArr []pgtype.Int2
EtchingRuneArr []pgtype.Text
EtchingSpacersArr []pgtype.Int4
EtchingSymbolArr []pgtype.Int4
EtchingTermsArr []pgtype.Bool
EtchingTermsHeightStartArr []pgtype.Int4
EtchingTermsHeightEndArr []pgtype.Int4
EtchingTermsOffsetStartArr []pgtype.Int4
EtchingTermsOffsetEndArr []pgtype.Int4
EtchingTurboArr []pgtype.Bool
MintArr []pgtype.Text
PointerArr []pgtype.Int4
}
func (q *Queries) BatchCreateRunestonesPatched(ctx context.Context, arg BatchCreateRunestonesPatchedParams) error {
_, err := q.db.Exec(ctx, batchCreateRunestones,
arg.TxHashArr,
arg.BlockHeightArr,
arg.EtchingArr,
arg.EtchingDivisibilityArr,
arg.EtchingPremineArr,
arg.EtchingRuneArr,
arg.EtchingSpacersArr,
arg.EtchingSymbolArr,
arg.EtchingTermsArr,
arg.EtchingTermsAmountArr,
arg.EtchingTermsCapArr,
arg.EtchingTermsHeightStartArr,
arg.EtchingTermsHeightEndArr,
arg.EtchingTermsOffsetStartArr,
arg.EtchingTermsOffsetEndArr,
arg.EtchingTurboArr,
arg.EdictsArr,
arg.MintArr,
arg.PointerArr,
arg.CenotaphArr,
arg.FlawsArr,
)
return errors.WithStack(err)
}

View File

@@ -45,6 +45,54 @@ func (q *Queries) CreateIndexedBlock(ctx context.Context, arg CreateIndexedBlock
return err
}
const createOutPointBalance = `-- name: CreateOutPointBalance :exec
INSERT INTO runes_outpoint_balances (rune_id, pkscript, tx_hash, tx_idx, amount, block_height, spent_height) VALUES ($1, $2, $3, $4, $5, $6, $7)
`
type CreateOutPointBalanceParams struct {
RuneID string
Pkscript string
TxHash string
TxIdx int32
Amount pgtype.Numeric
BlockHeight int32
SpentHeight pgtype.Int4
}
func (q *Queries) CreateOutPointBalance(ctx context.Context, arg CreateOutPointBalanceParams) error {
_, err := q.db.Exec(ctx, createOutPointBalance,
arg.RuneID,
arg.Pkscript,
arg.TxHash,
arg.TxIdx,
arg.Amount,
arg.BlockHeight,
arg.SpentHeight,
)
return err
}
const createRuneBalance = `-- name: CreateRuneBalance :exec
INSERT INTO runes_balances (pkscript, block_height, rune_id, amount) VALUES ($1, $2, $3, $4)
`
type CreateRuneBalanceParams struct {
Pkscript string
BlockHeight int32
RuneID string
Amount pgtype.Numeric
}
func (q *Queries) CreateRuneBalance(ctx context.Context, arg CreateRuneBalanceParams) error {
_, err := q.db.Exec(ctx, createRuneBalance,
arg.Pkscript,
arg.BlockHeight,
arg.RuneID,
arg.Amount,
)
return err
}
const createRuneEntry = `-- name: CreateRuneEntry :exec
INSERT INTO runes_entries (rune_id, rune, number, spacers, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, turbo, etching_block, etching_tx_hash, etched_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
@@ -454,8 +502,7 @@ SELECT runes_entries.rune_id, number, rune, spacers, premine, symbol, divisibili
$2::text = '' OR
runes_entries.rune ILIKE '%' || $2::text || '%'
)
ORDER BY (COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(states.mints, 0)) /
(COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(runes_entries.terms_cap, 0))::float DESC
ORDER BY states.mints DESC
LIMIT $4 OFFSET $3
`
@@ -1222,18 +1269,18 @@ func (q *Queries) GetTotalHoldersByRuneIds(ctx context.Context, arg GetTotalHold
return items, nil
}
const spendOutPointBalances = `-- name: SpendOutPointBalances :exec
const spendOutPointBalance = `-- name: SpendOutPointBalance :exec
UPDATE runes_outpoint_balances SET spent_height = $1 WHERE tx_hash = $2 AND tx_idx = $3
`
type SpendOutPointBalancesParams struct {
type SpendOutPointBalanceParams struct {
SpentHeight pgtype.Int4
TxHash string
TxIdx int32
}
func (q *Queries) SpendOutPointBalances(ctx context.Context, arg SpendOutPointBalancesParams) error {
_, err := q.db.Exec(ctx, spendOutPointBalances, arg.SpentHeight, arg.TxHash, arg.TxIdx)
func (q *Queries) SpendOutPointBalance(ctx context.Context, arg SpendOutPointBalanceParams) error {
_, err := q.db.Exec(ctx, spendOutPointBalance, arg.SpentHeight, arg.TxHash, arg.TxIdx)
return err
}

View File

@@ -15,7 +15,6 @@ type DBTX interface {
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
QueryRow(context.Context, string, ...interface{}) pgx.Row
SendBatch(context.Context, *pgx.Batch) pgx.BatchResults
}
func New(db DBTX) *Queries {

View File

@@ -11,6 +11,7 @@ import (
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
"github.com/gaze-network/indexer-network/modules/runes/repository/postgres/gen"
"github.com/gaze-network/indexer-network/modules/runes/runes"
"github.com/gaze-network/uint128"
"github.com/jackc/pgx/v5/pgtype"
"github.com/samber/lo"
@@ -47,7 +48,7 @@ func numericFromUint128(src *uint128.Uint128) (pgtype.Numeric, error) {
func mapIndexerStateModelToType(src gen.RunesIndexerState) entity.IndexerState {
var createdAt time.Time
if src.CreatedAt.Valid {
createdAt = src.CreatedAt.Time
createdAt = src.CreatedAt.Time.UTC()
}
return entity.IndexerState{
DBVersion: src.DbVersion,
@@ -86,7 +87,7 @@ func mapRuneEntryModelToType(src gen.GetRuneEntriesRow) (runes.RuneEntry, error)
}
var completedAt time.Time
if src.CompletedAt.Valid {
completedAt = src.CompletedAt.Time
completedAt = src.CompletedAt.Time.UTC()
}
var completedAtHeight *uint64
if src.CompletedAtHeight.Valid {
@@ -132,7 +133,7 @@ func mapRuneEntryModelToType(src gen.GetRuneEntriesRow) (runes.RuneEntry, error)
}
var etchedAt time.Time
if src.EtchedAt.Valid {
etchedAt = src.EtchedAt.Time
etchedAt = src.EtchedAt.Time.UTC()
}
return runes.RuneEntry{
RuneId: runeId,
@@ -153,31 +154,13 @@ func mapRuneEntryModelToType(src gen.GetRuneEntriesRow) (runes.RuneEntry, error)
}, nil
}
func mapRuneEntryTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.CreateRuneEntryParams, gen.CreateRuneEntryStateParams, error) {
func mapRuneEntryTypeToParams(src runes.RuneEntry) (gen.CreateRuneEntryParams, error) {
runeId := src.RuneId.String()
rune := src.SpacedRune.Rune.String()
spacers := int32(src.SpacedRune.Spacers)
mints, err := numericFromUint128(&src.Mints)
if err != nil {
return gen.CreateRuneEntryParams{}, gen.CreateRuneEntryStateParams{}, errors.Wrap(err, "failed to parse mints")
}
burnedAmount, err := numericFromUint128(&src.BurnedAmount)
if err != nil {
return gen.CreateRuneEntryParams{}, gen.CreateRuneEntryStateParams{}, errors.Wrap(err, "failed to parse burned amount")
}
premine, err := numericFromUint128(&src.Premine)
if err != nil {
return gen.CreateRuneEntryParams{}, gen.CreateRuneEntryStateParams{}, errors.Wrap(err, "failed to parse premine")
}
var completedAt pgtype.Timestamp
if !src.CompletedAt.IsZero() {
completedAt.Time = src.CompletedAt
completedAt.Valid = true
}
var completedAtHeight pgtype.Int4
if src.CompletedAtHeight != nil {
completedAtHeight.Int32 = int32(*src.CompletedAtHeight)
completedAtHeight.Valid = true
return gen.CreateRuneEntryParams{}, errors.Wrap(err, "failed to parse premine")
}
var terms bool
var termsAmount, termsCap pgtype.Numeric
@@ -187,13 +170,13 @@ func mapRuneEntryTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.Crea
if src.Terms.Amount != nil {
termsAmount, err = numericFromUint128(src.Terms.Amount)
if err != nil {
return gen.CreateRuneEntryParams{}, gen.CreateRuneEntryStateParams{}, errors.Wrap(err, "failed to parse terms amount")
return gen.CreateRuneEntryParams{}, errors.Wrap(err, "failed to parse terms amount")
}
}
if src.Terms.Cap != nil {
termsCap, err = numericFromUint128(src.Terms.Cap)
if err != nil {
return gen.CreateRuneEntryParams{}, gen.CreateRuneEntryStateParams{}, errors.Wrap(err, "failed to parse terms cap")
return gen.CreateRuneEntryParams{}, errors.Wrap(err, "failed to parse terms cap")
}
}
if src.Terms.HeightStart != nil {
@@ -221,51 +204,150 @@ func mapRuneEntryTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.Crea
}
}
}
etchedAt := pgtype.Timestamp{Time: src.EtchedAt, Valid: true}
etchedAt := pgtype.Timestamp{Time: src.EtchedAt.UTC(), Valid: true}
return gen.CreateRuneEntryParams{
RuneID: runeId,
Rune: rune,
Number: int64(src.Number),
Spacers: spacers,
Premine: premine,
Symbol: src.Symbol,
Divisibility: int16(src.Divisibility),
Terms: terms,
TermsAmount: termsAmount,
TermsCap: termsCap,
TermsHeightStart: termsHeightStart,
TermsHeightEnd: termsHeightEnd,
TermsOffsetStart: termsOffsetStart,
TermsOffsetEnd: termsOffsetEnd,
Turbo: src.Turbo,
EtchingBlock: int32(src.EtchingBlock),
EtchingTxHash: src.EtchingTxHash.String(),
EtchedAt: etchedAt,
}, gen.CreateRuneEntryStateParams{
BlockHeight: int32(blockHeight),
RuneID: runeId,
Mints: mints,
BurnedAmount: burnedAmount,
CompletedAt: completedAt,
CompletedAtHeight: completedAtHeight,
}, nil
RuneID: runeId,
Rune: rune,
Number: int64(src.Number),
Spacers: spacers,
Premine: premine,
Symbol: src.Symbol,
Divisibility: int16(src.Divisibility),
Terms: terms,
TermsAmount: termsAmount,
TermsCap: termsCap,
TermsHeightStart: termsHeightStart,
TermsHeightEnd: termsHeightEnd,
TermsOffsetStart: termsOffsetStart,
TermsOffsetEnd: termsOffsetEnd,
Turbo: src.Turbo,
EtchingBlock: int32(src.EtchingBlock),
EtchingTxHash: src.EtchingTxHash.String(),
EtchedAt: etchedAt,
}, nil
}
// mapRuneTransactionModelToType returns params for creating a new rune transaction and (optionally) a runestone.
func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneTransactionParams, *gen.CreateRunestoneParams, error) {
func mapRuneEntryStatesTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.CreateRuneEntryStateParams, error) {
runeId := src.RuneId.String()
mints, err := numericFromUint128(&src.Mints)
if err != nil {
return gen.CreateRuneEntryStateParams{}, errors.Wrap(err, "failed to parse mints")
}
burnedAmount, err := numericFromUint128(&src.BurnedAmount)
if err != nil {
return gen.CreateRuneEntryStateParams{}, errors.Wrap(err, "failed to parse burned amount")
}
var completedAt pgtype.Timestamp
if !src.CompletedAt.IsZero() {
completedAt.Time = src.CompletedAt.UTC()
completedAt.Valid = true
}
var completedAtHeight pgtype.Int4
if src.CompletedAtHeight != nil {
completedAtHeight.Int32 = int32(*src.CompletedAtHeight)
completedAtHeight.Valid = true
}
return gen.CreateRuneEntryStateParams{
BlockHeight: int32(blockHeight),
RuneID: runeId,
Mints: mints,
BurnedAmount: burnedAmount,
CompletedAt: completedAt,
CompletedAtHeight: completedAtHeight,
}, nil
}
func mapRuneEntryTypeToParamsBatch(srcs []*runes.RuneEntry) (gen.BatchCreateRuneEntriesPatchedParams, error) {
var batchParams gen.BatchCreateRuneEntriesPatchedParams
batchParams.RuneIDArr = make([]string, 0, len(srcs))
batchParams.RuneArr = make([]string, 0, len(srcs))
batchParams.NumberArr = make([]int64, 0, len(srcs))
batchParams.SpacersArr = make([]int32, 0, len(srcs))
batchParams.PremineArr = make([]pgtype.Numeric, 0, len(srcs))
batchParams.SymbolArr = make([]int32, 0, len(srcs))
batchParams.DivisibilityArr = make([]int16, 0, len(srcs))
batchParams.TermsArr = make([]bool, 0, len(srcs))
batchParams.TermsAmountArr = make([]pgtype.Numeric, 0, len(srcs))
batchParams.TermsCapArr = make([]pgtype.Numeric, 0, len(srcs))
batchParams.TermsHeightStartArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.TermsHeightEndArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.TermsOffsetStartArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.TermsOffsetEndArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.TurboArr = make([]bool, 0, len(srcs))
batchParams.EtchingBlockArr = make([]int32, 0, len(srcs))
batchParams.EtchingTxHashArr = make([]string, 0, len(srcs))
batchParams.EtchedAtArr = make([]pgtype.Timestamp, 0, len(srcs))
for i, src := range srcs {
param, err := mapRuneEntryTypeToParams(*src)
if err != nil {
return gen.BatchCreateRuneEntriesPatchedParams{}, errors.Wrapf(err, "failed to map rune entry to params batch at index %d", i)
}
batchParams.RuneIDArr = append(batchParams.RuneIDArr, param.RuneID)
batchParams.RuneArr = append(batchParams.RuneArr, param.Rune)
batchParams.NumberArr = append(batchParams.NumberArr, param.Number)
batchParams.SpacersArr = append(batchParams.SpacersArr, param.Spacers)
batchParams.PremineArr = append(batchParams.PremineArr, param.Premine)
batchParams.SymbolArr = append(batchParams.SymbolArr, param.Symbol)
batchParams.DivisibilityArr = append(batchParams.DivisibilityArr, param.Divisibility)
batchParams.TermsArr = append(batchParams.TermsArr, param.Terms)
batchParams.TermsAmountArr = append(batchParams.TermsAmountArr, param.TermsAmount)
batchParams.TermsCapArr = append(batchParams.TermsCapArr, param.TermsCap)
batchParams.TermsHeightStartArr = append(batchParams.TermsHeightStartArr, param.TermsHeightStart)
batchParams.TermsHeightEndArr = append(batchParams.TermsHeightEndArr, param.TermsHeightEnd)
batchParams.TermsOffsetStartArr = append(batchParams.TermsOffsetStartArr, param.TermsOffsetStart)
batchParams.TermsOffsetEndArr = append(batchParams.TermsOffsetEndArr, param.TermsOffsetEnd)
batchParams.TurboArr = append(batchParams.TurboArr, param.Turbo)
batchParams.EtchingBlockArr = append(batchParams.EtchingBlockArr, param.EtchingBlock)
batchParams.EtchingTxHashArr = append(batchParams.EtchingTxHashArr, param.EtchingTxHash)
batchParams.EtchedAtArr = append(batchParams.EtchedAtArr, param.EtchedAt)
}
return batchParams, nil
}
func mapRuneEntryStatesTypeToParamsBatch(srcs []*runes.RuneEntry, blockHeight uint64) (gen.BatchCreateRuneEntryStatesPatchedParams, error) {
var batchParams gen.BatchCreateRuneEntryStatesPatchedParams
batchParams.RuneIDArr = make([]string, 0, len(srcs))
batchParams.BlockHeightArr = make([]int32, 0, len(srcs))
batchParams.MintsArr = make([]pgtype.Numeric, 0, len(srcs))
batchParams.BurnedAmountArr = make([]pgtype.Numeric, 0, len(srcs))
batchParams.CompletedAtArr = make([]pgtype.Timestamp, 0, len(srcs))
batchParams.CompletedAtHeightArr = make([]pgtype.Int4, 0, len(srcs))
for i, src := range srcs {
param, err := mapRuneEntryStatesTypeToParams(*src, blockHeight)
if err != nil {
return gen.BatchCreateRuneEntryStatesPatchedParams{}, errors.Wrapf(err, "failed to map rune entry states to params batch at index %d", i)
}
batchParams.RuneIDArr = append(batchParams.RuneIDArr, param.RuneID)
batchParams.BlockHeightArr = append(batchParams.BlockHeightArr, param.BlockHeight)
batchParams.MintsArr = append(batchParams.MintsArr, param.Mints)
batchParams.BurnedAmountArr = append(batchParams.BurnedAmountArr, param.BurnedAmount)
batchParams.CompletedAtArr = append(batchParams.CompletedAtArr, param.CompletedAt)
batchParams.CompletedAtHeightArr = append(batchParams.CompletedAtHeightArr, param.CompletedAtHeight)
}
return batchParams, nil
}
func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneTransactionParams, error) {
var timestamp pgtype.Timestamp
if !src.Timestamp.IsZero() {
timestamp.Time = src.Timestamp
timestamp.Time = src.Timestamp.UTC()
timestamp.Valid = true
}
inputsBytes, err := json.Marshal(src.Inputs)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal inputs")
return gen.CreateRuneTransactionParams{}, errors.Wrap(err, "failed to marshal inputs")
}
outputsBytes, err := json.Marshal(src.Outputs)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal outputs")
return gen.CreateRuneTransactionParams{}, errors.Wrap(err, "failed to marshal outputs")
}
mints := make(map[string]uint128.Uint128)
for key, value := range src.Mints {
@@ -273,7 +355,7 @@ func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneT
}
mintsBytes, err := json.Marshal(mints)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal mints")
return gen.CreateRuneTransactionParams{}, errors.Wrap(err, "failed to marshal mints")
}
burns := make(map[string]uint128.Uint128)
for key, value := range src.Burns {
@@ -281,16 +363,7 @@ func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneT
}
burnsBytes, err := json.Marshal(burns)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal burns")
}
var runestoneParams *gen.CreateRunestoneParams
if src.Runestone != nil {
params, err := mapRunestoneTypeToParams(*src.Runestone, src.Hash, src.BlockHeight)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to map runestone to params")
}
runestoneParams = &params
return gen.CreateRuneTransactionParams{}, errors.Wrap(err, "failed to marshal burns")
}
return gen.CreateRuneTransactionParams{
@@ -303,7 +376,46 @@ func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneT
Mints: mintsBytes,
Burns: burnsBytes,
RuneEtched: src.RuneEtched,
}, runestoneParams, nil
}, nil
}
func mapRuneTransactionTypeToParamsBatch(srcs []*entity.RuneTransaction) (gen.BatchCreateRuneTransactionsParams, error) {
batchParams := gen.BatchCreateRuneTransactionsParams{
HashArr: make([]string, 0, len(srcs)),
BlockHeightArr: make([]int32, 0, len(srcs)),
IndexArr: make([]int32, 0, len(srcs)),
TimestampArr: make([]pgtype.Timestamp, 0, len(srcs)),
RuneEtchedArr: make([]bool, 0, len(srcs)),
}
inputsArr := make([][]byte, 0, len(srcs))
outputsArr := make([][]byte, 0, len(srcs))
mintsArr := make([][]byte, 0, len(srcs))
burnsArr := make([][]byte, 0, len(srcs))
for i, src := range srcs {
param, err := mapRuneTransactionTypeToParams(*src)
if err != nil {
return gen.BatchCreateRuneTransactionsParams{}, errors.Wrapf(err, "failed to map rune transaction to params batch at index %d", i)
}
batchParams.HashArr = append(batchParams.HashArr, param.Hash)
batchParams.BlockHeightArr = append(batchParams.BlockHeightArr, param.BlockHeight)
batchParams.IndexArr = append(batchParams.IndexArr, param.Index)
batchParams.TimestampArr = append(batchParams.TimestampArr, param.Timestamp)
batchParams.RuneEtchedArr = append(batchParams.RuneEtchedArr, param.RuneEtched)
inputsArr = append(inputsArr, param.Inputs)
outputsArr = append(outputsArr, param.Outputs)
mintsArr = append(mintsArr, param.Mints)
burnsArr = append(burnsArr, param.Burns)
}
batchParams.InputsArr = inputsArr
batchParams.OutputsArr = outputsArr
batchParams.MintsArr = mintsArr
batchParams.BurnsArr = burnsArr
return batchParams, nil
}
func extractModelRuneTxAndRunestone(src gen.GetRuneTransactionsRow) (gen.RunesTransaction, *gen.RunesRunestone, error) {
@@ -488,6 +600,65 @@ func mapRunestoneTypeToParams(src runes.Runestone, txHash chainhash.Hash, blockH
return runestoneParams, nil
}
func mapRunestoneTypeToParamsBatch(srcs []*entity.RuneTransaction) (gen.BatchCreateRunestonesPatchedParams, error) {
var batchParams gen.BatchCreateRunestonesPatchedParams
batchParams.TxHashArr = make([]string, 0, len(srcs))
batchParams.BlockHeightArr = make([]int32, 0, len(srcs))
batchParams.EtchingArr = make([]bool, 0, len(srcs))
batchParams.EtchingDivisibilityArr = make([]pgtype.Int2, 0, len(srcs))
batchParams.EtchingPremineArr = make([]pgtype.Numeric, 0, len(srcs))
batchParams.EtchingRuneArr = make([]pgtype.Text, 0, len(srcs))
batchParams.EtchingSpacersArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.EtchingSymbolArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.EtchingTermsArr = make([]pgtype.Bool, 0, len(srcs))
batchParams.EtchingTermsAmountArr = make([]pgtype.Numeric, 0, len(srcs))
batchParams.EtchingTermsCapArr = make([]pgtype.Numeric, 0, len(srcs))
batchParams.EtchingTermsHeightStartArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.EtchingTermsHeightEndArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.EtchingTermsOffsetStartArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.EtchingTermsOffsetEndArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.EtchingTurboArr = make([]pgtype.Bool, 0, len(srcs))
batchParams.EdictsArr = make([][]byte, 0, len(srcs))
batchParams.MintArr = make([]pgtype.Text, 0, len(srcs))
batchParams.PointerArr = make([]pgtype.Int4, 0, len(srcs))
batchParams.CenotaphArr = make([]bool, 0, len(srcs))
batchParams.FlawsArr = make([]int32, 0, len(srcs))
for i, src := range srcs {
if src.Runestone == nil {
continue
}
param, err := mapRunestoneTypeToParams(*src.Runestone, src.Hash, src.BlockHeight)
if err != nil {
return gen.BatchCreateRunestonesPatchedParams{}, errors.Wrapf(err, "failed to map runestone to params batch at index %d", i)
}
batchParams.TxHashArr = append(batchParams.TxHashArr, param.TxHash)
batchParams.BlockHeightArr = append(batchParams.BlockHeightArr, param.BlockHeight)
batchParams.EtchingArr = append(batchParams.EtchingArr, param.Etching)
batchParams.EtchingDivisibilityArr = append(batchParams.EtchingDivisibilityArr, param.EtchingDivisibility)
batchParams.EtchingPremineArr = append(batchParams.EtchingPremineArr, param.EtchingPremine)
batchParams.EtchingRuneArr = append(batchParams.EtchingRuneArr, param.EtchingRune)
batchParams.EtchingSpacersArr = append(batchParams.EtchingSpacersArr, param.EtchingSpacers)
batchParams.EtchingSymbolArr = append(batchParams.EtchingSymbolArr, param.EtchingSymbol)
batchParams.EtchingTermsArr = append(batchParams.EtchingTermsArr, param.EtchingTerms)
batchParams.EtchingTermsAmountArr = append(batchParams.EtchingTermsAmountArr, param.EtchingTermsAmount)
batchParams.EtchingTermsCapArr = append(batchParams.EtchingTermsCapArr, param.EtchingTermsCap)
batchParams.EtchingTermsHeightStartArr = append(batchParams.EtchingTermsHeightStartArr, param.EtchingTermsHeightStart)
batchParams.EtchingTermsHeightEndArr = append(batchParams.EtchingTermsHeightEndArr, param.EtchingTermsHeightEnd)
batchParams.EtchingTermsOffsetStartArr = append(batchParams.EtchingTermsOffsetStartArr, param.EtchingTermsOffsetStart)
batchParams.EtchingTermsOffsetEndArr = append(batchParams.EtchingTermsOffsetEndArr, param.EtchingTermsOffsetEnd)
batchParams.EtchingTurboArr = append(batchParams.EtchingTurboArr, param.EtchingTurbo)
batchParams.EdictsArr = append(batchParams.EdictsArr, param.Edicts)
batchParams.MintArr = append(batchParams.MintArr, param.Mint)
batchParams.PointerArr = append(batchParams.PointerArr, param.Pointer)
batchParams.CenotaphArr = append(batchParams.CenotaphArr, param.Cenotaph)
batchParams.FlawsArr = append(batchParams.FlawsArr, param.Flaws)
}
return batchParams, nil
}
func mapRunestoneModelToType(src gen.RunesRunestone) (runes.Runestone, error) {
runestone := runes.Runestone{
Cenotaph: src.Cenotaph,
@@ -602,6 +773,42 @@ func mapBalanceModelToType(src gen.RunesBalance) (*entity.Balance, error) {
}, nil
}
func mapBalanceTypeToParams(src entity.Balance) (gen.CreateRuneBalanceParams, error) {
amount, err := numericFromUint128(&src.Amount)
if err != nil {
return gen.CreateRuneBalanceParams{}, errors.Wrap(err, "failed to parse amount")
}
return gen.CreateRuneBalanceParams{
RuneID: src.RuneId.String(),
Amount: amount,
Pkscript: hex.EncodeToString(src.PkScript),
BlockHeight: int32(src.BlockHeight),
}, nil
}
func mapBalanceTypeToParamsBatch(srcs []*entity.Balance) (gen.BatchCreateRunesBalancesParams, error) {
batchParams := gen.BatchCreateRunesBalancesParams{
RuneIDArr: make([]string, 0, len(srcs)),
AmountArr: make([]pgtype.Numeric, 0, len(srcs)),
PkscriptArr: make([]string, 0, len(srcs)),
BlockHeightArr: make([]int32, 0, len(srcs)),
}
for i, src := range srcs {
param, err := mapBalanceTypeToParams(*src)
if err != nil {
return gen.BatchCreateRunesBalancesParams{}, errors.Wrapf(err, "failed to map balance to params batch at index %d", i)
}
batchParams.RuneIDArr = append(batchParams.RuneIDArr, param.RuneID)
batchParams.AmountArr = append(batchParams.AmountArr, param.Amount)
batchParams.PkscriptArr = append(batchParams.PkscriptArr, param.Pkscript)
batchParams.BlockHeightArr = append(batchParams.BlockHeightArr, param.BlockHeight)
}
return batchParams, nil
}
func mapIndexedBlockModelToType(src gen.RunesIndexedBlock) (*entity.IndexedBlock, error) {
hash, err := chainhash.NewHashFromStr(src.Hash)
if err != nil {
@@ -738,16 +945,16 @@ func mapOutPointBalanceModelToType(src gen.RunesOutpointBalance) (entity.OutPoin
}, nil
}
func mapOutPointBalanceTypeToParams(src entity.OutPointBalance) (gen.CreateOutPointBalancesParams, error) {
func mapOutPointBalanceTypeToParams(src entity.OutPointBalance) (gen.CreateOutPointBalanceParams, error) {
amount, err := numericFromUint128(&src.Amount)
if err != nil {
return gen.CreateOutPointBalancesParams{}, errors.Wrap(err, "failed to parse amount")
return gen.CreateOutPointBalanceParams{}, errors.Wrap(err, "failed to parse amount")
}
var spentHeight pgtype.Int4
if src.SpentHeight != nil {
spentHeight = pgtype.Int4{Int32: int32(*src.SpentHeight), Valid: true}
}
return gen.CreateOutPointBalancesParams{
return gen.CreateOutPointBalanceParams{
TxHash: src.OutPoint.Hash.String(),
TxIdx: int32(src.OutPoint.Index),
Pkscript: hex.EncodeToString(src.PkScript),
@@ -757,3 +964,31 @@ func mapOutPointBalanceTypeToParams(src entity.OutPointBalance) (gen.CreateOutPo
SpentHeight: spentHeight,
}, nil
}
func mapOutPointBalanceTypeToParamsBatch(srcs []*entity.OutPointBalance) (gen.BatchCreateRunesOutpointBalancesPatchedParams, error) {
var batchParams gen.BatchCreateRunesOutpointBalancesPatchedParams
batchParams.TxHashArr = make([]string, 0, len(srcs))
batchParams.TxIdxArr = make([]int32, 0, len(srcs))
batchParams.PkscriptArr = make([]string, 0, len(srcs))
batchParams.RuneIDArr = make([]string, 0, len(srcs))
batchParams.AmountArr = make([]pgtype.Numeric, 0, len(srcs))
batchParams.BlockHeightArr = make([]int32, 0, len(srcs))
batchParams.SpentHeightArr = make([]pgtype.Int4, 0, len(srcs))
for i, src := range srcs {
param, err := mapOutPointBalanceTypeToParams(*src)
if err != nil {
return gen.BatchCreateRunesOutpointBalancesPatchedParams{}, errors.Wrapf(err, "failed to map outpoint balance to params batch at index %d", i)
}
batchParams.TxHashArr = append(batchParams.TxHashArr, param.TxHash)
batchParams.TxIdxArr = append(batchParams.TxIdxArr, param.TxIdx)
batchParams.PkscriptArr = append(batchParams.PkscriptArr, param.Pkscript)
batchParams.RuneIDArr = append(batchParams.RuneIDArr, param.RuneID)
batchParams.AmountArr = append(batchParams.AmountArr, param.Amount)
batchParams.BlockHeightArr = append(batchParams.BlockHeightArr, param.BlockHeight)
batchParams.SpentHeightArr = append(batchParams.SpentHeightArr, param.SpentHeight)
}
return batchParams, nil
}

View File

@@ -478,111 +478,116 @@ func (r *Repository) GetTotalHoldersByRuneIds(ctx context.Context, runeIds []run
return holders, nil
}
func (r *Repository) CreateRuneTransaction(ctx context.Context, tx *entity.RuneTransaction) error {
if tx == nil {
func (r *Repository) CreateRuneTransactions(ctx context.Context, txs []*entity.RuneTransaction) error {
if len(txs) == 0 {
return nil
}
txParams, runestoneParams, err := mapRuneTransactionTypeToParams(*tx)
txParams, err := mapRuneTransactionTypeToParamsBatch(txs)
if err != nil {
return errors.Wrap(err, "failed to map rune transaction to params")
return errors.Wrap(err, "failed to map rune transactions to params")
}
if err = r.queries.CreateRuneTransaction(ctx, txParams); err != nil {
return errors.Wrap(err, "error during exec CreateRuneTransaction")
if err := r.queries.BatchCreateRuneTransactions(ctx, txParams); err != nil {
return errors.Wrap(err, "error during exec BatchCreateRuneTransactions")
}
if runestoneParams != nil {
if err = r.queries.CreateRunestone(ctx, *runestoneParams); err != nil {
return errors.Wrap(err, "error during exec CreateRunestone")
}
runestoneParams, err := mapRunestoneTypeToParamsBatch(txs)
if err != nil {
return errors.Wrap(err, "failed to map runestones to params")
}
if err := r.queries.BatchCreateRunestonesPatched(ctx, runestoneParams); err != nil {
return errors.Wrap(err, "error during exec BatchCreateRunestones")
}
return nil
}
func (r *Repository) CreateRuneEntry(ctx context.Context, entry *runes.RuneEntry, blockHeight uint64) error {
if entry == nil {
func (r *Repository) CreateRuneEntries(ctx context.Context, entries []*runes.RuneEntry) error {
if len(entries) == 0 {
return nil
}
createParams, _, err := mapRuneEntryTypeToParams(*entry, blockHeight)
params, err := mapRuneEntryTypeToParamsBatch(entries)
if err != nil {
return errors.Wrap(err, "failed to map rune entry to params")
return errors.Wrap(err, "failed to map rune entries to params")
}
if err = r.queries.CreateRuneEntry(ctx, createParams); err != nil {
return errors.Wrap(err, "error during exec CreateRuneEntry")
if err := r.queries.BatchCreateRuneEntriesPatched(ctx, params); err != nil {
return errors.Wrap(err, "error during exec")
}
return nil
}
func (r *Repository) CreateRuneEntryState(ctx context.Context, entry *runes.RuneEntry, blockHeight uint64) error {
if entry == nil {
func (r *Repository) CreateRuneEntryStates(ctx context.Context, entries []*runes.RuneEntry, blockHeight uint64) error {
if len(entries) == 0 {
return nil
}
_, createStateParams, err := mapRuneEntryTypeToParams(*entry, blockHeight)
params, err := mapRuneEntryStatesTypeToParamsBatch(entries, blockHeight)
if err != nil {
return errors.Wrap(err, "failed to map rune entry to params")
return errors.Wrap(err, "failed to map rune entry states to params")
}
if err = r.queries.CreateRuneEntryState(ctx, createStateParams); err != nil {
return errors.Wrap(err, "error during exec CreateRuneEntryState")
if err := r.queries.BatchCreateRuneEntryStatesPatched(ctx, params); err != nil {
return errors.Wrap(err, "error during exec")
}
return nil
}
func (r *Repository) CreateOutPointBalances(ctx context.Context, outPointBalances []*entity.OutPointBalance) error {
params := make([]gen.CreateOutPointBalancesParams, 0, len(outPointBalances))
for _, balance := range outPointBalances {
param, err := mapOutPointBalanceTypeToParams(*balance)
if err != nil {
return errors.Wrap(err, "failed to map outpoint balance to params")
}
params = append(params, param)
if len(outPointBalances) == 0 {
return nil
}
result := r.queries.CreateOutPointBalances(ctx, params)
var execErrors []error
result.Exec(func(i int, err error) {
if err != nil {
execErrors = append(execErrors, err)
}
})
if len(execErrors) > 0 {
return errors.Wrap(errors.Join(execErrors...), "error during exec")
}
return nil
}
func (r *Repository) SpendOutPointBalances(ctx context.Context, outPoint wire.OutPoint, blockHeight uint64) error {
if err := r.queries.SpendOutPointBalances(ctx, gen.SpendOutPointBalancesParams{
TxHash: outPoint.Hash.String(),
TxIdx: int32(outPoint.Index),
SpentHeight: pgtype.Int4{Int32: int32(blockHeight), Valid: true},
}); err != nil {
params, err := mapOutPointBalanceTypeToParamsBatch(outPointBalances)
if err != nil {
return errors.Wrap(err, "failed to map outpoint balances to params")
}
if err := r.queries.BatchCreateRunesOutpointBalancesPatched(ctx, params); err != nil {
return errors.Wrap(err, "error during exec")
}
return nil
}
func (r *Repository) CreateRuneBalances(ctx context.Context, params []datagateway.CreateRuneBalancesParams) error {
insertParams := make([]gen.CreateRuneBalanceAtBlockParams, 0, len(params))
for _, param := range params {
param := param
amount, err := numericFromUint128(&param.Balance)
if err != nil {
return errors.Wrap(err, "failed to convert balance to numeric")
}
insertParams = append(insertParams, gen.CreateRuneBalanceAtBlockParams{
Pkscript: hex.EncodeToString(param.PkScript),
BlockHeight: int32(param.BlockHeight),
RuneID: param.RuneId.String(),
Amount: amount,
})
func (r *Repository) SpendOutPointBalancesBatch(ctx context.Context, outPoints []wire.OutPoint, blockHeight uint64) error {
if len(outPoints) == 0 {
return nil
}
result := r.queries.CreateRuneBalanceAtBlock(ctx, insertParams)
var execErrors []error
result.Exec(func(i int, err error) {
if err != nil {
execErrors = append(execErrors, err)
}
})
if len(execErrors) > 0 {
return errors.Wrap(errors.Join(execErrors...), "error during exec")
params := gen.BatchSpendOutpointBalancesParams{
TxHashArr: make([]string, 0, len(outPoints)),
TxIdxArr: make([]int32, 0, len(outPoints)),
SpentHeight: int32(blockHeight),
}
for _, outPoint := range outPoints {
params.TxHashArr = append(params.TxHashArr, outPoint.Hash.String())
params.TxIdxArr = append(params.TxIdxArr, int32(outPoint.Index))
}
if err := r.queries.BatchSpendOutpointBalances(ctx, params); err != nil {
return errors.Wrap(err, "error during exec")
}
return nil
}
func (r *Repository) CreateRuneBalances(ctx context.Context, balances []*entity.Balance) error {
if len(balances) == 0 {
return nil
}
params, err := mapBalanceTypeToParamsBatch(balances)
if err != nil {
return errors.Wrap(err, "failed to map rune balances to params")
}
if err := r.queries.BatchCreateRunesBalances(ctx, params); err != nil {
return errors.Wrap(err, "error during exec")
}
return nil
}

View File

@@ -67,10 +67,11 @@ func New(injector do.Injector) (indexer.IndexerWorker, error) {
}
processor := NewProcessor(runesDg, indexerInfoDg, bitcoinClient, conf.Network, reportingClient, cleanupFuncs)
if err := processor.VerifyStates(ctx); err != nil {
return nil, errors.WithStack(err)
if !conf.APIOnly {
if err := processor.VerifyStates(ctx); err != nil {
return nil, errors.WithStack(err)
}
}
// Mount API
apiHandlers := lo.Uniq(conf.Modules.Runes.APIHandlers)
for _, handler := range apiHandlers {

View File

@@ -33,3 +33,13 @@ func (u *Usecase) GetTotalHoldersByRuneIds(ctx context.Context, runeIds []runes.
}
return holders, nil
}
func (u *Usecase) GetTotalHoldersByRuneId(ctx context.Context, runeId runes.RuneId, blockHeight uint64) (int64, error) {
holders, err := u.runesDg.GetTotalHoldersByRuneIds(ctx, []runes.RuneId{runeId}, blockHeight)
if err != nil {
return 0, errors.Wrap(err, "failed to get total holders by rune ids")
}
// defaults to zero holders if not found
return holders[runeId], nil
}

View File

@@ -40,11 +40,11 @@ func (u *Usecase) GetRuneEntryByRuneIdAndHeight(ctx context.Context, runeId rune
}
func (u *Usecase) GetRuneEntryByRuneIdAndHeightBatch(ctx context.Context, runeIds []runes.RuneId, blockHeight uint64) (map[runes.RuneId]*runes.RuneEntry, error) {
runeEntry, err := u.runesDg.GetRuneEntryByRuneIdAndHeightBatch(ctx, runeIds, blockHeight)
runeEntries, err := u.runesDg.GetRuneEntryByRuneIdAndHeightBatch(ctx, runeIds, blockHeight)
if err != nil {
return nil, errors.Wrap(err, "failed to get rune entries by rune ids and height")
}
return runeEntry, nil
return runeEntries, nil
}
func (u *Usecase) GetRuneEntries(ctx context.Context, search string, blockHeight uint64, limit, offset int32) ([]*runes.RuneEntry, error) {

191
pkg/bip322/bip322.go Normal file
View File

@@ -0,0 +1,191 @@
package bip322
// This package is forked from https://github.com/unisat-wallet/libbrc20-indexer/blob/v1.1.0/utils/bip322/verify.go,
// with a few modifications to make the interface more friendly with Gaze types.
import (
"crypto/sha256"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/pkg/btcutils"
)
func GetSha256(data []byte) (hash []byte) {
sha := sha256.New()
sha.Write(data[:])
hash = sha.Sum(nil)
return
}
func GetTagSha256(data []byte) (hash []byte) {
tag := []byte("BIP0322-signed-message")
hashTag := GetSha256(tag)
var msg []byte
msg = append(msg, hashTag...)
msg = append(msg, hashTag...)
msg = append(msg, data...)
return GetSha256(msg)
}
func PrepareTx(pkScript []byte, message string) (toSign *wire.MsgTx, err error) {
// Create a new transaction to spend
toSpend := wire.NewMsgTx(0)
// Decode the message hash
messageHash := GetTagSha256([]byte(message))
// Create the script for to_spend
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_0)
builder.AddData(messageHash)
scriptSig, err := builder.Script()
if err != nil {
return nil, errors.WithStack(err)
}
// Create a TxIn with the outpoint 000...000:FFFFFFFF
prevOutHash, _ := chainhash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000")
prevOut := wire.NewOutPoint(prevOutHash, wire.MaxPrevOutIndex)
txIn := wire.NewTxIn(prevOut, scriptSig, nil)
txIn.Sequence = 0
toSpend.AddTxIn(txIn)
toSpend.AddTxOut(wire.NewTxOut(0, pkScript))
// Create a transaction for to_sign
toSign = wire.NewMsgTx(0)
hash := toSpend.TxHash()
prevOutSpend := wire.NewOutPoint((*chainhash.Hash)(hash.CloneBytes()), 0)
txSignIn := wire.NewTxIn(prevOutSpend, nil, nil)
txSignIn.Sequence = 0
toSign.AddTxIn(txSignIn)
// Create the script for to_sign
builderPk := txscript.NewScriptBuilder()
builderPk.AddOp(txscript.OP_RETURN)
scriptPk, err := builderPk.Script()
if err != nil {
return nil, errors.WithStack(err)
}
toSign.AddTxOut(wire.NewTxOut(0, scriptPk))
return toSign, nil
}
func VerifyMessage(address *btcutils.Address, signature []byte, message string) bool {
if len(signature) == 0 {
// empty signature is invalid
return false
}
// BIP322 signature format is the serialized witness of the toSign transaction.
// [0x02] [SIGNATURE_LEN, ...(signature that go into witness[0])] [PUBLIC_KEY_LEN, ...(public key that was used to sign the message, go to witness[1])]
witness, err := DeserializeWitnessSignature(signature)
if err != nil {
// invalid signature
return false
}
return verifySignatureWitness(witness, address.ScriptPubKey(), message)
}
// verifySignatureWitness
// signature: 64B, pkScript: 33B, message: any
func verifySignatureWitness(witness wire.TxWitness, pkScript []byte, message string) bool {
toSign, err := PrepareTx(pkScript, message)
if err != nil {
return false
}
toSign.TxIn[0].Witness = witness
prevFetcher := txscript.NewCannedPrevOutputFetcher(
pkScript, 0,
)
hashCache := txscript.NewTxSigHashes(toSign, prevFetcher)
vm, err := txscript.NewEngine(pkScript, toSign, 0, txscript.StandardVerifyFlags, nil, hashCache, 0, prevFetcher)
if err != nil {
return false
}
if err := vm.Execute(); err != nil {
return false
}
return true
}
func SignMessage(privateKey *btcec.PrivateKey, address *btcutils.Address, message string) ([]byte, error) {
var witness wire.TxWitness
var err error
switch address.Type() {
case btcutils.AddressP2TR:
witness, _, err = SignSignatureTaproot(privateKey, message)
case btcutils.AddressP2WPKH:
witness, _, err = SignSignatureP2WPKH(privateKey, message)
}
if err != nil {
return nil, errors.WithStack(err)
}
signature, err := SerializeWitnessSignature(witness)
if err != nil {
return nil, errors.WithStack(err)
}
return signature, nil
}
func SignSignatureTaproot(privKey *btcec.PrivateKey, message string) (witness wire.TxWitness, pkScript []byte, err error) {
pubKey := txscript.ComputeTaprootKeyNoScript(privKey.PubKey())
pkScript, err = PayToTaprootScript(pubKey)
if err != nil {
return nil, nil, errors.WithStack(err)
}
toSign, err := PrepareTx(pkScript, message)
if err != nil {
return nil, nil, errors.WithStack(err)
}
prevFetcher := txscript.NewCannedPrevOutputFetcher(
pkScript, 0,
)
sigHashes := txscript.NewTxSigHashes(toSign, prevFetcher)
witness, err = txscript.TaprootWitnessSignature(
toSign, sigHashes, 0, 0, pkScript,
txscript.SigHashDefault, privKey,
)
if err != nil {
return nil, nil, errors.WithStack(err)
}
return witness, pkScript, nil
}
func SignSignatureP2WPKH(privKey *btcec.PrivateKey, message string) (witness wire.TxWitness, pkScript []byte, err error) {
pubKey := privKey.PubKey()
pkScript, err = PayToWitnessScript(pubKey)
if err != nil {
return nil, nil, errors.WithStack(err)
}
toSign, err := PrepareTx(pkScript, message)
if err != nil {
return nil, nil, errors.WithStack(err)
}
prevFetcher := txscript.NewCannedPrevOutputFetcher(
pkScript, 0,
)
sigHashes := txscript.NewTxSigHashes(toSign, prevFetcher)
witness, err = txscript.WitnessSignature(toSign, sigHashes,
0, 0, pkScript, txscript.SigHashAll,
privKey, true)
if err != nil {
return nil, nil, errors.WithStack(err)
}
return witness, pkScript, nil
}

145
pkg/bip322/bip322_test.go Normal file
View File

@@ -0,0 +1,145 @@
package bip322
import (
"encoding/base64"
"fmt"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/gaze-network/indexer-network/pkg/btcutils"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestVerifyMessage(t *testing.T) {
type testcase struct {
Address string
Message string
Signature string // base64
Expected bool
}
testcases := []testcase{
{
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
Message: "",
Signature: "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
Expected: true,
},
{
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
Message: "",
Signature: "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy",
Expected: true,
},
{
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
Message: "Hello World",
Signature: "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=",
Expected: true,
},
{
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
Message: "Hello World",
Signature: "AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy",
Expected: true,
},
{
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
Message: "",
Signature: "INVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVA",
Expected: false,
},
{
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
Message: "",
Signature: "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDLXXXX",
Expected: false,
},
{
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
Message: "Hello World",
Signature: "BkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDLXXXX",
Expected: false,
},
{
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
Message: "",
Signature: "AUDVvVp7mCtPZtoORKYcMM+idx9yy5+z4TGeoI/PWEUscd5x0QYJ6IPQ/anBSMWPWSRPqHVrEjOIWhP9FsZSMFdG",
Expected: true,
},
{
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
Message: "",
Signature: "AUDYeG/k6AL9pNuhgK8aJqxIqBIObX867yc3QgdfS70sWEdUg0Msv0Ps24Pt5aQmcI2wZdwI3Egp5tA5PW+wTOw6",
Expected: true,
},
{
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
Message: "Hello World",
Signature: "AUCkOlzIYSN6T+QzENjlp61Pa2l4EyDDH8c4pFANOwoh3oGi/iZHscAExUSePhbS94KIMgcg+yNp+LsckO+AfLQQ",
Expected: true,
},
{
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
Message: "Hello World",
Signature: "AUD5MwxtURP3tAip3fS5vVRwa4L15wEyTIG0BQ3DPktJpXvQe7Sh8kf+mVaO4ldEP+vhiVZ/sXvOHEbQQnsiYpCq",
Expected: true,
},
}
for _, tc := range testcases {
t.Run(fmt.Sprintf("%s_%s", tc.Address, tc.Message), func(t *testing.T) {
address, err := btcutils.SafeNewAddress(tc.Address)
require.NoError(t, err)
signature, err := base64.StdEncoding.DecodeString(tc.Signature)
require.NoError(t, err)
verified := VerifyMessage(&address, signature, tc.Message)
assert.Equal(t, tc.Expected, verified)
})
}
}
func TestSignMessage(t *testing.T) {
type testcase struct {
PrivateKey *btcec.PrivateKey
Address string
Message string
}
testcases := []testcase{
{
PrivateKey: lo.Must(btcutil.DecodeWIF("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k")).PrivKey,
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
Message: "",
},
{
PrivateKey: lo.Must(btcutil.DecodeWIF("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k")).PrivKey,
Address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l",
Message: "Hello World",
},
{
PrivateKey: lo.Must(btcutil.DecodeWIF("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k")).PrivKey,
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
Message: "",
},
{
PrivateKey: lo.Must(btcutil.DecodeWIF("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k")).PrivKey,
Address: "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
Message: "Hello World",
},
}
for _, tc := range testcases {
t.Run(fmt.Sprintf("%s_%s", tc.Address, tc.Message), func(t *testing.T) {
address, err := btcutils.SafeNewAddress(tc.Address)
require.NoError(t, err)
signature, err := SignMessage(tc.PrivateKey, &address, tc.Message)
require.NoError(t, err)
verified := VerifyMessage(&address, signature, tc.Message)
assert.True(t, verified)
})
}
}

77
pkg/bip322/bip322_util.go Normal file
View File

@@ -0,0 +1,77 @@
package bip322
import (
"bytes"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common/errs"
)
func SerializeWitnessSignature(witness wire.TxWitness) ([]byte, error) {
result := new(bytes.Buffer)
buf := make([]byte, 8)
if err := wire.WriteVarIntBuf(result, 0, uint64(len(witness)), buf); err != nil {
return nil, errors.WithStack(err)
}
for _, item := range witness {
if err := wire.WriteVarBytesBuf(result, 0, item, buf); err != nil {
return nil, errors.WithStack(err)
}
}
return result.Bytes(), nil
}
func DeserializeWitnessSignature(serialized []byte) (wire.TxWitness, error) {
if len(serialized) == 0 {
return nil, errors.Wrap(errs.ArgumentRequired, "serialized witness is required")
}
witness := make(wire.TxWitness, 0)
current := 0
witnessLen := int(serialized[current])
current++
for i := 0; i < witnessLen; i++ {
if current >= len(serialized) {
return nil, errors.Wrap(errs.InvalidArgument, "invalid serialized witness data: not enough bytes")
}
witnessItemLen := int(serialized[current])
current++
if current+witnessItemLen > len(serialized) {
return nil, errors.Wrap(errs.InvalidArgument, "invalid serialized witness data: not enough bytes")
}
witnessItem := serialized[current : current+witnessItemLen]
current += witnessItemLen
witness = append(witness, witnessItem)
}
return witness, nil
}
// PayToTaprootScript creates a pk script for a pay-to-taproot output key.
func PayToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) {
script, err := txscript.NewScriptBuilder().
AddOp(txscript.OP_1).
AddData(schnorr.SerializePubKey(taprootKey)).
Script()
if err != nil {
return nil, errors.WithStack(err)
}
return script, nil
}
// PayToWitnessScript creates a pk script for a pay-to-wpkh output key.
func PayToWitnessScript(pubkey *btcec.PublicKey) ([]byte, error) {
script, err := txscript.NewScriptBuilder().
AddOp(txscript.OP_0).
AddData(btcutil.Hash160(pubkey.SerializeCompressed())).
Script()
if err != nil {
return nil, errors.WithStack(err)
}
return script, nil
}