Merge branch 'feature/bitcoin-indexer' into feat/runes-module

This commit is contained in:
Gaze
2024-04-10 22:09:03 +07:00
7 changed files with 125 additions and 25 deletions

View File

@@ -6,7 +6,11 @@ type ErrorKind string
const (
// NotFound is returned when a requested item is not found.
NotFound = ErrorKind("Not Found")
NotFound = ErrorKind("Not Found")
// InternalError is returned when internal logic got error
InternalError = ErrorKind("Internal Error")
OverflowUint64 = ErrorKind("overflow uint64")
OverflowUint128 = ErrorKind("overflow uint128")
)

View File

@@ -0,0 +1,12 @@
package btcclient
import (
"context"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/gaze-network/indexer-network/core/types"
)
type Contract interface {
GetTransaction(ctx context.Context, txHash chainhash.Hash) (*types.Transaction, error)
}

View File

@@ -18,7 +18,7 @@ INSERT INTO bitcoin_transaction_txouts ("tx_hash","tx_idx","pkscript","value") V
WITH update_txout AS (
UPDATE "bitcoin_transaction_txouts"
SET "is_spent" = true
WHERE "tx_hash" = $3 AND "tx_idx" = $4
WHERE "tx_hash" = $3 AND "tx_idx" = $4 AND "is_spent" = false -- TODO: should throw an error if already spent
RETURNING "pkscript"
)
INSERT INTO bitcoin_transaction_txins ("tx_hash","tx_idx","prevout_tx_hash","prevout_tx_idx","prevout_pkscript","scriptsig","witness","sequence")

View File

@@ -0,0 +1,69 @@
package postgres
import (
"context"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/core/types"
)
func (r *Repository) GetLatestBlockHeader(ctx context.Context) (types.BlockHeader, error) {
model, err := r.queries.GetLatestBlockHeader(ctx)
if err != nil {
return types.BlockHeader{}, errors.Wrap(err, "failed to get latest block header")
}
data, err := mapBlockHeaderModelToType(model)
if err != nil {
return types.BlockHeader{}, errors.Wrap(err, "failed to map block header model to type")
}
return data, nil
}
func (r *Repository) InsertBlock(ctx context.Context, block *types.Block) error {
blockParams, txParams, txoutParams, txinParams := mapBlockTypeToParams(block)
tx, err := r.db.Begin(ctx)
if err != nil {
return errors.Wrap(err, "failed to begin transaction")
}
defer func() {
if r := recover(); r != nil {
_ = tx.Rollback(ctx)
panic(r) // re-throw panic after rollback
}
}()
queries := r.queries.WithTx(tx)
if err := queries.InsertBlock(ctx, blockParams); err != nil {
_ = tx.Rollback(ctx)
return errors.Wrapf(err, "failed to insert block, height: %d, hash: %s", blockParams.BlockHeight, blockParams.BlockHash)
}
for _, params := range txParams {
if err := queries.InsertTransaction(ctx, params); err != nil {
_ = tx.Rollback(ctx)
return errors.Wrapf(err, "failed to insert transaction, hash: %s", params.TxHash)
}
}
// Should insert txout first, then txin
// Because txin references txout
for _, params := range txoutParams {
if err := queries.InsertTransactionTxOut(ctx, params); err != nil {
_ = tx.Rollback(ctx)
return errors.Wrapf(err, "failed to insert transaction txout, %v:%v", params.TxHash, params.TxIdx)
}
}
for _, params := range txinParams {
if err := queries.InsertTransactionTxIn(ctx, params); err != nil {
_ = tx.Rollback(ctx)
return errors.Wrapf(err, "failed to insert transaction txin, %v:%v", params.TxHash, params.TxIdx)
}
}
return nil
}

View File

