ponder 0.9.2 → 0.9.3
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.
- package/dist/bin/ponder.js +1933 -1606
- package/dist/bin/ponder.js.map +1 -1
- package/dist/{chunk-IFTUFVCL.js → chunk-LHCA5XFV.js} +2 -5
- package/dist/chunk-LHCA5XFV.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/commands/codegen.ts +8 -10
- package/src/bin/commands/dev.ts +30 -42
- package/src/bin/commands/list.ts +9 -14
- package/src/bin/commands/serve.ts +26 -39
- package/src/bin/commands/start.ts +29 -42
- package/src/bin/utils/{shutdown.ts → exit.ts} +23 -37
- package/src/bin/utils/run.ts +275 -175
- package/src/bin/utils/runServer.ts +1 -5
- package/src/build/index.ts +3 -8
- package/src/build/pre.ts +3 -0
- package/src/config/index.ts +5 -2
- package/src/database/index.ts +72 -72
- package/src/drizzle/kit/index.ts +3 -3
- package/src/indexing/index.ts +0 -4
- package/src/indexing/service.ts +31 -93
- package/src/indexing-store/historical.ts +2 -4
- package/src/internal/common.ts +2 -0
- package/src/internal/errors.ts +9 -9
- package/src/internal/logger.ts +1 -1
- package/src/internal/metrics.ts +75 -103
- package/src/internal/shutdown.ts +25 -0
- package/src/internal/telemetry.ts +16 -18
- package/src/internal/types.ts +9 -1
- package/src/server/index.ts +3 -5
- package/src/sync/events.ts +4 -4
- package/src/sync/filter.ts +1 -0
- package/src/sync/index.ts +1046 -805
- package/src/sync-historical/index.ts +0 -37
- package/src/sync-realtime/index.ts +48 -48
- package/src/sync-store/encoding.ts +5 -5
- package/src/sync-store/index.ts +5 -23
- package/src/ui/index.ts +2 -11
- package/src/utils/checkpoint.ts +17 -3
- package/src/utils/chunk.ts +7 -0
- package/src/utils/generators.ts +66 -0
- package/src/utils/mutex.ts +34 -0
- package/src/utils/partition.ts +41 -0
- package/src/utils/requestQueue.ts +19 -10
- package/src/utils/zipper.ts +80 -0
- package/dist/chunk-IFTUFVCL.js.map +0 -1
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import readline from "node:readline";
|
|
3
3
|
import type { Common } from "@/internal/common.js";
|
|
4
|
-
import {
|
|
4
|
+
import { ShutdownError } from "@/internal/errors.js";
|
|
5
5
|
|
|
6
6
|
const SHUTDOWN_GRACE_PERIOD_MS = 5_000;
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
*/
|
|
11
|
-
export function setupShutdown({
|
|
8
|
+
/** Sets up shutdown handlers for the process. Accepts additional cleanup logic to run. */
|
|
9
|
+
export const createExit = ({
|
|
12
10
|
common,
|
|
13
|
-
cleanup,
|
|
14
11
|
}: {
|
|
15
|
-
common: Common
|
|
16
|
-
|
|
17
|
-
}) {
|
|
12
|
+
common: Pick<Common, "logger" | "telemetry" | "shutdown">;
|
|
13
|
+
}) => {
|
|
18
14
|
let isShuttingDown = false;
|
|
19
15
|
|
|
20
|
-
const
|
|
21
|
-
reason,
|
|
22
|
-
code,
|
|
23
|
-
}: { reason: string; code: 0 | 1 }) => {
|
|
16
|
+
const exit = async ({ reason, code }: { reason: string; code: 0 | 1 }) => {
|
|
24
17
|
if (isShuttingDown) return;
|
|
25
18
|
isShuttingDown = true;
|
|
26
|
-
setTimeout(async () => {
|
|
19
|
+
const timeout = setTimeout(async () => {
|
|
27
20
|
common.logger.fatal({
|
|
28
21
|
service: "process",
|
|
29
22
|
msg: "Failed to shutdown within 5 seconds, terminating (exit code 1)",
|
|
30
23
|
});
|
|
31
|
-
await common.logger.kill();
|
|
32
24
|
process.exit(1);
|
|
33
25
|
}, SHUTDOWN_GRACE_PERIOD_MS);
|
|
34
26
|
|
|
@@ -43,15 +35,14 @@ export function setupShutdown({
|
|
|
43
35
|
properties: { duration_seconds: process.uptime() },
|
|
44
36
|
});
|
|
45
37
|
|
|
46
|
-
await
|
|
38
|
+
await common.shutdown.kill();
|
|
39
|
+
clearTimeout(timeout);
|
|
47
40
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
41
|
+
if (process.stdin.isTTY) {
|
|
42
|
+
process.stdin.setRawMode(false);
|
|
43
|
+
process.stdin.pause();
|
|
44
|
+
}
|
|
53
45
|
|
|
54
|
-
await common.logger.kill();
|
|
55
46
|
process.exit(code);
|
|
56
47
|
};
|
|
57
48
|
|
|
@@ -61,36 +52,31 @@ export function setupShutdown({
|
|
|
61
52
|
output: process.stdout,
|
|
62
53
|
});
|
|
63
54
|
readlineInterface.on("SIGINT", () =>
|
|
64
|
-
|
|
55
|
+
exit({ reason: "Received SIGINT", code: 0 }),
|
|
65
56
|
);
|
|
66
57
|
}
|
|
67
58
|
|
|
68
|
-
process.on("SIGINT", () =>
|
|
69
|
-
process.on("SIGTERM", () =>
|
|
70
|
-
|
|
71
|
-
);
|
|
72
|
-
process.on("SIGQUIT", () =>
|
|
73
|
-
shutdown({ reason: "Received SIGQUIT", code: 0 }),
|
|
74
|
-
);
|
|
75
|
-
|
|
59
|
+
process.on("SIGINT", () => exit({ reason: "Received SIGINT", code: 0 }));
|
|
60
|
+
process.on("SIGTERM", () => exit({ reason: "Received SIGTERM", code: 0 }));
|
|
61
|
+
process.on("SIGQUIT", () => exit({ reason: "Received SIGQUIT", code: 0 }));
|
|
76
62
|
process.on("uncaughtException", (error: Error) => {
|
|
77
|
-
if (error instanceof
|
|
63
|
+
if (error instanceof ShutdownError) return;
|
|
78
64
|
common.logger.error({
|
|
79
65
|
service: "process",
|
|
80
66
|
msg: "Caught uncaughtException event",
|
|
81
67
|
error,
|
|
82
68
|
});
|
|
83
|
-
|
|
69
|
+
exit({ reason: "Received uncaughtException", code: 1 });
|
|
84
70
|
});
|
|
85
71
|
process.on("unhandledRejection", (error: Error) => {
|
|
86
|
-
if (error instanceof
|
|
72
|
+
if (error instanceof ShutdownError) return;
|
|
87
73
|
common.logger.error({
|
|
88
74
|
service: "process",
|
|
89
75
|
msg: "Caught unhandledRejection event",
|
|
90
76
|
error,
|
|
91
77
|
});
|
|
92
|
-
|
|
78
|
+
exit({ reason: "Received unhandledRejection", code: 1 });
|
|
93
79
|
});
|
|
94
80
|
|
|
95
|
-
return
|
|
96
|
-
}
|
|
81
|
+
return exit;
|
|
82
|
+
};
|
package/src/bin/utils/run.ts
CHANGED
|
@@ -6,22 +6,24 @@ import { createRealtimeIndexingStore } from "@/indexing-store/realtime.js";
|
|
|
6
6
|
import { createIndexingService } from "@/indexing/index.js";
|
|
7
7
|
import type { Common } from "@/internal/common.js";
|
|
8
8
|
import { getAppProgress } from "@/internal/metrics.js";
|
|
9
|
-
import type {
|
|
9
|
+
import type { IndexingBuild, PreBuild, SchemaBuild } from "@/internal/types.js";
|
|
10
10
|
import { createSyncStore } from "@/sync-store/index.js";
|
|
11
11
|
import { decodeEvents } from "@/sync/events.js";
|
|
12
12
|
import { type RealtimeEvent, createSync, splitEvents } from "@/sync/index.js";
|
|
13
13
|
import {
|
|
14
|
+
ZERO_CHECKPOINT_STRING,
|
|
14
15
|
decodeCheckpoint,
|
|
15
|
-
encodeCheckpoint,
|
|
16
|
-
zeroCheckpoint,
|
|
17
16
|
} from "@/utils/checkpoint.js";
|
|
17
|
+
import { chunk } from "@/utils/chunk.js";
|
|
18
18
|
import { formatEta, formatPercentage } from "@/utils/format.js";
|
|
19
|
+
import { createMutex } from "@/utils/mutex.js";
|
|
19
20
|
import { never } from "@/utils/never.js";
|
|
20
|
-
import {
|
|
21
|
+
import { createRequestQueue } from "@/utils/requestQueue.js";
|
|
21
22
|
|
|
22
23
|
/** Starts the sync and indexing services for the specified build. */
|
|
23
24
|
export async function run({
|
|
24
25
|
common,
|
|
26
|
+
preBuild,
|
|
25
27
|
schemaBuild,
|
|
26
28
|
indexingBuild,
|
|
27
29
|
database,
|
|
@@ -29,146 +31,178 @@ export async function run({
|
|
|
29
31
|
onReloadableError,
|
|
30
32
|
}: {
|
|
31
33
|
common: Common;
|
|
34
|
+
preBuild: PreBuild;
|
|
32
35
|
schemaBuild: SchemaBuild;
|
|
33
36
|
indexingBuild: IndexingBuild;
|
|
34
37
|
database: Database;
|
|
35
38
|
onFatalError: (error: Error) => void;
|
|
36
39
|
onReloadableError: (error: Error) => void;
|
|
37
40
|
}) {
|
|
38
|
-
let isKilled = false;
|
|
39
|
-
|
|
40
41
|
const initialCheckpoint = await database.recoverCheckpoint();
|
|
42
|
+
await database.migrateSync();
|
|
43
|
+
|
|
44
|
+
const requestQueues = indexingBuild.networks.map((network) =>
|
|
45
|
+
createRequestQueue({ network, common }),
|
|
46
|
+
);
|
|
47
|
+
|
|
41
48
|
const syncStore = createSyncStore({ common, database });
|
|
42
49
|
const metadataStore = getMetadataStore({ database });
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
// starting the server so the app can become responsive more quickly.
|
|
46
|
-
await database.migrateSync();
|
|
47
|
-
|
|
48
|
-
runCodegen({ common });
|
|
51
|
+
const realtimeMutex = createMutex();
|
|
49
52
|
|
|
50
|
-
// Note: can throw
|
|
51
53
|
const sync = await createSync({
|
|
52
54
|
common,
|
|
53
55
|
indexingBuild,
|
|
56
|
+
requestQueues,
|
|
54
57
|
syncStore,
|
|
55
|
-
// Note: this is not great because it references the
|
|
56
|
-
// `realtimeQueue` which isn't defined yet
|
|
57
58
|
onRealtimeEvent: (realtimeEvent) => {
|
|
58
|
-
|
|
59
|
+
if (realtimeEvent.type === "reorg") {
|
|
60
|
+
realtimeMutex.clear();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return onRealtimeEvent(realtimeEvent);
|
|
59
64
|
},
|
|
60
65
|
onFatalError,
|
|
61
66
|
initialCheckpoint,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const handleEvents = async (events: Event[], checkpoint: string) => {
|
|
65
|
-
if (events.length === 0) return { status: "success" } as const;
|
|
66
|
-
|
|
67
|
-
indexingService.updateTotalSeconds(decodeCheckpoint(checkpoint));
|
|
68
|
-
|
|
69
|
-
return await indexingService.processEvents({ events });
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const realtimeQueue = createQueue({
|
|
73
|
-
initialStart: true,
|
|
74
|
-
browser: false,
|
|
75
|
-
concurrency: 1,
|
|
76
|
-
worker: async (event: RealtimeEvent) => {
|
|
77
|
-
switch (event.type) {
|
|
78
|
-
case "block": {
|
|
79
|
-
// Events must be run block-by-block, so that `database.complete` can accurately
|
|
80
|
-
// update the temporary `checkpoint` value set in the trigger.
|
|
81
|
-
for (const { checkpoint, events } of splitEvents(event.events)) {
|
|
82
|
-
const result = await handleEvents(
|
|
83
|
-
decodeEvents(common, indexingBuild.sources, events),
|
|
84
|
-
event.checkpoint,
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
if (result.status === "error") onReloadableError(result.error);
|
|
88
|
-
|
|
89
|
-
// Set reorg table `checkpoint` column for newly inserted rows.
|
|
90
|
-
await database.complete({ checkpoint });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
await metadataStore.setStatus(event.status);
|
|
94
|
-
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
case "reorg":
|
|
98
|
-
await database.removeTriggers();
|
|
99
|
-
await database.revert({ checkpoint: event.checkpoint });
|
|
100
|
-
await database.createTriggers();
|
|
101
|
-
|
|
102
|
-
break;
|
|
103
|
-
|
|
104
|
-
case "finalize":
|
|
105
|
-
await database.finalize({ checkpoint: event.checkpoint });
|
|
106
|
-
break;
|
|
107
|
-
|
|
108
|
-
default:
|
|
109
|
-
never(event);
|
|
110
|
-
}
|
|
111
|
-
},
|
|
67
|
+
ordering: preBuild.ordering,
|
|
112
68
|
});
|
|
113
69
|
|
|
114
70
|
const indexingService = createIndexingService({
|
|
115
71
|
common,
|
|
116
72
|
indexingBuild,
|
|
117
|
-
|
|
73
|
+
requestQueues,
|
|
74
|
+
syncStore,
|
|
118
75
|
});
|
|
119
76
|
|
|
120
77
|
const historicalIndexingStore = createHistoricalIndexingStore({
|
|
121
78
|
common,
|
|
122
79
|
schemaBuild,
|
|
123
80
|
database,
|
|
124
|
-
initialCheckpoint,
|
|
81
|
+
isDatabaseEmpty: initialCheckpoint === ZERO_CHECKPOINT_STRING,
|
|
125
82
|
});
|
|
126
83
|
|
|
127
84
|
indexingService.setIndexingStore(historicalIndexingStore);
|
|
128
85
|
|
|
86
|
+
runCodegen({ common });
|
|
87
|
+
|
|
129
88
|
await metadataStore.setStatus(sync.getStatus());
|
|
130
89
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
90
|
+
for (const network of indexingBuild.networks) {
|
|
91
|
+
const label = { network: network.name };
|
|
92
|
+
common.metrics.ponder_historical_total_indexing_seconds.set(
|
|
93
|
+
label,
|
|
94
|
+
Math.max(
|
|
95
|
+
sync.seconds[network.name]!.end - sync.seconds[network.name]!.start,
|
|
96
|
+
0,
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
common.metrics.ponder_historical_cached_indexing_seconds.set(
|
|
100
|
+
label,
|
|
101
|
+
Math.max(
|
|
102
|
+
sync.seconds[network.name]!.cached - sync.seconds[network.name]!.start,
|
|
103
|
+
0,
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
common.metrics.ponder_historical_completed_indexing_seconds.set(label, 0);
|
|
107
|
+
common.metrics.ponder_indexing_timestamp.set(
|
|
108
|
+
label,
|
|
109
|
+
Math.max(
|
|
110
|
+
sync.seconds[network.name]!.cached,
|
|
111
|
+
sync.seconds[network.name]!.start,
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
145
115
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
116
|
+
// Reset the start timestamp so the eta estimate doesn't include
|
|
117
|
+
// the startup time.
|
|
118
|
+
common.metrics.start_timestamp = Date.now();
|
|
149
119
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
120
|
+
// If the initial checkpoint is zero, we need to run setup events.
|
|
121
|
+
if (initialCheckpoint === ZERO_CHECKPOINT_STRING) {
|
|
122
|
+
const result = await indexingService.processSetupEvents({
|
|
123
|
+
sources: indexingBuild.sources,
|
|
124
|
+
networks: indexingBuild.networks,
|
|
125
|
+
});
|
|
153
126
|
|
|
127
|
+
if (result.status === "error") {
|
|
128
|
+
onReloadableError(result.error);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let lastFlush = Date.now();
|
|
134
|
+
|
|
135
|
+
// Run historical indexing until complete.
|
|
136
|
+
for await (const events of sync.getEvents()) {
|
|
137
|
+
if (events.length > 0) {
|
|
154
138
|
const decodedEvents = decodeEvents(common, indexingBuild.sources, events);
|
|
155
|
-
const
|
|
139
|
+
const eventChunks = chunk(decodedEvents, 93);
|
|
140
|
+
common.logger.debug({
|
|
141
|
+
service: "app",
|
|
142
|
+
msg: `Decoded ${decodedEvents.length} events`,
|
|
143
|
+
});
|
|
144
|
+
for (const eventChunk of eventChunks) {
|
|
145
|
+
const result = await indexingService.processEvents({
|
|
146
|
+
events: eventChunk,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (result.status === "error") {
|
|
150
|
+
onReloadableError(result.error);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const checkpoint = decodeCheckpoint(
|
|
155
|
+
eventChunk[eventChunk.length - 1]!.checkpoint,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (preBuild.ordering === "multichain") {
|
|
159
|
+
const network = indexingBuild.networks.find(
|
|
160
|
+
(network) => network.chainId === Number(checkpoint.chainId),
|
|
161
|
+
)!;
|
|
162
|
+
common.metrics.ponder_historical_completed_indexing_seconds.set(
|
|
163
|
+
{ network: network.name },
|
|
164
|
+
Math.max(
|
|
165
|
+
checkpoint.blockTimestamp - sync.seconds[network.name]!.start,
|
|
166
|
+
0,
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
common.metrics.ponder_indexing_timestamp.set(
|
|
170
|
+
{ network: network.name },
|
|
171
|
+
checkpoint.blockTimestamp,
|
|
172
|
+
);
|
|
173
|
+
} else {
|
|
174
|
+
for (const network of indexingBuild.networks) {
|
|
175
|
+
common.metrics.ponder_historical_completed_indexing_seconds.set(
|
|
176
|
+
{ network: network.name },
|
|
177
|
+
Math.max(
|
|
178
|
+
checkpoint.blockTimestamp - sync.seconds[network.name]!.start,
|
|
179
|
+
0,
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
common.metrics.ponder_indexing_timestamp.set(
|
|
183
|
+
{ network: network.name },
|
|
184
|
+
checkpoint.blockTimestamp,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Note: allows for terminal and logs to be updated
|
|
190
|
+
await new Promise(setImmediate);
|
|
191
|
+
}
|
|
156
192
|
|
|
157
193
|
// underlying metrics collection is actually synchronous
|
|
158
194
|
// https://github.com/siimon/prom-client/blob/master/lib/histogram.js#L102-L125
|
|
159
195
|
const { eta, progress } = await getAppProgress(common.metrics);
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
171
|
-
}
|
|
196
|
+
if (eta === undefined || progress === undefined) {
|
|
197
|
+
common.logger.info({
|
|
198
|
+
service: "app",
|
|
199
|
+
msg: `Indexed ${decodedEvents.length} events`,
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
common.logger.info({
|
|
203
|
+
service: "app",
|
|
204
|
+
msg: `Indexed ${decodedEvents.length} events with ${formatPercentage(progress)} complete and ${formatEta(eta * 1_000)} remaining`,
|
|
205
|
+
});
|
|
172
206
|
}
|
|
173
207
|
|
|
174
208
|
// Persist the indexing store to the db if it is too full. The `finalized`
|
|
@@ -193,13 +227,9 @@ export async function run({
|
|
|
193
227
|
});
|
|
194
228
|
}
|
|
195
229
|
|
|
196
|
-
await database.finalize({
|
|
197
|
-
checkpoint: encodeCheckpoint(zeroCheckpoint),
|
|
198
|
-
});
|
|
230
|
+
await database.finalize({ checkpoint: ZERO_CHECKPOINT_STRING });
|
|
199
231
|
await historicalIndexingStore.flush();
|
|
200
|
-
await database.complete({
|
|
201
|
-
checkpoint: encodeCheckpoint(zeroCheckpoint),
|
|
202
|
-
});
|
|
232
|
+
await database.complete({ checkpoint: ZERO_CHECKPOINT_STRING });
|
|
203
233
|
await database.finalize({
|
|
204
234
|
checkpoint: events[events.length - 1]!.checkpoint,
|
|
205
235
|
});
|
|
@@ -210,86 +240,156 @@ export async function run({
|
|
|
210
240
|
msg: "Completed flush",
|
|
211
241
|
});
|
|
212
242
|
}
|
|
213
|
-
|
|
214
|
-
await metadataStore.setStatus(sync.getStatus());
|
|
215
|
-
if (result.status === "killed") {
|
|
216
|
-
return;
|
|
217
|
-
} else if (result.status === "error") {
|
|
218
|
-
onReloadableError(result.error);
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
243
|
}
|
|
222
244
|
|
|
223
|
-
|
|
245
|
+
await metadataStore.setStatus(sync.getStatus());
|
|
246
|
+
}
|
|
224
247
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
248
|
+
// Persist the indexing store to the db. The `finalized`
|
|
249
|
+
// checkpoint is used as a mutex. Any rows in the reorg table that may
|
|
250
|
+
// have been written because of raw sql access are deleted. Also must truncate
|
|
251
|
+
// the reorg tables that may have been written because of raw sql access.
|
|
229
252
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
253
|
+
common.logger.debug({
|
|
254
|
+
service: "indexing",
|
|
255
|
+
msg: "Completed all historical events, starting final flush",
|
|
256
|
+
});
|
|
234
257
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
258
|
+
await database.finalize({ checkpoint: ZERO_CHECKPOINT_STRING });
|
|
259
|
+
await historicalIndexingStore.flush();
|
|
260
|
+
await database.complete({ checkpoint: ZERO_CHECKPOINT_STRING });
|
|
261
|
+
await database.finalize({ checkpoint: sync.getFinalizedCheckpoint() });
|
|
262
|
+
|
|
263
|
+
// Manually update metrics to fix a UI bug that occurs when the end
|
|
264
|
+
// checkpoint is between the last processed event and the finalized
|
|
265
|
+
// checkpoint.
|
|
266
|
+
|
|
267
|
+
for (const network of indexingBuild.networks) {
|
|
268
|
+
const label = { network: network.name };
|
|
269
|
+
common.metrics.ponder_historical_completed_indexing_seconds.set(
|
|
270
|
+
label,
|
|
271
|
+
Math.max(
|
|
272
|
+
sync.seconds[network.name]!.end - sync.seconds[network.name]!.start,
|
|
273
|
+
0,
|
|
274
|
+
),
|
|
247
275
|
);
|
|
248
|
-
common.metrics.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
);
|
|
252
|
-
common.metrics.ponder_indexing_completed_timestamp.set(
|
|
253
|
-
decodeCheckpoint(end ?? start).blockTimestamp,
|
|
276
|
+
common.metrics.ponder_indexing_timestamp.set(
|
|
277
|
+
{ network: network.name },
|
|
278
|
+
sync.seconds[network.name]!.end,
|
|
254
279
|
);
|
|
280
|
+
}
|
|
255
281
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
282
|
+
// Become healthy
|
|
283
|
+
common.logger.info({
|
|
284
|
+
service: "indexing",
|
|
285
|
+
msg: "Completed historical indexing",
|
|
286
|
+
});
|
|
261
287
|
|
|
262
|
-
|
|
263
|
-
|
|
288
|
+
const onRealtimeEvent = realtimeMutex(async (event: RealtimeEvent) => {
|
|
289
|
+
switch (event.type) {
|
|
290
|
+
case "block": {
|
|
291
|
+
if (event.events.length > 0) {
|
|
292
|
+
// Events must be run block-by-block, so that `database.complete` can accurately
|
|
293
|
+
// update the temporary `checkpoint` value set in the trigger.
|
|
264
294
|
|
|
265
|
-
|
|
266
|
-
createRealtimeIndexingStore({
|
|
267
|
-
common,
|
|
268
|
-
schemaBuild,
|
|
269
|
-
database,
|
|
270
|
-
}),
|
|
271
|
-
);
|
|
295
|
+
const perBlockEvents = splitEvents(event.events);
|
|
272
296
|
|
|
273
|
-
|
|
297
|
+
common.logger.debug({
|
|
298
|
+
service: "app",
|
|
299
|
+
msg: `Partitioned events into ${perBlockEvents.length} blocks`,
|
|
300
|
+
});
|
|
274
301
|
|
|
275
|
-
|
|
302
|
+
for (const { checkpoint, events } of perBlockEvents) {
|
|
303
|
+
const network = indexingBuild.networks.find(
|
|
304
|
+
(network) =>
|
|
305
|
+
network.chainId ===
|
|
306
|
+
Number(decodeCheckpoint(checkpoint).chainId),
|
|
307
|
+
)!;
|
|
308
|
+
|
|
309
|
+
const decodedEvents = decodeEvents(
|
|
310
|
+
common,
|
|
311
|
+
indexingBuild.sources,
|
|
312
|
+
events,
|
|
313
|
+
);
|
|
276
314
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
315
|
+
common.logger.debug({
|
|
316
|
+
service: "app",
|
|
317
|
+
msg: `Decoded ${decodedEvents.length} '${network.name}' events for block ${Number(decodeCheckpoint(checkpoint).blockNumber)}`,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const result = await indexingService.processEvents({
|
|
321
|
+
events: decodedEvents,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
common.logger.info({
|
|
325
|
+
service: "app",
|
|
326
|
+
msg: `Indexed ${decodedEvents.length} '${network.name}' events for block ${Number(decodeCheckpoint(checkpoint).blockNumber)}`,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (result.status === "error") onReloadableError(result.error);
|
|
330
|
+
|
|
331
|
+
// Set reorg table `checkpoint` column for newly inserted rows.
|
|
332
|
+
await database.complete({ checkpoint });
|
|
333
|
+
|
|
334
|
+
if (preBuild.ordering === "multichain") {
|
|
335
|
+
const network = indexingBuild.networks.find(
|
|
336
|
+
(network) =>
|
|
337
|
+
network.chainId ===
|
|
338
|
+
Number(decodeCheckpoint(checkpoint).chainId),
|
|
339
|
+
)!;
|
|
340
|
+
|
|
341
|
+
common.metrics.ponder_indexing_timestamp.set(
|
|
342
|
+
{ network: network.name },
|
|
343
|
+
decodeCheckpoint(checkpoint).blockTimestamp,
|
|
344
|
+
);
|
|
345
|
+
} else {
|
|
346
|
+
for (const network of indexingBuild.networks) {
|
|
347
|
+
common.metrics.ponder_indexing_timestamp.set(
|
|
348
|
+
{ network: network.name },
|
|
349
|
+
decodeCheckpoint(checkpoint).blockTimestamp,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
await metadataStore.setStatus(event.status);
|
|
357
|
+
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
case "reorg":
|
|
361
|
+
await database.removeTriggers();
|
|
362
|
+
await database.revert({ checkpoint: event.checkpoint });
|
|
363
|
+
await database.createTriggers();
|
|
364
|
+
|
|
365
|
+
break;
|
|
366
|
+
|
|
367
|
+
case "finalize":
|
|
368
|
+
await database.finalize({ checkpoint: event.checkpoint });
|
|
369
|
+
break;
|
|
370
|
+
|
|
371
|
+
default:
|
|
372
|
+
never(event);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
await database.createIndexes();
|
|
377
|
+
await database.createTriggers();
|
|
378
|
+
|
|
379
|
+
indexingService.setIndexingStore(
|
|
380
|
+
createRealtimeIndexingStore({
|
|
381
|
+
common,
|
|
382
|
+
schemaBuild,
|
|
383
|
+
database,
|
|
384
|
+
}),
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
await sync.startRealtime();
|
|
388
|
+
|
|
389
|
+
await metadataStore.setStatus(sync.getStatus());
|
|
390
|
+
|
|
391
|
+
common.logger.info({
|
|
392
|
+
service: "server",
|
|
393
|
+
msg: "Started returning 200 responses from /ready endpoint",
|
|
394
|
+
});
|
|
295
395
|
}
|