ponder 0.9.2 → 0.9.4-debug.1

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.
Files changed (58) hide show
  1. package/dist/bin/ponder.js +2470 -3762
  2. package/dist/bin/ponder.js.map +1 -1
  3. package/dist/chunk-6AOFLZJ4.js +1692 -0
  4. package/dist/chunk-6AOFLZJ4.js.map +1 -0
  5. package/dist/chunk-DZFRP3KH.js +70 -0
  6. package/dist/chunk-DZFRP3KH.js.map +1 -0
  7. package/dist/{chunk-IFTUFVCL.js → chunk-MJKRYIBO.js} +3 -73
  8. package/dist/chunk-MJKRYIBO.js.map +1 -0
  9. package/dist/db-in86nyw7.d.ts +625 -0
  10. package/dist/experimental_unsafe_stores.d.ts +375 -0
  11. package/dist/experimental_unsafe_stores.js +11 -0
  12. package/dist/experimental_unsafe_stores.js.map +1 -0
  13. package/dist/index.d.ts +17 -429
  14. package/dist/index.js +4 -2
  15. package/dist/index.js.map +1 -1
  16. package/package.json +5 -1
  17. package/src/bin/commands/codegen.ts +8 -10
  18. package/src/bin/commands/dev.ts +30 -42
  19. package/src/bin/commands/list.ts +9 -14
  20. package/src/bin/commands/serve.ts +26 -39
  21. package/src/bin/commands/start.ts +29 -42
  22. package/src/bin/utils/{shutdown.ts → exit.ts} +23 -37
  23. package/src/bin/utils/run.ts +275 -175
  24. package/src/bin/utils/runServer.ts +1 -5
  25. package/src/build/configAndIndexingFunctions.ts +547 -512
  26. package/src/build/index.ts +5 -8
  27. package/src/build/pre.ts +3 -0
  28. package/src/config/index.ts +9 -6
  29. package/src/database/index.ts +72 -72
  30. package/src/drizzle/kit/index.ts +3 -3
  31. package/src/experimental_unsafe_stores.ts +4 -0
  32. package/src/indexing/index.ts +0 -4
  33. package/src/indexing/service.ts +31 -93
  34. package/src/indexing-store/historical.ts +2 -4
  35. package/src/internal/common.ts +2 -0
  36. package/src/internal/errors.ts +9 -9
  37. package/src/internal/logger.ts +1 -1
  38. package/src/internal/metrics.ts +75 -103
  39. package/src/internal/shutdown.ts +25 -0
  40. package/src/internal/telemetry.ts +16 -18
  41. package/src/internal/types.ts +9 -1
  42. package/src/server/index.ts +3 -5
  43. package/src/sync/events.ts +4 -4
  44. package/src/sync/filter.ts +1 -0
  45. package/src/sync/index.ts +1046 -805
  46. package/src/sync-historical/index.ts +0 -37
  47. package/src/sync-realtime/index.ts +48 -48
  48. package/src/sync-store/encoding.ts +5 -5
  49. package/src/sync-store/index.ts +5 -23
  50. package/src/ui/index.ts +2 -11
  51. package/src/utils/checkpoint.ts +17 -3
  52. package/src/utils/chunk.ts +7 -0
  53. package/src/utils/generators.ts +66 -0
  54. package/src/utils/mutex.ts +34 -0
  55. package/src/utils/partition.ts +41 -0
  56. package/src/utils/requestQueue.ts +19 -10
  57. package/src/utils/zipper.ts +80 -0
  58. package/dist/chunk-IFTUFVCL.js.map +0 -1
@@ -1,5 +1,6 @@
1
1
  import type { IndexingStore } from "@/indexing-store/index.js";
2
2
  import type { Common } from "@/internal/common.js";
