feat: implement more tables

This commit is contained in:
Gaze
2024-04-13 01:27:21 +07:00
parent f72c9da8c0
commit 0f1ec2a21e
19 changed files with 943 additions and 160 deletions

2
go.mod
View File

@@ -9,7 +9,7 @@ require (
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/cockroachdb/errors v1.11.1
github.com/gaze-network/uint128 v1.2.0
github.com/gaze-network/uint128 v1.3.0
github.com/gofiber/fiber/v2 v2.52.4
github.com/jackc/pgx/v5 v5.5.5
github.com/mcosta74/pgx-slog v0.3.0

4
go.sum
View File

@@ -51,8 +51,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gaze-network/uint128 v1.2.0 h1:LRruR+EvzNv/eJg8nk0hztMJ4tOpYKolFYlQZqNvWmo=
github.com/gaze-network/uint128 v1.2.0/go.mod h1:zAwwcnoRUNiiQj0vjLmHgNgJ+w2RUgzMAJgl8d7tRug=
github.com/gaze-network/uint128 v1.3.0 h1:25qtRiDKQXa+mD5rN0nbUkbvY26/uzfSF97eWvhIr0I=
github.com/gaze-network/uint128 v1.3.0/go.mod h1:zAwwcnoRUNiiQj0vjLmHgNgJ+w2RUgzMAJgl8d7tRug=
github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0=
github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=

View File

@@ -2,9 +2,13 @@ BEGIN;
DROP TABLE IF EXISTS "runes_indexer_stats";
DROP TABLE IF EXISTS "runes_indexer_db_version";
DROP TABLE IF EXISTS "runes_processor_state";
DROP TABLE IF EXISTS "runes_indexed_blocks";
DROP TABLE IF EXISTS "runes_entries";
DROP TABLE IF EXISTS "runes_entry_states";
DROP TABLE IF EXISTS "runes_transactions";
DROP TABLE IF EXISTS "runes_runestones";
DROP TABLE IF EXISTS "runes_outpoint_balances";
DROP TABLE IF EXISTS "runes_balances";
DROP TABLE IF EXISTS "runes_balances";
COMMIT;

View File

@@ -39,8 +39,6 @@ CREATE TABLE IF NOT EXISTS "runes_entries" (
"rune_id" TEXT NOT NULL PRIMARY KEY,
"rune" TEXT NOT NULL,
"spacers" INT NOT NULL,
"burned_amount" DECIMAL NOT NULL,
"mints" DECIMAL NOT NULL,
"premine" DECIMAL NOT NULL,
"symbol" INT NOT NULL,
"divisibility" SMALLINT NOT NULL,
@@ -51,10 +49,52 @@ CREATE TABLE IF NOT EXISTS "runes_entries" (
"terms_height_end" INT,
"terms_offset_start" INT,
"terms_offset_end" INT,
"completion_time" TIMESTAMP NOT NULL
"created_at_block" INT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS runes_entries_rune_idx ON "runes_entries" USING BTREE ("rune");
CREATE TABLE IF NOT EXISTS "runes_entry_states" (
"rune_id" TEXT NOT NULL,
"block_height" INT NOT NULL,
"mints" DECIMAL NOT NULL,
"burned_amount" DECIMAL NOT NULL,
"completion_time" TIMESTAMP,
PRIMARY KEY ("rune_id", "block_height")
);
CREATE TABLE IF NOT EXISTS "runes_transactions" (
"hash" TEXT NOT NULL PRIMARY KEY,
"block_height" INT NOT NULL,
"timestamp" TIMESTAMP NOT NULL,
"inputs" JSONB NOT NULL,
"outputs" JSONB NOT NULL,
"mints" JSONB NOT NULL,
"burns" JSONB NOT NULL
);
CREATE TABLE IF NOT EXISTS "runes_runestones" (
"tx_hash" TEXT NOT NULL PRIMARY KEY,
"block_height" INT NOT NULL,
"etching" BOOLEAN NOT NULL,
"etching_divisibility" SMALLINT,
"etching_premine" DECIMAL,
"etching_rune" TEXT,
"etching_spacers" INT,
"etching_symbol" INT,
"etching_terms" BOOLEAN NOT NULL,
"etching_terms_amount" DECIMAL,
"etching_terms_cap" DECIMAL,
"etching_terms_height_start" INT,
"etching_terms_height_end" INT,
"etching_terms_offset_start" INT,
"etching_terms_offset_end" INT,
"edicts" JSONB NOT NULL DEFAULT '[]',
"mint" TEXT,
"pointer" INT,
"cenotaph" BOOLEAN NOT NULL,
"flaws" INT NOT NULL
);
CREATE TABLE IF NOT EXISTS "runes_outpoint_balances" (
"rune_id" TEXT NOT NULL,
"tx_hash" TEXT NOT NULL,

View File

@@ -7,18 +7,36 @@ SELECT DISTINCT ON (pkscript) * FROM runes_balances WHERE rune_id = $1 AND block
-- name: GetOutPointBalances :many
SELECT * FROM runes_outpoint_balances WHERE tx_hash = $1 AND tx_idx = $2;
-- using FOR UPDATE to prevent other connections for updating the same rune entries if they are being accessed by Processor
-- name: GetRuneEntriesByRuneIds :many
SELECT * FROM runes_entries WHERE rune_id = ANY(@rune_ids::text[]) FOR UPDATE;
WITH states AS (
-- select latest state
SELECT DISTINCT ON (rune_id) * FROM runes_entry_states WHERE rune_id = ANY(@rune_ids::text[]) ORDER BY rune_id, block_height DESC
)
SELECT * FROM runes_entries
LEFT JOIN states ON runes_entries.rune_id = states.rune_id
WHERE rune_id = ANY(@rune_ids::text[]);
-- using FOR UPDATE to prevent other connections for updating the same rune entries if they are being accessed by Processor
-- name: GetRuneEntriesByRunes :many
SELECT * FROM runes_entries WHERE rune = ANY(@runes::text[]) FOR UPDATE;
-- name: GetRuneIdFromRune :one
SELECT rune_id FROM runes_entries WHERE rune = $1;
-- name: SetRuneEntry :exec
INSERT INTO runes_entries (rune_id, rune, spacers, burned_amount, mints, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, completion_time)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
ON CONFLICT (rune_id) DO UPDATE SET (burned_amount, mints, completion_time) = (excluded.burned_amount, excluded.mints, excluded.completion_time);
-- name: GetRuneTransactionsByHeight :many
SELECT * FROM runes_transactions
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
WHERE runes_transactions.block_height = $1;
-- name: CreateRuneEntry :exec
INSERT INTO runes_entries (rune_id, rune, spacers, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);
-- name: CreateRuneEntryState :exec
INSERT INTO runes_entry_states (rune_id, block_height, mints, burned_amount, completion_time) VALUES ($1, $2, $3, $4, $5);
-- name: CreateRuneTransaction :exec
INSERT INTO runes_transactions (hash, block_height, timestamp, inputs, outputs, mints, burns) VALUES ($1, $2, $3, $4, $5, $6, $7);
-- name: CreateRunestone :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, 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);
-- name: CreateRuneBalancesAtOutPoint :batchexec
INSERT INTO runes_outpoint_balances (rune_id, tx_hash, tx_idx, amount) VALUES ($1, $2, $3, $4);

View File

@@ -76,11 +76,11 @@ func (h *HttpHandler) resolveRuneId(ctx context.Context, id string) (runes.RuneI
// attempt to parse as rune
rune, err := runes.NewRuneFromString(id)
if err == nil {
runeEntry, err := h.usecase.GetRuneEntryByRune(ctx, rune)
runeId, err := h.usecase.GetRuneIdFromRune(ctx, rune)
if err != nil {
return runes.RuneId{}, false
}
return runeEntry.RuneId, true
return runeId, true
}
return runes.RuneId{}, false

View File

@@ -19,10 +19,11 @@ type RunesDataGateway interface {
type RunesReaderDataGateway interface {
GetLatestBlock(ctx context.Context) (types.BlockHeader, error)
GetIndexedBlockByHeight(ctx context.Context, height int64) (*entity.IndexedBlock, error)
GetRuneTransactionsByHeight(ctx context.Context, height uint64) ([]*entity.RuneTransaction, error)
GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wire.OutPoint) (map[runes.RuneId]uint128.Uint128, error)
// GetRuneEntryByRune returns the RuneEntry for the given rune. Returns errs.NotFound if the rune entry is not found.
GetRuneEntryByRune(ctx context.Context, rune runes.Rune) (*runes.RuneEntry, error)
// GetRuneIdFromRune returns the RuneId for the given rune. Returns errs.NotFound if the rune entry is not found.
GetRuneIdFromRune(ctx context.Context, rune runes.Rune) (runes.RuneId, error)
// GetRuneEntryByRuneId returns the RuneEntry for the given runeId. Returns errs.NotFound if the rune entry is not found.
GetRuneEntryByRuneId(ctx context.Context, runeId runes.RuneId) (*runes.RuneEntry, error)
// GetRuneEntryByRuneId returns the RuneEntry for the given runeId. Returns errs.NotFound if the rune entry is not found.
@@ -45,11 +46,13 @@ type RunesWriterDataGateway interface {
// Rollback() must be safe to call even if no transaction is active. Hence, a defer Rollback() is safe, even if Commit() was called prior with non-error conditions.
Rollback(ctx context.Context) error
SetRuneEntry(ctx context.Context, entry *runes.RuneEntry) error
CreateRuneEntry(ctx context.Context, entry *runes.RuneEntry) error
CreateRuneBalancesAtOutPoint(ctx context.Context, outPoint wire.OutPoint, balances map[runes.RuneId]uint128.Uint128) error
CreateRuneBalancesAtBlock(ctx context.Context, params []CreateRuneBalancesAtBlockParams) error
UpdateLatestBlock(ctx context.Context, blockHeader types.BlockHeader) error
CreateRuneTransaction(ctx context.Context, tx *entity.RuneTransaction) error
CreateIndexedBlock(ctx context.Context, block *entity.IndexedBlock) error
DeleteIndexedBlockByHash(ctx context.Context, hash chainhash.Hash) error
}

View File

@@ -0,0 +1,27 @@
package entity
import (
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/gaze-network/indexer-network/modules/runes/internal/runes"
"github.com/gaze-network/uint128"
)
type OutPointBalance struct {
PkScript []byte
Id runes.RuneId
Value uint128.Uint128
Index uint32
}
type RuneTransaction struct {
Hash chainhash.Hash
BlockHeight uint64
Timestamp time.Time
Inputs []*OutPointBalance
Outputs []*OutPointBalance
Mints []*OutPointBalance
Burns map[runes.RuneId]uint128.Uint128
Runestone *runes.Runestone
}

View File

@@ -32,6 +32,150 @@ func (q *Queries) CreateIndexedBlock(ctx context.Context, arg CreateIndexedBlock
return err
}
const createRuneEntry = `-- name: CreateRuneEntry :exec
INSERT INTO runes_entries (rune_id, rune, spacers, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
`
type CreateRuneEntryParams struct {
RuneID string
Rune string
Spacers int32
Premine pgtype.Numeric
Symbol int32
Divisibility int16
Terms bool
TermsAmount pgtype.Numeric
TermsCap pgtype.Numeric
TermsHeightStart pgtype.Int4
TermsHeightEnd pgtype.Int4
TermsOffsetStart pgtype.Int4
TermsOffsetEnd pgtype.Int4
}
func (q *Queries) CreateRuneEntry(ctx context.Context, arg CreateRuneEntryParams) error {
_, err := q.db.Exec(ctx, createRuneEntry,
arg.RuneID,
arg.Rune,
arg.Spacers,
arg.Premine,
arg.Symbol,
arg.Divisibility,
arg.Terms,
arg.TermsAmount,
arg.TermsCap,
arg.TermsHeightStart,
arg.TermsHeightEnd,
arg.TermsOffsetStart,
arg.TermsOffsetEnd,
)
return err
}
const createRuneEntryState = `-- name: CreateRuneEntryState :exec
INSERT INTO runes_entry_states (rune_id, block_height, mints, burned_amount, completion_time) VALUES ($1, $2, $3, $4, $5)
`
type CreateRuneEntryStateParams struct {
RuneID string
BlockHeight int32
Mints pgtype.Numeric
BurnedAmount pgtype.Numeric
CompletionTime pgtype.Timestamp
}
func (q *Queries) CreateRuneEntryState(ctx context.Context, arg CreateRuneEntryStateParams) error {
_, err := q.db.Exec(ctx, createRuneEntryState,
arg.RuneID,
arg.BlockHeight,
arg.Mints,
arg.BurnedAmount,
arg.CompletionTime,
)
return err
}
const createRuneTransaction = `-- name: CreateRuneTransaction :exec
INSERT INTO runes_transactions (hash, block_height, timestamp, inputs, outputs, mints, burns) VALUES ($1, $2, $3, $4, $5, $6, $7)
`
type CreateRuneTransactionParams struct {
Hash string
BlockHeight int32
Timestamp pgtype.Timestamp
Inputs []byte
Outputs []byte
Mints []byte
Burns []byte
}
func (q *Queries) CreateRuneTransaction(ctx context.Context, arg CreateRuneTransactionParams) error {
_, err := q.db.Exec(ctx, createRuneTransaction,
arg.Hash,
arg.BlockHeight,
arg.Timestamp,
arg.Inputs,
arg.Outputs,
arg.Mints,
arg.Burns,
)
return err
}
const createRunestone = `-- name: CreateRunestone :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, 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)
`
type CreateRunestoneParams struct {
TxHash string
BlockHeight int32
Etching bool
EtchingDivisibility pgtype.Int2
EtchingPremine pgtype.Numeric
EtchingRune pgtype.Text
EtchingSpacers pgtype.Int4
EtchingSymbol pgtype.Int4
EtchingTerms bool
EtchingTermsAmount pgtype.Numeric
EtchingTermsCap pgtype.Numeric
EtchingTermsHeightStart pgtype.Int4
EtchingTermsHeightEnd pgtype.Int4
EtchingTermsOffsetStart pgtype.Int4
EtchingTermsOffsetEnd pgtype.Int4
Edicts []byte
Mint pgtype.Text
Pointer pgtype.Int4
Cenotaph bool
Flaws int32
}
func (q *Queries) CreateRunestone(ctx context.Context, arg CreateRunestoneParams) error {
_, err := q.db.Exec(ctx, createRunestone,
arg.TxHash,
arg.BlockHeight,
arg.Etching,
arg.EtchingDivisibility,
arg.EtchingPremine,
arg.EtchingRune,
arg.EtchingSpacers,
arg.EtchingSymbol,
arg.EtchingTerms,
arg.EtchingTermsAmount,
arg.EtchingTermsCap,
arg.EtchingTermsHeightStart,
arg.EtchingTermsHeightEnd,
arg.EtchingTermsOffsetStart,
arg.EtchingTermsOffsetEnd,
arg.Edicts,
arg.Mint,
arg.Pointer,
arg.Cenotaph,
arg.Flaws,
)
return err
}
const deleteIndexedBlockByHash = `-- name: DeleteIndexedBlockByHash :exec
DELETE FROM runes_indexed_blocks WHERE hash = $1
`
@@ -162,25 +306,50 @@ func (q *Queries) GetOutPointBalances(ctx context.Context, arg GetOutPointBalanc
}
const getRuneEntriesByRuneIds = `-- name: GetRuneEntriesByRuneIds :many
SELECT rune_id, rune, spacers, burned_amount, mints, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, completion_time FROM runes_entries WHERE rune_id = ANY($1::text[]) FOR UPDATE
WITH states AS (
-- select latest state
SELECT DISTINCT ON (rune_id) rune_id, block_height, mints, burned_amount, completion_time FROM runes_entry_states WHERE rune_id = ANY($1::text[]) ORDER BY rune_id, block_height DESC
)
SELECT runes_entries.rune_id, rune, spacers, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, created_at_block, states.rune_id, block_height, mints, burned_amount, completion_time FROM runes_entries
LEFT JOIN states ON runes_entries.rune_id = states.rune_id
WHERE rune_id = ANY($1::text[])
`
// using FOR UPDATE to prevent other connections for updating the same rune entries if they are being accessed by Processor
func (q *Queries) GetRuneEntriesByRuneIds(ctx context.Context, runeIds []string) ([]RunesEntry, error) {
type GetRuneEntriesByRuneIdsRow struct {
RuneID string
Rune string
Spacers int32
Premine pgtype.Numeric
Symbol int32
Divisibility int16
Terms bool
TermsAmount pgtype.Numeric
TermsCap pgtype.Numeric
TermsHeightStart pgtype.Int4
TermsHeightEnd pgtype.Int4
TermsOffsetStart pgtype.Int4
TermsOffsetEnd pgtype.Int4
CreatedAtBlock int32
RuneID_2 pgtype.Text
BlockHeight pgtype.Int4
Mints pgtype.Numeric
BurnedAmount pgtype.Numeric
CompletionTime pgtype.Timestamp
}
func (q *Queries) GetRuneEntriesByRuneIds(ctx context.Context, runeIds []string) ([]GetRuneEntriesByRuneIdsRow, error) {
rows, err := q.db.Query(ctx, getRuneEntriesByRuneIds, runeIds)
if err != nil {
return nil, err
}
defer rows.Close()
var items []RunesEntry
var items []GetRuneEntriesByRuneIdsRow
for rows.Next() {
var i RunesEntry
var i GetRuneEntriesByRuneIdsRow
if err := rows.Scan(
&i.RuneID,
&i.Rune,
&i.Spacers,
&i.BurnedAmount,
&i.Mints,
&i.Premine,
&i.Symbol,
&i.Divisibility,
@@ -191,6 +360,11 @@ func (q *Queries) GetRuneEntriesByRuneIds(ctx context.Context, runeIds []string)
&i.TermsHeightEnd,
&i.TermsOffsetStart,
&i.TermsOffsetEnd,
&i.CreatedAtBlock,
&i.RuneID_2,
&i.BlockHeight,
&i.Mints,
&i.BurnedAmount,
&i.CompletionTime,
); err != nil {
return nil, err
@@ -203,37 +377,90 @@ func (q *Queries) GetRuneEntriesByRuneIds(ctx context.Context, runeIds []string)
return items, nil
}
const getRuneEntriesByRunes = `-- name: GetRuneEntriesByRunes :many
SELECT rune_id, rune, spacers, burned_amount, mints, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, completion_time FROM runes_entries WHERE rune = ANY($1::text[]) FOR UPDATE
const getRuneIdFromRune = `-- name: GetRuneIdFromRune :one
SELECT rune_id FROM runes_entries WHERE rune = $1
`
// using FOR UPDATE to prevent other connections for updating the same rune entries if they are being accessed by Processor
func (q *Queries) GetRuneEntriesByRunes(ctx context.Context, runes []string) ([]RunesEntry, error) {
rows, err := q.db.Query(ctx, getRuneEntriesByRunes, runes)
func (q *Queries) GetRuneIdFromRune(ctx context.Context, rune string) (string, error) {
row := q.db.QueryRow(ctx, getRuneIdFromRune, rune)
var rune_id string
err := row.Scan(&rune_id)
return rune_id, err
}
const getRuneTransactionsByHeight = `-- name: GetRuneTransactionsByHeight :many
SELECT hash, runes_transactions.block_height, timestamp, inputs, outputs, mints, burns, 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, edicts, mint, pointer, cenotaph, flaws FROM runes_transactions
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
WHERE runes_transactions.block_height = $1
`
type GetRuneTransactionsByHeightRow struct {
Hash string
BlockHeight int32
Timestamp pgtype.Timestamp
Inputs []byte
Outputs []byte
Mints []byte
Burns []byte
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
Edicts []byte
Mint pgtype.Text
Pointer pgtype.Int4
Cenotaph pgtype.Bool
Flaws pgtype.Int4
}
func (q *Queries) GetRuneTransactionsByHeight(ctx context.Context, blockHeight int32) ([]GetRuneTransactionsByHeightRow, error) {
rows, err := q.db.Query(ctx, getRuneTransactionsByHeight, blockHeight)
if err != nil {
return nil, err
}
defer rows.Close()
var items []RunesEntry
var items []GetRuneTransactionsByHeightRow
for rows.Next() {
var i RunesEntry
var i GetRuneTransactionsByHeightRow
if err := rows.Scan(
&i.RuneID,
&i.Rune,
&i.Spacers,
&i.BurnedAmount,
&i.Hash,
&i.BlockHeight,
&i.Timestamp,
&i.Inputs,
&i.Outputs,
&i.Mints,
&i.Premine,
&i.Symbol,
&i.Divisibility,
&i.Terms,
&i.TermsAmount,
&i.TermsCap,
&i.TermsHeightStart,
&i.TermsHeightEnd,
&i.TermsOffsetStart,
&i.TermsOffsetEnd,
&i.CompletionTime,
&i.Burns,
&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.Edicts,
&i.Mint,
&i.Pointer,
&i.Cenotaph,
&i.Flaws,
); err != nil {
return nil, err
}
@@ -261,53 +488,6 @@ func (q *Queries) GetRunesProcessorState(ctx context.Context) (RunesProcessorSta
return i, err
}
const setRuneEntry = `-- name: SetRuneEntry :exec
INSERT INTO runes_entries (rune_id, rune, spacers, burned_amount, mints, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, completion_time)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
ON CONFLICT (rune_id) DO UPDATE SET (burned_amount, mints, completion_time) = (excluded.burned_amount, excluded.mints, excluded.completion_time)
`
type SetRuneEntryParams struct {
RuneID string
Rune string
Spacers int32
BurnedAmount pgtype.Numeric
Mints pgtype.Numeric
Premine pgtype.Numeric
Symbol int32
Divisibility int16
Terms bool
TermsAmount pgtype.Numeric
TermsCap pgtype.Numeric
TermsHeightStart pgtype.Int4
TermsHeightEnd pgtype.Int4
TermsOffsetStart pgtype.Int4
TermsOffsetEnd pgtype.Int4
CompletionTime pgtype.Timestamp
}
func (q *Queries) SetRuneEntry(ctx context.Context, arg SetRuneEntryParams) error {
_, err := q.db.Exec(ctx, setRuneEntry,
arg.RuneID,
arg.Rune,
arg.Spacers,
arg.BurnedAmount,
arg.Mints,
arg.Premine,
arg.Symbol,
arg.Divisibility,
arg.Terms,
arg.TermsAmount,
arg.TermsCap,
arg.TermsHeightStart,
arg.TermsHeightEnd,
arg.TermsOffsetStart,
arg.TermsOffsetEnd,
arg.CompletionTime,
)
return err
}
const updateLatestBlock = `-- name: UpdateLatestBlock :exec
UPDATE runes_processor_state SET latest_block_height = $1, latest_block_hash = $2, latest_prev_block_hash = $3
`

View File

@@ -19,8 +19,6 @@ type RunesEntry struct {
RuneID string
Rune string
Spacers int32
BurnedAmount pgtype.Numeric
Mints pgtype.Numeric
Premine pgtype.Numeric
Symbol int32
Divisibility int16
@@ -31,7 +29,15 @@ type RunesEntry struct {
TermsHeightEnd pgtype.Int4
TermsOffsetStart pgtype.Int4
TermsOffsetEnd pgtype.Int4
CompletionTime pgtype.Timestamp
CreatedAtBlock int32
}
type RunesEntryState struct {
RuneID string
BlockHeight int32
Mints pgtype.Numeric
BurnedAmount pgtype.Numeric
CompletionTime pgtype.Timestamp
}
type RunesIndexedBlock struct {
@@ -71,3 +77,36 @@ type RunesProcessorState struct {
LatestPrevBlockHash string
UpdatedAt pgtype.Timestamp
}
type RunesRunestone struct {
TxHash string
BlockHeight int32
Etching bool
EtchingDivisibility pgtype.Int2
EtchingPremine pgtype.Numeric
EtchingRune pgtype.Text
EtchingSpacers pgtype.Int4
EtchingSymbol pgtype.Int4
EtchingTerms bool
EtchingTermsAmount pgtype.Numeric
EtchingTermsCap pgtype.Numeric
EtchingTermsHeightStart pgtype.Int4
EtchingTermsHeightEnd pgtype.Int4
EtchingTermsOffsetStart pgtype.Int4
EtchingTermsOffsetEnd pgtype.Int4
Edicts []byte
Mint pgtype.Text
Pointer pgtype.Int4
Cenotaph bool
Flaws int32
}
type RunesTransaction struct {
Hash string
BlockHeight int32
Timestamp pgtype.Timestamp
Inputs []byte
Outputs []byte
Mints []byte
Burns []byte
}

View File

@@ -2,6 +2,7 @@ package postgres
import (
"encoding/hex"
"encoding/json"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
@@ -42,7 +43,7 @@ func numericFromUint128(src *uint128.Uint128) (pgtype.Numeric, error) {
return result, nil
}
func mapRuneEntryModelToType(src gen.RunesEntry) (runes.RuneEntry, error) {
func mapRuneEntryModelToType(src gen.GetRuneEntriesByRuneIdsRow) (runes.RuneEntry, error) {
runeId, err := runes.NewRuneIdFromString(src.RuneID)
if err != nil {
return runes.RuneEntry{}, errors.Wrap(err, "failed to parse rune id")
@@ -114,21 +115,21 @@ func mapRuneEntryModelToType(src gen.RunesEntry) (runes.RuneEntry, error) {
}, nil
}
func mapRuneEntryTypeToParams(src runes.RuneEntry) (gen.SetRuneEntryParams, error) {
func mapRuneEntryTypeToParams(src runes.RuneEntry) (gen.CreateRuneEntryParams, gen.CreateRuneEntryStateParams, error) {
runeId := src.RuneId.String()
rune := src.SpacedRune.Rune.String()
spacers := int32(src.SpacedRune.Spacers)
burnedAmount, err := numericFromUint128(&src.BurnedAmount)
if err != nil {
return gen.SetRuneEntryParams{}, errors.Wrap(err, "failed to parse burned amount")
}
mints, err := numericFromUint128(&src.Mints)
if err != nil {
return gen.SetRuneEntryParams{}, errors.Wrap(err, "failed to parse mints")
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.SetRuneEntryParams{}, errors.Wrap(err, "failed to parse premine")
return gen.CreateRuneEntryParams{}, gen.CreateRuneEntryStateParams{}, errors.Wrap(err, "failed to parse premine")
}
var completionTime pgtype.Timestamp
if !src.CompletionTime.IsZero() {
@@ -143,13 +144,13 @@ func mapRuneEntryTypeToParams(src runes.RuneEntry) (gen.SetRuneEntryParams, erro
if src.Terms.Amount != nil {
termsAmount, err = numericFromUint128(src.Terms.Amount)
if err != nil {
return gen.SetRuneEntryParams{}, errors.Wrap(err, "failed to parse terms amount")
return gen.CreateRuneEntryParams{}, gen.CreateRuneEntryStateParams{}, errors.Wrap(err, "failed to parse terms amount")
}
}
if src.Terms.Cap != nil {
termsCap, err = numericFromUint128(src.Terms.Cap)
if err != nil {
return gen.SetRuneEntryParams{}, errors.Wrap(err, "failed to parse terms cap")
return gen.CreateRuneEntryParams{}, gen.CreateRuneEntryStateParams{}, errors.Wrap(err, "failed to parse terms cap")
}
}
if src.Terms.HeightStart != nil {
@@ -178,26 +179,353 @@ func mapRuneEntryTypeToParams(src runes.RuneEntry) (gen.SetRuneEntryParams, erro
}
}
return gen.SetRuneEntryParams{
RuneID: runeId,
Rune: rune,
Spacers: spacers,
BurnedAmount: burnedAmount,
Mints: mints,
Premine: premine,
Symbol: src.Symbol,
Divisibility: int16(src.Divisibility),
Terms: terms,
TermsAmount: termsAmount,
TermsCap: termsCap,
TermsHeightStart: termsHeightStart,
TermsHeightEnd: termsHeightEnd,
TermsOffsetStart: termsOffsetStart,
TermsOffsetEnd: termsOffsetEnd,
CompletionTime: completionTime,
return gen.CreateRuneEntryParams{
RuneID: runeId,
Rune: rune,
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,
}, gen.CreateRuneEntryStateParams{
RuneID: runeId,
Mints: mints,
BurnedAmount: burnedAmount,
CompletionTime: completionTime,
}, nil
}
type outpointBalanceModel struct {
PkScript string
Id runes.RuneId
Value uint128.Uint128
Index uint32
}
func mapOutPointBalanceTypeToModel(src entity.OutPointBalance) outpointBalanceModel {
return outpointBalanceModel{
PkScript: hex.EncodeToString(src.PkScript),
Id: src.Id,
Value: src.Value,
Index: src.Index,
}
}
// mapRuneTransactionModelToType returns params for creating a new rune transaction and (optionally) a runestone.
func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneTransactionParams, *gen.CreateRunestoneParams, error) {
var timestamp pgtype.Timestamp
if !src.Timestamp.IsZero() {
timestamp.Time = src.Timestamp
timestamp.Valid = true
}
inputs := lo.Map(src.Inputs, func(input *entity.OutPointBalance, _ int) outpointBalanceModel {
return mapOutPointBalanceTypeToModel(*input)
})
outputs := lo.Map(src.Outputs, func(output *entity.OutPointBalance, _ int) outpointBalanceModel {
return mapOutPointBalanceTypeToModel(*output)
})
mints := lo.Map(src.Mints, func(mint *entity.OutPointBalance, _ int) outpointBalanceModel {
return mapOutPointBalanceTypeToModel(*mint)
})
inputsBytes, err := json.Marshal(inputs)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal inputs")
}
outputsBytes, err := json.Marshal(outputs)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal outputs")
}
mintsBytes, err := json.Marshal(mints)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal mints")
}
burnsBytes, err := json.Marshal(src.Burns)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal burns")
}
var runestoneParams *gen.CreateRunestoneParams
if src.Runestone != nil {
params, err := mapRunestoneTypeToParams(*src.Runestone, src.Hash, src.BlockHeight)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to map runestone to params")
}
runestoneParams = &params
}
return gen.CreateRuneTransactionParams{
Hash: src.Hash.String(),
BlockHeight: int32(src.BlockHeight),
Timestamp: timestamp,
Inputs: inputsBytes,
Outputs: outputsBytes,
Mints: mintsBytes,
Burns: burnsBytes,
}, runestoneParams, nil
}
func extractModelRuneTxAndRunestone(src gen.GetRuneTransactionsByHeightRow) (gen.RunesTransaction, *gen.RunesRunestone, error) {
var runestone *gen.RunesRunestone
if src.TxHash.Valid {
// these fields should never be null
if !src.Etching.Valid {
return gen.RunesTransaction{}, nil, errors.New("runestone etching bool is null")
}
if !src.EtchingTerms.Valid {
return gen.RunesTransaction{}, nil, errors.New("runestone etching terms bool is null")
}
if !src.Cenotaph.Valid {
return gen.RunesTransaction{}, nil, errors.New("runestone cenotaph is null")
}
if !src.Flaws.Valid {
return gen.RunesTransaction{}, nil, errors.New("runestone flaws is null")
}
runestone = &gen.RunesRunestone{
TxHash: src.TxHash.String,
BlockHeight: src.BlockHeight,
Etching: src.Etching.Bool,
EtchingDivisibility: src.EtchingDivisibility,
EtchingPremine: src.EtchingPremine,
EtchingRune: src.EtchingRune,
EtchingSpacers: src.EtchingSpacers,
EtchingSymbol: src.EtchingSymbol,
EtchingTerms: src.EtchingTerms.Bool,
EtchingTermsAmount: src.EtchingTermsAmount,
EtchingTermsCap: src.EtchingTermsCap,
EtchingTermsHeightStart: src.EtchingTermsHeightStart,
EtchingTermsHeightEnd: src.EtchingTermsHeightEnd,
EtchingTermsOffsetStart: src.EtchingTermsOffsetStart,
EtchingTermsOffsetEnd: src.EtchingTermsOffsetEnd,
Edicts: src.Edicts,
Mint: src.Mint,
Pointer: src.Pointer,
Cenotaph: src.Cenotaph.Bool,
Flaws: src.Flaws.Int32,
}
}
return gen.RunesTransaction{
Hash: src.Hash,
BlockHeight: src.BlockHeight,
Timestamp: src.Timestamp,
Inputs: src.Inputs,
Outputs: src.Outputs,
Mints: src.Mints,
Burns: src.Burns,
}, runestone, nil
}
func mapRuneTransactionModelToType(src gen.RunesTransaction) (entity.RuneTransaction, error) {
hash, err := chainhash.NewHashFromStr(src.Hash)
if err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to parse transaction hash")
}
var timestamp time.Time
if src.Timestamp.Valid {
timestamp = src.Timestamp.Time
}
inputs := make([]*entity.OutPointBalance, 0)
if err := json.Unmarshal(src.Inputs, &inputs); err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to unmarshal inputs")
}
outputs := make([]*entity.OutPointBalance, 0)
if err := json.Unmarshal(src.Outputs, &outputs); err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to unmarshal outputs")
}
mints := make([]*entity.OutPointBalance, 0)
if err := json.Unmarshal(src.Mints, &mints); err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to unmarshal mints")
}
burns := make(map[runes.RuneId]uint128.Uint128)
if err := json.Unmarshal(src.Burns, &burns); err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to unmarshal burns")
}
return entity.RuneTransaction{
Hash: *hash,
BlockHeight: uint64(src.BlockHeight),
Timestamp: timestamp,
Inputs: inputs,
Outputs: outputs,
Mints: mints,
Burns: burns,
}, nil
}
func mapRunestoneTypeToParams(src runes.Runestone, txHash chainhash.Hash, blockHeight uint64) (gen.CreateRunestoneParams, error) {
var runestoneParams gen.CreateRunestoneParams
edictsBytes, err := json.Marshal(src.Edicts)
if err != nil {
return gen.CreateRunestoneParams{}, errors.Wrap(err, "failed to marshal runestone edicts")
}
runestoneParams = gen.CreateRunestoneParams{
TxHash: txHash.String(),
BlockHeight: int32(blockHeight),
Edicts: edictsBytes,
Cenotaph: src.Cenotaph,
Flaws: int32(src.Flaws),
}
if src.Etching != nil {
runestoneParams.Etching = true
etching := *src.Etching
if etching.Divisibility != nil {
runestoneParams.EtchingDivisibility = pgtype.Int2{Int16: int16(*etching.Divisibility), Valid: true}
}
if etching.Premine != nil {
premine, err := numericFromUint128(etching.Premine)
if err != nil {
return gen.CreateRunestoneParams{}, errors.Wrap(err, "failed to parse etching premine")
}
runestoneParams.EtchingPremine = premine
}
if etching.Rune != nil {
runestoneParams.EtchingRune = pgtype.Text{String: etching.Rune.String(), Valid: true}
}
if etching.Spacers != nil {
runestoneParams.EtchingSpacers = pgtype.Int4{Int32: int32(*etching.Spacers), Valid: true}
}
if etching.Symbol != nil {
runestoneParams.EtchingSymbol = pgtype.Int4{Int32: int32(*etching.Symbol), Valid: true}
}
if etching.Terms != nil {
runestoneParams.EtchingTerms = true
terms := *etching.Terms
if terms.Amount != nil {
amount, err := numericFromUint128(terms.Amount)
if err != nil {
return gen.CreateRunestoneParams{}, errors.Wrap(err, "failed to parse etching terms amount")
}
runestoneParams.EtchingTermsAmount = amount
}
if terms.Cap != nil {
cap, err := numericFromUint128(terms.Cap)
if err != nil {
return gen.CreateRunestoneParams{}, errors.Wrap(err, "failed to parse etching terms cap")
}
runestoneParams.EtchingTermsCap = cap
}
if terms.HeightStart != nil {
runestoneParams.EtchingTermsHeightStart = pgtype.Int4{Int32: int32(*terms.HeightStart), Valid: true}
}
if terms.HeightEnd != nil {
runestoneParams.EtchingTermsHeightEnd = pgtype.Int4{Int32: int32(*terms.HeightEnd), Valid: true}
}
if terms.OffsetStart != nil {
runestoneParams.EtchingTermsOffsetStart = pgtype.Int4{Int32: int32(*terms.OffsetStart), Valid: true}
}
if terms.OffsetEnd != nil {
runestoneParams.EtchingTermsOffsetEnd = pgtype.Int4{Int32: int32(*terms.OffsetEnd), Valid: true}
}
}
}
if src.Mint != nil {
runestoneParams.Mint = pgtype.Text{String: src.Mint.String(), Valid: true}
}
if src.Pointer != nil {
runestoneParams.Pointer = pgtype.Int4{Int32: int32(*src.Pointer), Valid: true}
}
return runestoneParams, nil
}
func mapRunestoneModelToType(src gen.RunesRunestone) (runes.Runestone, error) {
runestone := runes.Runestone{
Cenotaph: src.Cenotaph,
Flaws: runes.Flaws(src.Flaws),
}
if src.Etching {
etching := runes.Etching{}
if src.EtchingDivisibility.Valid {
divisibility := uint8(src.EtchingDivisibility.Int16)
etching.Divisibility = &divisibility
}
if src.EtchingPremine.Valid {
premine, err := uint128FromNumeric(src.EtchingPremine)
if err != nil {
return runes.Runestone{}, errors.Wrap(err, "failed to parse etching premine")
}
etching.Premine = premine
}
if src.EtchingRune.Valid {
rune, err := runes.NewRuneFromString(src.EtchingRune.String)
if err != nil {
return runes.Runestone{}, errors.Wrap(err, "failed to parse etching rune")
}
etching.Rune = &rune
}
if src.EtchingSpacers.Valid {
spacers := uint32(src.EtchingSpacers.Int32)
etching.Spacers = &spacers
}
if src.EtchingSymbol.Valid {
symbol := rune(src.EtchingSymbol.Int32)
etching.Symbol = &symbol
}
if src.EtchingTerms {
terms := runes.Terms{}
if src.EtchingTermsAmount.Valid {
amount, err := uint128FromNumeric(src.EtchingTermsAmount)
if err != nil {
return runes.Runestone{}, errors.Wrap(err, "failed to parse etching terms amount")
}
terms.Amount = amount
}
if src.EtchingTermsCap.Valid {
cap, err := uint128FromNumeric(src.EtchingTermsCap)
if err != nil {
return runes.Runestone{}, errors.Wrap(err, "failed to parse etching terms cap")
}
terms.Cap = cap
}
if src.EtchingTermsHeightStart.Valid {
heightStart := uint64(src.EtchingTermsHeightStart.Int32)
terms.HeightStart = &heightStart
}
if src.EtchingTermsHeightEnd.Valid {
heightEnd := uint64(src.EtchingTermsHeightEnd.Int32)
terms.HeightEnd = &heightEnd
}
if src.EtchingTermsOffsetStart.Valid {
offsetStart := uint64(src.EtchingTermsOffsetStart.Int32)
terms.OffsetStart = &offsetStart
}
if src.EtchingTermsOffsetEnd.Valid {
offsetEnd := uint64(src.EtchingTermsOffsetEnd.Int32)
terms.OffsetEnd = &offsetEnd
}
etching.Terms = &terms
}
runestone.Etching = &etching
}
if src.Mint.Valid {
mint, err := runes.NewRuneIdFromString(src.Mint.String)
if err != nil {
return runes.Runestone{}, errors.Wrap(err, "failed to parse mint")
}
runestone.Mint = &mint
}
if src.Pointer.Valid {
pointer := uint64(src.Pointer.Int32)
runestone.Pointer = &pointer
}
// Edicts
{
if err := json.Unmarshal(src.Edicts, &runestone.Edicts); err != nil {
return runes.Runestone{}, errors.Wrap(err, "failed to unmarshal edicts")
}
if len(runestone.Edicts) == 0 {
runestone.Edicts = nil
}
}
return runestone, nil
}
func mapBalanceModelToType(src gen.RunesBalance) (*entity.Balance, error) {
runeId, err := runes.NewRuneIdFromString(src.RuneID)
if err != nil {

View File

@@ -55,6 +55,35 @@ func (r *Repository) GetIndexedBlockByHeight(ctx context.Context, height int64)
return indexedBlock, nil
}
func (r *Repository) GetRuneTransactionsByHeight(ctx context.Context, height uint64) ([]*entity.RuneTransaction, error) {
rows, err := r.queries.GetRuneTransactionsByHeight(ctx, int32(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(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]uint128.Uint128, error) {
balances, err := r.queries.GetOutPointBalances(ctx, gen.GetOutPointBalancesParams{
TxHash: outPoint.Hash.String(),
@@ -79,20 +108,16 @@ func (r *Repository) GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wi
return result, nil
}
func (r *Repository) GetRuneEntryByRune(ctx context.Context, rune runes.Rune) (*runes.RuneEntry, error) {
runeEntryModels, err := r.queries.GetRuneEntriesByRunes(ctx, []string{rune.String()})
func (r *Repository) GetRuneIdFromRune(ctx context.Context, rune runes.Rune) (runes.RuneId, error) {
runeIdStr, err := r.queries.GetRuneIdFromRune(ctx, rune.String())
if err != nil {
return nil, errors.Wrap(err, "error during query")
return runes.RuneId{}, errors.Wrap(err, "error during query")
}
if len(runeEntryModels) == 0 {
return nil, errors.WithStack(errs.NotFound)
}
runeEntry, err := mapRuneEntryModelToType(runeEntryModels[0])
runeId, err := runes.NewRuneIdFromString(runeIdStr)
if err != nil {
return nil, errors.Wrap(err, "failed to parse rune entry model")
return runes.RuneId{}, errors.Wrap(err, "failed to parse RuneId")
}
return &runeEntry, nil
return runeId, nil
}
func (r *Repository) GetRuneEntryByRuneId(ctx context.Context, runeId runes.RuneId) (*runes.RuneEntry, error) {
@@ -135,16 +160,52 @@ func (r *Repository) GetRuneEntryByRuneIdBatch(ctx context.Context, runeIds []ru
return runeEntries, nil
}
func (r *Repository) SetRuneEntry(ctx context.Context, entry *runes.RuneEntry) error {
func (r *Repository) CreateRuneTransaction(ctx context.Context, tx *entity.RuneTransaction) error {
if tx == nil {
return nil
}
txParams, runestoneParams, err := mapRuneTransactionTypeToParams(*tx)
if err != nil {
return errors.Wrap(err, "failed to map rune transaction to params")
}
if err = r.queries.CreateRuneTransaction(ctx, txParams); err != nil {
return errors.Wrap(err, "error during exec CreateRuneTransaction")
}
if runestoneParams != nil {
if err = r.queries.CreateRunestone(ctx, *runestoneParams); err != nil {
return errors.Wrap(err, "error during exec CreateRunestone")
}
}
return nil
}
func (r *Repository) CreateRuneEntry(ctx context.Context, entry *runes.RuneEntry) error {
if entry == nil {
return nil
}
params, err := mapRuneEntryTypeToParams(*entry)
createParams, createStateParams, err := mapRuneEntryTypeToParams(*entry)
if err != nil {
return errors.Wrap(err, "failed to map rune entry to params")
}
if err = r.queries.SetRuneEntry(ctx, params); err != nil {
return errors.Wrap(err, "error during exec")
if err = r.queries.CreateRuneEntry(ctx, createParams); err != nil {
return errors.Wrap(err, "error during exec CreateRuneEntry")
}
if err = r.queries.CreateRuneEntryState(ctx, createStateParams); err != nil {
return errors.Wrap(err, "error during exec CreateRuneEntryState")
}
return nil
}
func (r *Repository) CreateRuneEntryState(ctx context.Context, entry *runes.RuneEntry) error {
if entry == nil {
return nil
}
_, createStateParams, err := mapRuneEntryTypeToParams(*entry)
if err != nil {
return errors.Wrap(err, "failed to map rune entry to params")
}
if err = r.queries.CreateRuneEntryState(ctx, createStateParams); err != nil {
return errors.Wrap(err, "error during exec CreateRuneEntryState")
}
return nil
}

View File

@@ -153,3 +153,23 @@ func GetReservedRune(blockHeight uint64, txIndex uint32) Rune {
delta := uint128.From64(blockHeight).Lsh(32).Or64(uint64(txIndex))
return Rune(firstReservedRune.Uint128().Add(delta))
}
// MarshalJSON implements json.Marshaler
func (r Rune) MarshalJSON() ([]byte, error) {
return []byte(`"` + r.String() + `"`), nil
}
// UnmarshalJSON implements json.Unmarshaler
func (r *Rune) UnmarshalJSON(data []byte) error {
// data must be quoted
if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
return errors.New("must be string")
}
data = data[1 : len(data)-1]
parsed, err := NewRuneFromString(string(data))
if err != nil {
return errors.WithStack(err)
}
*r = parsed
return nil
}

View File

@@ -80,3 +80,23 @@ func (r RuneId) Next(blockDelta uint64, txIndexDelta uint32) (RuneId, error) {
txIndexDelta,
)
}
// MarshalJSON implements json.Marshaler
func (r RuneId) MarshalJSON() ([]byte, error) {
return []byte(`"` + r.String() + `"`), nil
}
// UnmarshalJSON implements json.Unmarshaler
func (r *RuneId) UnmarshalJSON(data []byte) error {
// data must be quoted
if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
return errors.New("must be string")
}
data = data[1 : len(data)-1]
parsed, err := NewRuneIdFromString(string(data))
if err != nil {
return errors.WithStack(err)
}
*r = parsed
return nil
}

View File

@@ -81,3 +81,28 @@ func TestNewRuneIdFromString(t *testing.T) {
})
}
}
func TestRuneIdMarshal(t *testing.T) {
runeId := RuneId{
BlockHeight: 1,
TxIndex: 2,
}
bytes, err := runeId.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, []byte(`"1:2"`), bytes)
}
func TestRuneIdUnmarshal(t *testing.T) {
str := `"1:2"`
var runeId RuneId
err := runeId.UnmarshalJSON([]byte(str))
assert.NoError(t, err)
assert.Equal(t, RuneId{
BlockHeight: 1,
TxIndex: 2,
}, runeId)
str = `1`
err = runeId.UnmarshalJSON([]byte(str))
assert.Error(t, err)
}

View File

@@ -251,3 +251,22 @@ func TestCommitment(t *testing.T) {
test(NewRune(65535), []byte{255, 255})
test(NewRune(65536), []byte{0, 0, 1})
}
func TestRuneMarshal(t *testing.T) {
rune := NewRune(5)
bytes, err := rune.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, []byte(`"F"`), bytes)
}
func TestRuneUnmarshal(t *testing.T) {
str := `"F"`
var rune Rune
err := rune.UnmarshalJSON([]byte(str))
assert.NoError(t, err)
assert.Equal(t, NewRune(5), rune)
str = `1`
err = rune.UnmarshalJSON([]byte(str))
assert.Error(t, err)
}

View File

@@ -7,12 +7,12 @@ import (
"github.com/gaze-network/indexer-network/modules/runes/internal/runes"
)
func (u *Usecase) GetRuneEntryByRune(ctx context.Context, rune runes.Rune) (*runes.RuneEntry, error) {
runeEntry, err := u.runesDg.GetRuneEntryByRune(ctx, rune)
func (u *Usecase) GetRuneIdFromRune(ctx context.Context, rune runes.Rune) (runes.RuneId, error) {
runeId, err := u.runesDg.GetRuneIdFromRune(ctx, rune)
if err != nil {
return nil, errors.Wrap(err, "failed to get rune entry by rune")
return runes.RuneId{}, errors.Wrap(err, "failed to get rune entry by rune")
}
return runeEntry, nil
return runeId, nil
}
func (u *Usecase) GetRuneEntryByRuneId(ctx context.Context, runeId runes.RuneId) (*runes.RuneEntry, error) {

View File

@@ -14,22 +14,25 @@ import (
var _ indexers.BitcoinProcessor = (*Processor)(nil)
type Processor struct {
runesDg datagateway.RunesDataGateway
bitcoinClient btcclient.Contract
network common.Network
runesDg datagateway.RunesDataGateway
bitcoinClient btcclient.Contract
bitcoinDataSource indexers.BitcoinDatasource
network common.Network
}
type NewProcessorParams struct {
RunesDg datagateway.RunesDataGateway
BitcoinClient btcclient.Contract
Network common.Network
RunesDg datagateway.RunesDataGateway
BitcoinClient btcclient.Contract
BitcoinDataSource indexers.BitcoinDatasource
Network common.Network
}
func NewProcessor(params NewProcessorParams) *Processor {
return &Processor{
runesDg: params.RunesDg,
bitcoinClient: params.BitcoinClient,
network: params.Network,
runesDg: params.RunesDg,
bitcoinClient: params.BitcoinClient,
bitcoinDataSource: params.BitcoinDataSource,
network: params.Network,
}
}
@@ -58,10 +61,6 @@ func (p *Processor) GetIndexedBlock(ctx context.Context, height int64) (types.Bl
}, nil
}
func (p *Processor) PrepareData(ctx context.Context, from, to int64) ([]*types.Block, error) {
panic("implement me")
}
func (p *Processor) RevertData(ctx context.Context, from int64) error {
panic("implement me")
}

View File

@@ -270,8 +270,8 @@ func (p *Processor) getEtchedRune(ctx context.Context, tx *types.Transaction, ru
return nil, runes.RuneId{}, runes.Rune{}, nil
}
_, err := p.runesDg.GetRuneEntryByRune(ctx, *rune)
if err != nil && errors.Is(err, errs.NotFound) {
_, err := p.runesDg.GetRuneIdFromRune(ctx, *rune)
if err != nil && !errors.Is(err, errs.NotFound) {
return nil, runes.RuneId{}, runes.Rune{}, errors.Wrap(err, "error during get rune entry by rune")
}
// if found, then this is duplicate