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.
- package/package.json +6 -8
- package/src/BatchProcessing.res +15 -14
- package/src/BatchProcessing.res.mjs +8 -15
- package/src/Bin.res +24 -17
- package/src/Bin.res.mjs +5 -0
- package/src/ChainFetching.res +18 -24
- package/src/ChainFetching.res.mjs +14 -15
- package/src/ChainState.res +103 -38
- package/src/ChainState.res.mjs +79 -22
- package/src/ChainState.resi +14 -2
- package/src/Config.res +32 -0
- package/src/Config.res.mjs +24 -0
- package/src/Core.res +4 -0
- package/src/Core.res.mjs +4 -0
- package/src/CrossChainState.res +109 -11
- package/src/CrossChainState.res.mjs +71 -7
- package/src/CrossChainState.resi +11 -1
- package/src/Env.res +4 -0
- package/src/ErrorHandling.res +15 -4
- package/src/ErrorHandling.res.mjs +16 -5
- package/src/FinalizeBackfill.res +10 -4
- package/src/FinalizeBackfill.res.mjs +5 -3
- package/src/HandlerRegister.res +0 -37
- package/src/HandlerRegister.res.mjs +0 -18
- package/src/IndexerLoop.res +2 -0
- package/src/IndexerLoop.res.mjs +1 -0
- package/src/IndexerState.res +43 -1
- package/src/IndexerState.res.mjs +48 -4
- package/src/IndexerState.resi +11 -0
- package/src/Main.res +128 -268
- package/src/Main.res.mjs +27 -153
- package/src/Metrics.res +268 -102
- package/src/Metrics.res.mjs +227 -48
- package/src/Persistence.res +30 -2
- package/src/Persistence.res.mjs +12 -3
- package/src/PgStorage.res +19 -11
- package/src/PgStorage.res.mjs +13 -10
- package/src/Rollback.res +3 -3
- package/src/Server.res +178 -0
- package/src/Server.res.mjs +143 -0
- package/src/SimulateItems.res +1 -1
- package/src/SimulateItems.res.mjs +1 -1
- package/src/Supervisor.res +542 -0
- package/src/Supervisor.res.mjs +455 -0
- package/src/TestIndexer.res +1 -1
- package/src/TestIndexer.res.mjs +2 -2
- package/src/Worker.res +101 -0
- package/src/Worker.res.mjs +81 -0
- package/src/bindings/NodeJs.res +50 -0
- package/src/bindings/NodeJs.res.mjs +8 -1
- package/src/db/InternalTable.res +8 -1
- package/src/db/InternalTable.res.mjs +5 -1
- package/src/sources/AddressSet.res +5 -3
- package/src/sources/AddressStore.res +19 -11
- package/src/sources/AddressStore.res.mjs +2 -7
- package/src/sources/BlockStore.res +10 -9
- package/src/sources/BlockStore.res.mjs +4 -4
- package/src/sources/ChainSources.res +4 -0
- package/src/sources/ChainSources.res.mjs +2 -2
- package/src/sources/EvmChain.res +4 -0
- package/src/sources/EvmChain.res.mjs +3 -1
- package/src/sources/EvmEventItem.res +56 -0
- package/src/sources/EvmEventItem.res.mjs +32 -0
- package/src/sources/EvmHyperSyncSource.res +7 -37
- package/src/sources/EvmHyperSyncSource.res.mjs +8 -29
- package/src/sources/EvmRpcClient.res +41 -59
- package/src/sources/EvmRpcClient.res.mjs +4 -54
- package/src/sources/EvmRpcWs.res +4 -1
- package/src/sources/EvmRpcWs.res.mjs +6 -2
- package/src/sources/FuelHyperSyncSource.res +6 -4
- package/src/sources/FuelHyperSyncSource.res.mjs +7 -9
- package/src/sources/HeightFeed.res +7 -1
- package/src/sources/HeightFeed.res.mjs +9 -7
- package/src/sources/HyperSyncClient.res +1 -14
- package/src/sources/RequestStat.res +7 -0
- package/src/sources/RequestStat.res.mjs +14 -1
- package/src/sources/RpcSource.res +100 -1043
- package/src/sources/RpcSource.res.mjs +81 -1056
- package/src/sources/SimulateSource.res +2 -5
- package/src/sources/SimulateSource.res.mjs +2 -3
- package/src/sources/Source.res +34 -12
- package/src/sources/Source.res.mjs +11 -0
- package/src/sources/SourceManager.res +111 -31
- package/src/sources/SourceManager.res.mjs +65 -31
- package/src/sources/SourceManager.resi +4 -0
- package/src/sources/StartBlockResolver.res +8 -5
- package/src/sources/StartBlockResolver.res.mjs +4 -2
- package/src/sources/SvmHyperSyncSource.res +4 -2
- package/src/sources/SvmHyperSyncSource.res.mjs +4 -6
- package/src/sources/TransactionStore.res +9 -6
- package/src/sources/TransactionStore.res.mjs +2 -2
- package/src/tui/Tui.res +10 -0
- package/src/tui/Tui.res.mjs +15 -0
- package/src/tui/components/SyncETA.res +12 -6
- package/src/tui/components/SyncETA.res.mjs +12 -8
- package/src/LazyLoader.res +0 -135
- package/src/LazyLoader.res.mjs +0 -118
- package/src/bindings/SDSL.res +0 -12
- package/src/bindings/SDSL.res.mjs +0 -9
- package/src/bindings/Yargs.res +0 -8
- package/src/bindings/Yargs.res.mjs +0 -2
- package/src/sources/Rpc.res +0 -185
- 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 */
|
package/src/TestIndexer.res
CHANGED
|
@@ -948,7 +948,7 @@ let createTestIndexer = (): t<'processConfig> => {
|
|
|
948
948
|
~exitAfterFirstEventBlock,
|
|
949
949
|
~onError=errHandler => {
|
|
950
950
|
errHandler->ErrorHandling.log
|
|
951
|
-
reject(errHandler
|
|
951
|
+
reject(errHandler->ErrorHandling.toExn)
|
|
952
952
|
},
|
|
953
953
|
// Caught up: resolve the run instead of exiting the process.
|
|
954
954
|
~onExit=() => resolve(),
|
package/src/TestIndexer.res.mjs
CHANGED
|
@@ -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(
|
|
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 */
|