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.
Files changed (110) hide show
  1. package/evm.schema.json +26 -0
  2. package/fuel.schema.json +26 -0
  3. package/index.d.ts +35 -3
  4. package/package.json +6 -7
  5. package/src/Batch.res +9 -0
  6. package/src/ChainState.res +35 -39
  7. package/src/ChainState.res.mjs +14 -11
  8. package/src/ChainState.resi +15 -7
  9. package/src/Config.res +52 -44
  10. package/src/Config.res.mjs +51 -17
  11. package/src/Core.res +4 -0
  12. package/src/CrossChainState.res +15 -23
  13. package/src/CrossChainState.res.mjs +10 -18
  14. package/src/CrossChainState.resi +4 -1
  15. package/src/Ecosystem.res +7 -2
  16. package/src/Envio.res +6 -4
  17. package/src/EventConfigBuilder.res +169 -26
  18. package/src/EventConfigBuilder.res.mjs +124 -15
  19. package/src/HandlerRegister.res +21 -4
  20. package/src/HandlerRegister.res.mjs +24 -5
  21. package/src/InMemoryStore.res +9 -7
  22. package/src/InMemoryStore.res.mjs +3 -3
  23. package/src/IndexerState.res +43 -6
  24. package/src/IndexerState.res.mjs +36 -7
  25. package/src/IndexerState.resi +4 -4
  26. package/src/Internal.res +16 -17
  27. package/src/Metrics.res +72 -6
  28. package/src/Metrics.res.mjs +30 -2
  29. package/src/Persistence.res +36 -30
  30. package/src/Persistence.res.mjs +1 -20
  31. package/src/PgStorage.res +104 -56
  32. package/src/PgStorage.res.mjs +57 -35
  33. package/src/PruneStaleHistory.res +85 -52
  34. package/src/PruneStaleHistory.res.mjs +63 -37
  35. package/src/Rollback.res +68 -45
  36. package/src/Rollback.res.mjs +38 -19
  37. package/src/RollbackCommit.res +7 -9
  38. package/src/RollbackCommit.res.mjs +5 -6
  39. package/src/SafeCheckpointTracking.res +11 -11
  40. package/src/SafeCheckpointTracking.res.mjs +7 -6
  41. package/src/SimulateItems.res +33 -18
  42. package/src/SimulateItems.res.mjs +26 -22
  43. package/src/Sink.res +38 -26
  44. package/src/Sink.res.mjs +21 -16
  45. package/src/TestIndexer.res +5 -5
  46. package/src/TestIndexer.res.mjs +1 -1
  47. package/src/Utils.res +170 -0
  48. package/src/Utils.res.mjs +153 -0
  49. package/src/Writing.res +2 -2
  50. package/src/Writing.res.mjs +1 -1
  51. package/src/bindings/ClickHouse.res +366 -698
  52. package/src/bindings/ClickHouse.res.mjs +371 -421
  53. package/src/bindings/ClickHouseSink.res +337 -0
  54. package/src/bindings/ClickHouseSink.res.mjs +246 -0
  55. package/src/bindings/EventSource.res +8 -2
  56. package/src/bindings/NodeJs.res +6 -0
  57. package/src/bindings/Vitest.res +23 -0
  58. package/src/bindings/Vitest.res.mjs +3 -0
  59. package/src/bindings/WebSocket.res +9 -10
  60. package/src/db/CheckpointBounds.res +53 -0
  61. package/src/db/CheckpointBounds.res.mjs +44 -0
  62. package/src/db/EntityFilter.res +36 -17
  63. package/src/db/EntityFilter.res.mjs +25 -14
  64. package/src/db/EntityHistory.res +47 -22
  65. package/src/db/EntityHistory.res.mjs +19 -12
  66. package/src/db/InternalTable.res +84 -35
  67. package/src/db/InternalTable.res.mjs +56 -23
  68. package/src/db/RollbackFloors.res +55 -0
  69. package/src/db/RollbackFloors.res.mjs +92 -0
  70. package/src/db/Table.res +22 -4
  71. package/src/db/Table.res.mjs +30 -9
  72. package/src/sources/Evm.res +2 -0
  73. package/src/sources/Evm.res.mjs +2 -0
  74. package/src/sources/EvmHyperSyncSource.res +3 -3
  75. package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
  76. package/src/sources/EvmRpcWs.res +124 -0
  77. package/src/sources/EvmRpcWs.res.mjs +117 -0
  78. package/src/sources/Fuel.res +2 -0
  79. package/src/sources/Fuel.res.mjs +2 -0
  80. package/src/sources/FuelHyperSyncSource.res +1 -1
  81. package/src/sources/FuelHyperSyncSource.res.mjs +2 -1
  82. package/src/sources/HeightFeed.res +568 -0
  83. package/src/sources/HeightFeed.res.mjs +452 -0
  84. package/src/sources/HeightStream.res +257 -0
  85. package/src/sources/HeightStream.res.mjs +194 -0
  86. package/src/sources/HyperSync.res +5 -0
  87. package/src/sources/HyperSync.res.mjs +3 -0
  88. package/src/sources/HyperSync.resi +4 -0
  89. package/src/sources/HyperSyncSSE.res +49 -0
  90. package/src/sources/HyperSyncSSE.res.mjs +79 -0
  91. package/src/sources/RpcSource.res +1 -1
  92. package/src/sources/RpcSource.res.mjs +2 -2
  93. package/src/sources/Source.res +54 -1
  94. package/src/sources/Source.res.mjs +25 -0
  95. package/src/sources/SourceManager.res +220 -227
  96. package/src/sources/SourceManager.res.mjs +139 -164
  97. package/src/sources/SourceManager.resi +8 -0
  98. package/src/sources/Svm.res +2 -0
  99. package/src/sources/Svm.res.mjs +2 -0
  100. package/src/sources/SvmHyperSyncClient.res +70 -127
  101. package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
  102. package/src/sources/SvmHyperSyncSource.res +18 -15
  103. package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
  104. package/svm.schema.json +1 -75
  105. package/src/MemoryStorage.res +0 -729
  106. package/src/MemoryStorage.res.mjs +0 -586
  107. package/src/sources/HyperSyncHeightStream.res +0 -129
  108. package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
  109. package/src/sources/RpcWebSocketHeightStream.res +0 -175
  110. 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 recordRequestStats(sourceState, requestStats) {
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 = sourceState.requestStats[method];
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
- sourceState.requestStats[method] = {
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
- if (sourceState.knownHeight > 0) {
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: sourceState.knownHeight
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
- source: source,
257
- knownHeight: 0,
258
- unsubscribe: undefined,
259
- pendingHeightResolvers: [],
260
- disabled: false,
261
- lastFailedAt: undefined,
262
- requestStats: {}
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
- let unsubscribe = sourceState.unsubscribe;
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 = retryBackoffMillis(retry);
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 = retryBackoffMillis(retry);
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
- async function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling) {
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 status = {
616
- contents: "Active"
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
- let match = await Promise.race(mainSources.map(async sourceState => [
622
- sourceState.source,
623
- await getSourceNewHeight(sourceManager, sourceState, knownHeight, stallTimeout, isRealtime, status, logger, reducedPolling)
624
- ]).concat([Utils.delay(stallTimeout).then(() => {
625
- let fallbackSources = [];
626
- sourcesState.forEach(sourceState => {
627
- if (!sourceState.disabled && !mainSources.includes(sourceState) && Stdlib_Option.isSome(getSourceRole(sourceState.source.sourceFor, isRealtime, sourceManager.hasRealtime))) {
628
- fallbackSources.push(sourceState);
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 (status.contents !== "Done") {
633
- status.contents = "Stalled";
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
- return Promise.race(fallbackSources.map(async sourceState => [
641
- sourceState.source,
642
- await getSourceNewHeight(sourceManager, sourceState, knownHeight, stallTimeout, isRealtime, status, logger, reducedPolling)
643
- ]));
644
- })]));
645
- let newBlockHeight = match[1];
646
- let source = match[0];
647
- sourceManager.activeSource = source;
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
- recordRequestStats(sourceState, response.requestStats);
678
+ recordStatsInto(sourceState.requestStats, response.requestStats);
703
679
  validateResponseBlockStore("getItems", response.blockStore, undefined);
704
680
  sourceState.lastFailedAt = undefined;
705
- if (response.knownHeight > sourceState.knownHeight) {
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
- recordRequestStats(sourceState, match.requestStats);
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
- recordRequestStats(sourceState, err.requestStats);
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
- recordRequestStats(sourceState, res.requestStats);
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
- recordRequestStats(sourceState, match.requestStats);
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
- recordRequestStats(sourceState, err.requestStats);
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 = retryBackoffMillis(retry);
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
@@ -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))),
@@ -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,