envio 3.3.1 → 3.4.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 -6
- package/src/Api.res +1 -8
- package/src/Api.res.mjs +1 -5
- package/src/ChainState.res +6 -1
- package/src/ChainState.res.mjs +4 -3
- package/src/Config.res +33 -4
- package/src/Config.res.mjs +34 -5
- package/src/Core.res +30 -19
- package/src/Core.res.mjs +5 -5
- package/src/EnvioGlobal.res +0 -2
- package/src/EnvioGlobal.res.mjs +0 -1
- package/src/ExitOnCaughtUp.res +6 -2
- package/src/ExitOnCaughtUp.res.mjs +4 -0
- package/src/FetchState.res +3 -3
- package/src/HandlerRegister.res +357 -434
- package/src/HandlerRegister.res.mjs +202 -242
- package/src/HandlerRegister.resi +7 -15
- package/src/IndexerState.res +10 -0
- package/src/IndexerState.res.mjs +9 -3
- package/src/IndexerState.resi +3 -0
- package/src/Internal.res +10 -1
- package/src/Main.res.mjs +4 -4
- package/src/PgStorage.res +52 -26
- package/src/PgStorage.res.mjs +32 -23
- package/src/SimulateItems.res +61 -40
- package/src/SimulateItems.res.mjs +37 -21
- package/src/TestIndexer.res +446 -453
- package/src/TestIndexer.res.mjs +320 -343
- package/src/bindings/ClickHouse.res +68 -2
- package/src/bindings/ClickHouse.res.mjs +47 -2
- package/src/sources/EvmChain.res +1 -1
- package/src/sources/EvmChain.res.mjs +2 -2
- package/src/sources/FuelHyperSync.res +74 -0
- package/src/sources/FuelHyperSync.res.mjs +80 -0
- package/src/sources/FuelHyperSync.resi +22 -0
- package/src/sources/FuelHyperSyncClient.res +120 -0
- package/src/sources/FuelHyperSyncClient.res.mjs +71 -0
- package/src/sources/FuelHyperSyncSource.res +257 -0
- package/src/sources/FuelHyperSyncSource.res.mjs +252 -0
- package/src/sources/HyperSyncClient.res +2 -2
- package/src/sources/HyperSyncClient.res.mjs +1 -1
- package/src/sources/SvmHyperSyncClient.res +139 -102
- package/src/sources/SvmHyperSyncClient.res.mjs +45 -5
- package/src/sources/SvmHyperSyncSource.res +60 -440
- package/src/sources/SvmHyperSyncSource.res.mjs +42 -363
- package/src/TestIndexerProxyStorage.res +0 -196
- package/src/TestIndexerProxyStorage.res.mjs +0 -121
- package/src/TestIndexerWorker.res +0 -4
- package/src/TestIndexerWorker.res.mjs +0 -7
- package/src/sources/EventRouter.res +0 -165
- package/src/sources/EventRouter.res.mjs +0 -153
- package/src/sources/HyperFuel.res +0 -179
- package/src/sources/HyperFuel.res.mjs +0 -146
- package/src/sources/HyperFuel.resi +0 -36
- package/src/sources/HyperFuelClient.res +0 -127
- package/src/sources/HyperFuelClient.res.mjs +0 -20
- package/src/sources/HyperFuelSource.res +0 -502
- package/src/sources/HyperFuelSource.res.mjs +0 -481
- /package/src/sources/{HyperSyncSource.res → EvmHyperSyncSource.res} +0 -0
- /package/src/sources/{HyperSyncSource.res.mjs → EvmHyperSyncSource.res.mjs} +0 -0
package/src/IndexerState.res.mjs
CHANGED
|
@@ -44,7 +44,7 @@ function get(self, entityName) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt, committedCheckpointIdOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError) {
|
|
47
|
+
function make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt, committedCheckpointIdOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
|
|
48
48
|
let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : CrossChainState.calculateTargetBufferSize();
|
|
49
49
|
let committedCheckpointId = committedCheckpointIdOpt !== undefined ? committedCheckpointIdOpt : Internal.initialCheckpointId;
|
|
50
50
|
let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
|
|
@@ -81,6 +81,7 @@ function make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime
|
|
|
81
81
|
keepProcessAlive: isDevelopmentMode || shouldUseTui,
|
|
82
82
|
exitAfterFirstEventBlock: exitAfterFirstEventBlock,
|
|
83
83
|
onError: onError,
|
|
84
|
+
onExit: onExit,
|
|
84
85
|
isStopped: false,
|
|
85
86
|
epoch: 0,
|
|
86
87
|
simulateDeadInputTracker: SimulateDeadInputTracker.makeFromConfig(config),
|
|
@@ -96,7 +97,7 @@ function make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime
|
|
|
96
97
|
};
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
function makeFromDbState(config, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, reducedPollingInterval, targetBufferSizeOpt, onError) {
|
|
100
|
+
function makeFromDbState(config, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, reducedPollingInterval, targetBufferSizeOpt, onError, onExit) {
|
|
100
101
|
let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
|
|
101
102
|
let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
|
|
102
103
|
let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
|
|
@@ -118,7 +119,7 @@ function makeFromDbState(config, persistence, initialState, registrationsByChain
|
|
|
118
119
|
let chainConfig = ChainMap.get(config.chainMap, chain);
|
|
119
120
|
chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, registrationsByChainId, reducedPollingInterval);
|
|
120
121
|
});
|
|
121
|
-
let state = make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSize, initialState.checkpointId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError);
|
|
122
|
+
let state = make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSize, initialState.checkpointId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
|
|
122
123
|
Utils.Dict.forEach(initialState.cache, param => {
|
|
123
124
|
let count = param.count;
|
|
124
125
|
EffectState.setUnregisteredCacheCount(state.effectState, param.effectName, param.scope, count);
|
|
@@ -319,6 +320,10 @@ function exitAfterFirstEventBlock(state) {
|
|
|
319
320
|
return state.exitAfterFirstEventBlock;
|
|
320
321
|
}
|
|
321
322
|
|
|
323
|
+
function onExit(state) {
|
|
324
|
+
return state.onExit;
|
|
325
|
+
}
|
|
326
|
+
|
|
322
327
|
function isStopped(state) {
|
|
323
328
|
return state.isStopped;
|
|
324
329
|
}
|
|
@@ -718,6 +723,7 @@ export {
|
|
|
718
723
|
loadManager,
|
|
719
724
|
keepProcessAlive,
|
|
720
725
|
exitAfterFirstEventBlock,
|
|
726
|
+
onExit,
|
|
721
727
|
isStopped,
|
|
722
728
|
epoch,
|
|
723
729
|
lastPrunedAtMillis,
|
package/src/IndexerState.resi
CHANGED
|
@@ -30,6 +30,7 @@ let make: (
|
|
|
30
30
|
~shouldUseTui: bool=?,
|
|
31
31
|
~exitAfterFirstEventBlock: bool=?,
|
|
32
32
|
~onError: ErrorHandling.t => unit,
|
|
33
|
+
~onExit: unit => unit=?,
|
|
33
34
|
) => t
|
|
34
35
|
|
|
35
36
|
let makeFromDbState: (
|
|
@@ -43,6 +44,7 @@ let makeFromDbState: (
|
|
|
43
44
|
~reducedPollingInterval: int=?,
|
|
44
45
|
~targetBufferSize: int=?,
|
|
45
46
|
~onError: ErrorHandling.t => unit,
|
|
47
|
+
~onExit: unit => unit=?,
|
|
46
48
|
) => t
|
|
47
49
|
|
|
48
50
|
let unexpectedErrorMsg: string
|
|
@@ -96,6 +98,7 @@ let indexerStartTime: t => Date.t
|
|
|
96
98
|
let loadManager: t => LoadManager.t
|
|
97
99
|
let keepProcessAlive: t => bool
|
|
98
100
|
let exitAfterFirstEventBlock: t => bool
|
|
101
|
+
let onExit: t => option<unit => unit>
|
|
99
102
|
let isStopped: t => bool
|
|
100
103
|
let epoch: t => int
|
|
101
104
|
let lastPrunedAtMillis: t => dict<float>
|
package/src/Internal.res
CHANGED
|
@@ -179,7 +179,7 @@ type svmBlockField =
|
|
|
179
179
|
let allSvmBlockFields: array<svmBlockField> = [Height, ParentSlot, ParentHash]
|
|
180
180
|
let svmBlockFieldSchema = S.enum(allSvmBlockFields)
|
|
181
181
|
|
|
182
|
-
// Static sets of
|
|
182
|
+
// Static sets of field names whose source schemas must be wrapped with S.nullable.
|
|
183
183
|
let evmNullableBlockFields = Utils.Set.fromArray(
|
|
184
184
|
(
|
|
185
185
|
[
|
|
@@ -720,11 +720,20 @@ let fuelTransferParamsSchema = S.schema(s => {
|
|
|
720
720
|
|
|
721
721
|
type entity = private {id: string}
|
|
722
722
|
|
|
723
|
+
// Raw ClickHouse expressions/field names from the entity's
|
|
724
|
+
// @storage(clickhouse: {...}) directive, applied to the history table DDL.
|
|
725
|
+
type clickhouseTableOptions = {
|
|
726
|
+
partitionBy?: string,
|
|
727
|
+
orderBy?: array<string>,
|
|
728
|
+
ttl?: string,
|
|
729
|
+
}
|
|
730
|
+
|
|
723
731
|
// Per-entity storage resolved at parse time against the global storage
|
|
724
732
|
// config. Downstream PG/CH consumers just check the matching boolean.
|
|
725
733
|
type entityStorage = {
|
|
726
734
|
postgres: bool,
|
|
727
735
|
clickhouse: bool,
|
|
736
|
+
clickhouseOptions?: clickhouseTableOptions,
|
|
728
737
|
}
|
|
729
738
|
|
|
730
739
|
type genericEntityConfig<'entity> = {
|
package/src/Main.res.mjs
CHANGED
|
@@ -247,7 +247,7 @@ function getGlobalIndexer() {
|
|
|
247
247
|
let onEventFn = (identityConfig, handler) => {
|
|
248
248
|
HandlerRegister.throwIfFinishedRegistration("onEvent");
|
|
249
249
|
let match = parseIdentityConfig(identityConfig);
|
|
250
|
-
HandlerRegister.setHandler(match[0], match[1], handler, match[2]
|
|
250
|
+
HandlerRegister.setHandler(match[0], match[1], handler, match[2]);
|
|
251
251
|
};
|
|
252
252
|
let parseSvmIdentityConfig = identityConfig => {
|
|
253
253
|
let match;
|
|
@@ -279,12 +279,12 @@ function getGlobalIndexer() {
|
|
|
279
279
|
HandlerRegister.setHandler(match[0], match[1], args => handler({
|
|
280
280
|
instruction: args.event,
|
|
281
281
|
context: args.context
|
|
282
|
-
}), match[2]
|
|
282
|
+
}), match[2]);
|
|
283
283
|
};
|
|
284
284
|
let contractRegisterFn = (identityConfig, handler) => {
|
|
285
285
|
HandlerRegister.throwIfFinishedRegistration("contractRegister");
|
|
286
286
|
let match = parseIdentityConfig(identityConfig);
|
|
287
|
-
HandlerRegister.setContractRegister(match[0], match[1], handler, match[2]
|
|
287
|
+
HandlerRegister.setContractRegister(match[0], match[1], handler, match[2]);
|
|
288
288
|
};
|
|
289
289
|
let onRollbackCommitFn = callback => {
|
|
290
290
|
HandlerRegister.throwIfFinishedRegistration("~internalAndWillBeRemovedSoon_onRollbackCommit");
|
|
@@ -524,7 +524,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
524
524
|
};
|
|
525
525
|
}, persistence$1, isDevelopmentMode);
|
|
526
526
|
}
|
|
527
|
-
let state = IndexerState.makeFromDbState(config$2, persistence$1, Persistence.getInitializedState(persistence$1), registrationsByChainId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, undefined, undefined, onError);
|
|
527
|
+
let state = IndexerState.makeFromDbState(config$2, persistence$1, Persistence.getInitializedState(persistence$1), registrationsByChainId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, undefined, undefined, onError, undefined);
|
|
528
528
|
if (shouldUseTui) {
|
|
529
529
|
Tui.start(() => state);
|
|
530
530
|
}
|
package/src/PgStorage.res
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
let getCacheRowCountFnName = "get_cache_row_count"
|
|
2
|
-
|
|
3
1
|
let makeClient = () => {
|
|
4
2
|
Postgres.makeSql(
|
|
5
3
|
~config={
|
|
@@ -267,18 +265,7 @@ GRANT ALL ON SCHEMA "${pgSchema}" TO public;`,
|
|
|
267
265
|
| None => ()
|
|
268
266
|
}
|
|
269
267
|
|
|
270
|
-
[
|
|
271
|
-
query.contents,
|
|
272
|
-
`CREATE OR REPLACE FUNCTION ${getCacheRowCountFnName}(table_name text)
|
|
273
|
-
RETURNS integer AS $$
|
|
274
|
-
DECLARE
|
|
275
|
-
result integer;
|
|
276
|
-
BEGIN
|
|
277
|
-
EXECUTE format('SELECT COUNT(*) FROM "${pgSchema}".%I', table_name) INTO result;
|
|
278
|
-
RETURN result;
|
|
279
|
-
END;
|
|
280
|
-
$$ LANGUAGE plpgsql;`,
|
|
281
|
-
]
|
|
268
|
+
[query.contents]
|
|
282
269
|
}
|
|
283
270
|
|
|
284
271
|
let makeLoadQuery = (~pgSchema, ~tableName, ~condition) => {
|
|
@@ -661,16 +648,19 @@ type schemaCacheTableInfo = {
|
|
|
661
648
|
count: int,
|
|
662
649
|
}
|
|
663
650
|
|
|
651
|
+
type cacheRowCount = {
|
|
652
|
+
@as("count")
|
|
653
|
+
count: int,
|
|
654
|
+
}
|
|
655
|
+
|
|
664
656
|
// Matches both the cross-chain (`envio_effect_<name>`) and chain-scoped
|
|
665
657
|
// (`envio_<chainId>_effect_<name>`) cache-table formats. Kept in sync with
|
|
666
658
|
// Internal.EffectCache.
|
|
667
|
-
let
|
|
659
|
+
let makeEffectCacheTableNamesQuery = (~pgSchema) => {
|
|
668
660
|
// The column guard requires the effect-cache shape (exactly an `id` + `output`
|
|
669
661
|
// pair) so a user entity table that happens to match the name pattern is never
|
|
670
662
|
// mistaken for an effect cache.
|
|
671
|
-
`SELECT
|
|
672
|
-
t.table_name,
|
|
673
|
-
${getCacheRowCountFnName}(t.table_name) as count
|
|
663
|
+
`SELECT t.table_name
|
|
674
664
|
FROM information_schema.tables t
|
|
675
665
|
WHERE t.table_schema = '${pgSchema}'
|
|
676
666
|
AND t.table_name ~ '^envio_([0-9]+_)?effect_.+'
|
|
@@ -681,6 +671,15 @@ let makeSchemaCacheTableInfoQuery = (~pgSchema) => {
|
|
|
681
671
|
) = ARRAY['id', 'output'];`
|
|
682
672
|
}
|
|
683
673
|
|
|
674
|
+
let makeCacheRowCountQuery = (~pgSchema, ~tableName) => {
|
|
675
|
+
// The table name comes from information_schema, so anything cache-shaped that
|
|
676
|
+
// was created out-of-band in the schema reaches here. Splice both identifiers
|
|
677
|
+
// as quoted identifiers, doubling embedded quotes, so a crafted name can't
|
|
678
|
+
// break out into raw SQL.
|
|
679
|
+
let quoteIdent = ident => `"${ident->String.replaceAll("\"", "\"\"")}"`
|
|
680
|
+
`SELECT COUNT(*)::int AS count FROM ${quoteIdent(pgSchema)}.${quoteIdent(tableName)};`
|
|
681
|
+
}
|
|
682
|
+
|
|
684
683
|
type psqlExecState =
|
|
685
684
|
Unknown | Pending(promise<result<string, string>>) | Resolved(result<string, string>)
|
|
686
685
|
|
|
@@ -850,9 +849,24 @@ let rec writeBatch = async (
|
|
|
850
849
|
let specificError = ref(None)
|
|
851
850
|
|
|
852
851
|
let rawEvents = if config.enableRawEvents {
|
|
852
|
+
// A single on-chain log fans out to one item per matching registration;
|
|
853
|
+
// `raw_events` records the log itself, so dedupe by its coordinate
|
|
854
|
+
// (chain, block, logIndex) to keep one row per log.
|
|
855
|
+
let seenLogCoordinates = Utils.Set.make()
|
|
853
856
|
let rows = batch.items->Array.filterMap(item =>
|
|
854
857
|
switch item {
|
|
855
|
-
| Internal.Event(_) =>
|
|
858
|
+
| Internal.Event(_) =>
|
|
859
|
+
let coordinate = `${item
|
|
860
|
+
->Internal.getItemChainId
|
|
861
|
+
->Int.toString}-${item
|
|
862
|
+
->Internal.getItemBlockNumber
|
|
863
|
+
->Int.toString}-${item->Internal.getItemLogIndex->Int.toString}`
|
|
864
|
+
if seenLogCoordinates->Utils.Set.has(coordinate) {
|
|
865
|
+
None
|
|
866
|
+
} else {
|
|
867
|
+
seenLogCoordinates->Utils.Set.add(coordinate)->ignore
|
|
868
|
+
Some(config.ecosystem.toRawEvent(item->Internal.castUnsafeEventItem))
|
|
869
|
+
}
|
|
856
870
|
| Internal.Block(_) => None
|
|
857
871
|
}
|
|
858
872
|
)
|
|
@@ -1301,6 +1315,23 @@ let make = (
|
|
|
1301
1315
|
result
|
|
1302
1316
|
}
|
|
1303
1317
|
|
|
1318
|
+
// Each indexer counts the effect-cache tables in its own schema. The counts
|
|
1319
|
+
// are computed here per table rather than through a shared SQL helper so
|
|
1320
|
+
// indexers isolated by schema in one database never touch each other's state.
|
|
1321
|
+
let queryCacheTableInfo = async (): array<schemaCacheTableInfo> => {
|
|
1322
|
+
let tableNames: array<schemaTableName> = await sql->Postgres.unsafe(
|
|
1323
|
+
makeEffectCacheTableNamesQuery(~pgSchema),
|
|
1324
|
+
)
|
|
1325
|
+
await tableNames
|
|
1326
|
+
->Array.map(async ({tableName}) => {
|
|
1327
|
+
let rows: array<cacheRowCount> = await sql->Postgres.unsafe(
|
|
1328
|
+
makeCacheRowCountQuery(~pgSchema, ~tableName),
|
|
1329
|
+
)
|
|
1330
|
+
({tableName, count: (rows->Array.getUnsafe(0)).count}: schemaCacheTableInfo)
|
|
1331
|
+
})
|
|
1332
|
+
->Promise.all
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1304
1335
|
let restoreEffectCache = async (~withUpload) => {
|
|
1305
1336
|
if withUpload {
|
|
1306
1337
|
// Try to restore cache tables from the .envio/cache TSV files
|
|
@@ -1340,9 +1371,7 @@ let make = (
|
|
|
1340
1371
|
}
|
|
1341
1372
|
}
|
|
1342
1373
|
|
|
1343
|
-
let cacheTableInfo
|
|
1344
|
-
makeSchemaCacheTableInfoQuery(~pgSchema),
|
|
1345
|
-
)
|
|
1374
|
+
let cacheTableInfo = await queryCacheTableInfo()
|
|
1346
1375
|
|
|
1347
1376
|
if withUpload && cacheTableInfo->Utils.Array.notEmpty {
|
|
1348
1377
|
// Integration with other tools like Hasura
|
|
@@ -1551,10 +1580,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::int[],$3::
|
|
|
1551
1580
|
|
|
1552
1581
|
let dumpEffectCache = async () => {
|
|
1553
1582
|
try {
|
|
1554
|
-
let cacheTableInfo
|
|
1555
|
-
(await sql->Postgres.unsafe(makeSchemaCacheTableInfoQuery(~pgSchema)))->Array.filter(i =>
|
|
1556
|
-
i.count > 0
|
|
1557
|
-
)
|
|
1583
|
+
let cacheTableInfo = (await queryCacheTableInfo())->Array.filter(i => i.count > 0)
|
|
1558
1584
|
|
|
1559
1585
|
if cacheTableInfo->Utils.Array.notEmpty {
|
|
1560
1586
|
// Create .envio/cache directory if it doesn't exist
|
package/src/PgStorage.res.mjs
CHANGED
|
@@ -27,8 +27,6 @@ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js
|
|
|
27
27
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
28
28
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
29
29
|
|
|
30
|
-
let getCacheRowCountFnName = "get_cache_row_count";
|
|
31
|
-
|
|
32
30
|
function makeClient() {
|
|
33
31
|
return Postgres({
|
|
34
32
|
host: Env.Db.host,
|
|
@@ -222,18 +220,7 @@ GRANT ALL ON SCHEMA "` + pgSchema + `" TO public;`)
|
|
|
222
220
|
if (initialChainsValuesQuery !== undefined) {
|
|
223
221
|
query.contents = query.contents + "\n" + initialChainsValuesQuery;
|
|
224
222
|
}
|
|
225
|
-
return [
|
|
226
|
-
query.contents,
|
|
227
|
-
`CREATE OR REPLACE FUNCTION ` + getCacheRowCountFnName + `(table_name text)
|
|
228
|
-
RETURNS integer AS $$
|
|
229
|
-
DECLARE
|
|
230
|
-
result integer;
|
|
231
|
-
BEGIN
|
|
232
|
-
EXECUTE format('SELECT COUNT(*) FROM "` + pgSchema + `".%I', table_name) INTO result;
|
|
233
|
-
RETURN result;
|
|
234
|
-
END;
|
|
235
|
-
$$ LANGUAGE plpgsql;`
|
|
236
|
-
];
|
|
223
|
+
return [query.contents];
|
|
237
224
|
}
|
|
238
225
|
|
|
239
226
|
function makeLoadQuery(pgSchema, tableName, condition) {
|
|
@@ -492,10 +479,8 @@ function makeSchemaTableNamesQuery(pgSchema) {
|
|
|
492
479
|
return `SELECT table_name FROM information_schema.tables WHERE table_schema = '` + pgSchema + `';`;
|
|
493
480
|
}
|
|
494
481
|
|
|
495
|
-
function
|
|
496
|
-
return `SELECT
|
|
497
|
-
t.table_name,
|
|
498
|
-
` + getCacheRowCountFnName + `(t.table_name) as count
|
|
482
|
+
function makeEffectCacheTableNamesQuery(pgSchema) {
|
|
483
|
+
return `SELECT t.table_name
|
|
499
484
|
FROM information_schema.tables t
|
|
500
485
|
WHERE t.table_schema = '` + pgSchema + `'
|
|
501
486
|
AND t.table_name ~ '^envio_([0-9]+_)?effect_.+'
|
|
@@ -506,6 +491,11 @@ function makeSchemaCacheTableInfoQuery(pgSchema) {
|
|
|
506
491
|
) = ARRAY['id', 'output'];`;
|
|
507
492
|
}
|
|
508
493
|
|
|
494
|
+
function makeCacheRowCountQuery(pgSchema, tableName) {
|
|
495
|
+
let quoteIdent = ident => `"` + ident.replaceAll("\"", "\"\"") + `"`;
|
|
496
|
+
return `SELECT COUNT(*)::int AS count FROM ` + quoteIdent(pgSchema) + `.` + quoteIdent(tableName) + `;`;
|
|
497
|
+
}
|
|
498
|
+
|
|
509
499
|
let psqlExecState = {
|
|
510
500
|
contents: "Unknown"
|
|
511
501
|
};
|
|
@@ -617,8 +607,16 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
|
|
|
617
607
|
};
|
|
618
608
|
let rawEvents;
|
|
619
609
|
if (config.enableRawEvents) {
|
|
610
|
+
let seenLogCoordinates = new Set();
|
|
620
611
|
let rows = Stdlib_Array.filterMap(batch.items, item => {
|
|
621
|
-
if (item.kind
|
|
612
|
+
if (item.kind !== 0) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
let coordinate = Internal.getItemChainId(item).toString() + `-` + item.blockNumber.toString() + `-` + item.logIndex.toString();
|
|
616
|
+
if (seenLogCoordinates.has(coordinate)) {
|
|
617
|
+
return;
|
|
618
|
+
} else {
|
|
619
|
+
seenLogCoordinates.add(coordinate);
|
|
622
620
|
return config.ecosystem.toRawEvent(item);
|
|
623
621
|
}
|
|
624
622
|
});
|
|
@@ -889,6 +887,17 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
889
887
|
}));
|
|
890
888
|
return result;
|
|
891
889
|
};
|
|
890
|
+
let queryCacheTableInfo = async () => {
|
|
891
|
+
let tableNames = await sql.unsafe(makeEffectCacheTableNamesQuery(pgSchema));
|
|
892
|
+
return await Promise.all(tableNames.map(async param => {
|
|
893
|
+
let tableName = param.table_name;
|
|
894
|
+
let rows = await sql.unsafe(makeCacheRowCountQuery(pgSchema, tableName));
|
|
895
|
+
return {
|
|
896
|
+
table_name: tableName,
|
|
897
|
+
count: rows[0].count
|
|
898
|
+
};
|
|
899
|
+
}));
|
|
900
|
+
};
|
|
892
901
|
let restoreEffectCache = async withUpload => {
|
|
893
902
|
if (withUpload) {
|
|
894
903
|
let entries = await scanCacheDir();
|
|
@@ -920,7 +929,7 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
920
929
|
Logging.info("No cache found to upload.");
|
|
921
930
|
}
|
|
922
931
|
}
|
|
923
|
-
let cacheTableInfo = await
|
|
932
|
+
let cacheTableInfo = await queryCacheTableInfo();
|
|
924
933
|
if (withUpload && Utils.$$Array.notEmpty(cacheTableInfo) && onNewTables !== undefined) {
|
|
925
934
|
await onNewTables(cacheTableInfo.map(info => info.table_name));
|
|
926
935
|
}
|
|
@@ -1041,7 +1050,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::int[],$3::
|
|
|
1041
1050
|
};
|
|
1042
1051
|
let dumpEffectCache = async () => {
|
|
1043
1052
|
try {
|
|
1044
|
-
let cacheTableInfo = (await
|
|
1053
|
+
let cacheTableInfo = (await queryCacheTableInfo()).filter(i => i.count > 0);
|
|
1045
1054
|
if (!Utils.$$Array.notEmpty(cacheTableInfo)) {
|
|
1046
1055
|
return;
|
|
1047
1056
|
}
|
|
@@ -1250,7 +1259,6 @@ function makePersistenceFromConfig(config, storageOpt) {
|
|
|
1250
1259
|
let maxItemsPerQuery = 500;
|
|
1251
1260
|
|
|
1252
1261
|
export {
|
|
1253
|
-
getCacheRowCountFnName,
|
|
1254
1262
|
makeClient,
|
|
1255
1263
|
makeCreateIndexQuery,
|
|
1256
1264
|
directionToSql,
|
|
@@ -1279,7 +1287,8 @@ export {
|
|
|
1279
1287
|
setQueryCache,
|
|
1280
1288
|
setOrThrow,
|
|
1281
1289
|
makeSchemaTableNamesQuery,
|
|
1282
|
-
|
|
1290
|
+
makeEffectCacheTableNamesQuery,
|
|
1291
|
+
makeCacheRowCountQuery,
|
|
1283
1292
|
getConnectedPsqlExec,
|
|
1284
1293
|
deleteByIdsOrThrow,
|
|
1285
1294
|
makeInsertDeleteUpdatesQuery,
|
package/src/SimulateItems.res
CHANGED
|
@@ -339,46 +339,67 @@ let parse = (
|
|
|
339
339
|
| None => seenCoordinates->Dict.set(coordinate, itemIndex)
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
342
|
+
// Fan the simulated event out to every registration that would actually
|
|
343
|
+
// run here the way real routing does (one item per registration).
|
|
344
|
+
// Registrations are built the same way `HandlerRegister.finishRegistration`
|
|
345
|
+
// does at startup (not stubs), so the address filter and `where` behave
|
|
346
|
+
// identically to real indexing — the dead-input tracker relies on
|
|
347
|
+
// `clientAddressFilter` actually gating unrouted items. Drop registrations
|
|
348
|
+
// whose `where` excludes this chain (they wouldn't be fetched live), and
|
|
349
|
+
// ignore the bare handler-less fallback so a missing handler surfaces below
|
|
350
|
+
// instead of silently running nothing.
|
|
351
|
+
let liveRegistrations =
|
|
352
|
+
HandlerRegister.getSimulateOnEventRegistrations(
|
|
353
|
+
~config,
|
|
354
|
+
~chainId,
|
|
355
|
+
~eventConfig,
|
|
356
|
+
)->Array.filter(reg =>
|
|
357
|
+
(reg.handler->Option.isSome || reg.contractRegister->Option.isSome) &&
|
|
358
|
+
!HandlerRegister.isDroppedByWhere(~config, reg)
|
|
359
|
+
)
|
|
360
|
+
if liveRegistrations->Utils.Array.isEmpty {
|
|
361
|
+
JsError.throwWithMessage(
|
|
362
|
+
`simulate: no handler runs for event "${eventName}" on contract "${contractName}"${switch config.chainMap
|
|
363
|
+
->ChainMap.values
|
|
364
|
+
->Array.length {
|
|
365
|
+
| 1 => ""
|
|
366
|
+
| _ => ` on chain ${chainId->Int.toString}`
|
|
367
|
+
}}. Register a handler with indexer.onEvent (and check any \`where\` filter isn't excluding this chain) before simulating it.`,
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
liveRegistrations->Array.forEach(reg => {
|
|
371
|
+
// Append into the registration array that the chain state will own and
|
|
372
|
+
// put that same registration object directly on the simulated item.
|
|
373
|
+
let onEventRegistrationIndex = onEventRegistrations->Array.length
|
|
374
|
+
let onEventRegistration = {...reg, index: onEventRegistrationIndex}
|
|
375
|
+
onEventRegistrations->Array.push(onEventRegistration)->ignore
|
|
376
|
+
|
|
377
|
+
items
|
|
378
|
+
->Array.push(
|
|
379
|
+
Internal.Event({
|
|
380
|
+
onEventRegistration,
|
|
381
|
+
chain,
|
|
382
|
+
blockNumber,
|
|
383
|
+
logIndex,
|
|
384
|
+
// Simulate keeps the transaction inline on the payload, so the store
|
|
385
|
+
// key is unused.
|
|
386
|
+
transactionIndex: 0,
|
|
387
|
+
payload: (
|
|
388
|
+
{
|
|
389
|
+
contractName: eventConfig.contractName,
|
|
390
|
+
eventName: eventConfig.name,
|
|
391
|
+
params,
|
|
392
|
+
chainId,
|
|
393
|
+
srcAddress,
|
|
394
|
+
logIndex,
|
|
395
|
+
transaction,
|
|
396
|
+
block,
|
|
397
|
+
}: Evm.payload
|
|
398
|
+
)->Evm.fromPayload,
|
|
399
|
+
}),
|
|
400
|
+
)
|
|
401
|
+
->ignore
|
|
402
|
+
})
|
|
382
403
|
|
|
383
404
|
| _ =>
|
|
384
405
|
JsError.throwWithMessage(`simulate: Invalid item. Each item must have "contract" and "event" fields.`)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Utils from "./Utils.res.mjs";
|
|
3
4
|
import * as Config from "./Config.res.mjs";
|
|
4
5
|
import * as Address from "./Address.res.mjs";
|
|
5
6
|
import * as ChainMap from "./ChainMap.res.mjs";
|
|
@@ -232,6 +233,7 @@ function parse(simulateItems, config, chainConfig, onEventRegistrations) {
|
|
|
232
233
|
break;
|
|
233
234
|
}
|
|
234
235
|
let blockNumber = match$3[1];
|
|
236
|
+
let block$2 = match$3[0];
|
|
235
237
|
let match$4 = config.ecosystem.name;
|
|
236
238
|
let transaction;
|
|
237
239
|
switch (match$4) {
|
|
@@ -253,29 +255,43 @@ function parse(simulateItems, config, chainConfig, onEventRegistrations) {
|
|
|
253
255
|
} else {
|
|
254
256
|
seenCoordinates[coordinate] = itemIndex;
|
|
255
257
|
}
|
|
256
|
-
let
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
items.push({
|
|
262
|
-
kind: 0,
|
|
263
|
-
onEventRegistration: newrecord,
|
|
264
|
-
chain: chain,
|
|
265
|
-
blockNumber: blockNumber,
|
|
266
|
-
logIndex: logIndex,
|
|
267
|
-
transactionIndex: 0,
|
|
268
|
-
payload: {
|
|
269
|
-
contractName: eventConfig.contractName,
|
|
270
|
-
eventName: eventConfig.name,
|
|
271
|
-
params: params,
|
|
272
|
-
chainId: chainId,
|
|
273
|
-
srcAddress: srcAddress,
|
|
274
|
-
logIndex: logIndex,
|
|
275
|
-
transaction: Primitive_option.some(transaction),
|
|
276
|
-
block: Primitive_option.some(match$3[0])
|
|
258
|
+
let liveRegistrations = HandlerRegister.getSimulateOnEventRegistrations(config, chainId, eventConfig).filter(reg => {
|
|
259
|
+
if (Stdlib_Option.isSome(reg.handler) || Stdlib_Option.isSome(reg.contractRegister)) {
|
|
260
|
+
return !HandlerRegister.isDroppedByWhere(config, reg);
|
|
261
|
+
} else {
|
|
262
|
+
return false;
|
|
277
263
|
}
|
|
278
264
|
});
|
|
265
|
+
if (Utils.$$Array.isEmpty(liveRegistrations)) {
|
|
266
|
+
let match$5 = ChainMap.values(config.chainMap).length;
|
|
267
|
+
Stdlib_JsError.throwWithMessage(`simulate: no handler runs for event "` + match$1 + `" on contract "` + match + `"` + (
|
|
268
|
+
match$5 !== 1 ? ` on chain ` + chainId.toString() : ""
|
|
269
|
+
) + `. Register a handler with indexer.onEvent (and check any \`where\` filter isn't excluding this chain) before simulating it.`);
|
|
270
|
+
}
|
|
271
|
+
liveRegistrations.forEach(reg => {
|
|
272
|
+
let onEventRegistrationIndex = onEventRegistrations.length;
|
|
273
|
+
let newrecord = {...reg};
|
|
274
|
+
newrecord.index = onEventRegistrationIndex;
|
|
275
|
+
onEventRegistrations.push(newrecord);
|
|
276
|
+
items.push({
|
|
277
|
+
kind: 0,
|
|
278
|
+
onEventRegistration: newrecord,
|
|
279
|
+
chain: chain,
|
|
280
|
+
blockNumber: blockNumber,
|
|
281
|
+
logIndex: logIndex,
|
|
282
|
+
transactionIndex: 0,
|
|
283
|
+
payload: {
|
|
284
|
+
contractName: eventConfig.contractName,
|
|
285
|
+
eventName: eventConfig.name,
|
|
286
|
+
params: params,
|
|
287
|
+
chainId: chainId,
|
|
288
|
+
srcAddress: srcAddress,
|
|
289
|
+
logIndex: logIndex,
|
|
290
|
+
transaction: Primitive_option.some(transaction),
|
|
291
|
+
block: Primitive_option.some(block$2)
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
});
|
|
279
295
|
});
|
|
280
296
|
return items;
|
|
281
297
|
}
|