envio 3.12.1 → 3.13.0-rc.0

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 (103) hide show
  1. package/package.json +6 -8
  2. package/src/BatchProcessing.res +15 -14
  3. package/src/BatchProcessing.res.mjs +8 -15
  4. package/src/Bin.res +24 -17
  5. package/src/Bin.res.mjs +5 -0
  6. package/src/ChainFetching.res +18 -24
  7. package/src/ChainFetching.res.mjs +14 -15
  8. package/src/ChainState.res +103 -38
  9. package/src/ChainState.res.mjs +79 -22
  10. package/src/ChainState.resi +14 -2
  11. package/src/Config.res +32 -0
  12. package/src/Config.res.mjs +24 -0
  13. package/src/Core.res +4 -0
  14. package/src/Core.res.mjs +4 -0
  15. package/src/CrossChainState.res +109 -11
  16. package/src/CrossChainState.res.mjs +71 -7
  17. package/src/CrossChainState.resi +11 -1
  18. package/src/Env.res +4 -0
  19. package/src/ErrorHandling.res +15 -4
  20. package/src/ErrorHandling.res.mjs +16 -5
  21. package/src/FinalizeBackfill.res +10 -4
  22. package/src/FinalizeBackfill.res.mjs +5 -3
  23. package/src/HandlerRegister.res +0 -37
  24. package/src/HandlerRegister.res.mjs +0 -18
  25. package/src/IndexerLoop.res +2 -0
  26. package/src/IndexerLoop.res.mjs +1 -0
  27. package/src/IndexerState.res +43 -1
  28. package/src/IndexerState.res.mjs +48 -4
  29. package/src/IndexerState.resi +11 -0
  30. package/src/Main.res +128 -268
  31. package/src/Main.res.mjs +27 -153
  32. package/src/Metrics.res +268 -102
  33. package/src/Metrics.res.mjs +227 -48
  34. package/src/Persistence.res +30 -2
  35. package/src/Persistence.res.mjs +12 -3
  36. package/src/PgStorage.res +19 -11
  37. package/src/PgStorage.res.mjs +13 -10
  38. package/src/Rollback.res +3 -3
  39. package/src/Server.res +178 -0
  40. package/src/Server.res.mjs +143 -0
  41. package/src/SimulateItems.res +1 -1
  42. package/src/SimulateItems.res.mjs +1 -1
  43. package/src/Supervisor.res +542 -0
  44. package/src/Supervisor.res.mjs +455 -0
  45. package/src/TestIndexer.res +1 -1
  46. package/src/TestIndexer.res.mjs +2 -2
  47. package/src/Worker.res +101 -0
  48. package/src/Worker.res.mjs +81 -0
  49. package/src/bindings/NodeJs.res +50 -0
  50. package/src/bindings/NodeJs.res.mjs +8 -1
  51. package/src/db/InternalTable.res +8 -1
  52. package/src/db/InternalTable.res.mjs +5 -1
  53. package/src/sources/AddressSet.res +5 -3
  54. package/src/sources/AddressStore.res +19 -11
  55. package/src/sources/AddressStore.res.mjs +2 -7
  56. package/src/sources/BlockStore.res +10 -9
  57. package/src/sources/BlockStore.res.mjs +4 -4
  58. package/src/sources/ChainSources.res +4 -0
  59. package/src/sources/ChainSources.res.mjs +2 -2
  60. package/src/sources/EvmChain.res +4 -0
  61. package/src/sources/EvmChain.res.mjs +3 -1
  62. package/src/sources/EvmEventItem.res +56 -0
  63. package/src/sources/EvmEventItem.res.mjs +32 -0
  64. package/src/sources/EvmHyperSyncSource.res +7 -37
  65. package/src/sources/EvmHyperSyncSource.res.mjs +8 -29
  66. package/src/sources/EvmRpcClient.res +41 -59
  67. package/src/sources/EvmRpcClient.res.mjs +4 -54
  68. package/src/sources/EvmRpcWs.res +4 -1
  69. package/src/sources/EvmRpcWs.res.mjs +6 -2
  70. package/src/sources/FuelHyperSyncSource.res +6 -4
  71. package/src/sources/FuelHyperSyncSource.res.mjs +7 -9
  72. package/src/sources/HeightFeed.res +7 -1
  73. package/src/sources/HeightFeed.res.mjs +9 -7
  74. package/src/sources/HyperSyncClient.res +1 -14
  75. package/src/sources/RequestStat.res +7 -0
  76. package/src/sources/RequestStat.res.mjs +14 -1
  77. package/src/sources/RpcSource.res +100 -1043
  78. package/src/sources/RpcSource.res.mjs +81 -1056
  79. package/src/sources/SimulateSource.res +2 -5
  80. package/src/sources/SimulateSource.res.mjs +2 -3
  81. package/src/sources/Source.res +34 -12
  82. package/src/sources/Source.res.mjs +11 -0
  83. package/src/sources/SourceManager.res +111 -31
  84. package/src/sources/SourceManager.res.mjs +65 -31
  85. package/src/sources/SourceManager.resi +4 -0
  86. package/src/sources/StartBlockResolver.res +8 -5
  87. package/src/sources/StartBlockResolver.res.mjs +4 -2
  88. package/src/sources/SvmHyperSyncSource.res +4 -2
  89. package/src/sources/SvmHyperSyncSource.res.mjs +4 -6
  90. package/src/sources/TransactionStore.res +9 -6
  91. package/src/sources/TransactionStore.res.mjs +2 -2
  92. package/src/tui/Tui.res +10 -0
  93. package/src/tui/Tui.res.mjs +15 -0
  94. package/src/tui/components/SyncETA.res +12 -6
  95. package/src/tui/components/SyncETA.res.mjs +12 -8
  96. package/src/LazyLoader.res +0 -135
  97. package/src/LazyLoader.res.mjs +0 -118
  98. package/src/bindings/SDSL.res +0 -12
  99. package/src/bindings/SDSL.res.mjs +0 -9
  100. package/src/bindings/Yargs.res +0 -8
  101. package/src/bindings/Yargs.res.mjs +0 -2
  102. package/src/sources/Rpc.res +0 -185
  103. package/src/sources/Rpc.res.mjs +0 -183
