Files
gaze-brc20-indexer/modules/runes/repository/postgres/mapper.go
gazenw fcdecd4046 feat: v0.1.0 release (#13)
* fix: don't remove first block

* fix: make etching_terms nullable

* fix: fix panic if empty pkscript

* chore: change testnet starting block

* feat: more logs

* fix: extract tapscript bug

* feat: more logs

* fix: switch pk to block height

* chore: remove redundant log

* fix: repo

* fix: not found error

* fix: golangci-lint

* feat: add etching tx hash to rune entries

* feat: stop main if indexer failed

* fix: check balance after populating current balance

* fix: sql ambiguous column

* feat: add tx hash and out index in tx output

* fix: actually use transactions to write db

* fix: create rune entry states only during flushes

* fix: mint cap reached off by one

* fix: debug log unsafe

* feat: prevent processing of txs before activation height

* feat: add rune number to rune entry

* feat: include new rune entries in event hash and flushing

* refactor(config): separate init and get config func

Co-authored-by: Gaze <dev@gaze.network>

* feat: remove annoying log

Co-authored-by: Gaze <dev@gaze.network>

* feat: mod tidy

Co-authored-by: Gaze <dev@gaze.network>

* refactor: move main to root

Co-authored-by: Gaze <dev@gaze.network>

* feat(cli): create cli commands

Co-authored-by: Gaze <dev@gaze.network>

* refactor: move main logic to command

Co-authored-by: Gaze <dev@gaze.network>

* doc: remove unused desc

Co-authored-by: Gaze <dev@gaze.network>

* refactor: test structure in runestone_test.go

* fix: edict flaws were ignored

* feat: more tests

* refactor(cli): add local flag

Co-authored-by: Gaze <dev@gaze.network>

* feat: set symbol limit to utf8.MaxRune

* refactor(cli): flags for each module

Co-authored-by: Gaze <dev@gaze.network>

* feat(cli): support db selection

Co-authored-by: Gaze <dev@gaze.network>

* fix: remove temp code

Co-authored-by: Gaze <dev@gaze.network>

* fix: get data from cache in processor first, then dg

* feat(cli): add version command

Co-authored-by: Gaze <dev@gaze.network>

* doc(cli): add refactor plan

Co-authored-by: Gaze <dev@gaze.network>

* refactor(cli): rename files

Co-authored-by: Gaze <dev@gaze.network>

* feat: add main.go

Co-authored-by: Gaze <dev@gaze.network>

* feat: more tests

* feat: add overflow err

* feat: finish runestone tests

* refactor(cli): separate protocol config and cli flag

Co-authored-by: Gaze <dev@gaze.network>

* chore(btc): update example config

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add get block header to datasource interface

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): reorg handling

Co-authored-by: Gaze <dev@gaze.network>

* fix: interface

Co-authored-by: Gaze <dev@gaze.network>

* fix: rename postgres config key

* fix: migrated runes indexer integration to new cli

* fix: commit every block

* feat(btc): add revert data query

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add revert data to processor

Co-authored-by: Gaze <dev@gaze.network>

* feat: implement public errors

* fix: use errs in api

* refactor: move api and usecase outside of internal

* feat: add custom opcode check for datapush

* fix: break if input utxo is not P2TR

* fix: zero len destination case

* fix: get the rest of transaction data in GetTransaction

* refactor: create subscription utils tools

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add btc_database from datasource

Co-authored-by: Gaze <dev@gaze.network>

* doc(btc): add note

Co-authored-by: Gaze <dev@gaze.network>

* wip(btc): imple prepare range func

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add pg queries for datasource

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): update queries

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): implement repo for get blocks

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): update dg

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): return nil if errors

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): update fetch async for db datasource

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add get block header from db for reorg handling

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add todo notes

Co-authored-by: Gaze <dev@gaze.network>

* feat: implement get tx by hash

* fix: rename func

* fix: rename func

* fix: rename func

* fix: fix get transaction by hash

* feat: integrate bitcoin client db to main

* fix: reduce chunk size

* fix: stop main if bitcoin indexer failed

* fix: stop main if runes indexer failed

* fix: move stop() inside goroutine

* chore: add log

* fix: duplicate rune entry number

* feat(btc): add witness utils

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): witness datamodel parsing

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): invalid table name

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): remove uniqte index for hash

Co-authored-by: Gaze <dev@gaze.network>

* doc: add todo

Co-authored-by: Gaze <dev@gaze.network>

* feat(logger): remove error verbose

