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>
371 lines
13 KiB
Go
371 lines
13 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/rpcclient"
|
|
"github.com/cockroachdb/errors"
|
|
"github.com/gaze-network/indexer-network/common/errs"
|
|
"github.com/gaze-network/indexer-network/core/datasources"
|
|
"github.com/gaze-network/indexer-network/core/indexers"
|
|
"github.com/gaze-network/indexer-network/internal/config"
|
|
"github.com/gaze-network/indexer-network/internal/postgres"
|
|
"github.com/gaze-network/indexer-network/modules/bitcoin"
|
|
"github.com/gaze-network/indexer-network/modules/bitcoin/btcclient"
|
|
btcdatagateway "github.com/gaze-network/indexer-network/modules/bitcoin/datagateway"
|
|
btcpostgres "github.com/gaze-network/indexer-network/modules/bitcoin/repository/postgres"
|
|
"github.com/gaze-network/indexer-network/modules/runes"
|
|
runesapi "github.com/gaze-network/indexer-network/modules/runes/api"
|
|
runesdatagateway "github.com/gaze-network/indexer-network/modules/runes/datagateway"
|
|
runespostgres "github.com/gaze-network/indexer-network/modules/runes/repository/postgres"
|
|
runesusecase "github.com/gaze-network/indexer-network/modules/runes/usecase"
|
|
"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/gofiber/fiber/v2"
|
|
"github.com/gofiber/fiber/v2/middleware/compress"
|
|
fiberrecover "github.com/gofiber/fiber/v2/middleware/recover"
|
|
"github.com/samber/lo"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
shutdownTimeout = 60 * time.Second
|
|
)
|
|
|
|
type runCmdOptions struct {
|
|
APIOnly bool
|
|
Bitcoin bool
|
|
Runes bool
|
|
}
|
|
|
|
func NewRunCommand() *cobra.Command {
|
|
opts := &runCmdOptions{}
|
|
|
|
// Create command
|
|
runCmd := &cobra.Command{
|
|
Use: "run",
|
|
Short: "Start indexer-network service",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return runHandler(opts, cmd, args)
|
|
},
|
|
}
|
|
|
|
// TODO: separate flags and bind flags to each module cmd package.
|
|
|
|
// Add local flags
|
|
flags := runCmd.Flags()
|
|
flags.BoolVar(&opts.APIOnly, "api-only", false, "Run only API server")
|
|
flags.BoolVar(&opts.Bitcoin, "bitcoin", false, "Enable Bitcoin indexer module")
|
|
flags.String("bitcoin-db", "postgres", `Database to store bitcoin data. current supported databases: "postgres"`)
|
|
flags.BoolVar(&opts.Runes, "runes", false, "Enable Runes indexer module")
|
|
flags.String("runes-db", "postgres", `Database to store runes data. current supported databases: "postgres"`)
|
|
flags.String("runes-datasource", "bitcoin-node", `Datasource to fetch bitcoin data for processing Meta-Protocol data. current supported datasources: "bitcoin-node" | "database"`)
|
|
|
|
// Bind flags to configuration
|
|
config.BindPFlag("modules.bitcoin.database", flags.Lookup("bitcoin-db"))
|
|
config.BindPFlag("modules.runes.database", flags.Lookup("runes-db"))
|
|
config.BindPFlag("modules.runes.datasource", flags.Lookup("runes-datasource"))
|
|
|
|
return runCmd
|
|
}
|
|
|
|
type HttpHandler interface {
|
|
Mount(router fiber.Router) error
|
|
}
|
|
|
|
func runHandler(opts *runCmdOptions, cmd *cobra.Command, _ []string) error {
|
|
conf := config.Load()
|
|
|
|
// Validate inputs
|
|
{
|
|
if !conf.Network.IsSupported() {
|
|
return errors.Wrapf(errs.Unsupported, "%q network is not supported", conf.Network.String())
|
|
}
|
|
}
|
|
|
|
// Initialize application process context
|
|
ctx, stop := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
// Initialize worker context to separate worker's lifecycle from main process
|
|
ctxWorker, stopWorker := context.WithCancel(context.Background())
|
|
defer stopWorker()
|
|
|
|
// Add logger context
|
|
ctxWorker = logger.WithContext(ctxWorker, slogx.Stringer("network", conf.Network))
|
|
|
|
// Initialize Bitcoin Core RPC Client
|
|
client, err := rpcclient.New(&rpcclient.ConnConfig{
|
|
Host: conf.BitcoinNode.Host,
|
|
User: conf.BitcoinNode.User,
|
|
Pass: conf.BitcoinNode.Pass,
|
|
DisableTLS: conf.BitcoinNode.DisableTLS,
|
|
HTTPPostMode: true,
|
|
}, nil)
|
|
if err != nil {
|
|
logger.PanicContext(ctx, "Invalid Bitcoin node configuration", slogx.Error(err))
|
|
}
|
|
defer client.Shutdown()
|
|
|
|
// Check Bitcoin RPC connection
|
|
{
|
|
start := time.Now()
|
|
logger.InfoContext(ctx, "Connecting to Bitcoin Core RPC Server...", slogx.String("host", conf.BitcoinNode.Host))
|
|
if err := client.Ping(); err != nil {
|
|
logger.PanicContext(ctx, "Can't connect to Bitcoin Core RPC Server", slogx.String("host", conf.BitcoinNode.Host), slogx.Error(err))
|
|
}
|
|
logger.InfoContext(ctx, "Connected to Bitcoin Core RPC Server", slog.Duration("latency", time.Since(start)))
|
|
}
|
|
|
|
// TODO: create module command package.
|
|
// each module should have its own command package and main package will routing the command to the module command package.
|
|
|
|
// TODO: refactor module name to specific type instead of string?
|
|
httpHandlers := make(map[string]HttpHandler, 0)
|
|
|
|
var reportingClient *reportingclient.ReportingClient
|
|
if !conf.Reporting.Disabled {
|
|
reportingClient, err = reportingclient.New(conf.Reporting)
|
|
if err != nil {
|
|
if errors.Is(err, errs.InvalidArgument) {
|
|
logger.PanicContext(ctx, "Invalid reporting configuration", slogx.Error(err))
|
|
}
|
|
logger.PanicContext(ctx, "Something went wrong, can't create reporting client", slogx.Error(err))
|
|
}
|
|
}
|
|
|
|
// Initialize Bitcoin Indexer
|
|
if opts.Bitcoin {
|
|
ctx := logger.WithContext(ctx, slogx.String("module", "bitcoin"))
|
|
var (
|
|
btcDB btcdatagateway.BitcoinDataGateway
|
|
indexerInfoDB btcdatagateway.IndexerInformationDataGateway
|
|
)
|
|
switch strings.ToLower(conf.Modules.Bitcoin.Database) {
|
|
case "postgresql", "postgres", "pg":
|
|
pg, err := postgres.NewPool(ctx, conf.Modules.Bitcoin.Postgres)
|
|
if err != nil {
|
|
if errors.Is(err, errs.InvalidArgument) {
|
|
logger.PanicContext(ctx, "Invalid Postgres configuration for indexer", slogx.Error(err))
|
|
}
|
|
logger.PanicContext(ctx, "Something went wrong, can't create Postgres connection pool", slogx.Error(err))
|
|
}
|
|
defer pg.Close()
|
|
repo := btcpostgres.NewRepository(pg)
|
|
btcDB = repo
|
|
indexerInfoDB = repo
|
|
default:
|
|
return errors.Wrapf(errs.Unsupported, "%q database for indexer is not supported", conf.Modules.Bitcoin.Database)
|
|
}
|
|
if !opts.APIOnly {
|
|
processor := bitcoin.NewProcessor(conf, btcDB, indexerInfoDB)
|
|
datasource := datasources.NewBitcoinNode(client)
|
|
indexer := indexers.NewBitcoinIndexer(processor, datasource)
|
|
defer func() {
|
|
if err := indexer.ShutdownWithTimeout(shutdownTimeout); err != nil {
|
|
logger.ErrorContext(ctx, "Error during shutdown indexer", slogx.Error(err))
|
|
return
|
|
}
|
|
logger.InfoContext(ctx, "Indexer stopped gracefully")
|
|
}()
|
|
|
|
// Verify states before running Indexer
|
|
if err := processor.VerifyStates(ctx); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
// Run Indexer
|
|
go func() {
|
|
// stop main process if indexer stopped
|
|
defer stop()
|
|
|
|
logger.InfoContext(ctx, "Starting Gaze Indexer")
|
|
if err := indexer.Run(ctxWorker); err != nil {
|
|
logger.PanicContext(ctx, "Something went wrong, error during running indexer", slogx.Error(err))
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// Initialize Runes Indexer
|
|
if opts.Runes {
|
|
ctx := logger.WithContext(ctx, slogx.String("module", "runes"))
|
|
var (
|
|
runesDg runesdatagateway.RunesDataGateway
|
|
indexerInfoDg runesdatagateway.IndexerInfoDataGateway
|
|
)
|
|
switch strings.ToLower(conf.Modules.Runes.Database) {
|
|
case "postgresql", "postgres", "pg":
|
|
pg, err := postgres.NewPool(ctx, conf.Modules.Runes.Postgres)
|
|
if err != nil {
|
|
if errors.Is(err, errs.InvalidArgument) {
|
|
logger.PanicContext(ctx, "Invalid Postgres configuration for indexer", slogx.Error(err))
|
|
}
|
|
logger.PanicContext(ctx, "Something went wrong, can't create Postgres connection pool", slogx.Error(err))
|
|
}
|
|
defer pg.Close()
|
|
runesRepo := runespostgres.NewRepository(pg)
|
|
runesDg = runesRepo
|
|
indexerInfoDg = runesRepo
|
|
default:
|
|
return errors.Wrapf(errs.Unsupported, "%q database for indexer is not supported", conf.Modules.Runes.Database)
|
|
}
|
|
var bitcoinDatasource indexers.BitcoinDatasource
|
|
var bitcoinClient btcclient.Contract
|
|
switch strings.ToLower(conf.Modules.Runes.Datasource) {
|
|
case "bitcoin-node":
|
|
bitcoinNodeDatasource := datasources.NewBitcoinNode(client)
|
|
bitcoinDatasource = bitcoinNodeDatasource
|
|
bitcoinClient = bitcoinNodeDatasource
|
|
case "database":
|
|
pg, err := postgres.NewPool(ctx, conf.Modules.Bitcoin.Postgres)
|
|
if err != nil {
|
|
if errors.Is(err, errs.InvalidArgument) {
|
|
logger.PanicContext(ctx, "Invalid Postgres configuration for datasource", slogx.Error(err))
|
|
}
|
|
logger.PanicContext(ctx, "Something went wrong, can't create Postgres connection pool", slogx.Error(err))
|
|
}
|
|
defer pg.Close()
|
|
btcRepo := btcpostgres.NewRepository(pg)
|
|
btcClientDB := btcclient.NewClientDatabase(btcRepo)
|
|
bitcoinDatasource = btcClientDB
|
|
bitcoinClient = btcClientDB
|
|
default:
|
|
return errors.Wrapf(errs.Unsupported, "%q datasource is not supported", conf.Modules.Runes.Datasource)
|
|
}
|
|
|
|
if !opts.APIOnly {
|
|
processor := runes.NewProcessor(runesDg, indexerInfoDg, bitcoinClient, bitcoinDatasource, conf.Network, reportingClient)
|
|
indexer := indexers.NewBitcoinIndexer(processor, bitcoinDatasource)
|
|
defer func() {
|
|
if err := indexer.ShutdownWithTimeout(shutdownTimeout); err != nil {
|
|
logger.ErrorContext(ctx, "Error during shutdown indexer", slogx.Error(err))
|
|
return
|
|
}
|
|
logger.InfoContext(ctx, "Indexer stopped gracefully")
|
|
}()
|
|
|
|
if err := processor.VerifyStates(ctx); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
// Run Indexer
|
|
go func() {
|
|
// stop main process if indexer stopped
|
|
defer stop()
|
|
|
|
logger.InfoContext(ctx, "Starting Gaze Indexer")
|
|
if err := indexer.Run(ctxWorker); err != nil {
|
|
logger.PanicContext(ctx, "Something went wrong, error during running indexer", slogx.Error(err))
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Mount API
|
|
apiHandlers := lo.Uniq(conf.Modules.Runes.APIHandlers)
|
|
for _, handler := range apiHandlers {
|
|
switch handler { // TODO: support more handlers (e.g. gRPC)
|
|
case "http":
|
|
runesUsecase := runesusecase.New(runesDg, bitcoinClient)
|
|
runesHTTPHandler := runesapi.NewHTTPHandler(conf.Network, runesUsecase)
|
|
httpHandlers["runes"] = runesHTTPHandler
|
|
default:
|
|
logger.PanicContext(ctx, "Something went wrong, unsupported API handler", slogx.String("handler", handler))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wait for interrupt signal to gracefully stop the server with
|
|
// Setup HTTP server if there are any HTTP handlers
|
|
if len(httpHandlers) > 0 {
|
|
app := fiber.New(fiber.Config{
|
|
AppName: "Gaze Indexer",
|
|
ErrorHandler: errorhandler.NewHTTPErrorHandler(),
|
|
})
|
|
app.
|
|
Use(fiberrecover.New(fiberrecover.Config{
|
|
EnableStackTrace: true,
|
|
StackTraceHandler: func(c *fiber.Ctx, e interface{}) {
|
|
buf := make([]byte, 1024) // bufLen = 1024
|
|
buf = buf[:runtime.Stack(buf, false)]
|
|
logger.ErrorContext(c.UserContext(), "Something went wrong, panic in http handler", slogx.Any("panic", e), slog.String("stacktrace", string(buf)))
|
|
},
|
|
})).
|
|
Use(compress.New(compress.Config{
|
|
Level: compress.LevelDefault,
|
|
}))
|
|
|
|
defer func() {
|
|
if err := app.ShutdownWithTimeout(shutdownTimeout); err != nil {
|
|
logger.ErrorContext(ctx, "Error during shutdown HTTP server", slogx.Error(err))
|
|
return
|
|
}
|
|
logger.InfoContext(ctx, "HTTP server stopped gracefully")
|
|
}()
|
|
|
|
// Health check
|
|
app.Get("/", func(c *fiber.Ctx) error {
|
|
return errors.WithStack(c.SendStatus(http.StatusOK))
|
|
})
|
|
|
|
// mount http handlers from each http-enabled module
|
|
for module, handler := range httpHandlers {
|
|
if err := handler.Mount(app); err != nil {
|
|
logger.PanicContext(ctx, "Something went wrong, can't mount HTTP handler", slogx.Error(err), slogx.String("module", module))
|
|
}
|
|
logger.InfoContext(ctx, "Mounted HTTP handler", slogx.String("module", module))
|
|
}
|
|
|
|
go func() {
|
|
// stop main process if API stopped
|
|
defer stop()
|
|
|
|
logger.InfoContext(ctx, "Started HTTP server", slog.Int("port", conf.HTTPServer.Port))
|
|
if err := app.Listen(fmt.Sprintf(":%d", conf.HTTPServer.Port)); err != nil {
|
|
logger.PanicContext(ctx, "Something went wrong, error during running HTTP server", slogx.Error(err))
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Stop application if worker context is done
|
|
go func() {
|
|
<-ctxWorker.Done()
|
|
defer stop()
|
|
|
|
logger.InfoContext(ctx, "Gaze Indexer Worker is stopped. Stopping application...")
|
|
}()
|
|
|
|
logger.InfoContext(ctxWorker, "Gaze Indexer started")
|
|
|
|
// Wait for interrupt signal to gracefully stop the server
|
|
<-ctx.Done()
|
|
|
|
// Force shutdown if timeout exceeded or got signal again
|
|
go func() {
|
|
defer os.Exit(1)
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
logger.FatalContext(ctx, "Received exit signal again. Force shutdown...")
|
|
case <-time.After(shutdownTimeout + 15*time.Second):
|
|
logger.FatalContext(ctx, "Shutdown timeout exceeded. Force shutdown...")
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|