envio 3.15.0 → 3.16.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/README.md +40 -34
- package/evm.schema.json +11 -2
- package/fuel.schema.json +10 -1
- package/index.d.ts +6 -0
- package/licenses/README.md +1 -1
- package/package.json +8 -8
- package/src/Batch.res.mjs +5 -5
- package/src/BatchProcessing.res +1 -0
- package/src/BatchProcessing.res.mjs +1 -0
- package/src/ChainState.res +48 -7
- package/src/ChainState.res.mjs +27 -11
- package/src/Clock.res +57 -0
- package/src/Clock.res.mjs +72 -0
- package/src/Config.res +10 -3
- package/src/Config.res.mjs +4 -2
- package/src/Core.res +4 -40
- package/src/EffectState.res +10 -2
- package/src/EffectState.res.mjs +8 -4
- package/src/EffectState.resi +3 -1
- package/src/EntityId.res +2 -7
- package/src/EntityId.res.mjs +1 -8
- package/src/EntityTables.res +4 -1
- package/src/EntityTables.res.mjs +1 -1
- package/src/FetchState.res +157 -69
- package/src/FetchState.res.mjs +144 -49
- package/src/FinalizeBackfill.res +2 -4
- package/src/FinalizeBackfill.res.mjs +3 -4
- package/src/InMemoryTable.res +17 -16
- package/src/InMemoryTable.res.mjs +14 -15
- package/src/IndexerState.res +28 -4
- package/src/IndexerState.res.mjs +19 -6
- package/src/IndexerState.resi +4 -0
- package/src/Internal.res +0 -49
- package/src/Internal.res.mjs +1 -80
- package/src/LoadLayer.res +14 -8
- package/src/LoadLayer.res.mjs +8 -6
- package/src/Main.res.mjs +1 -1
- package/src/Persistence.res +3 -23
- package/src/Persistence.res.mjs +3 -3
- package/src/Rollback.res +0 -1
- package/src/Rollback.res.mjs +1 -1
- package/src/SafeCheckpointTracking.res +4 -4
- package/src/SafeCheckpointTracking.res.mjs +1 -1
- package/src/StagedRows.res +106 -0
- package/src/StagedRows.res.mjs +102 -0
- package/src/Staging.res +32 -14
- package/src/Staging.res.mjs +17 -20
- package/src/Storage.res +277 -948
- package/src/Storage.res.mjs +215 -707
- package/src/Supervisor.res +75 -56
- package/src/Supervisor.res.mjs +55 -26
- package/src/TestIndexer.res +22 -27
- package/src/TestIndexer.res.mjs +20 -17
- package/src/Throttler.res +15 -8
- package/src/Throttler.res.mjs +9 -6
- package/src/Throttler.resi +1 -1
- package/src/Utils.res +0 -6
- package/src/Utils.res.mjs +0 -5
- package/src/Writing.res +0 -1
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +0 -322
- package/src/bindings/ClickHouse.res.mjs +0 -338
- package/src/bindings/StorageClient.res +107 -174
- package/src/bindings/StorageClient.res.mjs +19 -8
- package/src/bindings/Vitest.res +0 -26
- package/src/bindings/Vitest.res.mjs +0 -3
- package/src/db/Table.res +24 -150
- package/src/db/Table.res.mjs +38 -272
- package/src/sources/ChainSources.res +2 -0
- package/src/sources/ChainSources.res.mjs +2 -2
- package/src/sources/EvmChain.res +3 -0
- package/src/sources/EvmChain.res.mjs +4 -2
- package/src/sources/EvmHyperSyncSource.res +2 -2
- package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
- package/src/sources/EvmRpcClient.res +13 -3
- package/src/sources/EvmRpcClient.res.mjs +4 -3
- package/src/sources/EvmRpcWs.res +4 -3
- package/src/sources/EvmRpcWs.res.mjs +2 -2
- package/src/sources/HeightFeed.res +31 -10
- package/src/sources/HeightFeed.res.mjs +10 -6
- package/src/sources/HeightStream.res +32 -17
- package/src/sources/HeightStream.res.mjs +10 -10
- package/src/sources/HyperSyncSSE.res +4 -6
- package/src/sources/HyperSyncSSE.res.mjs +2 -2
- package/src/sources/RpcSource.res +10 -1
- package/src/sources/RpcSource.res.mjs +4 -3
- package/src/sources/Source.res +3 -0
- package/src/sources/SourceManager.res +84 -77
- package/src/sources/SourceManager.res.mjs +17 -20
- package/src/sources/SourceManager.resi +3 -4
- package/src/sources/StartBlockResolver.res +1 -0
- package/src/sources/StartBlockResolver.res.mjs +3 -2
- package/src/sources/SvmHyperSyncSource.res +2 -2
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
- package/svm.schema.json +11 -2
- package/src/PgWriting.res +0 -36
- package/src/PgWriting.res.mjs +0 -34
- package/src/db/IndexDefinition.res +0 -47
- package/src/db/IndexDefinition.res.mjs +0 -67
package/src/Supervisor.res
CHANGED
|
@@ -374,74 +374,93 @@ let releaseIfAtHead = group =>
|
|
|
374
374
|
)
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
-
// The run's chains as
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
|
|
377
|
+
// The run's chains as a worker reports them before it has fetched anything:
|
|
378
|
+
// where `ChainState.makeFromDbState` resumes them. A display draws these for a
|
|
379
|
+
// worker it hasn't heard from yet, and its first sight of a chain is what it
|
|
380
|
+
// measures the sync pace from, so a resumed chain drawn from its start block
|
|
381
|
+
// would pass the earlier runs' progress off as this one's.
|
|
382
|
+
let resumedChains = (config: Config.t, ~resumed: array<Persistence.initialChainState>): array<
|
|
383
|
+
Metrics.chainMetrics,
|
|
384
|
+
> =>
|
|
381
385
|
config.chainMap
|
|
382
386
|
->ChainMap.values
|
|
383
387
|
->Array.map((chain): Metrics.chainMetrics => {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
timestampCaughtUpToHeadOrEndblock
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
388
|
+
let row = resumed->Array.find(row => row.id == chain.id)
|
|
389
|
+
let startBlock = row->Option.mapOr(chain->Config.startBlockOrZero, row => row.startBlock)
|
|
390
|
+
let endBlock = row->Option.mapOr(chain.endBlock, row => row.endBlock)
|
|
391
|
+
let progressBlockNumber = switch row {
|
|
392
|
+
| Some({progressBlockNumber}) if progressBlockNumber >= 0 => progressBlockNumber
|
|
393
|
+
| _ => startBlock - 1
|
|
394
|
+
}
|
|
395
|
+
let sourceBlockNumber = row->Option.mapOr(0, row => row.sourceBlockNumber)
|
|
396
|
+
let timestampCaughtUpToHeadOrEndblock =
|
|
397
|
+
row->Option.flatMap(row => row.timestampCaughtUpToHeadOrEndblock)
|
|
398
|
+
let metrics: Metrics.chainMetrics = {
|
|
399
|
+
chainId: chain.id,
|
|
400
|
+
poweredByHyperSync: switch chain.sourceConfig {
|
|
401
|
+
| EvmSourceConfig({hypersync}) => hypersync->Option.isSome
|
|
402
|
+
| FuelSourceConfig(_) | SvmSourceConfig(_) => true
|
|
403
|
+
| SimulateSourceConfig(_) | CustomSources(_) => false
|
|
404
|
+
},
|
|
405
|
+
firstEventBlockNumber: row->Option.flatMap(row => row.firstEventBlockNumber),
|
|
406
|
+
latestProcessedBlock: progressBlockNumber === -1 ? None : Some(progressBlockNumber),
|
|
407
|
+
timestampCaughtUpToHeadOrEndblock,
|
|
408
|
+
numEventsProcessed: row->Option.mapOr(0., row => row.numEventsProcessed),
|
|
409
|
+
latestFetchedBlockNumber: Pervasives.max(progressBlockNumber, 0),
|
|
410
|
+
knownHeight: sourceBlockNumber,
|
|
411
|
+
numBatchesFetched: 0,
|
|
412
|
+
startBlock,
|
|
413
|
+
endBlock,
|
|
414
|
+
numAddresses: 0,
|
|
415
|
+
addressesByContract: [],
|
|
416
|
+
isReady: timestampCaughtUpToHeadOrEndblock->Option.isSome,
|
|
417
|
+
sourceBlockNumber,
|
|
418
|
+
progressBlockNumber,
|
|
419
|
+
progressLatencyMs: None,
|
|
420
|
+
progressBlockTime: row->Option.flatMap(row => row.progressBlockTime),
|
|
421
|
+
concurrency: 0,
|
|
422
|
+
partitionsCount: 0,
|
|
423
|
+
bufferSize: 0,
|
|
424
|
+
bufferBlockNumber: progressBlockNumber,
|
|
425
|
+
idleSeconds: 0.,
|
|
426
|
+
waitingForNewBlockSeconds: 0.,
|
|
427
|
+
queryingSeconds: 0.,
|
|
428
|
+
blockRangeFetchSeconds: 0.,
|
|
429
|
+
blockRangeParseSeconds: 0.,
|
|
430
|
+
blockRangeFetchCount: 0.,
|
|
431
|
+
blockRangeFetchedEvents: 0.,
|
|
432
|
+
blockRangeFetchedBlocks: 0.,
|
|
433
|
+
reorgCount: 0,
|
|
434
|
+
reorgDetectedBlock: None,
|
|
435
|
+
rollbackTargetBlock: None,
|
|
436
|
+
rateLimitTimeMs: 0.,
|
|
437
|
+
rateLimitResetInMs: None,
|
|
438
|
+
}
|
|
439
|
+
switch (metrics->Metrics.hasProcessedToEndblock, endBlock) {
|
|
440
|
+
| (true, Some(endBlock)) => {...metrics, knownHeight: endBlock}
|
|
441
|
+
| _ => metrics
|
|
442
|
+
}
|
|
428
443
|
})
|
|
429
444
|
|
|
430
445
|
// Per worker rather than all or nothing: a worker done within its first report
|
|
431
446
|
// interval is heard from only as it exits, and a display holding just its
|
|
432
447
|
// siblings' finished chains would read as synced while it still indexes.
|
|
433
|
-
let displayedSnapshots = (
|
|
434
|
-
|
|
448
|
+
let displayedSnapshots = (
|
|
449
|
+
running: array<running>,
|
|
450
|
+
~resumedChains: array<Metrics.chainMetrics>,
|
|
451
|
+
~empty: Metrics.t,
|
|
452
|
+
) =>
|
|
435
453
|
running->Array.map(r =>
|
|
436
454
|
switch r.snapshot {
|
|
437
455
|
| Some(snapshot) => snapshot
|
|
438
456
|
| None => {
|
|
439
457
|
...empty,
|
|
440
|
-
chains:
|
|
458
|
+
chains: resumedChains->Array.filter(chain =>
|
|
459
|
+
r.worker.chainIds->Array.includes(chain.chainId)
|
|
460
|
+
),
|
|
441
461
|
}
|
|
442
462
|
}
|
|
443
463
|
)
|
|
444
|
-
}
|
|
445
464
|
|
|
446
465
|
// Runs the group: creates the schema for every chain, forks a worker per plan
|
|
447
466
|
// entry, and serves the run's metrics, console and display from what they
|
|
@@ -481,10 +500,9 @@ let run = async (~config: Config.t, ~logger, ~workers: array<worker>, ~reset) =>
|
|
|
481
500
|
let shouldUseTui = Tui.shouldUse()
|
|
482
501
|
// A run that resumed with every chain already caught up owes nobody a wait:
|
|
483
502
|
// its workers start realtime and there is no barrier to open.
|
|
503
|
+
let resumed = (persistence->Persistence.getInitializedState).chains
|
|
484
504
|
let holdRealtime =
|
|
485
|
-
|
|
486
|
-
chain.timestampCaughtUpToHeadOrEndblock->Option.isNone
|
|
487
|
-
)
|
|
505
|
+
resumed->Array.some(chain => chain.timestampCaughtUpToHeadOrEndblock->Option.isNone)
|
|
488
506
|
let group = {running: [], stopping: false, holdingRealtime: holdRealtime}
|
|
489
507
|
group.running =
|
|
490
508
|
workers->Array.mapWithIndex((worker, workerIndex) =>
|
|
@@ -534,8 +552,9 @@ let run = async (~config: Config.t, ~logger, ~workers: array<worker>, ~reset) =>
|
|
|
534
552
|
)
|
|
535
553
|
|
|
536
554
|
if shouldUseTui {
|
|
555
|
+
let resumedChains = resumedChains(config, ~resumed)
|
|
537
556
|
Tui.start(~config, ~logger, ~getMetrics=() =>
|
|
538
|
-
group.running->displayedSnapshots(~
|
|
557
|
+
group.running->displayedSnapshots(~resumedChains, ~empty=[]->merge)->merge
|
|
539
558
|
)
|
|
540
559
|
}
|
|
541
560
|
|
package/src/Supervisor.res.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
22
22
|
import * as Child_process from "child_process";
|
|
23
23
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
24
24
|
import * as CrossChainState from "./CrossChainState.res.mjs";
|
|
25
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
25
26
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
26
27
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
27
28
|
|
|
@@ -313,8 +314,20 @@ function releaseIfAtHead(group) {
|
|
|
313
314
|
}
|
|
314
315
|
}
|
|
315
316
|
|
|
316
|
-
function
|
|
317
|
+
function resumedChains(config, resumed) {
|
|
317
318
|
return ChainMap.values(config.chainMap).map(chain => {
|
|
319
|
+
let row = resumed.find(row => Primitive_object.equal(row.id, chain.id));
|
|
320
|
+
let startBlock = Stdlib_Option.mapOr(row, Config.startBlockOrZero(chain), row => row.startBlock);
|
|
321
|
+
let endBlock = Stdlib_Option.mapOr(row, chain.endBlock, row => row.endBlock);
|
|
322
|
+
let progressBlockNumber;
|
|
323
|
+
if (row !== undefined) {
|
|
324
|
+
let progressBlockNumber$1 = row.progressBlockNumber;
|
|
325
|
+
progressBlockNumber = progressBlockNumber$1 >= 0 ? progressBlockNumber$1 : startBlock - 1 | 0;
|
|
326
|
+
} else {
|
|
327
|
+
progressBlockNumber = startBlock - 1 | 0;
|
|
328
|
+
}
|
|
329
|
+
let sourceBlockNumber = Stdlib_Option.mapOr(row, 0, row => row.sourceBlockNumber);
|
|
330
|
+
let timestampCaughtUpToHeadOrEndblock = Stdlib_Option.flatMap(row, row => row.timestampCaughtUpToHeadOrEndblock);
|
|
318
331
|
let match = chain.sourceConfig;
|
|
319
332
|
let tmp;
|
|
320
333
|
switch (match.TAG) {
|
|
@@ -330,32 +343,37 @@ function configuredChains(config) {
|
|
|
330
343
|
tmp = false;
|
|
331
344
|
break;
|
|
332
345
|
}
|
|
333
|
-
let
|
|
334
|
-
let
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
346
|
+
let metrics_chainId = chain.id;
|
|
347
|
+
let metrics_firstEventBlockNumber = Stdlib_Option.flatMap(row, row => row.firstEventBlockNumber);
|
|
348
|
+
let metrics_latestProcessedBlock = progressBlockNumber === -1 ? undefined : progressBlockNumber;
|
|
349
|
+
let metrics_numEventsProcessed = Stdlib_Option.mapOr(row, 0, row => row.numEventsProcessed);
|
|
350
|
+
let metrics_latestFetchedBlockNumber = Primitive_int.max(progressBlockNumber, 0);
|
|
351
|
+
let metrics_addressesByContract = [];
|
|
352
|
+
let metrics_isReady = Stdlib_Option.isSome(timestampCaughtUpToHeadOrEndblock);
|
|
353
|
+
let metrics_progressBlockTime = Stdlib_Option.flatMap(row, row => row.progressBlockTime);
|
|
354
|
+
let metrics = {
|
|
355
|
+
chainId: metrics_chainId,
|
|
338
356
|
poweredByHyperSync: tmp,
|
|
339
|
-
firstEventBlockNumber:
|
|
340
|
-
latestProcessedBlock:
|
|
341
|
-
timestampCaughtUpToHeadOrEndblock:
|
|
342
|
-
numEventsProcessed:
|
|
343
|
-
latestFetchedBlockNumber:
|
|
344
|
-
knownHeight:
|
|
357
|
+
firstEventBlockNumber: metrics_firstEventBlockNumber,
|
|
358
|
+
latestProcessedBlock: metrics_latestProcessedBlock,
|
|
359
|
+
timestampCaughtUpToHeadOrEndblock: timestampCaughtUpToHeadOrEndblock,
|
|
360
|
+
numEventsProcessed: metrics_numEventsProcessed,
|
|
361
|
+
latestFetchedBlockNumber: metrics_latestFetchedBlockNumber,
|
|
362
|
+
knownHeight: sourceBlockNumber,
|
|
345
363
|
numBatchesFetched: 0,
|
|
346
|
-
startBlock:
|
|
347
|
-
endBlock:
|
|
364
|
+
startBlock: startBlock,
|
|
365
|
+
endBlock: endBlock,
|
|
348
366
|
numAddresses: 0,
|
|
349
|
-
addressesByContract:
|
|
350
|
-
isReady:
|
|
351
|
-
sourceBlockNumber:
|
|
352
|
-
progressBlockNumber:
|
|
367
|
+
addressesByContract: metrics_addressesByContract,
|
|
368
|
+
isReady: metrics_isReady,
|
|
369
|
+
sourceBlockNumber: sourceBlockNumber,
|
|
370
|
+
progressBlockNumber: progressBlockNumber,
|
|
353
371
|
progressLatencyMs: undefined,
|
|
354
|
-
progressBlockTime:
|
|
372
|
+
progressBlockTime: metrics_progressBlockTime,
|
|
355
373
|
concurrency: 0,
|
|
356
374
|
partitionsCount: 0,
|
|
357
375
|
bufferSize: 0,
|
|
358
|
-
bufferBlockNumber:
|
|
376
|
+
bufferBlockNumber: progressBlockNumber,
|
|
359
377
|
idleSeconds: 0,
|
|
360
378
|
waitingForNewBlockSeconds: 0,
|
|
361
379
|
queryingSeconds: 0,
|
|
@@ -370,18 +388,27 @@ function configuredChains(config) {
|
|
|
370
388
|
rateLimitTimeMs: 0,
|
|
371
389
|
rateLimitResetInMs: undefined
|
|
372
390
|
};
|
|
391
|
+
let match$1 = Metrics.hasProcessedToEndblock(metrics);
|
|
392
|
+
if (!match$1) {
|
|
393
|
+
return metrics;
|
|
394
|
+
}
|
|
395
|
+
if (endBlock === undefined) {
|
|
396
|
+
return metrics;
|
|
397
|
+
}
|
|
398
|
+
let newrecord = {...metrics};
|
|
399
|
+
newrecord.knownHeight = endBlock;
|
|
400
|
+
return newrecord;
|
|
373
401
|
});
|
|
374
402
|
}
|
|
375
403
|
|
|
376
|
-
function displayedSnapshots(running,
|
|
377
|
-
let configured = configuredChains(config);
|
|
404
|
+
function displayedSnapshots(running, resumedChains, empty) {
|
|
378
405
|
return running.map(r => {
|
|
379
406
|
let snapshot = r.snapshot;
|
|
380
407
|
if (snapshot !== undefined) {
|
|
381
408
|
return snapshot;
|
|
382
409
|
}
|
|
383
410
|
let newrecord = {...empty};
|
|
384
|
-
newrecord.chains =
|
|
411
|
+
newrecord.chains = resumedChains.filter(chain => r.worker.chainIds.includes(chain.chainId));
|
|
385
412
|
return newrecord;
|
|
386
413
|
});
|
|
387
414
|
}
|
|
@@ -402,7 +429,8 @@ async function run(config, logger, workers, reset) {
|
|
|
402
429
|
maxConnections: Env.Db.maxConnections
|
|
403
430
|
});
|
|
404
431
|
let shouldUseTui = Tui.shouldUse(undefined, undefined);
|
|
405
|
-
let
|
|
432
|
+
let resumed = Persistence.getInitializedState(persistence).chains;
|
|
433
|
+
let holdRealtime = resumed.some(chain => Stdlib_Option.isNone(chain.timestampCaughtUpToHeadOrEndblock));
|
|
406
434
|
let group = {
|
|
407
435
|
running: [],
|
|
408
436
|
stopping: false,
|
|
@@ -421,7 +449,8 @@ async function run(config, logger, workers, reset) {
|
|
|
421
449
|
runtime
|
|
422
450
|
]))), config.isDev);
|
|
423
451
|
if (shouldUseTui) {
|
|
424
|
-
|
|
452
|
+
let resumedChains$1 = resumedChains(config, resumed);
|
|
453
|
+
Tui.start(config, logger, () => merge(displayedSnapshots(group.running, resumedChains$1, merge([]))));
|
|
425
454
|
}
|
|
426
455
|
process.on("SIGTERM", () => stop(group));
|
|
427
456
|
process.on("SIGINT", () => stop(group));
|
|
@@ -465,7 +494,7 @@ export {
|
|
|
465
494
|
awaitExit,
|
|
466
495
|
isRunAtHead,
|
|
467
496
|
releaseIfAtHead,
|
|
468
|
-
|
|
497
|
+
resumedChains,
|
|
469
498
|
displayedSnapshots,
|
|
470
499
|
run,
|
|
471
500
|
}
|
package/src/TestIndexer.res
CHANGED
|
@@ -53,11 +53,13 @@ let renderRows = (rows: array<AddressRows.row>, ~config: Config.t) =>
|
|
|
53
53
|
|
|
54
54
|
// Rows of a per-chain entity are keyed per (chain, id): the same id exists
|
|
55
55
|
// independently on every chain.
|
|
56
|
-
let rowKey = (~scope: Internal.chainScope, ~entityId: EntityId.t) =>
|
|
56
|
+
let rowKey = (~table: Table.table, ~scope: Internal.chainScope, ~entityId: EntityId.t) => {
|
|
57
|
+
let entityKey = table.entityIdToKey(entityId)
|
|
57
58
|
switch scope {
|
|
58
|
-
| CrossChain =>
|
|
59
|
-
| Chain(chainId) => `${chainId->ChainId.toString}|${
|
|
59
|
+
| CrossChain => entityKey
|
|
60
|
+
| Chain(chainId) => `${chainId->ChainId.toString}|${entityKey}`
|
|
60
61
|
}
|
|
62
|
+
}
|
|
61
63
|
|
|
62
64
|
let readChainId = (entity: Internal.entity, ~field: Table.field): option<ChainId.t> =>
|
|
63
65
|
entity
|
|
@@ -158,19 +160,18 @@ let handleWriteBatch = (
|
|
|
158
160
|
switch change {
|
|
159
161
|
| Set({entityId, entity, checkpointId}) =>
|
|
160
162
|
// The store keeps decoded entities so load comparisons (bigint /
|
|
161
|
-
// BigDecimal) work on real values.
|
|
162
|
-
// since they may be string/int/bigint.
|
|
163
|
+
// BigDecimal) work on real values.
|
|
163
164
|
let storedEntity = switch (chainIdField, scope->Internal.chainScopeChainId) {
|
|
164
165
|
| (Some(field), Some(chainId)) =>
|
|
165
166
|
entity->Internal.stampChainId(~fieldName=field.fieldName, ~chainId)
|
|
166
167
|
| _ => entity
|
|
167
168
|
}
|
|
168
|
-
entityDict->Dict.set(rowKey(~scope, ~entityId), storedEntity)
|
|
169
|
+
entityDict->Dict.set(rowKey(~table=entityConfig.table, ~scope, ~entityId), storedEntity)
|
|
169
170
|
// The change already carries the checkpoint's chainId, so the entity
|
|
170
171
|
// inside it stays unstamped.
|
|
171
172
|
entityChangeFor(checkpointId).sets->Array.push(entity->Utils.magic)->ignore
|
|
172
173
|
| Delete({entityId, checkpointId}) =>
|
|
173
|
-
Dict.delete(entityDict->Obj.magic, rowKey(~scope, ~entityId))
|
|
174
|
+
Dict.delete(entityDict->Obj.magic, rowKey(~table=entityConfig.table, ~scope, ~entityId))
|
|
174
175
|
entityChangeFor(checkpointId).deleted->Array.push(entityId)->ignore
|
|
175
176
|
}
|
|
176
177
|
}
|
|
@@ -433,10 +434,15 @@ let getEntityFromState = (
|
|
|
433
434
|
)
|
|
434
435
|
}
|
|
435
436
|
let entityDict = state.entities->Dict.get(entityConfig.name)->Option.getOr(Dict.make())
|
|
437
|
+
let {entityIdToKey} = entityConfig.table
|
|
438
|
+
let key = entityIdToKey(entityId->EntityId.unsafeOfString)
|
|
436
439
|
switch entityConfig.table->Table.getChainIdField {
|
|
437
|
-
| None => entityDict->Dict.get(
|
|
440
|
+
| None => entityDict->Dict.get(key)->Option.map(copyEntity)
|
|
438
441
|
| Some(field) =>
|
|
439
|
-
let matches =
|
|
442
|
+
let matches =
|
|
443
|
+
entityDict
|
|
444
|
+
->Dict.valuesToArray
|
|
445
|
+
->Array.filter(entity => entityIdToKey(entity.id->EntityId.unsafeOfString) === key)
|
|
440
446
|
switch matches {
|
|
441
447
|
| [] => None
|
|
442
448
|
| [entity] => Some(copyEntity(entity))
|
|
@@ -508,7 +514,7 @@ let makeEntitySet = (~state: testIndexerState, ~entityConfig: Internal.entityCon
|
|
|
508
514
|
}
|
|
509
515
|
}
|
|
510
516
|
entityDict->Dict.set(
|
|
511
|
-
rowKey(~scope, ~entityId=entity.id->EntityId.unsafeOfString),
|
|
517
|
+
rowKey(~table=entityConfig.table, ~scope, ~entityId=entity.id->EntityId.unsafeOfString),
|
|
512
518
|
copyEntity(entity),
|
|
513
519
|
)
|
|
514
520
|
}
|
|
@@ -568,13 +574,7 @@ let makeInMemoryStorage = (~state: testIndexerState): Persistence.storage => {
|
|
|
568
574
|
// The runner injects the config-derived initial state by setting
|
|
569
575
|
// `persistence.storageStatus = Ready(...)` directly, bypassing `Persistence.init`,
|
|
570
576
|
// so none of these is reached.
|
|
571
|
-
initialize: async (
|
|
572
|
-
~chainConfigs as _=?,
|
|
573
|
-
~entities as _=?,
|
|
574
|
-
~enums as _=?,
|
|
575
|
-
~contractMapping as _,
|
|
576
|
-
~envioInfo as _,
|
|
577
|
-
) =>
|
|
577
|
+
initialize: async (~chainConfigs as _=?, ~contractMapping as _, ~envioInfo as _) =>
|
|
578
578
|
JsError.throwWithMessage(
|
|
579
579
|
"TestIndexer: initialize should not be called; the initial state is derived from config.",
|
|
580
580
|
),
|
|
@@ -582,11 +582,11 @@ let makeInMemoryStorage = (~state: testIndexerState): Persistence.storage => {
|
|
|
582
582
|
JsError.throwWithMessage(
|
|
583
583
|
"TestIndexer: readStoredConfig should not be called; the initial state is derived from config.",
|
|
584
584
|
),
|
|
585
|
-
addChain: async (~chainConfig as _, ~
|
|
585
|
+
addChain: async (~chainConfig as _, ~contractMapping as _) =>
|
|
586
586
|
JsError.throwWithMessage(
|
|
587
587
|
"TestIndexer: addChain should not be called; the initial state is derived from config.",
|
|
588
588
|
),
|
|
589
|
-
resumeInitialState: async (~
|
|
589
|
+
resumeInitialState: async (~chainIds as _, ~contractMapping as _) =>
|
|
590
590
|
JsError.throwWithMessage(
|
|
591
591
|
"TestIndexer: resumeInitialState should not be called; the initial state is derived from config.",
|
|
592
592
|
),
|
|
@@ -596,13 +596,12 @@ let makeInMemoryStorage = (~state: testIndexerState): Persistence.storage => {
|
|
|
596
596
|
->(Utils.magic: array<Internal.entity> => array<unknown>),
|
|
597
597
|
// The in-memory storage has no indexes to build, and it's always ready.
|
|
598
598
|
ensureQueryIndexes: async (~entityConfig as _, ~scope as _, ~filters as _) => (),
|
|
599
|
-
ensureSchemaIndexes: async (~
|
|
600
|
-
finalizeBackfill: async (~
|
|
599
|
+
ensureSchemaIndexes: async (~chainIds as _) => (),
|
|
600
|
+
finalizeBackfill: async (~chainIds as _, ~readyAt as _) => (),
|
|
601
601
|
writeBatch: async (
|
|
602
602
|
~batch,
|
|
603
603
|
~rollback as _,
|
|
604
604
|
~config,
|
|
605
|
-
~allEntities as _,
|
|
606
605
|
~updatedEffectsCache as _,
|
|
607
606
|
~updatedEntities,
|
|
608
607
|
~registeredAddresses,
|
|
@@ -623,11 +622,7 @@ let makeInMemoryStorage = (~state: testIndexerState): Persistence.storage => {
|
|
|
623
622
|
setChainMeta: async _ => Obj.magic(),
|
|
624
623
|
pruneStaleCheckpoints: async (~safeCheckpoints as _) => (),
|
|
625
624
|
pruneStaleEntityHistory: async (~entityConfig as _, ~safeCheckpoints as _) => (),
|
|
626
|
-
getRollbackTargetCheckpoint: async (
|
|
627
|
-
~reorgChainId as _,
|
|
628
|
-
~lastKnownValidBlockNumber as _,
|
|
629
|
-
~sequence as _,
|
|
630
|
-
) =>
|
|
625
|
+
getRollbackTargetCheckpoint: async (~reorgChainId as _, ~lastKnownValidBlockNumber as _) =>
|
|
631
626
|
JsError.throwWithMessage(
|
|
632
627
|
"TestIndexer: Rollback is not supported. The runner forces rollbackOnReorg off, so this should be unreachable.",
|
|
633
628
|
),
|
package/src/TestIndexer.res.mjs
CHANGED
|
@@ -7,7 +7,6 @@ import * as Config from "./Config.res.mjs";
|
|
|
7
7
|
import * as Logger from "./Logger.res.mjs";
|
|
8
8
|
import * as ChainId from "./ChainId.res.mjs";
|
|
9
9
|
import * as ChainMap from "./ChainMap.res.mjs";
|
|
10
|
-
import * as EntityId from "./EntityId.res.mjs";
|
|
11
10
|
import * as Frontier from "./db/Frontier.res.mjs";
|
|
12
11
|
import * as Internal from "./Internal.res.mjs";
|
|
13
12
|
import * as ChainState from "./ChainState.res.mjs";
|
|
@@ -39,11 +38,12 @@ function renderRows(rows, config) {
|
|
|
39
38
|
return AddressRows.render(rows, config.ecosystem.name, !config.lowercaseAddresses);
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
function rowKey(scope, entityId) {
|
|
41
|
+
function rowKey(table, scope, entityId) {
|
|
42
|
+
let entityKey = table.entityIdToKey(entityId);
|
|
43
43
|
if (scope === "crossChain") {
|
|
44
|
-
return
|
|
44
|
+
return entityKey;
|
|
45
45
|
} else {
|
|
46
|
-
return ChainId.toString(scope) + `|` +
|
|
46
|
+
return ChainId.toString(scope) + `|` + entityKey;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -126,12 +126,12 @@ function handleWriteBatch(state, config, updatedEntities, registeredAddresses, c
|
|
|
126
126
|
let entity = change.entity;
|
|
127
127
|
let match = Internal.chainScopeChainId(scope);
|
|
128
128
|
let storedEntity = chainIdField !== undefined && match !== undefined ? Internal.stampChainId(entity, chainIdField.fieldName, Primitive_option.valFromOption(match)) : entity;
|
|
129
|
-
entityDict[rowKey(scope, change.entityId)] = storedEntity;
|
|
129
|
+
entityDict[rowKey(entityConfig.table, scope, change.entityId)] = storedEntity;
|
|
130
130
|
entityChangeFor(change.checkpointId).sets.push(entity);
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
133
133
|
let entityId = change.entityId;
|
|
134
|
-
Stdlib_Dict.$$delete(entityDict, rowKey(scope, entityId));
|
|
134
|
+
Stdlib_Dict.$$delete(entityDict, rowKey(entityConfig.table, scope, entityId));
|
|
135
135
|
entityChangeFor(change.checkpointId).deleted.push(entityId);
|
|
136
136
|
};
|
|
137
137
|
param.changes.forEach(processChange);
|
|
@@ -304,11 +304,14 @@ function getEntityFromState(state, entityConfig, entityId, methodName) {
|
|
|
304
304
|
Stdlib_JsError.throwWithMessage(`Cannot call ` + entityConfig.name + `.` + methodName + `() while indexer.process() is running. ` + "Wait for process() to complete before accessing entities directly.");
|
|
305
305
|
}
|
|
306
306
|
let entityDict = Stdlib_Option.getOr(state.entities[entityConfig.name], {});
|
|
307
|
+
let match = entityConfig.table;
|
|
308
|
+
let entityIdToKey = match.entityIdToKey;
|
|
309
|
+
let key = entityIdToKey(entityId);
|
|
307
310
|
let field = Table.getChainIdField(entityConfig.table);
|
|
308
311
|
if (field === undefined) {
|
|
309
|
-
return Stdlib_Option.map(entityDict[
|
|
312
|
+
return Stdlib_Option.map(entityDict[key], copyEntity);
|
|
310
313
|
}
|
|
311
|
-
let matches = Object.values(entityDict).filter(entity => entity.id ===
|
|
314
|
+
let matches = Object.values(entityDict).filter(entity => entityIdToKey(entity.id) === key);
|
|
312
315
|
let len = matches.length;
|
|
313
316
|
if (len !== 1) {
|
|
314
317
|
if (len === 0) {
|
|
@@ -358,7 +361,7 @@ function makeEntitySet(state, entityConfig) {
|
|
|
358
361
|
} else {
|
|
359
362
|
scope = "crossChain";
|
|
360
363
|
}
|
|
361
|
-
entityDict[rowKey(scope, entity.id)] = Utils.Dict.shallowCopy(entity);
|
|
364
|
+
entityDict[rowKey(entityConfig.table, scope, entity.id)] = Utils.Dict.shallowCopy(entity);
|
|
362
365
|
};
|
|
363
366
|
}
|
|
364
367
|
|
|
@@ -388,23 +391,23 @@ function makeInMemoryStorage(state) {
|
|
|
388
391
|
name: "test-inmemory",
|
|
389
392
|
entityStoreName: param => "test-inmemory",
|
|
390
393
|
holding: async () => "Initialized",
|
|
391
|
-
initialize: async (param, param$1, param$2
|
|
394
|
+
initialize: async (param, param$1, param$2) => Stdlib_JsError.throwWithMessage("TestIndexer: initialize should not be called; the initial state is derived from config."),
|
|
392
395
|
readStoredConfig: async () => Stdlib_JsError.throwWithMessage("TestIndexer: readStoredConfig should not be called; the initial state is derived from config."),
|
|
393
|
-
addChain: async (param, param$1
|
|
394
|
-
resumeInitialState: async (param, param$1
|
|
396
|
+
addChain: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: addChain should not be called; the initial state is derived from config."),
|
|
397
|
+
resumeInitialState: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: resumeInitialState should not be called; the initial state is derived from config."),
|
|
395
398
|
loadOrThrow: async (filter, table) => handleLoad(state, table.tableName, filter),
|
|
396
399
|
ensureQueryIndexes: async (param, param$1, param$2) => {},
|
|
397
|
-
ensureSchemaIndexes: async
|
|
398
|
-
finalizeBackfill: async (param, param$1
|
|
400
|
+
ensureSchemaIndexes: async param => {},
|
|
401
|
+
finalizeBackfill: async (param, param$1) => {},
|
|
399
402
|
dumpEffectCache: async () => {},
|
|
400
403
|
reset: async () => {},
|
|
401
404
|
setChainMeta: async param => {},
|
|
402
405
|
pruneStaleCheckpoints: async param => {},
|
|
403
406
|
pruneStaleEntityHistory: async (param, param$1) => {},
|
|
404
|
-
getRollbackTargetCheckpoint: async (param, param$1
|
|
407
|
+
getRollbackTargetCheckpoint: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. The runner forces rollbackOnReorg off, so this should be unreachable."),
|
|
405
408
|
getRollbackProgressDiff: async param => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. The runner forces rollbackOnReorg off, so this should be unreachable."),
|
|
406
409
|
getRollbackData: async (param, param$1) => Stdlib_JsError.throwWithMessage("TestIndexer: Rollback is not supported. The runner forces rollbackOnReorg off, so this should be unreachable."),
|
|
407
|
-
writeBatch: async (batch, param, config, param$1,
|
|
410
|
+
writeBatch: async (batch, param, config, param$1, updatedEntities, registeredAddresses, param$2, param$3) => handleWriteBatch(state, config, updatedEntities, registeredAddresses, batch.checkpointIds, batch.checkpointChainIds, batch.checkpointBlockNumbers, batch.checkpointEventsProcessed),
|
|
408
411
|
close: async () => {}
|
|
409
412
|
};
|
|
410
413
|
}
|
|
@@ -648,7 +651,7 @@ function createTestIndexer() {
|
|
|
648
651
|
let indexerState = IndexerState.makeFromDbState(runConfig, logger$1, persistence, initialState, registrationsByChainId, undefined, undefined, exitAfterFirstEventBlock, undefined, undefined, undefined, errHandler => {
|
|
649
652
|
ErrorHandling.log(errHandler);
|
|
650
653
|
reject(ErrorHandling.toExn(errHandler));
|
|
651
|
-
}, () => resolve());
|
|
654
|
+
}, () => resolve(), undefined);
|
|
652
655
|
indexerStateRef.contents = Primitive_option.some(indexerState);
|
|
653
656
|
IndexerLoop.start(indexerState);
|
|
654
657
|
});
|
package/src/Throttler.res
CHANGED
|
@@ -5,27 +5,30 @@ type t = {
|
|
|
5
5
|
mutable scheduled: option<unit => promise<unit>>,
|
|
6
6
|
intervalMillis: float,
|
|
7
7
|
logger: Logger.t,
|
|
8
|
+
clock: Clock.t,
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
let make = (~intervalMillis: int, ~logger) => {
|
|
11
|
-
|
|
11
|
+
let make = (~intervalMillis: int, ~logger, ~clock=Clock.real) => {
|
|
12
|
+
// A clock reads from its own origin, which the first run must not wait on.
|
|
13
|
+
lastRunTimeMillis: -.(intervalMillis->Int.toFloat),
|
|
12
14
|
isRunning: false,
|
|
13
15
|
isAwaitingInterval: false,
|
|
14
16
|
scheduled: None,
|
|
15
17
|
intervalMillis: intervalMillis->Int.toFloat,
|
|
16
18
|
logger,
|
|
19
|
+
clock,
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
let rec startInternal = (throttler: t) => {
|
|
20
23
|
switch throttler {
|
|
21
24
|
| {scheduled: Some(fn), isRunning: false, isAwaitingInterval: false} =>
|
|
22
|
-
let timeSinceLastRun =
|
|
25
|
+
let timeSinceLastRun = throttler.clock.now() -. throttler.lastRunTimeMillis
|
|
23
26
|
|
|
24
27
|
//Only execute if we are passed the interval
|
|
25
28
|
if timeSinceLastRun >= throttler.intervalMillis {
|
|
26
29
|
throttler.isRunning = true
|
|
27
30
|
throttler.scheduled = None
|
|
28
|
-
throttler.lastRunTimeMillis =
|
|
31
|
+
throttler.lastRunTimeMillis = throttler.clock.now()
|
|
29
32
|
|
|
30
33
|
// Defer off the schedule call so work queued before it (e.g. a batch) runs first.
|
|
31
34
|
NodeJs.setImmediate(() => {
|
|
@@ -45,10 +48,14 @@ let rec startInternal = (throttler: t) => {
|
|
|
45
48
|
} else {
|
|
46
49
|
//Store isAwaitingInterval in state so that timers don't continuously get created
|
|
47
50
|
throttler.isAwaitingInterval = true
|
|
48
|
-
let _ = setTimeout(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
let _ = throttler.clock.setTimeout(
|
|
52
|
+
~label="throttled write",
|
|
53
|
+
() => {
|
|
54
|
+
throttler.isAwaitingInterval = false
|
|
55
|
+
throttler->startInternal
|
|
56
|
+
},
|
|
57
|
+
Int.fromFloat(throttler.intervalMillis -. timeSinceLastRun),
|
|
58
|
+
)
|
|
52
59
|
}
|
|
53
60
|
| _ => ()
|
|
54
61
|
}
|
package/src/Throttler.res.mjs
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Clock from "./Clock.res.mjs";
|
|
3
4
|
import * as Logger from "./Logger.res.mjs";
|
|
4
5
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
5
6
|
|
|
6
|
-
function make(intervalMillis, logger) {
|
|
7
|
+
function make(intervalMillis, logger, clockOpt) {
|
|
8
|
+
let clock = clockOpt !== undefined ? clockOpt : Clock.real;
|
|
7
9
|
return {
|
|
8
|
-
lastRunTimeMillis:
|
|
10
|
+
lastRunTimeMillis: - intervalMillis,
|
|
9
11
|
isRunning: false,
|
|
10
12
|
isAwaitingInterval: false,
|
|
11
13
|
scheduled: undefined,
|
|
12
14
|
intervalMillis: intervalMillis,
|
|
13
|
-
logger: logger
|
|
15
|
+
logger: logger,
|
|
16
|
+
clock: clock
|
|
14
17
|
};
|
|
15
18
|
}
|
|
16
19
|
|
|
@@ -27,11 +30,11 @@ function startInternal(throttler) {
|
|
|
27
30
|
if (fn === undefined) {
|
|
28
31
|
return;
|
|
29
32
|
}
|
|
30
|
-
let timeSinceLastRun =
|
|
33
|
+
let timeSinceLastRun = throttler.clock.now() - throttler.lastRunTimeMillis;
|
|
31
34
|
if (timeSinceLastRun >= throttler.intervalMillis) {
|
|
32
35
|
throttler.isRunning = true;
|
|
33
36
|
throttler.scheduled = undefined;
|
|
34
|
-
throttler.lastRunTimeMillis =
|
|
37
|
+
throttler.lastRunTimeMillis = throttler.clock.now();
|
|
35
38
|
setImmediate(() => {
|
|
36
39
|
(async () => {
|
|
37
40
|
try {
|
|
@@ -46,7 +49,7 @@ function startInternal(throttler) {
|
|
|
46
49
|
});
|
|
47
50
|
} else {
|
|
48
51
|
throttler.isAwaitingInterval = true;
|
|
49
|
-
setTimeout(() => {
|
|
52
|
+
throttler.clock.setTimeout("throttled write", () => {
|
|
50
53
|
throttler.isAwaitingInterval = false;
|
|
51
54
|
startInternal(throttler);
|
|
52
55
|
}, throttler.intervalMillis - timeSinceLastRun | 0);
|
package/src/Throttler.resi
CHANGED
|
@@ -15,7 +15,7 @@ Does NOT queue scheduled functions but rather overwrites them
|
|
|
15
15
|
The logger will be used to log any errors that occur in the scheduled
|
|
16
16
|
functions.
|
|
17
17
|
*/
|
|
18
|
-
let make: (~intervalMillis: int, ~logger: Logger.t) => t
|
|
18
|
+
let make: (~intervalMillis: int, ~logger: Logger.t, ~clock: Clock.t=?) => t
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
Schedules a function to be run on a throttler, overwriting any
|