Co-authored-by: Gaze <dev@gaze.network>

* feat: support postgresql db

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add err notfound

Co-authored-by: Gaze <dev@gaze.network>

* fix: invalid pgx version

Co-authored-by: Gaze <dev@gaze.network>

* fix: invalid indexer flow

Co-authored-by: Gaze <dev@gaze.network>

* feat: refactor runes api

* feat: implement http server

* fix: mount runes api

* fix: error handler

* fix: first empty state error

Co-authored-by: Gaze <dev@gaze.network>

* fix: off by one confirmation

* ci: ignore RollBack error

* fix: change WithPublicMessage to be prefix

* feat: bump cstream version

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): nullable pkscript

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): change rollback style

Co-authored-by: Gaze <dev@gaze.network>

* refactor: move runes out of internal

* feat: rename id field to runeId in rune transaction

* feat(btc): update index

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add default current block

Co-authored-by: Gaze <dev@gaze.network>

* doc: add note

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): use int64 to store sequence

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): upgrade data type for numbers

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc):  upgrade data type for idx

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): get indexed block impl

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add common.ZeroHash

Co-authored-by: Gaze <dev@gaze.network>

* feat: add chainparam

* feat: implement get transactions

* fix: wrong condition for non-OP_RETURN output

* feat(btc): add verify indexer states

Co-authored-by: Gaze <dev@gaze.network>

* refactor: sorting code

Co-authored-by: Gaze <dev@gaze.network>

* feat: fix interface

* feat(btc): update chuunk size

Co-authored-by: Gaze <dev@gaze.network>

* feat: add rune_etched column in rune transaction

* fix: missing field in create

* feat: add runeEtched in get transactions

* feat: implement get token info

* feat: add holders count in token info

* feat: implement get holders

* fix: return a new repository when beginning a new tx

* fix: rename type

* feat: add pkscript to outpoint balance

* feat: implement get utxos by address api

* fix: spend outpoint bug

* feat: implement get balances by address batch

* feat: sort balances result by amount

* ci: create Dockerfile

Co-authored-by: Gaze <dev@gaze.network>

* ci: add arg run

Co-authored-by: Gaze <dev@gaze.network>

* perf: add automaxprocs

Co-authored-by: Gaze <dev@gaze.network>

* chore: add performance logging

Co-authored-by: Gaze <dev@gaze.network>

* chore: add performance logger for debyug

Co-authored-by: Gaze <dev@gaze.network>

* fix: empty etched at

* fix: revert data sequentially

* fix: remove unused funcs

* fix: main.go

* feat: add flag --api-only to run cmd

* fix: create index

* fix: don't add zero mint to unallocated

* fix: ignore zero burn amount

* feat(reorg): add reorg detail

Co-authored-by: Gaze <dev@gaze.network>

* fix: wrong index type

* feat: implement reporting client to report runes blocks

* feat: implement report node

* feat(runes): add latest block api

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): use logger warn

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): txout aren't revert if it's have to revert spent

Co-authored-by: Gaze <dev@gaze.network>

* fix: annoying error when unsubscribe fetcher

Co-authored-by: Gaze <dev@gaze.network>

* refactor(btc): readable code

Co-authored-by: Gaze <dev@gaze.network>

* fix(indexer): fix subscription closed before process when success fetch

Co-authored-by: Gaze <dev@gaze.network>

* fix: remove module enum

* fix: increase max reorg limit

* feat: add starting height for runes mainnet

* fix(btc): fix `with` modified same row twice

Co-authored-by: Gaze <dev@gaze.network>

* fix(runes): handling latest block not found

Co-authored-by: Gaze <dev@gaze.network>

* feat: add decimals in get transactions

* fix: wrong condition

* feat: add more index

* feat: implement get transactions by pkscript

* feat: allow query by rune id too

* feat: more comments

* perf(btc): bitcoin indexer performance optimization (#4)

* feat(btc): not null to witness

Co-authored-by: Gaze <dev@gaze.network>

* perf(btc): add batch insert txin

Co-authored-by: Gaze <dev@gaze.network>

* perf(btc): batch insert txout

Co-authored-by: Gaze <dev@gaze.network>

* perf(btc): batch insert transaction

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): remove old queries

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): typo

Co-authored-by: Gaze <dev@gaze.network>

* perf(btc): batch insert blocks (#5)

Co-authored-by: Gaze <gazenw@users.noreply.github.com>

---------

Co-authored-by: Gaze <gazenw@users.noreply.github.com>

* feat(btc): Duplicate coinbase transaction handling (#7)

* feat(btc): tx_hash can duplicated in block v1

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): duplicate tx  will use same txin/txout from previous tx

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): prevent revert block v1 data