@@ -0,0 +1,455 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Env from "./Env.res.mjs";
4
+ import * as Tui from "./tui/Tui.res.mjs";
5
+ import * as Utils from "./Utils.res.mjs";
6
+ import * as Config from "./Config.res.mjs";
7
+ import * as Server from "./Server.res.mjs";
8
+ import * as Worker from "./Worker.res.mjs";
9
+ import * as ChainId from "./ChainId.res.mjs";
10
+ import * as Logging from "./Logging.res.mjs";
11
+ import * as Metrics from "./Metrics.res.mjs";
12
+ import * as Process from "process";
13
+ import * as ChainMap from "./ChainMap.res.mjs";
14
+ import * as PgStorage from "./PgStorage.res.mjs";
15
+ import * as Performance from "./bindings/Performance.res.mjs";
16
+ import * as Persistence from "./Persistence.res.mjs";
17
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
18
+ import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
19
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
20
+ import * as Child_process from "child_process";
21
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
22
+ import * as CrossChainState from "./CrossChainState.res.mjs";
23
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
24
+ import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
25
+
26
+ function plan(chainIds, maxConnections) {
27
+ let workerCount = Stdlib_Array.reduce([
28
+ chainIds.length,
29
+ maxConnections / 2 | 0,
30
+ 4
31
+ ], 4, Primitive_int.min);
32
+ if (workerCount < 2) {
33
+ return;
34
+ }
35
+ let evenShare = Primitive_int.div(maxConnections, workerCount);
36
+ let remainder = Primitive_int.mod_(maxConnections, workerCount);
37
+ return Stdlib_Array.fromInitializer(workerCount, workerIndex => ({
38
+ chainIds: chainIds.filter((param, dealIndex) => {
39
+ let position = Primitive_int.mod_(dealIndex, workerCount);
40
+ let isReversePass = Primitive_int.div(dealIndex, workerCount) % 2 === 1;
41
+ return (
42
+ isReversePass ? (workerCount - 1 | 0) - position | 0 : position
43
+ ) === workerIndex;
44
+ }),
45
+ maxConnections: evenShare + (
46
+ workerIndex < remainder ? 1 : 0
47
+ ) | 0
48
+ }));
49
+ }
50
+
51
+ function planForRun(config, maxConnectionsOpt) {
52
+ let maxConnections = maxConnectionsOpt !== undefined ? maxConnectionsOpt : Env.Db.maxConnections;
53
+ if (config.isolated || !Config.isPerChain(config)) {
54
+ return;
55
+ } else {
56
+ return plan(ChainMap.values(config.chainMap).map(chain => chain.id), maxConnections);
57
+ }
58
+ }
59
+
60
+ function name(worker) {
61
+ return worker.chainIds.map(ChainId.toString).join(";");
62
+ }
63
+
64
+ function label(worker) {
65
+ return `[chain ` + name(worker) + `]`;
66
+ }
67
+
68
+ function logFilePath(workerIndex, pathOpt) {
69
+ let path = pathOpt !== undefined ? pathOpt : Env.logFilePath;
70
+ let suffix = `.worker-` + workerIndex.toString();
71
+ let dot = path.lastIndexOf(".");
72
+ if (dot > path.lastIndexOf("/")) {
73
+ return path.slice(0, dot) + suffix + path.slice(dot, path.length);
74
+ } else {
75
+ return path + suffix;
76
+ }
77
+ }
78
+
79
+ function readLines(onLine) {
80
+ let pending = {
81
+ contents: ""
82
+ };
83
+ let read = chunk => {
84
+ let parts = (pending.contents + chunk).split("\n");
85
+ pending.contents = Stdlib_Option.getOr(parts.pop(), "");
86
+ parts.forEach(onLine);
87
+ };
88
+ let flush = () => {
89
+ let line = pending.contents;
90
+ if (line === "") {
91
+ return;
92
+ }
93
+ pending.contents = "";
94
+ onLine(line);
95
+ };
96
+ return [
97
+ read,
98
+ flush
99
+ ];
100
+ }
101
+
102
+ function memoryBudgets(workerCount) {
103
+ return [
104
+ [
105
+ "ENVIO_INDEXING_MAX_BUFFER_SIZE",
106
+ CrossChainState.calculateTargetBufferSize()
107
+ ],
108
+ [
109
+ "ENVIO_IN_MEMORY_OBJECTS_TARGET",
110
+ Env.inMemoryObjectsTarget | 0
111
+ ]
112
+ ].map(param => [
113
+ param[0],
114
+ Primitive_int.max(1, Primitive_int.div(param[1], workerCount)).toString()
115
+ ]);
116
+ }
117
+
118
+ function fork(worker, workerIndex, workerCount, holdRealtime, isDev, entryPathOpt, pipeOutputOpt, onOutputOpt, onErrorOutputOpt, onSnapshotOpt) {
119
+ let entryPath = entryPathOpt !== undefined ? entryPathOpt : process.argv[1];
120
+ let pipeOutput = pipeOutputOpt !== undefined ? pipeOutputOpt : false;
121
+ let onOutput = onOutputOpt !== undefined ? onOutputOpt : prim => {
122
+ console.log(prim);
123
+ };
124
+ let onErrorOutput = onErrorOutputOpt !== undefined ? onErrorOutputOpt : prim => {
125
+ console.error(prim);
126
+ };
127
+ let onSnapshot = onSnapshotOpt !== undefined ? onSnapshotOpt : () => {};
128
+ let env = Object.assign({}, Process.env);
129
+ env[Worker.envVar] = S$RescriptSchema.reverseConvertToJsonStringOrThrow({
130
+ chainIds: worker.chainIds,
131
+ holdRealtime: holdRealtime,
132
+ isDev: isDev
133
+ }, Worker.configSchema, undefined);
134
+ env["ENVIO_PG_MAX_CONNECTIONS"] = worker.maxConnections.toString();
135
+ memoryBudgets(workerCount).forEach(param => {
136
+ env[param[0]] = param[1];
137
+ });
138
+ env["LOG_FILE"] = logFilePath(workerIndex, undefined);
139
+ if (pipeOutput && Stdlib_Option.getOr((process.stdout.isTTY == null) ? undefined : Primitive_option.some(process.stdout.isTTY), false)) {
140
+ env["FORCE_COLOR"] = "1";
141
+ }
142
+ let child = Child_process.fork(entryPath, [], {
143
+ env: env,
144
+ serialization: "advanced",
145
+ stdio: pipeOutput ? [
146
+ "inherit",
147
+ "pipe",
148
+ "pipe",
149
+ "ipc"
150
+ ] : [
151
+ "inherit",
152
+ "inherit",
153
+ "inherit",
154
+ "ipc"
155
+ ]
156
+ });
157
+ if (pipeOutput) {
158
+ [
159
+ [
160
+ child.stdout,
161
+ onOutput
162
+ ],
163
+ [
164
+ child.stderr,
165
+ onErrorOutput
166
+ ]
167
+ ].forEach(param => {
168
+ let stream = param[0];
169
+ if (stream === null) {
170
+ return;
171
+ }
172
+ let match = readLines(param[1]);
173
+ stream.setEncoding("utf8");
174
+ stream.on("data", match[0]);
175
+ stream.on("end", match[1]);
176
+ });
177
+ }
178
+ let running = {
179
+ worker: worker,
180
+ child: child,
181
+ snapshot: undefined,
182
+ runtime: undefined,
183
+ settled: false
184
+ };
185
+ child.on("message", message => {
186
+ running.snapshot = message.metrics;
187
+ running.runtime = message.runtime;
188
+ onSnapshot();
189
+ });
190
+ return running;
191
+ }
192
+
193
+ function stop(group) {
194
+ group.stopping = true;
195
+ group.running.forEach(r => {
196
+ r.child.kill("SIGTERM");
197
+ });
198
+ }
199
+
200
+ let inFlight = {
201
+ contents: undefined
202
+ };
203
+
204
+ function syncCache(dump) {
205
+ let dumping = inFlight.contents;
206
+ if (dumping !== undefined) {
207
+ return dumping;
208
+ }
209
+ let dumping$1 = dump().finally(() => {
210
+ inFlight.contents = undefined;
211
+ });
212
+ inFlight.contents = dumping$1;
213
+ return dumping$1;
214
+ }
215
+
216
+ function dumpCache(config) {
217
+ let storage = PgStorage.makeStorageFromEnv(config, Primitive_option.some(PgStorage.makeClient(1)), undefined, undefined);
218
+ return storage.dumpEffectCache().finally(() => {
219
+ storage.close();
220
+ });
221
+ }
222
+
223
+ function classifyExit(code, signal, stopping) {
224
+ if (stopping) {
225
+ return "Expected";
226
+ }
227
+ if (code === 0) {
228
+ return "Expected";
229
+ }
230
+ if (signal === "SIGTERM") {
231
+ return "Stopping";
232
+ } else {
233
+ return "Failed";
234
+ }
235
+ }
236
+
237
+ async function awaitExit(group) {
238
+ let failed = {
239
+ contents: false
240
+ };
241
+ let alive = {
242
+ contents: group.running.length
243
+ };
244
+ await new Promise((resolve, param) => {
245
+ let onGone = (r, ending) => {
246
+ if (r.settled) {
247
+ return;
248
+ }
249
+ r.settled = true;
250
+ switch (ending) {
251
+ case "Expected" :
252
+ break;
253
+ case "Stopping" :
254
+ stop(group);
255
+ break;
256
+ case "Failed" :
257
+ failed.contents = true;
258
+ stop(group);
259
+ break;
260
+ }
261
+ alive.contents = alive.contents - 1 | 0;
262
+ if (alive.contents === 0) {
263
+ return resolve();
264
+ }
265
+ };
266
+ group.running.forEach(r => {
267
+ r.child.on("exit", (code, signal) => onGone(r, classifyExit(code, signal, group.stopping)));
268
+ r.child.on("error", exn => {
269
+ Logging.errorWithExn(exn, label(r.worker) + ` failed to start`);
270
+ onGone(r, "Failed");
271
+ });
272
+ });
273
+ });
274
+ if (failed.contents) {
275
+ Stdlib_JsError.throwWithMessage("An indexer process exited with a failure. Stopped the others.");
276
+ }
277
+ if (group.stopping) {
278
+ return "Stopped";
279
+ } else {
280
+ return "Finished";
281
+ }
282
+ }
283
+
284
+ function isRunAtHead(running) {
285
+ if (Utils.$$Array.notEmpty(running)) {
286
+ return running.every(r => {
287
+ if (r.child.connected) {
288
+ return Stdlib_Option.mapOr(r.snapshot, false, snapshot => snapshot.hasArrivedAtHead);
289
+ } else {
290
+ return false;
291
+ }
292
+ });
293
+ } else {
294
+ return false;
295
+ }
296
+ }
297
+
298
+ function releaseIfAtHead(group) {
299
+ if (group.holdingRealtime && !group.stopping && isRunAtHead(group.running)) {
300
+ group.holdingRealtime = false;
301
+ group.running.forEach(r => {
302
+ r.child.send("release-realtime");
303
+ });
304
+ return;
305
+ }
306
+ }
307
+
308
+ function configuredChains(config) {
309
+ return ChainMap.values(config.chainMap).map(chain => {
310
+ let match = chain.sourceConfig;
311
+ let tmp;
312
+ switch (match.TAG) {
313
+ case "EvmSourceConfig" :
314
+ tmp = Stdlib_Option.isSome(match.hypersync);
315
+ break;
316
+ case "FuelSourceConfig" :
317
+ case "SvmSourceConfig" :
318
+ tmp = true;
319
+ break;
320
+ case "SimulateSourceConfig" :
321
+ case "CustomSources" :
322
+ tmp = false;
323
+ break;
324
+ }
325
+ let block = chain.startBlock;
326
+ let tmp$1;
327
+ tmp$1 = block === "latest" ? 0 : block;
328
+ return {
329
+ chainId: chain.id,
330
+ poweredByHyperSync: tmp,
331
+ firstEventBlockNumber: undefined,
332
+ latestProcessedBlock: undefined,
333
+ timestampCaughtUpToHeadOrEndblock: undefined,
334
+ numEventsProcessed: 0,
335
+ latestFetchedBlockNumber: 0,
336
+ knownHeight: 0,
337
+ numBatchesFetched: 0,
338
+ startBlock: tmp$1,
339
+ endBlock: chain.endBlock,
340
+ numAddresses: 0,
341
+ addressesByContract: [],
342
+ isReady: false,
343
+ sourceBlockNumber: 0,
344
+ progressBlockNumber: -1,
345
+ progressLatencyMs: undefined,
346
+ progressBlockTime: undefined,
347
+ concurrency: 0,
348
+ partitionsCount: 0,
349
+ bufferSize: 0,
350
+ bufferBlockNumber: -1,
351
+ idleSeconds: 0,
352
+ waitingForNewBlockSeconds: 0,
353
+ queryingSeconds: 0,
354
+ blockRangeFetchSeconds: 0,
355
+ blockRangeParseSeconds: 0,
356
+ blockRangeFetchCount: 0,
357
+ blockRangeFetchedEvents: 0,
358
+ blockRangeFetchedBlocks: 0,
359
+ reorgCount: 0,
360
+ reorgDetectedBlock: undefined,
361
+ rollbackTargetBlock: undefined,
362
+ rateLimitTimeMs: 0,
363
+ rateLimitResetInMs: undefined
364
+ };
365
+ });
366
+ }
367
+
368
+ async function run(config, workers, reset) {
369
+ let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
370
+ await Persistence.initForRun(persistence, config, reset, config.isDev, false);
371
+ await persistence.storage.close();
372
+ let startTime = new Date();
373
+ let startTimeRef = Performance.now();
374
+ Logging.info({
375
+ msg: "Indexing will be split across multiple processes for faster and more reliable indexing.",
376
+ chains: ChainMap.values(config.chainMap).length,
377
+ processes: workers.length,
378
+ maxConnections: Env.Db.maxConnections
379
+ });
380
+ let shouldUseTui = Tui.shouldUse(undefined, undefined);
381
+ let holdRealtime = Persistence.getInitializedState(persistence).chains.some(chain => Stdlib_Option.isNone(chain.timestampCaughtUpToHeadOrEndblock));
382
+ let group = {
383
+ running: [],
384
+ stopping: false,
385
+ holdingRealtime: holdRealtime
386
+ };
387
+ group.running = workers.map((worker, workerIndex) => fork(worker, workerIndex, workers.length, holdRealtime, config.isDev, undefined, shouldUseTui, undefined, undefined, () => releaseIfAtHead(group)));
388
+ let reported = () => Stdlib_Array.filterMap(group.running, r => r.snapshot);
389
+ let merge = snapshots => Metrics.merge(snapshots, startTime, new Date(), Performance.secondsSince(startTimeRef), CrossChainState.calculateTargetBufferSize());
390
+ Server.startServer(() => {
391
+ let snapshots = reported();
392
+ if (snapshots.length !== 0) {
393
+ return merge(snapshots);
394
+ }
395
+ }, Utils.EnvioPackage.value.version, () => syncCache(() => dumpCache(config)), () => Metrics.renderRuntime(Stdlib_Array.filterMap(group.running, r => Stdlib_Option.map(r.runtime, runtime => [
396
+ `worker="` + name(r.worker) + `"`,
397
+ runtime
398
+ ]))), config.isDev);
399
+ if (shouldUseTui) {
400
+ Tui.start(config, () => {
401
+ let snapshots = reported();
402
+ if (snapshots.length !== 0) {
403
+ return merge(snapshots);
404
+ }
405
+ let newrecord = {...merge([])};
406
+ newrecord.chains = configuredChains(config);
407
+ return newrecord;
408
+ });
409
+ }
410
+ process.on("SIGTERM", () => stop(group));
411
+ process.on("SIGINT", () => stop(group));
412
+ let outcome = await awaitExit(group);
413
+ if (outcome === "Finished") {
414
+ if (shouldUseTui) {
415
+ process.on("SIGTERM", () => {
416
+ Process.exit(0);
417
+ });
418
+ process.on("SIGINT", () => {
419
+ Process.exit(0);
420
+ });
421
+ } else {
422
+ Logging.info("Exiting with success");
423
+ Process.exit(0);
424
+ }
425
+ return;
426
+ }
427
+ Process.exit(0);
428
+ }
429
+
430
+ let minConnectionsPerWorker = 2;
431
+
432
+ let maxWorkers = 4;
433
+
434
+ export {
435
+ minConnectionsPerWorker,
436
+ maxWorkers,
437
+ plan,
438
+ planForRun,
439
+ name,
440
+ label,
441
+ logFilePath,
442
+ readLines,
443
+ memoryBudgets,
444
+ fork,
445
+ stop,
446
+ syncCache,
447
+ dumpCache,
448
+ classifyExit,
449
+ awaitExit,
450
+ isRunAtHead,
451
+ releaseIfAtHead,
452
+ configuredChains,
453
+ run,
454
+ }
455
+ /* Env Not a pure module */
@@ -948,7 +948,7 @@ let createTestIndexer = (): t<'processConfig> => {
948
948
  ~exitAfterFirstEventBlock,