@@ -93,7 +93,7 @@ const insertTransactionTxIn = `-- name: InsertTransactionTxIn :exec
WITH update_txout AS (
UPDATE "bitcoin_transaction_txouts"
SET "is_spent" = true
WHERE "tx_hash" = $3 AND "tx_idx" = $4
WHERE "tx_hash" = $3 AND "tx_idx" = $4 AND "is_spent" = false -- TODO: should throw an error if already spent
RETURNING "pkscript"
)
INSERT INTO bitcoin_transaction_txins ("tx_hash","tx_idx","prevout_tx_hash","prevout_tx_idx","prevout_pkscript","scriptsig","witness","sequence")

View File

@@ -5,12 +5,13 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common/errs"
"github.com/gaze-network/indexer-network/core/types"
"github.com/gaze-network/indexer-network/modules/bitcoin/internal/repository/postgres/gen"
"github.com/jackc/pgx/v5/pgtype"
)
func mapBlockHeaderTypeToModel(src *types.BlockHeader) gen.BitcoinBlock {
func mapBlockHeaderTypeToModel(src types.BlockHeader) gen.BitcoinBlock {
return gen.BitcoinBlock{
BlockHeight: int32(src.Height),
BlockHash: src.Hash.String(),
@@ -26,20 +27,20 @@ func mapBlockHeaderTypeToModel(src *types.BlockHeader) gen.BitcoinBlock {
}
}
func mapBlockHeaderModelToType(src gen.BitcoinBlock) (*types.BlockHeader, error) {
func mapBlockHeaderModelToType(src gen.BitcoinBlock) (types.BlockHeader, error) {
hash, err := chainhash.NewHashFromStr(src.BlockHash)
if err != nil {
return nil, errors.Wrap(err, "failed to parse block hash")
return types.BlockHeader{}, errors.Join(errors.Wrap(err, "failed to parse block hash"), errs.InternalError)
}
prevHash, err := chainhash.NewHashFromStr(src.PrevBlockHash)
if err != nil {
return nil, errors.Wrap(err, "failed to parse prev block hash")
return types.BlockHeader{}, errors.Join(errors.Wrap(err, "failed to parse prev block hash"), errs.InternalError)
}
merkleRoot, err := chainhash.NewHashFromStr(src.MerkleRoot)
if err != nil {
return nil, errors.Wrap(err, "failed to parse merkle root")
return types.BlockHeader{}, errors.Join(errors.Wrap(err, "failed to parse merkle root"), errs.InternalError)
}
return &types.BlockHeader{
return types.BlockHeader{
Hash: *hash,
Height: int64(src.BlockHeight),
Version: src.Version,
@@ -51,13 +52,25 @@ func mapBlockHeaderModelToType(src gen.BitcoinBlock) (*types.BlockHeader, error)
}, nil
}
func mapBlockTypeToModel(src *types.Block) (gen.BitcoinBlock, []gen.BitcoinTransaction, []gen.BitcoinTransactionTxin, []gen.BitcoinTransactionTxout) {
block := mapBlockHeaderTypeToModel(&src.Header)
txs := make([]gen.BitcoinTransaction, 0, len(src.Transactions))
txins := make([]gen.BitcoinTransactionTxin, 0)
txouts := make([]gen.BitcoinTransactionTxout, 0)
func mapBlockTypeToParams(src *types.Block) (gen.InsertBlockParams, []gen.InsertTransactionParams, []gen.InsertTransactionTxOutParams, []gen.InsertTransactionTxInParams) {
txs := make([]gen.InsertTransactionParams, 0, len(src.Transactions))
txouts := make([]gen.InsertTransactionTxOutParams, 0)
txins := make([]gen.InsertTransactionTxInParams, 0)
block := gen.InsertBlockParams{
BlockHeight: int32(src.Header.Height),
BlockHash: src.Header.Hash.String(),
Version: src.Header.Version,
MerkleRoot: src.Header.MerkleRoot.String(),
PrevBlockHash: src.Header.PrevBlock.String(),
Timestamp: pgtype.Timestamptz{
Time: src.Header.Timestamp,
Valid: true,
},
Bits: int32(src.Header.Bits),
Nonce: int32(src.Header.Nonce),
}
for txIdx, srcTx := range src.Transactions {
tx := gen.BitcoinTransaction{
tx := gen.InsertTransactionParams{
TxHash: srcTx.TxHash.String(),
Version: srcTx.Version,
Locktime: int32(srcTx.LockTime),
@@ -68,13 +81,12 @@ func mapBlockTypeToModel(src *types.Block) (gen.BitcoinBlock, []gen.BitcoinTrans
txs = append(txs, tx)
for idx, txin := range srcTx.TxIn {
txins = append(txins, gen.BitcoinTransactionTxin{
TxHash: tx.TxHash,
TxIdx: int16(idx),
PrevoutTxHash: txin.PreviousOutTxHash.String(),
PrevoutTxIdx: int16(txin.PreviousOutIndex),
PrevoutPkscript: "", // Note: this will be updated while inserting to the database
Scriptsig: hex.EncodeToString(txin.SignatureScript),
txins = append(txins, gen.InsertTransactionTxInParams{
TxHash: tx.TxHash,
TxIdx: int16(idx),
PrevoutTxHash: txin.PreviousOutTxHash.String(),
PrevoutTxIdx: int16(txin.PreviousOutIndex),
Scriptsig: hex.EncodeToString(txin.SignatureScript),
// TODO: should figure out how to store this
// Witness: pgtype.Text{
// String: string(txin.Witness),
@@ -85,14 +97,13 @@ func mapBlockTypeToModel(src *types.Block) (gen.BitcoinBlock, []gen.BitcoinTrans
}
for idx, txout := range srcTx.TxOut {
txouts = append(txouts, gen.BitcoinTransactionTxout{
txouts = append(txouts, gen.InsertTransactionTxOutParams{
TxHash: tx.TxHash,
TxIdx: int16(idx),
Pkscript: hex.EncodeToString(txout.PkScript),
Value: txout.Value,
IsSpent: false,
})
}
}
return block, txs, txins, txouts
return block, txs, txouts, txins
}

View File

@@ -2,9 +2,13 @@ package postgres
import (
"github.com/gaze-network/indexer-network/internal/postgres"
"github.com/gaze-network/indexer-network/modules/bitcoin/internal/datagateway"
"github.com/gaze-network/indexer-network/modules/bitcoin/internal/repository/postgres/gen"
)
// Make sure Repository implements the BitcoinDataGateway interface
var _ datagateway.BitcoinDataGateway = (*Repository)(nil)
type Repository struct {
db postgres.DB
queries *gen.Queries