envio 3.11.0 → 3.12.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 (89) hide show
  1. package/index.d.ts +3 -0
  2. package/package.json +6 -6
  3. package/src/Batch.res +111 -78
  4. package/src/Batch.res.mjs +53 -46
  5. package/src/BatchProcessing.res +1 -11
  6. package/src/BatchProcessing.res.mjs +1 -4
  7. package/src/ChainState.res +72 -19
  8. package/src/ChainState.res.mjs +50 -12
  9. package/src/ChainState.resi +5 -0
  10. package/src/Config.res +58 -37
  11. package/src/Config.res.mjs +63 -53
  12. package/src/CrossChainState.res +49 -52
  13. package/src/CrossChainState.res.mjs +25 -23
  14. package/src/CrossChainState.resi +6 -13
  15. package/src/EffectState.res +13 -4
  16. package/src/EffectState.resi +7 -1
  17. package/src/EventProcessing.res +6 -6
  18. package/src/EventProcessing.res.mjs +6 -6
  19. package/src/FetchState.res +14 -0
  20. package/src/FetchState.res.mjs +14 -0
  21. package/src/FinalizeBackfill.res +16 -12
  22. package/src/FinalizeBackfill.res.mjs +3 -3
  23. package/src/HistoryPolicy.res +46 -0
  24. package/src/HistoryPolicy.res.mjs +50 -0
  25. package/src/InMemoryStore.res +30 -32
  26. package/src/InMemoryStore.res.mjs +33 -28
  27. package/src/IndexerState.res +66 -45
  28. package/src/IndexerState.res.mjs +57 -29
  29. package/src/IndexerState.resi +8 -13
  30. package/src/Internal.res +4 -2
  31. package/src/LoadLayer.res +11 -4
  32. package/src/LoadLayer.res.mjs +8 -5
  33. package/src/Main.res +25 -24
  34. package/src/Main.res.mjs +24 -25
  35. package/src/Metrics.res +49 -1
  36. package/src/Metrics.res.mjs +26 -3
  37. package/src/Persistence.res +55 -16
  38. package/src/Persistence.res.mjs +18 -3
  39. package/src/PgStorage.res +194 -118
  40. package/src/PgStorage.res.mjs +132 -72
  41. package/src/PruneStaleHistory.res +9 -15
  42. package/src/PruneStaleHistory.res.mjs +11 -20
  43. package/src/Rollback.res +9 -14
  44. package/src/Rollback.res.mjs +5 -3
  45. package/src/Sink.res +7 -5
  46. package/src/Sink.res.mjs +4 -4
  47. package/src/Staging.res +298 -0
  48. package/src/Staging.res.mjs +257 -0
  49. package/src/TestIndexer.res +5 -5
  50. package/src/TestIndexer.res.mjs +7 -6
  51. package/src/UserContext.res +7 -4
  52. package/src/UserContext.res.mjs +5 -2
  53. package/src/Utils.res +9 -0
  54. package/src/Utils.res.mjs +9 -0
  55. package/src/Writing.res +34 -20
  56. package/src/Writing.res.mjs +15 -20
  57. package/src/bindings/ClickHouse.res +41 -34
  58. package/src/bindings/ClickHouse.res.mjs +36 -30
  59. package/src/bindings/ClickHouseSink.res +45 -186
  60. package/src/bindings/ClickHouseSink.res.mjs +21 -174
  61. package/src/db/CheckpointSequence.res +109 -0
  62. package/src/db/CheckpointSequence.res.mjs +99 -0
  63. package/src/db/EntityHistory.res +6 -5
  64. package/src/db/EntityHistory.res.mjs +5 -5
  65. package/src/db/Frontier.res +86 -0
  66. package/src/db/Frontier.res.mjs +126 -0
  67. package/src/db/InternalTable.res +145 -27
  68. package/src/db/InternalTable.res.mjs +115 -29
  69. package/src/db/RollbackFloors.res +34 -41
  70. package/src/db/RollbackFloors.res.mjs +27 -71
  71. package/src/sources/BlockStore.res +3 -0
  72. package/src/sources/EvmHyperSyncSource.res +3 -1
  73. package/src/sources/EvmHyperSyncSource.res.mjs +2 -1
  74. package/src/sources/HyperSync.res +2 -0
  75. package/src/sources/HyperSync.res.mjs +1 -0
  76. package/src/sources/HyperSync.resi +1 -0
  77. package/src/sources/HyperSyncClient.res +3 -0
  78. package/src/sources/RequestStat.res +8 -1
  79. package/src/sources/Source.res +4 -3
  80. package/src/sources/SourceManager.res +30 -10
  81. package/src/sources/SourceManager.res.mjs +24 -7
  82. package/src/sources/SourceManager.resi +2 -0
  83. package/src/tui/Tui.res +56 -143
  84. package/src/tui/Tui.res.mjs +70 -166
  85. package/src/tui/components/TuiData.res +61 -16
  86. package/src/tui/components/TuiData.res.mjs +53 -15
  87. package/svm.schema.json +0 -7
  88. package/src/db/CheckpointBounds.res +0 -53
  89. package/src/db/CheckpointBounds.res.mjs +0 -44
