envio 3.13.0 → 3.14.0-subgraph
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/bin.mjs +10 -1
- package/index.d.ts +11 -0
- package/package.json +8 -20
- package/rescript.json +1 -4
- package/src/Config.res +78 -100
- package/src/Config.res.mjs +55 -64
- package/src/Core.res +67 -6
- package/src/Core.res.mjs +58 -4
- package/src/EventConfigBuilder.res +4 -10
- package/src/EventConfigBuilder.res.mjs +1 -3
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +8 -6
- package/src/ExitOnCaughtUp.res +5 -1
- package/src/ExitOnCaughtUp.res.mjs +8 -3
- package/src/HandlerLoader.res +98 -36
- package/src/HandlerLoader.res.mjs +89 -24
- package/src/HandlerTypeCheck.mjs +14 -0
- package/src/HandlerTypeCheckWorker.mjs +68 -0
- package/src/Internal.res +1 -1
- package/src/LoadLayer.res +39 -7
- package/src/LoadLayer.res.mjs +65 -38
- package/src/LoadLayer.resi +28 -0
- package/src/Main.res +17 -12
- package/src/Main.res.mjs +9 -3
- package/src/NodeVersion.mjs +13 -0
- package/src/Persistence.res +79 -38
- package/src/Persistence.res.mjs +41 -10
- package/src/PgStorage.res +86 -39
- package/src/PgStorage.res.mjs +83 -32
- package/src/ResumePlan.res +172 -0
- package/src/ResumePlan.res.mjs +122 -0
- package/src/Supervisor.res +8 -8
- package/src/Supervisor.res.mjs +6 -1
- package/src/SyncWrites.res +86 -0
- package/src/SyncWrites.res.mjs +85 -0
- package/src/TestIndexer.res +14 -4
- package/src/TestIndexer.res.mjs +3 -2
- package/src/TsModuleHooks.mjs +121 -0
- package/src/UserContext.res +377 -62
- package/src/UserContext.res.mjs +297 -39
- package/src/Utils.res +0 -8
- package/src/Worker.res +8 -0
- package/src/Worker.res.mjs +11 -0
- package/src/bindings/NodeJs.res +8 -7
- package/src/bindings/NodeJs.res.mjs +0 -3
- package/src/bindings/Pino.res +2 -2
- package/src/db/InternalTable.res +52 -2
- package/src/db/InternalTable.res.mjs +32 -3
- package/src/db/Table.res +10 -0
- package/src/db/Table.res.mjs +22 -11
- package/src/sources/FuelHyperSync.res +2 -0
- package/src/sources/FuelHyperSync.res.mjs +1 -0
- package/src/sources/FuelHyperSync.resi +1 -0
- package/src/sources/FuelHyperSyncClient.res +25 -8
- package/src/sources/FuelHyperSyncClient.res.mjs +8 -2
- package/src/sources/FuelHyperSyncSource.res +24 -22
- package/src/sources/FuelHyperSyncSource.res.mjs +18 -13
- package/src/subgraph/abi-types.ts +63 -0
- package/src/subgraph/assemblyscript.ts +85 -0
- package/src/subgraph/blocks.ts +183 -0
- package/src/subgraph/calls.ts +149 -0
- package/src/subgraph/conformance.ts +83 -0
- package/src/subgraph/errors.ts +96 -0
- package/src/subgraph/graph-cli-worker.mjs +26 -0
- package/src/subgraph/graph-cli.ts +190 -0
- package/src/subgraph/graph-ts-enums.ts +28 -0
- package/src/subgraph/graph-ts-types/VERSION +2 -0
- package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
- package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
- package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
- package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
- package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
- package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
- package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
- package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
- package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
- package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
- package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
- package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
- package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
- package/src/subgraph/graph-ts-types/index.d.ts +102 -0
- package/src/subgraph/graph-ts.ts +2285 -0
- package/src/subgraph/hosts.ts +158 -0
- package/src/subgraph/rpc.ts +65 -0
- package/src/subgraph/runtime.ts +595 -0
- package/src/subgraph/scope.ts +68 -0
- package/src/tui/InitApi.res +85 -0
- package/src/tui/{components/CustomHooks.res.mjs → InitApi.res.mjs} +11 -64
- package/src/tui/Tui.res +142 -249
- package/src/tui/Tui.res.mjs +123 -332
- package/src/Js.shim.ts +0 -11
- package/src/bindings/DateFns.res +0 -74
- package/src/bindings/DateFns.res.mjs +0 -22
- package/src/sources/FuelSDK.res +0 -38
- package/src/sources/FuelSDK.res.mjs +0 -27
- package/src/sources/vendored-fuel-abi-coder.js +0 -1792
- package/src/tui/bindings/Ink.res +0 -369
- package/src/tui/bindings/Ink.res.mjs +0 -66
- package/src/tui/bindings/Style.res +0 -123
- package/src/tui/bindings/Style.res.mjs +0 -2
- package/src/tui/components/BufferedProgressBar.res +0 -40
- package/src/tui/components/BufferedProgressBar.res.mjs +0 -57
- package/src/tui/components/CustomHooks.res +0 -122
- package/src/tui/components/Messages.res +0 -40
- package/src/tui/components/Messages.res.mjs +0 -71
- package/src/tui/components/SyncETA.res +0 -179
- package/src/tui/components/SyncETA.res.mjs +0 -249
- package/src/tui/components/TuiData.res +0 -98
- package/src/tui/components/TuiData.res.mjs +0 -73
package/src/PgStorage.res.mjs
CHANGED
|
@@ -32,6 +32,7 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
|
32
32
|
import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
33
33
|
import * as ContractMapping from "./ContractMapping.res.mjs";
|
|
34
34
|
import * as IndexDefinition from "./db/IndexDefinition.res.mjs";
|
|
35
|
+
import * as Stdlib_Nullable from "@rescript/runtime/lib/es6/Stdlib_Nullable.js";
|
|
35
36
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
36
37
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
37
38
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
@@ -244,12 +245,16 @@ function getEntityHistory(entityConfig) {
|
|
|
244
245
|
return cache$1;
|
|
245
246
|
}
|
|
246
247
|
|
|
248
|
+
function makeCreatePartitionQuery(entityConfig, pgSchema, chainId) {
|
|
249
|
+
return `CREATE TABLE IF NOT EXISTS "` + pgSchema + `"."` + partitionTableName(entityConfig, chainId) + `" PARTITION OF "` + pgSchema + `"."` + entityConfig.table.tableName + `" FOR VALUES IN (` + ChainId.toString(chainId) + `);`;
|
|
250
|
+
}
|
|
251
|
+
|
|
247
252
|
function makeCreateEntityTableQueries(entityConfig, pgSchema, isNumericArrayAsText, chainIdModeOpt, chainIds) {
|
|
248
253
|
let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
|
|
249
254
|
let createTable = (table, partitionByColumn) => makeCreateTableQuery(table, pgSchema, isNumericArrayAsText, chainIdMode, partitionByColumn);
|
|
250
255
|
let chainIdField = Table.getChainIdField(entityConfig.table);
|
|
251
256
|
return (
|
|
252
|
-
chainIdField !== undefined ? [createTable(entityConfig.table, Table.getPgDbFieldName(chainIdField))].concat(chainIds.map(chainId =>
|
|
257
|
+
chainIdField !== undefined ? [createTable(entityConfig.table, Table.getPgDbFieldName(chainIdField))].concat(chainIds.map(chainId => makeCreatePartitionQuery(entityConfig, pgSchema, chainId))) : [createTable(entityConfig.table, undefined)]
|
|
253
258
|
).concat([createTable(getEntityHistory(entityConfig).table, undefined)]);
|
|
254
259
|
}
|
|
255
260
|
|
|
@@ -444,6 +449,7 @@ SELECT * FROM unnest(` + match.arrayFieldTypes.map((arrayFieldType, idx) => `$`
|
|
|
444
449
|
function makeInsertValuesSetQuery(pgSchema, table, itemSchema, itemsCount, chainIdModeOpt) {
|
|
445
450
|
let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
|
|
446
451
|
let match = Table.toSqlParams(table, itemSchema, pgSchema, chainIdMode);
|
|
452
|
+
let booleanArrayColumnIndexes = match.booleanArrayColumnIndexes;
|
|
447
453
|
let quotedNonPrimaryFieldNames = match.quotedNonPrimaryFieldNames;
|
|
448
454
|
let quotedFieldNames = match.quotedFieldNames;
|
|
449
455
|
let primaryKeyFieldNames = Table.getPgPrimaryKeyFieldNames(table);
|
|
@@ -459,6 +465,9 @@ function makeInsertValuesSetQuery(pgSchema, table, itemSchema, itemsCount, chain
|
|
|
459
465
|
placeholders = placeholders + ",";
|
|
460
466
|
}
|
|
461
467
|
placeholders = placeholders + (`$` + ((fieldIdx * itemsCount | 0) + idx | 0).toString());
|
|
468
|
+
if (booleanArrayColumnIndexes.includes(fieldIdx)) {
|
|
469
|
+
placeholders = placeholders + "::integer[]::boolean[]";
|
|
470
|
+
}
|
|
462
471
|
}
|
|
463
472
|
placeholders = placeholders + ")";
|
|
464
473
|
}
|
|
@@ -473,6 +482,7 @@ VALUES` + placeholders + (
|
|
|
473
482
|
function makeTableBatchSetQuery(pgSchema, table, itemSchema, chainIdModeOpt) {
|
|
474
483
|
let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
|
|
475
484
|
let match = Table.toSqlParams(table, itemSchema, pgSchema, chainIdMode);
|
|
485
|
+
let booleanArrayColumnIndexes = match.booleanArrayColumnIndexes;
|
|
476
486
|
let byteaColumnIndexes = match.byteaColumnIndexes;
|
|
477
487
|
let isRawEvents = table.tableName === InternalTable.RawEvents.table.tableName;
|
|
478
488
|
let isHistoryUpdate = table.tableName.startsWith(EntityHistory.historyTablePrefix);
|
|
@@ -493,7 +503,18 @@ function makeTableBatchSetQuery(pgSchema, table, itemSchema, chainIdModeOpt) {
|
|
|
493
503
|
return {
|
|
494
504
|
query: makeInsertValuesSetQuery(pgSchema, table, itemSchema, 500, chainIdMode),
|
|
495
505
|
convertOrThrow: S$RescriptSchema.compile(S$RescriptSchema.preprocess(S$RescriptSchema.unnest(itemSchema), param => ({
|
|
496
|
-
s:
|
|
506
|
+
s: columns => {
|
|
507
|
+
booleanArrayColumnIndexes.forEach(index => {
|
|
508
|
+
columns[index] = columns[index].map(value => Stdlib_Nullable.map(value, flags => flags.map(flag => {
|
|
509
|
+
if (flag) {
|
|
510
|
+
return 1;
|
|
511
|
+
} else {
|
|
512
|
+
return 0;
|
|
513
|
+
}
|
|
514
|
+
})));
|
|
515
|
+
});
|
|
516
|
+
return columns.flat(1);
|
|
517
|
+
}
|
|
497
518
|
})), "Output", "Input", "Sync", false),
|
|
498
519
|
isInsertValues: true
|
|
499
520
|
};
|
|
@@ -1234,7 +1255,6 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1234
1255
|
return {
|
|
1235
1256
|
cleanRun: true,
|
|
1236
1257
|
contractMapping: contractMapping,
|
|
1237
|
-
envioInfo: envioInfo,
|
|
1238
1258
|
cache: cache,
|
|
1239
1259
|
chains: chainConfigs.map((chainConfig, idx) => ({
|
|
1240
1260
|
id: chainConfig.id,
|
|
@@ -1500,7 +1520,58 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1500
1520
|
return Logging.errorWithExn(Utils.prettifyExn(exn$1), `Failed to dump cache.`);
|
|
1501
1521
|
}
|
|
1502
1522
|
};
|
|
1503
|
-
let
|
|
1523
|
+
let readStoredConfig = async () => {
|
|
1524
|
+
let match = await Promise.all([
|
|
1525
|
+
InternalTable.EnvioInfo.read(sql, pgSchema),
|
|
1526
|
+
InternalTable.EnvioContracts.read(sql, pgSchema)
|
|
1527
|
+
]);
|
|
1528
|
+
let contractNames = match[1];
|
|
1529
|
+
let envioInfo = match[0];
|
|
1530
|
+
if (envioInfo !== undefined) {
|
|
1531
|
+
if (contractNames !== undefined) {
|
|
1532
|
+
return {
|
|
1533
|
+
envioInfo: envioInfo,
|
|
1534
|
+
chains: await InternalTable.Chains.readStoredChains(sql, pgSchema),
|
|
1535
|
+
contractMapping: ContractMapping.fromStoredNames(contractNames)
|
|
1536
|
+
};
|
|
1537
|
+
} else {
|
|
1538
|
+
return {
|
|
1539
|
+
envioInfo: undefined,
|
|
1540
|
+
chains: [],
|
|
1541
|
+
contractMapping: ContractMapping.empty
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
} else {
|
|
1545
|
+
return {
|
|
1546
|
+
envioInfo: undefined,
|
|
1547
|
+
chains: [],
|
|
1548
|
+
contractMapping: ContractMapping.empty
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
let addChain = async (chainConfig, entities, contractMapping) => {
|
|
1553
|
+
let partitionQueries = entities.filter(entityConfig => {
|
|
1554
|
+
if (entityConfig.storage.postgres) {
|
|
1555
|
+
return Stdlib_Option.isSome(Table.getChainIdField(entityConfig.table));
|
|
1556
|
+
} else {
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
}).map(entityConfig => makeCreatePartitionQuery(entityConfig, pgSchema, chainConfig.id));
|
|
1560
|
+
let addressRows = ChainState.configStorageRows(chainConfig, ecosystem, contractMapping);
|
|
1561
|
+
await sql.begin(async sql => {
|
|
1562
|
+
for (let idx = 0, idx_finish = partitionQueries.length; idx < idx_finish; ++idx) {
|
|
1563
|
+
await sql.unsafe(partitionQueries[idx]);
|
|
1564
|
+
}
|
|
1565
|
+
if (Utils.$$Array.notEmpty(addressRows)) {
|
|
1566
|
+
await InternalTable.EnvioAddresses.insert(sql, pgSchema, addressRows, chainIdMode);
|
|
1567
|
+
}
|
|
1568
|
+
let query = InternalTable.Chains.makeInitialValuesQuery(pgSchema, [chainConfig]);
|
|
1569
|
+
if (query !== undefined) {
|
|
1570
|
+
return await sql.unsafe(query);
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
};
|
|
1574
|
+
let resumeInitialState = async (entities, chainIds, contractMapping) => {
|
|
1504
1575
|
let match = await Promise.all([
|
|
1505
1576
|
restoreEffectCache(false),
|
|
1506
1577
|
InternalTable.Chains.getInitialState(sql, pgSchema).then(rawInitialStates => {
|
|
@@ -1525,33 +1596,11 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1525
1596
|
]))
|
|
1526
1597
|
];
|
|
1527
1598
|
}),
|
|
1528
|
-
sql.unsafe(InternalTable.Checkpoints.makeGetReorgCheckpointsQuery(pgSchema))
|
|
1529
|
-
Promise.all([
|
|
1530
|
-
InternalTable.EnvioInfo.read(sql, pgSchema),
|
|
1531
|
-
InternalTable.EnvioContracts.read(sql, pgSchema)
|
|
1532
|
-
]).then(param => {
|
|
1533
|
-
let names = param[1];
|
|
1534
|
-
let info = param[0];
|
|
1535
|
-
if (info !== undefined && names !== undefined) {
|
|
1536
|
-
return [
|
|
1537
|
-
info,
|
|
1538
|
-
ContractMapping.fromStoredNames(names)
|
|
1539
|
-
];
|
|
1540
|
-
} else {
|
|
1541
|
-
return [
|
|
1542
|
-
undefined,
|
|
1543
|
-
ContractMapping.empty
|
|
1544
|
-
];
|
|
1545
|
-
}
|
|
1546
|
-
})
|
|
1599
|
+
sql.unsafe(InternalTable.Checkpoints.makeGetReorgCheckpointsQuery(pgSchema))
|
|
1547
1600
|
]);
|
|
1548
|
-
let match$1 = match[
|
|
1549
|
-
let
|
|
1550
|
-
let
|
|
1551
|
-
let match$2 = match[1];
|
|
1552
|
-
let checkpointFrontier = match$2[1];
|
|
1553
|
-
let chains = match$2[0];
|
|
1554
|
-
throwIfIncompatible(storedEnvioInfo, storedContractMapping);
|
|
1601
|
+
let match$1 = match[1];
|
|
1602
|
+
let checkpointFrontier = match$1[1];
|
|
1603
|
+
let chains = match$1[0];
|
|
1555
1604
|
await reloadIndexCatalog();
|
|
1556
1605
|
let reorgCheckpoints = match[2].map(raw => ({
|
|
1557
1606
|
id: BigInt(raw.id),
|
|
@@ -1564,8 +1613,7 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1564
1613
|
}
|
|
1565
1614
|
return {
|
|
1566
1615
|
cleanRun: false,
|
|
1567
|
-
contractMapping:
|
|
1568
|
-
envioInfo: storedEnvioInfo,
|
|
1616
|
+
contractMapping: contractMapping,
|
|
1569
1617
|
cache: match[0],
|
|
1570
1618
|
chains: chains,
|
|
1571
1619
|
checkpointFrontier: checkpointFrontier,
|
|
@@ -1647,6 +1695,8 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1647
1695
|
name: storageName,
|
|
1648
1696
|
isInitialized: isInitialized,
|
|
1649
1697
|
initialize: initialize,
|
|
1698
|
+
readStoredConfig: readStoredConfig,
|
|
1699
|
+
addChain: addChain,
|
|
1650
1700
|
resumeInitialState: resumeInitialState,
|
|
1651
1701
|
loadOrThrow: loadOrThrow,
|
|
1652
1702
|
ensureQueryIndexes: ensureQueryIndexes,
|
|
@@ -1720,6 +1770,7 @@ export {
|
|
|
1720
1770
|
getRowSchema,
|
|
1721
1771
|
entityHistoryCache,
|
|
1722
1772
|
getEntityHistory,
|
|
1773
|
+
makeCreatePartitionQuery,
|
|
1723
1774
|
makeCreateEntityTableQueries,
|
|
1724
1775
|
makeInitializeTransaction,
|
|
1725
1776
|
makeLoadQuery,
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// A chain as its storage holds it: what its `envio_chains` row keeps of its
|
|
2
|
+
// config, and the addresses the config declared for it.
|
|
3
|
+
type storedChain = {
|
|
4
|
+
id: ChainId.t,
|
|
5
|
+
ecosystem: string,
|
|
6
|
+
startBlock: int,
|
|
7
|
+
endBlock: option<int>,
|
|
8
|
+
maxReorgDepth: int,
|
|
9
|
+
configAddresses: array<AddressRows.row>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// What a storage holds of the config it was built from. `envioInfo` is `None`
|
|
13
|
+
// for a storage an older envio built, which kept no such record.
|
|
14
|
+
type stored = {
|
|
15
|
+
envioInfo: option<JSON.t>,
|
|
16
|
+
chains: array<storedChain>,
|
|
17
|
+
contractMapping: ContractMapping.t,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type t =
|
|
21
|
+
| Resume
|
|
22
|
+
// Configured since the storage was built, and the only chain its
|
|
23
|
+
// `envio start --chain` process drives.
|
|
24
|
+
| AddChain(Config.chain)
|
|
25
|
+
| Incompatible(array<string>)
|
|
26
|
+
|
|
27
|
+
// The part of a chain's config its storage keeps, shaped so a stored chain and
|
|
28
|
+
// a configured one diff by the path config.yaml spells: `chains.137.endBlock`,
|
|
29
|
+
// `chains.137.contracts.Token.addresses.0x…`.
|
|
30
|
+
let chainEntry = (
|
|
31
|
+
~startBlock: option<int>,
|
|
32
|
+
~endBlock: option<int>,
|
|
33
|
+
~maxReorgDepth: int,
|
|
34
|
+
~addresses: array<(string, string)>,
|
|
35
|
+
) => {
|
|
36
|
+
let contracts = Dict.make()
|
|
37
|
+
addresses->Array.forEach(((contractName, address)) =>
|
|
38
|
+
contracts->Utils.Dict.push(contractName, address)
|
|
39
|
+
)
|
|
40
|
+
let entry = dict{
|
|
41
|
+
"maxReorgDepth": JSON.Number(maxReorgDepth->Int.toFloat),
|
|
42
|
+
"contracts": JSON.Object(
|
|
43
|
+
contracts->Dict.mapValues(addresses => JSON.Object(
|
|
44
|
+
dict{
|
|
45
|
+
"addresses": JSON.Object(
|
|
46
|
+
addresses->Array.map(address => (address, JSON.Boolean(true)))->Dict.fromArray,
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
)),
|
|
50
|
+
),
|
|
51
|
+
}
|
|
52
|
+
startBlock->Option.forEach(startBlock =>
|
|
53
|
+
entry->Dict.set("startBlock", JSON.Number(startBlock->Int.toFloat))
|
|
54
|
+
)
|
|
55
|
+
endBlock->Option.forEach(endBlock =>
|
|
56
|
+
entry->Dict.set("endBlock", JSON.Number(endBlock->Int.toFloat))
|
|
57
|
+
)
|
|
58
|
+
JSON.Object(entry)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let withChains = (envioInfo: JSON.t, ~chains: array<(ChainId.t, JSON.t)>) =>
|
|
62
|
+
switch envioInfo {
|
|
63
|
+
| Object(fields) =>
|
|
64
|
+
let joined = fields->Dict.copy
|
|
65
|
+
joined->Dict.set(
|
|
66
|
+
"chains",
|
|
67
|
+
JSON.Object(
|
|
68
|
+
chains->Array.map(((chainId, entry)) => (chainId->ChainId.toString, entry))->Dict.fromArray,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
JSON.Object(joined)
|
|
72
|
+
| _ => envioInfo
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// `envioInfo` is the running config's, `chainConfigs` the chains this process
|
|
76
|
+
// drives. An isolated process answers for `envioInfo` and its own chains only:
|
|
77
|
+
// the ones it leaves out are checked, and added, by the processes that drive
|
|
78
|
+
// them.
|
|
79
|
+
let make = (
|
|
80
|
+
~stored: stored,
|
|
81
|
+
~envioInfo: JSON.t,
|
|
82
|
+
~chainConfigs: array<Config.chain>,
|
|
83
|
+
~contractMapping: ContractMapping.t,
|
|
84
|
+
~lowercaseAddresses: bool,
|
|
85
|
+
~isolated: bool,
|
|
86
|
+
) =>
|
|
87
|
+
switch stored.envioInfo {
|
|
88
|
+
| None => Incompatible(["storage was initialized by an older envio version"])
|
|
89
|
+
| Some(storedEnvioInfo) =>
|
|
90
|
+
let storedChainOf = chainId => stored.chains->Array.find(chain => chain.id == chainId)
|
|
91
|
+
let added = switch (isolated, chainConfigs) {
|
|
92
|
+
| (true, [chain]) if storedChainOf(chain.id)->Option.isNone => Some(chain)
|
|
93
|
+
| _ => None
|
|
94
|
+
}
|
|
95
|
+
let storedChains = isolated
|
|
96
|
+
? stored.chains->Array.filter(storedChain =>
|
|
97
|
+
chainConfigs->Array.some(chain => chain.id == storedChain.id)
|
|
98
|
+
)
|
|
99
|
+
: stored.chains
|
|
100
|
+
let storedEntries = storedChains->Array.map(storedChain => {
|
|
101
|
+
let addresses =
|
|
102
|
+
storedChain.configAddresses
|
|
103
|
+
->AddressRows.render(~ecosystem=storedChain.ecosystem, ~shouldChecksum=!lowercaseAddresses)
|
|
104
|
+
->Array.mapWithIndex((address, idx) => (
|
|
105
|
+
stored.contractMapping->ContractMapping.nameOfOrThrow(
|
|
106
|
+
(storedChain.configAddresses->Array.getUnsafe(idx)).contractId,
|
|
107
|
+
),
|
|
108
|
+
address->Address.toString,
|
|
109
|
+
))
|
|
110
|
+
(
|
|
111
|
+
storedChain.id,
|
|
112
|
+
chainEntry(
|
|
113
|
+
~startBlock=Some(storedChain.startBlock),
|
|
114
|
+
~endBlock=storedChain.endBlock,
|
|
115
|
+
~maxReorgDepth=storedChain.maxReorgDepth,
|
|
116
|
+
~addresses,
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
let currentEntries = chainConfigs->Array.filterMap(chain =>
|
|
121
|
+
switch added {
|
|
122
|
+
| Some(added) if added.id == chain.id => None
|
|
123
|
+
| _ =>
|
|
124
|
+
Some((
|
|
125
|
+
chain.id,
|
|
126
|
+
chainEntry(
|
|
127
|
+
// `latest` is whatever it resolved to when the chain was first
|
|
128
|
+
// stored, so it matches any stored block.
|
|
129
|
+
~startBlock=switch chain.startBlock {
|
|
130
|
+
| Block(startBlock) => Some(startBlock)
|
|
131
|
+
| Latest => storedChainOf(chain.id)->Option.map(storedChain => storedChain.startBlock)
|
|
132
|
+
},
|
|
133
|
+
~endBlock=chain.endBlock,
|
|
134
|
+
~maxReorgDepth=chain.maxReorgDepth,
|
|
135
|
+
~addresses=chain.contracts->Array.flatMap(contract =>
|
|
136
|
+
contract.addresses->Array.map(address => (contract.name, address->Address.toString))
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
))
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
let changedPaths = Config.diffPaths(
|
|
143
|
+
~stored=storedEnvioInfo->withChains(~chains=storedEntries),
|
|
144
|
+
~current=envioInfo->withChains(~chains=currentEntries),
|
|
145
|
+
)
|
|
146
|
+
let changedPaths =
|
|
147
|
+
stored.contractMapping->ContractMapping.isEqual(contractMapping)
|
|
148
|
+
? changedPaths
|
|
149
|
+
: changedPaths->Array.concat(["contracts"])
|
|
150
|
+
switch (changedPaths, added) {
|
|
151
|
+
| ([], None) => Resume
|
|
152
|
+
| ([], Some(chain)) => AddChain(chain)
|
|
153
|
+
| (changedPaths, _) => Incompatible(changedPaths)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let incompatibleMessage = (
|
|
158
|
+
changedPaths,
|
|
159
|
+
~envioInfo: JSON.t,
|
|
160
|
+
~resetCommand: string,
|
|
161
|
+
~runCommand: option<string>,
|
|
162
|
+
) => {
|
|
163
|
+
let hasClickhouse = switch envioInfo {
|
|
164
|
+
| Object(d) =>
|
|
165
|
+
switch d->Dict.get("storage") {
|
|
166
|
+
| Some(Object(s)) => s->Dict.get("clickhouse") == Some(Boolean(true))
|
|
167
|
+
| _ => false
|
|
168
|
+
}
|
|
169
|
+
| _ => false
|
|
170
|
+
}
|
|
171
|
+
Config.incompatibleMessage(changedPaths, ~resetCommand, ~runCommand, ~hasClickhouse)
|
|
172
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Utils from "./Utils.res.mjs";
|
|
4
|
+
import * as Config from "./Config.res.mjs";
|
|
5
|
+
import * as ChainId from "./ChainId.res.mjs";
|
|
6
|
+
import * as AddressRows from "./AddressRows.res.mjs";
|
|
7
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
8
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
9
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
10
|
+
import * as ContractMapping from "./ContractMapping.res.mjs";
|
|
11
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
12
|
+
|
|
13
|
+
function chainEntry(startBlock, endBlock, maxReorgDepth, addresses) {
|
|
14
|
+
let contracts = {};
|
|
15
|
+
addresses.forEach(param => Utils.Dict.push(contracts, param[0], param[1]));
|
|
16
|
+
let entry = {
|
|
17
|
+
maxReorgDepth: maxReorgDepth,
|
|
18
|
+
contracts: Stdlib_Dict.mapValues(contracts, addresses => ({
|
|
19
|
+
addresses: Object.fromEntries(addresses.map(address => [
|
|
20
|
+
address,
|
|
21
|
+
true
|
|
22
|
+
]))
|
|
23
|
+
}))
|
|
24
|
+
};
|
|
25
|
+
Stdlib_Option.forEach(startBlock, startBlock => {
|
|
26
|
+
entry["startBlock"] = startBlock;
|
|
27
|
+
});
|
|
28
|
+
Stdlib_Option.forEach(endBlock, endBlock => {
|
|
29
|
+
entry["endBlock"] = endBlock;
|
|
30
|
+
});
|
|
31
|
+
return entry;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function withChains(envioInfo, chains) {
|
|
35
|
+
if (typeof envioInfo !== "object" || envioInfo === null || Array.isArray(envioInfo)) {
|
|
36
|
+
return envioInfo;
|
|
37
|
+
}
|
|
38
|
+
let joined = Object.assign({}, envioInfo);
|
|
39
|
+
joined["chains"] = Object.fromEntries(chains.map(param => [
|
|
40
|
+
ChainId.toString(param[0]),
|
|
41
|
+
param[1]
|
|
42
|
+
]));
|
|
43
|
+
return joined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function make(stored, envioInfo, chainConfigs, contractMapping, lowercaseAddresses, isolated) {
|
|
47
|
+
let storedEnvioInfo = stored.envioInfo;
|
|
48
|
+
if (storedEnvioInfo === undefined) {
|
|
49
|
+
return {
|
|
50
|
+
TAG: "Incompatible",
|
|
51
|
+
_0: ["storage was initialized by an older envio version"]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
let storedChainOf = chainId => stored.chains.find(chain => Primitive_object.equal(chain.id, chainId));
|
|
55
|
+
let added;
|
|
56
|
+
if (isolated && chainConfigs.length === 1) {
|
|
57
|
+
let chain = chainConfigs[0];
|
|
58
|
+
added = Stdlib_Option.isNone(storedChainOf(chain.id)) ? chain : undefined;
|
|
59
|
+
} else {
|
|
60
|
+
added = undefined;
|
|
61
|
+
}
|
|
62
|
+
let storedChains = isolated ? stored.chains.filter(storedChain => chainConfigs.some(chain => Primitive_object.equal(chain.id, storedChain.id))) : stored.chains;
|
|
63
|
+
let storedEntries = storedChains.map(storedChain => {
|
|
64
|
+
let addresses = AddressRows.render(storedChain.configAddresses, storedChain.ecosystem, !lowercaseAddresses).map((address, idx) => [
|
|
65
|
+
ContractMapping.nameOfOrThrow(stored.contractMapping, storedChain.configAddresses[idx].contractId),
|
|
66
|
+
address
|
|
67
|
+
]);
|
|
68
|
+
return [
|
|
69
|
+
storedChain.id,
|
|
70
|
+
chainEntry(storedChain.startBlock, storedChain.endBlock, storedChain.maxReorgDepth, addresses)
|
|
71
|
+
];
|
|
72
|
+
});
|
|
73
|
+
let currentEntries = Stdlib_Array.filterMap(chainConfigs, chain => {
|
|
74
|
+
if (added !== undefined && Primitive_object.equal(added.id, chain.id)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let startBlock = chain.startBlock;
|
|
78
|
+
let tmp;
|
|
79
|
+
tmp = startBlock === "latest" ? Stdlib_Option.map(storedChainOf(chain.id), storedChain => storedChain.startBlock) : startBlock;
|
|
80
|
+
return [
|
|
81
|
+
chain.id,
|
|
82
|
+
chainEntry(tmp, chain.endBlock, chain.maxReorgDepth, chain.contracts.flatMap(contract => contract.addresses.map(address => [
|
|
83
|
+
contract.name,
|
|
84
|
+
address
|
|
85
|
+
])))
|
|
86
|
+
];
|
|
87
|
+
});
|
|
88
|
+
let changedPaths = Config.diffPaths(withChains(storedEnvioInfo, storedEntries), withChains(envioInfo, currentEntries));
|
|
89
|
+
let changedPaths$1 = ContractMapping.isEqual(stored.contractMapping, contractMapping) ? changedPaths : changedPaths.concat(["contracts"]);
|
|
90
|
+
if (changedPaths$1.length !== 0) {
|
|
91
|
+
return {
|
|
92
|
+
TAG: "Incompatible",
|
|
93
|
+
_0: changedPaths$1
|
|
94
|
+
};
|
|
95
|
+
} else if (added !== undefined) {
|
|
96
|
+
return {
|
|
97
|
+
TAG: "AddChain",
|
|
98
|
+
_0: added
|
|
99
|
+
};
|
|
100
|
+
} else {
|
|
101
|
+
return "Resume";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function incompatibleMessage(changedPaths, envioInfo, resetCommand, runCommand) {
|
|
106
|
+
let hasClickhouse;
|
|
107
|
+
if (typeof envioInfo === "object" && envioInfo !== null && !Array.isArray(envioInfo)) {
|
|
108
|
+
let match = envioInfo["storage"];
|
|
109
|
+
hasClickhouse = typeof match === "object" && match !== null && !Array.isArray(match) ? Primitive_object.equal(match["clickhouse"], true) : false;
|
|
110
|
+
} else {
|
|
111
|
+
hasClickhouse = false;
|
|
112
|
+
}
|
|
113
|
+
return Config.incompatibleMessage(changedPaths, resetCommand, runCommand, hasClickhouse);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
chainEntry,
|
|
118
|
+
withChains,
|
|
119
|
+
make,
|
|
120
|
+
incompatibleMessage,
|
|
121
|
+
}
|
|
122
|
+
/* Utils Not a pure module */
|
package/src/Supervisor.res
CHANGED
|
@@ -183,7 +183,7 @@ let fork = (
|
|
|
183
183
|
)
|
|
184
184
|
if pipeOutput {
|
|
185
185
|
// The supervisor writes a worker's lines the way it writes its own, which is
|
|
186
|
-
// the only way
|
|
186
|
+
// the only way the display can print them above its frame. Each stream keeps the one
|
|
187
187
|
// it was written to, so a worker's errors stay on stderr for whoever is
|
|
188
188
|
// redirecting it.
|
|
189
189
|
[
|
|
@@ -430,12 +430,12 @@ let run = async (~config: Config.t, ~workers: array<worker>, ~reset) => {
|
|
|
430
430
|
// same initialization an unsplit run does, and the supervisor hands the
|
|
431
431
|
// connections it used to its workers.
|
|
432
432
|
let persistence = PgStorage.makePersistenceFromConfig(~config)
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
~isDevelopmentMode=config.isDev,
|
|
437
|
-
~
|
|
438
|
-
)
|
|
433
|
+
// Checked here once, so the workers don't each check the same handlers.
|
|
434
|
+
let autoLoadFiles = await HandlerLoader.getAutoLoadFiles(~config)
|
|
435
|
+
let _ = await Promise.all2((
|
|
436
|
+
persistence->Persistence.initForRun(~config, ~reset, ~isDevelopmentMode=config.isDev),
|
|
437
|
+
HandlerLoader.typeCheck(~config, ~autoLoadFiles),
|
|
438
|
+
))
|
|
439
439
|
await persistence.storage.close()
|
|
440
440
|
|
|
441
441
|
let startTime = Date.make()
|
|
@@ -508,7 +508,7 @@ let run = async (~config: Config.t, ~workers: array<worker>, ~reset) => {
|
|
|
508
508
|
)
|
|
509
509
|
|
|
510
510
|
if shouldUseTui {
|
|
511
|
-
|
|
511
|
+
Tui.start(~config, ~getMetrics=() =>
|
|
512
512
|
switch reported() {
|
|
513
513
|
| [] => {...[]->merge, chains: configuredChains(config)}
|
|
514
514
|
| snapshots => snapshots->merge
|
package/src/Supervisor.res.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import * as PgStorage from "./PgStorage.res.mjs";
|
|
|
15
15
|
import * as Performance from "./bindings/Performance.res.mjs";
|
|
16
16
|
import * as Persistence from "./Persistence.res.mjs";
|
|
17
17
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
18
|
+
import * as HandlerLoader from "./HandlerLoader.res.mjs";
|
|
18
19
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
19
20
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
20
21
|
import * as Child_process from "child_process";
|
|
@@ -367,7 +368,11 @@ function configuredChains(config) {
|
|
|
367
368
|
|
|
368
369
|
async function run(config, workers, reset) {
|
|
369
370
|
let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
|
|
370
|
-
await
|
|
371
|
+
let autoLoadFiles = await HandlerLoader.getAutoLoadFiles(config);
|
|
372
|
+
await Promise.all([
|
|
373
|
+
Persistence.initForRun(persistence, config, reset, config.isDev),
|
|
374
|
+
HandlerLoader.typeCheck(config, autoLoadFiles)
|
|
375
|
+
]);
|
|
371
376
|
await persistence.storage.close();
|
|
372
377
|
let startTime = new Date();
|
|
373
378
|
let startTimeRef = Performance.now();
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// The writes one round of a synchronous handler has made. A round that
|
|
2
|
+
// suspends is replayed from the top, so its writes are held here rather than
|
|
3
|
+
// applied: the replay has to see the store exactly as the first round did, or a
|
|
4
|
+
// read-modify-write lands once per round.
|
|
5
|
+
|
|
6
|
+
type table = {
|
|
7
|
+
entityConfig: Internal.entityConfig,
|
|
8
|
+
scope: Internal.chainScope,
|
|
9
|
+
changes: dict<Change.t<Internal.entity>>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// A handler runs on one chain, so an entity's scope is fixed for the whole
|
|
13
|
+
// round and the entity name is enough to key its table.
|
|
14
|
+
type t = dict<table>
|
|
15
|
+
|
|
16
|
+
let make = (): t => Dict.make()
|
|
17
|
+
|
|
18
|
+
let record = (writes: t, ~entityConfig: Internal.entityConfig, ~scope, change) => {
|
|
19
|
+
let table = switch writes->Utils.Dict.dangerouslyGetNonOption(entityConfig.name) {
|
|
20
|
+
| Some(table) => table
|
|
21
|
+
| None =>
|
|
22
|
+
let table = {entityConfig, scope, changes: Dict.make()}
|
|
23
|
+
writes->Dict.set(entityConfig.name, table)
|
|
24
|
+
table
|
|
25
|
+
}
|
|
26
|
+
table.changes->Dict.set(change->Change.getEntityId->EntityId.toKey, change)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let changesOf = (writes: t, ~entityConfig: Internal.entityConfig) =>
|
|
30
|
+
switch writes->Utils.Dict.dangerouslyGetNonOption(entityConfig.name) {
|
|
31
|
+
| Some({changes}) => Some(changes)
|
|
32
|
+
| None => None
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// `Some(None)` is a delete this round made: the entity is known not to exist.
|
|
36
|
+
let getById = (writes: t, ~entityConfig, ~entityId: string) =>
|
|
37
|
+
switch writes->changesOf(~entityConfig) {
|
|
38
|
+
| Some(changes) =>
|
|
39
|
+
changes
|
|
40
|
+
->Utils.Dict.dangerouslyGetNonOption(entityId)
|
|
41
|
+
->Option.map(InMemoryTable.Entity.mapChangeToEntity)
|
|
42
|
+
| None => None
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// What the store answered, as it reads once this round's writes land: a row
|
|
46
|
+
// the round rewrote is re-tested against the filter, and one it created joins.
|
|
47
|
+
let overlayFilter = (
|
|
48
|
+
writes: t,
|
|
49
|
+
entities: array<Internal.entity>,
|
|
50
|
+
~entityConfig: Internal.entityConfig,
|
|
51
|
+
~filter: EntityFilter.t,
|
|
52
|
+
) =>
|
|
53
|
+
switch writes->changesOf(~entityConfig) {
|
|
54
|
+
| None => entities
|
|
55
|
+
| Some(changes) =>
|
|
56
|
+
let matches = filter->EntityFilter.makeMatcher(~table=entityConfig.table)
|
|
57
|
+
let untouched =
|
|
58
|
+
entities->Array.filter(entity =>
|
|
59
|
+
!(changes->Dict.has(entity->InMemoryTable.Entity.getEntityIdUnsafe))
|
|
60
|
+
)
|
|
61
|
+
let written =
|
|
62
|
+
changes
|
|
63
|
+
->Dict.valuesToArray
|
|
64
|
+
->Array.filterMap(change =>
|
|
65
|
+
switch change->InMemoryTable.Entity.mapChangeToEntity {
|
|
66
|
+
| Some(entity) if matches(entity) => Some(entity)
|
|
67
|
+
| _ => None
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
untouched->Array.concat(written)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// How a change lands in the in-memory store, whether written directly or
|
|
74
|
+
// committed at the end of a round.
|
|
75
|
+
let write = (indexerState, ~entityConfig, ~scope, change) =>
|
|
76
|
+
indexerState
|
|
77
|
+
->InMemoryStore.getInMemTable(~entityConfig, ~scope)
|
|
78
|
+
->InMemoryTable.Entity.set(
|
|
79
|
+
~committedCheckpointId=indexerState->IndexerState.committedCheckpointIdFor(~scope),
|
|
80
|
+
change,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
let commit = (writes: t, ~indexerState) =>
|
|
84
|
+
writes->Utils.Dict.forEach(({entityConfig, scope, changes}) =>
|
|
85
|
+
changes->Utils.Dict.forEach(change => indexerState->write(~entityConfig, ~scope, change))
|
|
86
|
+
)
|