feat: implement get transactions by pkscript

This commit is contained in:
Gaze
2024-04-23 15:06:04 +07:00
parent 185d26c651
commit 5276136718
7 changed files with 185 additions and 12 deletions

View File

@@ -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{

View File

@@ -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,

View File

@@ -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;

View File

@@ -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)

View File

@@ -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)
`

View File

@@ -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(),

View File

@@ -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
}