@@ -58,12 +58,12 @@ type t = {
58
58
  mutable entities: EntityTables.t,
59
59
  effectState: EffectState.t,
60
60
  mutable rollback: option<Persistence.rollback>,
61
- // Last checkpoint persisted to the db.
62
- mutable committedCheckpointId: Internal.checkpointId,
63
- // Processing frontier; runs ahead of committedCheckpointId while writes lag.
64
- mutable processedCheckpointId: Internal.checkpointId,
61
+ // Last checkpoint each chain has persisted to the db.
62
+ mutable committedFrontier: Frontier.t,
63
+ // Processing frontier; runs ahead of committedFrontier while writes lag.
64
+ mutable processedFrontier: Frontier.t,
65
65
  // Processed but unwritten. The cycle drains them, splitting each write at a
66
- // change in isInReorgThreshold so it never mixes history-saving modes.
66
+ // change in the history policy so it never mixes history-saving modes.
67
67
  mutable processedBatches: array<Batch.t>,
68
68
  // Count of processed batches; version-independent progress counter.
69
69
  mutable processedBatchesCount: int,
@@ -139,10 +139,9 @@ let make = (
139
139
  ~config: Config.t,
140
140
  ~persistence: Persistence.t,
141
141
  ~chainStates: dict<ChainState.t>,
142
- ~isInReorgThreshold: bool,
143
142
  ~isRealtime: bool,
144
143
  ~targetBufferSize=CrossChainState.calculateTargetBufferSize(),
145
- ~committedCheckpointId=Internal.initialCheckpointId,
144
+ ~committedFrontier=Frontier.empty(),
146
145
  ~isDevelopmentMode=false,
147
146
  ~shouldUseTui=false,
148
147
  ~exitAfterFirstEventBlock=false,
@@ -169,8 +168,8 @@ let make = (
169
168
  entities: EntityTables.make(persistence.allEntities->EntityTables.crossChain),
170
169
  effectState: EffectState.make(),
171
170
  rollback: None,
172
- committedCheckpointId,
173
- processedCheckpointId: committedCheckpointId,
171
+ committedFrontier,
172
+ processedFrontier: committedFrontier->Frontier.copy,
174
173
  processedBatches: [],
175
174
  processedBatchesCount: 0,
176
175
  writeFiber: None,
@@ -181,12 +180,7 @@ let make = (
181
180
  chainMetaDirty: false,
182
181
  chainMetaThrottler,
183
182
  isProcessing: false,
184
- crossChainState: CrossChainState.make(
185
- ~chainStates,
186
- ~isInReorgThreshold,
187
- ~isRealtime,
188
- ~targetBufferSize,
189
- ),
183
+ crossChainState: CrossChainState.make(~chainStates, ~isRealtime, ~targetBufferSize),
190
184
  indexerStartTime: Date.make(),
191
185
  indexerStartTimeRef: Performance.now(),
192
186
  rollbackState: NoRollback,
@@ -278,10 +272,9 @@ let makeFromDbState = (
278
272
  ~config,
279
273
  ~persistence,
280
274
  ~chainStates,
281
- ~isInReorgThreshold,
282
275
  ~isRealtime,
283
276
  ~targetBufferSize,
284
- ~committedCheckpointId=initialState.checkpointId,
277
+ ~committedFrontier=initialState.checkpointFrontier,
285
278
  ~isDevelopmentMode,
286
279
  ~shouldUseTui,
287
280
  ~exitAfterFirstEventBlock,
@@ -357,19 +350,15 @@ let getChainState = (state: t, ~chainId: ChainId.t): ChainState.t =>
357
350
 
358
351
  let getSafeCheckpointIdByChain = (state: t) =>
359
352
  state.crossChainState->CrossChainState.getSafeCheckpointIdByChain(
360
- ~committedCheckpointId=state.committedCheckpointId,
353
+ ~sequence=state.config.checkpointSequence,
354
+ ~committedFrontier=state.committedFrontier,
361
355
  )
362
356
 
363
- let createBatch = (
364
- state: t,
365
- ~processedCheckpointId,
366
- ~batchSizeTarget: int,
367
- ~isRollback: bool,
368
- ): Batch.t =>
357
+ let createBatch = (state: t, ~batchSizeTarget: int): Batch.t =>
369
358
  state.crossChainState->CrossChainState.createBatch(
370
- ~processedCheckpointId,
359
+ ~config=state.config,
360
+ ~frontier=state.processedFrontier,
371
361
  ~batchSizeTarget,
372
- ~isRollback,
373
362
  )
374
363
 
375
364
  let enterReorgThreshold = (state: t) => state.crossChainState->CrossChainState.enterReorgThreshold
@@ -483,8 +472,13 @@ let eachEntityTable = (
483
472
  )
484
473
  }
485
474
  let effectState = (state: t) => state.effectState
486
- let committedCheckpointId = (state: t) => state.committedCheckpointId
487
- let processedCheckpointId = (state: t) => state.processedCheckpointId
475
+ let committedFrontier = (state: t) => state.committedFrontier
476
+ let processedFrontier = (state: t) => state.processedFrontier
477
+
478
+ // The committed id a scope's in-memory rows compare against.
479
+ let committedCheckpointIdFor = (state: t, ~scope) =>
480
+ state.config.checkpointSequence->CheckpointSequence.forScope(state.committedFrontier, ~scope)
481
+
488
482
  let processedBatches = (state: t) => state.processedBatches
489
483
  let processedBatchesCount = (state: t) => state.processedBatchesCount
490
484
  let writeFiber = (state: t) => state.writeFiber
@@ -495,11 +489,13 @@ let chainMetaThrottler = (state: t) => state.chainMetaThrottler
495
489
  let crossChainState = (state: t) => state.crossChainState
496
490
  let chainStates = (state: t) => state.crossChainState->CrossChainState.chainStates
497
491
  let isInReorgThreshold = (state: t) => state.crossChainState->CrossChainState.isInReorgThreshold
492
+ let shouldSaveHistory = (state: t) => state.crossChainState->CrossChainState.shouldSaveHistory
498
493
  let isRealtime = (state: t) => state.crossChainState->CrossChainState.isRealtime
499
494
 
500
495
  // The indexer runs Backfilling → FinalizingIndexes → Ready. This is true only
501
- // in the middle phase: every chain has caught up, but the deferred schema
502
- // indexes and `ready_at` haven't been committed yet.
496
+ // in the middle phase: the chains this process drives have caught up, but the
497
+ // indexes the schema promises them and their `ready_at` haven't been committed
498
+ // yet.
503
499
  let isFinalizingIndexes = (state: t) =>
504
500
  state.crossChainState->CrossChainState.isCaughtUp &&
505
501
  !(state.crossChainState->CrossChainState.isRealtime)
@@ -538,6 +534,8 @@ let toMetrics = (state: t): Metrics.t => {
538
534
  method: s.method,
539
535
  count: s.count,
540
536
  seconds: s.seconds,
537
+ responseBlocks: s.responseBlocks,
538
+ emptyResponseCount: s.emptyResponseCount,
541
539
  })
542
540
  )
543
541
  sourceManager
@@ -758,18 +756,18 @@ let recordRollbackSuccess = (state: t, ~timeSeconds, ~rollbackedProcessedEvents)
758
756
  // Queue a processed batch for writing and advance the processing frontier.
759
757
  let queueProcessedBatch = (state: t, ~batch: Batch.t) => {
760
758
  state.processedBatches->Array.push(batch)->ignore
761
- switch batch.checkpointIds->Utils.Array.last {
762
- | Some(checkpointId) => state.processedCheckpointId = checkpointId
763
- | None => ()
764
- }
759
+ state.processedFrontier = Frontier.mergeMax(
760
+ state.processedFrontier,
761
+ batch->Batch.checkpointFrontier,
762
+ )
765
763
  }
766
764
 
767
- // Take the leading run of queued batches sharing isInReorgThreshold as one merged
765
+ // Take the leading run of queued batches sharing a history policy as one merged
768
766
  // batch, leaving the rest queued for the next write. Caller guarantees the queue
769
767
  // is non-empty.
770
768
  let drainBatchRun = (state: t): Batch.t => {
771
769
  let all = state.processedBatches
772
- let isInReorgThreshold = (all->Array.getUnsafe(0)).isInReorgThreshold
770
+ let history = (all->Array.getUnsafe(0)).history
773
771
 
774
772
  let rest = []
775
773
  let progressedChainsById = Dict.make()
@@ -779,11 +777,12 @@ let drainBatchRun = (state: t): Batch.t => {
779
777
  let checkpointChainIds = []
780
778
  let checkpointBlockNumbers = []
781
779
  let checkpointBlockHashes = []
780
+ let checkpointItemsCount = []
782
781
  let checkpointEventsProcessed = []
783
782
  let registeredAddresses = []
784
783
  all->Array.forEach(batch => {
785
784
  // Once one batch lands in rest, all later ones follow it, preserving order.
786
- if rest->Utils.Array.isEmpty && batch.isInReorgThreshold == isInReorgThreshold {
785
+ if rest->Utils.Array.isEmpty && batch.history == history {
787
786
  batch.progressedChainsById->Utils.Dict.forEachWithKey((chainAfterBatch, key) =>
788
787
  progressedChainsById->Dict.set(key, chainAfterBatch)
789
788
  )
@@ -793,6 +792,7 @@ let drainBatchRun = (state: t): Batch.t => {
793
792
  checkpointChainIds->Array.pushMany(batch.checkpointChainIds)
794
793
  checkpointBlockNumbers->Array.pushMany(batch.checkpointBlockNumbers)
795
794
  checkpointBlockHashes->Array.pushMany(batch.checkpointBlockHashes)
795
+ checkpointItemsCount->Array.pushMany(batch.checkpointItemsCount)
796
796
  checkpointEventsProcessed->Array.pushMany(batch.checkpointEventsProcessed)
797
797
  registeredAddresses->Array.pushMany(batch.registeredAddresses)
798
798
  } else {
@@ -805,11 +805,12 @@ let drainBatchRun = (state: t): Batch.t => {
805
805
  totalBatchSize: totalBatchSize.contents,
806
806
  items,
807
807
  progressedChainsById,
808
- isInReorgThreshold,
808
+ history,
809
809
  checkpointIds,
810
810
  checkpointChainIds,
811
811
  checkpointBlockNumbers,
812
812
  checkpointBlockHashes,
813
+ checkpointItemsCount,
813
814
  checkpointEventsProcessed,
814
815
  registeredAddresses,
815
816
  }
@@ -829,18 +830,30 @@ let takeRollback = (state: t): option<Persistence.rollback> => {
829
830
  // committed. A failed write keeps them, and re-inserting a row the database
830
831
  // already has is a no-op. Rows staged while the write was in flight belong to
831
832
  // later checkpoints — ids only ever grow — so this can't drop one unwritten.
832
- let markCommitted = (state: t, ~upToCheckpointId) => {
833
- state.committedCheckpointId = upToCheckpointId
833
+ let markCommitted = (state: t, ~writtenFrontier) => {
834
+ state.committedFrontier = Frontier.mergeMax(state.committedFrontier, writtenFrontier)
834
835
  }
835
836
 
836
- // Reset the in-memory tables and arm the rollback diff that the next write commits.
837
+ // Reset the in-memory tables and arm the rollback diff that the next write
838
+ // commits. The diff ids start from the committed frontier — a rollback that
839
+ // supersedes an unwritten one takes over its ids along with its rows. A sibling
840
+ // the rollback leaves alone gets no diff row: burning an id on it would leave a
841
+ // hole in its sequence.
837
842
  let beginRollbackDiff = (
838
843
  state: t,
839
- ~diffCheckpointId,
840
- ~floors,
844
+ ~floors: RollbackFloors.t,
841
845
  ~progressedChains: array<InternalTable.Chains.progressedChain>,
842
846
  ~rolledBackAddresses,
843
- ) => {
847
+ ): Frontier.t => {
848
+ let diffFrontier = {
849
+ let cursor =
850
+ state.config.checkpointSequence->CheckpointSequence.cursor(~frontier=state.committedFrontier)
851
+ floors.checkpointBounds.byChain
852
+ ->Frontier.chainIds
853
+ ->Array.map(chainId => (chainId, cursor->CheckpointSequence.next(~chainId)))
854
+ ->Frontier.fromEntries
855
+ }
856
+ state.processedFrontier = Frontier.mergeMax(state.processedFrontier, diffFrontier)
844
857
  let perChainEntities = state.allEntities->EntityTables.perChain
845
858
  state.entities = EntityTables.make(state.allEntities->EntityTables.crossChain)
846
859
  state->chainStates->Utils.Dict.forEach(cs => cs->ChainState.resetEntities(~perChainEntities))
@@ -867,11 +880,19 @@ let beginRollbackDiff = (
867
880
  | None => progressedChains
868
881
  }
869
882
  state.rollback = Some({
870
- diffCheckpointId,
883
+ diffFrontier,
884
+ diffCheckpoints: diffFrontier
885
+ ->Frontier.entries
886
+ ->Array.map(((chainId, checkpointId)): InternalTable.Checkpoints.diffCheckpoint => {
887
+ chainId,
888
+ checkpointId,
889
+ blockNumber: state->getChainState(~chainId)->ChainState.committedProgressBlockNumber,
890
+ }),
871
891
  floors,
872
892
  progressedChains,
873
893
  rolledBackAddresses,
874
894
  })
895
+ diffFrontier
875
896
  }
876
897
 
877
898
  // Stop the write loop and surface the failure; the error itself goes to onError.
@@ -1,12 +1,13 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Env from "./Env.res.mjs";
4
+ import * as Batch from "./Batch.res.mjs";
4
5
  import * as Utils from "./Utils.res.mjs";
5
6
  import * as Config from "./Config.res.mjs";
6
7
  import * as ChainId from "./ChainId.res.mjs";
7
8
  import * as Logging from "./Logging.res.mjs";
8
9
  import * as ChainMap from "./ChainMap.res.mjs";
9
- import * as Internal from "./Internal.res.mjs";
10
+ import * as Frontier from "./db/Frontier.res.mjs";
10
11
  import * as Throttler from "./Throttler.res.mjs";
11
12
  import * as ChainState from "./ChainState.res.mjs";
12
13
  import * as EffectState from "./EffectState.res.mjs";
@@ -20,11 +21,12 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
20
21
  import * as CrossChainState from "./CrossChainState.res.mjs";
21
22
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
22
23
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
24
+ import * as CheckpointSequence from "./db/CheckpointSequence.res.mjs";
23
25
  import * as SimulateDeadInputTracker from "./SimulateDeadInputTracker.res.mjs";
24
26
 
25
- function make(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt, committedCheckpointIdOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
27
+ function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt, committedFrontierOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
26
28
  let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : CrossChainState.calculateTargetBufferSize();
27
- let committedCheckpointId = committedCheckpointIdOpt !== undefined ? committedCheckpointIdOpt : Internal.initialCheckpointId;
29
+ let committedFrontier = committedFrontierOpt !== undefined ? committedFrontierOpt : Frontier.empty();
28
30
  let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
29
31
  let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
30
32
  let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
@@ -40,8 +42,8 @@ function make(config, persistence, chainStates, isInReorgThreshold, isRealtime,
40
42
  entities: EntityTables.make(EntityTables.crossChain(persistence.allEntities)),
41
43
  effectState: EffectState.make(),
42
44
  rollback: undefined,
43
- committedCheckpointId: committedCheckpointId,
44
- processedCheckpointId: committedCheckpointId,
45
+ committedFrontier: committedFrontier,
46
+ processedFrontier: Frontier.copy(committedFrontier),
45
47
  processedBatches: [],
46
48
  processedBatchesCount: 0,
47
49
  writeFiber: undefined,
@@ -52,7 +54,7 @@ function make(config, persistence, chainStates, isInReorgThreshold, isRealtime,
52
54
  chainMetaDirty: false,
53
55
  chainMetaThrottler: chainMetaThrottler,
54
56
  isProcessing: false,
55
- crossChainState: CrossChainState.make(chainStates, isInReorgThreshold, isRealtime, targetBufferSize),
57
+ crossChainState: CrossChainState.make(chainStates, isRealtime, targetBufferSize),
56
58
  rollbackState: "NoRollback",
57
59
  indexerStartTime: new Date(),
58
60
  indexerStartTimeRef: Performance.now(),
@@ -102,7 +104,7 @@ function makeFromDbState(config, persistence, initialState, registrationsByChain
102
104
  let chainConfig = ChainMap.get(config.chainMap, chainId);
103
105
  chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, initialState.contractMapping, registrationsByChainId, reducedPollingInterval);
104
106
  });
105
- let state = make(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSize, initialState.checkpointId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
107
+ let state = make(config, persistence, chainStates, isRealtime, targetBufferSize, initialState.checkpointFrontier, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
106
108
  CrossChainState.markCaughtUpOnResume(state.crossChainState);
107
109
  Utils.Dict.forEach(initialState.cache, param => {
108
110
  let count = param.count;
@@ -160,11 +162,11 @@ function getChainState(state, chainId) {
160
162
  }
161
163
 
162
164
  function getSafeCheckpointIdByChain(state) {
163
- return CrossChainState.getSafeCheckpointIdByChain(state.crossChainState, state.committedCheckpointId);
165
+ return CrossChainState.getSafeCheckpointIdByChain(state.crossChainState, state.config.checkpointSequence, state.committedFrontier);
164
166
  }
165
167
 
166
- function createBatch(state, processedCheckpointId, batchSizeTarget, isRollback) {
167
- return CrossChainState.createBatch(state.crossChainState, processedCheckpointId, batchSizeTarget, isRollback);
168
+ function createBatch(state, batchSizeTarget) {
169
+ return CrossChainState.createBatch(state.crossChainState, state.config, state.processedFrontier, batchSizeTarget);
168
170
  }
169
171
 
170
172
  function enterReorgThreshold(state) {
@@ -280,12 +282,16 @@ function effectState(state) {
280
282
  return state.effectState;
281
283
  }
282
284
 
283
- function committedCheckpointId(state) {
284
- return state.committedCheckpointId;
285
+ function committedFrontier(state) {
286
+ return state.committedFrontier;
285
287
  }
286
288
 
287
- function processedCheckpointId(state) {
288
- return state.processedCheckpointId;
289
+ function processedFrontier(state) {
290
+ return state.processedFrontier;
291
+ }
292
+
293
+ function committedCheckpointIdFor(state, scope) {
294
+ return CheckpointSequence.forScope(state.config.checkpointSequence, state.committedFrontier, scope);
289
295
  }
290
296
 
291
297
  function processedBatches(state) {
@@ -328,6 +334,10 @@ function isInReorgThreshold(state) {
328
334
  return CrossChainState.isInReorgThreshold(state.crossChainState);
329
335
  }
330
336
 
337
+ function shouldSaveHistory(state) {
338
+ return CrossChainState.shouldSaveHistory(state.crossChainState);
339
+ }
340
+
331
341
  function isRealtime(state) {
332
342
  return CrossChainState.isRealtime(state.crossChainState);
333
343
  }
@@ -401,7 +411,9 @@ function toMetrics(state) {
401
411
  chainId: s.chainId,
402
412
  method: s.method,
403
413
  count: s.count,
404
- seconds: s.seconds
414
+ seconds: s.seconds,
415
+ responseBlocks: s.responseBlocks,
416
+ emptyResponseCount: s.emptyResponseCount
405
417
  });
406
418
  });
407
419
  SourceManager.getSourceHeightSamples(sourceManager).forEach(s => {
@@ -614,16 +626,12 @@ function recordRollbackSuccess(state, timeSeconds, rollbackedProcessedEvents) {
614
626
 
615
627
  function queueProcessedBatch(state, batch) {
616
628
  state.processedBatches.push(batch);
617
- let checkpointId = Utils.$$Array.last(batch.checkpointIds);
618
- if (checkpointId !== undefined) {
619
- state.processedCheckpointId = checkpointId;
620
- return;
621
- }
629
+ state.processedFrontier = Frontier.mergeMax(state.processedFrontier, Batch.checkpointFrontier(batch));
622
630
  }
623
631
 
624
632
  function drainBatchRun(state) {
625
633
  let all = state.processedBatches;
626
- let isInReorgThreshold = all[0].isInReorgThreshold;
634
+ let history = all[0].history;
627
635
  let rest = [];
628
636
  let progressedChainsById = {};
629
637
  let totalBatchSize = {
@@ -634,10 +642,11 @@ function drainBatchRun(state) {
634
642
  let checkpointChainIds = [];
635
643
  let checkpointBlockNumbers = [];
636
644
  let checkpointBlockHashes = [];
645
+ let checkpointItemsCount = [];
637
646
  let checkpointEventsProcessed = [];
638
647
  let registeredAddresses = [];
639
648
  all.forEach(batch => {
640
- if (Utils.$$Array.isEmpty(rest) && batch.isInReorgThreshold === isInReorgThreshold) {
649
+ if (Utils.$$Array.isEmpty(rest) && Primitive_object.equal(batch.history, history)) {
641
650
  Utils.Dict.forEachWithKey(batch.progressedChainsById, (chainAfterBatch, key) => {
642
651
  progressedChainsById[key] = chainAfterBatch;
643
652
  });
@@ -647,6 +656,7 @@ function drainBatchRun(state) {
647
656
  checkpointChainIds.push(...batch.checkpointChainIds);
648
657
  checkpointBlockNumbers.push(...batch.checkpointBlockNumbers);
649
658
  checkpointBlockHashes.push(...batch.checkpointBlockHashes);
659
+ checkpointItemsCount.push(...batch.checkpointItemsCount);
650
660
  checkpointEventsProcessed.push(...batch.checkpointEventsProcessed);
651
661
  registeredAddresses.push(...batch.registeredAddresses);
652
662
  } else {
@@ -658,11 +668,12 @@ function drainBatchRun(state) {
658
668
  totalBatchSize: totalBatchSize.contents,
659
669
  items: items,
660
670
  progressedChainsById: progressedChainsById,
661
- isInReorgThreshold: isInReorgThreshold,
671
+ history: history,
662
672
  checkpointIds: checkpointIds,
663
673
  checkpointChainIds: checkpointChainIds,
664
674
  checkpointBlockNumbers: checkpointBlockNumbers,
665
675
  checkpointBlockHashes: checkpointBlockHashes,
676
+ checkpointItemsCount: checkpointItemsCount,
666
677
  checkpointEventsProcessed: checkpointEventsProcessed,
667
678
  registeredAddresses: registeredAddresses
668
679
  };
@@ -678,11 +689,17 @@ function takeRollback(state) {
678
689
  return rollback;
679
690
  }
680
691
 
681
- function markCommitted(state, upToCheckpointId) {
682
- state.committedCheckpointId = upToCheckpointId;
692
+ function markCommitted(state, writtenFrontier) {
693
+ state.committedFrontier = Frontier.mergeMax(state.committedFrontier, writtenFrontier);
683
694
  }
684
695
 
685
- function beginRollbackDiff(state, diffCheckpointId, floors, progressedChains, rolledBackAddresses) {
696
+ function beginRollbackDiff(state, floors, progressedChains, rolledBackAddresses) {
697
+ let cursor = CheckpointSequence.cursor(state.config.checkpointSequence, state.committedFrontier);
698
+ let diffFrontier = Frontier.fromEntries(Frontier.chainIds(floors.checkpointBounds.byChain).map(chainId => [
699
+ chainId,
700
+ CheckpointSequence.next(cursor, chainId)
701
+ ]));
702
+ state.processedFrontier = Frontier.mergeMax(state.processedFrontier, diffFrontier);
686
703
  let perChainEntities = EntityTables.perChain(state.allEntities);
687
704
  state.entities = EntityTables.make(EntityTables.crossChain(state.allEntities));
688
705
  Utils.Dict.forEach(CrossChainState.chainStates(state.crossChainState), cs => ChainState.resetEntities(cs, perChainEntities));
@@ -704,11 +721,20 @@ function beginRollbackDiff(state, diffCheckpointId, floors, progressedChains, ro
704
721
  progressedChains$1 = progressedChains;
705
722
  }
706
723
  state.rollback = {
707
- diffCheckpointId: diffCheckpointId,
724
+ diffFrontier: diffFrontier,
725
+ diffCheckpoints: Frontier.entries(diffFrontier).map(param => {
726
+ let chainId = param[0];
727
+ return {
728
+ chainId: chainId,
729
+ checkpointId: param[1],
730
+ blockNumber: ChainState.committedProgressBlockNumber(getChainState(state, chainId))
731
+ };
732
+ }),
708
733
  floors: floors,
709
734
  rolledBackAddresses: rolledBackAddresses$1,
710
735
  progressedChains: progressedChains$1
711
736
  };
737
+ return diffFrontier;
712
738
  }
713
739
 
714
740
  function recordWriteFailure(state, exn) {
@@ -801,8 +827,9 @@ export {
801
827
  entities,
802
828
  eachEntityTable,
803
829
  effectState,
804
- committedCheckpointId,
805
- processedCheckpointId,
830
+ committedFrontier,
831
+ processedFrontier,
832
+ committedCheckpointIdFor,
806
833
  processedBatches,
807
834
  processedBatchesCount,
808
835
  writeFiber,
@@ -813,6 +840,7 @@ export {
813
840
  crossChainState,
814
841
  chainStates,
815
842
  isInReorgThreshold,
843
+ shouldSaveHistory,
816
844
  isRealtime,
817
845
  isFinalizingIndexes,
818
846
  markCaughtUpIfSettled,
@@ -14,10 +14,9 @@ let make: (
14
14
  ~config: Config.t,
15
15
  ~persistence: Persistence.t,
16
16
  ~chainStates: dict<ChainState.t>,
17
- ~isInReorgThreshold: bool,
18
17
  ~isRealtime: bool,
19
18
  ~targetBufferSize: int=?,
20
- ~committedCheckpointId: Internal.checkpointId=?,
19
+ ~committedFrontier: Frontier.t=?,
21
20
  ~isDevelopmentMode: bool=?,
22
21
  ~shouldUseTui: bool=?,
23
22
  ~exitAfterFirstEventBlock: bool=?,
@@ -60,12 +59,7 @@ let beginProcessing: t => unit
60
59
  let endProcessing: t => unit
61
60
  let markProcessingStalledOnFetch: t => unit
62
61
  let recordProcessedBatch: t => unit
63
- let createBatch: (
64
- t,
65
- ~processedCheckpointId: Internal.checkpointId,
66
- ~batchSizeTarget: int,
67
- ~isRollback: bool,
68
- ) => Batch.t
62
+ let createBatch: (t, ~batchSizeTarget: int) => Batch.t
69
63
  let getSafeCheckpointIdByChain: t => array<(ChainId.t, option<Internal.checkpointId>)>
70
64
 
71
65
  // Accessors.
@@ -82,8 +76,9 @@ let eachEntityTable: (
82
76
  ) => unit,
83
77
  ) => unit
84
78
  let effectState: t => EffectState.t
85
- let committedCheckpointId: t => Internal.checkpointId
86
- let processedCheckpointId: t => Internal.checkpointId
79
+ let committedFrontier: t => Frontier.t
80
+ let processedFrontier: t => Frontier.t
81
+ let committedCheckpointIdFor: (t, ~scope: Internal.chainScope) => Internal.checkpointId
87
82
  let processedBatches: t => array<Batch.t>
88
83
  let processedBatchesCount: t => int
89
84
  let writeFiber: t => option<promise<unit>>
@@ -94,6 +89,7 @@ let chainMetaThrottler: t => Throttler.t
94
89
  let crossChainState: t => CrossChainState.t
95
90
  let chainStates: t => dict<ChainState.t>
96
91
  let isInReorgThreshold: t => bool
92
+ let shouldSaveHistory: t => dict<bool>
97
93
  let isRealtime: t => bool
98
94
  let isFinalizingIndexes: t => bool
99
95
  let markCaughtUpIfSettled: t => unit
@@ -137,14 +133,13 @@ let queueProcessedBatch: (t, ~batch: Batch.t) => unit
137
133
  let drainBatchRun: t => Batch.t
138
134
  let pendingRollback: t => option<Persistence.rollback>
139
135
  let takeRollback: t => option<Persistence.rollback>
140
- let markCommitted: (t, ~upToCheckpointId: Internal.checkpointId) => unit
136
+ let markCommitted: (t, ~writtenFrontier: Frontier.t) => unit
141
137
  let beginRollbackDiff: (
142
138
  t,
143
- ~diffCheckpointId: Internal.checkpointId,
144
139
  ~floors: RollbackFloors.t,
145
140
  ~progressedChains: array<InternalTable.Chains.progressedChain>,
146
141
  ~rolledBackAddresses: array<AddressRows.key>,
147
- ) => unit
142
+ ) => Frontier.t
148
143
  let recordWriteFailure: (t, exn) => unit
149
144
  let beginWriteFiber: (t, promise<unit>) => unit
150
145
  let endWriteFiber: t => unit
package/src/Internal.res CHANGED
@@ -854,8 +854,10 @@ type effectArgs = {
854
854
  input: effectInput,
855
855
  context: effectContext,
856
856
  cacheKey: string,
857
- // The processing checkpoint that referenced this effect; stamped on the
858
- // in-memory cache entry so it's evicted once the checkpoint commits.
857
+ // The processing checkpoint that referenced this effect, on the chain whose
858
+ // handler ran it; stamped on the in-memory cache entry so it's evicted once
859
+ // that chain commits the checkpoint.
860
+ chainId: ChainId.t,
859
861
  checkpointId: bigint,
860
862
  }
861
863
  type effectCacheItem = {id: string, output: effectOutput}
package/src/LoadLayer.res CHANGED
@@ -69,9 +69,10 @@ let loadById = (
69
69
  let entity = dbEntities->Array.getUnsafe(idx)
70
70
  entitiesMap->Dict.set(entity.id, entity)
71
71
  }
72
+ let committedCheckpointId = indexerState->IndexerState.committedCheckpointIdFor(~scope)
72
73
  idsToLoad->Array.forEach(entityId => {
73
74
  inMemTable->InMemoryTable.Entity.initValue(
74
- ~committedCheckpointId=indexerState->IndexerState.committedCheckpointId,
75
+ ~committedCheckpointId,
75
76
  ~key=entityId,
76
77
  ~entity=entitiesMap->Utils.Dict.dangerouslyGetNonOption(entityId),
77
78
  )
@@ -109,6 +110,7 @@ let callEffect = (
109
110
  effect.handler(arg)
110
111
  ->Promise.thenResolve(output => {
111
112
  inMemTable->InMemoryStore.setEffectOutput(
113
+ ~chainId=arg.chainId,
112
114
  ~checkpointId=arg.checkpointId,
113
115
  ~cacheKey=arg.cacheKey,
114
116
  ~output,
@@ -294,7 +296,11 @@ let loadEffect = (
294
296
  try {
295
297
  let output = dbEntity.output->S.parseOrThrow(outputSchema)
296
298
  idsFromCache->Utils.Set.add(dbEntity.id)->ignore
297
- inMemTable->InMemoryStore.initEffectOutputFromDb(~cacheKey=dbEntity.id, ~output)
299
+ inMemTable->InMemoryStore.initEffectOutputFromDb(
300
+ ~chainId=item->Internal.getItemChainId,
301
+ ~cacheKey=dbEntity.id,
302
+ ~output,
303
+ )
298
304
  } catch {
299
305
  | S.Raised(error) =>
300
306
  inMemTable->EffectState.recordInvalidation
@@ -379,7 +385,7 @@ let loadByFilter = (
379
385
  // Inside the load timing: waiting on the build is time the handler spends
380
386
  // waiting for this operation, and it's the only thing that explains an
381
387
  // occasional very slow getWhere.
382
- await storage.ensureQueryIndexes(~table=entityConfig.table, ~filters)
388
+ await storage.ensureQueryIndexes(~entityConfig, ~scope, ~filters)
383
389
 
384
390
  // Loading a superset of rows via a merged query is safe: every loaded
385
391
  // entity is matched against all registered indexes, not only the
@@ -397,9 +403,10 @@ let loadByFilter = (
397
403
  )
398
404
  )->(Utils.magic: array<unknown> => array<Internal.entity>)
399
405
 
406
+ let committedCheckpointId = indexerState->IndexerState.committedCheckpointIdFor(~scope)
400
407
  entities->Array.forEach(entity => {
401
408
  inMemTable->InMemoryTable.Entity.initValue(
402
- ~committedCheckpointId=indexerState->IndexerState.committedCheckpointId,
409
+ ~committedCheckpointId,
403
410
  ~key=entity.id,
404
411
  ~entity=Some(entity),
405
412
  )
@@ -4,6 +4,7 @@ import * as Table from "./db/Table.res.mjs";
4
4
  import * as Utils from "./Utils.res.mjs";
5
5
  import * as ChainId from "./ChainId.res.mjs";
6
6
  import * as Logging from "./Logging.res.mjs";
7
+ import * as Internal from "./Internal.res.mjs";
7
8
  import * as Ecosystem from "./Ecosystem.res.mjs";
8
9
  import * as EffectState from "./EffectState.res.mjs";
9
10
  import * as LoadManager from "./LoadManager.res.mjs";
@@ -71,7 +72,8 @@ function loadById(loadManager, persistence, entityConfig, scope, indexerState, s
71
72
  let entity = dbEntities[idx];
72
73
  entitiesMap[entity.id] = entity;
73
74
  }
74
- idsToLoad.forEach(entityId => InMemoryTable.Entity.initValue(inMemTable, IndexerState.committedCheckpointId(indexerState), entityId, entitiesMap[entityId]));
75
+ let committedCheckpointId = IndexerState.committedCheckpointIdFor(indexerState, scope);
76
+ idsToLoad.forEach(entityId => InMemoryTable.Entity.initValue(inMemTable, committedCheckpointId, entityId, entitiesMap[entityId]));
75
77
  return IndexerState.endStorageLoad(indexerState, timerRef, storage.name, key, idsToLoad.length, dbEntities.length);
76
78
  };
77
79
  return LoadManager.call(loadManager, entityId, key, load, LoadManager.noopHasher, shouldGroup, hash => hash in inMemTable.latestEntityChangeById, InMemoryTable.Entity.getUnsafe(inMemTable));
@@ -79,7 +81,7 @@ function loadById(loadManager, persistence, entityConfig, scope, indexerState, s
79
81
 
80
82
  function callEffect(effect, arg, inMemTable, timerRef, onError) {
81
83
  EffectState.startCall(inMemTable.stats, timerRef);
82
- return effect.handler(arg).then(output => InMemoryStore.setEffectOutput(inMemTable, arg.checkpointId, arg.cacheKey, output, arg.context.cache)).catch(exn => onError(arg.cacheKey, exn)).finally(() => EffectState.endCall(inMemTable.stats, timerRef));
84
+ return effect.handler(arg).then(output => InMemoryStore.setEffectOutput(inMemTable, arg.chainId, arg.checkpointId, arg.cacheKey, output, arg.context.cache)).catch(exn => onError(arg.cacheKey, exn)).finally(() => EffectState.endCall(inMemTable.stats, timerRef));
83
85
  }
84
86
 
85
87
  function executeWithRateLimit(effect, effectArgs, inMemTable, onError, isFromQueue) {
@@ -173,7 +175,7 @@ function loadEffect(loadManager, persistence, effect, effectArgs, scope, indexer
173
175
  try {
174
176
  let output = S$RescriptSchema.parseOrThrow(dbEntity.output, outputSchema);
175
177
  idsFromCache.add(dbEntity.id);
176
- return InMemoryStore.initEffectOutputFromDb(inMemTable, dbEntity.id, output);
178
+ return InMemoryStore.initEffectOutputFromDb(inMemTable, Internal.getItemChainId(item), dbEntity.id, output);
177
179
  } catch (raw_error) {
178
180
  let error = Primitive_exceptions.internalToException(raw_error);
179
181
  if (error.RE_EXN_ID === S$RescriptSchema.Raised) {
@@ -218,12 +220,13 @@ function loadByFilter(loadManager, persistence, entityConfig, scope, indexerStat
218
220
  contents: 0
219
221
  };
220
222
  filters.forEach(filter => InMemoryTable.Entity.addEmptyIndex(inMemTable, filter));
221
- await storage.ensureQueryIndexes(entityConfig.table, filters);
223
+ await storage.ensureQueryIndexes(entityConfig, scope, filters);
222
224
  let queries = EntityFilter.merge(filters);
223
225
  await Promise.all(queries.map(async filter => {
224
226
  try {
225
227
  let entities = await storage.loadOrThrow(scopeFilter(filter, entityConfig.table, scope), entityConfig.table);
226
- entities.forEach(entity => InMemoryTable.Entity.initValue(inMemTable, IndexerState.committedCheckpointId(indexerState), entity.id, entity));
228
+ let committedCheckpointId = IndexerState.committedCheckpointIdFor(indexerState, scope);
229
+ entities.forEach(entity => InMemoryTable.Entity.initValue(inMemTable, committedCheckpointId, entity.id, entity));
227
230
  size.contents = size.contents + entities.length | 0;
228
231
  return;
229
232
  } catch (raw_exn) {