diff --git a/modules/runes/database/postgresql/queries/data.sql b/modules/runes/database/postgresql/queries/data.sql index 36e9b3a..ed60e84 100644 --- a/modules/runes/database/postgresql/queries/data.sql +++ b/modules/runes/database/postgresql/queries/data.sql @@ -103,8 +103,7 @@ SELECT * FROM runes_entries @search::text = '' OR runes_entries.rune ILIKE '%' || @search::text || '%' ) - ORDER BY (COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(states.mints, 0)) / - (COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(runes_entries.terms_cap, 0))::float DESC + ORDER BY states.mints DESC LIMIT @_limit OFFSET @_offset; -- name: GetRuneIdFromRune :one @@ -137,24 +136,24 @@ SELECT * FROM runes_transactions -- name: CountRuneEntries :one SELECT COUNT(*) FROM runes_entries; --- name: CreateRuneEntry :exec +-- name: CreateRuneEntries :batchexec INSERT INTO runes_entries (rune_id, rune, number, spacers, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, turbo, etching_block, etching_tx_hash, etched_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18); --- name: CreateRuneEntryState :exec +-- name: CreateRuneEntryStates :batchexec INSERT INTO runes_entry_states (rune_id, block_height, mints, burned_amount, completed_at, completed_at_height) VALUES ($1, $2, $3, $4, $5, $6); --- name: CreateRuneTransaction :exec +-- name: CreateRuneTransactions :batchexec INSERT INTO runes_transactions (hash, block_height, index, timestamp, inputs, outputs, mints, burns, rune_etched) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9); --- name: CreateRunestone :exec +-- name: CreateRunestones :batchexec 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, etching_turbo, 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, $21); -- name: CreateOutPointBalances :batchexec INSERT INTO runes_outpoint_balances (rune_id, pkscript, tx_hash, tx_idx, amount, block_height, spent_height) VALUES ($1, $2, $3, $4, $5, $6, $7); --- name: SpendOutPointBalances :exec +-- name: SpendOutPointBalancesBatch :batchexec UPDATE runes_outpoint_balances SET spent_height = $1 WHERE tx_hash = $2 AND tx_idx = $3; -- name: CreateRuneBalanceAtBlock :batchexec diff --git a/modules/runes/datagateway/runes.go b/modules/runes/datagateway/runes.go index b673f80..aa17d41 100644 --- a/modules/runes/datagateway/runes.go +++ b/modules/runes/datagateway/runes.go @@ -65,12 +65,12 @@ type RunesReaderDataGateway interface { } type RunesWriterDataGateway interface { - CreateRuneEntry(ctx context.Context, entry *runes.RuneEntry, blockHeight uint64) error - CreateRuneEntryState(ctx context.Context, entry *runes.RuneEntry, blockHeight uint64) error + CreateRuneEntries(ctx context.Context, entries []*runes.RuneEntry, blockHeight uint64) error + CreateRuneEntryStates(ctx context.Context, entries []*runes.RuneEntry, blockHeight uint64) error CreateOutPointBalances(ctx context.Context, outPointBalances []*entity.OutPointBalance) error - SpendOutPointBalances(ctx context.Context, outPoint wire.OutPoint, blockHeight uint64) error + SpendOutPointBalancesBatch(ctx context.Context, outPoints []wire.OutPoint, blockHeight uint64) error CreateRuneBalances(ctx context.Context, params []CreateRuneBalancesParams) error - CreateRuneTransaction(ctx context.Context, tx *entity.RuneTransaction) error + CreateRuneTransactions(ctx context.Context, txs []*entity.RuneTransaction) error CreateIndexedBlock(ctx context.Context, block *entity.IndexedBlock) error // TODO: collapse these into a single function (ResetStateToHeight)? diff --git a/modules/runes/processor.go b/modules/runes/processor.go index 3c80c72..6bcfc25 100644 --- a/modules/runes/processor.go +++ b/modules/runes/processor.go @@ -145,7 +145,10 @@ func (p *Processor) ensureGenesisRune(ctx context.Context, network common.Networ EtchingTxHash: genesisRuneConfig.EtchingTxHash, EtchedAt: genesisRuneConfig.EtchedAt, } - if err := p.runesDg.CreateRuneEntry(ctx, runeEntry, genesisRuneConfig.RuneId.BlockHeight); err != nil { + if err := p.runesDg.CreateRuneEntries(ctx, []*runes.RuneEntry{runeEntry}, genesisRuneConfig.RuneId.BlockHeight); err != nil { + return errors.Wrap(err, "failed to create genesis rune entry") + } + if err := p.runesDg.CreateRuneEntryStates(ctx, []*runes.RuneEntry{runeEntry}, genesisRuneConfig.RuneId.BlockHeight); err != nil { return errors.Wrap(err, "failed to create genesis rune entry") } } diff --git a/modules/runes/processor_process.go b/modules/runes/processor_process.go index 165df69..215d886 100644 --- a/modules/runes/processor_process.go +++ b/modules/runes/processor_process.go @@ -27,19 +27,26 @@ import ( func (p *Processor) Process(ctx context.Context, blocks []*types.Block) error { for _, block := range blocks { ctx := logger.WithContext(ctx, slog.Int64("height", block.Header.Height)) - logger.DebugContext(ctx, "Processing new block", slog.Int("txs", len(block.Transactions))) + logger.InfoContext(ctx, "Processing new block", + slogx.String("event", "runes_processor_processing_block"), + slog.Int("txs", len(block.Transactions)), + ) + start := time.Now() for _, tx := range block.Transactions { if err := p.processTx(ctx, tx, block.Header); err != nil { return errors.Wrap(err, "failed to process tx") } } + timeTakenToProcess := time.Since(start) + logger.InfoContext(ctx, "Processed block", + slogx.String("event", "runes_processor_processed_block"), + slog.Duration("time_taken", timeTakenToProcess), + ) if err := p.flushBlock(ctx, block.Header); err != nil { return errors.Wrap(err, "failed to flush block") } - - logger.DebugContext(ctx, "Inserted new block") } return nil } @@ -669,6 +676,7 @@ func (p *Processor) getRunesBalancesAtOutPoint(ctx context.Context, outPoint wir } func (p *Processor) flushBlock(ctx context.Context, blockHeader types.BlockHeader) error { + start := time.Now() runesDgTx, err := p.runesDg.BeginRunesTx(ctx) if err != nil { return errors.Wrap(err, "failed to begin runes tx") @@ -715,78 +723,82 @@ func (p *Processor) flushBlock(ctx context.Context, blockHeader types.BlockHeade return errors.Wrap(err, "failed to create indexed block") } // flush new rune entries - { - for _, runeEntry := range p.newRuneEntries { - if err := runesDgTx.CreateRuneEntry(ctx, runeEntry, uint64(blockHeader.Height)); err != nil { - return errors.Wrap(err, "failed to create rune entry") - } - } - p.newRuneEntries = make(map[runes.RuneId]*runes.RuneEntry) + newRuneEntries := lo.Values(p.newRuneEntries) + if err := runesDgTx.CreateRuneEntries(ctx, newRuneEntries, uint64(blockHeader.Height)); err != nil { + return errors.Wrap(err, "failed to create rune entry") } + p.newRuneEntries = make(map[runes.RuneId]*runes.RuneEntry) + // flush new rune entry states - { - for _, runeEntry := range p.newRuneEntryStates { - if err := runesDgTx.CreateRuneEntryState(ctx, runeEntry, uint64(blockHeader.Height)); err != nil { - return errors.Wrap(err, "failed to create rune entry state") - } - } - p.newRuneEntryStates = make(map[runes.RuneId]*runes.RuneEntry) + newRuneEntryStates := lo.Values(p.newRuneEntryStates) + if err := runesDgTx.CreateRuneEntryStates(ctx, newRuneEntryStates, uint64(blockHeader.Height)); err != nil { + return errors.Wrap(err, "failed to create rune entry state") } + p.newRuneEntryStates = make(map[runes.RuneId]*runes.RuneEntry) + // flush new outpoint balances - { - newBalances := make([]*entity.OutPointBalance, 0) - for _, balances := range p.newOutPointBalances { - newBalances = append(newBalances, balances...) - } - if err := runesDgTx.CreateOutPointBalances(ctx, newBalances); err != nil { - return errors.Wrap(err, "failed to create outpoint balances") - } - p.newOutPointBalances = make(map[wire.OutPoint][]*entity.OutPointBalance) + newBalances := make([]*entity.OutPointBalance, 0) + for _, balances := range p.newOutPointBalances { + newBalances = append(newBalances, balances...) } + if err := runesDgTx.CreateOutPointBalances(ctx, newBalances); err != nil { + return errors.Wrap(err, "failed to create outpoint balances") + } + p.newOutPointBalances = make(map[wire.OutPoint][]*entity.OutPointBalance) + // flush new spend outpoints - { - for _, outPoint := range p.newSpendOutPoints { - if err := runesDgTx.SpendOutPointBalances(ctx, outPoint, uint64(blockHeader.Height)); err != nil { - return errors.Wrap(err, "failed to create spend outpoint") - } - } - p.newSpendOutPoints = make([]wire.OutPoint, 0) + newSpendOutPoints := p.newSpendOutPoints + if err := runesDgTx.SpendOutPointBalancesBatch(ctx, newSpendOutPoints, uint64(blockHeader.Height)); err != nil { + return errors.Wrap(err, "failed to create spend outpoint") } + p.newSpendOutPoints = make([]wire.OutPoint, 0) + // flush new balances - { - params := make([]datagateway.CreateRuneBalancesParams, 0) - for pkScriptStr, balances := range p.newBalances { - pkScript, err := hex.DecodeString(pkScriptStr) - if err != nil { - return errors.Wrap(err, "failed to decode pk script") - } - for runeId, balance := range balances { - params = append(params, datagateway.CreateRuneBalancesParams{ - PkScript: pkScript, - RuneId: runeId, - Balance: balance, - BlockHeight: uint64(blockHeader.Height), - }) - } + params := make([]datagateway.CreateRuneBalancesParams, 0) + for pkScriptStr, balances := range p.newBalances { + pkScript, err := hex.DecodeString(pkScriptStr) + if err != nil { + return errors.Wrap(err, "failed to decode pk script") } - if err := runesDgTx.CreateRuneBalances(ctx, params); err != nil { - return errors.Wrap(err, "failed to create balances at block") + for runeId, balance := range balances { + params = append(params, datagateway.CreateRuneBalancesParams{ + PkScript: pkScript, + RuneId: runeId, + Balance: balance, + BlockHeight: uint64(blockHeader.Height), + }) } - p.newBalances = make(map[string]map[runes.RuneId]uint128.Uint128) } + if err := runesDgTx.CreateRuneBalances(ctx, params); err != nil { + return errors.Wrap(err, "failed to create balances at block") + } + p.newBalances = make(map[string]map[runes.RuneId]uint128.Uint128) + // flush new rune transactions - { - for _, runeTx := range p.newRuneTxs { - if err := runesDgTx.CreateRuneTransaction(ctx, runeTx); err != nil { - return errors.Wrap(err, "failed to create rune transaction") - } - } - p.newRuneTxs = make([]*entity.RuneTransaction, 0) + newRuneTxs := p.newRuneTxs + if err := runesDgTx.CreateRuneTransactions(ctx, newRuneTxs); err != nil { + return errors.Wrap(err, "failed to create rune transaction") } + p.newRuneTxs = make([]*entity.RuneTransaction, 0) if err := runesDgTx.Commit(ctx); err != nil { return errors.Wrap(err, "failed to commit runes tx") } + timeTaken := time.Since(start) + logger.InfoContext(ctx, "Flushed block", + slogx.String("event", "runes_processor_flushed_block"), + slog.Int64("height", blockHeader.Height), + slog.String("hash", blockHeader.Hash.String()), + slog.String("event_hash", hex.EncodeToString(eventHash[:])), + slog.String("cumulative_event_hash", hex.EncodeToString(cumulativeEventHash[:])), + slog.Int("new_rune_entries", len(newRuneEntries)), + slog.Int("new_rune_entry_states", len(newRuneEntryStates)), + slog.Int("new_outpoint_balances", len(newBalances)), + slog.Int("new_spend_outpoints", len(newSpendOutPoints)), + slog.Int("new_balances", len(params)), + slog.Int("new_rune_txs", len(newRuneTxs)), + slogx.Duration("time_taken", timeTaken), + ) // submit event to reporting system if p.reportingClient != nil { diff --git a/modules/runes/repository/postgres/gen/batch.go b/modules/runes/repository/postgres/gen/batch.go index 7642018..17278ff 100644 --- a/modules/runes/repository/postgres/gen/batch.go +++ b/modules/runes/repository/postgres/gen/batch.go @@ -128,3 +128,344 @@ func (b *CreateRuneBalanceAtBlockBatchResults) Close() error { b.closed = true return b.br.Close() } + +const createRuneEntries = `-- name: CreateRuneEntries :batchexec +INSERT INTO runes_entries (rune_id, rune, number, spacers, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, turbo, etching_block, etching_tx_hash, etched_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) +` + +type CreateRuneEntriesBatchResults struct { + br pgx.BatchResults + tot int + closed bool +} + +type CreateRuneEntriesParams struct { + RuneID string + Rune string + Number int64 + 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 + Turbo bool + EtchingBlock int32 + EtchingTxHash string + EtchedAt pgtype.Timestamp +} + +func (q *Queries) CreateRuneEntries(ctx context.Context, arg []CreateRuneEntriesParams) *CreateRuneEntriesBatchResults { + batch := &pgx.Batch{} + for _, a := range arg { + vals := []interface{}{ + a.RuneID, + a.Rune, + a.Number, + a.Spacers, + a.Premine, + a.Symbol, + a.Divisibility, + a.Terms, + a.TermsAmount, + a.TermsCap, + a.TermsHeightStart, + a.TermsHeightEnd, + a.TermsOffsetStart, + a.TermsOffsetEnd, + a.Turbo, + a.EtchingBlock, + a.EtchingTxHash, + a.EtchedAt, + } + batch.Queue(createRuneEntries, vals...) + } + br := q.db.SendBatch(ctx, batch) + return &CreateRuneEntriesBatchResults{br, len(arg), false} +} + +func (b *CreateRuneEntriesBatchResults) Exec(f func(int, error)) { + defer b.br.Close() + for t := 0; t < b.tot; t++ { + if b.closed { + if f != nil { + f(t, ErrBatchAlreadyClosed) + } + continue + } + _, err := b.br.Exec() + if f != nil { + f(t, err) + } + } +} + +func (b *CreateRuneEntriesBatchResults) Close() error { + b.closed = true + return b.br.Close() +} + +const createRuneEntryStates = `-- name: CreateRuneEntryStates :batchexec +INSERT INTO runes_entry_states (rune_id, block_height, mints, burned_amount, completed_at, completed_at_height) VALUES ($1, $2, $3, $4, $5, $6) +` + +type CreateRuneEntryStatesBatchResults struct { + br pgx.BatchResults + tot int + closed bool +} + +type CreateRuneEntryStatesParams struct { + RuneID string + BlockHeight int32 + Mints pgtype.Numeric + BurnedAmount pgtype.Numeric + CompletedAt pgtype.Timestamp + CompletedAtHeight pgtype.Int4 +} + +func (q *Queries) CreateRuneEntryStates(ctx context.Context, arg []CreateRuneEntryStatesParams) *CreateRuneEntryStatesBatchResults { + batch := &pgx.Batch{} + for _, a := range arg { + vals := []interface{}{ + a.RuneID, + a.BlockHeight, + a.Mints, + a.BurnedAmount, + a.CompletedAt, + a.CompletedAtHeight, + } + batch.Queue(createRuneEntryStates, vals...) + } + br := q.db.SendBatch(ctx, batch) + return &CreateRuneEntryStatesBatchResults{br, len(arg), false} +} + +func (b *CreateRuneEntryStatesBatchResults) Exec(f func(int, error)) { + defer b.br.Close() + for t := 0; t < b.tot; t++ { + if b.closed { + if f != nil { + f(t, ErrBatchAlreadyClosed) + } + continue + } + _, err := b.br.Exec() + if f != nil { + f(t, err) + } + } +} + +func (b *CreateRuneEntryStatesBatchResults) Close() error { + b.closed = true + return b.br.Close() +} + +const createRuneTransactions = `-- name: CreateRuneTransactions :batchexec +INSERT INTO runes_transactions (hash, block_height, index, timestamp, inputs, outputs, mints, burns, rune_etched) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +` + +type CreateRuneTransactionsBatchResults struct { + br pgx.BatchResults + tot int + closed bool +} + +type CreateRuneTransactionsParams struct { + Hash string + BlockHeight int32 + Index int32 + Timestamp pgtype.Timestamp + Inputs []byte + Outputs []byte + Mints []byte + Burns []byte + RuneEtched bool +} + +func (q *Queries) CreateRuneTransactions(ctx context.Context, arg []CreateRuneTransactionsParams) *CreateRuneTransactionsBatchResults { + batch := &pgx.Batch{} + for _, a := range arg { + vals := []interface{}{ + a.Hash, + a.BlockHeight, + a.Index, + a.Timestamp, + a.Inputs, + a.Outputs, + a.Mints, + a.Burns, + a.RuneEtched, + } + batch.Queue(createRuneTransactions, vals...) + } + br := q.db.SendBatch(ctx, batch) + return &CreateRuneTransactionsBatchResults{br, len(arg), false} +} + +func (b *CreateRuneTransactionsBatchResults) Exec(f func(int, error)) { + defer b.br.Close() + for t := 0; t < b.tot; t++ { + if b.closed { + if f != nil { + f(t, ErrBatchAlreadyClosed) + } + continue + } + _, err := b.br.Exec() + if f != nil { + f(t, err) + } + } +} + +func (b *CreateRuneTransactionsBatchResults) Close() error { + b.closed = true + return b.br.Close() +} + +const createRunestones = `-- name: CreateRunestones :batchexec +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, etching_turbo, 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, $21) +` + +type CreateRunestonesBatchResults struct { + br pgx.BatchResults + tot int + closed bool +} + +type CreateRunestonesParams struct { + TxHash string + BlockHeight int32 + Etching bool + EtchingDivisibility pgtype.Int2 + EtchingPremine pgtype.Numeric + EtchingRune pgtype.Text + EtchingSpacers pgtype.Int4 + EtchingSymbol pgtype.Int4 + EtchingTerms pgtype.Bool + EtchingTermsAmount pgtype.Numeric + EtchingTermsCap pgtype.Numeric + EtchingTermsHeightStart pgtype.Int4 + EtchingTermsHeightEnd pgtype.Int4 + EtchingTermsOffsetStart pgtype.Int4 + EtchingTermsOffsetEnd pgtype.Int4 + EtchingTurbo pgtype.Bool + Edicts []byte + Mint pgtype.Text + Pointer pgtype.Int4 + Cenotaph bool + Flaws int32 +} + +func (q *Queries) CreateRunestones(ctx context.Context, arg []CreateRunestonesParams) *CreateRunestonesBatchResults { + batch := &pgx.Batch{} + for _, a := range arg { + vals := []interface{}{ + a.TxHash, + a.BlockHeight, + a.Etching, + a.EtchingDivisibility, + a.EtchingPremine, + a.EtchingRune, + a.EtchingSpacers, + a.EtchingSymbol, + a.EtchingTerms, + a.EtchingTermsAmount, + a.EtchingTermsCap, + a.EtchingTermsHeightStart, + a.EtchingTermsHeightEnd, + a.EtchingTermsOffsetStart, + a.EtchingTermsOffsetEnd, + a.EtchingTurbo, + a.Edicts, + a.Mint, + a.Pointer, + a.Cenotaph, + a.Flaws, + } + batch.Queue(createRunestones, vals...) + } + br := q.db.SendBatch(ctx, batch) + return &CreateRunestonesBatchResults{br, len(arg), false} +} + +func (b *CreateRunestonesBatchResults) Exec(f func(int, error)) { + defer b.br.Close() + for t := 0; t < b.tot; t++ { + if b.closed { + if f != nil { + f(t, ErrBatchAlreadyClosed) + } + continue + } + _, err := b.br.Exec() + if f != nil { + f(t, err) + } + } +} + +func (b *CreateRunestonesBatchResults) Close() error { + b.closed = true + return b.br.Close() +} + +const spendOutPointBalancesBatch = `-- name: SpendOutPointBalancesBatch :batchexec +UPDATE runes_outpoint_balances SET spent_height = $1 WHERE tx_hash = $2 AND tx_idx = $3 +` + +type SpendOutPointBalancesBatchBatchResults struct { + br pgx.BatchResults + tot int + closed bool +} + +type SpendOutPointBalancesBatchParams struct { + SpentHeight pgtype.Int4 + TxHash string + TxIdx int32 +} + +func (q *Queries) SpendOutPointBalancesBatch(ctx context.Context, arg []SpendOutPointBalancesBatchParams) *SpendOutPointBalancesBatchBatchResults { + batch := &pgx.Batch{} + for _, a := range arg { + vals := []interface{}{ + a.SpentHeight, + a.TxHash, + a.TxIdx, + } + batch.Queue(spendOutPointBalancesBatch, vals...) + } + br := q.db.SendBatch(ctx, batch) + return &SpendOutPointBalancesBatchBatchResults{br, len(arg), false} +} + +func (b *SpendOutPointBalancesBatchBatchResults) Exec(f func(int, error)) { + defer b.br.Close() + for t := 0; t < b.tot; t++ { + if b.closed { + if f != nil { + f(t, ErrBatchAlreadyClosed) + } + continue + } + _, err := b.br.Exec() + if f != nil { + f(t, err) + } + } +} + +func (b *SpendOutPointBalancesBatchBatchResults) Close() error { + b.closed = true + return b.br.Close() +} diff --git a/modules/runes/repository/postgres/gen/data.sql.go b/modules/runes/repository/postgres/gen/data.sql.go index 22846fc..39e9d09 100644 --- a/modules/runes/repository/postgres/gen/data.sql.go +++ b/modules/runes/repository/postgres/gen/data.sql.go @@ -45,168 +45,6 @@ func (q *Queries) CreateIndexedBlock(ctx context.Context, arg CreateIndexedBlock return err } -const createRuneEntry = `-- name: CreateRuneEntry :exec -INSERT INTO runes_entries (rune_id, rune, number, spacers, premine, symbol, divisibility, terms, terms_amount, terms_cap, terms_height_start, terms_height_end, terms_offset_start, terms_offset_end, turbo, etching_block, etching_tx_hash, etched_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) -` - -type CreateRuneEntryParams struct { - RuneID string - Rune string - Number int64 - 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 - Turbo bool - EtchingBlock int32 - EtchingTxHash string - EtchedAt pgtype.Timestamp -} - -func (q *Queries) CreateRuneEntry(ctx context.Context, arg CreateRuneEntryParams) error { - _, err := q.db.Exec(ctx, createRuneEntry, - arg.RuneID, - arg.Rune, - arg.Number, - arg.Spacers, - arg.Premine, - arg.Symbol, - arg.Divisibility, - arg.Terms, - arg.TermsAmount, - arg.TermsCap, - arg.TermsHeightStart, - arg.TermsHeightEnd, - arg.TermsOffsetStart, - arg.TermsOffsetEnd, - arg.Turbo, - arg.EtchingBlock, - arg.EtchingTxHash, - arg.EtchedAt, - ) - return err -} - -const createRuneEntryState = `-- name: CreateRuneEntryState :exec -INSERT INTO runes_entry_states (rune_id, block_height, mints, burned_amount, completed_at, completed_at_height) VALUES ($1, $2, $3, $4, $5, $6) -` - -type CreateRuneEntryStateParams struct { - RuneID string - BlockHeight int32 - Mints pgtype.Numeric - BurnedAmount pgtype.Numeric - CompletedAt pgtype.Timestamp - CompletedAtHeight pgtype.Int4 -} - -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.CompletedAt, - arg.CompletedAtHeight, - ) - return err -} - -const createRuneTransaction = `-- name: CreateRuneTransaction :exec -INSERT INTO runes_transactions (hash, block_height, index, timestamp, inputs, outputs, mints, burns, rune_etched) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) -` - -type CreateRuneTransactionParams struct { - Hash string - BlockHeight int32 - Index int32 - Timestamp pgtype.Timestamp - Inputs []byte - Outputs []byte - Mints []byte - Burns []byte - RuneEtched bool -} - -func (q *Queries) CreateRuneTransaction(ctx context.Context, arg CreateRuneTransactionParams) error { - _, err := q.db.Exec(ctx, createRuneTransaction, - arg.Hash, - arg.BlockHeight, - arg.Index, - arg.Timestamp, - arg.Inputs, - arg.Outputs, - arg.Mints, - arg.Burns, - arg.RuneEtched, - ) - 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, etching_turbo, 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, $21) -` - -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 pgtype.Bool - EtchingTermsAmount pgtype.Numeric - EtchingTermsCap pgtype.Numeric - EtchingTermsHeightStart pgtype.Int4 - EtchingTermsHeightEnd pgtype.Int4 - EtchingTermsOffsetStart pgtype.Int4 - EtchingTermsOffsetEnd pgtype.Int4 - EtchingTurbo pgtype.Bool - Edicts []byte - Mint pgtype.Text - Pointer pgtype.Int4 - Cenotaph 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.EtchingTurbo, - arg.Edicts, - arg.Mint, - arg.Pointer, - arg.Cenotaph, - arg.Flaws, - ) - return err -} - const deleteIndexedBlockSinceHeight = `-- name: DeleteIndexedBlockSinceHeight :exec DELETE FROM runes_indexed_blocks WHERE height >= $1 ` @@ -454,8 +292,7 @@ SELECT runes_entries.rune_id, number, rune, spacers, premine, symbol, divisibili $2::text = '' OR runes_entries.rune ILIKE '%' || $2::text || '%' ) - ORDER BY (COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(states.mints, 0)) / - (COALESCE(runes_entries.premine, 0) + COALESCE(runes_entries.terms_amount, 0) * COALESCE(runes_entries.terms_cap, 0))::float DESC + ORDER BY states.mints DESC LIMIT $4 OFFSET $3 ` @@ -1222,21 +1059,6 @@ func (q *Queries) GetTotalHoldersByRuneIds(ctx context.Context, arg GetTotalHold return items, nil } -const spendOutPointBalances = `-- name: SpendOutPointBalances :exec -UPDATE runes_outpoint_balances SET spent_height = $1 WHERE tx_hash = $2 AND tx_idx = $3 -` - -type SpendOutPointBalancesParams struct { - SpentHeight pgtype.Int4 - TxHash string - TxIdx int32 -} - -func (q *Queries) SpendOutPointBalances(ctx context.Context, arg SpendOutPointBalancesParams) error { - _, err := q.db.Exec(ctx, spendOutPointBalances, arg.SpentHeight, arg.TxHash, arg.TxIdx) - return err -} - const unspendOutPointBalancesSinceHeight = `-- name: UnspendOutPointBalancesSinceHeight :exec UPDATE runes_outpoint_balances SET spent_height = NULL WHERE spent_height >= $1 ` diff --git a/modules/runes/repository/postgres/mapper.go b/modules/runes/repository/postgres/mapper.go index 5e28511..f950690 100644 --- a/modules/runes/repository/postgres/mapper.go +++ b/modules/runes/repository/postgres/mapper.go @@ -153,21 +153,21 @@ func mapRuneEntryModelToType(src gen.GetRuneEntriesRow) (runes.RuneEntry, error) }, nil } -func mapRuneEntryTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.CreateRuneEntryParams, gen.CreateRuneEntryStateParams, error) { +func mapRuneEntryTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.CreateRuneEntriesParams, gen.CreateRuneEntryStatesParams, 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") + return gen.CreateRuneEntriesParams{}, gen.CreateRuneEntryStatesParams{}, 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") + return gen.CreateRuneEntriesParams{}, gen.CreateRuneEntryStatesParams{}, 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") + return gen.CreateRuneEntriesParams{}, gen.CreateRuneEntryStatesParams{}, errors.Wrap(err, "failed to parse premine") } var completedAt pgtype.Timestamp if !src.CompletedAt.IsZero() { @@ -187,13 +187,13 @@ func mapRuneEntryTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.Crea 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") + return gen.CreateRuneEntriesParams{}, gen.CreateRuneEntryStatesParams{}, 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") + return gen.CreateRuneEntriesParams{}, gen.CreateRuneEntryStatesParams{}, errors.Wrap(err, "failed to parse terms cap") } } if src.Terms.HeightStart != nil { @@ -223,7 +223,7 @@ func mapRuneEntryTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.Crea } etchedAt := pgtype.Timestamp{Time: src.EtchedAt, Valid: true} - return gen.CreateRuneEntryParams{ + return gen.CreateRuneEntriesParams{ RuneID: runeId, Rune: rune, Number: int64(src.Number), @@ -242,7 +242,7 @@ func mapRuneEntryTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.Crea EtchingBlock: int32(src.EtchingBlock), EtchingTxHash: src.EtchingTxHash.String(), EtchedAt: etchedAt, - }, gen.CreateRuneEntryStateParams{ + }, gen.CreateRuneEntryStatesParams{ BlockHeight: int32(blockHeight), RuneID: runeId, Mints: mints, @@ -253,7 +253,7 @@ func mapRuneEntryTypeToParams(src runes.RuneEntry, blockHeight uint64) (gen.Crea } // mapRuneTransactionModelToType returns params for creating a new rune transaction and (optionally) a runestone. -func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneTransactionParams, *gen.CreateRunestoneParams, error) { +func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneTransactionsParams, *gen.CreateRunestonesParams, error) { var timestamp pgtype.Timestamp if !src.Timestamp.IsZero() { timestamp.Time = src.Timestamp @@ -261,11 +261,11 @@ func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneT } inputsBytes, err := json.Marshal(src.Inputs) if err != nil { - return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal inputs") + return gen.CreateRuneTransactionsParams{}, 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") + return gen.CreateRuneTransactionsParams{}, nil, errors.Wrap(err, "failed to marshal outputs") } mints := make(map[string]uint128.Uint128) for key, value := range src.Mints { @@ -273,7 +273,7 @@ func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneT } mintsBytes, err := json.Marshal(mints) if err != nil { - return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal mints") + return gen.CreateRuneTransactionsParams{}, nil, errors.Wrap(err, "failed to marshal mints") } burns := make(map[string]uint128.Uint128) for key, value := range src.Burns { @@ -281,19 +281,19 @@ func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneT } burnsBytes, err := json.Marshal(burns) if err != nil { - return gen.CreateRuneTransactionParams{}, nil, errors.Wrap(err, "failed to marshal burns") + return gen.CreateRuneTransactionsParams{}, nil, errors.Wrap(err, "failed to marshal burns") } - var runestoneParams *gen.CreateRunestoneParams + var runestoneParams *gen.CreateRunestonesParams 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") + return gen.CreateRuneTransactionsParams{}, nil, errors.Wrap(err, "failed to map runestone to params") } runestoneParams = ¶ms } - return gen.CreateRuneTransactionParams{ + return gen.CreateRuneTransactionsParams{ Hash: src.Hash.String(), BlockHeight: int32(src.BlockHeight), Index: int32(src.Index), @@ -409,15 +409,15 @@ func mapRuneTransactionModelToType(src gen.RunesTransaction) (entity.RuneTransac }, nil } -func mapRunestoneTypeToParams(src runes.Runestone, txHash chainhash.Hash, blockHeight uint64) (gen.CreateRunestoneParams, error) { - var runestoneParams gen.CreateRunestoneParams +func mapRunestoneTypeToParams(src runes.Runestone, txHash chainhash.Hash, blockHeight uint64) (gen.CreateRunestonesParams, error) { + var runestoneParams gen.CreateRunestonesParams // TODO: optimize serialized edicts edictsBytes, err := json.Marshal(src.Edicts) if err != nil { - return gen.CreateRunestoneParams{}, errors.Wrap(err, "failed to marshal runestone edicts") + return gen.CreateRunestonesParams{}, errors.Wrap(err, "failed to marshal runestone edicts") } - runestoneParams = gen.CreateRunestoneParams{ + runestoneParams = gen.CreateRunestonesParams{ TxHash: txHash.String(), BlockHeight: int32(blockHeight), Edicts: edictsBytes, @@ -433,7 +433,7 @@ func mapRunestoneTypeToParams(src runes.Runestone, txHash chainhash.Hash, blockH if etching.Premine != nil { premine, err := numericFromUint128(etching.Premine) if err != nil { - return gen.CreateRunestoneParams{}, errors.Wrap(err, "failed to parse etching premine") + return gen.CreateRunestonesParams{}, errors.Wrap(err, "failed to parse etching premine") } runestoneParams.EtchingPremine = premine } @@ -452,14 +452,14 @@ func mapRunestoneTypeToParams(src runes.Runestone, txHash chainhash.Hash, blockH if terms.Amount != nil { amount, err := numericFromUint128(terms.Amount) if err != nil { - return gen.CreateRunestoneParams{}, errors.Wrap(err, "failed to parse etching terms amount") + return gen.CreateRunestonesParams{}, 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") + return gen.CreateRunestonesParams{}, errors.Wrap(err, "failed to parse etching terms cap") } runestoneParams.EtchingTermsCap = cap } diff --git a/modules/runes/repository/postgres/runes.go b/modules/runes/repository/postgres/runes.go index 83a4b33..567545c 100644 --- a/modules/runes/repository/postgres/runes.go +++ b/modules/runes/repository/postgres/runes.go @@ -478,49 +478,94 @@ func (r *Repository) GetTotalHoldersByRuneIds(ctx context.Context, runeIds []run return holders, nil } -func (r *Repository) CreateRuneTransaction(ctx context.Context, tx *entity.RuneTransaction) error { - if tx == nil { +func (r *Repository) CreateRuneTransactions(ctx context.Context, txs []*entity.RuneTransaction) error { + if len(txs) == 0 { return nil } - txParams, runestoneParams, err := mapRuneTransactionTypeToParams(*tx) - if err != nil { - return errors.Wrap(err, "failed to map rune transaction to params") + txParams := make([]gen.CreateRuneTransactionsParams, 0, len(txs)) + runestoneParams := make([]gen.CreateRunestonesParams, 0, len(txs)) + for _, tx := range txs { + txParam, runestoneParam, err := mapRuneTransactionTypeToParams(*tx) + if err != nil { + return errors.Wrap(err, "failed to map rune transaction to params") + } + txParams = append(txParams, txParam) + if runestoneParam != nil { + runestoneParams = append(runestoneParams, *runestoneParam) + } } - if err = r.queries.CreateRuneTransaction(ctx, txParams); err != nil { - return errors.Wrap(err, "error during exec CreateRuneTransaction") + createTxResults := r.queries.CreateRuneTransactions(ctx, txParams) + var execErrors []error + createTxResults.Exec(func(i int, err error) { + if err != nil { + execErrors = append(execErrors, err) + } + }) + if len(execErrors) > 0 { + return errors.Wrap(errors.Join(execErrors...), "error during exec CreateRuneTransactions") } - if runestoneParams != nil { - if err = r.queries.CreateRunestone(ctx, *runestoneParams); err != nil { - return errors.Wrap(err, "error during exec CreateRunestone") + + createRunestoneResults := r.queries.CreateRunestones(ctx, runestoneParams) + execErrors = make([]error, 0) + createRunestoneResults.Exec(func(i int, err error) { + if err != nil { + execErrors = append(execErrors, err) } + }) + if len(execErrors) > 0 { + return errors.Wrap(errors.Join(execErrors...), "error during exec CreateRunestones") } return nil } -func (r *Repository) CreateRuneEntry(ctx context.Context, entry *runes.RuneEntry, blockHeight uint64) error { - if entry == nil { +func (r *Repository) CreateRuneEntries(ctx context.Context, entries []*runes.RuneEntry, blockHeight uint64) error { + if len(entries) == 0 { return nil } - createParams, _, err := mapRuneEntryTypeToParams(*entry, blockHeight) - if err != nil { - return errors.Wrap(err, "failed to map rune entry to params") + createParams := make([]gen.CreateRuneEntriesParams, 0, len(entries)) + for _, entry := range entries { + param, _, err := mapRuneEntryTypeToParams(*entry, blockHeight) + if err != nil { + return errors.Wrap(err, "failed to map rune entry to params") + } + createParams = append(createParams, param) } - if err = r.queries.CreateRuneEntry(ctx, createParams); err != nil { - return errors.Wrap(err, "error during exec CreateRuneEntry") + + results := r.queries.CreateRuneEntries(ctx, createParams) + var execErrors []error + results.Exec(func(i int, err error) { + if err != nil { + execErrors = append(execErrors, err) + } + }) + if len(execErrors) > 0 { + return errors.Wrap(errors.Join(execErrors...), "error during exec CreateRuneEntries") } return nil } -func (r *Repository) CreateRuneEntryState(ctx context.Context, entry *runes.RuneEntry, blockHeight uint64) error { - if entry == nil { +func (r *Repository) CreateRuneEntryStates(ctx context.Context, entries []*runes.RuneEntry, blockHeight uint64) error { + if len(entries) == 0 { return nil } - _, createStateParams, err := mapRuneEntryTypeToParams(*entry, blockHeight) - if err != nil { - return errors.Wrap(err, "failed to map rune entry to params") + createParams := make([]gen.CreateRuneEntryStatesParams, 0, len(entries)) + for _, entry := range entries { + _, param, err := mapRuneEntryTypeToParams(*entry, blockHeight) + if err != nil { + return errors.Wrap(err, "failed to map rune entry to params") + } + createParams = append(createParams, param) } - if err = r.queries.CreateRuneEntryState(ctx, createStateParams); err != nil { - return errors.Wrap(err, "error during exec CreateRuneEntryState") + + results := r.queries.CreateRuneEntryStates(ctx, createParams) + var execErrors []error + results.Exec(func(i int, err error) { + if err != nil { + execErrors = append(execErrors, err) + } + }) + if len(execErrors) > 0 { + return errors.Wrap(errors.Join(execErrors...), "error during exec CreateRuneEntryStates") } return nil } @@ -547,13 +592,25 @@ func (r *Repository) CreateOutPointBalances(ctx context.Context, outPointBalance return nil } -func (r *Repository) SpendOutPointBalances(ctx context.Context, outPoint wire.OutPoint, blockHeight uint64) error { - if err := r.queries.SpendOutPointBalances(ctx, gen.SpendOutPointBalancesParams{ - TxHash: outPoint.Hash.String(), - TxIdx: int32(outPoint.Index), - SpentHeight: pgtype.Int4{Int32: int32(blockHeight), Valid: true}, - }); err != nil { - return errors.Wrap(err, "error during exec") +func (r *Repository) SpendOutPointBalancesBatch(ctx context.Context, outPoints []wire.OutPoint, blockHeight uint64) error { + params := make([]gen.SpendOutPointBalancesBatchParams, 0, len(outPoints)) + for _, outPoint := range outPoints { + params = append(params, gen.SpendOutPointBalancesBatchParams{ + TxHash: outPoint.Hash.String(), + TxIdx: int32(outPoint.Index), + SpentHeight: pgtype.Int4{Int32: int32(blockHeight), Valid: true}, + }) + } + + results := r.queries.SpendOutPointBalancesBatch(ctx, params) + var execErrors []error + results.Exec(func(i int, err error) { + if err != nil { + execErrors = append(execErrors, err) + } + }) + if len(execErrors) > 0 { + return errors.Wrap(errors.Join(execErrors...), "error during exec") } return nil }