949
949
  ~onError=errHandler => {
950
950
  errHandler->ErrorHandling.log
951
- reject(errHandler.exn->Utils.prettifyExn)
951
+ reject(errHandler->ErrorHandling.toExn)
952
952
  },
953
953
  // Caught up: resolve the run instead of exiting the process.
954
954
  ~onExit=() => resolve(),
@@ -639,9 +639,9 @@ function createTestIndexer() {
639
639
  };
640
640
  try {
641
641
  await new Promise((resolve, reject) => {
642
- let indexerState = IndexerState.makeFromDbState(runConfig, persistence, initialState, registrationsByChainId, undefined, undefined, exitAfterFirstEventBlock, undefined, undefined, errHandler => {
642
+ let indexerState = IndexerState.makeFromDbState(runConfig, persistence, initialState, registrationsByChainId, undefined, undefined, exitAfterFirstEventBlock, undefined, undefined, undefined, errHandler => {
643
643
  ErrorHandling.log(errHandler);
644
- reject(Utils.prettifyExn(errHandler.exn));
644
+ reject(ErrorHandling.toExn(errHandler));
645
645
  }, () => resolve());
646
646
  indexerStateRef.contents = Primitive_option.some(indexerState);
647
647
  IndexerLoop.start(indexerState);
package/src/Worker.res ADDED
@@ -0,0 +1,101 @@
1
+ // The worker side of a supervised run: a process the supervisor forked to drive
2
+ // a subset of the chains. It has no server and no TUI of its own — it reports
3
+ // through the IPC channel, and the supervisor is the one operational surface.
4
+
5
+ // Set by a supervisor in the environment of the workers it forks. Internal:
6
+ // it counts only together with the fork's own channel, so a copy left in a
7
+ // shell starts nothing, and an indexer a user starts themselves takes every
8
+ // path it takes today.
9
+ let envVar = "ENVIO_INTERNAL_WORKER"
10
+
11
+ // What the supervisor decided about this worker, handed over in the spawn
12
+ // environment rather than over the channel: it is settled before the process
13
+ // starts, and the worker needs it before it can load its own config.
14
+ type config = {
15
+ chainIds: array<ChainId.t>,
16
+ // The chains this worker drives may reach the head while chains in another
17
+ // process are still backfilling, and an indexer goes realtime as a whole or
18
+ // not at all. Cleared by the supervisor's `ReleaseRealtime`.
19
+ holdRealtime: bool,
20
+ // Which command the run was started by. A worker re-parses the project's
21
+ // files, which say nothing about that, so it can only be told — and a worker
22
+ // that took a dev run for a plain one would exit at its end block and leave
23
+ // the console it was still serving with a chain missing.
24
+ isDev: bool,
25
+ }
26
+
27
+ let configSchema = S.object((s): config => {
28
+ chainIds: s.field("chainIds", S.array(ChainId.schema)),
29
+ holdRealtime: s.fieldOr("holdRealtime", S.bool, false),
30
+ isDev: s.field("isDev", S.bool),
31
+ })
32
+
33
+ // Read as this module loads, which is before anything that could catch a bare
34
+ // schema error and say where it came from.
35
+ let detect = (~env: dict<string>, ~hasChannel) =>
36
+ switch (hasChannel, env->Dict.get(envVar)) {
37
+ | (true, Some(json)) =>
38
+ switch json->S.parseJsonStringOrThrow(configSchema) {
39
+ | config => Some(config)
40
+ | exception S.Raised(error) =>
41
+ JsError.throwWithMessage(
42
+ `Invalid ${envVar}: ${error->S.Error.message}. It is set by an indexer supervisor for the processes it forks, and isn't meant to be set by hand.`,
43
+ )
44
+ }
45
+ | _ => None
46
+ }
47
+
48
+ let config = detect(
49
+ ~env=NodeJs.Process.process.env,
50
+ ~hasChannel=NodeJs.Process.channel->Nullable.toOption->Option.isSome,
51
+ )
52
+
53
+ let isEnabled = config->Option.isSome
54
+
55
+ @tag("kind")
56
+ type parentMessage =
57
+ // Every chain in the run has reached the head, so this worker may enter the
58
+ // reorg threshold and switch to realtime with the rest of them.
59
+ | @as("release-realtime") ReleaseRealtime
60
+
61
+ @tag("kind")
62
+ type workerMessage =
63
+ | @as("snapshot") Snapshot({metrics: Metrics.t, runtime: Metrics.runtimeSample})
64
+
65
+ // How often a worker reports. Matches the TUI's own refresh, so the supervised
66
+ // display moves at the same rate an unsplit run's does.
67
+ %%private(let snapshotIntervalMillis = 500)
68
+
69
+ // The supervisor is the one that stops a worker, and the one whose absence
70
+ // ends it. A terminal's interrupt reaches the whole group at once, so the
71
+ // worker leaves it to the supervisor, which stops every worker in turn; without
72
+ // that, a worker gone on its own would read as a failure to the supervisor
73
+ // still deciding what the interrupt meant. A supervisor that dies can't tear
74
+ // the group down, so losing the channel is what ends the worker then.
75
+ let bindToSupervisor = () => {
76
+ NodeJs.Process.onSignal("SIGINT", () => ())
77
+ NodeJs.Process.onDisconnect(() => {
78
+ Logging.error("The indexer supervisor is gone. Stopping this chain's process.")
79
+ NodeJs.process->NodeJs.exitWithCode(Failure)
80
+ })
81
+ }
82
+
83
+ %%private(let send = (message: workerMessage) => NodeJs.Process.sendToParent(message)->ignore)
84
+
85
+ // Reports this process's chains and its own runtime for as long as it runs, so
86
+ // the supervisor can merge every worker's into the one snapshot the run serves,
87
+ // and listens for the one decision the supervisor makes on the run's behalf.
88
+ // Does nothing in a process nobody forked.
89
+ let bindRun = (~getMetrics: unit => Metrics.t, ~onReleaseRealtime: unit => unit) =>
90
+ if isEnabled {
91
+ Metrics.startRuntimeCollectors()
92
+ let _intervalId = setInterval(
93
+ () => send(Snapshot({metrics: getMetrics(), runtime: Metrics.sampleRuntime()})),
94
+ snapshotIntervalMillis,
95
+ )
96
+ NodeJs.Process.onMessage((message: parentMessage) =>
97
+ switch message {
98
+ | ReleaseRealtime => onReleaseRealtime()
99
+ }
100
+ )
101
+ }
@@ -0,0 +1,81 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as ChainId from "./ChainId.res.mjs";
4
+ import * as Logging from "./Logging.res.mjs";
5
+ import * as Metrics from "./Metrics.res.mjs";
6
+ import * as Process from "process";
7
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
9
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
10
+ import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
11
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
12
+
13
+ let envVar = "ENVIO_INTERNAL_WORKER";
14
+
15
+ let configSchema = S$RescriptSchema.object(s => ({
16
+ chainIds: s.f("chainIds", S$RescriptSchema.array(ChainId.schema)),
17
+ holdRealtime: s.fieldOr("holdRealtime", S$RescriptSchema.bool, false),
18
+ isDev: s.f("isDev", S$RescriptSchema.bool)
19
+ }));
20
+
21
+ function detect(env, hasChannel) {
22
+ let match = env[envVar];
23
+ if (!hasChannel) {
24
+ return;
25
+ }
26
+ if (match === undefined) {
27
+ return;
28
+ }
29
+ let config;
30
+ try {
31
+ config = S$RescriptSchema.parseJsonStringOrThrow(match, configSchema);
32
+ } catch (raw_error) {
33
+ let error = Primitive_exceptions.internalToException(raw_error);
34
+ if (error.RE_EXN_ID === S$RescriptSchema.Raised) {
35
+ return Stdlib_JsError.throwWithMessage(`Invalid ` + envVar + `: ` + S$RescriptSchema.$$Error.message(error._1) + `. It is set by an indexer supervisor for the processes it forks, and isn't meant to be set by hand.`);
36
+ }
37
+ throw error;
38
+ }
39
+ return config;
40
+ }
41
+
42
+ let config = detect(Process.env, Stdlib_Option.isSome(Primitive_option.fromNullable(process.channel)));
43
+
44
+ let isEnabled = Stdlib_Option.isSome(config);
45
+
46
+ function bindToSupervisor() {
47
+ process.on("SIGINT", () => {});
48
+ process.on("disconnect", () => {
49
+ Logging.error("The indexer supervisor is gone. Stopping this chain's process.");
50
+ Process.exit(1);
51
+ });
52
+ }
53
+
54
+ function bindRun(getMetrics, onReleaseRealtime) {
55
+ if (isEnabled) {
56
+ Metrics.startRuntimeCollectors();
57
+ setInterval(() => {
58
+ let message_0 = getMetrics();
59
+ let message_1 = Metrics.sampleRuntime();
60
+ let message = {
61
+ kind: "snapshot",
62
+ metrics: message_0,
63
+ runtime: message_1
64
+ };
65
+ process.send(message);
66
+ }, 500);
67
+ process.on("message", message => onReleaseRealtime());
68
+ return;
69
+ }
70
+ }
71
+
72
+ export {
73
+ envVar,
74
+ configSchema,
75
+ detect,
76
+ config,
77
+ isEnabled,
78
+ bindToSupervisor,
79
+ bindRun,
80
+ }
81
+ /* configSchema Not a pure module */