mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-01-12 22:43:22 +08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58f8497997 | ||
|
|
920f7fe07b | ||
|
|
0cb66232ef | ||
|
|
4074548b3e | ||
|
|
c5c9a7bdeb | ||
|
|
58334dd3e4 | ||
|
|
cffe378beb | ||
|
|
9a7ee49228 | ||
|
|
9739f61067 | ||
|
|
f1267b387e | ||
|
|
8883c24c77 | ||
|
|
e9ce8df01a | ||
|
|
3ff73a99f8 | ||
|
|
96afdfd255 | ||
|
|
c49e39be97 | ||
|
|
12985ae432 | ||
|
|
2d51e52b83 |
@@ -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
2
go.sum
@@ -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=
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
118
modules/runes/api/httphandler/get_token_info_batch.go
Normal file
118
modules/runes/api/httphandler/get_token_info_batch.go
Normal 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))
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" (
|
||||
|
||||
104
modules/runes/database/postgresql/queries/batch.sql
Normal file
104
modules/runes/database/postgresql/queries/batch.sql
Normal 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[])
|
||||
);
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
319
modules/runes/repository/postgres/gen/batch.sql.go
Normal file
319
modules/runes/repository/postgres/gen/batch.sql.go
Normal 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
|
||||
}
|
||||
118
modules/runes/repository/postgres/gen/batch.sql.patch.go
Normal file
118
modules/runes/repository/postgres/gen/batch.sql.patch.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = ¶ms
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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(¶m.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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
191
pkg/bip322/bip322.go
Normal 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
145
pkg/bip322/bip322_test.go
Normal 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
77
pkg/bip322/bip322_util.go
Normal 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
|
||||
}
|
||||
@@ -14,11 +14,10 @@ import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
var defaultClient = fasthttp.Client{
|
||||
MaxConnsPerHost: 10240, // default is 512
|
||||
MaxConnWaitTimeout: 5 * time.Second, // default is no wating
|
||||
ReadBufferSize: 4 * 1024,
|
||||
WriteBufferSize: 4 * 1024,
|
||||
var DefaultClient = fasthttp.Client{
|
||||
MaxConnsPerHost: 10240, // default is 512
|
||||
ReadBufferSize: 4 * 1024,
|
||||
WriteBufferSize: 4 * 1024,
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -150,7 +149,7 @@ func (h *Client) request(ctx context.Context, reqOptions RequestOptions) (*HttpR
|
||||
fasthttp.ReleaseRequest(req)
|
||||
}()
|
||||
|
||||
if err := defaultClient.Do(req, resp); err != nil {
|
||||
if err := DefaultClient.Do(req, resp); err != nil {
|
||||
return nil, errors.Wrapf(err, "url: %s", url)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user