@eclesia/indexer-engine 2.16.0 → 2.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -305,18 +305,28 @@ var EclesiaIndexer = class extends require_index.EclesiaEmitter {
305
305
  this.requestRecovery("RPC unreachable during liveness check", generation);
306
306
  }
307
307
  }
308
- blockListener = {
309
- next: (data) => {
310
- this.newBlockReceived(data.header.height);
311
- },
312
- error: (error) => {
313
- this.log.error("Block subscription error", { error });
314
- this.requestRecovery("block subscription errored");
315
- },
316
- complete: () => {
317
- if (this.started) this.requestRecovery("block subscription closed by the node");
318
- }
319
- };
308
+ /**
309
+ * Builds the listener for one run's block subscription. It carries the generation it was
310
+ * created for, so when a restart disconnects the previous client and that subscription
311
+ * completes, the completion is attributed to the finished run and ignored instead of
312
+ * poisoning the run that is starting.
313
+ */
314
+ makeBlockListener(generation) {
315
+ return {
316
+ next: (data) => {
317
+ if (generation === this.runGeneration) this.newBlockReceived(data.header.height);
318
+ },
319
+ error: (error) => {
320
+ this.log.error("Block subscription error", { error });
321
+ this.requestRecovery("block subscription errored", generation);
322
+ },
323
+ complete: () => {
324
+ if (this.started) this.requestRecovery("block subscription closed by the node", generation);
325
+ }
326
+ };
327
+ }
328
+ /** Listener attached to the current block subscription */
329
+ blockListener = this.makeBlockListener(0);
320
330
  isMinimal(_blockqueue) {
321
331
  if (this.config.minimal) return true;
322
332
  else return false;
@@ -325,6 +335,12 @@ var EclesiaIndexer = class extends require_index.EclesiaEmitter {
325
335
  try {
326
336
  if (this.client) {
327
337
  this.log.verbose("Recover from error. Attempting to disconnect from RPC");
338
+ if (this.subscription) {
339
+ try {
340
+ this.subscription.removeListener(this.blockListener);
341
+ } catch (_e) {}
342
+ this.subscription = null;
343
+ }
328
344
  try {
329
345
  this.client.disconnect();
330
346
  } catch (_e) {}
@@ -441,7 +457,10 @@ var EclesiaIndexer = class extends require_index.EclesiaEmitter {
441
457
  this.subscription = null;
442
458
  this.log.verbose("Removed existing block listener and subscription");
443
459
  }
444
- if (!this.config.usePolling) this.subscription = this.client.subscribeNewBlock ? this.client.subscribeNewBlock() : null;
460
+ if (!this.config.usePolling) {
461
+ this.subscription = this.client.subscribeNewBlock ? this.client.subscribeNewBlock() : null;
462
+ this.blockListener = this.makeBlockListener(this.runGeneration);
463
+ }
445
464
  const status = await require_timeout.withTimeout(this.client.status(), require_constants.RPC_TIMEOUT_MS, new require_index$1.RPCError("RPC status call timed out"));
446
465
  this.assertChainId(status.nodeInfo.network);
447
466
  this.latestHeight = status.syncInfo.latestBlockHeight;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["DEFAULT_START_HEIGHT","DEFAULT_BATCH_SIZE","DEFAULT_POLLING_INTERVAL_MS","DEFAULT_HEALTH_CHECK_PORT","DEFAULT_PROMETHEUS_PORT","EclesiaEmitter","redactUrl","CircularBuffer","IndexerMetrics","DEFAULT_BIND_HOST","RPCError","ConfigurationError","MAX_FAILURES_PER_BLOCK","IDLE_CHECK_INTERVAL_MS","withTimeout","RPC_TIMEOUT_MS","CONNECT_TIMEOUT_MS","status: StatusResponse","lastProcessed: number | undefined","failingHeight: number | undefined","height: number","timestamp: string","QueryValidatorsResponse","PERIODIC_INTERVALS","retryDelay","RETRY_BASE_DELAY_MS","RETRY_MAX_DELAY_MS","beginBlockEvents: readonly Event[] | readonly Event38[]","endBlockEvents: readonly Event[] | readonly Event38[]","hasBlockEventMode","Tx","events: Array<{\n msg_index?: number\n events: (Event | Event38)[]\n }>","eventsToAdd: typeof events","decodeAttr","MsgExec","events","validators: Validator[]","key: Uint8Array | undefined","QueryValidatorsRequest","PAGINATION_LIMITS","GENESIS_BATCH_SIZE"],"sources":["../../src/indexer/index.ts"],"sourcesContent":["/* eslint-disable @stylistic/no-multi-spaces */\n/* eslint-disable max-lines */\nimport {\n createHash,\n} from \"node:crypto\";\nimport * as fs from \"node:fs\";\n\nimport {\n BlockResponse, BlockResultsResponse, CometClient, connectComet, Event, StatusResponse, toRfc3339WithNanoseconds,\n} from \"@cosmjs/tendermint-rpc\";\nimport {\n BlockResultsResponse as BlockResultsResponse38, Event as Event38,\n} from \"@cosmjs/tendermint-rpc/build/comet38/responses.js\";\nimport {\n MsgExec,\n} from \"cosmjs-types/cosmos/authz/v1beta1/tx.js\";\nimport {\n QueryValidatorsRequest,\n QueryValidatorsResponse,\n} from \"cosmjs-types/cosmos/staking/v1beta1/query.js\";\nimport {\n Validator,\n} from \"cosmjs-types/cosmos/staking/v1beta1/staking.js\";\nimport {\n Tx,\n} from \"cosmjs-types/cosmos/tx/v1beta1/tx.js\";\nimport Fastify, {\n FastifyInstance,\n} from \"fastify\";\nimport {\n chain,\n} from \"stream-chain\";\nimport pick from \"stream-json/filters/pick.js\";\nimport parser from \"stream-json/parser.js\";\nimport streamArray from \"stream-json/streamers/stream-array.js\";\nimport streamValues from \"stream-json/streamers/stream-values.js\";\nimport batch from \"stream-json/utils/batch.js\";\nimport * as winston from \"winston\";\n\nimport {\n CONNECT_TIMEOUT_MS,\n DEFAULT_BATCH_SIZE, DEFAULT_BIND_HOST, DEFAULT_HEALTH_CHECK_PORT, DEFAULT_POLLING_INTERVAL_MS, DEFAULT_PROMETHEUS_PORT, DEFAULT_START_HEIGHT,\n GENESIS_BATCH_SIZE, IDLE_CHECK_INTERVAL_MS, MAX_FAILURES_PER_BLOCK, PAGINATION_LIMITS, PERIODIC_INTERVALS, RETRY_BASE_DELAY_MS, RETRY_MAX_DELAY_MS, RPC_TIMEOUT_MS,\n} from \"../constants.js\";\nimport {\n EclesiaEmitter,\n} from \"../emitter/index.js\";\nimport {\n ConfigurationError, RPCError,\n} from \"../errors/index.js\";\nimport {\n IndexerMetrics,\n} from \"../metrics/index.js\";\nimport {\n CircularBuffer,\n} from \"../promise-queue/index.js\";\nimport {\n BlockQueue, EclesiaIndexerConfig, EmitFunc, MinimalBlockQueue, WithHeightAndUUID,\n} from \"../types/index.js\";\nimport {\n decodeAttr, hasBlockEventMode, redactUrl, retryDelay, withTimeout,\n} from \"../utils/index.js\";\nimport {\n validateFilePath, validatePort, validatePositiveInteger, validateUrl,\n} from \"../validation/index.js\";\n\n/** Default configuration for the Eclesia indexer */\nexport const defaultIndexerConfig = {\n startHeight: DEFAULT_START_HEIGHT, // Start indexing from block 1\n batchSize: DEFAULT_BATCH_SIZE, // Process blocks in batches of 500\n modules: [], // No modules enabled by default\n getNextHeight: () => DEFAULT_START_HEIGHT, // Default height retrieval function\n logLevel: \"info\" as EclesiaIndexerConfig[\"logLevel\"], // Default log level\n usePolling: false, // Use WebSocket subscription by default\n pollingInterval: DEFAULT_POLLING_INTERVAL_MS, // Poll every 5 seconds when polling enabled\n shouldProcessGenesis: () => false, // Skip genesis processing by default\n minimal: true, // Use minimal indexing by default\n enableHealthcheck: true, // Enable health check server by default\n healthCheckPort: DEFAULT_HEALTH_CHECK_PORT, // Default health check port\n enablePrometheus: false, // Disable Prometheus metrics server by default\n prometheusPort: DEFAULT_PROMETHEUS_PORT, // Default Prometheus metrics server port\n init: () => Promise.resolve(), // No-op initialization function\n beginTransaction: () => Promise.resolve(), // No-op transaction begin function\n endTransaction: (_status: boolean) => Promise.resolve(), // No-op transaction end function\n};\n\n/**\n * Core blockchain indexer that connects to Tendermint RPC and processes blocks\n * Extends EclesiaEmitter to provide event-driven architecture for modules\n */\nexport class EclesiaIndexer extends EclesiaEmitter {\n /** Indexer configuration settings */\n public config: EclesiaIndexerConfig;\n\n /** Fastify HTTP server for health checks */\n private fastify: FastifyInstance | null = null;\n\n /** Prometheus HTTP server instance */\n private prometheusServer: FastifyInstance | null = null;\n\n /** Indicates if the indexer has started */\n private started: boolean = false;\n\n /** Queue for managing block processing pipeline */\n private blockQueue: BlockQueue;\n\n /** Latest block height from the chain */\n private latestHeight!: number;\n\n /** Next block height to process */\n public heightToProcess!: number;\n\n /** Whether the indexer has been initialized */\n private initialized = false;\n\n /** Prometheus metrics server instance */\n public prometheus: IndexerMetrics | null = null;\n\n /** Number of retry attempts for error recovery */\n private retryCount = 0;\n\n /** CometBFT client for ad-hoc queries */\n public client!: CometClient;\n\n /** CometBFT client for block and validator queries */\n public blockClient!: CometClient;\n\n /** Winston logger instance */\n public log: winston.Logger;\n\n /** Flag indicating if indexer should attempt recovery */\n private tryToRecover: boolean = false;\n\n /** Health check status for monitoring */\n private healthCheck = {\n status: \"CONNECTING\",\n };\n\n /** WebSocket subscription for new block notifications */\n private subscription: ReturnType<CometClient[\"subscribeNewBlock\"]> | null = null;\n\n /** Timeout handler for block reception */\n private blockTimeout: NodeJS.Timeout | null = null;\n\n /** Timer for the next poll in polling mode */\n private pollTimer: NodeJS.Timeout | null = null;\n\n /** Bumped on every (re)start and stop so a polling chain from a previous run exits */\n private pollGeneration = 0;\n\n /** Bumped on every start() so callbacks left over from a previous run cannot trigger recovery in this one */\n private runGeneration = 0;\n\n /**\n * Rejecters of waits parked in waitForBlockData(). Each entry is removed as soon as its block\n * arrives, so a healthy run keeps this empty instead of accumulating one entry per block.\n */\n private blockWaiters = new Set<(error: Error) => void>();\n\n /** Pending restart timer */\n private retryTimer: NodeJS.Timeout | null = null;\n\n /** Resolves when the indexer has stopped for good: stop() was called, endHeight was reached, or it gave up */\n private stopped: Promise<void> = Promise.resolve();\n\n private resolveStopped: () => void = () => {};\n\n /** Next height the fetcher will request; advances as fetches are enqueued */\n private nextFetchHeight = 0;\n\n /** Whether a fetcher loop is active, and for which run */\n private fetcherRunning = false;\n\n private fetcherGeneration = 0;\n\n private fetcherToken = 0;\n\n /** Height of the block whose processing failed most recently, and how many times in a row */\n private lastFailedHeight: number | undefined;\n\n private sameHeightFailures = 0;\n\n /**\n * Creates a new Eclesia indexer instance\n * @param config - Indexer configuration options\n */\n constructor(config: EclesiaIndexerConfig) {\n super();\n\n // Validate required configuration\n validateUrl(config.rpcUrl, \"rpcUrl\");\n validatePositiveInteger(config.batchSize, \"batchSize\");\n\n // Validate optional genesis path if processing genesis\n if (config.genesisPath) {\n validateFilePath(config.genesisPath, \"genesisPath\");\n }\n\n // Validate health check port if provided\n if (config.healthCheckPort !== undefined) {\n validatePort(config.healthCheckPort, \"healthCheckPort\");\n }\n\n // Validate prometheus port if provided\n if (config.prometheusPort !== undefined) {\n validatePort(config.prometheusPort, \"prometheusPort\");\n }\n\n // Validate start height if provided\n if (config.startHeight !== undefined) {\n validatePositiveInteger(config.startHeight, \"startHeight\");\n }\n\n // Validate polling interval if provided\n if (config.pollingInterval !== undefined) {\n validatePositiveInteger(config.pollingInterval, \"pollingInterval\");\n }\n\n // Explicit undefined values (typical when config is assembled from env vars) must not\n // override the defaults, so drop them before merging\n const provided = Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n ) as EclesiaIndexerConfig;\n this.config = {\n ...defaultIndexerConfig,\n ...provided,\n };\n\n // Structured logging to stdout only: files, rotation and shipping are the deployment's job.\n // Errors are passed as { error } so their stack survives; the text format prints it under\n // the message and the json format emits it as a nested object.\n const errorFormat = winston.format((info) => {\n const error = info.error;\n if (error instanceof Error) {\n info.error = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n }\n else if (error !== undefined && (typeof error !== \"object\" || error === null)) {\n info.error = {\n message: String(error),\n };\n }\n return info;\n });\n const textFormat = winston.format.printf(({\n level, message, timestamp, error,\n }) => {\n const detail = error as {\n stack?: string\n message?: string\n } | undefined;\n const suffix = detail ? \"\\n\" + (detail.stack ?? detail.message ?? \"\") : \"\";\n return `${timestamp} [${level.toUpperCase()}]:\\t${message}${suffix}`;\n });\n this.log = winston.createLogger({\n level: this.config.logLevel,\n defaultMeta: {\n service: \"Eclesia Indexer\",\n },\n transports: [\n new winston.transports.Console({\n format: this.config.logFormat === \"json\"\n ? winston.format.combine(winston.format.splat(),\n winston.format.timestamp(),\n errorFormat(),\n winston.format.json())\n : winston.format.combine(winston.format.splat(),\n winston.format.timestamp(),\n errorFormat(),\n textFormat,\n winston.format.colorize({\n all: true,\n })),\n }),\n ],\n });\n\n // cosmjs cannot subscribe to blocks over plain HTTP; that needs a ws:// or wss:// URL.\n // Switch to polling now instead of failing after several restarts.\n const protocol = new URL(this.config.rpcUrl).protocol;\n if (!this.config.usePolling && (protocol === \"http:\" || protocol === \"https:\")) {\n this.log.warn(\"rpcUrl \" + redactUrl(this.config.rpcUrl) + \" is HTTP, which cannot deliver block subscriptions; polling every \" + this.config.pollingInterval + \" ms instead (use a ws:// or wss:// URL for WebSocket mode)\");\n this.config.usePolling = true;\n }\n\n // Initialize block queue based on minimal or full indexing mode\n // Pass error handler that uses the logger\n const queueErrorHandler = (e: unknown) => {\n this.prometheus?.recordError(\"rpc\");\n this.log.error(\"Error enqueueing block data\", {\n error: e,\n });\n };\n\n if (this.config.minimal) {\n // Minimal mode: only store block and block results\n this.blockQueue = new CircularBuffer<[BlockResponse, BlockResultsResponse]>(this.config.batchSize, queueErrorHandler);\n }\n else {\n // Full mode: also store validator information\n this.blockQueue = new CircularBuffer<[BlockResponse, BlockResultsResponse, Uint8Array]>(this.config.batchSize, queueErrorHandler);\n }\n\n this.on(\"_unhandled\",\n (msg) => {\n // Guarded: this runs once per unhandled message, and winston formats a record before\n // the transport drops it by level\n if (msg.type !== \"uuid\" && this.log.isVerboseEnabled()) {\n this.log.verbose(\"Unhandled event: \" + msg.type);\n }\n });\n if (this.config.enablePrometheus) {\n this.prometheus = new IndexerMetrics();\n this.prometheusServer = Fastify({\n logger: false,\n });\n this.prometheusServer.get(\"/metrics\",\n async (_req, res) => {\n res.header(\"Content-Type\", this.prometheus!.registry.contentType);\n res.send(await this.prometheus!.getMetrics());\n },\n );\n\n const prometheusPort = this.config.prometheusPort\n ?? (process.env.PROMETHEUS_PORT ? parseInt(process.env.PROMETHEUS_PORT, 10) : DEFAULT_PROMETHEUS_PORT);\n this.prometheusServer.listen({\n port: prometheusPort,\n host: this.config.prometheusHost ?? DEFAULT_BIND_HOST,\n },\n (err) => {\n if (err) {\n this.log.error(\"Prometheus server error\", {\n error: err,\n });\n this.prometheus?.recordError(\"metrics_server\");\n this.emit(\"fatal-error\", {\n error: err,\n message: \"Failed to start metrics server\",\n });\n }\n });\n }\n if (this.config.enableHealthcheck) {\n this.fastify = Fastify({\n logger: false,\n });\n this.fastify.get(\"/health\",\n async (_request, reply) => {\n // WAITING means caught up with an idle chain, which is healthy\n const code = this.healthCheck.status == \"OK\" || this.healthCheck.status == \"WAITING\"\n ? 200\n : 503;\n reply.code(code).send(this.healthCheck);\n });\n const healthPort = this.config.healthCheckPort\n ?? (process.env.HEALTH_CHECK_PORT ? parseInt(process.env.HEALTH_CHECK_PORT, 10) : DEFAULT_HEALTH_CHECK_PORT);\n this.fastify.listen({\n port: healthPort,\n host: this.config.healthCheckHost ?? DEFAULT_BIND_HOST,\n },\n (err) => {\n if (err) {\n this.log.error(\"Health check server error\", {\n error: err,\n });\n this.prometheus?.recordError(\"health_check_server\");\n this.emit(\"fatal-error\", {\n error: err,\n message: \"Failed to start health check server\",\n });\n }\n });\n }\n }\n\n private setStatus(status: string) {\n this.healthCheck.status = status;\n this.prometheus?.setWaiting(status === \"WAITING\");\n }\n\n /**\n * Marks the current run for recovery and wakes the main loop if it is parked waiting for a\n * block. Callbacks left over from a previous run pass their generation and are ignored.\n */\n private requestRecovery(reason: string, generation: number = this.runGeneration): void {\n if (generation !== this.runGeneration) {\n this.log.debug(\"Ignoring recovery request from a previous run: \" + reason);\n return;\n }\n if (!this.tryToRecover) {\n this.log.warn(\"Recovery requested: \" + reason);\n }\n this.tryToRecover = true;\n this.wakeBlockWaiters(\"Recovery requested while waiting for block data\");\n }\n\n /** Rejects every wait parked in waitForBlockData() */\n private wakeBlockWaiters(reason: string): void {\n const waiters = [...this.blockWaiters];\n this.blockWaiters.clear();\n for (const reject of waiters) {\n reject(new RPCError(reason));\n }\n }\n\n /**\n * Refuses to index a chain other than the configured one. An RPC pool that mixes networks, or\n * a wrong URL, would otherwise write a different chain's blocks into the database.\n */\n private assertChainId(network: string): void {\n if (this.config.chainId !== undefined && network !== this.config.chainId) {\n throw new ConfigurationError(\"RPC serves chain \" + network + \" but chainId is configured as \" + this.config.chainId, {\n expected: this.config.chainId,\n actual: network,\n });\n }\n }\n\n /** Counts consecutive processing failures per block height */\n private noteBlockFailure(height: number): void {\n if (height === this.lastFailedHeight) {\n this.sameHeightFailures++;\n }\n else {\n this.lastFailedHeight = height;\n this.sameHeightFailures = 1;\n }\n }\n\n /** True once one block has failed maxFailuresPerBlock times in a row */\n private isStuck(): boolean {\n return this.lastFailedHeight !== undefined\n && this.sameHeightFailures >= (this.config.maxFailuresPerBlock ?? MAX_FAILURES_PER_BLOCK);\n }\n\n /**\n * Waits for the next dequeued block but wakes early when recovery or stop is requested, so a\n * loop parked on an empty queue never waits for a block that will not come.\n */\n private waitForBlockData<T>(dequeued: Promise<T>): Promise<T> {\n if (this.tryToRecover || !this.started) {\n return Promise.reject(new RPCError(\"Recovery requested while waiting for block data\"));\n }\n return new Promise<T>((resolve, reject) => {\n this.blockWaiters.add(reject);\n dequeued.then((value) => {\n this.blockWaiters.delete(reject);\n resolve(value);\n },\n (error) => {\n this.blockWaiters.delete(reject);\n reject(error);\n });\n });\n }\n\n /** (Re)arms the idle check that runs when no block has been announced for a while */\n private armIdleCheck(): void {\n if (this.blockTimeout) {\n clearTimeout(this.blockTimeout);\n }\n this.blockTimeout = setTimeout(() => {\n this.checkLiveness();\n }, IDLE_CHECK_INTERVAL_MS);\n }\n\n /**\n * Runs when no block has been announced for IDLE_CHECK_INTERVAL_MS. A chain that has stopped\n * producing blocks (halt, upgrade, slow chain) is not an error: the indexer reports WAITING and\n * checks again later. Recovery is requested only when the chain has moved on without us, which\n * means the subscription is dead, or when the RPC cannot be reached at all.\n */\n private async checkLiveness(): Promise<void> {\n if (!this.started) {\n return;\n }\n const generation = this.runGeneration;\n try {\n const status = await withTimeout(this.client.status(), RPC_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n if (!this.started || generation !== this.runGeneration) {\n return;\n }\n const chainHeight = status.syncInfo.latestBlockHeight;\n if (chainHeight > this.latestHeight) {\n this.requestRecovery(\"chain is at \" + chainHeight + \" but nothing was announced since \" + this.latestHeight, generation);\n return;\n }\n this.log.info(\"No new block for \" + IDLE_CHECK_INTERVAL_MS / 1000 + \" s, chain height is still \" + chainHeight);\n this.setStatus(\"WAITING\");\n this.armIdleCheck();\n }\n catch (e) {\n this.log.error(\"Liveness check failed\", {\n error: e,\n });\n this.requestRecovery(\"RPC unreachable during liveness check\", generation);\n }\n }\n\n private blockListener = {\n next: (data: {\n header: {\n height: number\n }\n }) => {\n this.newBlockReceived(data.header.height);\n },\n error: (error: unknown) => {\n this.log.error(\"Block subscription error\", {\n error,\n });\n this.requestRecovery(\"block subscription errored\");\n },\n complete: () => {\n if (this.started) {\n this.requestRecovery(\"block subscription closed by the node\");\n }\n },\n };\n\n private isMinimal(_blockqueue: BlockQueue): _blockqueue is MinimalBlockQueue {\n if (this.config.minimal) {\n return true;\n }\n else {\n return false;\n }\n }\n\n public async connect() {\n try {\n if (this.client) {\n this.log.verbose(\"Recover from error. Attempting to disconnect from RPC\");\n try {\n this.client.disconnect();\n }\n catch (_e) { /* empty */ }\n try {\n this.blockClient?.disconnect();\n }\n catch (_e) { /* empty */ }\n this.log.verbose(\"Disconnected from RPC\");\n }\n this.log.info(\"Attempting to connect to RPC: \" + redactUrl(this.config.rpcUrl));\n this.client = await this.connectWithTimeout();\n await withTimeout(this.client.status(), CONNECT_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.log.info(\"Connected to RPC for ad hoc queries\");\n this.blockClient = await this.connectWithTimeout();\n await withTimeout(this.blockClient.status(), CONNECT_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.log.info(\"Connected to RPC for block & validator info\");\n\n return true;\n }\n catch (error) {\n this.log.error(\"RPC connection error\", {\n error,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"RPC connection failed\");\n return false;\n }\n }\n\n /**\n * Opens one CometBFT client with its own timeout. If the timeout wins, the client\n * that may still arrive is disconnected so a slow RPC never leaks a socket.\n */\n private async connectWithTimeout(): Promise<CometClient> {\n let timedOut = false;\n const pending = connectComet(this.config.rpcUrl);\n pending.then((client) => {\n if (timedOut) {\n try {\n client.disconnect();\n }\n catch (_e) { /* empty */ }\n }\n }).catch(() => { /* surfaced through the race below */ });\n try {\n return await withTimeout(pending, CONNECT_TIMEOUT_MS, new RPCError(\"RPC connection timed out\"));\n }\n catch (e) {\n timedOut = true;\n throw e;\n }\n }\n\n private async initialize() {\n if (!this.initialized) {\n try {\n if (this.config.init) {\n await this.config.init();\n }\n }\n catch (e) {\n this.log.error(\"Failed to initialize indexer\", {\n error: e,\n });\n\n this.prometheus?.recordError(\"init_error\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n if (await this.config.shouldProcessGenesis()) {\n try {\n if (this.config.genesisPath) {\n await this.parseGenesis();\n }\n else {\n this.log.warn(\"shouldProcessGenesis() returned true but no genesisPath is configured, skipping genesis import\");\n }\n }\n catch (e) {\n this.log.error(\"Failed to parse genesis\", {\n error: e,\n });\n\n this.prometheus?.recordError(\"genesis_error\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n }\n this.initialized = true;\n }\n }\n\n /**\n * Stops the indexer and releases everything that would keep the process alive:\n * the block subscription, polling and inactivity timers, both RPC clients and the\n * health and metrics servers. Safe to call more than once.\n */\n public async stop(): Promise<void> {\n this.started = false;\n this.resolveStopped();\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n this.wakeBlockWaiters(\"Indexer stopped while waiting for block data\");\n this.stopPolling();\n if (this.blockTimeout) {\n clearTimeout(this.blockTimeout);\n this.blockTimeout = null;\n }\n if (this.subscription) {\n try {\n this.subscription.removeListener(this.blockListener);\n }\n catch (_e) { /* empty */ }\n this.subscription = null;\n }\n for (const client of [this.client, this.blockClient]) {\n try {\n client?.disconnect();\n }\n catch (_e) { /* empty */ }\n }\n const servers = [this.fastify, this.prometheusServer];\n this.fastify = null;\n this.prometheusServer = null;\n await Promise.all(servers.map(server => server?.close().catch((e: unknown) => {\n this.log.warn(\"Error closing HTTP server\", {\n error: e,\n });\n })));\n this.log.info(\"Indexer stopped\");\n }\n\n private clearBlockQueue() {\n if (this.blockQueue) {\n this.blockQueue.clear();\n this.log.verbose(\"Starting, clearing block queue\");\n }\n }\n\n private async setupBlockListening() {\n const connected = await this.connect();\n if (!connected) {\n this.setStatus(\"FAILED\");\n throw new RPCError(\"Failed to connect to RPC\");\n }\n\n try {\n if (!this.config.usePolling && this.subscription) {\n this.subscription.removeListener(this.blockListener);\n this.subscription = null;\n this.log.verbose(\"Removed existing block listener and subscription\");\n }\n if (!this.config.usePolling) {\n this.subscription = this.client.subscribeNewBlock\n ? this.client.subscribeNewBlock()\n : null;\n }\n const status: StatusResponse = await withTimeout(this.client.status(), RPC_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.assertChainId(status.nodeInfo.network);\n this.latestHeight = status.syncInfo.latestBlockHeight;\n this.log.info(\"Connected to \" + status.nodeInfo.network + \", current chain height: \" + this.latestHeight);\n\n this.heightToProcess = await this.config.getNextHeight();\n this.nextFetchHeight = this.heightToProcess;\n if (this.config.usePolling) {\n this.startPolling();\n }\n else {\n if (this.subscription) {\n this.subscription.addListener(this.blockListener);\n }\n else {\n this.prometheus?.recordError(\"rpc\");\n throw new Error(\"Could not subscribe to new blocks\");\n }\n }\n // A subscription that never delivers anything must still be noticed\n this.armIdleCheck();\n }\n catch (e) {\n this.log.error(\"Failed to set up block listening\", {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n }\n\n /**\n * Resolves once the indexer has stopped for good: stop() was called, endHeight was reached, or\n * a fatal-error was emitted. Restarts with backoff do not resolve it. Use it to keep a caller\n * waiting for the whole run rather than for the first loop exit.\n */\n public whenStopped(): Promise<void> {\n return this.stopped;\n }\n\n public async start() {\n if (!this.started) {\n // A fresh run (not a restart after backoff) gets a fresh completion promise\n this.stopped = new Promise<void>((resolve) => {\n this.resolveStopped = resolve;\n });\n }\n this.started = true;\n this.runGeneration++;\n const generation = this.runGeneration;\n this.tryToRecover = false;\n this.blockWaiters.clear();\n this.clearBlockQueue();\n await this.initialize();\n try {\n await this.setupBlockListening();\n this.log.debug(\"Starting main processing loop\");\n this.fetcher().catch((e) => {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.log.error(\"Error in fetching service\", {\n error: e,\n });\n this.requestRecovery(\"fetcher failed\", generation);\n });\n }\n catch (e) {\n this.requestRecovery(\"block listening setup failed: \" + e, generation);\n }\n\n let lastProcessed: number | undefined;\n while (this.started && !this.tryToRecover) {\n let txOpen = false;\n let failingHeight: number | undefined;\n try {\n this.prometheus?.updateRetryCount(this.retryCount);\n if (this.blockQueue.synced && this.blockQueue.size() <= 1) {\n // Only the sentinel is queued: we are at the chain tip. Waiting here is normal and can\n // last hours during a halt or an upgrade, so no transaction is held while we wait.\n this.setStatus(\"WAITING\");\n }\n let height: number;\n let timestamp: string;\n\n // Main block processing (minimal)\n if (this.isMinimal(this.blockQueue)) {\n const toProcess = await this.waitForBlockData(this.blockQueue.dequeue());\n this.log.silly(\"Retrieved block data\");\n if (!toProcess || !toProcess[0] || !toProcess[1]) {\n throw new RPCError(\"Could not fetch block(minimal)\");\n }\n height = toProcess[0].block.header.height;\n failingHeight = height;\n timestamp = toRfc3339WithNanoseconds(toProcess[0].block.header.time);\n // Index block inside a db transaction to ensure data consistency\n await this.config.beginTransaction();\n txOpen = true;\n this.log.silly(\"Started db tx\");\n await this.processBlock(toProcess[0],\n toProcess[1]);\n }\n // Main block processing (full)\n else {\n const toProcess = await this.waitForBlockData(this.blockQueue.dequeue());\n this.log.silly(\"Retrieved block data\");\n if (!toProcess || !toProcess[0] || !toProcess[1] || !toProcess[2]) {\n throw new RPCError(\"Could not fetch block(full)\");\n }\n\n this.log.silly(\"Decoded block\");\n height = toProcess[0].block.header.height;\n failingHeight = height;\n timestamp = toRfc3339WithNanoseconds(toProcess[0].block.header.time);\n await this.config.beginTransaction();\n txOpen = true;\n this.log.silly(\"Started db tx\");\n await this.processBlock(toProcess[0],\n toProcess[1],\n QueryValidatorsResponse.decode(toProcess[2]).validators);\n }\n\n // Emit events to trigger periodic operations every 50, 100 and 1000 blocks\n if (height % PERIODIC_INTERVALS.LARGE == 0) {\n await this.asyncEmit(\"periodic/large\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n if (height % PERIODIC_INTERVALS.MEDIUM == 0) {\n await this.asyncEmit(\"periodic/medium\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n if (height % PERIODIC_INTERVALS.SMALL == 0) {\n await this.asyncEmit(\"periodic/small\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n this.log.silly(\"Handled periodic events\");\n\n await this.config.endTransaction(true);\n txOpen = false;\n lastProcessed = height;\n this.lastFailedHeight = undefined;\n this.sameHeightFailures = 0;\n\n this.log.silly(\"Committed db tx\");\n }\n catch (e) {\n if (txOpen) {\n try {\n await this.config.endTransaction(false);\n }\n catch (dbe) {\n this.prometheus?.recordError(\"database\");\n this.log.error(\"Error ending transaction\", {\n error: dbe,\n });\n }\n }\n if (!this.started) {\n // stop() woke us up; nothing failed\n break;\n }\n if (failingHeight !== undefined) {\n // The data was there and processing failed: count it against this block\n this.noteBlockFailure(failingHeight);\n }\n // any error here is likely recoverable (e.g. RPC timeout, DB error)\n this.prometheus?.recordError(\"block\");\n this.log.error(\"Block processing error\", {\n error: e,\n });\n this.setStatus(\"FAILED\");\n this.requestRecovery(\"block processing failed\", generation);\n break;\n }\n // Reset retry count and status on successful block processing\n this.retryCount = 0;\n this.setStatus(\"OK\");\n if (this.config.endHeight !== undefined && lastProcessed !== undefined && lastProcessed >= this.config.endHeight) {\n this.log.info(\"Reached configured end height \" + this.config.endHeight + \". Stopping indexer.\");\n await this.stop();\n return;\n }\n }\n\n // Normal exit from processing loop\n if (!this.started) {\n this.log.info(\"Indexer manually stopped.\");\n return;\n }\n\n // A block that keeps failing after its data was fetched is a bug or bad data, not an outage.\n // Give up loudly instead of retrying it forever.\n if (this.isStuck()) {\n const height = this.lastFailedHeight;\n this.log.error(\"Block \" + height + \" failed \" + this.sameHeightFailures + \" times in a row. This is a deterministic failure in a handler or the data, not an outage. Giving up.\");\n this.started = false;\n this.emit(\"fatal-error\", {\n error: new Error(\"Block \" + height + \" failed \" + this.sameHeightFailures + \" times in a row\"),\n message: \"Block processing is stuck\",\n retryCount: this.retryCount,\n height,\n });\n this.resolveStopped();\n return;\n }\n\n // Abnormal exit: restart with exponential backoff. Retries are unlimited unless maxRetries\n // is configured, because an RPC or database outage of any length must not kill the indexer.\n this.retryCount++;\n if (this.config.maxRetries !== undefined && this.retryCount > this.config.maxRetries) {\n this.log.error(\"Indexer failed \" + this.retryCount + \" times in a row, giving up (maxRetries=\" + this.config.maxRetries + \")\");\n this.started = false;\n this.emit(\"fatal-error\", {\n error: new Error(\"Max retry attempts exceeded\"),\n message: \"Indexer failed too many times\",\n retryCount: this.retryCount,\n });\n this.resolveStopped();\n return;\n }\n const delay = retryDelay(this.retryCount, RETRY_BASE_DELAY_MS, RETRY_MAX_DELAY_MS);\n this.log.warn(\"Indexer is restarting in \" + delay / 1000 + \" s (attempt \" + this.retryCount + \")\");\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.start().catch((e) => {\n this.log.error(\"Restart failed\", {\n error: e,\n });\n });\n }, delay);\n }\n\n /**\n * Emits an event and waits for its handlers. Handlers run one after another in registration\n * order: they share one database connection and one transaction, so interleaving them at\n * await points would let two handlers read and write the same rows in an unpredictable order,\n * and a failure in one would leave the others mid-flight while the block is rolled back. The\n * first rejection propagates and stops the remaining handlers.\n */\n public asyncEmit: EmitFunc<keyof WithHeightAndUUID<EventMap>> = async (\n type,\n event,\n ) => {\n const handlers = this.handlersFor(type);\n if (handlers.length === 0) {\n // Same routing as emit(): the _unhandled listener logs it at verbose level\n this.emit(type,\n event);\n return;\n }\n for (const handler of handlers) {\n await handler(event);\n }\n };\n\n private async processBlock(block: BlockResponse, block_results: BlockResultsResponse | BlockResultsResponse38, validators?: Validator[]) {\n const endTimer = this.prometheus?.timeBlockProcessing();\n const height = block.block.header.height;\n this.heightToProcess = height;\n this.log.debug(\"Processing block: %d\",\n height);\n // Initialize height & timestamp to be used for this block-processing run\n const timestamp = toRfc3339WithNanoseconds(block.block.header.time);\n\n // Use & await asyncEmit to ensure db insertions in order\n\n /*\n * Emit block information to any interested modules.\n * Primarily the required block module listens to this\n */\n await this.asyncEmit(\"block\",\n {\n value: {\n block,\n block_results,\n },\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled block event\");\n\n let beginBlockEvents: readonly Event[] | readonly Event38[];\n let endBlockEvents: readonly Event[] | readonly Event38[];\n if ((block_results as BlockResultsResponse38).finalizeBlockEvents) {\n // Cosmos SDK 0.50+ tags each finalize_block event with mode=BeginBlock / mode=EndBlock (baseapp.go)\n beginBlockEvents = (block_results as BlockResultsResponse38).finalizeBlockEvents.filter(x => hasBlockEventMode(x, \"BeginBlock\")) as readonly Event38[];\n endBlockEvents = (block_results as BlockResultsResponse38).finalizeBlockEvents.filter(x => hasBlockEventMode(x, \"EndBlock\")) as readonly Event38[];\n }\n else {\n beginBlockEvents = (block_results as BlockResultsResponse).beginBlockEvents;\n endBlockEvents = (block_results as BlockResultsResponse).endBlockEvents;\n }\n // Deal with begin_block events first\n await this.asyncEmit(\"begin_block\",\n {\n value: {\n events: beginBlockEvents!,\n validators,\n },\n height,\n timestamp,\n });\n\n this.log.silly(\"Modules handled begin_block events\");\n\n // Then individual tx_events\n await this.asyncEmit(\"tx_events\",\n {\n value: block_results.results,\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled tx events\");\n\n // Emit details and result for each tx msg separately\n for (let t = 0; t < block.block.txs.length; t++) {\n const tx = Tx.decode(block.block.txs[t]);\n\n const result = block_results.results[t].code;\n const txlog = block_results.results[t].log;\n\n if (result != 0) {\n // Tx failed. Ignore\n continue;\n }\n if (tx.body && tx.body.memo != \"\") {\n const txHash = createHash(\"sha256\").update(block.block.txs[t])\n .digest(\"hex\");\n await this.asyncEmit(\"tx_memo\",\n {\n value: {\n txHash,\n txBody: tx.body,\n },\n height,\n timestamp,\n });\n }\n // parsing log rather than using events directly in order to have msg_index available to filter appropriate events for each msg\n let events: Array<{\n msg_index?: number\n events: (Event | Event38)[]\n }> = [];\n if (txlog) {\n try {\n const parsed = JSON.parse(txlog);\n if (Array.isArray(parsed)) {\n events = parsed;\n }\n }\n catch (_e) {\n // Not every chain writes a JSON log; the msg_index attributes below cover those\n this.log.silly(\"Tx log is not JSON, using msg_index attributes instead\");\n }\n }\n if (events.length == 0) {\n const eventsToAdd: typeof events = [];\n this.log.silly(\"No events found in tx log. Parsing events for msg_index\");\n for (let m = 0; m < block_results.results[t].events.length; m++) {\n if (block_results.results[t].events[m].attributes.find(a => decodeAttr(a.key) == \"msg_index\")) {\n const mi = decodeAttr(block_results.results[t].events[m].attributes.find(a => decodeAttr(a.key) == \"msg_index\")?.value ?? \"\");\n if (mi != \"\") {\n const miNum = parseInt(mi);\n let ev = eventsToAdd.find(x => x.msg_index == miNum);\n if (!ev) {\n ev = {\n msg_index: miNum,\n events: [block_results.results[t].events[m]],\n };\n eventsToAdd.push(ev);\n }\n else {\n ev.events.push(block_results.results[t].events[m] as Event);\n }\n }\n }\n }\n events = events.concat(eventsToAdd);\n }\n const msgs = tx.body?.messages;\n\n if (msgs) {\n for (let i = 0; i < msgs.length; i++) {\n if (this.log.isSillyEnabled()) {\n this.log.silly(\"Indexer broadcasting msg for handling: \" + msgs[i].typeUrl);\n }\n const msgevents\n = msgs.length > 1\n ? events.find(x => x.msg_index == i)?.events\n : events[0]?.events ?? [];\n await this.asyncEmit(msgs[i].typeUrl as never,\n {\n value: {\n tx: msgs[i].value as never,\n events: msgevents,\n } as never,\n height,\n timestamp,\n });\n if (msgs[i].typeUrl == \"/cosmos.authz.v1beta1.MsgExec\") {\n const authzMsgs = MsgExec.decode(msgs[i].value).msgs;\n if (authzMsgs) {\n for (let r = 0; r < authzMsgs.length; r++) {\n if (this.log.isSillyEnabled()) {\n this.log.silly(\"Indexer broadcasting msg for handling: \" + authzMsgs[r].typeUrl);\n }\n const authzMsgEvents = msgevents?.reduce((events, evt) => {\n if (evt.attributes.filter(x => decodeAttr(x.key) == \"authz_msg_index\" && decodeAttr(x.value) == \"\" + r).length > 0) {\n events.push(evt);\n }\n return events;\n },\n [] as (Event | Event38)[]);\n await this.asyncEmit(authzMsgs[r].typeUrl as never,\n {\n value: {\n tx: authzMsgs[r].value as never,\n events: authzMsgEvents,\n } as never,\n height,\n timestamp,\n });\n }\n }\n }\n }\n }\n }\n this.log.silly(\"Modules handled msg events\");\n this.prometheus?.recordTransactions(block.block.txs.length);\n // Then deal with end_block events\n await this.asyncEmit(\"end_block\",\n {\n value: endBlockEvents!,\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled end_block events\");\n\n endTimer?.();\n this.prometheus?.updateBlockMetrics(height, this.latestHeight, this.blockQueue.size());\n }\n\n /**\n * Fetches every height from nextFetchHeight up to latestHeight, waiting for queue space\n * before each fetch. Serves the initial catch-up and live blocks alike: a new announcement\n * only moves latestHeight and starts this loop if it is not already running, so bursts and\n * skipped announcements are handled by the same code and the queue can never overflow.\n */\n private async fetcher() {\n if (this.fetcherRunning && this.fetcherGeneration === this.runGeneration) {\n return;\n }\n const generation = this.runGeneration;\n const token = ++this.fetcherToken;\n this.fetcherRunning = true;\n this.fetcherGeneration = generation;\n try {\n while (\n this.nextFetchHeight <= this.latestHeight\n && (this.config.endHeight === undefined || this.nextFetchHeight <= this.config.endHeight)\n ) {\n // If some other async process triggers recovery, exit the fetching loop\n if (this.tryToRecover || !this.started || generation !== this.runGeneration) {\n this.log.verbose(\"Exiting fetcher loop. Attempting to recover indexer\");\n break;\n }\n const i = this.nextFetchHeight;\n this.log.debug(\"Fetching: \" + i);\n try {\n // Main fetching logic for minimal indexer\n if (this.isMinimal(this.blockQueue)) {\n // We do not await here so that multiple fetches can be in-flight\n const toIndex = withTimeout(Promise.all([this.blockClient.block(i) as Promise<BlockResponse>, this.blockClient.blockResults(i) as Promise<BlockResultsResponse>]), RPC_TIMEOUT_MS, new RPCError(\"Timed out fetching block \" + i)).catch((e) => {\n this.log.error(\"Error fetching block \" + i, {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"fetch failed for block \" + i, generation);\n return Promise.resolve([]);\n }) as Promise<[BlockResponse, BlockResultsResponse]>;\n this.blockQueue.enqueue(toIndex);\n }\n else {\n // Full indexer: block, block results and the complete validator set\n const toIndex = withTimeout(Promise.all([this.blockClient.block(i) as Promise<BlockResponse>, this.blockClient.blockResults(i) as Promise<BlockResultsResponse>, this.fetchValidatorSet(i)]), RPC_TIMEOUT_MS, new RPCError(\"Timed out fetching block \" + i)).catch((e) => {\n this.log.error(\"Error fetching block \" + i, {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"fetch failed for block \" + i, generation);\n return Promise.resolve([]);\n }) as Promise<[BlockResponse, BlockResultsResponse, Uint8Array]>;\n this.blockQueue.enqueue(toIndex);\n }\n }\n catch (e) {\n this.log.error(\"Fetching error\", {\n error: e,\n });\n break;\n }\n this.nextFetchHeight = i + 1;\n // Resolves immediately while the queue has room, otherwise when the processor dequeues\n await this.blockQueue.continue();\n }\n // Caught up with everything announced so far\n if (!this.tryToRecover && this.started && generation === this.runGeneration && !this.blockQueue.synced) {\n this.blockQueue.setSynced();\n this.log.info(\"Synced to latest height\");\n }\n }\n finally {\n if (this.fetcherToken === token) {\n this.fetcherRunning = false;\n }\n }\n }\n\n /**\n * Runs an ABCI query. A transport failure (RPC down, timeout, empty reply) requests a recovery.\n * A reply with a non-zero code is the chain answering \"no\" (pruned height, unknown path, bad\n * key): it is thrown as an RPCError with the code and log, and no recovery is requested for\n * ad-hoc queries, so modules can catch it. Block-pipeline queries reject into the fetcher,\n * which requests recovery itself.\n */\n public async callABCI(path: string, data: Uint8Array, height?: number, adHoc: boolean = true): Promise<Uint8Array> {\n let abciq;\n const endTimer = this.prometheus?.timeRpcCall(path) ?? void 0;\n try {\n abciq = await\n (adHoc\n ? this.client\n : this.blockClient).abciQuery({\n path,\n data,\n height: height,\n });\n }\n catch (e) {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"ABCI query failed for \" + path);\n throw new RPCError(\"RPC not responding. Query at: \" + path + \" (\" + e + \")\");\n }\n finally {\n endTimer?.();\n }\n if (!abciq) {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"empty ABCI response for \" + path);\n throw new RPCError(\"RPC not responding. Query at: \" + path);\n }\n if (abciq.code) {\n // Previously an error reply decoded as an empty result (for example zero validators)\n this.prometheus?.recordError(\"rpc\");\n throw new RPCError(\"ABCI query \" + path + \" failed with code \" + abciq.code + (abciq.log ? \": \" + abciq.log : \"\"));\n }\n return abciq.value;\n }\n\n /**\n * Fetches the complete validator set at a height, following pagination, and returns it\n * re-encoded as a single QueryValidatorsResponse so the block queue payload keeps its shape.\n * Chains with more validators than one page (1000) were silently truncated before.\n */\n private async fetchValidatorSet(height: number): Promise<Uint8Array> {\n const validators: Validator[] = [];\n let key: Uint8Array | undefined;\n do {\n const request = QueryValidatorsRequest.fromPartial({\n pagination: key\n ? {\n limit: PAGINATION_LIMITS.VALIDATORS,\n key,\n }\n : {\n limit: PAGINATION_LIMITS.VALIDATORS,\n },\n });\n const page = QueryValidatorsResponse.decode(\n await this.callABCI(\"/cosmos.staking.v1beta1.Query/Validators\", QueryValidatorsRequest.encode(request).finish(), height, false),\n );\n validators.push(...page.validators);\n key = page.pagination?.nextKey && page.pagination.nextKey.length > 0 ? page.pagination.nextKey : undefined;\n } while (key);\n return QueryValidatorsResponse.encode(QueryValidatorsResponse.fromPartial({\n validators,\n })).finish();\n }\n\n private newBlockReceived(height: number): void {\n this.armIdleCheck();\n this.log.info(\"Received new block: %d\",\n height);\n if (height <= this.latestHeight) {\n // Re-announced, or from a lagging node behind a load balancer: never move backwards\n return;\n }\n this.latestHeight = height;\n if (this.tryToRecover || !this.started) {\n return;\n }\n // The fetcher requests every height up to latestHeight and waits for queue space as it\n // goes, so a burst of blocks or an announcement that skipped heights is handled exactly\n // like the initial catch-up. Nothing to do if it is already running.\n this.fetcher().catch((e) => {\n this.log.error(\"Error in fetching service\", {\n error: e,\n });\n this.requestRecovery(\"fetcher failed\");\n });\n }\n\n /** Starts a single polling chain, retiring any chain left over from a previous run */\n private startPolling(): void {\n this.stopPolling();\n const generation = ++this.pollGeneration;\n this.pollForBlock(generation);\n }\n\n private stopPolling(): void {\n this.pollGeneration++;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n }\n\n private async pollForBlock(generation: number) {\n if (!this.started || generation !== this.pollGeneration) {\n return;\n }\n try {\n const status = await this.client.status();\n // A restart or stop may have happened while waiting on the RPC\n if (!this.started || generation !== this.pollGeneration) {\n return;\n }\n if (status.syncInfo.latestBlockHeight > this.latestHeight) {\n this.newBlockReceived(status.syncInfo.latestBlockHeight);\n }\n }\n catch (e) {\n this.log.error(\"Error polling for new block\", {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"polling failed\");\n // Recovery restarts polling from setupBlockListening\n return;\n }\n this.pollTimer = setTimeout(() => {\n this.pollForBlock(generation);\n },\n this.config.pollingInterval);\n }\n\n private readGenesis(): fs.ReadStream {\n if (this.config.genesisPath) {\n return fs.createReadStream(this.config.genesisPath);\n }\n else {\n throw new Error(\"Genesis path not set\");\n }\n }\n\n private async setArrayReader(path: string, processor: (chunk: unknown) => Promise<void>): Promise<boolean> {\n const readPromise = new Promise<boolean>((resolve, reject) => {\n try {\n const filters = path.split(\".\");\n const pickers = filters.map(filter => pick({\n filter,\n }));\n let counter = 0;\n let chunkCounter = 0;\n\n // Wrapper processor that handles transaction chunking\n const chunkProcessor = async (data: unknown) => {\n chunkCounter++;\n this.log.debug(`Processing genesis chunk ${chunkCounter}`);\n\n await processor(data);\n\n // Commit and restart transaction every 5 chunks (5000 entries)\n // This prevents timeout on large genesis files\n if (chunkCounter % 5 === 0) {\n this.log.debug(`Committing transaction after chunk ${chunkCounter}`);\n await this.config.endTransaction(true);\n await this.config.beginTransaction();\n }\n // Pass the chunk on so the \"data\" listener below can count what was processed\n return data;\n };\n\n chain([\n this.readGenesis(),\n parser(),\n ...pickers,\n streamArray(),\n batch({\n batchSize: GENESIS_BATCH_SIZE,\n }),\n chunkProcessor,\n ])\n .on(\"data\",\n (data) => {\n if (data && Array.isArray(data)) {\n counter = counter + data.length;\n }\n })\n .on(\"end\",\n () => {\n this.log.info(`Processed ${counter} entries in ${chunkCounter} chunks`);\n resolve(true);\n })\n // stream-chain re-emits parser and processor errors here; without a listener Node\n // raises them as an uncaught exception and parseGenesis never rolls back\n .on(\"error\",\n (e) => {\n this.log.error(\"Error reading genesis array \" + path, {\n error: e,\n });\n reject(e);\n });\n }\n catch (e) {\n this.log.verbose(\"Error in setArrayReader: \" + e);\n reject(e);\n }\n });\n\n return readPromise;\n }\n\n private async setValueReader(path: string, processor: (chunk: unknown) => Promise<void>): Promise<boolean> {\n const readPromise = new Promise<boolean>((resolve, reject) => {\n try {\n const filters = path.split(\".\");\n const pickers = filters.map(filter => pick({\n filter,\n }));\n\n let counter = 0;\n chain([this.readGenesis(), parser(), ...pickers, streamValues(), processor])\n .on(\"data\",\n (_data) => {\n counter++;\n })\n .on(\"end\",\n () => {\n this.log.info(`Processed ${counter} entries`);\n resolve(true);\n })\n .on(\"error\",\n (e) => {\n this.log.error(\"Error reading genesis value \" + path, {\n error: e,\n });\n reject(e);\n });\n }\n catch (e) {\n this.log.verbose(\"Error in setValueReader: \" + e);\n reject(e);\n }\n });\n\n return readPromise;\n }\n\n private async parseGenesis() {\n this.log.info(\"Parsing genesis\");\n // Lets the storage layer mark the import as in progress before anything is written\n await this.config.onGenesisStart?.();\n await this.config.beginTransaction();\n try {\n this.log.info(\"Starting genesis import\");\n this.log.debug(\"Importing genesis file...\");\n\n for (const [key, _value] of this.handled) {\n if (key.startsWith(\"genesis/\")) {\n const genesisEntry = key.split(\"/\");\n\n this.log.verbose(\"Importing \" + key + \"...\");\n if (genesisEntry[1] == \"array\") {\n await this.setArrayReader(genesisEntry[2],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n await this.asyncEmit(key as never,\n {\n value: data.map((x: {\n value: never\n }) => x.value),\n } as never);\n return data;\n });\n }\n else {\n await this.setValueReader(genesisEntry[2],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n await this.asyncEmit(key as never,\n {\n value: data.value,\n } as never);\n return data;\n });\n }\n }\n }\n\n this.log.info(\"Importing gen TXs...\");\n\n await this.setArrayReader(\"app_state.genutil.gen_txs\",\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n for (let j = 0; j < data.length; j++) {\n const gentx = data[j].value;\n for (let i = 0; i < gentx.body.messages.length; i++) {\n const msg = gentx.body.messages[i];\n await this.asyncEmit((\"gentx\" + msg[\"@type\"]) as never,\n {\n value: msg,\n } as never);\n }\n }\n return data;\n });\n // Recorded inside the last transaction, so \"complete\" commits together with the final chunk\n await this.config.onGenesisComplete?.();\n await this.config.endTransaction(true);\n\n this.log.info(\"Finished importing\");\n }\n catch (e) {\n try {\n await this.config.endTransaction(false);\n }\n catch (dbe) {\n this.prometheus?.recordError(\"database\");\n this.log.error(\"Error ending transaction\", {\n error: dbe,\n });\n }\n this.log.error(\"Failed to import genesis\");\n throw e;\n }\n }\n}\n\n/** @deprecated Misspelling kept for compatibility, use EclesiaIndexer. Removed in 3.0. */\nexport const EcleciaIndexer = EclesiaIndexer;\n/** @deprecated Misspelling kept for compatibility, use EclesiaIndexer. Removed in 3.0. */\nexport type EcleciaIndexer = EclesiaIndexer;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["DEFAULT_START_HEIGHT","DEFAULT_BATCH_SIZE","DEFAULT_POLLING_INTERVAL_MS","DEFAULT_HEALTH_CHECK_PORT","DEFAULT_PROMETHEUS_PORT","EclesiaEmitter","redactUrl","CircularBuffer","IndexerMetrics","DEFAULT_BIND_HOST","RPCError","ConfigurationError","MAX_FAILURES_PER_BLOCK","IDLE_CHECK_INTERVAL_MS","withTimeout","RPC_TIMEOUT_MS","CONNECT_TIMEOUT_MS","status: StatusResponse","lastProcessed: number | undefined","failingHeight: number | undefined","height: number","timestamp: string","QueryValidatorsResponse","PERIODIC_INTERVALS","retryDelay","RETRY_BASE_DELAY_MS","RETRY_MAX_DELAY_MS","beginBlockEvents: readonly Event[] | readonly Event38[]","endBlockEvents: readonly Event[] | readonly Event38[]","hasBlockEventMode","Tx","events: Array<{\n msg_index?: number\n events: (Event | Event38)[]\n }>","eventsToAdd: typeof events","decodeAttr","MsgExec","events","validators: Validator[]","key: Uint8Array | undefined","QueryValidatorsRequest","PAGINATION_LIMITS","GENESIS_BATCH_SIZE"],"sources":["../../src/indexer/index.ts"],"sourcesContent":["/* eslint-disable @stylistic/no-multi-spaces */\n/* eslint-disable max-lines */\nimport {\n createHash,\n} from \"node:crypto\";\nimport * as fs from \"node:fs\";\n\nimport {\n BlockResponse, BlockResultsResponse, CometClient, connectComet, Event, StatusResponse, toRfc3339WithNanoseconds,\n} from \"@cosmjs/tendermint-rpc\";\nimport {\n BlockResultsResponse as BlockResultsResponse38, Event as Event38,\n} from \"@cosmjs/tendermint-rpc/build/comet38/responses.js\";\nimport {\n MsgExec,\n} from \"cosmjs-types/cosmos/authz/v1beta1/tx.js\";\nimport {\n QueryValidatorsRequest,\n QueryValidatorsResponse,\n} from \"cosmjs-types/cosmos/staking/v1beta1/query.js\";\nimport {\n Validator,\n} from \"cosmjs-types/cosmos/staking/v1beta1/staking.js\";\nimport {\n Tx,\n} from \"cosmjs-types/cosmos/tx/v1beta1/tx.js\";\nimport Fastify, {\n FastifyInstance,\n} from \"fastify\";\nimport {\n chain,\n} from \"stream-chain\";\nimport pick from \"stream-json/filters/pick.js\";\nimport parser from \"stream-json/parser.js\";\nimport streamArray from \"stream-json/streamers/stream-array.js\";\nimport streamValues from \"stream-json/streamers/stream-values.js\";\nimport batch from \"stream-json/utils/batch.js\";\nimport * as winston from \"winston\";\n\nimport {\n CONNECT_TIMEOUT_MS,\n DEFAULT_BATCH_SIZE, DEFAULT_BIND_HOST, DEFAULT_HEALTH_CHECK_PORT, DEFAULT_POLLING_INTERVAL_MS, DEFAULT_PROMETHEUS_PORT, DEFAULT_START_HEIGHT,\n GENESIS_BATCH_SIZE, IDLE_CHECK_INTERVAL_MS, MAX_FAILURES_PER_BLOCK, PAGINATION_LIMITS, PERIODIC_INTERVALS, RETRY_BASE_DELAY_MS, RETRY_MAX_DELAY_MS, RPC_TIMEOUT_MS,\n} from \"../constants.js\";\nimport {\n EclesiaEmitter,\n} from \"../emitter/index.js\";\nimport {\n ConfigurationError, RPCError,\n} from \"../errors/index.js\";\nimport {\n IndexerMetrics,\n} from \"../metrics/index.js\";\nimport {\n CircularBuffer,\n} from \"../promise-queue/index.js\";\nimport {\n BlockQueue, EclesiaIndexerConfig, EmitFunc, MinimalBlockQueue, WithHeightAndUUID,\n} from \"../types/index.js\";\nimport {\n decodeAttr, hasBlockEventMode, redactUrl, retryDelay, withTimeout,\n} from \"../utils/index.js\";\nimport {\n validateFilePath, validatePort, validatePositiveInteger, validateUrl,\n} from \"../validation/index.js\";\n\n/** Default configuration for the Eclesia indexer */\nexport const defaultIndexerConfig = {\n startHeight: DEFAULT_START_HEIGHT, // Start indexing from block 1\n batchSize: DEFAULT_BATCH_SIZE, // Process blocks in batches of 500\n modules: [], // No modules enabled by default\n getNextHeight: () => DEFAULT_START_HEIGHT, // Default height retrieval function\n logLevel: \"info\" as EclesiaIndexerConfig[\"logLevel\"], // Default log level\n usePolling: false, // Use WebSocket subscription by default\n pollingInterval: DEFAULT_POLLING_INTERVAL_MS, // Poll every 5 seconds when polling enabled\n shouldProcessGenesis: () => false, // Skip genesis processing by default\n minimal: true, // Use minimal indexing by default\n enableHealthcheck: true, // Enable health check server by default\n healthCheckPort: DEFAULT_HEALTH_CHECK_PORT, // Default health check port\n enablePrometheus: false, // Disable Prometheus metrics server by default\n prometheusPort: DEFAULT_PROMETHEUS_PORT, // Default Prometheus metrics server port\n init: () => Promise.resolve(), // No-op initialization function\n beginTransaction: () => Promise.resolve(), // No-op transaction begin function\n endTransaction: (_status: boolean) => Promise.resolve(), // No-op transaction end function\n};\n\n/**\n * Core blockchain indexer that connects to Tendermint RPC and processes blocks\n * Extends EclesiaEmitter to provide event-driven architecture for modules\n */\nexport class EclesiaIndexer extends EclesiaEmitter {\n /** Indexer configuration settings */\n public config: EclesiaIndexerConfig;\n\n /** Fastify HTTP server for health checks */\n private fastify: FastifyInstance | null = null;\n\n /** Prometheus HTTP server instance */\n private prometheusServer: FastifyInstance | null = null;\n\n /** Indicates if the indexer has started */\n private started: boolean = false;\n\n /** Queue for managing block processing pipeline */\n private blockQueue: BlockQueue;\n\n /** Latest block height from the chain */\n private latestHeight!: number;\n\n /** Next block height to process */\n public heightToProcess!: number;\n\n /** Whether the indexer has been initialized */\n private initialized = false;\n\n /** Prometheus metrics server instance */\n public prometheus: IndexerMetrics | null = null;\n\n /** Number of retry attempts for error recovery */\n private retryCount = 0;\n\n /** CometBFT client for ad-hoc queries */\n public client!: CometClient;\n\n /** CometBFT client for block and validator queries */\n public blockClient!: CometClient;\n\n /** Winston logger instance */\n public log: winston.Logger;\n\n /** Flag indicating if indexer should attempt recovery */\n private tryToRecover: boolean = false;\n\n /** Health check status for monitoring */\n private healthCheck = {\n status: \"CONNECTING\",\n };\n\n /** WebSocket subscription for new block notifications */\n private subscription: ReturnType<CometClient[\"subscribeNewBlock\"]> | null = null;\n\n /** Timeout handler for block reception */\n private blockTimeout: NodeJS.Timeout | null = null;\n\n /** Timer for the next poll in polling mode */\n private pollTimer: NodeJS.Timeout | null = null;\n\n /** Bumped on every (re)start and stop so a polling chain from a previous run exits */\n private pollGeneration = 0;\n\n /** Bumped on every start() so callbacks left over from a previous run cannot trigger recovery in this one */\n private runGeneration = 0;\n\n /**\n * Rejecters of waits parked in waitForBlockData(). Each entry is removed as soon as its block\n * arrives, so a healthy run keeps this empty instead of accumulating one entry per block.\n */\n private blockWaiters = new Set<(error: Error) => void>();\n\n /** Pending restart timer */\n private retryTimer: NodeJS.Timeout | null = null;\n\n /** Resolves when the indexer has stopped for good: stop() was called, endHeight was reached, or it gave up */\n private stopped: Promise<void> = Promise.resolve();\n\n private resolveStopped: () => void = () => {};\n\n /** Next height the fetcher will request; advances as fetches are enqueued */\n private nextFetchHeight = 0;\n\n /** Whether a fetcher loop is active, and for which run */\n private fetcherRunning = false;\n\n private fetcherGeneration = 0;\n\n private fetcherToken = 0;\n\n /** Height of the block whose processing failed most recently, and how many times in a row */\n private lastFailedHeight: number | undefined;\n\n private sameHeightFailures = 0;\n\n /**\n * Creates a new Eclesia indexer instance\n * @param config - Indexer configuration options\n */\n constructor(config: EclesiaIndexerConfig) {\n super();\n\n // Validate required configuration\n validateUrl(config.rpcUrl, \"rpcUrl\");\n validatePositiveInteger(config.batchSize, \"batchSize\");\n\n // Validate optional genesis path if processing genesis\n if (config.genesisPath) {\n validateFilePath(config.genesisPath, \"genesisPath\");\n }\n\n // Validate health check port if provided\n if (config.healthCheckPort !== undefined) {\n validatePort(config.healthCheckPort, \"healthCheckPort\");\n }\n\n // Validate prometheus port if provided\n if (config.prometheusPort !== undefined) {\n validatePort(config.prometheusPort, \"prometheusPort\");\n }\n\n // Validate start height if provided\n if (config.startHeight !== undefined) {\n validatePositiveInteger(config.startHeight, \"startHeight\");\n }\n\n // Validate polling interval if provided\n if (config.pollingInterval !== undefined) {\n validatePositiveInteger(config.pollingInterval, \"pollingInterval\");\n }\n\n // Explicit undefined values (typical when config is assembled from env vars) must not\n // override the defaults, so drop them before merging\n const provided = Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n ) as EclesiaIndexerConfig;\n this.config = {\n ...defaultIndexerConfig,\n ...provided,\n };\n\n // Structured logging to stdout only: files, rotation and shipping are the deployment's job.\n // Errors are passed as { error } so their stack survives; the text format prints it under\n // the message and the json format emits it as a nested object.\n const errorFormat = winston.format((info) => {\n const error = info.error;\n if (error instanceof Error) {\n info.error = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n }\n else if (error !== undefined && (typeof error !== \"object\" || error === null)) {\n info.error = {\n message: String(error),\n };\n }\n return info;\n });\n const textFormat = winston.format.printf(({\n level, message, timestamp, error,\n }) => {\n const detail = error as {\n stack?: string\n message?: string\n } | undefined;\n const suffix = detail ? \"\\n\" + (detail.stack ?? detail.message ?? \"\") : \"\";\n return `${timestamp} [${level.toUpperCase()}]:\\t${message}${suffix}`;\n });\n this.log = winston.createLogger({\n level: this.config.logLevel,\n defaultMeta: {\n service: \"Eclesia Indexer\",\n },\n transports: [\n new winston.transports.Console({\n format: this.config.logFormat === \"json\"\n ? winston.format.combine(winston.format.splat(),\n winston.format.timestamp(),\n errorFormat(),\n winston.format.json())\n : winston.format.combine(winston.format.splat(),\n winston.format.timestamp(),\n errorFormat(),\n textFormat,\n winston.format.colorize({\n all: true,\n })),\n }),\n ],\n });\n\n // cosmjs cannot subscribe to blocks over plain HTTP; that needs a ws:// or wss:// URL.\n // Switch to polling now instead of failing after several restarts.\n const protocol = new URL(this.config.rpcUrl).protocol;\n if (!this.config.usePolling && (protocol === \"http:\" || protocol === \"https:\")) {\n this.log.warn(\"rpcUrl \" + redactUrl(this.config.rpcUrl) + \" is HTTP, which cannot deliver block subscriptions; polling every \" + this.config.pollingInterval + \" ms instead (use a ws:// or wss:// URL for WebSocket mode)\");\n this.config.usePolling = true;\n }\n\n // Initialize block queue based on minimal or full indexing mode\n // Pass error handler that uses the logger\n const queueErrorHandler = (e: unknown) => {\n this.prometheus?.recordError(\"rpc\");\n this.log.error(\"Error enqueueing block data\", {\n error: e,\n });\n };\n\n if (this.config.minimal) {\n // Minimal mode: only store block and block results\n this.blockQueue = new CircularBuffer<[BlockResponse, BlockResultsResponse]>(this.config.batchSize, queueErrorHandler);\n }\n else {\n // Full mode: also store validator information\n this.blockQueue = new CircularBuffer<[BlockResponse, BlockResultsResponse, Uint8Array]>(this.config.batchSize, queueErrorHandler);\n }\n\n this.on(\"_unhandled\",\n (msg) => {\n // Guarded: this runs once per unhandled message, and winston formats a record before\n // the transport drops it by level\n if (msg.type !== \"uuid\" && this.log.isVerboseEnabled()) {\n this.log.verbose(\"Unhandled event: \" + msg.type);\n }\n });\n if (this.config.enablePrometheus) {\n this.prometheus = new IndexerMetrics();\n this.prometheusServer = Fastify({\n logger: false,\n });\n this.prometheusServer.get(\"/metrics\",\n async (_req, res) => {\n res.header(\"Content-Type\", this.prometheus!.registry.contentType);\n res.send(await this.prometheus!.getMetrics());\n },\n );\n\n const prometheusPort = this.config.prometheusPort\n ?? (process.env.PROMETHEUS_PORT ? parseInt(process.env.PROMETHEUS_PORT, 10) : DEFAULT_PROMETHEUS_PORT);\n this.prometheusServer.listen({\n port: prometheusPort,\n host: this.config.prometheusHost ?? DEFAULT_BIND_HOST,\n },\n (err) => {\n if (err) {\n this.log.error(\"Prometheus server error\", {\n error: err,\n });\n this.prometheus?.recordError(\"metrics_server\");\n this.emit(\"fatal-error\", {\n error: err,\n message: \"Failed to start metrics server\",\n });\n }\n });\n }\n if (this.config.enableHealthcheck) {\n this.fastify = Fastify({\n logger: false,\n });\n this.fastify.get(\"/health\",\n async (_request, reply) => {\n // WAITING means caught up with an idle chain, which is healthy\n const code = this.healthCheck.status == \"OK\" || this.healthCheck.status == \"WAITING\"\n ? 200\n : 503;\n reply.code(code).send(this.healthCheck);\n });\n const healthPort = this.config.healthCheckPort\n ?? (process.env.HEALTH_CHECK_PORT ? parseInt(process.env.HEALTH_CHECK_PORT, 10) : DEFAULT_HEALTH_CHECK_PORT);\n this.fastify.listen({\n port: healthPort,\n host: this.config.healthCheckHost ?? DEFAULT_BIND_HOST,\n },\n (err) => {\n if (err) {\n this.log.error(\"Health check server error\", {\n error: err,\n });\n this.prometheus?.recordError(\"health_check_server\");\n this.emit(\"fatal-error\", {\n error: err,\n message: \"Failed to start health check server\",\n });\n }\n });\n }\n }\n\n private setStatus(status: string) {\n this.healthCheck.status = status;\n this.prometheus?.setWaiting(status === \"WAITING\");\n }\n\n /**\n * Marks the current run for recovery and wakes the main loop if it is parked waiting for a\n * block. Callbacks left over from a previous run pass their generation and are ignored.\n */\n private requestRecovery(reason: string, generation: number = this.runGeneration): void {\n if (generation !== this.runGeneration) {\n this.log.debug(\"Ignoring recovery request from a previous run: \" + reason);\n return;\n }\n if (!this.tryToRecover) {\n this.log.warn(\"Recovery requested: \" + reason);\n }\n this.tryToRecover = true;\n this.wakeBlockWaiters(\"Recovery requested while waiting for block data\");\n }\n\n /** Rejects every wait parked in waitForBlockData() */\n private wakeBlockWaiters(reason: string): void {\n const waiters = [...this.blockWaiters];\n this.blockWaiters.clear();\n for (const reject of waiters) {\n reject(new RPCError(reason));\n }\n }\n\n /**\n * Refuses to index a chain other than the configured one. An RPC pool that mixes networks, or\n * a wrong URL, would otherwise write a different chain's blocks into the database.\n */\n private assertChainId(network: string): void {\n if (this.config.chainId !== undefined && network !== this.config.chainId) {\n throw new ConfigurationError(\"RPC serves chain \" + network + \" but chainId is configured as \" + this.config.chainId, {\n expected: this.config.chainId,\n actual: network,\n });\n }\n }\n\n /** Counts consecutive processing failures per block height */\n private noteBlockFailure(height: number): void {\n if (height === this.lastFailedHeight) {\n this.sameHeightFailures++;\n }\n else {\n this.lastFailedHeight = height;\n this.sameHeightFailures = 1;\n }\n }\n\n /** True once one block has failed maxFailuresPerBlock times in a row */\n private isStuck(): boolean {\n return this.lastFailedHeight !== undefined\n && this.sameHeightFailures >= (this.config.maxFailuresPerBlock ?? MAX_FAILURES_PER_BLOCK);\n }\n\n /**\n * Waits for the next dequeued block but wakes early when recovery or stop is requested, so a\n * loop parked on an empty queue never waits for a block that will not come.\n */\n private waitForBlockData<T>(dequeued: Promise<T>): Promise<T> {\n if (this.tryToRecover || !this.started) {\n return Promise.reject(new RPCError(\"Recovery requested while waiting for block data\"));\n }\n return new Promise<T>((resolve, reject) => {\n this.blockWaiters.add(reject);\n dequeued.then((value) => {\n this.blockWaiters.delete(reject);\n resolve(value);\n },\n (error) => {\n this.blockWaiters.delete(reject);\n reject(error);\n });\n });\n }\n\n /** (Re)arms the idle check that runs when no block has been announced for a while */\n private armIdleCheck(): void {\n if (this.blockTimeout) {\n clearTimeout(this.blockTimeout);\n }\n this.blockTimeout = setTimeout(() => {\n this.checkLiveness();\n }, IDLE_CHECK_INTERVAL_MS);\n }\n\n /**\n * Runs when no block has been announced for IDLE_CHECK_INTERVAL_MS. A chain that has stopped\n * producing blocks (halt, upgrade, slow chain) is not an error: the indexer reports WAITING and\n * checks again later. Recovery is requested only when the chain has moved on without us, which\n * means the subscription is dead, or when the RPC cannot be reached at all.\n */\n private async checkLiveness(): Promise<void> {\n if (!this.started) {\n return;\n }\n const generation = this.runGeneration;\n try {\n const status = await withTimeout(this.client.status(), RPC_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n if (!this.started || generation !== this.runGeneration) {\n return;\n }\n const chainHeight = status.syncInfo.latestBlockHeight;\n if (chainHeight > this.latestHeight) {\n this.requestRecovery(\"chain is at \" + chainHeight + \" but nothing was announced since \" + this.latestHeight, generation);\n return;\n }\n this.log.info(\"No new block for \" + IDLE_CHECK_INTERVAL_MS / 1000 + \" s, chain height is still \" + chainHeight);\n this.setStatus(\"WAITING\");\n this.armIdleCheck();\n }\n catch (e) {\n this.log.error(\"Liveness check failed\", {\n error: e,\n });\n this.requestRecovery(\"RPC unreachable during liveness check\", generation);\n }\n }\n\n /**\n * Builds the listener for one run's block subscription. It carries the generation it was\n * created for, so when a restart disconnects the previous client and that subscription\n * completes, the completion is attributed to the finished run and ignored instead of\n * poisoning the run that is starting.\n */\n private makeBlockListener(generation: number) {\n return {\n next: (data: {\n header: {\n height: number\n }\n }) => {\n if (generation === this.runGeneration) {\n this.newBlockReceived(data.header.height);\n }\n },\n error: (error: unknown) => {\n this.log.error(\"Block subscription error\", {\n error,\n });\n this.requestRecovery(\"block subscription errored\", generation);\n },\n complete: () => {\n if (this.started) {\n this.requestRecovery(\"block subscription closed by the node\", generation);\n }\n },\n };\n }\n\n /** Listener attached to the current block subscription */\n private blockListener = this.makeBlockListener(0);\n\n private isMinimal(_blockqueue: BlockQueue): _blockqueue is MinimalBlockQueue {\n if (this.config.minimal) {\n return true;\n }\n else {\n return false;\n }\n }\n\n public async connect() {\n try {\n if (this.client) {\n this.log.verbose(\"Recover from error. Attempting to disconnect from RPC\");\n // Detach first: closing the socket completes the subscription, and that completion\n // must not be mistaken for the node dropping us\n if (this.subscription) {\n try {\n this.subscription.removeListener(this.blockListener);\n }\n catch (_e) { /* empty */ }\n this.subscription = null;\n }\n try {\n this.client.disconnect();\n }\n catch (_e) { /* empty */ }\n try {\n this.blockClient?.disconnect();\n }\n catch (_e) { /* empty */ }\n this.log.verbose(\"Disconnected from RPC\");\n }\n this.log.info(\"Attempting to connect to RPC: \" + redactUrl(this.config.rpcUrl));\n this.client = await this.connectWithTimeout();\n await withTimeout(this.client.status(), CONNECT_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.log.info(\"Connected to RPC for ad hoc queries\");\n this.blockClient = await this.connectWithTimeout();\n await withTimeout(this.blockClient.status(), CONNECT_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.log.info(\"Connected to RPC for block & validator info\");\n\n return true;\n }\n catch (error) {\n this.log.error(\"RPC connection error\", {\n error,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"RPC connection failed\");\n return false;\n }\n }\n\n /**\n * Opens one CometBFT client with its own timeout. If the timeout wins, the client\n * that may still arrive is disconnected so a slow RPC never leaks a socket.\n */\n private async connectWithTimeout(): Promise<CometClient> {\n let timedOut = false;\n const pending = connectComet(this.config.rpcUrl);\n pending.then((client) => {\n if (timedOut) {\n try {\n client.disconnect();\n }\n catch (_e) { /* empty */ }\n }\n }).catch(() => { /* surfaced through the race below */ });\n try {\n return await withTimeout(pending, CONNECT_TIMEOUT_MS, new RPCError(\"RPC connection timed out\"));\n }\n catch (e) {\n timedOut = true;\n throw e;\n }\n }\n\n private async initialize() {\n if (!this.initialized) {\n try {\n if (this.config.init) {\n await this.config.init();\n }\n }\n catch (e) {\n this.log.error(\"Failed to initialize indexer\", {\n error: e,\n });\n\n this.prometheus?.recordError(\"init_error\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n if (await this.config.shouldProcessGenesis()) {\n try {\n if (this.config.genesisPath) {\n await this.parseGenesis();\n }\n else {\n this.log.warn(\"shouldProcessGenesis() returned true but no genesisPath is configured, skipping genesis import\");\n }\n }\n catch (e) {\n this.log.error(\"Failed to parse genesis\", {\n error: e,\n });\n\n this.prometheus?.recordError(\"genesis_error\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n }\n this.initialized = true;\n }\n }\n\n /**\n * Stops the indexer and releases everything that would keep the process alive:\n * the block subscription, polling and inactivity timers, both RPC clients and the\n * health and metrics servers. Safe to call more than once.\n */\n public async stop(): Promise<void> {\n this.started = false;\n this.resolveStopped();\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n this.wakeBlockWaiters(\"Indexer stopped while waiting for block data\");\n this.stopPolling();\n if (this.blockTimeout) {\n clearTimeout(this.blockTimeout);\n this.blockTimeout = null;\n }\n if (this.subscription) {\n try {\n this.subscription.removeListener(this.blockListener);\n }\n catch (_e) { /* empty */ }\n this.subscription = null;\n }\n for (const client of [this.client, this.blockClient]) {\n try {\n client?.disconnect();\n }\n catch (_e) { /* empty */ }\n }\n const servers = [this.fastify, this.prometheusServer];\n this.fastify = null;\n this.prometheusServer = null;\n await Promise.all(servers.map(server => server?.close().catch((e: unknown) => {\n this.log.warn(\"Error closing HTTP server\", {\n error: e,\n });\n })));\n this.log.info(\"Indexer stopped\");\n }\n\n private clearBlockQueue() {\n if (this.blockQueue) {\n this.blockQueue.clear();\n this.log.verbose(\"Starting, clearing block queue\");\n }\n }\n\n private async setupBlockListening() {\n const connected = await this.connect();\n if (!connected) {\n this.setStatus(\"FAILED\");\n throw new RPCError(\"Failed to connect to RPC\");\n }\n\n try {\n if (!this.config.usePolling && this.subscription) {\n this.subscription.removeListener(this.blockListener);\n this.subscription = null;\n this.log.verbose(\"Removed existing block listener and subscription\");\n }\n if (!this.config.usePolling) {\n this.subscription = this.client.subscribeNewBlock\n ? this.client.subscribeNewBlock()\n : null;\n this.blockListener = this.makeBlockListener(this.runGeneration);\n }\n const status: StatusResponse = await withTimeout(this.client.status(), RPC_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.assertChainId(status.nodeInfo.network);\n this.latestHeight = status.syncInfo.latestBlockHeight;\n this.log.info(\"Connected to \" + status.nodeInfo.network + \", current chain height: \" + this.latestHeight);\n\n this.heightToProcess = await this.config.getNextHeight();\n this.nextFetchHeight = this.heightToProcess;\n if (this.config.usePolling) {\n this.startPolling();\n }\n else {\n if (this.subscription) {\n this.subscription.addListener(this.blockListener);\n }\n else {\n this.prometheus?.recordError(\"rpc\");\n throw new Error(\"Could not subscribe to new blocks\");\n }\n }\n // A subscription that never delivers anything must still be noticed\n this.armIdleCheck();\n }\n catch (e) {\n this.log.error(\"Failed to set up block listening\", {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n }\n\n /**\n * Resolves once the indexer has stopped for good: stop() was called, endHeight was reached, or\n * a fatal-error was emitted. Restarts with backoff do not resolve it. Use it to keep a caller\n * waiting for the whole run rather than for the first loop exit.\n */\n public whenStopped(): Promise<void> {\n return this.stopped;\n }\n\n public async start() {\n if (!this.started) {\n // A fresh run (not a restart after backoff) gets a fresh completion promise\n this.stopped = new Promise<void>((resolve) => {\n this.resolveStopped = resolve;\n });\n }\n this.started = true;\n this.runGeneration++;\n const generation = this.runGeneration;\n this.tryToRecover = false;\n this.blockWaiters.clear();\n this.clearBlockQueue();\n await this.initialize();\n try {\n await this.setupBlockListening();\n this.log.debug(\"Starting main processing loop\");\n this.fetcher().catch((e) => {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.log.error(\"Error in fetching service\", {\n error: e,\n });\n this.requestRecovery(\"fetcher failed\", generation);\n });\n }\n catch (e) {\n this.requestRecovery(\"block listening setup failed: \" + e, generation);\n }\n\n let lastProcessed: number | undefined;\n while (this.started && !this.tryToRecover) {\n let txOpen = false;\n let failingHeight: number | undefined;\n try {\n this.prometheus?.updateRetryCount(this.retryCount);\n if (this.blockQueue.synced && this.blockQueue.size() <= 1) {\n // Only the sentinel is queued: we are at the chain tip. Waiting here is normal and can\n // last hours during a halt or an upgrade, so no transaction is held while we wait.\n this.setStatus(\"WAITING\");\n }\n let height: number;\n let timestamp: string;\n\n // Main block processing (minimal)\n if (this.isMinimal(this.blockQueue)) {\n const toProcess = await this.waitForBlockData(this.blockQueue.dequeue());\n this.log.silly(\"Retrieved block data\");\n if (!toProcess || !toProcess[0] || !toProcess[1]) {\n throw new RPCError(\"Could not fetch block(minimal)\");\n }\n height = toProcess[0].block.header.height;\n failingHeight = height;\n timestamp = toRfc3339WithNanoseconds(toProcess[0].block.header.time);\n // Index block inside a db transaction to ensure data consistency\n await this.config.beginTransaction();\n txOpen = true;\n this.log.silly(\"Started db tx\");\n await this.processBlock(toProcess[0],\n toProcess[1]);\n }\n // Main block processing (full)\n else {\n const toProcess = await this.waitForBlockData(this.blockQueue.dequeue());\n this.log.silly(\"Retrieved block data\");\n if (!toProcess || !toProcess[0] || !toProcess[1] || !toProcess[2]) {\n throw new RPCError(\"Could not fetch block(full)\");\n }\n\n this.log.silly(\"Decoded block\");\n height = toProcess[0].block.header.height;\n failingHeight = height;\n timestamp = toRfc3339WithNanoseconds(toProcess[0].block.header.time);\n await this.config.beginTransaction();\n txOpen = true;\n this.log.silly(\"Started db tx\");\n await this.processBlock(toProcess[0],\n toProcess[1],\n QueryValidatorsResponse.decode(toProcess[2]).validators);\n }\n\n // Emit events to trigger periodic operations every 50, 100 and 1000 blocks\n if (height % PERIODIC_INTERVALS.LARGE == 0) {\n await this.asyncEmit(\"periodic/large\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n if (height % PERIODIC_INTERVALS.MEDIUM == 0) {\n await this.asyncEmit(\"periodic/medium\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n if (height % PERIODIC_INTERVALS.SMALL == 0) {\n await this.asyncEmit(\"periodic/small\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n this.log.silly(\"Handled periodic events\");\n\n await this.config.endTransaction(true);\n txOpen = false;\n lastProcessed = height;\n this.lastFailedHeight = undefined;\n this.sameHeightFailures = 0;\n\n this.log.silly(\"Committed db tx\");\n }\n catch (e) {\n if (txOpen) {\n try {\n await this.config.endTransaction(false);\n }\n catch (dbe) {\n this.prometheus?.recordError(\"database\");\n this.log.error(\"Error ending transaction\", {\n error: dbe,\n });\n }\n }\n if (!this.started) {\n // stop() woke us up; nothing failed\n break;\n }\n if (failingHeight !== undefined) {\n // The data was there and processing failed: count it against this block\n this.noteBlockFailure(failingHeight);\n }\n // any error here is likely recoverable (e.g. RPC timeout, DB error)\n this.prometheus?.recordError(\"block\");\n this.log.error(\"Block processing error\", {\n error: e,\n });\n this.setStatus(\"FAILED\");\n this.requestRecovery(\"block processing failed\", generation);\n break;\n }\n // Reset retry count and status on successful block processing\n this.retryCount = 0;\n this.setStatus(\"OK\");\n if (this.config.endHeight !== undefined && lastProcessed !== undefined && lastProcessed >= this.config.endHeight) {\n this.log.info(\"Reached configured end height \" + this.config.endHeight + \". Stopping indexer.\");\n await this.stop();\n return;\n }\n }\n\n // Normal exit from processing loop\n if (!this.started) {\n this.log.info(\"Indexer manually stopped.\");\n return;\n }\n\n // A block that keeps failing after its data was fetched is a bug or bad data, not an outage.\n // Give up loudly instead of retrying it forever.\n if (this.isStuck()) {\n const height = this.lastFailedHeight;\n this.log.error(\"Block \" + height + \" failed \" + this.sameHeightFailures + \" times in a row. This is a deterministic failure in a handler or the data, not an outage. Giving up.\");\n this.started = false;\n this.emit(\"fatal-error\", {\n error: new Error(\"Block \" + height + \" failed \" + this.sameHeightFailures + \" times in a row\"),\n message: \"Block processing is stuck\",\n retryCount: this.retryCount,\n height,\n });\n this.resolveStopped();\n return;\n }\n\n // Abnormal exit: restart with exponential backoff. Retries are unlimited unless maxRetries\n // is configured, because an RPC or database outage of any length must not kill the indexer.\n this.retryCount++;\n if (this.config.maxRetries !== undefined && this.retryCount > this.config.maxRetries) {\n this.log.error(\"Indexer failed \" + this.retryCount + \" times in a row, giving up (maxRetries=\" + this.config.maxRetries + \")\");\n this.started = false;\n this.emit(\"fatal-error\", {\n error: new Error(\"Max retry attempts exceeded\"),\n message: \"Indexer failed too many times\",\n retryCount: this.retryCount,\n });\n this.resolveStopped();\n return;\n }\n const delay = retryDelay(this.retryCount, RETRY_BASE_DELAY_MS, RETRY_MAX_DELAY_MS);\n this.log.warn(\"Indexer is restarting in \" + delay / 1000 + \" s (attempt \" + this.retryCount + \")\");\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.start().catch((e) => {\n this.log.error(\"Restart failed\", {\n error: e,\n });\n });\n }, delay);\n }\n\n /**\n * Emits an event and waits for its handlers. Handlers run one after another in registration\n * order: they share one database connection and one transaction, so interleaving them at\n * await points would let two handlers read and write the same rows in an unpredictable order,\n * and a failure in one would leave the others mid-flight while the block is rolled back. The\n * first rejection propagates and stops the remaining handlers.\n */\n public asyncEmit: EmitFunc<keyof WithHeightAndUUID<EventMap>> = async (\n type,\n event,\n ) => {\n const handlers = this.handlersFor(type);\n if (handlers.length === 0) {\n // Same routing as emit(): the _unhandled listener logs it at verbose level\n this.emit(type,\n event);\n return;\n }\n for (const handler of handlers) {\n await handler(event);\n }\n };\n\n private async processBlock(block: BlockResponse, block_results: BlockResultsResponse | BlockResultsResponse38, validators?: Validator[]) {\n const endTimer = this.prometheus?.timeBlockProcessing();\n const height = block.block.header.height;\n this.heightToProcess = height;\n this.log.debug(\"Processing block: %d\",\n height);\n // Initialize height & timestamp to be used for this block-processing run\n const timestamp = toRfc3339WithNanoseconds(block.block.header.time);\n\n // Use & await asyncEmit to ensure db insertions in order\n\n /*\n * Emit block information to any interested modules.\n * Primarily the required block module listens to this\n */\n await this.asyncEmit(\"block\",\n {\n value: {\n block,\n block_results,\n },\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled block event\");\n\n let beginBlockEvents: readonly Event[] | readonly Event38[];\n let endBlockEvents: readonly Event[] | readonly Event38[];\n if ((block_results as BlockResultsResponse38).finalizeBlockEvents) {\n // Cosmos SDK 0.50+ tags each finalize_block event with mode=BeginBlock / mode=EndBlock (baseapp.go)\n beginBlockEvents = (block_results as BlockResultsResponse38).finalizeBlockEvents.filter(x => hasBlockEventMode(x, \"BeginBlock\")) as readonly Event38[];\n endBlockEvents = (block_results as BlockResultsResponse38).finalizeBlockEvents.filter(x => hasBlockEventMode(x, \"EndBlock\")) as readonly Event38[];\n }\n else {\n beginBlockEvents = (block_results as BlockResultsResponse).beginBlockEvents;\n endBlockEvents = (block_results as BlockResultsResponse).endBlockEvents;\n }\n // Deal with begin_block events first\n await this.asyncEmit(\"begin_block\",\n {\n value: {\n events: beginBlockEvents!,\n validators,\n },\n height,\n timestamp,\n });\n\n this.log.silly(\"Modules handled begin_block events\");\n\n // Then individual tx_events\n await this.asyncEmit(\"tx_events\",\n {\n value: block_results.results,\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled tx events\");\n\n // Emit details and result for each tx msg separately\n for (let t = 0; t < block.block.txs.length; t++) {\n const tx = Tx.decode(block.block.txs[t]);\n\n const result = block_results.results[t].code;\n const txlog = block_results.results[t].log;\n\n if (result != 0) {\n // Tx failed. Ignore\n continue;\n }\n if (tx.body && tx.body.memo != \"\") {\n const txHash = createHash(\"sha256\").update(block.block.txs[t])\n .digest(\"hex\");\n await this.asyncEmit(\"tx_memo\",\n {\n value: {\n txHash,\n txBody: tx.body,\n },\n height,\n timestamp,\n });\n }\n // parsing log rather than using events directly in order to have msg_index available to filter appropriate events for each msg\n let events: Array<{\n msg_index?: number\n events: (Event | Event38)[]\n }> = [];\n if (txlog) {\n try {\n const parsed = JSON.parse(txlog);\n if (Array.isArray(parsed)) {\n events = parsed;\n }\n }\n catch (_e) {\n // Not every chain writes a JSON log; the msg_index attributes below cover those\n this.log.silly(\"Tx log is not JSON, using msg_index attributes instead\");\n }\n }\n if (events.length == 0) {\n const eventsToAdd: typeof events = [];\n this.log.silly(\"No events found in tx log. Parsing events for msg_index\");\n for (let m = 0; m < block_results.results[t].events.length; m++) {\n if (block_results.results[t].events[m].attributes.find(a => decodeAttr(a.key) == \"msg_index\")) {\n const mi = decodeAttr(block_results.results[t].events[m].attributes.find(a => decodeAttr(a.key) == \"msg_index\")?.value ?? \"\");\n if (mi != \"\") {\n const miNum = parseInt(mi);\n let ev = eventsToAdd.find(x => x.msg_index == miNum);\n if (!ev) {\n ev = {\n msg_index: miNum,\n events: [block_results.results[t].events[m]],\n };\n eventsToAdd.push(ev);\n }\n else {\n ev.events.push(block_results.results[t].events[m] as Event);\n }\n }\n }\n }\n events = events.concat(eventsToAdd);\n }\n const msgs = tx.body?.messages;\n\n if (msgs) {\n for (let i = 0; i < msgs.length; i++) {\n if (this.log.isSillyEnabled()) {\n this.log.silly(\"Indexer broadcasting msg for handling: \" + msgs[i].typeUrl);\n }\n const msgevents\n = msgs.length > 1\n ? events.find(x => x.msg_index == i)?.events\n : events[0]?.events ?? [];\n await this.asyncEmit(msgs[i].typeUrl as never,\n {\n value: {\n tx: msgs[i].value as never,\n events: msgevents,\n } as never,\n height,\n timestamp,\n });\n if (msgs[i].typeUrl == \"/cosmos.authz.v1beta1.MsgExec\") {\n const authzMsgs = MsgExec.decode(msgs[i].value).msgs;\n if (authzMsgs) {\n for (let r = 0; r < authzMsgs.length; r++) {\n if (this.log.isSillyEnabled()) {\n this.log.silly(\"Indexer broadcasting msg for handling: \" + authzMsgs[r].typeUrl);\n }\n const authzMsgEvents = msgevents?.reduce((events, evt) => {\n if (evt.attributes.filter(x => decodeAttr(x.key) == \"authz_msg_index\" && decodeAttr(x.value) == \"\" + r).length > 0) {\n events.push(evt);\n }\n return events;\n },\n [] as (Event | Event38)[]);\n await this.asyncEmit(authzMsgs[r].typeUrl as never,\n {\n value: {\n tx: authzMsgs[r].value as never,\n events: authzMsgEvents,\n } as never,\n height,\n timestamp,\n });\n }\n }\n }\n }\n }\n }\n this.log.silly(\"Modules handled msg events\");\n this.prometheus?.recordTransactions(block.block.txs.length);\n // Then deal with end_block events\n await this.asyncEmit(\"end_block\",\n {\n value: endBlockEvents!,\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled end_block events\");\n\n endTimer?.();\n this.prometheus?.updateBlockMetrics(height, this.latestHeight, this.blockQueue.size());\n }\n\n /**\n * Fetches every height from nextFetchHeight up to latestHeight, waiting for queue space\n * before each fetch. Serves the initial catch-up and live blocks alike: a new announcement\n * only moves latestHeight and starts this loop if it is not already running, so bursts and\n * skipped announcements are handled by the same code and the queue can never overflow.\n */\n private async fetcher() {\n if (this.fetcherRunning && this.fetcherGeneration === this.runGeneration) {\n return;\n }\n const generation = this.runGeneration;\n const token = ++this.fetcherToken;\n this.fetcherRunning = true;\n this.fetcherGeneration = generation;\n try {\n while (\n this.nextFetchHeight <= this.latestHeight\n && (this.config.endHeight === undefined || this.nextFetchHeight <= this.config.endHeight)\n ) {\n // If some other async process triggers recovery, exit the fetching loop\n if (this.tryToRecover || !this.started || generation !== this.runGeneration) {\n this.log.verbose(\"Exiting fetcher loop. Attempting to recover indexer\");\n break;\n }\n const i = this.nextFetchHeight;\n this.log.debug(\"Fetching: \" + i);\n try {\n // Main fetching logic for minimal indexer\n if (this.isMinimal(this.blockQueue)) {\n // We do not await here so that multiple fetches can be in-flight\n const toIndex = withTimeout(Promise.all([this.blockClient.block(i) as Promise<BlockResponse>, this.blockClient.blockResults(i) as Promise<BlockResultsResponse>]), RPC_TIMEOUT_MS, new RPCError(\"Timed out fetching block \" + i)).catch((e) => {\n this.log.error(\"Error fetching block \" + i, {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"fetch failed for block \" + i, generation);\n return Promise.resolve([]);\n }) as Promise<[BlockResponse, BlockResultsResponse]>;\n this.blockQueue.enqueue(toIndex);\n }\n else {\n // Full indexer: block, block results and the complete validator set\n const toIndex = withTimeout(Promise.all([this.blockClient.block(i) as Promise<BlockResponse>, this.blockClient.blockResults(i) as Promise<BlockResultsResponse>, this.fetchValidatorSet(i)]), RPC_TIMEOUT_MS, new RPCError(\"Timed out fetching block \" + i)).catch((e) => {\n this.log.error(\"Error fetching block \" + i, {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"fetch failed for block \" + i, generation);\n return Promise.resolve([]);\n }) as Promise<[BlockResponse, BlockResultsResponse, Uint8Array]>;\n this.blockQueue.enqueue(toIndex);\n }\n }\n catch (e) {\n this.log.error(\"Fetching error\", {\n error: e,\n });\n break;\n }\n this.nextFetchHeight = i + 1;\n // Resolves immediately while the queue has room, otherwise when the processor dequeues\n await this.blockQueue.continue();\n }\n // Caught up with everything announced so far\n if (!this.tryToRecover && this.started && generation === this.runGeneration && !this.blockQueue.synced) {\n this.blockQueue.setSynced();\n this.log.info(\"Synced to latest height\");\n }\n }\n finally {\n if (this.fetcherToken === token) {\n this.fetcherRunning = false;\n }\n }\n }\n\n /**\n * Runs an ABCI query. A transport failure (RPC down, timeout, empty reply) requests a recovery.\n * A reply with a non-zero code is the chain answering \"no\" (pruned height, unknown path, bad\n * key): it is thrown as an RPCError with the code and log, and no recovery is requested for\n * ad-hoc queries, so modules can catch it. Block-pipeline queries reject into the fetcher,\n * which requests recovery itself.\n */\n public async callABCI(path: string, data: Uint8Array, height?: number, adHoc: boolean = true): Promise<Uint8Array> {\n let abciq;\n const endTimer = this.prometheus?.timeRpcCall(path) ?? void 0;\n try {\n abciq = await\n (adHoc\n ? this.client\n : this.blockClient).abciQuery({\n path,\n data,\n height: height,\n });\n }\n catch (e) {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"ABCI query failed for \" + path);\n throw new RPCError(\"RPC not responding. Query at: \" + path + \" (\" + e + \")\");\n }\n finally {\n endTimer?.();\n }\n if (!abciq) {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"empty ABCI response for \" + path);\n throw new RPCError(\"RPC not responding. Query at: \" + path);\n }\n if (abciq.code) {\n // Previously an error reply decoded as an empty result (for example zero validators)\n this.prometheus?.recordError(\"rpc\");\n throw new RPCError(\"ABCI query \" + path + \" failed with code \" + abciq.code + (abciq.log ? \": \" + abciq.log : \"\"));\n }\n return abciq.value;\n }\n\n /**\n * Fetches the complete validator set at a height, following pagination, and returns it\n * re-encoded as a single QueryValidatorsResponse so the block queue payload keeps its shape.\n * Chains with more validators than one page (1000) were silently truncated before.\n */\n private async fetchValidatorSet(height: number): Promise<Uint8Array> {\n const validators: Validator[] = [];\n let key: Uint8Array | undefined;\n do {\n const request = QueryValidatorsRequest.fromPartial({\n pagination: key\n ? {\n limit: PAGINATION_LIMITS.VALIDATORS,\n key,\n }\n : {\n limit: PAGINATION_LIMITS.VALIDATORS,\n },\n });\n const page = QueryValidatorsResponse.decode(\n await this.callABCI(\"/cosmos.staking.v1beta1.Query/Validators\", QueryValidatorsRequest.encode(request).finish(), height, false),\n );\n validators.push(...page.validators);\n key = page.pagination?.nextKey && page.pagination.nextKey.length > 0 ? page.pagination.nextKey : undefined;\n } while (key);\n return QueryValidatorsResponse.encode(QueryValidatorsResponse.fromPartial({\n validators,\n })).finish();\n }\n\n private newBlockReceived(height: number): void {\n this.armIdleCheck();\n this.log.info(\"Received new block: %d\",\n height);\n if (height <= this.latestHeight) {\n // Re-announced, or from a lagging node behind a load balancer: never move backwards\n return;\n }\n this.latestHeight = height;\n if (this.tryToRecover || !this.started) {\n return;\n }\n // The fetcher requests every height up to latestHeight and waits for queue space as it\n // goes, so a burst of blocks or an announcement that skipped heights is handled exactly\n // like the initial catch-up. Nothing to do if it is already running.\n this.fetcher().catch((e) => {\n this.log.error(\"Error in fetching service\", {\n error: e,\n });\n this.requestRecovery(\"fetcher failed\");\n });\n }\n\n /** Starts a single polling chain, retiring any chain left over from a previous run */\n private startPolling(): void {\n this.stopPolling();\n const generation = ++this.pollGeneration;\n this.pollForBlock(generation);\n }\n\n private stopPolling(): void {\n this.pollGeneration++;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n }\n\n private async pollForBlock(generation: number) {\n if (!this.started || generation !== this.pollGeneration) {\n return;\n }\n try {\n const status = await this.client.status();\n // A restart or stop may have happened while waiting on the RPC\n if (!this.started || generation !== this.pollGeneration) {\n return;\n }\n if (status.syncInfo.latestBlockHeight > this.latestHeight) {\n this.newBlockReceived(status.syncInfo.latestBlockHeight);\n }\n }\n catch (e) {\n this.log.error(\"Error polling for new block\", {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"polling failed\");\n // Recovery restarts polling from setupBlockListening\n return;\n }\n this.pollTimer = setTimeout(() => {\n this.pollForBlock(generation);\n },\n this.config.pollingInterval);\n }\n\n private readGenesis(): fs.ReadStream {\n if (this.config.genesisPath) {\n return fs.createReadStream(this.config.genesisPath);\n }\n else {\n throw new Error(\"Genesis path not set\");\n }\n }\n\n private async setArrayReader(path: string, processor: (chunk: unknown) => Promise<void>): Promise<boolean> {\n const readPromise = new Promise<boolean>((resolve, reject) => {\n try {\n const filters = path.split(\".\");\n const pickers = filters.map(filter => pick({\n filter,\n }));\n let counter = 0;\n let chunkCounter = 0;\n\n // Wrapper processor that handles transaction chunking\n const chunkProcessor = async (data: unknown) => {\n chunkCounter++;\n this.log.debug(`Processing genesis chunk ${chunkCounter}`);\n\n await processor(data);\n\n // Commit and restart transaction every 5 chunks (5000 entries)\n // This prevents timeout on large genesis files\n if (chunkCounter % 5 === 0) {\n this.log.debug(`Committing transaction after chunk ${chunkCounter}`);\n await this.config.endTransaction(true);\n await this.config.beginTransaction();\n }\n // Pass the chunk on so the \"data\" listener below can count what was processed\n return data;\n };\n\n chain([\n this.readGenesis(),\n parser(),\n ...pickers,\n streamArray(),\n batch({\n batchSize: GENESIS_BATCH_SIZE,\n }),\n chunkProcessor,\n ])\n .on(\"data\",\n (data) => {\n if (data && Array.isArray(data)) {\n counter = counter + data.length;\n }\n })\n .on(\"end\",\n () => {\n this.log.info(`Processed ${counter} entries in ${chunkCounter} chunks`);\n resolve(true);\n })\n // stream-chain re-emits parser and processor errors here; without a listener Node\n // raises them as an uncaught exception and parseGenesis never rolls back\n .on(\"error\",\n (e) => {\n this.log.error(\"Error reading genesis array \" + path, {\n error: e,\n });\n reject(e);\n });\n }\n catch (e) {\n this.log.verbose(\"Error in setArrayReader: \" + e);\n reject(e);\n }\n });\n\n return readPromise;\n }\n\n private async setValueReader(path: string, processor: (chunk: unknown) => Promise<void>): Promise<boolean> {\n const readPromise = new Promise<boolean>((resolve, reject) => {\n try {\n const filters = path.split(\".\");\n const pickers = filters.map(filter => pick({\n filter,\n }));\n\n let counter = 0;\n chain([this.readGenesis(), parser(), ...pickers, streamValues(), processor])\n .on(\"data\",\n (_data) => {\n counter++;\n })\n .on(\"end\",\n () => {\n this.log.info(`Processed ${counter} entries`);\n resolve(true);\n })\n .on(\"error\",\n (e) => {\n this.log.error(\"Error reading genesis value \" + path, {\n error: e,\n });\n reject(e);\n });\n }\n catch (e) {\n this.log.verbose(\"Error in setValueReader: \" + e);\n reject(e);\n }\n });\n\n return readPromise;\n }\n\n private async parseGenesis() {\n this.log.info(\"Parsing genesis\");\n // Lets the storage layer mark the import as in progress before anything is written\n await this.config.onGenesisStart?.();\n await this.config.beginTransaction();\n try {\n this.log.info(\"Starting genesis import\");\n this.log.debug(\"Importing genesis file...\");\n\n for (const [key, _value] of this.handled) {\n if (key.startsWith(\"genesis/\")) {\n const genesisEntry = key.split(\"/\");\n\n this.log.verbose(\"Importing \" + key + \"...\");\n if (genesisEntry[1] == \"array\") {\n await this.setArrayReader(genesisEntry[2],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n await this.asyncEmit(key as never,\n {\n value: data.map((x: {\n value: never\n }) => x.value),\n } as never);\n return data;\n });\n }\n else {\n await this.setValueReader(genesisEntry[2],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n await this.asyncEmit(key as never,\n {\n value: data.value,\n } as never);\n return data;\n });\n }\n }\n }\n\n this.log.info(\"Importing gen TXs...\");\n\n await this.setArrayReader(\"app_state.genutil.gen_txs\",\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n for (let j = 0; j < data.length; j++) {\n const gentx = data[j].value;\n for (let i = 0; i < gentx.body.messages.length; i++) {\n const msg = gentx.body.messages[i];\n await this.asyncEmit((\"gentx\" + msg[\"@type\"]) as never,\n {\n value: msg,\n } as never);\n }\n }\n return data;\n });\n // Recorded inside the last transaction, so \"complete\" commits together with the final chunk\n await this.config.onGenesisComplete?.();\n await this.config.endTransaction(true);\n\n this.log.info(\"Finished importing\");\n }\n catch (e) {\n try {\n await this.config.endTransaction(false);\n }\n catch (dbe) {\n this.prometheus?.recordError(\"database\");\n this.log.error(\"Error ending transaction\", {\n error: dbe,\n });\n }\n this.log.error(\"Failed to import genesis\");\n throw e;\n }\n }\n}\n\n/** @deprecated Misspelling kept for compatibility, use EclesiaIndexer. Removed in 3.0. */\nexport const EcleciaIndexer = EclesiaIndexer;\n/** @deprecated Misspelling kept for compatibility, use EclesiaIndexer. Removed in 3.0. */\nexport type EcleciaIndexer = EclesiaIndexer;\n"],"mappings":"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"}
@@ -124,6 +124,14 @@ declare class EclesiaIndexer extends EclesiaEmitter {
124
124
  * means the subscription is dead, or when the RPC cannot be reached at all.
125
125
  */
126
126
  private checkLiveness;
127
+ /**
128
+ * Builds the listener for one run's block subscription. It carries the generation it was
129
+ * created for, so when a restart disconnects the previous client and that subscription
130
+ * completes, the completion is attributed to the finished run and ignored instead of
131
+ * poisoning the run that is starting.
132
+ */
133
+ private makeBlockListener;
134
+ /** Listener attached to the current block subscription */
127
135
  private blockListener;
128
136
  private isMinimal;
129
137
  connect(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/indexer/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;cAmEa;EAAA,WAAA,EAAA,MAAA;EAKS,SAAA,EAAA,MAAA;;;EAWa,QAAA,EAXb,oBAWa,CAAA,UAAA,CAAA;EAAA,UAAA,EAAA,OAAA;EAOtB,eAAA,EAAe,MAAA;EAEX,oBAAA,EAAA,GAAA,GAAA,OAAA;EAwBI,OAAA,EAAA,OAAA;EAMH,iBAAA,EAAA,OAAA;EAGK,eAAA,EAAA,MAAA;EAGT,gBAAQ,EAAA,OAAA;EA0DA,cAAA,EAAA,MAAA;EA0VA,IAAA,EAAA,GAAA,UAAA,CAAA,IAAA,CAAA;EAsGC,gBAAA,EAAA,GAAA,UAAA,CAAA,IAAA,CAAA;EAmGC,cAAA,EAAA,CAAA,OAAA,EAAA,OAAA,EAAA,GA1oBW,OA0oBX,CAAA,IAAA,CAAA;CAIJ;;;;;AAifqF,cAxnC5F,cAAA,SAAuB,cAAA,CAwnCqE;EAAR;EAxnC7D,MAAA,EAEnB,oBAFmB;EAAc;EA+7CrC,QAAA,OAAA;EAED;;;;;;;;;;;;;cAv6CS;;;;UAMH;;eAGK;;OAGT,OAAA,CAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA0DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA0VA;;;;;;;;;;;;UAsGC;;;;;;;;iBAmGC;WAIJ;;;;;;;;aAkNA,eAAe,kBAAkB;;;;;;;;;;;;;;;;+BA+RT,+CAAqD,QAAQ;;;;;;;;;;;;;;;;;;cAuU5F,uBAAc;;KAEf,cAAA,GAAiB"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/indexer/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;cAmEa;EAAA,WAAA,EAAA,MAAA;EAKS,SAAA,EAAA,MAAA;;;EAWa,QAAA,EAXb,oBAWa,CAAA,UAAA,CAAA;EAAA,UAAA,EAAA,OAAA;EAOtB,eAAA,EAAe,MAAA;EAEX,oBAAA,EAAA,GAAA,GAAA,OAAA;EAwBI,OAAA,EAAA,OAAA;EAMH,iBAAA,EAAA,OAAA;EAGK,eAAA,EAAA,MAAA;EAGT,gBAAQ,EAAA,OAAA;EA0DA,cAAA,EAAA,MAAA;EAuWA,IAAA,EAAA,GAAA,UAAA,CAAA,IAAA,CAAA;EA+GC,gBAAA,EAAA,GAAA,UAAA,CAAA,IAAA,CAAA;EAoGC,cAAA,EAAA,CAAA,OAAA,EAAA,OAAA,EAAA,GAjqBW,OAiqBX,CAAA,IAAA,CAAA;CAIJ;;;;;AAifqF,cA/oC5F,cAAA,SAAuB,cAAA,CA+oCqE;EAAR;EA/oC7D,MAAA,EAEnB,oBAFmB;EAAc;EAs9CrC,QAAA,OAAA;EAED;;;;;;;;;;;;;cA97CS;;;;UAMH;;eAGK;;OAGT,OAAA,CAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA0DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAuWA;;;;;;;;;;;;UA+GC;;;;;;;;iBAoGC;WAIJ;;;;;;;;aAkNA,eAAe,kBAAkB;;;;;;;;;;;;;;;;+BA+RT,+CAAqD,QAAQ;;;;;;;;;;;;;;;;;;cAuU5F,uBAAc;;KAEf,cAAA,GAAiB"}
@@ -124,6 +124,14 @@ declare class EclesiaIndexer extends EclesiaEmitter {
124
124
  * means the subscription is dead, or when the RPC cannot be reached at all.
125
125
  */
126
126
  private checkLiveness;
127
+ /**
128
+ * Builds the listener for one run's block subscription. It carries the generation it was
129
+ * created for, so when a restart disconnects the previous client and that subscription
130
+ * completes, the completion is attributed to the finished run and ignored instead of
131
+ * poisoning the run that is starting.
132
+ */
133
+ private makeBlockListener;
134
+ /** Listener attached to the current block subscription */
127
135
  private blockListener;
128
136
  private isMinimal;
129
137
  connect(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/indexer/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;cAmEa;EAAA,WAAA,EAAA,MAAA;EAKS,SAAA,EAAA,MAAA;;;EAWa,QAAA,EAXb,oBAWa,CAAA,UAAA,CAAA;EAAA,UAAA,EAAA,OAAA;EAOtB,eAAA,EAAe,MAAA;EAEX,oBAAA,EAAA,GAAA,GAAA,OAAA;EAwBI,OAAA,EAAA,OAAA;EAMH,iBAAA,EAAA,OAAA;EAGK,eAAA,EAAA,MAAA;EAGT,gBAAQ,EAAA,OAAA;EA0DA,cAAA,EAAA,MAAA;EA0VA,IAAA,EAAA,GAAA,UAAA,CAAA,IAAA,CAAA;EAsGC,gBAAA,EAAA,GAAA,UAAA,CAAA,IAAA,CAAA;EAmGC,cAAA,EAAA,CAAA,OAAA,EAAA,OAAA,EAAA,GA1oBW,OA0oBX,CAAA,IAAA,CAAA;CAIJ;;;;;AAifqF,cAxnC5F,cAAA,SAAuB,cAAA,CAwnCqE;EAAR;EAxnC7D,MAAA,EAEnB,oBAFmB;EAAc;EA+7CrC,QAAA,OAAA;EAED;;;;;;;;;;;;;cAv6CS;;;;UAMH;;eAGK;;OAGT,OAAA,CAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA0DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA0VA;;;;;;;;;;;;UAsGC;;;;;;;;iBAmGC;WAIJ;;;;;;;;aAkNA,eAAe,kBAAkB;;;;;;;;;;;;;;;;+BA+RT,+CAAqD,QAAQ;;;;;;;;;;;;;;;;;;cAuU5F,uBAAc;;KAEf,cAAA,GAAiB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/indexer/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;cAmEa;EAAA,WAAA,EAAA,MAAA;EAKS,SAAA,EAAA,MAAA;;;EAWa,QAAA,EAXb,oBAWa,CAAA,UAAA,CAAA;EAAA,UAAA,EAAA,OAAA;EAOtB,eAAA,EAAe,MAAA;EAEX,oBAAA,EAAA,GAAA,GAAA,OAAA;EAwBI,OAAA,EAAA,OAAA;EAMH,iBAAA,EAAA,OAAA;EAGK,eAAA,EAAA,MAAA;EAGT,gBAAQ,EAAA,OAAA;EA0DA,cAAA,EAAA,MAAA;EAuWA,IAAA,EAAA,GAAA,UAAA,CAAA,IAAA,CAAA;EA+GC,gBAAA,EAAA,GAAA,UAAA,CAAA,IAAA,CAAA;EAoGC,cAAA,EAAA,CAAA,OAAA,EAAA,OAAA,EAAA,GAjqBW,OAiqBX,CAAA,IAAA,CAAA;CAIJ;;;;;AAifqF,cA/oC5F,cAAA,SAAuB,cAAA,CA+oCqE;EAAR;EA/oC7D,MAAA,EAEnB,oBAFmB;EAAc;EAs9CrC,QAAA,OAAA;EAED;;;;;;;;;;;;;cA97CS;;;;UAMH;;eAGK;;OAGT,OAAA,CAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA0DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAuWA;;;;;;;;;;;;UA+GC;;;;;;;;iBAoGC;WAIJ;;;;;;;;aAkNA,eAAe,kBAAkB;;;;;;;;;;;;;;;;+BA+RT,+CAAqD,QAAQ;;;;;;;;;;;;;;;;;;cAuU5F,uBAAc;;KAEf,cAAA,GAAiB"}
@@ -296,18 +296,28 @@ var EclesiaIndexer = class extends EclesiaEmitter {
296
296
  this.requestRecovery("RPC unreachable during liveness check", generation);
297
297
  }
298
298
  }
299
- blockListener = {
300
- next: (data) => {
301
- this.newBlockReceived(data.header.height);
302
- },
303
- error: (error) => {
304
- this.log.error("Block subscription error", { error });
305
- this.requestRecovery("block subscription errored");
306
- },
307
- complete: () => {
308
- if (this.started) this.requestRecovery("block subscription closed by the node");
309
- }
310
- };
299
+ /**
300
+ * Builds the listener for one run's block subscription. It carries the generation it was
301
+ * created for, so when a restart disconnects the previous client and that subscription
302
+ * completes, the completion is attributed to the finished run and ignored instead of
303
+ * poisoning the run that is starting.
304
+ */
305
+ makeBlockListener(generation) {
306
+ return {
307
+ next: (data) => {
308
+ if (generation === this.runGeneration) this.newBlockReceived(data.header.height);
309
+ },
310
+ error: (error) => {
311
+ this.log.error("Block subscription error", { error });
312
+ this.requestRecovery("block subscription errored", generation);
313
+ },
314
+ complete: () => {
315
+ if (this.started) this.requestRecovery("block subscription closed by the node", generation);
316
+ }
317
+ };
318
+ }
319
+ /** Listener attached to the current block subscription */
320
+ blockListener = this.makeBlockListener(0);
311
321
  isMinimal(_blockqueue) {
312
322
  if (this.config.minimal) return true;
313
323
  else return false;
@@ -316,6 +326,12 @@ var EclesiaIndexer = class extends EclesiaEmitter {
316
326
  try {
317
327
  if (this.client) {
318
328
  this.log.verbose("Recover from error. Attempting to disconnect from RPC");
329
+ if (this.subscription) {
330
+ try {
331
+ this.subscription.removeListener(this.blockListener);
332
+ } catch (_e) {}
333
+ this.subscription = null;
334
+ }
319
335
  try {
320
336
  this.client.disconnect();
321
337
  } catch (_e) {}
@@ -432,7 +448,10 @@ var EclesiaIndexer = class extends EclesiaEmitter {
432
448
  this.subscription = null;
433
449
  this.log.verbose("Removed existing block listener and subscription");
434
450
  }
435
- if (!this.config.usePolling) this.subscription = this.client.subscribeNewBlock ? this.client.subscribeNewBlock() : null;
451
+ if (!this.config.usePolling) {
452
+ this.subscription = this.client.subscribeNewBlock ? this.client.subscribeNewBlock() : null;
453
+ this.blockListener = this.makeBlockListener(this.runGeneration);
454
+ }
436
455
  const status = await withTimeout(this.client.status(), RPC_TIMEOUT_MS, new RPCError("RPC status call timed out"));
437
456
  this.assertChainId(status.nodeInfo.network);
438
457
  this.latestHeight = status.syncInfo.latestBlockHeight;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["status: StatusResponse","lastProcessed: number | undefined","failingHeight: number | undefined","height: number","timestamp: string","beginBlockEvents: readonly Event[] | readonly Event38[]","endBlockEvents: readonly Event[] | readonly Event38[]","events: Array<{\n msg_index?: number\n events: (Event | Event38)[]\n }>","eventsToAdd: typeof events","events","validators: Validator[]","key: Uint8Array | undefined"],"sources":["../../src/indexer/index.ts"],"sourcesContent":["/* eslint-disable @stylistic/no-multi-spaces */\n/* eslint-disable max-lines */\nimport {\n createHash,\n} from \"node:crypto\";\nimport * as fs from \"node:fs\";\n\nimport {\n BlockResponse, BlockResultsResponse, CometClient, connectComet, Event, StatusResponse, toRfc3339WithNanoseconds,\n} from \"@cosmjs/tendermint-rpc\";\nimport {\n BlockResultsResponse as BlockResultsResponse38, Event as Event38,\n} from \"@cosmjs/tendermint-rpc/build/comet38/responses.js\";\nimport {\n MsgExec,\n} from \"cosmjs-types/cosmos/authz/v1beta1/tx.js\";\nimport {\n QueryValidatorsRequest,\n QueryValidatorsResponse,\n} from \"cosmjs-types/cosmos/staking/v1beta1/query.js\";\nimport {\n Validator,\n} from \"cosmjs-types/cosmos/staking/v1beta1/staking.js\";\nimport {\n Tx,\n} from \"cosmjs-types/cosmos/tx/v1beta1/tx.js\";\nimport Fastify, {\n FastifyInstance,\n} from \"fastify\";\nimport {\n chain,\n} from \"stream-chain\";\nimport pick from \"stream-json/filters/pick.js\";\nimport parser from \"stream-json/parser.js\";\nimport streamArray from \"stream-json/streamers/stream-array.js\";\nimport streamValues from \"stream-json/streamers/stream-values.js\";\nimport batch from \"stream-json/utils/batch.js\";\nimport * as winston from \"winston\";\n\nimport {\n CONNECT_TIMEOUT_MS,\n DEFAULT_BATCH_SIZE, DEFAULT_BIND_HOST, DEFAULT_HEALTH_CHECK_PORT, DEFAULT_POLLING_INTERVAL_MS, DEFAULT_PROMETHEUS_PORT, DEFAULT_START_HEIGHT,\n GENESIS_BATCH_SIZE, IDLE_CHECK_INTERVAL_MS, MAX_FAILURES_PER_BLOCK, PAGINATION_LIMITS, PERIODIC_INTERVALS, RETRY_BASE_DELAY_MS, RETRY_MAX_DELAY_MS, RPC_TIMEOUT_MS,\n} from \"../constants.js\";\nimport {\n EclesiaEmitter,\n} from \"../emitter/index.js\";\nimport {\n ConfigurationError, RPCError,\n} from \"../errors/index.js\";\nimport {\n IndexerMetrics,\n} from \"../metrics/index.js\";\nimport {\n CircularBuffer,\n} from \"../promise-queue/index.js\";\nimport {\n BlockQueue, EclesiaIndexerConfig, EmitFunc, MinimalBlockQueue, WithHeightAndUUID,\n} from \"../types/index.js\";\nimport {\n decodeAttr, hasBlockEventMode, redactUrl, retryDelay, withTimeout,\n} from \"../utils/index.js\";\nimport {\n validateFilePath, validatePort, validatePositiveInteger, validateUrl,\n} from \"../validation/index.js\";\n\n/** Default configuration for the Eclesia indexer */\nexport const defaultIndexerConfig = {\n startHeight: DEFAULT_START_HEIGHT, // Start indexing from block 1\n batchSize: DEFAULT_BATCH_SIZE, // Process blocks in batches of 500\n modules: [], // No modules enabled by default\n getNextHeight: () => DEFAULT_START_HEIGHT, // Default height retrieval function\n logLevel: \"info\" as EclesiaIndexerConfig[\"logLevel\"], // Default log level\n usePolling: false, // Use WebSocket subscription by default\n pollingInterval: DEFAULT_POLLING_INTERVAL_MS, // Poll every 5 seconds when polling enabled\n shouldProcessGenesis: () => false, // Skip genesis processing by default\n minimal: true, // Use minimal indexing by default\n enableHealthcheck: true, // Enable health check server by default\n healthCheckPort: DEFAULT_HEALTH_CHECK_PORT, // Default health check port\n enablePrometheus: false, // Disable Prometheus metrics server by default\n prometheusPort: DEFAULT_PROMETHEUS_PORT, // Default Prometheus metrics server port\n init: () => Promise.resolve(), // No-op initialization function\n beginTransaction: () => Promise.resolve(), // No-op transaction begin function\n endTransaction: (_status: boolean) => Promise.resolve(), // No-op transaction end function\n};\n\n/**\n * Core blockchain indexer that connects to Tendermint RPC and processes blocks\n * Extends EclesiaEmitter to provide event-driven architecture for modules\n */\nexport class EclesiaIndexer extends EclesiaEmitter {\n /** Indexer configuration settings */\n public config: EclesiaIndexerConfig;\n\n /** Fastify HTTP server for health checks */\n private fastify: FastifyInstance | null = null;\n\n /** Prometheus HTTP server instance */\n private prometheusServer: FastifyInstance | null = null;\n\n /** Indicates if the indexer has started */\n private started: boolean = false;\n\n /** Queue for managing block processing pipeline */\n private blockQueue: BlockQueue;\n\n /** Latest block height from the chain */\n private latestHeight!: number;\n\n /** Next block height to process */\n public heightToProcess!: number;\n\n /** Whether the indexer has been initialized */\n private initialized = false;\n\n /** Prometheus metrics server instance */\n public prometheus: IndexerMetrics | null = null;\n\n /** Number of retry attempts for error recovery */\n private retryCount = 0;\n\n /** CometBFT client for ad-hoc queries */\n public client!: CometClient;\n\n /** CometBFT client for block and validator queries */\n public blockClient!: CometClient;\n\n /** Winston logger instance */\n public log: winston.Logger;\n\n /** Flag indicating if indexer should attempt recovery */\n private tryToRecover: boolean = false;\n\n /** Health check status for monitoring */\n private healthCheck = {\n status: \"CONNECTING\",\n };\n\n /** WebSocket subscription for new block notifications */\n private subscription: ReturnType<CometClient[\"subscribeNewBlock\"]> | null = null;\n\n /** Timeout handler for block reception */\n private blockTimeout: NodeJS.Timeout | null = null;\n\n /** Timer for the next poll in polling mode */\n private pollTimer: NodeJS.Timeout | null = null;\n\n /** Bumped on every (re)start and stop so a polling chain from a previous run exits */\n private pollGeneration = 0;\n\n /** Bumped on every start() so callbacks left over from a previous run cannot trigger recovery in this one */\n private runGeneration = 0;\n\n /**\n * Rejecters of waits parked in waitForBlockData(). Each entry is removed as soon as its block\n * arrives, so a healthy run keeps this empty instead of accumulating one entry per block.\n */\n private blockWaiters = new Set<(error: Error) => void>();\n\n /** Pending restart timer */\n private retryTimer: NodeJS.Timeout | null = null;\n\n /** Resolves when the indexer has stopped for good: stop() was called, endHeight was reached, or it gave up */\n private stopped: Promise<void> = Promise.resolve();\n\n private resolveStopped: () => void = () => {};\n\n /** Next height the fetcher will request; advances as fetches are enqueued */\n private nextFetchHeight = 0;\n\n /** Whether a fetcher loop is active, and for which run */\n private fetcherRunning = false;\n\n private fetcherGeneration = 0;\n\n private fetcherToken = 0;\n\n /** Height of the block whose processing failed most recently, and how many times in a row */\n private lastFailedHeight: number | undefined;\n\n private sameHeightFailures = 0;\n\n /**\n * Creates a new Eclesia indexer instance\n * @param config - Indexer configuration options\n */\n constructor(config: EclesiaIndexerConfig) {\n super();\n\n // Validate required configuration\n validateUrl(config.rpcUrl, \"rpcUrl\");\n validatePositiveInteger(config.batchSize, \"batchSize\");\n\n // Validate optional genesis path if processing genesis\n if (config.genesisPath) {\n validateFilePath(config.genesisPath, \"genesisPath\");\n }\n\n // Validate health check port if provided\n if (config.healthCheckPort !== undefined) {\n validatePort(config.healthCheckPort, \"healthCheckPort\");\n }\n\n // Validate prometheus port if provided\n if (config.prometheusPort !== undefined) {\n validatePort(config.prometheusPort, \"prometheusPort\");\n }\n\n // Validate start height if provided\n if (config.startHeight !== undefined) {\n validatePositiveInteger(config.startHeight, \"startHeight\");\n }\n\n // Validate polling interval if provided\n if (config.pollingInterval !== undefined) {\n validatePositiveInteger(config.pollingInterval, \"pollingInterval\");\n }\n\n // Explicit undefined values (typical when config is assembled from env vars) must not\n // override the defaults, so drop them before merging\n const provided = Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n ) as EclesiaIndexerConfig;\n this.config = {\n ...defaultIndexerConfig,\n ...provided,\n };\n\n // Structured logging to stdout only: files, rotation and shipping are the deployment's job.\n // Errors are passed as { error } so their stack survives; the text format prints it under\n // the message and the json format emits it as a nested object.\n const errorFormat = winston.format((info) => {\n const error = info.error;\n if (error instanceof Error) {\n info.error = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n }\n else if (error !== undefined && (typeof error !== \"object\" || error === null)) {\n info.error = {\n message: String(error),\n };\n }\n return info;\n });\n const textFormat = winston.format.printf(({\n level, message, timestamp, error,\n }) => {\n const detail = error as {\n stack?: string\n message?: string\n } | undefined;\n const suffix = detail ? \"\\n\" + (detail.stack ?? detail.message ?? \"\") : \"\";\n return `${timestamp} [${level.toUpperCase()}]:\\t${message}${suffix}`;\n });\n this.log = winston.createLogger({\n level: this.config.logLevel,\n defaultMeta: {\n service: \"Eclesia Indexer\",\n },\n transports: [\n new winston.transports.Console({\n format: this.config.logFormat === \"json\"\n ? winston.format.combine(winston.format.splat(),\n winston.format.timestamp(),\n errorFormat(),\n winston.format.json())\n : winston.format.combine(winston.format.splat(),\n winston.format.timestamp(),\n errorFormat(),\n textFormat,\n winston.format.colorize({\n all: true,\n })),\n }),\n ],\n });\n\n // cosmjs cannot subscribe to blocks over plain HTTP; that needs a ws:// or wss:// URL.\n // Switch to polling now instead of failing after several restarts.\n const protocol = new URL(this.config.rpcUrl).protocol;\n if (!this.config.usePolling && (protocol === \"http:\" || protocol === \"https:\")) {\n this.log.warn(\"rpcUrl \" + redactUrl(this.config.rpcUrl) + \" is HTTP, which cannot deliver block subscriptions; polling every \" + this.config.pollingInterval + \" ms instead (use a ws:// or wss:// URL for WebSocket mode)\");\n this.config.usePolling = true;\n }\n\n // Initialize block queue based on minimal or full indexing mode\n // Pass error handler that uses the logger\n const queueErrorHandler = (e: unknown) => {\n this.prometheus?.recordError(\"rpc\");\n this.log.error(\"Error enqueueing block data\", {\n error: e,\n });\n };\n\n if (this.config.minimal) {\n // Minimal mode: only store block and block results\n this.blockQueue = new CircularBuffer<[BlockResponse, BlockResultsResponse]>(this.config.batchSize, queueErrorHandler);\n }\n else {\n // Full mode: also store validator information\n this.blockQueue = new CircularBuffer<[BlockResponse, BlockResultsResponse, Uint8Array]>(this.config.batchSize, queueErrorHandler);\n }\n\n this.on(\"_unhandled\",\n (msg) => {\n // Guarded: this runs once per unhandled message, and winston formats a record before\n // the transport drops it by level\n if (msg.type !== \"uuid\" && this.log.isVerboseEnabled()) {\n this.log.verbose(\"Unhandled event: \" + msg.type);\n }\n });\n if (this.config.enablePrometheus) {\n this.prometheus = new IndexerMetrics();\n this.prometheusServer = Fastify({\n logger: false,\n });\n this.prometheusServer.get(\"/metrics\",\n async (_req, res) => {\n res.header(\"Content-Type\", this.prometheus!.registry.contentType);\n res.send(await this.prometheus!.getMetrics());\n },\n );\n\n const prometheusPort = this.config.prometheusPort\n ?? (process.env.PROMETHEUS_PORT ? parseInt(process.env.PROMETHEUS_PORT, 10) : DEFAULT_PROMETHEUS_PORT);\n this.prometheusServer.listen({\n port: prometheusPort,\n host: this.config.prometheusHost ?? DEFAULT_BIND_HOST,\n },\n (err) => {\n if (err) {\n this.log.error(\"Prometheus server error\", {\n error: err,\n });\n this.prometheus?.recordError(\"metrics_server\");\n this.emit(\"fatal-error\", {\n error: err,\n message: \"Failed to start metrics server\",\n });\n }\n });\n }\n if (this.config.enableHealthcheck) {\n this.fastify = Fastify({\n logger: false,\n });\n this.fastify.get(\"/health\",\n async (_request, reply) => {\n // WAITING means caught up with an idle chain, which is healthy\n const code = this.healthCheck.status == \"OK\" || this.healthCheck.status == \"WAITING\"\n ? 200\n : 503;\n reply.code(code).send(this.healthCheck);\n });\n const healthPort = this.config.healthCheckPort\n ?? (process.env.HEALTH_CHECK_PORT ? parseInt(process.env.HEALTH_CHECK_PORT, 10) : DEFAULT_HEALTH_CHECK_PORT);\n this.fastify.listen({\n port: healthPort,\n host: this.config.healthCheckHost ?? DEFAULT_BIND_HOST,\n },\n (err) => {\n if (err) {\n this.log.error(\"Health check server error\", {\n error: err,\n });\n this.prometheus?.recordError(\"health_check_server\");\n this.emit(\"fatal-error\", {\n error: err,\n message: \"Failed to start health check server\",\n });\n }\n });\n }\n }\n\n private setStatus(status: string) {\n this.healthCheck.status = status;\n this.prometheus?.setWaiting(status === \"WAITING\");\n }\n\n /**\n * Marks the current run for recovery and wakes the main loop if it is parked waiting for a\n * block. Callbacks left over from a previous run pass their generation and are ignored.\n */\n private requestRecovery(reason: string, generation: number = this.runGeneration): void {\n if (generation !== this.runGeneration) {\n this.log.debug(\"Ignoring recovery request from a previous run: \" + reason);\n return;\n }\n if (!this.tryToRecover) {\n this.log.warn(\"Recovery requested: \" + reason);\n }\n this.tryToRecover = true;\n this.wakeBlockWaiters(\"Recovery requested while waiting for block data\");\n }\n\n /** Rejects every wait parked in waitForBlockData() */\n private wakeBlockWaiters(reason: string): void {\n const waiters = [...this.blockWaiters];\n this.blockWaiters.clear();\n for (const reject of waiters) {\n reject(new RPCError(reason));\n }\n }\n\n /**\n * Refuses to index a chain other than the configured one. An RPC pool that mixes networks, or\n * a wrong URL, would otherwise write a different chain's blocks into the database.\n */\n private assertChainId(network: string): void {\n if (this.config.chainId !== undefined && network !== this.config.chainId) {\n throw new ConfigurationError(\"RPC serves chain \" + network + \" but chainId is configured as \" + this.config.chainId, {\n expected: this.config.chainId,\n actual: network,\n });\n }\n }\n\n /** Counts consecutive processing failures per block height */\n private noteBlockFailure(height: number): void {\n if (height === this.lastFailedHeight) {\n this.sameHeightFailures++;\n }\n else {\n this.lastFailedHeight = height;\n this.sameHeightFailures = 1;\n }\n }\n\n /** True once one block has failed maxFailuresPerBlock times in a row */\n private isStuck(): boolean {\n return this.lastFailedHeight !== undefined\n && this.sameHeightFailures >= (this.config.maxFailuresPerBlock ?? MAX_FAILURES_PER_BLOCK);\n }\n\n /**\n * Waits for the next dequeued block but wakes early when recovery or stop is requested, so a\n * loop parked on an empty queue never waits for a block that will not come.\n */\n private waitForBlockData<T>(dequeued: Promise<T>): Promise<T> {\n if (this.tryToRecover || !this.started) {\n return Promise.reject(new RPCError(\"Recovery requested while waiting for block data\"));\n }\n return new Promise<T>((resolve, reject) => {\n this.blockWaiters.add(reject);\n dequeued.then((value) => {\n this.blockWaiters.delete(reject);\n resolve(value);\n },\n (error) => {\n this.blockWaiters.delete(reject);\n reject(error);\n });\n });\n }\n\n /** (Re)arms the idle check that runs when no block has been announced for a while */\n private armIdleCheck(): void {\n if (this.blockTimeout) {\n clearTimeout(this.blockTimeout);\n }\n this.blockTimeout = setTimeout(() => {\n this.checkLiveness();\n }, IDLE_CHECK_INTERVAL_MS);\n }\n\n /**\n * Runs when no block has been announced for IDLE_CHECK_INTERVAL_MS. A chain that has stopped\n * producing blocks (halt, upgrade, slow chain) is not an error: the indexer reports WAITING and\n * checks again later. Recovery is requested only when the chain has moved on without us, which\n * means the subscription is dead, or when the RPC cannot be reached at all.\n */\n private async checkLiveness(): Promise<void> {\n if (!this.started) {\n return;\n }\n const generation = this.runGeneration;\n try {\n const status = await withTimeout(this.client.status(), RPC_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n if (!this.started || generation !== this.runGeneration) {\n return;\n }\n const chainHeight = status.syncInfo.latestBlockHeight;\n if (chainHeight > this.latestHeight) {\n this.requestRecovery(\"chain is at \" + chainHeight + \" but nothing was announced since \" + this.latestHeight, generation);\n return;\n }\n this.log.info(\"No new block for \" + IDLE_CHECK_INTERVAL_MS / 1000 + \" s, chain height is still \" + chainHeight);\n this.setStatus(\"WAITING\");\n this.armIdleCheck();\n }\n catch (e) {\n this.log.error(\"Liveness check failed\", {\n error: e,\n });\n this.requestRecovery(\"RPC unreachable during liveness check\", generation);\n }\n }\n\n private blockListener = {\n next: (data: {\n header: {\n height: number\n }\n }) => {\n this.newBlockReceived(data.header.height);\n },\n error: (error: unknown) => {\n this.log.error(\"Block subscription error\", {\n error,\n });\n this.requestRecovery(\"block subscription errored\");\n },\n complete: () => {\n if (this.started) {\n this.requestRecovery(\"block subscription closed by the node\");\n }\n },\n };\n\n private isMinimal(_blockqueue: BlockQueue): _blockqueue is MinimalBlockQueue {\n if (this.config.minimal) {\n return true;\n }\n else {\n return false;\n }\n }\n\n public async connect() {\n try {\n if (this.client) {\n this.log.verbose(\"Recover from error. Attempting to disconnect from RPC\");\n try {\n this.client.disconnect();\n }\n catch (_e) { /* empty */ }\n try {\n this.blockClient?.disconnect();\n }\n catch (_e) { /* empty */ }\n this.log.verbose(\"Disconnected from RPC\");\n }\n this.log.info(\"Attempting to connect to RPC: \" + redactUrl(this.config.rpcUrl));\n this.client = await this.connectWithTimeout();\n await withTimeout(this.client.status(), CONNECT_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.log.info(\"Connected to RPC for ad hoc queries\");\n this.blockClient = await this.connectWithTimeout();\n await withTimeout(this.blockClient.status(), CONNECT_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.log.info(\"Connected to RPC for block & validator info\");\n\n return true;\n }\n catch (error) {\n this.log.error(\"RPC connection error\", {\n error,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"RPC connection failed\");\n return false;\n }\n }\n\n /**\n * Opens one CometBFT client with its own timeout. If the timeout wins, the client\n * that may still arrive is disconnected so a slow RPC never leaks a socket.\n */\n private async connectWithTimeout(): Promise<CometClient> {\n let timedOut = false;\n const pending = connectComet(this.config.rpcUrl);\n pending.then((client) => {\n if (timedOut) {\n try {\n client.disconnect();\n }\n catch (_e) { /* empty */ }\n }\n }).catch(() => { /* surfaced through the race below */ });\n try {\n return await withTimeout(pending, CONNECT_TIMEOUT_MS, new RPCError(\"RPC connection timed out\"));\n }\n catch (e) {\n timedOut = true;\n throw e;\n }\n }\n\n private async initialize() {\n if (!this.initialized) {\n try {\n if (this.config.init) {\n await this.config.init();\n }\n }\n catch (e) {\n this.log.error(\"Failed to initialize indexer\", {\n error: e,\n });\n\n this.prometheus?.recordError(\"init_error\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n if (await this.config.shouldProcessGenesis()) {\n try {\n if (this.config.genesisPath) {\n await this.parseGenesis();\n }\n else {\n this.log.warn(\"shouldProcessGenesis() returned true but no genesisPath is configured, skipping genesis import\");\n }\n }\n catch (e) {\n this.log.error(\"Failed to parse genesis\", {\n error: e,\n });\n\n this.prometheus?.recordError(\"genesis_error\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n }\n this.initialized = true;\n }\n }\n\n /**\n * Stops the indexer and releases everything that would keep the process alive:\n * the block subscription, polling and inactivity timers, both RPC clients and the\n * health and metrics servers. Safe to call more than once.\n */\n public async stop(): Promise<void> {\n this.started = false;\n this.resolveStopped();\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n this.wakeBlockWaiters(\"Indexer stopped while waiting for block data\");\n this.stopPolling();\n if (this.blockTimeout) {\n clearTimeout(this.blockTimeout);\n this.blockTimeout = null;\n }\n if (this.subscription) {\n try {\n this.subscription.removeListener(this.blockListener);\n }\n catch (_e) { /* empty */ }\n this.subscription = null;\n }\n for (const client of [this.client, this.blockClient]) {\n try {\n client?.disconnect();\n }\n catch (_e) { /* empty */ }\n }\n const servers = [this.fastify, this.prometheusServer];\n this.fastify = null;\n this.prometheusServer = null;\n await Promise.all(servers.map(server => server?.close().catch((e: unknown) => {\n this.log.warn(\"Error closing HTTP server\", {\n error: e,\n });\n })));\n this.log.info(\"Indexer stopped\");\n }\n\n private clearBlockQueue() {\n if (this.blockQueue) {\n this.blockQueue.clear();\n this.log.verbose(\"Starting, clearing block queue\");\n }\n }\n\n private async setupBlockListening() {\n const connected = await this.connect();\n if (!connected) {\n this.setStatus(\"FAILED\");\n throw new RPCError(\"Failed to connect to RPC\");\n }\n\n try {\n if (!this.config.usePolling && this.subscription) {\n this.subscription.removeListener(this.blockListener);\n this.subscription = null;\n this.log.verbose(\"Removed existing block listener and subscription\");\n }\n if (!this.config.usePolling) {\n this.subscription = this.client.subscribeNewBlock\n ? this.client.subscribeNewBlock()\n : null;\n }\n const status: StatusResponse = await withTimeout(this.client.status(), RPC_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.assertChainId(status.nodeInfo.network);\n this.latestHeight = status.syncInfo.latestBlockHeight;\n this.log.info(\"Connected to \" + status.nodeInfo.network + \", current chain height: \" + this.latestHeight);\n\n this.heightToProcess = await this.config.getNextHeight();\n this.nextFetchHeight = this.heightToProcess;\n if (this.config.usePolling) {\n this.startPolling();\n }\n else {\n if (this.subscription) {\n this.subscription.addListener(this.blockListener);\n }\n else {\n this.prometheus?.recordError(\"rpc\");\n throw new Error(\"Could not subscribe to new blocks\");\n }\n }\n // A subscription that never delivers anything must still be noticed\n this.armIdleCheck();\n }\n catch (e) {\n this.log.error(\"Failed to set up block listening\", {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n }\n\n /**\n * Resolves once the indexer has stopped for good: stop() was called, endHeight was reached, or\n * a fatal-error was emitted. Restarts with backoff do not resolve it. Use it to keep a caller\n * waiting for the whole run rather than for the first loop exit.\n */\n public whenStopped(): Promise<void> {\n return this.stopped;\n }\n\n public async start() {\n if (!this.started) {\n // A fresh run (not a restart after backoff) gets a fresh completion promise\n this.stopped = new Promise<void>((resolve) => {\n this.resolveStopped = resolve;\n });\n }\n this.started = true;\n this.runGeneration++;\n const generation = this.runGeneration;\n this.tryToRecover = false;\n this.blockWaiters.clear();\n this.clearBlockQueue();\n await this.initialize();\n try {\n await this.setupBlockListening();\n this.log.debug(\"Starting main processing loop\");\n this.fetcher().catch((e) => {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.log.error(\"Error in fetching service\", {\n error: e,\n });\n this.requestRecovery(\"fetcher failed\", generation);\n });\n }\n catch (e) {\n this.requestRecovery(\"block listening setup failed: \" + e, generation);\n }\n\n let lastProcessed: number | undefined;\n while (this.started && !this.tryToRecover) {\n let txOpen = false;\n let failingHeight: number | undefined;\n try {\n this.prometheus?.updateRetryCount(this.retryCount);\n if (this.blockQueue.synced && this.blockQueue.size() <= 1) {\n // Only the sentinel is queued: we are at the chain tip. Waiting here is normal and can\n // last hours during a halt or an upgrade, so no transaction is held while we wait.\n this.setStatus(\"WAITING\");\n }\n let height: number;\n let timestamp: string;\n\n // Main block processing (minimal)\n if (this.isMinimal(this.blockQueue)) {\n const toProcess = await this.waitForBlockData(this.blockQueue.dequeue());\n this.log.silly(\"Retrieved block data\");\n if (!toProcess || !toProcess[0] || !toProcess[1]) {\n throw new RPCError(\"Could not fetch block(minimal)\");\n }\n height = toProcess[0].block.header.height;\n failingHeight = height;\n timestamp = toRfc3339WithNanoseconds(toProcess[0].block.header.time);\n // Index block inside a db transaction to ensure data consistency\n await this.config.beginTransaction();\n txOpen = true;\n this.log.silly(\"Started db tx\");\n await this.processBlock(toProcess[0],\n toProcess[1]);\n }\n // Main block processing (full)\n else {\n const toProcess = await this.waitForBlockData(this.blockQueue.dequeue());\n this.log.silly(\"Retrieved block data\");\n if (!toProcess || !toProcess[0] || !toProcess[1] || !toProcess[2]) {\n throw new RPCError(\"Could not fetch block(full)\");\n }\n\n this.log.silly(\"Decoded block\");\n height = toProcess[0].block.header.height;\n failingHeight = height;\n timestamp = toRfc3339WithNanoseconds(toProcess[0].block.header.time);\n await this.config.beginTransaction();\n txOpen = true;\n this.log.silly(\"Started db tx\");\n await this.processBlock(toProcess[0],\n toProcess[1],\n QueryValidatorsResponse.decode(toProcess[2]).validators);\n }\n\n // Emit events to trigger periodic operations every 50, 100 and 1000 blocks\n if (height % PERIODIC_INTERVALS.LARGE == 0) {\n await this.asyncEmit(\"periodic/large\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n if (height % PERIODIC_INTERVALS.MEDIUM == 0) {\n await this.asyncEmit(\"periodic/medium\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n if (height % PERIODIC_INTERVALS.SMALL == 0) {\n await this.asyncEmit(\"periodic/small\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n this.log.silly(\"Handled periodic events\");\n\n await this.config.endTransaction(true);\n txOpen = false;\n lastProcessed = height;\n this.lastFailedHeight = undefined;\n this.sameHeightFailures = 0;\n\n this.log.silly(\"Committed db tx\");\n }\n catch (e) {\n if (txOpen) {\n try {\n await this.config.endTransaction(false);\n }\n catch (dbe) {\n this.prometheus?.recordError(\"database\");\n this.log.error(\"Error ending transaction\", {\n error: dbe,\n });\n }\n }\n if (!this.started) {\n // stop() woke us up; nothing failed\n break;\n }\n if (failingHeight !== undefined) {\n // The data was there and processing failed: count it against this block\n this.noteBlockFailure(failingHeight);\n }\n // any error here is likely recoverable (e.g. RPC timeout, DB error)\n this.prometheus?.recordError(\"block\");\n this.log.error(\"Block processing error\", {\n error: e,\n });\n this.setStatus(\"FAILED\");\n this.requestRecovery(\"block processing failed\", generation);\n break;\n }\n // Reset retry count and status on successful block processing\n this.retryCount = 0;\n this.setStatus(\"OK\");\n if (this.config.endHeight !== undefined && lastProcessed !== undefined && lastProcessed >= this.config.endHeight) {\n this.log.info(\"Reached configured end height \" + this.config.endHeight + \". Stopping indexer.\");\n await this.stop();\n return;\n }\n }\n\n // Normal exit from processing loop\n if (!this.started) {\n this.log.info(\"Indexer manually stopped.\");\n return;\n }\n\n // A block that keeps failing after its data was fetched is a bug or bad data, not an outage.\n // Give up loudly instead of retrying it forever.\n if (this.isStuck()) {\n const height = this.lastFailedHeight;\n this.log.error(\"Block \" + height + \" failed \" + this.sameHeightFailures + \" times in a row. This is a deterministic failure in a handler or the data, not an outage. Giving up.\");\n this.started = false;\n this.emit(\"fatal-error\", {\n error: new Error(\"Block \" + height + \" failed \" + this.sameHeightFailures + \" times in a row\"),\n message: \"Block processing is stuck\",\n retryCount: this.retryCount,\n height,\n });\n this.resolveStopped();\n return;\n }\n\n // Abnormal exit: restart with exponential backoff. Retries are unlimited unless maxRetries\n // is configured, because an RPC or database outage of any length must not kill the indexer.\n this.retryCount++;\n if (this.config.maxRetries !== undefined && this.retryCount > this.config.maxRetries) {\n this.log.error(\"Indexer failed \" + this.retryCount + \" times in a row, giving up (maxRetries=\" + this.config.maxRetries + \")\");\n this.started = false;\n this.emit(\"fatal-error\", {\n error: new Error(\"Max retry attempts exceeded\"),\n message: \"Indexer failed too many times\",\n retryCount: this.retryCount,\n });\n this.resolveStopped();\n return;\n }\n const delay = retryDelay(this.retryCount, RETRY_BASE_DELAY_MS, RETRY_MAX_DELAY_MS);\n this.log.warn(\"Indexer is restarting in \" + delay / 1000 + \" s (attempt \" + this.retryCount + \")\");\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.start().catch((e) => {\n this.log.error(\"Restart failed\", {\n error: e,\n });\n });\n }, delay);\n }\n\n /**\n * Emits an event and waits for its handlers. Handlers run one after another in registration\n * order: they share one database connection and one transaction, so interleaving them at\n * await points would let two handlers read and write the same rows in an unpredictable order,\n * and a failure in one would leave the others mid-flight while the block is rolled back. The\n * first rejection propagates and stops the remaining handlers.\n */\n public asyncEmit: EmitFunc<keyof WithHeightAndUUID<EventMap>> = async (\n type,\n event,\n ) => {\n const handlers = this.handlersFor(type);\n if (handlers.length === 0) {\n // Same routing as emit(): the _unhandled listener logs it at verbose level\n this.emit(type,\n event);\n return;\n }\n for (const handler of handlers) {\n await handler(event);\n }\n };\n\n private async processBlock(block: BlockResponse, block_results: BlockResultsResponse | BlockResultsResponse38, validators?: Validator[]) {\n const endTimer = this.prometheus?.timeBlockProcessing();\n const height = block.block.header.height;\n this.heightToProcess = height;\n this.log.debug(\"Processing block: %d\",\n height);\n // Initialize height & timestamp to be used for this block-processing run\n const timestamp = toRfc3339WithNanoseconds(block.block.header.time);\n\n // Use & await asyncEmit to ensure db insertions in order\n\n /*\n * Emit block information to any interested modules.\n * Primarily the required block module listens to this\n */\n await this.asyncEmit(\"block\",\n {\n value: {\n block,\n block_results,\n },\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled block event\");\n\n let beginBlockEvents: readonly Event[] | readonly Event38[];\n let endBlockEvents: readonly Event[] | readonly Event38[];\n if ((block_results as BlockResultsResponse38).finalizeBlockEvents) {\n // Cosmos SDK 0.50+ tags each finalize_block event with mode=BeginBlock / mode=EndBlock (baseapp.go)\n beginBlockEvents = (block_results as BlockResultsResponse38).finalizeBlockEvents.filter(x => hasBlockEventMode(x, \"BeginBlock\")) as readonly Event38[];\n endBlockEvents = (block_results as BlockResultsResponse38).finalizeBlockEvents.filter(x => hasBlockEventMode(x, \"EndBlock\")) as readonly Event38[];\n }\n else {\n beginBlockEvents = (block_results as BlockResultsResponse).beginBlockEvents;\n endBlockEvents = (block_results as BlockResultsResponse).endBlockEvents;\n }\n // Deal with begin_block events first\n await this.asyncEmit(\"begin_block\",\n {\n value: {\n events: beginBlockEvents!,\n validators,\n },\n height,\n timestamp,\n });\n\n this.log.silly(\"Modules handled begin_block events\");\n\n // Then individual tx_events\n await this.asyncEmit(\"tx_events\",\n {\n value: block_results.results,\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled tx events\");\n\n // Emit details and result for each tx msg separately\n for (let t = 0; t < block.block.txs.length; t++) {\n const tx = Tx.decode(block.block.txs[t]);\n\n const result = block_results.results[t].code;\n const txlog = block_results.results[t].log;\n\n if (result != 0) {\n // Tx failed. Ignore\n continue;\n }\n if (tx.body && tx.body.memo != \"\") {\n const txHash = createHash(\"sha256\").update(block.block.txs[t])\n .digest(\"hex\");\n await this.asyncEmit(\"tx_memo\",\n {\n value: {\n txHash,\n txBody: tx.body,\n },\n height,\n timestamp,\n });\n }\n // parsing log rather than using events directly in order to have msg_index available to filter appropriate events for each msg\n let events: Array<{\n msg_index?: number\n events: (Event | Event38)[]\n }> = [];\n if (txlog) {\n try {\n const parsed = JSON.parse(txlog);\n if (Array.isArray(parsed)) {\n events = parsed;\n }\n }\n catch (_e) {\n // Not every chain writes a JSON log; the msg_index attributes below cover those\n this.log.silly(\"Tx log is not JSON, using msg_index attributes instead\");\n }\n }\n if (events.length == 0) {\n const eventsToAdd: typeof events = [];\n this.log.silly(\"No events found in tx log. Parsing events for msg_index\");\n for (let m = 0; m < block_results.results[t].events.length; m++) {\n if (block_results.results[t].events[m].attributes.find(a => decodeAttr(a.key) == \"msg_index\")) {\n const mi = decodeAttr(block_results.results[t].events[m].attributes.find(a => decodeAttr(a.key) == \"msg_index\")?.value ?? \"\");\n if (mi != \"\") {\n const miNum = parseInt(mi);\n let ev = eventsToAdd.find(x => x.msg_index == miNum);\n if (!ev) {\n ev = {\n msg_index: miNum,\n events: [block_results.results[t].events[m]],\n };\n eventsToAdd.push(ev);\n }\n else {\n ev.events.push(block_results.results[t].events[m] as Event);\n }\n }\n }\n }\n events = events.concat(eventsToAdd);\n }\n const msgs = tx.body?.messages;\n\n if (msgs) {\n for (let i = 0; i < msgs.length; i++) {\n if (this.log.isSillyEnabled()) {\n this.log.silly(\"Indexer broadcasting msg for handling: \" + msgs[i].typeUrl);\n }\n const msgevents\n = msgs.length > 1\n ? events.find(x => x.msg_index == i)?.events\n : events[0]?.events ?? [];\n await this.asyncEmit(msgs[i].typeUrl as never,\n {\n value: {\n tx: msgs[i].value as never,\n events: msgevents,\n } as never,\n height,\n timestamp,\n });\n if (msgs[i].typeUrl == \"/cosmos.authz.v1beta1.MsgExec\") {\n const authzMsgs = MsgExec.decode(msgs[i].value).msgs;\n if (authzMsgs) {\n for (let r = 0; r < authzMsgs.length; r++) {\n if (this.log.isSillyEnabled()) {\n this.log.silly(\"Indexer broadcasting msg for handling: \" + authzMsgs[r].typeUrl);\n }\n const authzMsgEvents = msgevents?.reduce((events, evt) => {\n if (evt.attributes.filter(x => decodeAttr(x.key) == \"authz_msg_index\" && decodeAttr(x.value) == \"\" + r).length > 0) {\n events.push(evt);\n }\n return events;\n },\n [] as (Event | Event38)[]);\n await this.asyncEmit(authzMsgs[r].typeUrl as never,\n {\n value: {\n tx: authzMsgs[r].value as never,\n events: authzMsgEvents,\n } as never,\n height,\n timestamp,\n });\n }\n }\n }\n }\n }\n }\n this.log.silly(\"Modules handled msg events\");\n this.prometheus?.recordTransactions(block.block.txs.length);\n // Then deal with end_block events\n await this.asyncEmit(\"end_block\",\n {\n value: endBlockEvents!,\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled end_block events\");\n\n endTimer?.();\n this.prometheus?.updateBlockMetrics(height, this.latestHeight, this.blockQueue.size());\n }\n\n /**\n * Fetches every height from nextFetchHeight up to latestHeight, waiting for queue space\n * before each fetch. Serves the initial catch-up and live blocks alike: a new announcement\n * only moves latestHeight and starts this loop if it is not already running, so bursts and\n * skipped announcements are handled by the same code and the queue can never overflow.\n */\n private async fetcher() {\n if (this.fetcherRunning && this.fetcherGeneration === this.runGeneration) {\n return;\n }\n const generation = this.runGeneration;\n const token = ++this.fetcherToken;\n this.fetcherRunning = true;\n this.fetcherGeneration = generation;\n try {\n while (\n this.nextFetchHeight <= this.latestHeight\n && (this.config.endHeight === undefined || this.nextFetchHeight <= this.config.endHeight)\n ) {\n // If some other async process triggers recovery, exit the fetching loop\n if (this.tryToRecover || !this.started || generation !== this.runGeneration) {\n this.log.verbose(\"Exiting fetcher loop. Attempting to recover indexer\");\n break;\n }\n const i = this.nextFetchHeight;\n this.log.debug(\"Fetching: \" + i);\n try {\n // Main fetching logic for minimal indexer\n if (this.isMinimal(this.blockQueue)) {\n // We do not await here so that multiple fetches can be in-flight\n const toIndex = withTimeout(Promise.all([this.blockClient.block(i) as Promise<BlockResponse>, this.blockClient.blockResults(i) as Promise<BlockResultsResponse>]), RPC_TIMEOUT_MS, new RPCError(\"Timed out fetching block \" + i)).catch((e) => {\n this.log.error(\"Error fetching block \" + i, {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"fetch failed for block \" + i, generation);\n return Promise.resolve([]);\n }) as Promise<[BlockResponse, BlockResultsResponse]>;\n this.blockQueue.enqueue(toIndex);\n }\n else {\n // Full indexer: block, block results and the complete validator set\n const toIndex = withTimeout(Promise.all([this.blockClient.block(i) as Promise<BlockResponse>, this.blockClient.blockResults(i) as Promise<BlockResultsResponse>, this.fetchValidatorSet(i)]), RPC_TIMEOUT_MS, new RPCError(\"Timed out fetching block \" + i)).catch((e) => {\n this.log.error(\"Error fetching block \" + i, {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"fetch failed for block \" + i, generation);\n return Promise.resolve([]);\n }) as Promise<[BlockResponse, BlockResultsResponse, Uint8Array]>;\n this.blockQueue.enqueue(toIndex);\n }\n }\n catch (e) {\n this.log.error(\"Fetching error\", {\n error: e,\n });\n break;\n }\n this.nextFetchHeight = i + 1;\n // Resolves immediately while the queue has room, otherwise when the processor dequeues\n await this.blockQueue.continue();\n }\n // Caught up with everything announced so far\n if (!this.tryToRecover && this.started && generation === this.runGeneration && !this.blockQueue.synced) {\n this.blockQueue.setSynced();\n this.log.info(\"Synced to latest height\");\n }\n }\n finally {\n if (this.fetcherToken === token) {\n this.fetcherRunning = false;\n }\n }\n }\n\n /**\n * Runs an ABCI query. A transport failure (RPC down, timeout, empty reply) requests a recovery.\n * A reply with a non-zero code is the chain answering \"no\" (pruned height, unknown path, bad\n * key): it is thrown as an RPCError with the code and log, and no recovery is requested for\n * ad-hoc queries, so modules can catch it. Block-pipeline queries reject into the fetcher,\n * which requests recovery itself.\n */\n public async callABCI(path: string, data: Uint8Array, height?: number, adHoc: boolean = true): Promise<Uint8Array> {\n let abciq;\n const endTimer = this.prometheus?.timeRpcCall(path) ?? void 0;\n try {\n abciq = await\n (adHoc\n ? this.client\n : this.blockClient).abciQuery({\n path,\n data,\n height: height,\n });\n }\n catch (e) {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"ABCI query failed for \" + path);\n throw new RPCError(\"RPC not responding. Query at: \" + path + \" (\" + e + \")\");\n }\n finally {\n endTimer?.();\n }\n if (!abciq) {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"empty ABCI response for \" + path);\n throw new RPCError(\"RPC not responding. Query at: \" + path);\n }\n if (abciq.code) {\n // Previously an error reply decoded as an empty result (for example zero validators)\n this.prometheus?.recordError(\"rpc\");\n throw new RPCError(\"ABCI query \" + path + \" failed with code \" + abciq.code + (abciq.log ? \": \" + abciq.log : \"\"));\n }\n return abciq.value;\n }\n\n /**\n * Fetches the complete validator set at a height, following pagination, and returns it\n * re-encoded as a single QueryValidatorsResponse so the block queue payload keeps its shape.\n * Chains with more validators than one page (1000) were silently truncated before.\n */\n private async fetchValidatorSet(height: number): Promise<Uint8Array> {\n const validators: Validator[] = [];\n let key: Uint8Array | undefined;\n do {\n const request = QueryValidatorsRequest.fromPartial({\n pagination: key\n ? {\n limit: PAGINATION_LIMITS.VALIDATORS,\n key,\n }\n : {\n limit: PAGINATION_LIMITS.VALIDATORS,\n },\n });\n const page = QueryValidatorsResponse.decode(\n await this.callABCI(\"/cosmos.staking.v1beta1.Query/Validators\", QueryValidatorsRequest.encode(request).finish(), height, false),\n );\n validators.push(...page.validators);\n key = page.pagination?.nextKey && page.pagination.nextKey.length > 0 ? page.pagination.nextKey : undefined;\n } while (key);\n return QueryValidatorsResponse.encode(QueryValidatorsResponse.fromPartial({\n validators,\n })).finish();\n }\n\n private newBlockReceived(height: number): void {\n this.armIdleCheck();\n this.log.info(\"Received new block: %d\",\n height);\n if (height <= this.latestHeight) {\n // Re-announced, or from a lagging node behind a load balancer: never move backwards\n return;\n }\n this.latestHeight = height;\n if (this.tryToRecover || !this.started) {\n return;\n }\n // The fetcher requests every height up to latestHeight and waits for queue space as it\n // goes, so a burst of blocks or an announcement that skipped heights is handled exactly\n // like the initial catch-up. Nothing to do if it is already running.\n this.fetcher().catch((e) => {\n this.log.error(\"Error in fetching service\", {\n error: e,\n });\n this.requestRecovery(\"fetcher failed\");\n });\n }\n\n /** Starts a single polling chain, retiring any chain left over from a previous run */\n private startPolling(): void {\n this.stopPolling();\n const generation = ++this.pollGeneration;\n this.pollForBlock(generation);\n }\n\n private stopPolling(): void {\n this.pollGeneration++;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n }\n\n private async pollForBlock(generation: number) {\n if (!this.started || generation !== this.pollGeneration) {\n return;\n }\n try {\n const status = await this.client.status();\n // A restart or stop may have happened while waiting on the RPC\n if (!this.started || generation !== this.pollGeneration) {\n return;\n }\n if (status.syncInfo.latestBlockHeight > this.latestHeight) {\n this.newBlockReceived(status.syncInfo.latestBlockHeight);\n }\n }\n catch (e) {\n this.log.error(\"Error polling for new block\", {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"polling failed\");\n // Recovery restarts polling from setupBlockListening\n return;\n }\n this.pollTimer = setTimeout(() => {\n this.pollForBlock(generation);\n },\n this.config.pollingInterval);\n }\n\n private readGenesis(): fs.ReadStream {\n if (this.config.genesisPath) {\n return fs.createReadStream(this.config.genesisPath);\n }\n else {\n throw new Error(\"Genesis path not set\");\n }\n }\n\n private async setArrayReader(path: string, processor: (chunk: unknown) => Promise<void>): Promise<boolean> {\n const readPromise = new Promise<boolean>((resolve, reject) => {\n try {\n const filters = path.split(\".\");\n const pickers = filters.map(filter => pick({\n filter,\n }));\n let counter = 0;\n let chunkCounter = 0;\n\n // Wrapper processor that handles transaction chunking\n const chunkProcessor = async (data: unknown) => {\n chunkCounter++;\n this.log.debug(`Processing genesis chunk ${chunkCounter}`);\n\n await processor(data);\n\n // Commit and restart transaction every 5 chunks (5000 entries)\n // This prevents timeout on large genesis files\n if (chunkCounter % 5 === 0) {\n this.log.debug(`Committing transaction after chunk ${chunkCounter}`);\n await this.config.endTransaction(true);\n await this.config.beginTransaction();\n }\n // Pass the chunk on so the \"data\" listener below can count what was processed\n return data;\n };\n\n chain([\n this.readGenesis(),\n parser(),\n ...pickers,\n streamArray(),\n batch({\n batchSize: GENESIS_BATCH_SIZE,\n }),\n chunkProcessor,\n ])\n .on(\"data\",\n (data) => {\n if (data && Array.isArray(data)) {\n counter = counter + data.length;\n }\n })\n .on(\"end\",\n () => {\n this.log.info(`Processed ${counter} entries in ${chunkCounter} chunks`);\n resolve(true);\n })\n // stream-chain re-emits parser and processor errors here; without a listener Node\n // raises them as an uncaught exception and parseGenesis never rolls back\n .on(\"error\",\n (e) => {\n this.log.error(\"Error reading genesis array \" + path, {\n error: e,\n });\n reject(e);\n });\n }\n catch (e) {\n this.log.verbose(\"Error in setArrayReader: \" + e);\n reject(e);\n }\n });\n\n return readPromise;\n }\n\n private async setValueReader(path: string, processor: (chunk: unknown) => Promise<void>): Promise<boolean> {\n const readPromise = new Promise<boolean>((resolve, reject) => {\n try {\n const filters = path.split(\".\");\n const pickers = filters.map(filter => pick({\n filter,\n }));\n\n let counter = 0;\n chain([this.readGenesis(), parser(), ...pickers, streamValues(), processor])\n .on(\"data\",\n (_data) => {\n counter++;\n })\n .on(\"end\",\n () => {\n this.log.info(`Processed ${counter} entries`);\n resolve(true);\n })\n .on(\"error\",\n (e) => {\n this.log.error(\"Error reading genesis value \" + path, {\n error: e,\n });\n reject(e);\n });\n }\n catch (e) {\n this.log.verbose(\"Error in setValueReader: \" + e);\n reject(e);\n }\n });\n\n return readPromise;\n }\n\n private async parseGenesis() {\n this.log.info(\"Parsing genesis\");\n // Lets the storage layer mark the import as in progress before anything is written\n await this.config.onGenesisStart?.();\n await this.config.beginTransaction();\n try {\n this.log.info(\"Starting genesis import\");\n this.log.debug(\"Importing genesis file...\");\n\n for (const [key, _value] of this.handled) {\n if (key.startsWith(\"genesis/\")) {\n const genesisEntry = key.split(\"/\");\n\n this.log.verbose(\"Importing \" + key + \"...\");\n if (genesisEntry[1] == \"array\") {\n await this.setArrayReader(genesisEntry[2],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n await this.asyncEmit(key as never,\n {\n value: data.map((x: {\n value: never\n }) => x.value),\n } as never);\n return data;\n });\n }\n else {\n await this.setValueReader(genesisEntry[2],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n await this.asyncEmit(key as never,\n {\n value: data.value,\n } as never);\n return data;\n });\n }\n }\n }\n\n this.log.info(\"Importing gen TXs...\");\n\n await this.setArrayReader(\"app_state.genutil.gen_txs\",\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n for (let j = 0; j < data.length; j++) {\n const gentx = data[j].value;\n for (let i = 0; i < gentx.body.messages.length; i++) {\n const msg = gentx.body.messages[i];\n await this.asyncEmit((\"gentx\" + msg[\"@type\"]) as never,\n {\n value: msg,\n } as never);\n }\n }\n return data;\n });\n // Recorded inside the last transaction, so \"complete\" commits together with the final chunk\n await this.config.onGenesisComplete?.();\n await this.config.endTransaction(true);\n\n this.log.info(\"Finished importing\");\n }\n catch (e) {\n try {\n await this.config.endTransaction(false);\n }\n catch (dbe) {\n this.prometheus?.recordError(\"database\");\n this.log.error(\"Error ending transaction\", {\n error: dbe,\n });\n }\n this.log.error(\"Failed to import genesis\");\n throw e;\n }\n }\n}\n\n/** @deprecated Misspelling kept for compatibility, use EclesiaIndexer. Removed in 3.0. */\nexport const EcleciaIndexer = EclesiaIndexer;\n/** @deprecated Misspelling kept for compatibility, use EclesiaIndexer. Removed in 3.0. */\nexport type EcleciaIndexer = EclesiaIndexer;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["status: StatusResponse","lastProcessed: number | undefined","failingHeight: number | undefined","height: number","timestamp: string","beginBlockEvents: readonly Event[] | readonly Event38[]","endBlockEvents: readonly Event[] | readonly Event38[]","events: Array<{\n msg_index?: number\n events: (Event | Event38)[]\n }>","eventsToAdd: typeof events","events","validators: Validator[]","key: Uint8Array | undefined"],"sources":["../../src/indexer/index.ts"],"sourcesContent":["/* eslint-disable @stylistic/no-multi-spaces */\n/* eslint-disable max-lines */\nimport {\n createHash,\n} from \"node:crypto\";\nimport * as fs from \"node:fs\";\n\nimport {\n BlockResponse, BlockResultsResponse, CometClient, connectComet, Event, StatusResponse, toRfc3339WithNanoseconds,\n} from \"@cosmjs/tendermint-rpc\";\nimport {\n BlockResultsResponse as BlockResultsResponse38, Event as Event38,\n} from \"@cosmjs/tendermint-rpc/build/comet38/responses.js\";\nimport {\n MsgExec,\n} from \"cosmjs-types/cosmos/authz/v1beta1/tx.js\";\nimport {\n QueryValidatorsRequest,\n QueryValidatorsResponse,\n} from \"cosmjs-types/cosmos/staking/v1beta1/query.js\";\nimport {\n Validator,\n} from \"cosmjs-types/cosmos/staking/v1beta1/staking.js\";\nimport {\n Tx,\n} from \"cosmjs-types/cosmos/tx/v1beta1/tx.js\";\nimport Fastify, {\n FastifyInstance,\n} from \"fastify\";\nimport {\n chain,\n} from \"stream-chain\";\nimport pick from \"stream-json/filters/pick.js\";\nimport parser from \"stream-json/parser.js\";\nimport streamArray from \"stream-json/streamers/stream-array.js\";\nimport streamValues from \"stream-json/streamers/stream-values.js\";\nimport batch from \"stream-json/utils/batch.js\";\nimport * as winston from \"winston\";\n\nimport {\n CONNECT_TIMEOUT_MS,\n DEFAULT_BATCH_SIZE, DEFAULT_BIND_HOST, DEFAULT_HEALTH_CHECK_PORT, DEFAULT_POLLING_INTERVAL_MS, DEFAULT_PROMETHEUS_PORT, DEFAULT_START_HEIGHT,\n GENESIS_BATCH_SIZE, IDLE_CHECK_INTERVAL_MS, MAX_FAILURES_PER_BLOCK, PAGINATION_LIMITS, PERIODIC_INTERVALS, RETRY_BASE_DELAY_MS, RETRY_MAX_DELAY_MS, RPC_TIMEOUT_MS,\n} from \"../constants.js\";\nimport {\n EclesiaEmitter,\n} from \"../emitter/index.js\";\nimport {\n ConfigurationError, RPCError,\n} from \"../errors/index.js\";\nimport {\n IndexerMetrics,\n} from \"../metrics/index.js\";\nimport {\n CircularBuffer,\n} from \"../promise-queue/index.js\";\nimport {\n BlockQueue, EclesiaIndexerConfig, EmitFunc, MinimalBlockQueue, WithHeightAndUUID,\n} from \"../types/index.js\";\nimport {\n decodeAttr, hasBlockEventMode, redactUrl, retryDelay, withTimeout,\n} from \"../utils/index.js\";\nimport {\n validateFilePath, validatePort, validatePositiveInteger, validateUrl,\n} from \"../validation/index.js\";\n\n/** Default configuration for the Eclesia indexer */\nexport const defaultIndexerConfig = {\n startHeight: DEFAULT_START_HEIGHT, // Start indexing from block 1\n batchSize: DEFAULT_BATCH_SIZE, // Process blocks in batches of 500\n modules: [], // No modules enabled by default\n getNextHeight: () => DEFAULT_START_HEIGHT, // Default height retrieval function\n logLevel: \"info\" as EclesiaIndexerConfig[\"logLevel\"], // Default log level\n usePolling: false, // Use WebSocket subscription by default\n pollingInterval: DEFAULT_POLLING_INTERVAL_MS, // Poll every 5 seconds when polling enabled\n shouldProcessGenesis: () => false, // Skip genesis processing by default\n minimal: true, // Use minimal indexing by default\n enableHealthcheck: true, // Enable health check server by default\n healthCheckPort: DEFAULT_HEALTH_CHECK_PORT, // Default health check port\n enablePrometheus: false, // Disable Prometheus metrics server by default\n prometheusPort: DEFAULT_PROMETHEUS_PORT, // Default Prometheus metrics server port\n init: () => Promise.resolve(), // No-op initialization function\n beginTransaction: () => Promise.resolve(), // No-op transaction begin function\n endTransaction: (_status: boolean) => Promise.resolve(), // No-op transaction end function\n};\n\n/**\n * Core blockchain indexer that connects to Tendermint RPC and processes blocks\n * Extends EclesiaEmitter to provide event-driven architecture for modules\n */\nexport class EclesiaIndexer extends EclesiaEmitter {\n /** Indexer configuration settings */\n public config: EclesiaIndexerConfig;\n\n /** Fastify HTTP server for health checks */\n private fastify: FastifyInstance | null = null;\n\n /** Prometheus HTTP server instance */\n private prometheusServer: FastifyInstance | null = null;\n\n /** Indicates if the indexer has started */\n private started: boolean = false;\n\n /** Queue for managing block processing pipeline */\n private blockQueue: BlockQueue;\n\n /** Latest block height from the chain */\n private latestHeight!: number;\n\n /** Next block height to process */\n public heightToProcess!: number;\n\n /** Whether the indexer has been initialized */\n private initialized = false;\n\n /** Prometheus metrics server instance */\n public prometheus: IndexerMetrics | null = null;\n\n /** Number of retry attempts for error recovery */\n private retryCount = 0;\n\n /** CometBFT client for ad-hoc queries */\n public client!: CometClient;\n\n /** CometBFT client for block and validator queries */\n public blockClient!: CometClient;\n\n /** Winston logger instance */\n public log: winston.Logger;\n\n /** Flag indicating if indexer should attempt recovery */\n private tryToRecover: boolean = false;\n\n /** Health check status for monitoring */\n private healthCheck = {\n status: \"CONNECTING\",\n };\n\n /** WebSocket subscription for new block notifications */\n private subscription: ReturnType<CometClient[\"subscribeNewBlock\"]> | null = null;\n\n /** Timeout handler for block reception */\n private blockTimeout: NodeJS.Timeout | null = null;\n\n /** Timer for the next poll in polling mode */\n private pollTimer: NodeJS.Timeout | null = null;\n\n /** Bumped on every (re)start and stop so a polling chain from a previous run exits */\n private pollGeneration = 0;\n\n /** Bumped on every start() so callbacks left over from a previous run cannot trigger recovery in this one */\n private runGeneration = 0;\n\n /**\n * Rejecters of waits parked in waitForBlockData(). Each entry is removed as soon as its block\n * arrives, so a healthy run keeps this empty instead of accumulating one entry per block.\n */\n private blockWaiters = new Set<(error: Error) => void>();\n\n /** Pending restart timer */\n private retryTimer: NodeJS.Timeout | null = null;\n\n /** Resolves when the indexer has stopped for good: stop() was called, endHeight was reached, or it gave up */\n private stopped: Promise<void> = Promise.resolve();\n\n private resolveStopped: () => void = () => {};\n\n /** Next height the fetcher will request; advances as fetches are enqueued */\n private nextFetchHeight = 0;\n\n /** Whether a fetcher loop is active, and for which run */\n private fetcherRunning = false;\n\n private fetcherGeneration = 0;\n\n private fetcherToken = 0;\n\n /** Height of the block whose processing failed most recently, and how many times in a row */\n private lastFailedHeight: number | undefined;\n\n private sameHeightFailures = 0;\n\n /**\n * Creates a new Eclesia indexer instance\n * @param config - Indexer configuration options\n */\n constructor(config: EclesiaIndexerConfig) {\n super();\n\n // Validate required configuration\n validateUrl(config.rpcUrl, \"rpcUrl\");\n validatePositiveInteger(config.batchSize, \"batchSize\");\n\n // Validate optional genesis path if processing genesis\n if (config.genesisPath) {\n validateFilePath(config.genesisPath, \"genesisPath\");\n }\n\n // Validate health check port if provided\n if (config.healthCheckPort !== undefined) {\n validatePort(config.healthCheckPort, \"healthCheckPort\");\n }\n\n // Validate prometheus port if provided\n if (config.prometheusPort !== undefined) {\n validatePort(config.prometheusPort, \"prometheusPort\");\n }\n\n // Validate start height if provided\n if (config.startHeight !== undefined) {\n validatePositiveInteger(config.startHeight, \"startHeight\");\n }\n\n // Validate polling interval if provided\n if (config.pollingInterval !== undefined) {\n validatePositiveInteger(config.pollingInterval, \"pollingInterval\");\n }\n\n // Explicit undefined values (typical when config is assembled from env vars) must not\n // override the defaults, so drop them before merging\n const provided = Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n ) as EclesiaIndexerConfig;\n this.config = {\n ...defaultIndexerConfig,\n ...provided,\n };\n\n // Structured logging to stdout only: files, rotation and shipping are the deployment's job.\n // Errors are passed as { error } so their stack survives; the text format prints it under\n // the message and the json format emits it as a nested object.\n const errorFormat = winston.format((info) => {\n const error = info.error;\n if (error instanceof Error) {\n info.error = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n }\n else if (error !== undefined && (typeof error !== \"object\" || error === null)) {\n info.error = {\n message: String(error),\n };\n }\n return info;\n });\n const textFormat = winston.format.printf(({\n level, message, timestamp, error,\n }) => {\n const detail = error as {\n stack?: string\n message?: string\n } | undefined;\n const suffix = detail ? \"\\n\" + (detail.stack ?? detail.message ?? \"\") : \"\";\n return `${timestamp} [${level.toUpperCase()}]:\\t${message}${suffix}`;\n });\n this.log = winston.createLogger({\n level: this.config.logLevel,\n defaultMeta: {\n service: \"Eclesia Indexer\",\n },\n transports: [\n new winston.transports.Console({\n format: this.config.logFormat === \"json\"\n ? winston.format.combine(winston.format.splat(),\n winston.format.timestamp(),\n errorFormat(),\n winston.format.json())\n : winston.format.combine(winston.format.splat(),\n winston.format.timestamp(),\n errorFormat(),\n textFormat,\n winston.format.colorize({\n all: true,\n })),\n }),\n ],\n });\n\n // cosmjs cannot subscribe to blocks over plain HTTP; that needs a ws:// or wss:// URL.\n // Switch to polling now instead of failing after several restarts.\n const protocol = new URL(this.config.rpcUrl).protocol;\n if (!this.config.usePolling && (protocol === \"http:\" || protocol === \"https:\")) {\n this.log.warn(\"rpcUrl \" + redactUrl(this.config.rpcUrl) + \" is HTTP, which cannot deliver block subscriptions; polling every \" + this.config.pollingInterval + \" ms instead (use a ws:// or wss:// URL for WebSocket mode)\");\n this.config.usePolling = true;\n }\n\n // Initialize block queue based on minimal or full indexing mode\n // Pass error handler that uses the logger\n const queueErrorHandler = (e: unknown) => {\n this.prometheus?.recordError(\"rpc\");\n this.log.error(\"Error enqueueing block data\", {\n error: e,\n });\n };\n\n if (this.config.minimal) {\n // Minimal mode: only store block and block results\n this.blockQueue = new CircularBuffer<[BlockResponse, BlockResultsResponse]>(this.config.batchSize, queueErrorHandler);\n }\n else {\n // Full mode: also store validator information\n this.blockQueue = new CircularBuffer<[BlockResponse, BlockResultsResponse, Uint8Array]>(this.config.batchSize, queueErrorHandler);\n }\n\n this.on(\"_unhandled\",\n (msg) => {\n // Guarded: this runs once per unhandled message, and winston formats a record before\n // the transport drops it by level\n if (msg.type !== \"uuid\" && this.log.isVerboseEnabled()) {\n this.log.verbose(\"Unhandled event: \" + msg.type);\n }\n });\n if (this.config.enablePrometheus) {\n this.prometheus = new IndexerMetrics();\n this.prometheusServer = Fastify({\n logger: false,\n });\n this.prometheusServer.get(\"/metrics\",\n async (_req, res) => {\n res.header(\"Content-Type\", this.prometheus!.registry.contentType);\n res.send(await this.prometheus!.getMetrics());\n },\n );\n\n const prometheusPort = this.config.prometheusPort\n ?? (process.env.PROMETHEUS_PORT ? parseInt(process.env.PROMETHEUS_PORT, 10) : DEFAULT_PROMETHEUS_PORT);\n this.prometheusServer.listen({\n port: prometheusPort,\n host: this.config.prometheusHost ?? DEFAULT_BIND_HOST,\n },\n (err) => {\n if (err) {\n this.log.error(\"Prometheus server error\", {\n error: err,\n });\n this.prometheus?.recordError(\"metrics_server\");\n this.emit(\"fatal-error\", {\n error: err,\n message: \"Failed to start metrics server\",\n });\n }\n });\n }\n if (this.config.enableHealthcheck) {\n this.fastify = Fastify({\n logger: false,\n });\n this.fastify.get(\"/health\",\n async (_request, reply) => {\n // WAITING means caught up with an idle chain, which is healthy\n const code = this.healthCheck.status == \"OK\" || this.healthCheck.status == \"WAITING\"\n ? 200\n : 503;\n reply.code(code).send(this.healthCheck);\n });\n const healthPort = this.config.healthCheckPort\n ?? (process.env.HEALTH_CHECK_PORT ? parseInt(process.env.HEALTH_CHECK_PORT, 10) : DEFAULT_HEALTH_CHECK_PORT);\n this.fastify.listen({\n port: healthPort,\n host: this.config.healthCheckHost ?? DEFAULT_BIND_HOST,\n },\n (err) => {\n if (err) {\n this.log.error(\"Health check server error\", {\n error: err,\n });\n this.prometheus?.recordError(\"health_check_server\");\n this.emit(\"fatal-error\", {\n error: err,\n message: \"Failed to start health check server\",\n });\n }\n });\n }\n }\n\n private setStatus(status: string) {\n this.healthCheck.status = status;\n this.prometheus?.setWaiting(status === \"WAITING\");\n }\n\n /**\n * Marks the current run for recovery and wakes the main loop if it is parked waiting for a\n * block. Callbacks left over from a previous run pass their generation and are ignored.\n */\n private requestRecovery(reason: string, generation: number = this.runGeneration): void {\n if (generation !== this.runGeneration) {\n this.log.debug(\"Ignoring recovery request from a previous run: \" + reason);\n return;\n }\n if (!this.tryToRecover) {\n this.log.warn(\"Recovery requested: \" + reason);\n }\n this.tryToRecover = true;\n this.wakeBlockWaiters(\"Recovery requested while waiting for block data\");\n }\n\n /** Rejects every wait parked in waitForBlockData() */\n private wakeBlockWaiters(reason: string): void {\n const waiters = [...this.blockWaiters];\n this.blockWaiters.clear();\n for (const reject of waiters) {\n reject(new RPCError(reason));\n }\n }\n\n /**\n * Refuses to index a chain other than the configured one. An RPC pool that mixes networks, or\n * a wrong URL, would otherwise write a different chain's blocks into the database.\n */\n private assertChainId(network: string): void {\n if (this.config.chainId !== undefined && network !== this.config.chainId) {\n throw new ConfigurationError(\"RPC serves chain \" + network + \" but chainId is configured as \" + this.config.chainId, {\n expected: this.config.chainId,\n actual: network,\n });\n }\n }\n\n /** Counts consecutive processing failures per block height */\n private noteBlockFailure(height: number): void {\n if (height === this.lastFailedHeight) {\n this.sameHeightFailures++;\n }\n else {\n this.lastFailedHeight = height;\n this.sameHeightFailures = 1;\n }\n }\n\n /** True once one block has failed maxFailuresPerBlock times in a row */\n private isStuck(): boolean {\n return this.lastFailedHeight !== undefined\n && this.sameHeightFailures >= (this.config.maxFailuresPerBlock ?? MAX_FAILURES_PER_BLOCK);\n }\n\n /**\n * Waits for the next dequeued block but wakes early when recovery or stop is requested, so a\n * loop parked on an empty queue never waits for a block that will not come.\n */\n private waitForBlockData<T>(dequeued: Promise<T>): Promise<T> {\n if (this.tryToRecover || !this.started) {\n return Promise.reject(new RPCError(\"Recovery requested while waiting for block data\"));\n }\n return new Promise<T>((resolve, reject) => {\n this.blockWaiters.add(reject);\n dequeued.then((value) => {\n this.blockWaiters.delete(reject);\n resolve(value);\n },\n (error) => {\n this.blockWaiters.delete(reject);\n reject(error);\n });\n });\n }\n\n /** (Re)arms the idle check that runs when no block has been announced for a while */\n private armIdleCheck(): void {\n if (this.blockTimeout) {\n clearTimeout(this.blockTimeout);\n }\n this.blockTimeout = setTimeout(() => {\n this.checkLiveness();\n }, IDLE_CHECK_INTERVAL_MS);\n }\n\n /**\n * Runs when no block has been announced for IDLE_CHECK_INTERVAL_MS. A chain that has stopped\n * producing blocks (halt, upgrade, slow chain) is not an error: the indexer reports WAITING and\n * checks again later. Recovery is requested only when the chain has moved on without us, which\n * means the subscription is dead, or when the RPC cannot be reached at all.\n */\n private async checkLiveness(): Promise<void> {\n if (!this.started) {\n return;\n }\n const generation = this.runGeneration;\n try {\n const status = await withTimeout(this.client.status(), RPC_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n if (!this.started || generation !== this.runGeneration) {\n return;\n }\n const chainHeight = status.syncInfo.latestBlockHeight;\n if (chainHeight > this.latestHeight) {\n this.requestRecovery(\"chain is at \" + chainHeight + \" but nothing was announced since \" + this.latestHeight, generation);\n return;\n }\n this.log.info(\"No new block for \" + IDLE_CHECK_INTERVAL_MS / 1000 + \" s, chain height is still \" + chainHeight);\n this.setStatus(\"WAITING\");\n this.armIdleCheck();\n }\n catch (e) {\n this.log.error(\"Liveness check failed\", {\n error: e,\n });\n this.requestRecovery(\"RPC unreachable during liveness check\", generation);\n }\n }\n\n /**\n * Builds the listener for one run's block subscription. It carries the generation it was\n * created for, so when a restart disconnects the previous client and that subscription\n * completes, the completion is attributed to the finished run and ignored instead of\n * poisoning the run that is starting.\n */\n private makeBlockListener(generation: number) {\n return {\n next: (data: {\n header: {\n height: number\n }\n }) => {\n if (generation === this.runGeneration) {\n this.newBlockReceived(data.header.height);\n }\n },\n error: (error: unknown) => {\n this.log.error(\"Block subscription error\", {\n error,\n });\n this.requestRecovery(\"block subscription errored\", generation);\n },\n complete: () => {\n if (this.started) {\n this.requestRecovery(\"block subscription closed by the node\", generation);\n }\n },\n };\n }\n\n /** Listener attached to the current block subscription */\n private blockListener = this.makeBlockListener(0);\n\n private isMinimal(_blockqueue: BlockQueue): _blockqueue is MinimalBlockQueue {\n if (this.config.minimal) {\n return true;\n }\n else {\n return false;\n }\n }\n\n public async connect() {\n try {\n if (this.client) {\n this.log.verbose(\"Recover from error. Attempting to disconnect from RPC\");\n // Detach first: closing the socket completes the subscription, and that completion\n // must not be mistaken for the node dropping us\n if (this.subscription) {\n try {\n this.subscription.removeListener(this.blockListener);\n }\n catch (_e) { /* empty */ }\n this.subscription = null;\n }\n try {\n this.client.disconnect();\n }\n catch (_e) { /* empty */ }\n try {\n this.blockClient?.disconnect();\n }\n catch (_e) { /* empty */ }\n this.log.verbose(\"Disconnected from RPC\");\n }\n this.log.info(\"Attempting to connect to RPC: \" + redactUrl(this.config.rpcUrl));\n this.client = await this.connectWithTimeout();\n await withTimeout(this.client.status(), CONNECT_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.log.info(\"Connected to RPC for ad hoc queries\");\n this.blockClient = await this.connectWithTimeout();\n await withTimeout(this.blockClient.status(), CONNECT_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.log.info(\"Connected to RPC for block & validator info\");\n\n return true;\n }\n catch (error) {\n this.log.error(\"RPC connection error\", {\n error,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"RPC connection failed\");\n return false;\n }\n }\n\n /**\n * Opens one CometBFT client with its own timeout. If the timeout wins, the client\n * that may still arrive is disconnected so a slow RPC never leaks a socket.\n */\n private async connectWithTimeout(): Promise<CometClient> {\n let timedOut = false;\n const pending = connectComet(this.config.rpcUrl);\n pending.then((client) => {\n if (timedOut) {\n try {\n client.disconnect();\n }\n catch (_e) { /* empty */ }\n }\n }).catch(() => { /* surfaced through the race below */ });\n try {\n return await withTimeout(pending, CONNECT_TIMEOUT_MS, new RPCError(\"RPC connection timed out\"));\n }\n catch (e) {\n timedOut = true;\n throw e;\n }\n }\n\n private async initialize() {\n if (!this.initialized) {\n try {\n if (this.config.init) {\n await this.config.init();\n }\n }\n catch (e) {\n this.log.error(\"Failed to initialize indexer\", {\n error: e,\n });\n\n this.prometheus?.recordError(\"init_error\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n if (await this.config.shouldProcessGenesis()) {\n try {\n if (this.config.genesisPath) {\n await this.parseGenesis();\n }\n else {\n this.log.warn(\"shouldProcessGenesis() returned true but no genesisPath is configured, skipping genesis import\");\n }\n }\n catch (e) {\n this.log.error(\"Failed to parse genesis\", {\n error: e,\n });\n\n this.prometheus?.recordError(\"genesis_error\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n }\n this.initialized = true;\n }\n }\n\n /**\n * Stops the indexer and releases everything that would keep the process alive:\n * the block subscription, polling and inactivity timers, both RPC clients and the\n * health and metrics servers. Safe to call more than once.\n */\n public async stop(): Promise<void> {\n this.started = false;\n this.resolveStopped();\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n this.wakeBlockWaiters(\"Indexer stopped while waiting for block data\");\n this.stopPolling();\n if (this.blockTimeout) {\n clearTimeout(this.blockTimeout);\n this.blockTimeout = null;\n }\n if (this.subscription) {\n try {\n this.subscription.removeListener(this.blockListener);\n }\n catch (_e) { /* empty */ }\n this.subscription = null;\n }\n for (const client of [this.client, this.blockClient]) {\n try {\n client?.disconnect();\n }\n catch (_e) { /* empty */ }\n }\n const servers = [this.fastify, this.prometheusServer];\n this.fastify = null;\n this.prometheusServer = null;\n await Promise.all(servers.map(server => server?.close().catch((e: unknown) => {\n this.log.warn(\"Error closing HTTP server\", {\n error: e,\n });\n })));\n this.log.info(\"Indexer stopped\");\n }\n\n private clearBlockQueue() {\n if (this.blockQueue) {\n this.blockQueue.clear();\n this.log.verbose(\"Starting, clearing block queue\");\n }\n }\n\n private async setupBlockListening() {\n const connected = await this.connect();\n if (!connected) {\n this.setStatus(\"FAILED\");\n throw new RPCError(\"Failed to connect to RPC\");\n }\n\n try {\n if (!this.config.usePolling && this.subscription) {\n this.subscription.removeListener(this.blockListener);\n this.subscription = null;\n this.log.verbose(\"Removed existing block listener and subscription\");\n }\n if (!this.config.usePolling) {\n this.subscription = this.client.subscribeNewBlock\n ? this.client.subscribeNewBlock()\n : null;\n this.blockListener = this.makeBlockListener(this.runGeneration);\n }\n const status: StatusResponse = await withTimeout(this.client.status(), RPC_TIMEOUT_MS, new RPCError(\"RPC status call timed out\"));\n this.assertChainId(status.nodeInfo.network);\n this.latestHeight = status.syncInfo.latestBlockHeight;\n this.log.info(\"Connected to \" + status.nodeInfo.network + \", current chain height: \" + this.latestHeight);\n\n this.heightToProcess = await this.config.getNextHeight();\n this.nextFetchHeight = this.heightToProcess;\n if (this.config.usePolling) {\n this.startPolling();\n }\n else {\n if (this.subscription) {\n this.subscription.addListener(this.blockListener);\n }\n else {\n this.prometheus?.recordError(\"rpc\");\n throw new Error(\"Could not subscribe to new blocks\");\n }\n }\n // A subscription that never delivers anything must still be noticed\n this.armIdleCheck();\n }\n catch (e) {\n this.log.error(\"Failed to set up block listening\", {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.setStatus(\"FAILED\");\n throw e;\n }\n }\n\n /**\n * Resolves once the indexer has stopped for good: stop() was called, endHeight was reached, or\n * a fatal-error was emitted. Restarts with backoff do not resolve it. Use it to keep a caller\n * waiting for the whole run rather than for the first loop exit.\n */\n public whenStopped(): Promise<void> {\n return this.stopped;\n }\n\n public async start() {\n if (!this.started) {\n // A fresh run (not a restart after backoff) gets a fresh completion promise\n this.stopped = new Promise<void>((resolve) => {\n this.resolveStopped = resolve;\n });\n }\n this.started = true;\n this.runGeneration++;\n const generation = this.runGeneration;\n this.tryToRecover = false;\n this.blockWaiters.clear();\n this.clearBlockQueue();\n await this.initialize();\n try {\n await this.setupBlockListening();\n this.log.debug(\"Starting main processing loop\");\n this.fetcher().catch((e) => {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.log.error(\"Error in fetching service\", {\n error: e,\n });\n this.requestRecovery(\"fetcher failed\", generation);\n });\n }\n catch (e) {\n this.requestRecovery(\"block listening setup failed: \" + e, generation);\n }\n\n let lastProcessed: number | undefined;\n while (this.started && !this.tryToRecover) {\n let txOpen = false;\n let failingHeight: number | undefined;\n try {\n this.prometheus?.updateRetryCount(this.retryCount);\n if (this.blockQueue.synced && this.blockQueue.size() <= 1) {\n // Only the sentinel is queued: we are at the chain tip. Waiting here is normal and can\n // last hours during a halt or an upgrade, so no transaction is held while we wait.\n this.setStatus(\"WAITING\");\n }\n let height: number;\n let timestamp: string;\n\n // Main block processing (minimal)\n if (this.isMinimal(this.blockQueue)) {\n const toProcess = await this.waitForBlockData(this.blockQueue.dequeue());\n this.log.silly(\"Retrieved block data\");\n if (!toProcess || !toProcess[0] || !toProcess[1]) {\n throw new RPCError(\"Could not fetch block(minimal)\");\n }\n height = toProcess[0].block.header.height;\n failingHeight = height;\n timestamp = toRfc3339WithNanoseconds(toProcess[0].block.header.time);\n // Index block inside a db transaction to ensure data consistency\n await this.config.beginTransaction();\n txOpen = true;\n this.log.silly(\"Started db tx\");\n await this.processBlock(toProcess[0],\n toProcess[1]);\n }\n // Main block processing (full)\n else {\n const toProcess = await this.waitForBlockData(this.blockQueue.dequeue());\n this.log.silly(\"Retrieved block data\");\n if (!toProcess || !toProcess[0] || !toProcess[1] || !toProcess[2]) {\n throw new RPCError(\"Could not fetch block(full)\");\n }\n\n this.log.silly(\"Decoded block\");\n height = toProcess[0].block.header.height;\n failingHeight = height;\n timestamp = toRfc3339WithNanoseconds(toProcess[0].block.header.time);\n await this.config.beginTransaction();\n txOpen = true;\n this.log.silly(\"Started db tx\");\n await this.processBlock(toProcess[0],\n toProcess[1],\n QueryValidatorsResponse.decode(toProcess[2]).validators);\n }\n\n // Emit events to trigger periodic operations every 50, 100 and 1000 blocks\n if (height % PERIODIC_INTERVALS.LARGE == 0) {\n await this.asyncEmit(\"periodic/large\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n if (height % PERIODIC_INTERVALS.MEDIUM == 0) {\n await this.asyncEmit(\"periodic/medium\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n if (height % PERIODIC_INTERVALS.SMALL == 0) {\n await this.asyncEmit(\"periodic/small\",\n {\n value: null,\n height,\n timestamp,\n });\n }\n this.log.silly(\"Handled periodic events\");\n\n await this.config.endTransaction(true);\n txOpen = false;\n lastProcessed = height;\n this.lastFailedHeight = undefined;\n this.sameHeightFailures = 0;\n\n this.log.silly(\"Committed db tx\");\n }\n catch (e) {\n if (txOpen) {\n try {\n await this.config.endTransaction(false);\n }\n catch (dbe) {\n this.prometheus?.recordError(\"database\");\n this.log.error(\"Error ending transaction\", {\n error: dbe,\n });\n }\n }\n if (!this.started) {\n // stop() woke us up; nothing failed\n break;\n }\n if (failingHeight !== undefined) {\n // The data was there and processing failed: count it against this block\n this.noteBlockFailure(failingHeight);\n }\n // any error here is likely recoverable (e.g. RPC timeout, DB error)\n this.prometheus?.recordError(\"block\");\n this.log.error(\"Block processing error\", {\n error: e,\n });\n this.setStatus(\"FAILED\");\n this.requestRecovery(\"block processing failed\", generation);\n break;\n }\n // Reset retry count and status on successful block processing\n this.retryCount = 0;\n this.setStatus(\"OK\");\n if (this.config.endHeight !== undefined && lastProcessed !== undefined && lastProcessed >= this.config.endHeight) {\n this.log.info(\"Reached configured end height \" + this.config.endHeight + \". Stopping indexer.\");\n await this.stop();\n return;\n }\n }\n\n // Normal exit from processing loop\n if (!this.started) {\n this.log.info(\"Indexer manually stopped.\");\n return;\n }\n\n // A block that keeps failing after its data was fetched is a bug or bad data, not an outage.\n // Give up loudly instead of retrying it forever.\n if (this.isStuck()) {\n const height = this.lastFailedHeight;\n this.log.error(\"Block \" + height + \" failed \" + this.sameHeightFailures + \" times in a row. This is a deterministic failure in a handler or the data, not an outage. Giving up.\");\n this.started = false;\n this.emit(\"fatal-error\", {\n error: new Error(\"Block \" + height + \" failed \" + this.sameHeightFailures + \" times in a row\"),\n message: \"Block processing is stuck\",\n retryCount: this.retryCount,\n height,\n });\n this.resolveStopped();\n return;\n }\n\n // Abnormal exit: restart with exponential backoff. Retries are unlimited unless maxRetries\n // is configured, because an RPC or database outage of any length must not kill the indexer.\n this.retryCount++;\n if (this.config.maxRetries !== undefined && this.retryCount > this.config.maxRetries) {\n this.log.error(\"Indexer failed \" + this.retryCount + \" times in a row, giving up (maxRetries=\" + this.config.maxRetries + \")\");\n this.started = false;\n this.emit(\"fatal-error\", {\n error: new Error(\"Max retry attempts exceeded\"),\n message: \"Indexer failed too many times\",\n retryCount: this.retryCount,\n });\n this.resolveStopped();\n return;\n }\n const delay = retryDelay(this.retryCount, RETRY_BASE_DELAY_MS, RETRY_MAX_DELAY_MS);\n this.log.warn(\"Indexer is restarting in \" + delay / 1000 + \" s (attempt \" + this.retryCount + \")\");\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.start().catch((e) => {\n this.log.error(\"Restart failed\", {\n error: e,\n });\n });\n }, delay);\n }\n\n /**\n * Emits an event and waits for its handlers. Handlers run one after another in registration\n * order: they share one database connection and one transaction, so interleaving them at\n * await points would let two handlers read and write the same rows in an unpredictable order,\n * and a failure in one would leave the others mid-flight while the block is rolled back. The\n * first rejection propagates and stops the remaining handlers.\n */\n public asyncEmit: EmitFunc<keyof WithHeightAndUUID<EventMap>> = async (\n type,\n event,\n ) => {\n const handlers = this.handlersFor(type);\n if (handlers.length === 0) {\n // Same routing as emit(): the _unhandled listener logs it at verbose level\n this.emit(type,\n event);\n return;\n }\n for (const handler of handlers) {\n await handler(event);\n }\n };\n\n private async processBlock(block: BlockResponse, block_results: BlockResultsResponse | BlockResultsResponse38, validators?: Validator[]) {\n const endTimer = this.prometheus?.timeBlockProcessing();\n const height = block.block.header.height;\n this.heightToProcess = height;\n this.log.debug(\"Processing block: %d\",\n height);\n // Initialize height & timestamp to be used for this block-processing run\n const timestamp = toRfc3339WithNanoseconds(block.block.header.time);\n\n // Use & await asyncEmit to ensure db insertions in order\n\n /*\n * Emit block information to any interested modules.\n * Primarily the required block module listens to this\n */\n await this.asyncEmit(\"block\",\n {\n value: {\n block,\n block_results,\n },\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled block event\");\n\n let beginBlockEvents: readonly Event[] | readonly Event38[];\n let endBlockEvents: readonly Event[] | readonly Event38[];\n if ((block_results as BlockResultsResponse38).finalizeBlockEvents) {\n // Cosmos SDK 0.50+ tags each finalize_block event with mode=BeginBlock / mode=EndBlock (baseapp.go)\n beginBlockEvents = (block_results as BlockResultsResponse38).finalizeBlockEvents.filter(x => hasBlockEventMode(x, \"BeginBlock\")) as readonly Event38[];\n endBlockEvents = (block_results as BlockResultsResponse38).finalizeBlockEvents.filter(x => hasBlockEventMode(x, \"EndBlock\")) as readonly Event38[];\n }\n else {\n beginBlockEvents = (block_results as BlockResultsResponse).beginBlockEvents;\n endBlockEvents = (block_results as BlockResultsResponse).endBlockEvents;\n }\n // Deal with begin_block events first\n await this.asyncEmit(\"begin_block\",\n {\n value: {\n events: beginBlockEvents!,\n validators,\n },\n height,\n timestamp,\n });\n\n this.log.silly(\"Modules handled begin_block events\");\n\n // Then individual tx_events\n await this.asyncEmit(\"tx_events\",\n {\n value: block_results.results,\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled tx events\");\n\n // Emit details and result for each tx msg separately\n for (let t = 0; t < block.block.txs.length; t++) {\n const tx = Tx.decode(block.block.txs[t]);\n\n const result = block_results.results[t].code;\n const txlog = block_results.results[t].log;\n\n if (result != 0) {\n // Tx failed. Ignore\n continue;\n }\n if (tx.body && tx.body.memo != \"\") {\n const txHash = createHash(\"sha256\").update(block.block.txs[t])\n .digest(\"hex\");\n await this.asyncEmit(\"tx_memo\",\n {\n value: {\n txHash,\n txBody: tx.body,\n },\n height,\n timestamp,\n });\n }\n // parsing log rather than using events directly in order to have msg_index available to filter appropriate events for each msg\n let events: Array<{\n msg_index?: number\n events: (Event | Event38)[]\n }> = [];\n if (txlog) {\n try {\n const parsed = JSON.parse(txlog);\n if (Array.isArray(parsed)) {\n events = parsed;\n }\n }\n catch (_e) {\n // Not every chain writes a JSON log; the msg_index attributes below cover those\n this.log.silly(\"Tx log is not JSON, using msg_index attributes instead\");\n }\n }\n if (events.length == 0) {\n const eventsToAdd: typeof events = [];\n this.log.silly(\"No events found in tx log. Parsing events for msg_index\");\n for (let m = 0; m < block_results.results[t].events.length; m++) {\n if (block_results.results[t].events[m].attributes.find(a => decodeAttr(a.key) == \"msg_index\")) {\n const mi = decodeAttr(block_results.results[t].events[m].attributes.find(a => decodeAttr(a.key) == \"msg_index\")?.value ?? \"\");\n if (mi != \"\") {\n const miNum = parseInt(mi);\n let ev = eventsToAdd.find(x => x.msg_index == miNum);\n if (!ev) {\n ev = {\n msg_index: miNum,\n events: [block_results.results[t].events[m]],\n };\n eventsToAdd.push(ev);\n }\n else {\n ev.events.push(block_results.results[t].events[m] as Event);\n }\n }\n }\n }\n events = events.concat(eventsToAdd);\n }\n const msgs = tx.body?.messages;\n\n if (msgs) {\n for (let i = 0; i < msgs.length; i++) {\n if (this.log.isSillyEnabled()) {\n this.log.silly(\"Indexer broadcasting msg for handling: \" + msgs[i].typeUrl);\n }\n const msgevents\n = msgs.length > 1\n ? events.find(x => x.msg_index == i)?.events\n : events[0]?.events ?? [];\n await this.asyncEmit(msgs[i].typeUrl as never,\n {\n value: {\n tx: msgs[i].value as never,\n events: msgevents,\n } as never,\n height,\n timestamp,\n });\n if (msgs[i].typeUrl == \"/cosmos.authz.v1beta1.MsgExec\") {\n const authzMsgs = MsgExec.decode(msgs[i].value).msgs;\n if (authzMsgs) {\n for (let r = 0; r < authzMsgs.length; r++) {\n if (this.log.isSillyEnabled()) {\n this.log.silly(\"Indexer broadcasting msg for handling: \" + authzMsgs[r].typeUrl);\n }\n const authzMsgEvents = msgevents?.reduce((events, evt) => {\n if (evt.attributes.filter(x => decodeAttr(x.key) == \"authz_msg_index\" && decodeAttr(x.value) == \"\" + r).length > 0) {\n events.push(evt);\n }\n return events;\n },\n [] as (Event | Event38)[]);\n await this.asyncEmit(authzMsgs[r].typeUrl as never,\n {\n value: {\n tx: authzMsgs[r].value as never,\n events: authzMsgEvents,\n } as never,\n height,\n timestamp,\n });\n }\n }\n }\n }\n }\n }\n this.log.silly(\"Modules handled msg events\");\n this.prometheus?.recordTransactions(block.block.txs.length);\n // Then deal with end_block events\n await this.asyncEmit(\"end_block\",\n {\n value: endBlockEvents!,\n height,\n timestamp,\n });\n this.log.silly(\"Modules handled end_block events\");\n\n endTimer?.();\n this.prometheus?.updateBlockMetrics(height, this.latestHeight, this.blockQueue.size());\n }\n\n /**\n * Fetches every height from nextFetchHeight up to latestHeight, waiting for queue space\n * before each fetch. Serves the initial catch-up and live blocks alike: a new announcement\n * only moves latestHeight and starts this loop if it is not already running, so bursts and\n * skipped announcements are handled by the same code and the queue can never overflow.\n */\n private async fetcher() {\n if (this.fetcherRunning && this.fetcherGeneration === this.runGeneration) {\n return;\n }\n const generation = this.runGeneration;\n const token = ++this.fetcherToken;\n this.fetcherRunning = true;\n this.fetcherGeneration = generation;\n try {\n while (\n this.nextFetchHeight <= this.latestHeight\n && (this.config.endHeight === undefined || this.nextFetchHeight <= this.config.endHeight)\n ) {\n // If some other async process triggers recovery, exit the fetching loop\n if (this.tryToRecover || !this.started || generation !== this.runGeneration) {\n this.log.verbose(\"Exiting fetcher loop. Attempting to recover indexer\");\n break;\n }\n const i = this.nextFetchHeight;\n this.log.debug(\"Fetching: \" + i);\n try {\n // Main fetching logic for minimal indexer\n if (this.isMinimal(this.blockQueue)) {\n // We do not await here so that multiple fetches can be in-flight\n const toIndex = withTimeout(Promise.all([this.blockClient.block(i) as Promise<BlockResponse>, this.blockClient.blockResults(i) as Promise<BlockResultsResponse>]), RPC_TIMEOUT_MS, new RPCError(\"Timed out fetching block \" + i)).catch((e) => {\n this.log.error(\"Error fetching block \" + i, {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"fetch failed for block \" + i, generation);\n return Promise.resolve([]);\n }) as Promise<[BlockResponse, BlockResultsResponse]>;\n this.blockQueue.enqueue(toIndex);\n }\n else {\n // Full indexer: block, block results and the complete validator set\n const toIndex = withTimeout(Promise.all([this.blockClient.block(i) as Promise<BlockResponse>, this.blockClient.blockResults(i) as Promise<BlockResultsResponse>, this.fetchValidatorSet(i)]), RPC_TIMEOUT_MS, new RPCError(\"Timed out fetching block \" + i)).catch((e) => {\n this.log.error(\"Error fetching block \" + i, {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"fetch failed for block \" + i, generation);\n return Promise.resolve([]);\n }) as Promise<[BlockResponse, BlockResultsResponse, Uint8Array]>;\n this.blockQueue.enqueue(toIndex);\n }\n }\n catch (e) {\n this.log.error(\"Fetching error\", {\n error: e,\n });\n break;\n }\n this.nextFetchHeight = i + 1;\n // Resolves immediately while the queue has room, otherwise when the processor dequeues\n await this.blockQueue.continue();\n }\n // Caught up with everything announced so far\n if (!this.tryToRecover && this.started && generation === this.runGeneration && !this.blockQueue.synced) {\n this.blockQueue.setSynced();\n this.log.info(\"Synced to latest height\");\n }\n }\n finally {\n if (this.fetcherToken === token) {\n this.fetcherRunning = false;\n }\n }\n }\n\n /**\n * Runs an ABCI query. A transport failure (RPC down, timeout, empty reply) requests a recovery.\n * A reply with a non-zero code is the chain answering \"no\" (pruned height, unknown path, bad\n * key): it is thrown as an RPCError with the code and log, and no recovery is requested for\n * ad-hoc queries, so modules can catch it. Block-pipeline queries reject into the fetcher,\n * which requests recovery itself.\n */\n public async callABCI(path: string, data: Uint8Array, height?: number, adHoc: boolean = true): Promise<Uint8Array> {\n let abciq;\n const endTimer = this.prometheus?.timeRpcCall(path) ?? void 0;\n try {\n abciq = await\n (adHoc\n ? this.client\n : this.blockClient).abciQuery({\n path,\n data,\n height: height,\n });\n }\n catch (e) {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"ABCI query failed for \" + path);\n throw new RPCError(\"RPC not responding. Query at: \" + path + \" (\" + e + \")\");\n }\n finally {\n endTimer?.();\n }\n if (!abciq) {\n this.setStatus(\"FAILED\");\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"empty ABCI response for \" + path);\n throw new RPCError(\"RPC not responding. Query at: \" + path);\n }\n if (abciq.code) {\n // Previously an error reply decoded as an empty result (for example zero validators)\n this.prometheus?.recordError(\"rpc\");\n throw new RPCError(\"ABCI query \" + path + \" failed with code \" + abciq.code + (abciq.log ? \": \" + abciq.log : \"\"));\n }\n return abciq.value;\n }\n\n /**\n * Fetches the complete validator set at a height, following pagination, and returns it\n * re-encoded as a single QueryValidatorsResponse so the block queue payload keeps its shape.\n * Chains with more validators than one page (1000) were silently truncated before.\n */\n private async fetchValidatorSet(height: number): Promise<Uint8Array> {\n const validators: Validator[] = [];\n let key: Uint8Array | undefined;\n do {\n const request = QueryValidatorsRequest.fromPartial({\n pagination: key\n ? {\n limit: PAGINATION_LIMITS.VALIDATORS,\n key,\n }\n : {\n limit: PAGINATION_LIMITS.VALIDATORS,\n },\n });\n const page = QueryValidatorsResponse.decode(\n await this.callABCI(\"/cosmos.staking.v1beta1.Query/Validators\", QueryValidatorsRequest.encode(request).finish(), height, false),\n );\n validators.push(...page.validators);\n key = page.pagination?.nextKey && page.pagination.nextKey.length > 0 ? page.pagination.nextKey : undefined;\n } while (key);\n return QueryValidatorsResponse.encode(QueryValidatorsResponse.fromPartial({\n validators,\n })).finish();\n }\n\n private newBlockReceived(height: number): void {\n this.armIdleCheck();\n this.log.info(\"Received new block: %d\",\n height);\n if (height <= this.latestHeight) {\n // Re-announced, or from a lagging node behind a load balancer: never move backwards\n return;\n }\n this.latestHeight = height;\n if (this.tryToRecover || !this.started) {\n return;\n }\n // The fetcher requests every height up to latestHeight and waits for queue space as it\n // goes, so a burst of blocks or an announcement that skipped heights is handled exactly\n // like the initial catch-up. Nothing to do if it is already running.\n this.fetcher().catch((e) => {\n this.log.error(\"Error in fetching service\", {\n error: e,\n });\n this.requestRecovery(\"fetcher failed\");\n });\n }\n\n /** Starts a single polling chain, retiring any chain left over from a previous run */\n private startPolling(): void {\n this.stopPolling();\n const generation = ++this.pollGeneration;\n this.pollForBlock(generation);\n }\n\n private stopPolling(): void {\n this.pollGeneration++;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n }\n\n private async pollForBlock(generation: number) {\n if (!this.started || generation !== this.pollGeneration) {\n return;\n }\n try {\n const status = await this.client.status();\n // A restart or stop may have happened while waiting on the RPC\n if (!this.started || generation !== this.pollGeneration) {\n return;\n }\n if (status.syncInfo.latestBlockHeight > this.latestHeight) {\n this.newBlockReceived(status.syncInfo.latestBlockHeight);\n }\n }\n catch (e) {\n this.log.error(\"Error polling for new block\", {\n error: e,\n });\n this.prometheus?.recordError(\"rpc\");\n this.requestRecovery(\"polling failed\");\n // Recovery restarts polling from setupBlockListening\n return;\n }\n this.pollTimer = setTimeout(() => {\n this.pollForBlock(generation);\n },\n this.config.pollingInterval);\n }\n\n private readGenesis(): fs.ReadStream {\n if (this.config.genesisPath) {\n return fs.createReadStream(this.config.genesisPath);\n }\n else {\n throw new Error(\"Genesis path not set\");\n }\n }\n\n private async setArrayReader(path: string, processor: (chunk: unknown) => Promise<void>): Promise<boolean> {\n const readPromise = new Promise<boolean>((resolve, reject) => {\n try {\n const filters = path.split(\".\");\n const pickers = filters.map(filter => pick({\n filter,\n }));\n let counter = 0;\n let chunkCounter = 0;\n\n // Wrapper processor that handles transaction chunking\n const chunkProcessor = async (data: unknown) => {\n chunkCounter++;\n this.log.debug(`Processing genesis chunk ${chunkCounter}`);\n\n await processor(data);\n\n // Commit and restart transaction every 5 chunks (5000 entries)\n // This prevents timeout on large genesis files\n if (chunkCounter % 5 === 0) {\n this.log.debug(`Committing transaction after chunk ${chunkCounter}`);\n await this.config.endTransaction(true);\n await this.config.beginTransaction();\n }\n // Pass the chunk on so the \"data\" listener below can count what was processed\n return data;\n };\n\n chain([\n this.readGenesis(),\n parser(),\n ...pickers,\n streamArray(),\n batch({\n batchSize: GENESIS_BATCH_SIZE,\n }),\n chunkProcessor,\n ])\n .on(\"data\",\n (data) => {\n if (data && Array.isArray(data)) {\n counter = counter + data.length;\n }\n })\n .on(\"end\",\n () => {\n this.log.info(`Processed ${counter} entries in ${chunkCounter} chunks`);\n resolve(true);\n })\n // stream-chain re-emits parser and processor errors here; without a listener Node\n // raises them as an uncaught exception and parseGenesis never rolls back\n .on(\"error\",\n (e) => {\n this.log.error(\"Error reading genesis array \" + path, {\n error: e,\n });\n reject(e);\n });\n }\n catch (e) {\n this.log.verbose(\"Error in setArrayReader: \" + e);\n reject(e);\n }\n });\n\n return readPromise;\n }\n\n private async setValueReader(path: string, processor: (chunk: unknown) => Promise<void>): Promise<boolean> {\n const readPromise = new Promise<boolean>((resolve, reject) => {\n try {\n const filters = path.split(\".\");\n const pickers = filters.map(filter => pick({\n filter,\n }));\n\n let counter = 0;\n chain([this.readGenesis(), parser(), ...pickers, streamValues(), processor])\n .on(\"data\",\n (_data) => {\n counter++;\n })\n .on(\"end\",\n () => {\n this.log.info(`Processed ${counter} entries`);\n resolve(true);\n })\n .on(\"error\",\n (e) => {\n this.log.error(\"Error reading genesis value \" + path, {\n error: e,\n });\n reject(e);\n });\n }\n catch (e) {\n this.log.verbose(\"Error in setValueReader: \" + e);\n reject(e);\n }\n });\n\n return readPromise;\n }\n\n private async parseGenesis() {\n this.log.info(\"Parsing genesis\");\n // Lets the storage layer mark the import as in progress before anything is written\n await this.config.onGenesisStart?.();\n await this.config.beginTransaction();\n try {\n this.log.info(\"Starting genesis import\");\n this.log.debug(\"Importing genesis file...\");\n\n for (const [key, _value] of this.handled) {\n if (key.startsWith(\"genesis/\")) {\n const genesisEntry = key.split(\"/\");\n\n this.log.verbose(\"Importing \" + key + \"...\");\n if (genesisEntry[1] == \"array\") {\n await this.setArrayReader(genesisEntry[2],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n await this.asyncEmit(key as never,\n {\n value: data.map((x: {\n value: never\n }) => x.value),\n } as never);\n return data;\n });\n }\n else {\n await this.setValueReader(genesisEntry[2],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n await this.asyncEmit(key as never,\n {\n value: data.value,\n } as never);\n return data;\n });\n }\n }\n }\n\n this.log.info(\"Importing gen TXs...\");\n\n await this.setArrayReader(\"app_state.genutil.gen_txs\",\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async (data: any) => {\n for (let j = 0; j < data.length; j++) {\n const gentx = data[j].value;\n for (let i = 0; i < gentx.body.messages.length; i++) {\n const msg = gentx.body.messages[i];\n await this.asyncEmit((\"gentx\" + msg[\"@type\"]) as never,\n {\n value: msg,\n } as never);\n }\n }\n return data;\n });\n // Recorded inside the last transaction, so \"complete\" commits together with the final chunk\n await this.config.onGenesisComplete?.();\n await this.config.endTransaction(true);\n\n this.log.info(\"Finished importing\");\n }\n catch (e) {\n try {\n await this.config.endTransaction(false);\n }\n catch (dbe) {\n this.prometheus?.recordError(\"database\");\n this.log.error(\"Error ending transaction\", {\n error: dbe,\n });\n }\n this.log.error(\"Failed to import genesis\");\n throw e;\n }\n }\n}\n\n/** @deprecated Misspelling kept for compatibility, use EclesiaIndexer. Removed in 3.0. */\nexport const EcleciaIndexer = EclesiaIndexer;\n/** @deprecated Misspelling kept for compatibility, use EclesiaIndexer. Removed in 3.0. */\nexport type EcleciaIndexer = EclesiaIndexer;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eclesia/indexer-engine",
3
- "version": "2.16.0",
3
+ "version": "2.16.2",
4
4
  "description": "Core eclesia indexer engine",
5
5
  "files": [
6
6
  "dist/"