if you really want to revert the data before the block version 2, you should reset the database and reindex the data instead.

Co-authored-by: Gaze <dev@gaze.network>

* doc(btc): update list duplicate tx hash

Co-authored-by: Gaze <dev@gaze.network>

* doc(btc): update docs

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): use last v1 block instead

Co-authored-by: Gaze <dev@gaze.network>

---------

Co-authored-by: Gaze <gazenw@users.noreply.github.com>

* feat: add ping handler

* fix: type

Co-authored-by: Gaze <dev@gaze.network>

* doc: add refactor note

Co-authored-by: Gaze <dev@gaze.network>

* ci: add golang linter and test runner gh action

* ci: use go-test-action@v0

* ci: annotate test result

* ci: update running flag

* fix: try to fix malformed import path

* feat: add mock test

* ci: remove annotation ci

* ci: add annotate test result

* chore: remove unused

* feat: try testify

* feat: remove test

* ci: add go test on macos, windows and go latest version

* ci: test building

* feat: remove mock code

* ci: add sqlc diff checker action (#10)

* feat: Graceful shutdown (#8)

* feat: add shutdown function for indexer

Co-authored-by: Gaze <dev@gaze.network>

* feat: add force shutdown

Co-authored-by: Gaze <dev@gaze.network>

* revert

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): remove unused

Co-authored-by: Gaze <dev@gaze.network>

* style: go fmt

Co-authored-by: Gaze <dev@gaze.network>

* feat: separate context for worker and application

* feat: increase force shutdown timeout

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): update logging

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): update shutdown function

Co-authored-by: Gaze <dev@gaze.network>

* feat: remove wg for shutdown

Co-authored-by: Gaze <dev@gaze.network>

* feat: refactor shutdown flow

Co-authored-by: Gaze <dev@gaze.network>

* feat: update shutdown flow

Co-authored-by: Gaze <dev@gaze.network>

* feat: update maming

Co-authored-by: Gaze <dev@gaze.network>

* feat: update force shutdown logic

Co-authored-by: Gaze <dev@gaze.network>

---------

Co-authored-by: Gaze <gazenw@users.noreply.github.com>

* feat: check reporting config name

* fix: use db config in bitcoin module for runes datasource

