mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-04-30 04:35:13 +08:00
feat: implement get transactions by pkscript
This commit is contained in:
@@ -3,6 +3,7 @@ package httphandler
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"slices"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/cockroachdb/errors"
|
||||
@@ -84,6 +85,7 @@ type amountWithDecimal struct {
|
||||
type transaction struct {
|
||||
TxHash chainhash.Hash `json:"txHash"`
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
Index uint32 `json:"index"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Inputs []txInputOutput `json:"inputs"`
|
||||
Outputs []txInputOutput `json:"outputs"`
|
||||
@@ -116,15 +118,6 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
blockHeight := req.BlockHeight
|
||||
if blockHeight == 0 {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
var runeId runes.RuneId
|
||||
if req.Id != "" {
|
||||
var ok bool
|
||||
@@ -134,9 +127,29 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
txs, err := h.usecase.GetTransactionsByHeight(ctx.UserContext(), blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetTransactionsByHeight")
|
||||
var txs []*entity.RuneTransaction
|
||||
if pkScript != nil {
|
||||
var blockHeight *uint64
|
||||
if req.BlockHeight > 0 {
|
||||
blockHeight = &req.BlockHeight
|
||||
}
|
||||
txs, err = h.usecase.GetTransactionsByPkScript(ctx.UserContext(), pkScript, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetTransactionsByPkScript")
|
||||
}
|
||||
} else {
|
||||
blockHeight := req.BlockHeight
|
||||
if blockHeight == 0 {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
txs, err = h.usecase.GetTransactionsByHeight(ctx.UserContext(), blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetTransactionsByHeight")
|
||||
}
|
||||
}
|
||||
|
||||
filteredTxs := make([]*entity.RuneTransaction, 0)
|
||||
@@ -220,6 +233,7 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
respTx := transaction{
|
||||
TxHash: tx.Hash,
|
||||
BlockHeight: tx.BlockHeight,
|
||||
Index: tx.Index,
|
||||
Timestamp: tx.Timestamp.Unix(),
|
||||
Inputs: make([]txInputOutput, 0, len(tx.Inputs)),
|
||||
Outputs: make([]txInputOutput, 0, len(tx.Outputs)),
|
||||
@@ -309,6 +323,13 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
}
|
||||
txList = append(txList, respTx)
|
||||
}
|
||||
// sort by block height ASC, then index ASC
|
||||
slices.SortFunc(txList, func(t1, t2 transaction) int {
|
||||
if t1.BlockHeight != t2.BlockHeight {
|
||||
return int(t1.BlockHeight - t2.BlockHeight)
|
||||
}
|
||||
return int(t1.Index - t2.Index)
|
||||
})
|
||||
|
||||
resp := getTransactionsResponse{
|
||||
Result: &getTransactionsResult{
|
||||
|
||||
@@ -72,6 +72,8 @@ CREATE TABLE IF NOT EXISTS "runes_transactions" (
|
||||
"rune_etched" BOOLEAN NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS runes_transactions_block_height_idx ON "runes_transactions" USING BTREE ("block_height");
|
||||
CREATE INDEX IF NOT EXISTS runes_transactions_output_idx ON "runes_transactions" USING GIN ("outputs");
|
||||
CREATE INDEX IF NOT EXISTS runes_transactions_input_idx ON "runes_transactions" USING GIN ("inputs");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "runes_runestones" (
|
||||
"tx_hash" TEXT NOT NULL PRIMARY KEY,
|
||||
|
||||
@@ -45,6 +45,13 @@ SELECT * FROM runes_transactions
|
||||
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
|
||||
WHERE runes_transactions.block_height = $1;
|
||||
|
||||
-- name: GetRuneTransactionsByPkScript :many
|
||||
SELECT * FROM runes_transactions
|
||||
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
|
||||
WHERE (
|
||||
runes_transactions.outputs @> @pk_script_param::JSONB OR runes_transactions.inputs @> @pk_script_param::JSONB
|
||||
) AND (@block_height::INT = 0 OR runes_transactions.block_height = @block_height::INT); -- optionally filter by block height if block height > 0
|
||||
|
||||
-- name: CountRuneEntries :one
|
||||
SELECT COUNT(*) FROM runes_entries;
|
||||
|
||||
|
||||
@@ -26,7 +26,10 @@ type RunesDataGatewayWithTx interface {
|
||||
type RunesReaderDataGateway interface {
|
||||
GetLatestBlock(ctx context.Context) (types.BlockHeader, error)
|
||||
GetIndexedBlockByHeight(ctx context.Context, height int64) (*entity.IndexedBlock, error)
|
||||
// GetRuneTransactionsByHeight returns the transactions for the given height.
|
||||
GetRuneTransactionsByHeight(ctx context.Context, height uint64) ([]*entity.RuneTransaction, error)
|
||||
// GetRuneTransactionsByPkScript returns the transactions for the given pkScript. If height is not nil, filter returned transactions by the given height.
|
||||
GetRuneTransactionsByPkScript(ctx context.Context, pkScript []byte, height *uint64) ([]*entity.RuneTransaction, error)
|
||||
|
||||
GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wire.OutPoint) (map[runes.RuneId]*entity.OutPointBalance, error)
|
||||
GetUnspentOutPointBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) ([]*entity.OutPointBalance, error)
|
||||
|
||||
@@ -721,6 +721,104 @@ func (q *Queries) GetRuneTransactionsByHeight(ctx context.Context, blockHeight i
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getRuneTransactionsByPkScript = `-- name: GetRuneTransactionsByPkScript :many
|
||||
SELECT hash, runes_transactions.block_height, index, timestamp, inputs, outputs, mints, burns, rune_etched, tx_hash, runes_runestones.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 FROM runes_transactions
|
||||
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
|
||||
WHERE (
|
||||
runes_transactions.outputs @> $1::JSONB OR runes_transactions.inputs @> $1::JSONB
|
||||
) AND ($2::INT = 0 OR runes_transactions.block_height = $2::INT) -- optionally filter by block height if block height > 0
|
||||
ORDER BY runes_transactions.block_height ASC
|
||||
`
|
||||
|
||||
type GetRuneTransactionsByPkScriptParams struct {
|
||||
PkScriptParam []byte
|
||||
BlockHeight int32
|
||||
}
|
||||
|
||||
type GetRuneTransactionsByPkScriptRow struct {
|
||||
Hash string
|
||||
BlockHeight int32
|
||||
Index int32
|
||||
Timestamp pgtype.Timestamp
|
||||
Inputs []byte
|
||||
Outputs []byte
|
||||
Mints []byte
|
||||
Burns []byte
|
||||
RuneEtched bool
|
||||
TxHash pgtype.Text
|
||||
BlockHeight_2 pgtype.Int4
|
||||
Etching pgtype.Bool
|
||||
EtchingDivisibility pgtype.Int2
|
||||
EtchingPremine pgtype.Numeric
|
||||
EtchingRune pgtype.Text
|
||||
EtchingSpacers pgtype.Int4
|
||||
EtchingSymbol pgtype.Int4
|
||||
EtchingTerms pgtype.Bool
|
||||
EtchingTermsAmount pgtype.Numeric
|
||||
EtchingTermsCap pgtype.Numeric
|
||||
EtchingTermsHeightStart pgtype.Int4
|
||||
EtchingTermsHeightEnd pgtype.Int4
|
||||
EtchingTermsOffsetStart pgtype.Int4
|
||||
EtchingTermsOffsetEnd pgtype.Int4
|
||||
EtchingTurbo pgtype.Bool
|
||||
Edicts []byte
|
||||
Mint pgtype.Text
|
||||
Pointer pgtype.Int4
|
||||
Cenotaph pgtype.Bool
|
||||
Flaws pgtype.Int4
|
||||
}
|
||||
|
||||
func (q *Queries) GetRuneTransactionsByPkScript(ctx context.Context, arg GetRuneTransactionsByPkScriptParams) ([]GetRuneTransactionsByPkScriptRow, error) {
|
||||
rows, err := q.db.Query(ctx, getRuneTransactionsByPkScript, arg.PkScriptParam, arg.BlockHeight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetRuneTransactionsByPkScriptRow
|
||||
for rows.Next() {
|
||||
var i GetRuneTransactionsByPkScriptRow
|
||||
if err := rows.Scan(
|
||||
&i.Hash,
|
||||
&i.BlockHeight,
|
||||
&i.Index,
|
||||
&i.Timestamp,
|
||||
&i.Inputs,
|
||||
&i.Outputs,
|
||||
&i.Mints,
|
||||
&i.Burns,
|
||||
&i.RuneEtched,
|
||||
&i.TxHash,
|
||||
&i.BlockHeight_2,
|
||||
&i.Etching,
|
||||
&i.EtchingDivisibility,
|
||||
&i.EtchingPremine,
|
||||
&i.EtchingRune,
|
||||
&i.EtchingSpacers,
|
||||
&i.EtchingSymbol,
|
||||
&i.EtchingTerms,
|
||||
&i.EtchingTermsAmount,
|
||||
&i.EtchingTermsCap,
|
||||
&i.EtchingTermsHeightStart,
|
||||
&i.EtchingTermsHeightEnd,
|
||||
&i.EtchingTermsOffsetStart,
|
||||
&i.EtchingTermsOffsetEnd,
|
||||
&i.EtchingTurbo,
|
||||
&i.Edicts,
|
||||
&i.Mint,
|
||||
&i.Pointer,
|
||||
&i.Cenotaph,
|
||||
&i.Flaws,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getUnspentOutPointBalancesByPkScript = `-- name: GetUnspentOutPointBalancesByPkScript :many
|
||||
SELECT rune_id, pkscript, tx_hash, tx_idx, amount, block_height, spent_height FROM runes_outpoint_balances WHERE pkscript = $1 AND block_height <= $2 AND (spent_height IS NULL OR spent_height > $2)
|
||||
`
|
||||
|
||||
@@ -3,6 +3,7 @@ package postgres
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
@@ -90,6 +91,39 @@ func (r *Repository) GetRuneTransactionsByHeight(ctx context.Context, height uin
|
||||
return runeTxs, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetRuneTransactionsByPkScript(ctx context.Context, pkScript []byte, height *uint64) ([]*entity.RuneTransaction, error) {
|
||||
pkScriptParam := []byte(fmt.Sprintf(`[{"pkScript":"%s"}]`, hex.EncodeToString(pkScript)))
|
||||
rows, err := r.queries.GetRuneTransactionsByPkScript(ctx, gen.GetRuneTransactionsByPkScriptParams{
|
||||
PkScriptParam: pkScriptParam,
|
||||
BlockHeight: int32(lo.FromPtr(height)),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
}
|
||||
|
||||
runeTxs := make([]*entity.RuneTransaction, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
runeTxModel, runestoneModel, err := extractModelRuneTxAndRunestone(gen.GetRuneTransactionsByHeightRow(row))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to extract rune transaction and runestone from row")
|
||||
}
|
||||
|
||||
runeTx, err := mapRuneTransactionModelToType(runeTxModel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse rune transaction model")
|
||||
}
|
||||
if runestoneModel != nil {
|
||||
runestone, err := mapRunestoneModelToType(*runestoneModel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse runestone model")
|
||||
}
|
||||
runeTx.Runestone = &runestone
|
||||
}
|
||||
runeTxs = append(runeTxs, &runeTx)
|
||||
}
|
||||
return runeTxs, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wire.OutPoint) (map[runes.RuneId]*entity.OutPointBalance, error) {
|
||||
balances, err := r.queries.GetOutPointBalancesAtOutPoint(ctx, gen.GetOutPointBalancesAtOutPointParams{
|
||||
TxHash: outPoint.Hash.String(),
|
||||
|
||||
@@ -14,3 +14,11 @@ func (u *Usecase) GetTransactionsByHeight(ctx context.Context, height uint64) ([
|
||||
}
|
||||
return txs, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetTransactionsByPkScript(ctx context.Context, pkScript []byte, height *uint64) ([]*entity.RuneTransaction, error) {
|
||||
txs, err := u.runesDg.GetRuneTransactionsByPkScript(ctx, pkScript, height)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetTransactionsByHeight")
|
||||
}
|
||||
return txs, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user