mirror of
https://github.com/alexgo-io/gaze-brc20-indexer.git
synced 2026-01-12 14:34:54 +08:00
* 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>
373 lines
11 KiB
Go
373 lines
11 KiB
Go
package runes
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/cockroachdb/errors"
|
|
"github.com/gaze-network/indexer-network/core/types"
|
|
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
|
|
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
|
"github.com/gaze-network/uint128"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
// TODO: implement test to ensure that the event hash is calculated the same way for same version
|
|
func (p *Processor) calculateEventHash(header types.BlockHeader) (chainhash.Hash, error) {
|
|
payload, err := p.getHashPayload(header)
|
|
if err != nil {
|
|
return chainhash.Hash{}, errors.Wrap(err, "failed to get hash payload")
|
|
}
|
|
return chainhash.DoubleHashH(payload), nil
|
|
}
|
|
|
|
func (p *Processor) getHashPayload(header types.BlockHeader) ([]byte, error) {
|
|
var sb strings.Builder
|
|
sb.WriteString("payload:v" + strconv.Itoa(EventHashVersion) + ":")
|
|
sb.WriteString("blockHash:")
|
|
sb.Write(header.Hash[:])
|
|
|
|
// serialize new rune entries
|
|
{
|
|
runeEntries := lo.Values(p.newRuneEntries)
|
|
slices.SortFunc(runeEntries, func(t1, t2 *runes.RuneEntry) int {
|
|
return int(t1.Number) - int(t2.Number)
|
|
})
|
|
for _, entry := range runeEntries {
|
|
sb.Write(serializeNewRuneEntry(entry))
|
|
}
|
|
}
|
|
// serialize new rune entry states
|
|
{
|
|
runeIds := lo.Keys(p.newRuneEntryStates)
|
|
slices.SortFunc(runeIds, func(t1, t2 runes.RuneId) int {
|
|
return t1.Cmp(t2)
|
|
})
|
|
for _, runeId := range runeIds {
|
|
sb.Write(serializeNewRuneEntryState(p.newRuneEntryStates[runeId]))
|
|
}
|
|
}
|
|
// serialize new out point balances
|
|
sb.Write(serializeNewOutPointBalances(p.newOutPointBalances))
|
|
|
|
// serialize spend out points
|
|
sb.Write(serializeSpendOutPoints(p.newSpendOutPoints))
|
|
|
|
// serialize new balances
|
|
{
|
|
bytes, err := serializeNewBalances(p.newBalances)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to serialize new balances")
|
|
}
|
|
sb.Write(bytes)
|
|
}
|
|
|
|
// serialize new txs
|
|
// sort txs by block height and index
|
|
{
|
|
bytes, err := serializeRuneTxs(p.newRuneTxs)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to serialize new rune txs")
|
|
}
|
|
sb.Write(bytes)
|
|
}
|
|
return []byte(sb.String()), nil
|
|
}
|
|
|
|
func serializeNewRuneEntry(entry *runes.RuneEntry) []byte {
|
|
var sb strings.Builder
|
|
sb.WriteString("newRuneEntry:")
|
|
// nolint:goconst
|
|
sb.WriteString("runeId:" + entry.RuneId.String())
|
|
sb.WriteString("number:" + strconv.Itoa(int(entry.Number)))
|
|
sb.WriteString("divisibility:" + strconv.Itoa(int(entry.Divisibility)))
|
|
sb.WriteString("premine:" + entry.Premine.String())
|
|
sb.WriteString("rune:" + entry.SpacedRune.Rune.String())
|
|
sb.WriteString("spacers:" + strconv.Itoa(int(entry.SpacedRune.Spacers)))
|
|
sb.WriteString("symbol:" + string(entry.Symbol))
|
|
if entry.Terms != nil {
|
|
sb.WriteString("terms:")
|
|
terms := entry.Terms
|
|
if terms.Amount != nil {
|
|
// nolint:goconst
|
|
sb.WriteString("amount:" + terms.Amount.String())
|
|
}
|
|
if terms.Cap != nil {
|
|
sb.WriteString("cap:" + terms.Cap.String())
|
|
}
|
|
if terms.HeightStart != nil {
|
|
sb.WriteString("heightStart:" + strconv.Itoa(int(*terms.HeightStart)))
|
|
}
|
|
if terms.HeightEnd != nil {
|
|
sb.WriteString("heightEnd:" + strconv.Itoa(int(*terms.HeightEnd)))
|
|
}
|
|
if terms.OffsetStart != nil {
|
|
sb.WriteString("offsetStart:" + strconv.Itoa(int(*terms.OffsetStart)))
|
|
}
|
|
if terms.OffsetEnd != nil {
|
|
sb.WriteString("offsetEnd:" + strconv.Itoa(int(*terms.OffsetEnd)))
|
|
}
|
|
}
|
|
sb.WriteString("turbo:" + strconv.FormatBool(entry.Turbo))
|
|
sb.WriteString("etchingBlock:" + strconv.Itoa(int(entry.EtchingBlock)))
|
|
sb.WriteString("etchingTxHash:" + entry.EtchingTxHash.String())
|
|
sb.WriteString("etchedAt:" + strconv.Itoa(int(entry.EtchedAt.Unix())))
|
|
sb.WriteString(";")
|
|
return []byte(sb.String())
|
|
}
|
|
|
|
func serializeNewRuneEntryState(entry *runes.RuneEntry) []byte {
|
|
var sb strings.Builder
|
|
sb.WriteString("newRuneEntryState:")
|
|
// write only mutable states
|
|
sb.WriteString("runeId:" + entry.RuneId.String())
|
|
sb.WriteString("mints:" + entry.Mints.String())
|
|
sb.WriteString("burnedAmount:" + entry.BurnedAmount.String())
|
|
if entry.CompletedAtHeight != nil {
|
|
sb.WriteString("completedAtHeight:" + strconv.Itoa(int(*entry.CompletedAtHeight)))
|
|
sb.WriteString("completedAt:" + strconv.Itoa(int(entry.CompletedAt.Unix())))
|
|
}
|
|
sb.WriteString(";")
|
|
return []byte(sb.String())
|
|
}
|
|
|
|
func serializeNewOutPointBalances(outPointBalances map[wire.OutPoint][]*entity.OutPointBalance) []byte {
|
|
var sb strings.Builder
|
|
sb.WriteString("newOutPointBalances:")
|
|
|
|
// collect balance values
|
|
newBalances := make([]*entity.OutPointBalance, 0)
|
|
for _, balances := range outPointBalances {
|
|
newBalances = append(newBalances, balances...)
|
|
}
|
|
|
|
// sort balances to ensure order
|
|
slices.SortFunc(newBalances, func(t1, t2 *entity.OutPointBalance) int {
|
|
// sort by outpoint first
|
|
if t1.OutPoint != t2.OutPoint {
|
|
if t1.OutPoint.Hash != t2.OutPoint.Hash {
|
|
return bytes.Compare(t1.OutPoint.Hash[:], t2.OutPoint.Hash[:])
|
|
}
|
|
return int(t1.OutPoint.Index) - int(t2.OutPoint.Index)
|
|
}
|
|
// sort by runeId
|
|
return t1.RuneId.Cmp(t2.RuneId)
|
|
})
|
|
for _, balance := range newBalances {
|
|
sb.WriteString("outPoint:")
|
|
sb.WriteString("hash:")
|
|
sb.Write(balance.OutPoint.Hash[:])
|
|
sb.WriteString("index:" + strconv.Itoa(int(balance.OutPoint.Index)))
|
|
sb.WriteString("pkScript:")
|
|
sb.Write(balance.PkScript)
|
|
sb.WriteString("runeId:" + balance.RuneId.String())
|
|
sb.WriteString("amount:" + balance.Amount.String())
|
|
sb.WriteString(";")
|
|
}
|
|
return []byte(sb.String())
|
|
}
|
|
|
|
func serializeSpendOutPoints(spendOutPoints []wire.OutPoint) []byte {
|
|
var sb strings.Builder
|
|
sb.WriteString("spendOutPoints:")
|
|
// sort outpoints to ensure order
|
|
slices.SortFunc(spendOutPoints, func(t1, t2 wire.OutPoint) int {
|
|
if t1.Hash != t2.Hash {
|
|
return bytes.Compare(t1.Hash[:], t2.Hash[:])
|
|
}
|
|
return int(t1.Index) - int(t2.Index)
|
|
})
|
|
for _, outPoint := range spendOutPoints {
|
|
sb.WriteString("hash:")
|
|
sb.Write(outPoint.Hash[:])
|
|
sb.WriteString("index:" + strconv.Itoa(int(outPoint.Index)))
|
|
sb.WriteString(";")
|
|
}
|
|
return []byte(sb.String())
|
|
}
|
|
|
|
func serializeNewBalances(balances map[string]map[runes.RuneId]uint128.Uint128) ([]byte, error) {
|
|
var sb strings.Builder
|
|
sb.WriteString("newBalances:")
|
|
|
|
pkScriptStrs := lo.Keys(balances)
|
|
// sort pkScripts to ensure order
|
|
slices.SortFunc(pkScriptStrs, func(t1, t2 string) int {
|
|
return strings.Compare(t1, t2)
|
|
})
|
|
for _, pkScriptStr := range pkScriptStrs {
|
|
runeIds := lo.Keys(balances[pkScriptStr])
|
|
// sort runeIds to ensure order
|
|
slices.SortFunc(runeIds, func(t1, t2 runes.RuneId) int {
|
|
return t1.Cmp(t2)
|
|
})
|
|
pkScript, err := hex.DecodeString(pkScriptStr)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode pkScript")
|
|
}
|
|
for _, runeId := range runeIds {
|
|
sb.WriteString("pkScript:")
|
|
sb.Write(pkScript)
|
|
sb.WriteString("runeId:" + runeId.String())
|
|
sb.WriteString("amount:" + balances[pkScriptStr][runeId].String())
|
|
sb.WriteString(";")
|
|
}
|
|
}
|
|
return []byte(sb.String()), nil
|
|
}
|
|
|
|
func serializeRuneTxs(txs []*entity.RuneTransaction) ([]byte, error) {
|
|
var sb strings.Builder
|
|
|
|
slices.SortFunc(txs, func(t1, t2 *entity.RuneTransaction) int {
|
|
if t1.BlockHeight != t2.BlockHeight {
|
|
return int(t1.BlockHeight) - int(t2.BlockHeight)
|
|
}
|
|
return int(t1.Index) - int(t2.Index)
|
|
})
|
|
|
|
sb.WriteString("txs:")
|
|
for _, tx := range txs {
|
|
sb.WriteString("hash:")
|
|
sb.Write(tx.Hash[:])
|
|
sb.WriteString("blockHeight:" + strconv.Itoa(int(tx.BlockHeight)))
|
|
sb.WriteString("index:" + strconv.Itoa(int(tx.Index)))
|
|
|
|
writeOutPointBalance := func(ob *entity.TxInputOutput) {
|
|
sb.WriteString("pkScript:")
|
|
sb.Write(ob.PkScript)
|
|
sb.WriteString("runeId:" + ob.RuneId.String())
|
|
sb.WriteString("amount:" + ob.Amount.String())
|
|
sb.WriteString("index:" + strconv.Itoa(int(ob.Index)))
|
|
sb.WriteString("txHash:")
|
|
sb.Write(ob.TxHash[:])
|
|
sb.WriteString("txOutIndex:" + strconv.Itoa(int(ob.TxOutIndex)))
|
|
sb.WriteString(";")
|
|
}
|
|
// sort inputs to ensure order
|
|
slices.SortFunc(tx.Inputs, func(t1, t2 *entity.TxInputOutput) int {
|
|
if t1.Index != t2.Index {
|
|
return int(t1.Index) - int(t2.Index)
|
|
}
|
|
return t1.RuneId.Cmp(t2.RuneId)
|
|
})
|
|
|
|
sb.WriteString("in:")
|
|
for _, in := range tx.Inputs {
|
|
writeOutPointBalance(in)
|
|
}
|
|
// sort outputs to ensure order
|
|
slices.SortFunc(tx.Inputs, func(t1, t2 *entity.TxInputOutput) int {
|
|
if t1.Index != t2.Index {
|
|
return int(t1.Index) - int(t2.Index)
|
|
}
|
|
return t1.RuneId.Cmp(t2.RuneId)
|
|
})
|
|
sb.WriteString("out:")
|
|
for _, out := range tx.Outputs {
|
|
writeOutPointBalance(out)
|
|
}
|
|
|
|
mintsKeys := lo.Keys(tx.Mints)
|
|
slices.SortFunc(mintsKeys, func(t1, t2 runes.RuneId) int {
|
|
return t1.Cmp(t2)
|
|
})
|
|
sb.WriteString("mints:")
|
|
for _, runeId := range mintsKeys {
|
|
amount := tx.Mints[runeId]
|
|
sb.WriteString(runeId.String())
|
|
sb.WriteString(amount.String())
|
|
sb.WriteString(";")
|
|
}
|
|
|
|
burnsKeys := lo.Keys(tx.Burns)
|
|
slices.SortFunc(mintsKeys, func(t1, t2 runes.RuneId) int {
|
|
return t1.Cmp(t2)
|
|
})
|
|
sb.WriteString("burns:")
|
|
for _, runeId := range burnsKeys {
|
|
amount := tx.Burns[runeId]
|
|
sb.WriteString(runeId.String())
|
|
sb.WriteString(amount.String())
|
|
sb.WriteString(";")
|
|
}
|
|
sb.WriteString("runeEtched:" + strconv.FormatBool(tx.RuneEtched))
|
|
|
|
sb.Write(serializeRunestoneForEventHash(tx.Runestone))
|
|
sb.WriteString(";")
|
|
}
|
|
return []byte(sb.String()), nil
|
|
}
|
|
|
|
func serializeRunestoneForEventHash(r *runes.Runestone) []byte {
|
|
if r == nil {
|
|
return []byte("rune:nil")
|
|
}
|
|
var sb strings.Builder
|
|
sb.WriteString("rune:")
|
|
if r.Etching != nil {
|
|
etching := r.Etching
|
|
sb.WriteString("etching:")
|
|
if etching.Divisibility != nil {
|
|
sb.WriteString("divisibility:" + strconv.Itoa(int(*etching.Divisibility)))
|
|
}
|
|
if etching.Premine != nil {
|
|
sb.WriteString("premine:" + etching.Premine.String())
|
|
}
|
|
if etching.Rune != nil {
|
|
sb.WriteString("rune:" + etching.Rune.String())
|
|
}
|
|
if etching.Spacers != nil {
|
|
sb.WriteString("spacers:" + strconv.Itoa(int(*etching.Spacers)))
|
|
}
|
|
if etching.Symbol != nil {
|
|
sb.WriteString("symbol:" + string(*etching.Symbol))
|
|
}
|
|
if etching.Terms != nil {
|
|
terms := etching.Terms
|
|
if terms.Amount != nil {
|
|
sb.WriteString("amount:" + terms.Amount.String())
|
|
}
|
|
if terms.Cap != nil {
|
|
sb.WriteString("cap:" + terms.Cap.String())
|
|
}
|
|
if terms.HeightStart != nil {
|
|
sb.WriteString("heightStart:" + strconv.Itoa(int(*terms.HeightStart)))
|
|
}
|
|
if terms.HeightEnd != nil {
|
|
sb.WriteString("heightEnd:" + strconv.Itoa(int(*terms.HeightEnd)))
|
|
}
|
|
if terms.OffsetStart != nil {
|
|
sb.WriteString("offsetStart:" + strconv.Itoa(int(*terms.OffsetStart)))
|
|
}
|
|
if terms.OffsetEnd != nil {
|
|
sb.WriteString("offsetEnd:" + strconv.Itoa(int(*terms.OffsetEnd)))
|
|
}
|
|
}
|
|
if etching.Turbo {
|
|
sb.WriteString("turbo:" + strconv.FormatBool(etching.Turbo))
|
|
}
|
|
}
|
|
if len(r.Edicts) > 0 {
|
|
sb.WriteString("edicts:")
|
|
// don't sort edicts, order must be kept the same because of delta encoding
|
|
for _, edict := range r.Edicts {
|
|
sb.WriteString(edict.Id.String() + edict.Amount.String() + strconv.Itoa(edict.Output) + ";")
|
|
}
|
|
}
|
|
if r.Mint != nil {
|
|
sb.WriteString("mint:" + r.Mint.String())
|
|
}
|
|
if r.Pointer != nil {
|
|
sb.WriteString("pointer:" + strconv.Itoa(int(*r.Pointer)))
|
|
}
|
|
sb.WriteString("cenotaph:" + strconv.FormatBool(r.Cenotaph))
|
|
sb.WriteString("flaws:" + strconv.Itoa(int(r.Flaws)))
|
|
return []byte(sb.String())
|
|
}
|