3
+ import { ShutdownError } from "@/internal/errors.js";
3
4
  import type {
4
5
  ContractSource,
5
6
  Event,
@@ -10,23 +11,25 @@ import type {
10
11
  SetupEvent,
11
12
  Source,
12
13
  } from "@/internal/types.js";
14
+ import type { SyncStore } from "@/sync-store/index.js";
13
15
  import { isAddressFactory } from "@/sync/filter.js";
14
- import type { Sync } from "@/sync/index.js";
16
+ import { cachedTransport } from "@/sync/transport.js";
15
17
  import type { Db } from "@/types/db.js";
16
18
  import type { Block, Log, Trace, Transaction } from "@/types/eth.js";
17
19
  import type { DeepPartial } from "@/types/utils.js";
18
20
  import {
19
- type Checkpoint,
21
+ ZERO_CHECKPOINT,
20
22
  decodeCheckpoint,
21
23
  encodeCheckpoint,
22
- zeroCheckpoint,
23
24
  } from "@/utils/checkpoint.js";
24
25
  import { prettyPrint } from "@/utils/print.js";
26
+ import type { RequestQueue } from "@/utils/requestQueue.js";
25
27
  import { startClock } from "@/utils/timer.js";
26
- import type { Abi, Address } from "viem";
27
- import { checksumAddress, createClient } from "viem";
28
+ import { type Abi, type Address, createClient } from "viem";
29
+ import { checksumAddress } from "viem";
28
30
  import { addStackTrace } from "./addStackTrace.js";
29
- import { type ReadOnlyClient, getPonderActions } from "./ponderActions.js";
31
+ import type { ReadOnlyClient } from "./ponderActions.js";
32
+ import { getPonderActions } from "./ponderActions.js";
30
33
 
31
34
  export type Context = {
32
35
  network: { chainId: number; name: string };
@@ -49,12 +52,9 @@ export type Service = {
49
52
  indexingFunctions: IndexingFunctions;
50
53
 
51
54
  // state
52
- isKilled: boolean;
53
-
54
55
  eventCount: {
55
56
  [eventName: string]: number;
56
57
  };
57
- startCheckpoint: Checkpoint;
58
58
 
59
59
  /**
60
60
  * Reduce memory usage by reserving space for objects ahead of time
@@ -76,14 +76,16 @@ export type Service = {
76
76
  export const create = ({
77
77
  common,
78
78
  indexingBuild: { sources, networks, indexingFunctions },
79
- sync,
79
+ requestQueues,
80
+ syncStore,
80
81
  }: {
81
82
  common: Common;
82
83
  indexingBuild: Pick<
83
84
  IndexingBuild,
84
85
  "sources" | "networks" | "indexingFunctions"
85
86
  >;
86
- sync: Sync;
87
+ requestQueues: RequestQueue[];
88
+ syncStore: SyncStore;
87
89
  }): Service => {
88
90
  const contextState: Service["currentEvent"]["contextState"] = {
89
91
  blockNumber: undefined!,
@@ -139,10 +141,12 @@ export const create = ({
139
141
  }
140
142
 
141
143
  // build clientByChainId
142
- for (const network of networks) {
143
- const transport = sync.getCachedTransport(network);
144
+ for (let i = 0; i < networks.length; i++) {
145
+ const network = networks[i]!;
146
+ const requestQueue = requestQueues[i]!;
147
+
144
148
  clientByChainId[network.chainId] = createClient({
145
- transport,
149
+ transport: cachedTransport({ requestQueue, syncStore }),
146
150
  chain: network.chain,
147
151
  // @ts-ignore
148
152
  }).extend(getPonderActions(contextState));
@@ -157,9 +161,7 @@ export const create = ({
157
161
  return {
158
162
  common,
159
163
  indexingFunctions,
160
- isKilled: false,
161
164
  eventCount,
162
- startCheckpoint: decodeCheckpoint(sync.getStartCheckpoint()),
163
165
  currentEvent: {
164
166
  contextState,
165
167
  context: {
@@ -184,11 +186,7 @@ export const processSetupEvents = async (
184
186
  sources: Source[];
185
187
  networks: Network[];
186
188
  },
187
- ): Promise<
188
- | { status: "error"; error: Error }
189
- | { status: "success" }
190
- | { status: "killed" }
191
- > => {
189
+ ): Promise<{ status: "error"; error: Error } | { status: "success" }> => {
192
190
  for (const eventName of Object.keys(indexingService.indexingFunctions)) {
193
191
  if (!eventName.endsWith(":setup")) continue;
194
192
 
@@ -204,8 +202,6 @@ export const processSetupEvents = async (
204
202
 
205
203
  if (source === undefined) continue;
206
204
 
207
- if (indexingService.isKilled) return { status: "killed" };
208
-
209
205
  indexingService.eventCount[eventName]!++;
210
206
 
211
207
  const result = await executeSetup(indexingService, {
@@ -213,7 +209,7 @@ export const processSetupEvents = async (
213
209
  type: "setup",
214
210
  chainId: network.chainId,
215
211
  checkpoint: encodeCheckpoint({
216
- ...zeroCheckpoint,
212
+ ...ZERO_CHECKPOINT,
217
213
  chainId: BigInt(network.chainId),
218
214
  blockNumber: BigInt(source.filter.fromBlock ?? 0),
219
215
  }),
@@ -236,14 +232,8 @@ export const processSetupEvents = async (
236
232
  export const processEvents = async (
237
233
  indexingService: Service,
238
234
  { events }: { events: Event[] },
239
- ): Promise<
240
- | { status: "error"; error: Error }
241
- | { status: "success" }
242
- | { status: "killed" }
243
- > => {
235
+ ): Promise<{ status: "error"; error: Error } | { status: "success" }> => {
244
236
  for (let i = 0; i < events.length; i++) {
245
- if (indexingService.isKilled) return { status: "killed" };
246
-
247
237
  const event = events[i]!;
248
238
 
249
239
  indexingService.eventCount[event.name]!++;
@@ -262,40 +252,8 @@ export const processEvents = async (
262
252
  service: "indexing",
263
253
  msg: `Completed indexing function (event="${event.name}", checkpoint=${event.checkpoint})`,
264
254
  });
265
-
266
- // periodically update metrics
267
- if (i % 93 === 0) {
268
- updateCompletedEvents(indexingService);
269
-
270
- const eventTimestamp = decodeCheckpoint(event.checkpoint).blockTimestamp;
271
-
272
- indexingService.common.metrics.ponder_indexing_completed_seconds.set(
273
- eventTimestamp - indexingService.startCheckpoint.blockTimestamp,
274
- );
275
- indexingService.common.metrics.ponder_indexing_completed_timestamp.set(
276
- eventTimestamp,
277
- );
278
-
279
- // Note: allows for terminal and logs to be updated
280
- await new Promise(setImmediate);
281
- }
282
255
  }
283
256
 
284
- // set completed seconds
285
- if (events.length > 0) {
286
- const lastEventInBatchTimestamp = decodeCheckpoint(
287
- events[events.length - 1]!.checkpoint,
288
- ).blockTimestamp;
289
-
290
- indexingService.common.metrics.ponder_indexing_completed_seconds.set(
291
- lastEventInBatchTimestamp -
292
- indexingService.startCheckpoint.blockTimestamp,
293
- );
294
- indexingService.common.metrics.ponder_indexing_completed_timestamp.set(
295
- lastEventInBatchTimestamp,
296
- );
297
- }
298
- // set completed events
299
257
  updateCompletedEvents(indexingService);
300
258
 
301
259
  return { status: "success" };
@@ -314,24 +272,6 @@ export const setIndexingStore = (
314
272
  };
315
273
  };
316
274
 
317
- export const kill = (indexingService: Service) => {
318
- indexingService.common.logger.debug({
319
- service: "indexing",
320
- msg: "Killed indexing service",
321
- });
322
- indexingService.isKilled = true;
323
- };
324
-
325
- export const updateTotalSeconds = (
326
- indexingService: Service,
327
- endCheckpoint: Checkpoint,
328
- ) => {
329
- indexingService.common.metrics.ponder_indexing_total_seconds.set(
330
- endCheckpoint.blockTimestamp -
331
- indexingService.startCheckpoint.blockTimestamp,
332
- );
333
- };
334
-
335
275
  const updateCompletedEvents = (indexingService: Service) => {
336
276
  for (const event of Object.keys(indexingService.eventCount)) {
337
277
  const metricLabel = {
@@ -347,11 +287,7 @@ const updateCompletedEvents = (indexingService: Service) => {
347
287
  const executeSetup = async (
348
288
  indexingService: Service,
349
289
  { event }: { event: SetupEvent },
350
- ): Promise<
351
- | { status: "error"; error: Error }
352
- | { status: "success" }
353
- | { status: "killed" }
354
- > => {
290
+ ): Promise<{ status: "error"; error: Error } | { status: "success" }> => {
355
291
  const {
356
292
  common,
357
293
  indexingFunctions,
@@ -382,9 +318,12 @@ const executeSetup = async (
382
318
  endClock(),
383
319
  );
384
320
  } catch (_error) {
385
- if (indexingService.isKilled) return { status: "killed" };
386
321
  const error = _error instanceof Error ? _error : new Error(String(_error));
387
322
 
323
+ if (common.shutdown.isKilled) {
324
+ throw new ShutdownError();
325
+ }
326
+
388
327
  addStackTrace(error, common.options);
389
328
  addErrorMeta(error, toErrorMeta(event));
390
329
 
@@ -406,11 +345,7 @@ const executeSetup = async (
406
345
  const executeEvent = async (
407
346
  indexingService: Service,
408
347
  { event }: { event: Event },
409
- ): Promise<
410
- | { status: "error"; error: Error }
411
- | { status: "success" }
412
- | { status: "killed" }
413
- > => {
348
+ ): Promise<{ status: "error"; error: Error } | { status: "success" }> => {
414
349
  const {
415
350
  common,
416
351
  indexingFunctions,
@@ -442,9 +377,12 @@ const executeEvent = async (
442
377
  endClock(),
443
378
  );
444
379
  } catch (_error) {
445
- if (indexingService.isKilled) return { status: "killed" };
446
380
  const error = _error instanceof Error ? _error : new Error(String(_error));
447
381
 
382
+ if (common.shutdown.isKilled) {
383
+ throw new ShutdownError();
384
+ }
385
+
448
386
  addStackTrace(error, common.options);
449
387
  addErrorMeta(error, toErrorMeta(event));
450
388
 
@@ -13,7 +13,6 @@ import {
13
13
  UniqueConstraintError,
14
14
  } from "@/internal/errors.js";
15
15
  import type { SchemaBuild } from "@/internal/types.js";
16
- import { encodeCheckpoint, zeroCheckpoint } from "@/utils/checkpoint.js";
17
16
  import { prettyPrint } from "@/utils/print.js";
18
17
  import { createQueue } from "@ponder/common";
19
18
  import {
@@ -146,12 +145,12 @@ export const createHistoricalIndexingStore = ({
146
145
  common,
147
146
  schemaBuild: { schema },
148
147
  database,
149
- initialCheckpoint,
148
+ isDatabaseEmpty,
150
149
  }: {
151
150
  common: Common;
152
151
  schemaBuild: Pick<SchemaBuild, "schema">;
153
152
  database: Database;
154
- initialCheckpoint: string;
153
+ isDatabaseEmpty: boolean;
155
154
  }): IndexingStore<"historical"> => {
156
155
  // Operation queue to make sure all queries are run in order, circumventing race conditions
157
156
  const queue = createQueue<unknown, () => Promise<unknown>>({
@@ -302,7 +301,6 @@ export const createHistoricalIndexingStore = ({
302
301
  return size;
303
302
  };
304
303
 
305
- let isDatabaseEmpty = initialCheckpoint === encodeCheckpoint(zeroCheckpoint);
306
304
  /** Estimated number of bytes used by cache. */
307
305
  let cacheBytes = 0;
308
306
  /** LRU counter. */
@@ -1,6 +1,7 @@
1
1
  import type { Logger } from "./logger.js";
2
2
  import type { MetricsService } from "./metrics.js";
3
3
  import type { Options } from "./options.js";
4
+ import type { Shutdown } from "./shutdown.js";
4
5
  import type { Telemetry } from "./telemetry.js";
5
6
 
6
7
  export type Common = {
@@ -8,4 +9,5 @@ export type Common = {
8
9
  logger: Logger;
9
10
  metrics: MetricsService;
10
11
  telemetry: Telemetry;
12
+ shutdown: Shutdown;
11
13
  };
@@ -35,15 +35,6 @@ export class NonRetryableError extends BaseError {
35
35
  }
36
36
  }
37
37
 
38
- export class IgnorableError extends BaseError {
39
- override name = "IgnorableError";
40
-
41
- constructor(message?: string | undefined) {
42
- super(message);
43
- Object.setPrototypeOf(this, IgnorableError.prototype);
44
- }
45
- }
46
-
47
38
  // Indexing store errors
48
39
 
49
40
  export class StoreError extends NonRetryableError {
@@ -126,3 +117,12 @@ export class FlushError extends NonRetryableError {
126
117
  Object.setPrototypeOf(this, FlushError.prototype);
127
118
  }
128
119
  }
120
+
121
+ export class ShutdownError extends NonRetryableError {
122
+ override name = "ShutdownError";
123
+
124
+ constructor(message?: string | undefined) {
125
+ super(message);
126
+ Object.setPrototypeOf(this, ShutdownError.prototype);
127
+ }
128
+ }
@@ -72,7 +72,7 @@ export function createLogger({
72
72
  trace(options: Omit<Log, "level" | "time">) {
73
73
  logger.trace(options);
74
74
  },
75
- async kill() {},
75
+ flush: () => new Promise((resolve) => logger.flush(resolve)),
76
76
  };
77
77
  }
78
78
 
@@ -17,14 +17,17 @@ const httpRequestSizeBytes = [
17
17
 
18
18
  export class MetricsService {
19
19
  registry: prometheus.Registry;
20
+ start_timestamp: number;
21
+ rps: { [network: string]: { count: number; timestamp: number }[] };
20
22
 
21
- ponder_indexing_total_seconds: prometheus.Gauge;
22
- ponder_indexing_completed_seconds: prometheus.Gauge;
23
- ponder_indexing_completed_events: prometheus.Gauge<"event">;
23
+ ponder_historical_total_indexing_seconds: prometheus.Gauge<"network">;
24
+ ponder_historical_cached_indexing_seconds: prometheus.Gauge<"network">;
25
+ ponder_historical_completed_indexing_seconds: prometheus.Gauge<"network">;
24
26
 
25
- ponder_indexing_completed_timestamp: prometheus.Gauge;
26
- ponder_indexing_has_error: prometheus.Gauge;
27
+ ponder_indexing_timestamp: prometheus.Gauge<"network">;
28
+ ponder_indexing_has_error: prometheus.Gauge<"network">;
27
29
 
30
+ ponder_indexing_completed_events: prometheus.Gauge<"event">;
28
31
  ponder_indexing_function_duration: prometheus.Histogram<"event">;
29
32
  ponder_indexing_abi_decoding_duration: prometheus.Histogram;
30
33
 
@@ -64,15 +67,25 @@ export class MetricsService {
64
67
 
65
68
  constructor() {
66
69
  this.registry = new prometheus.Registry();
70
+ this.start_timestamp = Date.now();
71
+ this.rps = {};
67
72
 
68
- this.ponder_indexing_total_seconds = new prometheus.Gauge({
69
- name: "ponder_indexing_total_seconds",
73
+ this.ponder_historical_total_indexing_seconds = new prometheus.Gauge({
74
+ name: "ponder_historical_total_indexing_seconds",
70
75
  help: "Total number of seconds that are required",
76
+ labelNames: ["network"] as const,
77
+ registers: [this.registry],
78
+ });
79
+ this.ponder_historical_cached_indexing_seconds = new prometheus.Gauge({
80
+ name: "ponder_historical_cached_indexing_seconds",
81
+ help: "Number of seconds that have been cached",
82
+ labelNames: ["network"] as const,
71
83
  registers: [this.registry],
72
84
  });
73
- this.ponder_indexing_completed_seconds = new prometheus.Gauge({
74
- name: "ponder_indexing_completed_seconds",
85
+ this.ponder_historical_completed_indexing_seconds = new prometheus.Gauge({
86
+ name: "ponder_historical_completed_indexing_seconds",
75
87
  help: "Number of seconds that have been completed",
88
+ labelNames: ["network"] as const,
76
89
  registers: [this.registry],
77
90
  });
78
91
  this.ponder_indexing_completed_events = new prometheus.Gauge({
@@ -81,9 +94,10 @@ export class MetricsService {
81
94
  labelNames: ["network", "event"] as const,
82
95
  registers: [this.registry],
83
96
  });
84
- this.ponder_indexing_completed_timestamp = new prometheus.Gauge({
85
- name: "ponder_indexing_completed_timestamp",
97
+ this.ponder_indexing_timestamp = new prometheus.Gauge({
98
+ name: "ponder_indexing_timestamp",
86
99
  help: "Timestamp through which all events have been completed",
100
+ labelNames: ["network"] as const,
87
101
  registers: [this.registry],
88
102
  });
89
103
  this.ponder_indexing_has_error = new prometheus.Gauge({
@@ -245,10 +259,14 @@ export class MetricsService {
245
259
  }
246
260
 
247
261
  resetIndexingMetrics() {
248
- this.ponder_indexing_total_seconds.reset();
249
- this.ponder_indexing_completed_seconds.reset();
262
+ this.start_timestamp = Date.now();
263
+ this.rps = {};
264
+
265
+ this.ponder_historical_total_indexing_seconds.reset();
266
+ this.ponder_historical_cached_indexing_seconds.reset();
267
+ this.ponder_historical_completed_indexing_seconds.reset();
250
268
  this.ponder_indexing_completed_events.reset();
251
- this.ponder_indexing_completed_timestamp.reset();
269
+ this.ponder_indexing_timestamp.reset();
252
270
  this.ponder_indexing_has_error.reset();
253
271
  this.ponder_indexing_function_duration.reset();
254
272
  this.ponder_indexing_abi_decoding_duration.reset();
@@ -284,8 +302,6 @@ export class MetricsService {
284
302
  }
285
303
  }
286
304
 
287
- const rps: { [network: string]: { count: number; timestamp: number }[] } = {};
288
-
289
305
  export async function getSyncProgress(metrics: MetricsService): Promise<
290
306
  {
291
307
  networkName: string;
@@ -338,14 +354,14 @@ export async function getSyncProgress(metrics: MetricsService): Promise<
338
354
  }
339
355
 
340
356
  for (const [networkName, count] of Object.entries(requestCount)) {
341
- if (rps[networkName] === undefined) {
342
- rps[networkName] = [{ count, timestamp: Date.now() }];
357
+ if (metrics.rps[networkName] === undefined) {
358
+ metrics.rps[networkName] = [{ count, timestamp: Date.now() }];
343
359
  } else {
344
- rps[networkName]!.push({ count, timestamp: Date.now() });
360
+ metrics.rps[networkName]!.push({ count, timestamp: Date.now() });
345
361
  }
346
362
 
347
- if (rps[networkName]!.length > 100) {
348
- rps[networkName]!.shift();
363
+ if (metrics.rps[networkName]!.length > 100) {
364
+ metrics.rps[networkName]!.shift();
349
365
  }
350
366
  }
351
367
 
@@ -365,9 +381,9 @@ export async function getSyncProgress(metrics: MetricsService): Promise<
365
381
  // The ETA is low quality if we've completed only one or two blocks.
366
382
  const eta = completedBlocks >= 3 ? total - elapsed : undefined;
367
383
 
368
- const _length = rps[labels.network!]!.length;
369
- const _firstRps = rps[labels.network!]![0]!;
370
- const _lastRps = rps[labels.network!]![_length - 1]!;
384
+ const _length = metrics.rps[labels.network!]!.length;
385
+ const _firstRps = metrics.rps[labels.network!]![0]!;
386
+ const _lastRps = metrics.rps[labels.network!]![_length - 1]!;
371
387
 
372
388
  const requests = _lastRps.count - (_length > 1 ? _firstRps.count : 0);
373
389
  const seconds =
@@ -389,16 +405,33 @@ export async function getIndexingProgress(metrics: MetricsService) {
389
405
  .values[0]?.value;
390
406
  const hasError = hasErrorMetric === 1;
391
407
 
392
- const totalSeconds =
393
- (await metrics.ponder_indexing_total_seconds.get()).values[0]?.value ?? 0;
408
+ const sum = (x: number[]) => x.reduce((a, b) => a + b, 0);
409
+ const max = (x: number[]) => x.reduce((a, b) => Math.max(a, b), 0);
410
+
411
+ const totalSeconds = await metrics.ponder_historical_total_indexing_seconds
412
+ .get()
413
+ .then(({ values }) => values.map(({ value }) => value))
414
+ .then(sum);
415
+ const cachedSeconds = await metrics.ponder_historical_cached_indexing_seconds
416
+ .get()
417
+ .then(({ values }) => values.map(({ value }) => value))
418
+ .then(sum);
394
419
  const completedSeconds =
395
- (await metrics.ponder_indexing_completed_seconds.get()).values[0]?.value ??
396
- 0;
397
- const completedToTimestamp =
398
- (await metrics.ponder_indexing_completed_timestamp.get()).values[0]!
399
- .value ?? 0;
420
+ await metrics.ponder_historical_completed_indexing_seconds
421
+ .get()
422
+ .then(({ values }) => values.map(({ value }) => value))
423
+ .then(sum);
424
+ const timestamp = await metrics.ponder_indexing_timestamp
425
+ .get()
426
+ .then(({ values }) => values.map(({ value }) => value))
427
+ .then(max);
400
428
 
401
- const progress = totalSeconds === 0 ? 0 : completedSeconds / totalSeconds;
429
+ const progress =
430
+ timestamp === 0
431
+ ? 0
432
+ : totalSeconds === 0
433
+ ? 1
434
+ : (completedSeconds + cachedSeconds) / totalSeconds;
402
435
 
403
436
  const indexingCompletedEventsMetric = (
404
437
  await metrics.ponder_indexing_completed_events.get()
@@ -433,10 +466,10 @@ export async function getIndexingProgress(metrics: MetricsService) {
433
466
  return {
434
467
  hasError,
435
468
  overall: {
436
- completedSeconds,
437
469
  totalSeconds,
470
+ cachedSeconds,
471
+ completedSeconds,
438
472
  progress,
439
- completedToTimestamp,
440
473
  totalEvents,
441
474
  },
442
475
  events,
@@ -444,86 +477,25 @@ export async function getIndexingProgress(metrics: MetricsService) {
444
477
  }
445
478
 
446
479
  export async function getAppProgress(metrics: MetricsService): Promise<{
447
- mode: "historical" | "realtime" | "complete" | undefined;
480
+ mode: "historical" | "realtime" | undefined;
448
481
  progress: number;
449
482
  eta: number | undefined;
450
483
  }> {
451
- const sync = await getSyncProgress(metrics);
452
484
  const indexing = await getIndexingProgress(metrics);
453
- const decodingSum = await metrics.ponder_indexing_abi_decoding_duration
454
- .get()
455
- .then(
456
- (m) =>
457
- m.values.find(
458
- (v) => v.metricName === "ponder_indexing_abi_decoding_duration_sum",
459
- )?.value,
460
- );
461
- const getEventsSum = await metrics.ponder_database_method_duration
462
- .get()
463
- .then(
464
- (m) =>
465
- m.values.find(
466
- (v) =>
467
- v.labels.method === "getEvents" &&
468
- v.metricName === "ponder_database_method_duration_sum",
469
- )?.value,
470
- );
471
- const indexingSum = indexing.events.reduce(
472
- (acc, cur) => acc + cur.averageDuration * cur.count,
473
- 0,
474
- );
475
-
476
- let maxSync: (typeof sync)[number] | undefined;
477
- for (const networkSync of sync) {
478
- if (
479
- maxSync === undefined ||
480
- maxSync.eta === undefined ||
481
- (networkSync.eta && networkSync.eta > maxSync.eta)
482
- ) {
483
- maxSync = networkSync;
484
- }
485
- }
486
485
 
487
486
  const remainingSeconds =
488
- indexing.overall.totalSeconds - indexing.overall.completedSeconds;
487
+ indexing.overall.totalSeconds -
488
+ (indexing.overall.completedSeconds + indexing.overall.cachedSeconds);
489
+ const elapsedSeconds = (Date.now() - metrics.start_timestamp) / 1_000;
489
490
 
490
- const indexingEta =
491
+ const eta =
491
492
  indexing.overall.completedSeconds === 0
492
- ? undefined
493
- : (((decodingSum ?? 0) + (getEventsSum ?? 0) + indexingSum) *
494
- remainingSeconds) /
495
- indexing.overall.completedSeconds;
496
-
497
- const eta = sync.every((n) => n.progress === 1)
498
- ? indexingEta
499
- : maxSync?.eta === undefined && indexingEta === undefined
500
- ? undefined
501
- : maxSync?.eta === undefined && maxSync?.progress !== undefined
502
- ? undefined
503
- : Math.max(maxSync?.eta ?? 0, indexingEta ?? 0);
504
-
505
- // Edge case: If all matched events occurred in the same unix timestamp (second), progress will
506
- // be zero, even though indexing is complete. When this happens, totalEvents will be non-zero.
507
- const indexingProgress =
508
- indexing.overall.progress === 0 && indexing.overall.totalEvents > 0
509
- ? 1
510
- : indexing.overall.progress;
511
-
512
- const progress = sync.every((n) => n.progress === 1)
513
- ? indexingProgress
514
- : maxSync?.progress === undefined
515
493
  ? 0
516
- : maxSync!.progress * indexingProgress;
494
+ : (elapsedSeconds / indexing.overall.completedSeconds) * remainingSeconds;
517
495
 
518
496
  return {
519
- mode: sync.some((n) => n.status === "realtime")
520
- ? "realtime"
521
- : sync.every((n) => n.status === "complete")
522
- ? "complete"
523
- : sync.length === 0
524
- ? undefined
525
- : "historical",
526
- progress,
497
+ mode: indexing.overall.progress === 1 ? "realtime" : "historical",
498
+ progress: indexing.overall.progress,
527
499
  eta,
528
500
  };
529
501
  }
@@ -0,0 +1,25 @@
1
+ export type Shutdown = {
2
+ add: (callback: () => unknown | Promise<unknown>) => void;
3
+ kill: () => Promise<void>;
4
+ isKilled: boolean;
5
+ abortController: AbortController;
6
+ };
7
+
8
+ export const createShutdown = (): Shutdown => {
9
+ const abortController = new AbortController();
10
+ const callbacks: (() => unknown | Promise<unknown>)[] = [];
11
+
12
+ return {
13
+ add: (callback) => {
14
+ callbacks.push(callback);
15
+ },
16
+ kill: async () => {
17
+ abortController.abort();
18
+ await Promise.all(callbacks.map((callback) => callback()));
19
+ },
20
+ get isKilled() {
21
+ return abortController.signal.aborted;
22
+ },
23
+ abortController,
24
+ };
25
+ };