* Add migrate commands (#2)

* feat: add migrate up

* feat: add down migration

* fix: example

* feat: change description

* fix: hardcode migration source directory

* Update README.md for public release. (#11)

* feat: initial draft for README.md

* fix: remove some sections

* feat: add block reporting to first description

* fix: reduce redundancy

* feat: update README.md

* Update README.md

* feat: update README.md

* fix: update config.yaml in README

* fix: remove redundant words

* fix: change default datasource

* fix: config.yaml comments

* feat: update README.md

* refactor(logger): format logging (#12)

* feat(logger): format main logger

* feat(logger): use duration ms for gcp output

* refactor(logger): bitcoin node logger

* refactor(logger): indexer logger

* refactor(logger): fix cmd logger

* refactor(logger): logger in config pacakge

* refactor(logger): set pgx error log level debug

* refactor(logger): btcclient datasource

* refactor: processor name

* refactor(logger): runese logger

* refactor(logger): update logger

* fix(runes): wrong btc db datasource

* refactor(logger): remove unnecessary debug log

* refactor: update logger in indexer

* fix(logger): deadlock in load()

* fix: remove unused

---------

Co-authored-by: Gaze <gazenw@users.noreply.github.com>

* feat(btc): remove unused func

* fix: fix golangci-lint error

* fix(pg): update logger level

* doc: update config example

* feat: go mod tidy

* doc: update readme

* fix: panic cause didn't handle error

* doc: update example config

* doc: update example config in readme

* feat(logger): only log error stacktrace when debug mode is on

* feat(reporting): handling invalid config error

* feat(pg): handling invalid config error

* fix: panic in get_token_info

---------

Co-authored-by: Gaze <gazenw@users.noreply.github.com>
Co-authored-by: Planxnx <thanee@cleverse.com>
Co-authored-by: Thanee Charattrakool <37617738+Planxnx@users.noreply.github.com>
2024-04-29 15:16:10 +07:00

694 lines
22 KiB
Go

package postgres
import (
"encoding/hex"
"encoding/json"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/cockroachdb/errors"
"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"
)
func uint128FromNumeric(src pgtype.Numeric) (*uint128.Uint128, error) {
if !src.Valid {
return nil, nil
}
bytes, err := src.MarshalJSON()
if err != nil {
return nil, errors.WithStack(err)
}
result, err := uint128.FromString(string(bytes))
if err != nil {
return nil, errors.WithStack(err)
}
return &result, nil
}
func numericFromUint128(src *uint128.Uint128) (pgtype.Numeric, error) {
if src == nil {
return pgtype.Numeric{}, nil
}
bytes := []byte(src.String())
var result pgtype.Numeric
err := result.UnmarshalJSON(bytes)
if err != nil {
return pgtype.Numeric{}, errors.WithStack(err)
}
return result, nil
}
func mapIndexerStateModelToType(src gen.RunesIndexerState) entity.IndexerState {
var createdAt time.Time
if src.CreatedAt.Valid {
createdAt = src.CreatedAt.Time
}
return entity.IndexerState{
DBVersion: src.DbVersion,
EventHashVersion: src.EventHashVersion,
CreatedAt: createdAt,
}
}
func mapIndexerStateTypeToParams(src entity.IndexerState) gen.SetIndexerStateParams {
return gen.SetIndexerStateParams{
DbVersion: src.DBVersion,
EventHashVersion: src.EventHashVersion,
}
}
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")
}
burnedAmount, err := uint128FromNumeric(src.BurnedAmount)
if err != nil {
return runes.RuneEntry{}, errors.Wrap(err, "failed to parse burned amount")
}
rune, err := runes.NewRuneFromString(src.Rune)
if err != nil {
return runes.RuneEntry{}, errors.Wrap(err, "failed to parse rune")
}
mints, err := uint128FromNumeric(src.Mints)
if err != nil {
return runes.RuneEntry{}, errors.Wrap(err, "failed to parse mints")
}
premine, err := uint128FromNumeric(src.Premine)
if err != nil {
return runes.RuneEntry{}, errors.Wrap(err, "failed to parse premine")
}
var completedAt time.Time
if src.CompletedAt.Valid {
completedAt = src.CompletedAt.Time
}
var completedAtHeight *uint64
if src.CompletedAtHeight.Valid {
completedAtHeight = lo.ToPtr(uint64(src.CompletedAtHeight.Int32))
}
var terms *runes.Terms
if src.Terms {
terms = &runes.Terms{}
if src.TermsAmount.Valid {
amount, err := uint128FromNumeric(src.TermsAmount)
if err != nil {
return runes.RuneEntry{}, errors.Wrap(err, "failed to parse terms amount")
}
terms.Amount = amount
}
if src.TermsCap.Valid {
cap, err := uint128FromNumeric(src.TermsCap)
if err != nil {
return runes.RuneEntry{}, errors.Wrap(err, "failed to parse terms cap")
}
terms.Cap = cap
}
if src.TermsHeightStart.Valid {
heightStart := uint64(src.TermsHeightStart.Int32)
terms.HeightStart = &heightStart
}
if src.TermsHeightEnd.Valid {
heightEnd := uint64(src.TermsHeightEnd.Int32)
terms.HeightEnd = &heightEnd
}
if src.TermsOffsetStart.Valid {
offsetStart := uint64(src.TermsOffsetStart.Int32)
terms.OffsetStart = &offsetStart
}
if src.TermsOffsetEnd.Valid {
offsetEnd := uint64(src.TermsOffsetEnd.Int32)
terms.OffsetEnd = &offsetEnd
}
}
etchingTxHash, err := chainhash.NewHashFromStr(src.EtchingTxHash)
if err != nil {
return runes.RuneEntry{}, errors.Wrap(err, "failed to parse etching tx hash")
}
var etchedAt time.Time
if src.EtchedAt.Valid {
etchedAt = src.EtchedAt.Time
}
return runes.RuneEntry{
RuneId: runeId,
Number: uint64(src.Number),
Divisibility: uint8(src.Divisibility),
Premine: lo.FromPtr(premine),
SpacedRune: runes.NewSpacedRune(rune, uint32(src.Spacers)),
Symbol: src.Symbol,
Terms: terms,
Turbo: src.Turbo,
Mints: lo.FromPtr(mints),
BurnedAmount: lo.FromPtr(burnedAmount),
CompletedAt: completedAt,
CompletedAtHeight: completedAtHeight,
EtchingBlock: uint64(src.EtchingBlock),
EtchingTxHash: *etchingTxHash,
EtchedAt: etchedAt,
}, nil
}
func mapRuneEntryTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.CreateRuneEntryParams, gen.CreateRuneEntryStateParams, 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
}
var terms bool
var termsAmount, termsCap pgtype.Numeric
var termsHeightStart, termsHeightEnd, termsOffsetStart, termsOffsetEnd pgtype.Int4
if src.Terms != nil {
terms = true
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")
}
}
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")
}
}
if src.Terms.HeightStart != nil {
termsHeightStart = pgtype.Int4{
Int32: int32(*src.Terms.HeightStart),
Valid: true,
}
}
if src.Terms.HeightEnd != nil {
termsHeightEnd = pgtype.Int4{
Int32: int32(*src.Terms.HeightEnd),
Valid: true,
}
}
if src.Terms.OffsetStart != nil {
termsOffsetStart = pgtype.Int4{
Int32: int32(*src.Terms.OffsetStart),
Valid: true,
}
}
if src.Terms.OffsetEnd != nil {
termsOffsetEnd = pgtype.Int4{
Int32: int32(*src.Terms.OffsetEnd),
Valid: true,
}
}
}
etchedAt := pgtype.Timestamp{Time: time.Time{}, 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
}
// 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
}
inputsBytes, err := json.Marshal(src.Inputs)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, 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")
}
mints := make(map[string]uint128.Uint128)
for key, value := range src.Mints {
mints[key.String()] = value
}
mintsBytes, err := json.Marshal(mints)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal mints")
}
burns := make(map[string]uint128.Uint128)
for key, value := range src.Burns {
burns[key.String()] = value
}
burnsBytes, err := json.Marshal(burns)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal burns")
}
var runestoneParams *gen.CreateRunestoneParams
if src.Runestone != nil {
params, err := mapRunestoneTypeToParams(*src.Runestone, src.Hash, src.BlockHeight)
if err != nil {
return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to map runestone to params")
}
runestoneParams = &params
}
return gen.CreateRuneTransactionParams{
Hash: src.Hash.String(),
BlockHeight: int32(src.BlockHeight),
Index: int32(src.Index),
Timestamp: timestamp,
Inputs: inputsBytes,
Outputs: outputsBytes,
Mints: mintsBytes,
Burns: burnsBytes,
RuneEtched: src.RuneEtched,
}, runestoneParams, nil
}
func extractModelRuneTxAndRunestone(src gen.GetRuneTransactionsRow) (gen.RunesTransaction, *gen.RunesRunestone, error) {
var runestone *gen.RunesRunestone
if src.TxHash.Valid {
// these fields should never be 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,
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,
Index: src.Index,
Timestamp: src.Timestamp,
Inputs: src.Inputs,
Outputs: src.Outputs,
Mints: src.Mints,
Burns: src.Burns,
RuneEtched: src.RuneEtched,
}, 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.TxInputOutput, 0)
if err := json.Unmarshal(src.Inputs, &inputs); err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to unmarshal inputs")
}
outputs := make([]*entity.TxInputOutput, 0)
if err := json.Unmarshal(src.Outputs, &outputs); err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to unmarshal outputs")
}
mintsRaw := make(map[string]uint128.Uint128)
if err := json.Unmarshal(src.Mints, &mintsRaw); err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to unmarshal mints")
}
mints := make(map[runes.RuneId]uint128.Uint128)
for key, value := range mintsRaw {
runeId, err := runes.NewRuneIdFromString(key)
if err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to parse rune id")
}
mints[runeId] = value
}
burnsRaw := make(map[string]uint128.Uint128)
if err := json.Unmarshal(src.Burns, &burnsRaw); err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to unmarshal burns")
}
burns := make(map[runes.RuneId]uint128.Uint128)
for key, value := range burnsRaw {
runeId, err := runes.NewRuneIdFromString(key)
if err != nil {
return entity.RuneTransaction{}, errors.Wrap(err, "failed to parse rune id")
}
burns[runeId] = value
}
return entity.RuneTransaction{
Hash: *hash,
BlockHeight: uint64(src.BlockHeight),
Index: uint32(src.Index),
Timestamp: timestamp,
Inputs: inputs,
Outputs: outputs,
Mints: mints,
Burns: burns,
RuneEtched: src.RuneEtched,
}, nil
}
func mapRunestoneTypeToParams(src runes.Runestone, txHash chainhash.Hash, blockHeight uint64) (gen.CreateRunestoneParams, error) {
var runestoneParams gen.CreateRunestoneParams
// TODO: optimize serialized edicts
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: *etching.Symbol, Valid: true}
}
if etching.Terms != nil {
runestoneParams.EtchingTerms = pgtype.Bool{Bool: true, Valid: 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}
}
}
runestoneParams.EtchingTurbo = pgtype.Bool{Bool: etching.Turbo, 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 {
var symbol rune = src.EtchingSymbol.Int32
etching.Symbol = &symbol
}
if src.EtchingTerms.Valid && src.EtchingTerms.Bool {
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
}
etching.Turbo = src.EtchingTurbo.Valid && src.EtchingTurbo.Bool
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 {
return nil, errors.Wrap(err, "failed to parse rune id")
}
amount, err := uint128FromNumeric(src.Amount)
if err != nil {
return nil, errors.Wrap(err, "failed to parse balance")
}
pkScript, err := hex.DecodeString(src.Pkscript)
if err != nil {
return nil, errors.Wrap(err, "failed to parse pkscript")
}
return &entity.Balance{
PkScript: pkScript,
RuneId: runeId,
Amount: lo.FromPtr(amount),
BlockHeight: uint64(src.BlockHeight),
}, nil
}
func mapIndexedBlockModelToType(src gen.RunesIndexedBlock) (*entity.IndexedBlock, error) {
hash, err := chainhash.NewHashFromStr(src.Hash)
if err != nil {
return nil, errors.Wrap(err, "failed to parse block hash")
}
prevBlockHash, err := chainhash.NewHashFromStr(src.PrevHash)
if err != nil {
return nil, errors.Wrap(err, "failed to parse prev block hash")
}
eventHash, err := chainhash.NewHashFromStr(src.EventHash)
if err != nil {
return nil, errors.Wrap(err, "failed to parse event hash")
}
cumulativeEventHash, err := chainhash.NewHashFromStr(src.CumulativeEventHash)
if err != nil {
return nil, errors.Wrap(err, "failed to parse cumulative event hash")
}
return &entity.IndexedBlock{
Height: int64(src.Height),
Hash: *hash,
PrevHash: *prevBlockHash,
EventHash: *eventHash,
CumulativeEventHash: *cumulativeEventHash,
}, nil
}
func mapIndexedBlockTypeToParams(src entity.IndexedBlock) (gen.CreateIndexedBlockParams, error) {
return gen.CreateIndexedBlockParams{
Height: int32(src.Height),
Hash: src.Hash.String(),
PrevHash: src.PrevHash.String(),
EventHash: src.EventHash.String(),
CumulativeEventHash: src.CumulativeEventHash.String(),
}, nil
}
func mapOutPointBalanceModelToType(src gen.RunesOutpointBalance) (entity.OutPointBalance, error) {
runeId, err := runes.NewRuneIdFromString(src.RuneID)
if err != nil {
return entity.OutPointBalance{}, errors.Wrap(err, "failed to parse rune id")
}
amount, err := uint128FromNumeric(src.Amount)
if err != nil {
return entity.OutPointBalance{}, errors.Wrap(err, "failed to parse balance")
}
pkScript, err := hex.DecodeString(src.Pkscript)
if err != nil {
return entity.OutPointBalance{}, errors.Wrap(err, "failed to parse pkscript")
}
txHash, err := chainhash.NewHashFromStr(src.TxHash)
if err != nil {
return entity.OutPointBalance{}, errors.Wrap(err, "failed to parse tx hash")
}
var spentHeight *uint64
if src.SpentHeight.Valid {
spentHeight = lo.ToPtr(uint64(src.SpentHeight.Int32))
}
return entity.OutPointBalance{
PkScript: pkScript,
RuneId: runeId,
Amount: lo.FromPtr(amount),
OutPoint: wire.OutPoint{
Hash: *txHash,
Index: uint32(src.TxIdx),
},
BlockHeight: uint64(src.BlockHeight),
SpentHeight: spentHeight,
}, nil
}
func mapOutPointBalanceTypeToParams(src entity.OutPointBalance) (gen.CreateOutPointBalancesParams, error) {
amount, err := numericFromUint128(&src.Amount)
if err != nil {
return gen.CreateOutPointBalancesParams{}, 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{
TxHash: src.OutPoint.Hash.String(),
TxIdx: int32(src.OutPoint.Index),
Pkscript: hex.EncodeToString(src.PkScript),
RuneID: src.RuneId.String(),
Amount: amount,
BlockHeight: int32(src.BlockHeight),
SpentHeight: spentHeight,
}, nil
}