Compare commits

..

7 Commits

Author SHA1 Message Date
PanJ
4b2da298ba read private key from file 2024-05-21 15:18:55 +07:00
Panjamapong Sermsawatsri
bd9acb8841 feat: fix reporting errors 2024-05-15 17:09:48 +07:00
PanJ
58761b1ef5 update reporting api 2024-05-15 15:33:59 +07:00
Panjamapong Sermsawatsri
85fdf79a00 fix: wif file name to add mainnet 2024-05-08 10:35:21 +07:00
Panjamapong Sermsawatsri
e0e2934160 feat: add wif generation 2024-05-08 10:28:47 +07:00
Panjamapong Sermsawatsri
d56c58334a feat: add new reporting client 2024-04-26 16:20:24 +07:00
Panjamapong Sermsawatsri
be316442ea feat: generate key command and crypto pkg 2024-04-23 16:44:16 +07:00
21 changed files with 1723 additions and 128 deletions

2
.gitignore vendored
View File

@@ -3,6 +3,8 @@
# Eg. ignore.foo_test.go, ignore.credentials.json, ignore.config.yml
ignore.*
/key
**/cmd.local/**
**/cmd.local.**/**

View File

@@ -14,13 +14,14 @@ var (
// root command
cmd = &cobra.Command{
Use: "gaze",
Long: `Description of gaze indexer`,
Long: `Gaze in a Bitcoin meta-protocol indexer`,
}
// sub-commands
// sub-commandsf
cmds = []*cobra.Command{
NewVersionCommand(),
NewRunCommand(),
NewGenerateKeypairCommand(),
}
)

100
cmd/cmd_generate_keypair.go Normal file
View File

@@ -0,0 +1,100 @@
package cmd
import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"path"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common/errs"
"github.com/gaze-network/indexer-network/pkg/crypto"
"github.com/spf13/cobra"
)
type generateKeypairCmdOptions struct {
Path string
}
func NewGenerateKeypairCommand() *cobra.Command {
opts := &generateKeypairCmdOptions{}
cmd := &cobra.Command{
Use: "generate-keypair",
Short: "Generate new public/private keypair for encryption and signature generation",
RunE: func(cmd *cobra.Command, args []string) error {
return generateKeypairHandler(opts, cmd, args)
},
}
flags := cmd.Flags()
flags.StringVar(&opts.Path, "path", "/data/keys", `Path to save to key pair file`)
return cmd
}
func generateKeypairHandler(opts *generateKeypairCmdOptions, _ *cobra.Command, _ []string) error {
fmt.Printf("Generating key pair\n")
privKeyBytes := make([]byte, 32)
_, err := rand.Read(privKeyBytes)
if err != nil {
return errors.Wrap(errs.SomethingWentWrong, "random bytes")
}
_, pubKey := btcec.PrivKeyFromBytes(privKeyBytes)
serializedPubKey := pubKey.SerializeCompressed()
// fmt.Println(hex.EncodeToString(privKeyBytes))
fmt.Printf("Public key: %s\n", hex.EncodeToString(serializedPubKey))
err = os.MkdirAll(opts.Path, 0o755)
if err != nil {
return errors.Wrap(errs.SomethingWentWrong, "create directory")
}
privateKeyPath := path.Join(opts.Path, "priv.key")
_, err = os.Stat(privateKeyPath)
if err == nil {
fmt.Printf("Existing private key found at %s\n[WARNING] THE EXISTING PRIVATE KEY WILL BE LOST\nType [replace] to replace existing private key: ", privateKeyPath)
var ans string
fmt.Scanln(&ans)
if ans != "replace" {
fmt.Printf("Keypair generation aborted\n")
return nil
}
}
err = os.WriteFile(privateKeyPath, []byte(hex.EncodeToString(privKeyBytes)), 0o644)
if err != nil {
return errors.Wrap(err, "write private key file")
}
fmt.Printf("Private key saved at %s\n", privateKeyPath)
wifKeyPath := path.Join(opts.Path, "priv_wif_mainnet.key")
client, err := crypto.New(hex.EncodeToString(privKeyBytes))
if err != nil {
return errors.Wrap(err, "new crypto client")
}
wifKey, err := client.WIF(&chaincfg.MainNetParams)
if err != nil {
return errors.Wrap(err, "get WIF key")
}
err = os.WriteFile(wifKeyPath, []byte(wifKey), 0o644)
if err != nil {
return errors.Wrap(err, "write WIF private key file")
}
fmt.Printf("WIF private key saved at %s\n", wifKeyPath)
publicKeyPath := path.Join(opts.Path, "pub.key")
err = os.WriteFile(publicKeyPath, []byte(hex.EncodeToString(serializedPubKey)), 0o644)
if err != nil {
return errors.Wrap(errs.SomethingWentWrong, "write public key file")
}
fmt.Printf("Public key saved at %s\n", publicKeyPath)
return nil
}

View File

@@ -30,7 +30,7 @@ import (
"github.com/gaze-network/indexer-network/pkg/errorhandler"
"github.com/gaze-network/indexer-network/pkg/logger"
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
"github.com/gaze-network/indexer-network/pkg/reportingclient"
"github.com/gaze-network/indexer-network/pkg/reportingclientv2"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/compress"
fiberrecover "github.com/gofiber/fiber/v2/middleware/recover"
@@ -90,6 +90,16 @@ func runHandler(opts *runCmdOptions, cmd *cobra.Command, _ []string) error {
// Add logger context
ctx = logger.WithContext(ctx, slogx.Stringer("network", conf.Network))
// Load private key
privKeyPath := conf.NodeKey.Path
if privKeyPath == "" {
privKeyPath = "/data/keys/priv.key"
}
privKeyByte, err := os.ReadFile(privKeyPath)
if err != nil {
logger.PanicContext(ctx, "Failed to read private key file", slogx.Error(err))
}
// Initialize Bitcoin Core RPC Client
client, err := rpcclient.New(&rpcclient.ConnConfig{
Host: conf.BitcoinNode.Host,
@@ -123,9 +133,9 @@ func runHandler(opts *runCmdOptions, cmd *cobra.Command, _ []string) error {
// use gracefulEG to coordinate graceful shutdown after context is done. (e.g. shut down http server, shutdown logic of each module, etc.)
gracefulEG, gctx := errgroup.WithContext(context.Background())
var reportingClient *reportingclient.ReportingClient
var reportingClient *reportingclientv2.ReportingClient
if !conf.Reporting.Disabled {
reportingClient, err = reportingclient.New(conf.Reporting)
reportingClient, err = reportingclientv2.New(conf.Reporting, string(privKeyByte)) // TODO: read private key from file
if err != nil {
logger.PanicContext(ctx, "Failed to create reporting client", slogx.Error(err))
}

8
go.mod
View File

@@ -26,15 +26,17 @@ require (
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/ecies/go/v2 v2.0.9 // indirect
github.com/ethereum/go-ethereum v1.13.5 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getsentry/sentry-go v0.18.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect

1058
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ import (
runesconfig "github.com/gaze-network/indexer-network/modules/runes/config"
"github.com/gaze-network/indexer-network/pkg/logger"
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
"github.com/gaze-network/indexer-network/pkg/reportingclient"
"github.com/gaze-network/indexer-network/pkg/reportingclientv2"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
@@ -32,12 +32,17 @@ var (
)
type Config struct {
Logger logger.Config `mapstructure:"logger"`
BitcoinNode BitcoinNodeClient `mapstructure:"bitcoin_node"`
Network common.Network `mapstructure:"network"`
HTTPServer HTTPServerConfig `mapstructure:"http_server"`
Modules Modules `mapstructure:"modules"`
Reporting reportingclient.Config `mapstructure:"reporting"`
Logger logger.Config `mapstructure:"logger"`
BitcoinNode BitcoinNodeClient `mapstructure:"bitcoin_node"`
Network common.Network `mapstructure:"network"`
HTTPServer HTTPServerConfig `mapstructure:"http_server"`
Modules Modules `mapstructure:"modules"`
Reporting reportingclientv2.Config `mapstructure:"reporting"`
NodeKey NodeKey `mapstructure:"node_key"`
}
type NodeKey struct {
Path string `mapstructure:"path"`
}
type BitcoinNodeClient struct {

View File

@@ -1,12 +1,13 @@
package httphandler
import (
"bytes"
"encoding/hex"
"slices"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common/errs"
"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/gofiber/fiber/v2"
@@ -83,7 +84,6 @@ type amountWithDecimal struct {
type transaction struct {
TxHash chainhash.Hash `json:"txHash"`
BlockHeight uint64 `json:"blockHeight"`
Index uint32 `json:"index"`
Timestamp int64 `json:"timestamp"`
Inputs []txInputOutput `json:"inputs"`
Outputs []txInputOutput `json:"outputs"`
@@ -116,6 +116,15 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
}
}
blockHeight := req.BlockHeight
if blockHeight == 0 {
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
if err != nil {
return errors.Wrap(err, "error during GetLatestBlock")
}
blockHeight = uint64(blockHeader.Height)
}
var runeId runes.RuneId
if req.Id != "" {
var ok bool
@@ -125,23 +134,68 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
}
}
blockHeight := req.BlockHeight
// set blockHeight to the latest block height blockHeight, pkScript, and runeId are not provided
if blockHeight == 0 && pkScript == nil && runeId == (runes.RuneId{}) {
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
if err != nil {
return errors.Wrap(err, "error during GetLatestBlock")
}
blockHeight = uint64(blockHeader.Height)
}
txs, err := h.usecase.GetRuneTransactions(ctx.UserContext(), pkScript, runeId, blockHeight)
txs, err := h.usecase.GetTransactionsByHeight(ctx.UserContext(), blockHeight)
if err != nil {
return errors.Wrap(err, "error during GetRuneTransactions")
return errors.Wrap(err, "error during GetTransactionsByHeight")
}
var allRuneIds []runes.RuneId
filteredTxs := make([]*entity.RuneTransaction, 0)
isTxContainPkScript := func(tx *entity.RuneTransaction) bool {
for _, input := range tx.Inputs {
if bytes.Equal(input.PkScript, pkScript) {
return true
}
}
for _, output := range tx.Outputs {
if bytes.Equal(output.PkScript, pkScript) {
return true
}
}
return false
}
isTxContainRuneId := func(tx *entity.RuneTransaction) bool {
for _, input := range tx.Inputs {
if input.RuneId == runeId {
return true
}
}
for _, output := range tx.Outputs {
if output.RuneId == runeId {
return true
}
}
for mintedRuneId := range tx.Mints {
if mintedRuneId == runeId {
return true
}
}
for burnedRuneId := range tx.Burns {
if burnedRuneId == runeId {
return true
}
}
if tx.Runestone != nil {
if tx.Runestone.Mint != nil && *tx.Runestone.Mint == runeId {
return true
}
// returns true if this tx etched this runeId
if tx.RuneEtched && tx.BlockHeight == runeId.BlockHeight && tx.Index == runeId.TxIndex {
return true
}
}
return false
}
for _, tx := range txs {
if pkScript != nil && !isTxContainPkScript(tx) {
continue
}
if runeId != (runes.RuneId{}) && !isTxContainRuneId(tx) {
continue
}
filteredTxs = append(filteredTxs, tx)
}
var allRuneIds []runes.RuneId
for _, tx := range filteredTxs {
for id := range tx.Mints {
allRuneIds = append(allRuneIds, id)
}
@@ -161,12 +215,11 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
return errors.Wrap(err, "error during GetRuneEntryByRuneIdBatch")
}
txList := make([]transaction, 0, len(txs))
for _, tx := range txs {
txList := make([]transaction, 0, len(filteredTxs))
for _, tx := range filteredTxs {
respTx := transaction{
TxHash: tx.Hash,
BlockHeight: tx.BlockHeight,
Index: tx.Index,
Timestamp: tx.Timestamp.Unix(),
Inputs: make([]txInputOutput, 0, len(tx.Inputs)),
Outputs: make([]txInputOutput, 0, len(tx.Outputs)),
@@ -256,13 +309,6 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
}
txList = append(txList, respTx)
}
// sort by block height ASC, then index ASC
slices.SortFunc(txList, func(t1, t2 transaction) int {
if t1.BlockHeight != t2.BlockHeight {
return int(t1.BlockHeight - t2.BlockHeight)
}
return int(t1.Index - t2.Index)
})
resp := getTransactionsResponse{
Result: &getTransactionsResult{

View File

@@ -72,7 +72,6 @@ CREATE TABLE IF NOT EXISTS "runes_transactions" (
"rune_etched" BOOLEAN NOT NULL
);
CREATE INDEX IF NOT EXISTS runes_transactions_block_height_idx ON "runes_transactions" USING BTREE ("block_height");
CREATE INDEX IF NOT EXISTS runes_transactions_jsonb_idx ON "runes_transactions" USING GIN ("inputs", "outputs", "mints", "burns");
CREATE TABLE IF NOT EXISTS "runes_runestones" (
"tx_hash" TEXT NOT NULL PRIMARY KEY,

View File

@@ -40,23 +40,10 @@ SELECT * FROM runes_entries
-- name: GetRuneIdFromRune :one
SELECT rune_id FROM runes_entries WHERE rune = $1;
-- name: GetRuneTransactions :many
SELECT * FROM runes_transactions
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
WHERE (
@filter_pk_script::BOOLEAN = FALSE -- if @filter_pk_script is TRUE, apply pk_script filter
OR runes_transactions.outputs @> @pk_script_param::JSONB
OR runes_transactions.inputs @> @pk_script_param::JSONB
) AND (
@filter_rune_id::BOOLEAN = FALSE -- if @filter_rune_id is TRUE, apply rune_id filter
OR runes_transactions.outputs @> @rune_id_param::JSONB
OR runes_transactions.inputs @> @rune_id_param::JSONB
OR runes_transactions.mints ? @rune_id
OR runes_transactions.burns ? @rune_id
OR (runes_transactions.rune_etched = TRUE AND runes_transactions.block_height = @rune_id_block_height AND runes_transactions.index = @rune_id_tx_index)
) AND (
@block_height::INT = 0 OR runes_transactions.block_height = @block_height::INT -- if @block_height > 0, apply block_height filter
);
-- name: GetRuneTransactionsByHeight :many
SELECT * FROM runes_transactions
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
WHERE runes_transactions.block_height = $1;
-- name: CountRuneEntries :one
SELECT COUNT(*) FROM runes_entries;

View File

@@ -26,8 +26,7 @@ type RunesDataGatewayWithTx interface {
type RunesReaderDataGateway interface {
GetLatestBlock(ctx context.Context) (types.BlockHeader, error)
GetIndexedBlockByHeight(ctx context.Context, height int64) (*entity.IndexedBlock, error)
// GetRuneTransactions returns the runes transactions, filterable by pkScript, runeId and height. If pkScript, runeId or height is zero value, that filter is ignored.
GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, height uint64) ([]*entity.RuneTransaction, error)
GetRuneTransactionsByHeight(ctx context.Context, height uint64) ([]*entity.RuneTransaction, error)
GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wire.OutPoint) (map[runes.RuneId]*entity.OutPointBalance, error)
GetUnspentOutPointBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) ([]*entity.OutPointBalance, error)

View File

@@ -16,7 +16,7 @@ import (
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
"github.com/gaze-network/indexer-network/modules/runes/runes"
"github.com/gaze-network/indexer-network/pkg/logger"
"github.com/gaze-network/indexer-network/pkg/reportingclient"
"github.com/gaze-network/indexer-network/pkg/reportingclientv2"
"github.com/gaze-network/uint128"
"github.com/samber/lo"
)
@@ -29,7 +29,7 @@ type Processor struct {
bitcoinClient btcclient.Contract
bitcoinDataSource indexers.BitcoinDatasource
network common.Network
reportingClient *reportingclient.ReportingClient
reportingClient *reportingclientv2.ReportingClient
newRuneEntries map[runes.RuneId]*runes.RuneEntry
newRuneEntryStates map[runes.RuneId]*runes.RuneEntry
@@ -39,7 +39,7 @@ type Processor struct {
newRuneTxs []*entity.RuneTransaction
}
func NewProcessor(runesDg datagateway.RunesDataGateway, indexerInfoDg datagateway.IndexerInfoDataGateway, bitcoinClient btcclient.Contract, bitcoinDataSource indexers.BitcoinDatasource, network common.Network, reportingClient *reportingclient.ReportingClient) *Processor {
func NewProcessor(runesDg datagateway.RunesDataGateway, indexerInfoDg datagateway.IndexerInfoDataGateway, bitcoinClient btcclient.Contract, bitcoinDataSource indexers.BitcoinDatasource, network common.Network, reportingClient *reportingclientv2.ReportingClient) *Processor {
return &Processor{
runesDg: runesDg,
indexerInfoDg: indexerInfoDg,
@@ -72,7 +72,7 @@ func (p *Processor) VerifyStates(ctx context.Context) error {
}
}
if p.reportingClient != nil {
if err := p.reportingClient.SubmitNodeReport(ctx, "runes", p.network); err != nil {
if err := p.reportingClient.SubmitNodeReport(ctx, "runes", p.network, Version); err != nil {
return errors.Wrap(err, "failed to submit node report")
}
}

View File

@@ -18,7 +18,7 @@ import (
"github.com/gaze-network/indexer-network/modules/runes/runes"
"github.com/gaze-network/indexer-network/pkg/logger"
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
"github.com/gaze-network/indexer-network/pkg/reportingclient"
"github.com/gaze-network/indexer-network/pkg/reportingclientv2"
"github.com/gaze-network/uint128"
"github.com/samber/lo"
)
@@ -810,7 +810,7 @@ func (p *Processor) flushBlock(ctx context.Context, blockHeader types.BlockHeade
// submit event to reporting system
if p.reportingClient != nil {
if err := p.reportingClient.SubmitBlockReport(ctx, reportingclient.SubmitBlockReportPayload{
if err := p.reportingClient.SubmitBlockReport(ctx, reportingclientv2.SubmitBlockReportPayloadData{
Type: "runes",
ClientVersion: Version,
DBVersion: DBVersion,

View File

@@ -631,37 +631,13 @@ func (q *Queries) GetRuneIdFromRune(ctx context.Context, rune string) (string, e
return rune_id, err
}
const getRuneTransactions = `-- name: GetRuneTransactions :many
SELECT hash, runes_transactions.block_height, index, timestamp, inputs, outputs, mints, burns, rune_etched, tx_hash, runes_runestones.block_height, etching, etching_divisibility, etching_premine, etching_rune, etching_spacers, etching_symbol, etching_terms, etching_terms_amount, etching_terms_cap, etching_terms_height_start, etching_terms_height_end, etching_terms_offset_start, etching_terms_offset_end, etching_turbo, edicts, mint, pointer, cenotaph, flaws FROM runes_transactions
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
WHERE (
$1::BOOLEAN = FALSE -- if @filter_pk_script is TRUE, apply pk_script filter
OR runes_transactions.outputs @> $2::JSONB
OR runes_transactions.inputs @> $2::JSONB
) AND (
$3::BOOLEAN = FALSE -- if @filter_rune_id is TRUE, apply rune_id filter
OR runes_transactions.outputs @> $4::JSONB
OR runes_transactions.inputs @> $4::JSONB
OR runes_transactions.mints ? $5
OR runes_transactions.burns ? $5
OR (runes_transactions.rune_etched = TRUE AND runes_transactions.block_height = $6 AND runes_transactions.index = $7)
) AND (
$8::INT = 0 OR runes_transactions.block_height = $8::INT -- if @block_height > 0, apply block_height filter
)
const getRuneTransactionsByHeight = `-- name: GetRuneTransactionsByHeight :many
SELECT hash, runes_transactions.block_height, index, timestamp, inputs, outputs, mints, burns, rune_etched, tx_hash, runes_runestones.block_height, etching, etching_divisibility, etching_premine, etching_rune, etching_spacers, etching_symbol, etching_terms, etching_terms_amount, etching_terms_cap, etching_terms_height_start, etching_terms_height_end, etching_terms_offset_start, etching_terms_offset_end, etching_turbo, edicts, mint, pointer, cenotaph, flaws FROM runes_transactions
LEFT JOIN runes_runestones ON runes_transactions.hash = runes_runestones.tx_hash
WHERE runes_transactions.block_height = $1
`
type GetRuneTransactionsParams struct {
FilterPkScript bool
PkScriptParam []byte
FilterRuneID bool
RuneIDParam []byte
RuneID []byte
RuneIDBlockHeight int32
RuneIDTxIndex int32
BlockHeight int32
}
type GetRuneTransactionsRow struct {
type GetRuneTransactionsByHeightRow struct {
Hash string
BlockHeight int32
Index int32
@@ -694,24 +670,15 @@ type GetRuneTransactionsRow struct {
Flaws pgtype.Int4
}
func (q *Queries) GetRuneTransactions(ctx context.Context, arg GetRuneTransactionsParams) ([]GetRuneTransactionsRow, error) {
rows, err := q.db.Query(ctx, getRuneTransactions,
arg.FilterPkScript,
arg.PkScriptParam,
arg.FilterRuneID,
arg.RuneIDParam,
arg.RuneID,
arg.RuneIDBlockHeight,
arg.RuneIDTxIndex,
arg.BlockHeight,
)
func (q *Queries) GetRuneTransactionsByHeight(ctx context.Context, blockHeight int32) ([]GetRuneTransactionsByHeightRow, error) {
rows, err := q.db.Query(ctx, getRuneTransactionsByHeight, blockHeight)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetRuneTransactionsRow
var items []GetRuneTransactionsByHeightRow
for rows.Next() {
var i GetRuneTransactionsRow
var i GetRuneTransactionsByHeightRow
if err := rows.Scan(
&i.Hash,
&i.BlockHeight,

View File

@@ -306,7 +306,7 @@ func mapRuneTransactionTypeToParams(src entity.RuneTransaction) (gen.CreateRuneT
}, runestoneParams, nil
}
func extractModelRuneTxAndRunestone(src gen.GetRuneTransactionsRow) (gen.RunesTransaction, *gen.RunesRunestone, error) {
func extractModelRuneTxAndRunestone(src gen.GetRuneTransactionsByHeightRow) (gen.RunesTransaction, *gen.RunesRunestone, error) {
var runestone *gen.RunesRunestone
if src.TxHash.Valid {
// these fields should never be null

View File

@@ -3,7 +3,6 @@ package postgres
import (
"context"
"encoding/hex"
"fmt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
@@ -62,21 +61,8 @@ func (r *Repository) GetIndexedBlockByHeight(ctx context.Context, height int64)
return indexedBlock, nil
}
func (r *Repository) GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, height uint64) ([]*entity.RuneTransaction, error) {
pkScriptParam := []byte(fmt.Sprintf(`[{"pkScript":"%s"}]`, hex.EncodeToString(pkScript)))
runeIdParam := []byte(fmt.Sprintf(`[{"runeId":"%s"}]`, runeId.String()))
rows, err := r.queries.GetRuneTransactions(ctx, gen.GetRuneTransactionsParams{
FilterPkScript: pkScript != nil,
PkScriptParam: pkScriptParam,
FilterRuneID: runeId != runes.RuneId{},
RuneIDParam: runeIdParam,
RuneID: []byte(runeId.String()),
RuneIDBlockHeight: int32(runeId.BlockHeight),
RuneIDTxIndex: int32(runeId.TxIndex),
BlockHeight: int32(height),
})
func (r *Repository) GetRuneTransactionsByHeight(ctx context.Context, height uint64) ([]*entity.RuneTransaction, error) {
rows, err := r.queries.GetRuneTransactionsByHeight(ctx, int32(height))
if err != nil {
return nil, errors.Wrap(err, "error during query")
}

View File

@@ -5,11 +5,10 @@ import (
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
"github.com/gaze-network/indexer-network/modules/runes/runes"
)
func (u *Usecase) GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, height uint64) ([]*entity.RuneTransaction, error) {
txs, err := u.runesDg.GetRuneTransactions(ctx, pkScript, runeId, height)
func (u *Usecase) GetTransactionsByHeight(ctx context.Context, height uint64) ([]*entity.RuneTransaction, error) {
txs, err := u.runesDg.GetRuneTransactionsByHeight(ctx, height)
if err != nil {
return nil, errors.Wrap(err, "error during GetTransactionsByHeight")
}

105
pkg/crypto/crypto.go Normal file
View File

@@ -0,0 +1,105 @@
package crypto
import (
"encoding/base64"
"encoding/hex"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/cockroachdb/errors"
ecies "github.com/ecies/go/v2"
)
type Client struct {
privateKey *btcec.PrivateKey
eciesPrivateKey *ecies.PrivateKey
}
func New(privateKeyStr string) (*Client, error) {
if privateKeyStr != "" {
privateKeyBytes, err := hex.DecodeString(privateKeyStr)
if err != nil {
return nil, errors.Wrap(err, "decode private key")
}
privateKey, _ := btcec.PrivKeyFromBytes(privateKeyBytes)
eciesPrivateKey := ecies.NewPrivateKeyFromBytes(privateKeyBytes)
return &Client{
privateKey: privateKey,
eciesPrivateKey: eciesPrivateKey,
}, nil
}
return &Client{}, nil
}
func (c *Client) PublicKey() string {
return hex.EncodeToString(c.privateKey.PubKey().SerializeCompressed())
}
func (c *Client) WIF(params *chaincfg.Params) (string, error) {
wif, err := btcutil.NewWIF(c.privateKey, params, true)
if err != nil {
return "", errors.Wrap(err, "wif convert failed")
}
return wif.String(), nil
}
func (c *Client) Sign(message string) string {
messageHash := chainhash.DoubleHashB([]byte(message))
signature := ecdsa.Sign(c.privateKey, messageHash)
return hex.EncodeToString(signature.Serialize())
}
func (c *Client) Verify(message, sigStr, pubKeyStr string) (bool, error) {
sigBytes, err := hex.DecodeString(sigStr)
if err != nil {
return false, errors.Wrap(err, "signature decode")
}
pubBytes, err := hex.DecodeString(pubKeyStr)
if err != nil {
return false, errors.Wrap(err, "pubkey decode")
}
pubKey, err := btcec.ParsePubKey(pubBytes)
if err != nil {
return false, errors.Wrap(err, "pubkey parse")
}
messageHash := chainhash.DoubleHashB([]byte(message))
signature, err := ecdsa.ParseSignature(sigBytes)
if err != nil {
return false, errors.Wrap(err, "signature parse")
}
return signature.Verify(messageHash, pubKey), nil
}
func (c *Client) Encrypt(message, pubKeyStr string) (string, error) {
pubKey, err := ecies.NewPublicKeyFromHex(pubKeyStr)
if err != nil {
return "", errors.Wrap(err, "parse pubkey")
}
ciphertext, err := ecies.Encrypt(pubKey, []byte(message))
if err != nil {
return "", errors.Wrap(err, "encrypt message")
}
ciphertextStr := base64.StdEncoding.EncodeToString(ciphertext)
return ciphertextStr, nil
}
func (c *Client) Decrypt(ciphertextStr string) (string, error) {
ciphertext, err := base64.StdEncoding.DecodeString(ciphertextStr)
if err != nil {
return "", errors.Wrap(err, "decode ciphertext")
}
plaintext, err := ecies.Decrypt(c.eciesPrivateKey, ciphertext)
if err != nil {
return "", errors.Wrap(err, "decrypt")
}
return string(plaintext), nil
}

65
pkg/crypto/crypto_test.go Normal file
View File

@@ -0,0 +1,65 @@
package crypto
import (
"testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/stretchr/testify/assert"
)
// Key for test only. DO NOT USE IN PRODUCTION
const (
privateKeyStr = "ce9c2fd75623e82a83ed743518ec7749f6f355f7301dd432400b087717fed2f2"
mainnetKey = "L49LKamtrPZxty5TG7jaFPHMRZbrvAr4Dvn5BHGdvmvbcTDNAbZj"
pubKeyStr = "0251e2dfcdeea17cc9726e4be0855cd0bae19e64f3e247b10760cd76851e7df47e"
)
func TestEncryptDecrypt(t *testing.T) {
plaintext := "hello world"
privClient, err := New(privateKeyStr)
assert.NoError(t, err)
pubClient, err := New("")
assert.NoError(t, err)
ciphertext, err := pubClient.Encrypt(plaintext, pubKeyStr)
assert.NoError(t, err)
decrypted, err := privClient.Decrypt(ciphertext)
assert.NoError(t, err)
assert.Equal(t, plaintext, decrypted)
}
func TestSignVerify(t *testing.T) {
plaintext := "hello world"
invalidSignature := "3044022066504a82e2bc23167214e05497a1ca957add9cacc078aa69f5417079a4d56f0002206b215920b046c779d4a58d4029c26dbadcaf6d3c884b3463f44e70ef9146c1cd"
privClient, err := New(privateKeyStr)
assert.NoError(t, err)
pubClient, err := New("")
assert.NoError(t, err)
signature := privClient.Sign(plaintext)
println(signature)
verified, err := pubClient.Verify(plaintext, signature, pubKeyStr)
assert.NoError(t, err)
assert.True(t, verified)
verified, err = pubClient.Verify(plaintext, invalidSignature, pubKeyStr)
assert.NoError(t, err)
assert.False(t, verified)
}
func TestWIF(t *testing.T) {
privClient, err := New(privateKeyStr)
assert.NoError(t, err)
wifPrivKey, err := privClient.WIF(&chaincfg.MainNetParams)
assert.NoError(t, err)
assert.Equal(t, wifPrivKey, mainnetKey)
}

View File

@@ -0,0 +1,168 @@
package reportingclientv2
import (
"context"
"encoding/json"
"log/slog"
"github.com/Cleverse/go-utilities/utils"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common"
"github.com/gaze-network/indexer-network/pkg/crypto"
"github.com/gaze-network/indexer-network/pkg/httpclient"
"github.com/gaze-network/indexer-network/pkg/logger"
)
type Config struct {
Disabled bool `mapstructure:"disabled"`
ReportCenter ReportCenter `mapstructure:"report_center"`
NodeInfo NodeInfo `mapstructure:"node_info"`
}
type NodeInfo struct {
Name string `mapstructure:"name"`
WebsiteURL string `mapstructure:"website_url"`
APIURL string `mapstructure:"api_url"`
}
type ReportCenter struct {
BaseURL string `mapstructure:"base_url"`
PublicKey string `mapstructure:"public_key"`
}
type ReportingClient struct {
httpClient *httpclient.Client
cryptoClient *crypto.Client
config Config
}
const (
defaultBaseURL = "https://indexer.api.gaze.network"
defaultPublicKey = "039298683d53a1cbdb6f318d5ad4b12bc0d752f3a6cd62c19b2c22b1ae1e12fe05"
)
func New(config Config, indexerPrivateKey string) (*ReportingClient, error) {
config.ReportCenter.BaseURL = utils.Default(config.ReportCenter.BaseURL, defaultBaseURL)
config.ReportCenter.PublicKey = utils.Default(config.ReportCenter.PublicKey, defaultPublicKey)
httpClient, err := httpclient.New(config.ReportCenter.BaseURL)
if err != nil {
return nil, errors.Wrap(err, "can't create http client")
}
cryptoClient, err := crypto.New(indexerPrivateKey)
if err != nil {
return nil, errors.Wrap(err, "can't create crypto client")
}
return &ReportingClient{
httpClient: httpClient,
config: config,
cryptoClient: cryptoClient,
}, nil
}
type SubmitBlockReportPayload struct {
EncryptedData string `json:"encryptedData"`
IndexerPublicKey string `json:"indexerPublicKey"`
}
type SubmitBlockReportPayloadData struct {
Type string `json:"type"`
ClientVersion string `json:"clientVersion"`
DBVersion int `json:"dbVersion"`
EventHashVersion int `json:"eventHashVersion"`
Network common.Network `json:"network"`
BlockHeight uint64 `json:"blockHeight"`
BlockHash chainhash.Hash `json:"blockHash"`
EventHash chainhash.Hash `json:"eventHash"`
CumulativeEventHash chainhash.Hash `json:"cumulativeEventHash"`
IndexerPublicKey string `json:"indexerPublicKey"`
}
func (r *ReportingClient) SubmitBlockReport(ctx context.Context, payload SubmitBlockReportPayloadData) error {
payload.IndexerPublicKey = r.cryptoClient.PublicKey()
data, err := json.Marshal(payload)
if err != nil {
return errors.Wrap(err, "can't marshal payload")
}
encryptedData, err := r.cryptoClient.Encrypt(string(data), r.config.ReportCenter.PublicKey)
if err != nil {
return errors.Wrap(err, "can't encrypt data")
}
bodyStruct := SubmitBlockReportPayload{
EncryptedData: encryptedData,
IndexerPublicKey: r.cryptoClient.PublicKey(),
}
body, err := json.Marshal(bodyStruct)
if err != nil {
return errors.Wrap(err, "can't marshal payload")
}
resp, err := r.httpClient.Post(ctx, "/v2/report/block", httpclient.RequestOptions{
Body: body,
})
if err != nil {
return errors.Wrap(err, "can't send request")
}
if resp.StatusCode() >= 400 {
logger.WarnContext(ctx, "failed to submit block report", slog.Any("payload", bodyStruct), slog.Any("responseBody", resp.Body()))
} else {
logger.DebugContext(ctx, "block report submitted", slog.Any("payload", bodyStruct))
}
return nil
}
type SubmitNodeReportPayload struct {
Data SubmitNodeReportPayloadData `json:"data"`
IndexerPublicKey string `json:"indexerPublicKey"`
Signature string `json:"string"`
}
type SubmitNodeReportPayloadData struct {
Name string `json:"name"`
Type string `json:"type"`
Network common.Network `json:"network"`
WebsiteURL string `json:"websiteUrl,omitempty"`
APIURL string `json:"apiUrl,omitempty"`
IndexerPublicKey string `json:"indexerPublicKey"`
ClientVersion string `json:"clientVersion"`
}
func (r *ReportingClient) SubmitNodeReport(ctx context.Context, module string, network common.Network, clientVersion string) error {
payload := SubmitNodeReportPayload{
Data: SubmitNodeReportPayloadData{
Name: r.config.NodeInfo.Name,
Type: module,
Network: network,
WebsiteURL: r.config.NodeInfo.WebsiteURL,
APIURL: r.config.NodeInfo.APIURL,
IndexerPublicKey: r.cryptoClient.PublicKey(),
ClientVersion: clientVersion,
},
IndexerPublicKey: r.cryptoClient.PublicKey(),
}
dataPayload, err := json.Marshal(payload.Data)
if err != nil {
return errors.Wrap(err, "can't marshal payload data")
}
payload.Signature = r.cryptoClient.Sign(string(dataPayload))
body, err := json.Marshal(payload)
if err != nil {
return errors.Wrap(err, "can't marshal payload")
}
resp, err := r.httpClient.Post(ctx, "/v2/report/node", httpclient.RequestOptions{
Body: body,
})
if err != nil {
return errors.Wrap(err, "can't send request")
}
if resp.StatusCode() >= 400 {
logger.WarnContext(ctx, "failed to submit node report", slog.Any("payload", payload), slog.Any("responseBody", resp.Body()))
} else {
logger.InfoContext(ctx, "node report submitted", slog.Any("payload", payload))
}
return nil
}

View File

@@ -0,0 +1,96 @@
package reportingclientv2
import (
"context"
"fmt"
"testing"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/gaze-network/indexer-network/common"
"github.com/stretchr/testify/assert"
)
// Key for test only. DO NOT USE IN PRODUCTION
const (
privateKeyStrOfficial = "ce9c2fd75623e82a83ed743518ec7749f6f355f7301dd432400b087717fed2f2"
mainnetKeyOfficial = "L2hqSEYLjRQHHSiSgfNSFwaZ1dYpwn4PUTt2nQT8AefzYGQCwsGY"
pubKeyStrOfficial = "0251e2dfcdeea17cc9726e4be0855cd0bae19e64f3e247b10760cd76851e7df47e"
)
const (
privateKeyStr1 = "a3a7d2c40c8bb7a4b4afa9a6b3ed613da1de233913ed07a017e6dd44ef542d80"
mainnetKey1 = "L49LKamtrPZxty5TG7jaFPHMRZbrvAr4Dvn5BHGdvmvbcTDNAbZj"
pubKeyStr1 = "02596e11b5a2104533d2732a3df35eadaeb61b189b9069715106e72e27c1de7775"
)
const (
privateKeyStr2 = "a3a7d2c40c8bb7a4b4afa9a6b3ed613da1de233913ed07a017e6dd44ef542d81"
// mainnetKey1 = "L49LKamtrPZxty5TG7jaFPHMRZbrvAr4Dvn5BHGdvmvbcTDNAbZj"
// pubKeyStr1 = "02596e11b5a2104533d2732a3df35eadaeb61b189b9069715106e72e27c1de7775"
)
func TestReport1(t *testing.T) {
block := 18
hash, err := chainhash.NewHashFromStr(fmt.Sprintf("%d", block))
assert.NoError(t, err)
configOfficial := Config{
ReportCenter: ReportCenter{
BaseURL: "https://indexer-dev.api.gaze.network",
PublicKey: defaultPublicKey,
},
NodeInfo: NodeInfo{
Name: "Official Node",
APIURL: "http://localhost:2000",
},
}
config1 := Config{
ReportCenter: ReportCenter{
BaseURL: "https://indexer-dev.api.gaze.network",
PublicKey: defaultPublicKey,
},
NodeInfo: NodeInfo{
Name: "Node 1",
APIURL: "http://localhost:2000",
},
}
config2 := Config{
ReportCenter: ReportCenter{
BaseURL: "https://indexer-dev.api.gaze.network",
PublicKey: defaultPublicKey,
},
NodeInfo: NodeInfo{
Name: "Node 2",
APIURL: "http://localhost:2000",
},
}
clientOfficial, err := New(configOfficial, privateKeyStrOfficial)
assert.NoError(t, err)
client1, err := New(config1, privateKeyStr1)
assert.NoError(t, err)
client2, err := New(config2, privateKeyStr2)
assert.NoError(t, err)
err = clientOfficial.SubmitNodeReport(context.Background(), "runes", "mainnet", "v0.0.1")
assert.NoError(t, err)
err = client1.SubmitNodeReport(context.Background(), "runes", "mainnet", "v0.0.1")
assert.NoError(t, err)
err = client2.SubmitNodeReport(context.Background(), "runes", "mainnet", "v0.0.1")
assert.NoError(t, err)
blockReport := SubmitBlockReportPayloadData{
Type: "runes",
ClientVersion: "v0.0.1",
DBVersion: 1,
EventHashVersion: 1,
Network: common.NetworkMainnet,
BlockHeight: uint64(block),
BlockHash: *hash,
EventHash: *hash,
CumulativeEventHash: *hash,
}
err = clientOfficial.SubmitBlockReport(context.Background(), blockReport)
err = client1.SubmitBlockReport(context.Background(), blockReport)
err = client2.SubmitBlockReport(context.Background(), blockReport)
}