envio 3.9.0 → 3.10.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/evm.schema.json +26 -0
- package/fuel.schema.json +26 -0
- package/index.d.ts +35 -3
- package/package.json +6 -7
- package/src/Batch.res +9 -0
- package/src/ChainState.res +35 -39
- package/src/ChainState.res.mjs +14 -11
- package/src/ChainState.resi +15 -7
- package/src/Config.res +52 -44
- package/src/Config.res.mjs +51 -17
- package/src/Core.res +4 -0
- package/src/CrossChainState.res +15 -23
- package/src/CrossChainState.res.mjs +10 -18
- package/src/CrossChainState.resi +4 -1
- package/src/Ecosystem.res +7 -2
- package/src/Envio.res +6 -4
- package/src/EventConfigBuilder.res +169 -26
- package/src/EventConfigBuilder.res.mjs +124 -15
- package/src/HandlerRegister.res +21 -4
- package/src/HandlerRegister.res.mjs +24 -5
- package/src/InMemoryStore.res +9 -7
- package/src/InMemoryStore.res.mjs +3 -3
- package/src/IndexerState.res +43 -6
- package/src/IndexerState.res.mjs +36 -7
- package/src/IndexerState.resi +4 -4
- package/src/Internal.res +16 -17
- package/src/Metrics.res +72 -6
- package/src/Metrics.res.mjs +30 -2
- package/src/Persistence.res +36 -30
- package/src/Persistence.res.mjs +1 -20
- package/src/PgStorage.res +104 -56
- package/src/PgStorage.res.mjs +57 -35
- package/src/PruneStaleHistory.res +85 -52
- package/src/PruneStaleHistory.res.mjs +63 -37
- package/src/Rollback.res +68 -45
- package/src/Rollback.res.mjs +38 -19
- package/src/RollbackCommit.res +7 -9
- package/src/RollbackCommit.res.mjs +5 -6
- package/src/SafeCheckpointTracking.res +11 -11
- package/src/SafeCheckpointTracking.res.mjs +7 -6
- package/src/SimulateItems.res +33 -18
- package/src/SimulateItems.res.mjs +26 -22
- package/src/Sink.res +38 -26
- package/src/Sink.res.mjs +21 -16
- package/src/TestIndexer.res +5 -5
- package/src/TestIndexer.res.mjs +1 -1
- package/src/Utils.res +170 -0
- package/src/Utils.res.mjs +153 -0
- package/src/Writing.res +2 -2
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +366 -698
- package/src/bindings/ClickHouse.res.mjs +371 -421
- package/src/bindings/ClickHouseSink.res +337 -0
- package/src/bindings/ClickHouseSink.res.mjs +246 -0
- package/src/bindings/EventSource.res +8 -2
- package/src/bindings/NodeJs.res +6 -0
- package/src/bindings/Vitest.res +23 -0
- package/src/bindings/Vitest.res.mjs +3 -0
- package/src/bindings/WebSocket.res +9 -10
- package/src/db/CheckpointBounds.res +53 -0
- package/src/db/CheckpointBounds.res.mjs +44 -0
- package/src/db/EntityFilter.res +36 -17
- package/src/db/EntityFilter.res.mjs +25 -14
- package/src/db/EntityHistory.res +47 -22
- package/src/db/EntityHistory.res.mjs +19 -12
- package/src/db/InternalTable.res +84 -35
- package/src/db/InternalTable.res.mjs +56 -23
- package/src/db/RollbackFloors.res +55 -0
- package/src/db/RollbackFloors.res.mjs +92 -0
- package/src/db/Table.res +22 -4
- package/src/db/Table.res.mjs +30 -9
- package/src/sources/Evm.res +2 -0
- package/src/sources/Evm.res.mjs +2 -0
- package/src/sources/EvmHyperSyncSource.res +3 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcWs.res +124 -0
- package/src/sources/EvmRpcWs.res.mjs +117 -0
- package/src/sources/Fuel.res +2 -0
- package/src/sources/Fuel.res.mjs +2 -0
- package/src/sources/FuelHyperSyncSource.res +1 -1
- package/src/sources/FuelHyperSyncSource.res.mjs +2 -1
- package/src/sources/HeightFeed.res +568 -0
- package/src/sources/HeightFeed.res.mjs +452 -0
- package/src/sources/HeightStream.res +257 -0
- package/src/sources/HeightStream.res.mjs +194 -0
- package/src/sources/HyperSync.res +5 -0
- package/src/sources/HyperSync.res.mjs +3 -0
- package/src/sources/HyperSync.resi +4 -0
- package/src/sources/HyperSyncSSE.res +49 -0
- package/src/sources/HyperSyncSSE.res.mjs +79 -0
- package/src/sources/RpcSource.res +1 -1
- package/src/sources/RpcSource.res.mjs +2 -2
- package/src/sources/Source.res +54 -1
- package/src/sources/Source.res.mjs +25 -0
- package/src/sources/SourceManager.res +220 -227
- package/src/sources/SourceManager.res.mjs +139 -164
- package/src/sources/SourceManager.resi +8 -0
- package/src/sources/Svm.res +2 -0
- package/src/sources/Svm.res.mjs +2 -0
- package/src/sources/SvmHyperSyncClient.res +70 -127
- package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
- package/src/sources/SvmHyperSyncSource.res +18 -15
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/svm.schema.json +1 -75
- package/src/MemoryStorage.res +0 -729
- package/src/MemoryStorage.res.mjs +0 -586
- package/src/sources/HyperSyncHeightStream.res +0 -129
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
- package/src/sources/RpcWebSocketHeightStream.res +0 -175
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -180
|
@@ -4,6 +4,7 @@ import * as Utils from "../Utils.res.mjs";
|
|
|
4
4
|
import * as Source from "./Source.res.mjs";
|
|
5
5
|
import * as Logging from "../Logging.res.mjs";
|
|
6
6
|
import * as FetchState from "../FetchState.res.mjs";
|
|
7
|
+
import * as HeightFeed from "./HeightFeed.res.mjs";
|
|
7
8
|
import * as Performance from "../bindings/Performance.res.mjs";
|
|
8
9
|
import * as ErrorHandling from "../ErrorHandling.res.mjs";
|
|
9
10
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
@@ -13,16 +14,16 @@ import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
|
|
|
13
14
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
14
15
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
15
16
|
|
|
16
|
-
function
|
|
17
|
+
function recordStatsInto(aggregates, requestStats) {
|
|
17
18
|
requestStats.forEach(param => {
|
|
18
19
|
let seconds = param.seconds;
|
|
19
20
|
let method = param.method;
|
|
20
|
-
let agg =
|
|
21
|
+
let agg = aggregates[method];
|
|
21
22
|
if (agg !== undefined) {
|
|
22
23
|
agg.count = agg.count + 1 | 0;
|
|
23
24
|
agg.seconds = agg.seconds + seconds;
|
|
24
25
|
} else {
|
|
25
|
-
|
|
26
|
+
aggregates[method] = {
|
|
26
27
|
count: 1,
|
|
27
28
|
seconds: seconds
|
|
28
29
|
};
|
|
@@ -54,11 +55,28 @@ function getRequestStatSamples(sourceManager) {
|
|
|
54
55
|
function getSourceHeightSamples(sourceManager) {
|
|
55
56
|
let samples = [];
|
|
56
57
|
sourceManager.sourcesState.forEach(sourceState => {
|
|
57
|
-
|
|
58
|
+
let knownHeight = HeightFeed.knownHeight(sourceState.feed);
|
|
59
|
+
if (knownHeight > 0) {
|
|
58
60
|
samples.push({
|
|
59
61
|
sourceName: sourceState.source.name,
|
|
60
62
|
chainId: sourceState.source.chainId,
|
|
61
|
-
height:
|
|
63
|
+
height: knownHeight
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return samples;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getHeightStreamSamples(sourceManager) {
|
|
72
|
+
let samples = [];
|
|
73
|
+
sourceManager.sourcesState.forEach(sourceState => {
|
|
74
|
+
let stream = HeightFeed.sample(sourceState.feed);
|
|
75
|
+
if (stream !== undefined) {
|
|
76
|
+
samples.push({
|
|
77
|
+
sourceName: sourceState.source.name,
|
|
78
|
+
chainId: sourceState.source.chainId,
|
|
79
|
+
stream: stream
|
|
62
80
|
});
|
|
63
81
|
return;
|
|
64
82
|
}
|
|
@@ -252,15 +270,16 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
|
|
|
252
270
|
let source = sources.find(source => getSourceRole(source.sourceFor, isRealtime, hasRealtime) === "Primary");
|
|
253
271
|
let initialActiveSource = source !== undefined ? source : Stdlib_JsError.throwWithMessage("Invalid configuration, no data-source for historical sync provided");
|
|
254
272
|
return {
|
|
255
|
-
sourcesState: sources.map(source =>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
273
|
+
sourcesState: sources.map(source => {
|
|
274
|
+
let requestStats = {};
|
|
275
|
+
return {
|
|
276
|
+
source: source,
|
|
277
|
+
feed: HeightFeed.make(source, stats => recordStatsInto(requestStats, stats), getHeightRetryInterval),
|
|
278
|
+
disabled: false,
|
|
279
|
+
lastFailedAt: undefined,
|
|
280
|
+
requestStats: requestStats
|
|
281
|
+
};
|
|
282
|
+
}),
|
|
264
283
|
statusStart: Performance.now(),
|
|
265
284
|
status: "Idle",
|
|
266
285
|
idleSeconds: 0,
|
|
@@ -270,7 +289,6 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
|
|
|
270
289
|
newBlockStallTimeoutRealtime: newBlockStallTimeoutRealtime,
|
|
271
290
|
stalledPollingInterval: stalledPollingInterval,
|
|
272
291
|
reducedPollingInterval: reducedPollingInterval,
|
|
273
|
-
getHeightRetryInterval: getHeightRetryInterval,
|
|
274
292
|
activeSource: initialActiveSource,
|
|
275
293
|
waitingForNewBlockStateId: undefined,
|
|
276
294
|
waitingLogged: false,
|
|
@@ -345,10 +363,7 @@ function disableSource(sourceManager, sourceState) {
|
|
|
345
363
|
return false;
|
|
346
364
|
}
|
|
347
365
|
sourceState.disabled = true;
|
|
348
|
-
|
|
349
|
-
if (unsubscribe !== undefined) {
|
|
350
|
-
unsubscribe();
|
|
351
|
-
}
|
|
366
|
+
HeightFeed.stop(sourceState.feed);
|
|
352
367
|
if (sourceState.source.sourceFor === "Realtime") {
|
|
353
368
|
let hasOtherRealtime = sourceManager.sourcesState.some(s => {
|
|
354
369
|
if (s !== sourceState && !s.disabled) {
|
|
@@ -362,105 +377,6 @@ function disableSource(sourceManager, sourceState) {
|
|
|
362
377
|
return true;
|
|
363
378
|
}
|
|
364
379
|
|
|
365
|
-
async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stallTimeout, isRealtime, status, logger, reducedPolling) {
|
|
366
|
-
let source = sourceState.source;
|
|
367
|
-
let initialHeight = sourceState.knownHeight;
|
|
368
|
-
let newHeight = {
|
|
369
|
-
contents: initialHeight
|
|
370
|
-
};
|
|
371
|
-
let retry = 0;
|
|
372
|
-
while (newHeight.contents <= knownHeight && status.contents !== "Done") {
|
|
373
|
-
let match = sourceState.unsubscribe;
|
|
374
|
-
if (match !== undefined) {
|
|
375
|
-
let subscriptionPromise = new Promise((resolve, _reject) => {
|
|
376
|
-
sourceState.pendingHeightResolvers.push(resolve);
|
|
377
|
-
});
|
|
378
|
-
let half = stallTimeout / 2 | 0;
|
|
379
|
-
let pollingFallback = Utils.delay(half + (Math.random() * half | 0) | 0).then(async () => {
|
|
380
|
-
Logging.childTrace(logger, {
|
|
381
|
-
msg: "onHeight subscription stale, switching to polling fallback",
|
|
382
|
-
source: source.name,
|
|
383
|
-
chainId: source.chainId
|
|
384
|
-
});
|
|
385
|
-
let h = initialHeight;
|
|
386
|
-
while (h <= knownHeight && newHeight.contents <= initialHeight) {
|
|
387
|
-
try {
|
|
388
|
-
let res = await source.getHeightOrThrow();
|
|
389
|
-
recordRequestStats(sourceState, res.requestStats);
|
|
390
|
-
h = res.height;
|
|
391
|
-
} catch (exn) {
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
if (h <= knownHeight && newHeight.contents <= initialHeight) {
|
|
395
|
-
await Utils.delay(source.pollingInterval);
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
return h;
|
|
399
|
-
});
|
|
400
|
-
let height = await Promise.race([
|
|
401
|
-
subscriptionPromise,
|
|
402
|
-
pollingFallback
|
|
403
|
-
]);
|
|
404
|
-
if (height > initialHeight) {
|
|
405
|
-
newHeight.contents = height;
|
|
406
|
-
}
|
|
407
|
-
} else {
|
|
408
|
-
try {
|
|
409
|
-
let res = await source.getHeightOrThrow();
|
|
410
|
-
recordRequestStats(sourceState, res.requestStats);
|
|
411
|
-
let height$1 = res.height;
|
|
412
|
-
newHeight.contents = height$1;
|
|
413
|
-
if (height$1 <= knownHeight) {
|
|
414
|
-
retry = 0;
|
|
415
|
-
let createSubscription = source.createHeightSubscription;
|
|
416
|
-
let exit = 0;
|
|
417
|
-
if (createSubscription !== undefined && isRealtime) {
|
|
418
|
-
let unsubscribe = createSubscription(newHeight => {
|
|
419
|
-
if (newHeight <= sourceState.knownHeight) {
|
|
420
|
-
return recordRequestStats(sourceState, [{
|
|
421
|
-
method: "heightPushIgnored",
|
|
422
|
-
seconds: 0
|
|
423
|
-
}]);
|
|
424
|
-
}
|
|
425
|
-
recordRequestStats(sourceState, [{
|
|
426
|
-
method: "heightPush",
|
|
427
|
-
seconds: 0
|
|
428
|
-
}]);
|
|
429
|
-
sourceState.knownHeight = newHeight;
|
|
430
|
-
let resolvers = sourceState.pendingHeightResolvers;
|
|
431
|
-
sourceState.pendingHeightResolvers = [];
|
|
432
|
-
resolvers.forEach(resolve => resolve(newHeight));
|
|
433
|
-
});
|
|
434
|
-
sourceState.unsubscribe = unsubscribe;
|
|
435
|
-
} else {
|
|
436
|
-
exit = 1;
|
|
437
|
-
}
|
|
438
|
-
if (exit === 1) {
|
|
439
|
-
let pollingInterval = reducedPolling ? sourceManager.reducedPollingInterval : (
|
|
440
|
-
status.contents === "Stalled" ? sourceManager.stalledPollingInterval : source.pollingInterval
|
|
441
|
-
);
|
|
442
|
-
await Utils.delay(pollingInterval);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
} catch (raw_exn) {
|
|
446
|
-
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
447
|
-
let retryInterval = sourceManager.getHeightRetryInterval(retry);
|
|
448
|
-
Logging.childTrace(logger, {
|
|
449
|
-
msg: `Height retrieval from ` + source.name + ` source failed. Retrying in ` + retryInterval.toString() + `ms.`,
|
|
450
|
-
source: source.name,
|
|
451
|
-
err: Utils.prettifyExn(exn)
|
|
452
|
-
});
|
|
453
|
-
retry = retry + 1 | 0;
|
|
454
|
-
await Utils.delay(retryInterval);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
if (newHeight.contents > sourceState.knownHeight) {
|
|
459
|
-
sourceState.knownHeight = newHeight.contents;
|
|
460
|
-
}
|
|
461
|
-
return newHeight.contents;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
380
|
function compareByOldestFailure(a, b) {
|
|
465
381
|
let match = a.lastFailedAt;
|
|
466
382
|
let match$1 = b.lastFailedAt;
|
|
@@ -536,10 +452,6 @@ function getNextSource(sourceManager, isRealtime, excludedSources) {
|
|
|
536
452
|
}
|
|
537
453
|
}
|
|
538
454
|
|
|
539
|
-
function retryBackoffMillis(retry) {
|
|
540
|
-
return Primitive_float.min(100 * 2 ** retry, 60000) | 0;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
455
|
async function backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, minBackoffMillisOpt, excludedSources) {
|
|
544
456
|
let minBackoffMillis = minBackoffMillisOpt !== undefined ? minBackoffMillisOpt : 0;
|
|
545
457
|
let switchedToWorkingSource;
|
|
@@ -568,7 +480,7 @@ async function backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime,
|
|
|
568
480
|
}
|
|
569
481
|
|
|
570
482
|
async function retryBehindHead(sourceManager, sourceState, retry, isRealtime, logger, blockNumber, method, err, excludedSources) {
|
|
571
|
-
let backoffMillis =
|
|
483
|
+
let backoffMillis = Utils.expBackoff(100, retry, 60000);
|
|
572
484
|
let log = retry >= 4 ? Logging.childWarn : Logging.childTrace;
|
|
573
485
|
log(logger, {
|
|
574
486
|
msg: `Block #` + blockNumber.toString() + ` is not available on the ` + sourceState.source.name + ` source yet. Instances of a load-balanced backend drift slightly around the head, so this is expected - indexing continues after an automatic retry.`,
|
|
@@ -581,7 +493,7 @@ async function retryBehindHead(sourceManager, sourceState, retry, isRealtime, lo
|
|
|
581
493
|
}
|
|
582
494
|
|
|
583
495
|
async function retryInconsistentResponse(sourceManager, sourceState, retry, isRealtime, logger, method, err, excludedSources) {
|
|
584
|
-
let backoffMillis =
|
|
496
|
+
let backoffMillis = Utils.expBackoff(100, retry, 60000);
|
|
585
497
|
let msg = `Received a partial indicator of a possible reorg from the ` + sourceState.source.name + ` source while fetching ` + method + `. Retrying the request to better identify whether a reorg happened.`;
|
|
586
498
|
let match = retry >= 13 ? [
|
|
587
499
|
Logging.childError,
|
|
@@ -601,7 +513,7 @@ async function retryInconsistentResponse(sourceManager, sourceState, retry, isRe
|
|
|
601
513
|
return await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, 50, excludedSources);
|
|
602
514
|
}
|
|
603
515
|
|
|
604
|
-
|
|
516
|
+
function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling) {
|
|
605
517
|
let sourcesState = sourceManager.sourcesState;
|
|
606
518
|
let logger = Logging.createChild({
|
|
607
519
|
chainId: sourceManager.activeSource.chainId,
|
|
@@ -612,48 +524,112 @@ async function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPo
|
|
|
612
524
|
sourceManager.waitingLogged = true;
|
|
613
525
|
}
|
|
614
526
|
let mainSources = getNextSources(sourceManager, isRealtime, undefined);
|
|
615
|
-
let
|
|
616
|
-
contents:
|
|
527
|
+
let stalled = {
|
|
528
|
+
contents: false
|
|
617
529
|
};
|
|
618
530
|
let stallTimeout = reducedPolling ? (sourceManager.reducedPollingInterval << 1) : (
|
|
619
531
|
isRealtime ? sourceManager.newBlockStallTimeoutRealtime : sourceManager.newBlockStallTimeout
|
|
620
532
|
);
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
533
|
+
return new Promise((resolve, _reject) => {
|
|
534
|
+
let watched = [];
|
|
535
|
+
let pokeTimeoutId = {
|
|
536
|
+
contents: undefined
|
|
537
|
+
};
|
|
538
|
+
let stallTimeoutId = {
|
|
539
|
+
contents: undefined
|
|
540
|
+
};
|
|
541
|
+
let cleanup = () => {
|
|
542
|
+
watched.forEach(subscription => subscription.unsubscribe());
|
|
543
|
+
Utils.$$Array.clearInPlace(watched);
|
|
544
|
+
Utils.clearTimeoutRef(pokeTimeoutId);
|
|
545
|
+
Utils.clearTimeoutRef(stallTimeoutId);
|
|
546
|
+
};
|
|
547
|
+
let settled = {
|
|
548
|
+
contents: false
|
|
549
|
+
};
|
|
550
|
+
let watch = (sourceState, withStream) => {
|
|
551
|
+
if (settled.contents) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (withStream) {
|
|
555
|
+
HeightFeed.enableStream(sourceState.feed);
|
|
556
|
+
}
|
|
557
|
+
let subscription = HeightFeed.onHeightAbove(sourceState.feed, knownHeight, () => {
|
|
558
|
+
if (reducedPolling) {
|
|
559
|
+
return sourceManager.reducedPollingInterval;
|
|
560
|
+
} else if (stalled.contents) {
|
|
561
|
+
return sourceManager.stalledPollingInterval;
|
|
562
|
+
} else {
|
|
563
|
+
return sourceState.source.pollingInterval;
|
|
564
|
+
}
|
|
565
|
+
}, height => {
|
|
566
|
+
let source = sourceState.source;
|
|
567
|
+
if (settled.contents) {
|
|
629
568
|
return;
|
|
630
569
|
}
|
|
570
|
+
settled.contents = true;
|
|
571
|
+
cleanup();
|
|
572
|
+
sourceManager.activeSource = source;
|
|
573
|
+
let log = stalled.contents ? Logging.childInfo : Logging.childTrace;
|
|
574
|
+
log(logger, {
|
|
575
|
+
msg: `New blocks successfully found.`,
|
|
576
|
+
source: source.name,
|
|
577
|
+
newBlockHeight: height
|
|
578
|
+
});
|
|
579
|
+
sourceManager.waitingLogged = false;
|
|
580
|
+
resolve(height);
|
|
631
581
|
});
|
|
632
|
-
if (
|
|
633
|
-
|
|
582
|
+
if (settled.contents) {
|
|
583
|
+
return subscription.unsubscribe();
|
|
584
|
+
} else {
|
|
585
|
+
watched.push(subscription);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
mainSources.forEach(sourceState => watch(sourceState, isRealtime));
|
|
590
|
+
if (settled.contents) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
let armPokeTimeout = () => {
|
|
594
|
+
pokeTimeoutId.contents = Primitive_option.some(setTimeout(() => {
|
|
595
|
+
pokeTimeoutId.contents = undefined;
|
|
596
|
+
if (!settled.contents) {
|
|
597
|
+
watched.forEach(subscription => subscription.distrustStream());
|
|
598
|
+
}
|
|
599
|
+
if (!settled.contents) {
|
|
600
|
+
return armPokeTimeout();
|
|
601
|
+
}
|
|
602
|
+
}, Utils.jitter(stallTimeout)));
|
|
603
|
+
};
|
|
604
|
+
let armStallTimeout = () => {
|
|
605
|
+
stallTimeoutId.contents = Primitive_option.some(setTimeout(() => {
|
|
606
|
+
stallTimeoutId.contents = undefined;
|
|
607
|
+
if (settled.contents) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
stalled.contents = true;
|
|
611
|
+
let fallbackSources = [];
|
|
612
|
+
sourcesState.forEach(sourceState => {
|
|
613
|
+
if (!sourceState.disabled && !mainSources.includes(sourceState) && Stdlib_Option.isSome(getSourceRole(sourceState.source.sourceFor, isRealtime, sourceManager.hasRealtime))) {
|
|
614
|
+
fallbackSources.push(sourceState);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
});
|
|
634
618
|
if (fallbackSources.length !== 0) {
|
|
635
619
|
Logging.childWarn(logger, `No new blocks detected within ` + (stallTimeout / 1000 | 0).toString() + `s. Continuing polling with secondary RPC sources from the configuration.`);
|
|
636
620
|
} else {
|
|
637
621
|
Logging.childWarn(logger, `No new blocks detected within ` + (stallTimeout / 1000 | 0).toString() + `s. Polling will continue at a reduced rate. For better reliability, refer to our RPC fallback guide: https://docs.envio.dev/docs/HyperIndex/rpc-sync`);
|
|
638
622
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
let log = status.contents === "Stalled" ? Logging.childInfo : Logging.childTrace;
|
|
649
|
-
log(logger, {
|
|
650
|
-
msg: `New blocks successfully found.`,
|
|
651
|
-
source: source.name,
|
|
652
|
-
newBlockHeight: newBlockHeight
|
|
623
|
+
fallbackSources.forEach(sourceState => watch(sourceState, false));
|
|
624
|
+
if (!settled.contents) {
|
|
625
|
+
watched.forEach(subscription => subscription.distrustStream());
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
}, stallTimeout));
|
|
629
|
+
};
|
|
630
|
+
armPokeTimeout();
|
|
631
|
+
armStallTimeout();
|
|
653
632
|
});
|
|
654
|
-
sourceManager.waitingLogged = false;
|
|
655
|
-
status.contents = "Done";
|
|
656
|
-
return newBlockHeight;
|
|
657
633
|
}
|
|
658
634
|
|
|
659
635
|
async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
@@ -699,22 +675,20 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
|
699
675
|
});
|
|
700
676
|
try {
|
|
701
677
|
let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addresses, knownHeight, query.partitionId, FetchState.narrowSelectionToRange(query.selection, toBlock), query.itemsTarget, retry, logger$2);
|
|
702
|
-
|
|
678
|
+
recordStatsInto(sourceState.requestStats, response.requestStats);
|
|
703
679
|
validateResponseBlockStore("getItems", response.blockStore, undefined);
|
|
704
680
|
sourceState.lastFailedAt = undefined;
|
|
705
|
-
|
|
706
|
-
sourceState.knownHeight = response.knownHeight;
|
|
707
|
-
}
|
|
681
|
+
HeightFeed.recordHeight(sourceState.feed, response.knownHeight);
|
|
708
682
|
responseRef = response;
|
|
709
683
|
} catch (raw_err) {
|
|
710
684
|
let err = Primitive_exceptions.internalToException(raw_err);
|
|
711
685
|
if (err.RE_EXN_ID === Source.RateLimited) {
|
|
712
686
|
let match = err._1;
|
|
713
|
-
|
|
687
|
+
recordStatsInto(sourceState.requestStats, match.requestStats);
|
|
714
688
|
await waitForRateLimitReset(sourceManager, match.resetMs, retry, logger$2);
|
|
715
689
|
retryRef = retryRef + 1 | 0;
|
|
716
690
|
} else if (err.RE_EXN_ID === Source.SourceBehindHead) {
|
|
717
|
-
|
|
691
|
+
recordStatsInto(sourceState.requestStats, err.requestStats);
|
|
718
692
|
await retryBehindHead(sourceManager, sourceState, retry, isRealtime, logger$2, err.blockNumber, "getItems", err, excludedSourcesRef);
|
|
719
693
|
retryRef = retryRef + 1 | 0;
|
|
720
694
|
} else if (err.RE_EXN_ID === Source.InconsistentResponse) {
|
|
@@ -831,7 +805,7 @@ async function getBlockHashes(sourceManager, blockNumbers, isRealtime) {
|
|
|
831
805
|
});
|
|
832
806
|
try {
|
|
833
807
|
let res = await source.getBlockHashes(blockNumbers, logger$1);
|
|
834
|
-
|
|
808
|
+
recordStatsInto(sourceState.requestStats, res.requestStats);
|
|
835
809
|
let data = res.result;
|
|
836
810
|
if (data.TAG === "Ok") {
|
|
837
811
|
let data$1 = data._0;
|
|
@@ -845,18 +819,18 @@ async function getBlockHashes(sourceManager, blockNumbers, isRealtime) {
|
|
|
845
819
|
let err = Primitive_exceptions.internalToException(raw_err);
|
|
846
820
|
if (err.RE_EXN_ID === Source.RateLimited) {
|
|
847
821
|
let match = err._1;
|
|
848
|
-
|
|
822
|
+
recordStatsInto(sourceState.requestStats, match.requestStats);
|
|
849
823
|
await waitForRateLimitReset(sourceManager, match.resetMs, retry, logger$1);
|
|
850
824
|
retryRef = retryRef + 1 | 0;
|
|
851
825
|
} else if (err.RE_EXN_ID === Source.SourceBehindHead) {
|
|
852
|
-
|
|
826
|
+
recordStatsInto(sourceState.requestStats, err.requestStats);
|
|
853
827
|
await retryBehindHead(sourceManager, sourceState, retry, isRealtime, logger$1, err.blockNumber, "getBlockHashes", err, undefined);
|
|
854
828
|
retryRef = retryRef + 1 | 0;
|
|
855
829
|
} else if (err.RE_EXN_ID === Source.InconsistentResponse) {
|
|
856
830
|
await retryInconsistentResponse(sourceManager, sourceState, retry, isRealtime, logger$1, "getBlockHashes", err, undefined);
|
|
857
831
|
retryRef = retryRef + 1 | 0;
|
|
858
832
|
} else {
|
|
859
|
-
let backoffMillis =
|
|
833
|
+
let backoffMillis = Utils.expBackoff(100, retry, 60000);
|
|
860
834
|
let log = retry >= 4 ? Logging.childWarn : Logging.childTrace;
|
|
861
835
|
log(logger$1, {
|
|
862
836
|
msg: "Failed to fetch block hashes. Retrying.",
|
|
@@ -878,6 +852,7 @@ export {
|
|
|
878
852
|
getActiveSource,
|
|
879
853
|
getRequestStatSamples,
|
|
880
854
|
getSourceHeightSamples,
|
|
855
|
+
getHeightStreamSamples,
|
|
881
856
|
idleSeconds,
|
|
882
857
|
waitingForNewBlockSeconds,
|
|
883
858
|
queryingSeconds,
|
|
@@ -39,6 +39,14 @@ type sourceHeightSample = {
|
|
|
39
39
|
|
|
40
40
|
let getSourceHeightSamples: t => array<sourceHeightSample>
|
|
41
41
|
|
|
42
|
+
type heightStreamSample = {
|
|
43
|
+
sourceName: string,
|
|
44
|
+
chainId: ChainId.t,
|
|
45
|
+
stream: HeightFeed.streamSample,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let getHeightStreamSamples: t => array<heightStreamSample>
|
|
49
|
+
|
|
42
50
|
let idleSeconds: t => float
|
|
43
51
|
let waitingForNewBlockSeconds: t => float
|
|
44
52
|
let queryingSeconds: t => float
|
package/src/sources/Svm.res
CHANGED
|
@@ -52,6 +52,8 @@ let make = (~logger: Pino.t): Ecosystem.t => {
|
|
|
52
52
|
blockTimestampName: "time",
|
|
53
53
|
blockHashName: "hash",
|
|
54
54
|
onBlockMethodName: "onSlot",
|
|
55
|
+
contractNoun: "program",
|
|
56
|
+
eventNoun: "instruction",
|
|
55
57
|
// SVM filter shape: `{slot: {_gte?, _lte?, _every?}}`.
|
|
56
58
|
// Inner range chunk parsed by `blockRangeSchema` in `Main.res`.
|
|
57
59
|
onBlockFilterSchema: S.object(s => s.field("slot", S.option(S.unknown))),
|
package/src/sources/Svm.res.mjs
CHANGED
|
@@ -59,6 +59,8 @@ function make(logger) {
|
|
|
59
59
|
blockTimestampName: "time",
|
|
60
60
|
blockHashName: "hash",
|
|
61
61
|
onBlockMethodName: "onSlot",
|
|
62
|
+
contractNoun: "program",
|
|
63
|
+
eventNoun: "instruction",
|
|
62
64
|
onBlockFilterSchema: S$RescriptSchema.object(s => s.f("slot", S$RescriptSchema.option(S$RescriptSchema.unknown))),
|
|
63
65
|
onEventBlockFilterSchema: S$RescriptSchema.object(param => {}),
|
|
64
66
|
logger: logger,
|