envio 3.5.0-alpha.2 → 3.5.0-rc.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 (129) hide show
  1. package/package.json +6 -6
  2. package/src/Batch.res +8 -8
  3. package/src/Batch.res.mjs +4 -3
  4. package/src/BatchProcessing.res +24 -0
  5. package/src/BatchProcessing.res.mjs +12 -0
  6. package/src/ChainFetching.res +20 -19
  7. package/src/ChainFetching.res.mjs +14 -14
  8. package/src/ChainId.res +58 -0
  9. package/src/ChainId.res.mjs +49 -0
  10. package/src/ChainId.resi +27 -0
  11. package/src/ChainMap.res +12 -26
  12. package/src/ChainMap.res.mjs +5 -41
  13. package/src/ChainMap.resi +5 -20
  14. package/src/ChainMetadata.res +1 -1
  15. package/src/ChainMetadata.res.mjs +3 -2
  16. package/src/ChainState.res +45 -24
  17. package/src/ChainState.res.mjs +40 -16
  18. package/src/ChainState.resi +3 -1
  19. package/src/Config.res +43 -26
  20. package/src/Config.res.mjs +19 -16
  21. package/src/ContractRegisterContext.res +1 -1
  22. package/src/ContractRegisterContext.res.mjs +1 -1
  23. package/src/Core.res +1 -0
  24. package/src/CrossChainState.res +90 -22
  25. package/src/CrossChainState.res.mjs +55 -16
  26. package/src/CrossChainState.resi +5 -1
  27. package/src/Env.res +0 -3
  28. package/src/Env.res.mjs +0 -3
  29. package/src/EventConfigBuilder.res +1 -1
  30. package/src/EventProcessing.res +4 -4
  31. package/src/EventProcessing.res.mjs +5 -5
  32. package/src/FetchState.res +489 -153
  33. package/src/FetchState.res.mjs +399 -129
  34. package/src/FinalizeBackfill.res +61 -0
  35. package/src/FinalizeBackfill.res.mjs +44 -0
  36. package/src/HandlerRegister.res +8 -8
  37. package/src/HandlerRegister.res.mjs +5 -4
  38. package/src/HandlerRegister.resi +1 -1
  39. package/src/InMemoryStore.res +4 -0
  40. package/src/InMemoryStore.res.mjs +1 -1
  41. package/src/InMemoryTable.res +15 -15
  42. package/src/InMemoryTable.res.mjs +14 -14
  43. package/src/IndexerLoop.res +9 -2
  44. package/src/IndexerLoop.res.mjs +8 -1
  45. package/src/IndexerState.res +52 -23
  46. package/src/IndexerState.res.mjs +52 -14
  47. package/src/IndexerState.resi +11 -6
  48. package/src/Internal.res +19 -17
  49. package/src/Internal.res.mjs +18 -9
  50. package/src/LoadLayer.res +10 -2
  51. package/src/LoadLayer.res.mjs +3 -1
  52. package/src/LogSelection.res +2 -2
  53. package/src/Main.res +7 -11
  54. package/src/Main.res.mjs +4 -6
  55. package/src/Metrics.res +6 -6
  56. package/src/Metrics.res.mjs +4 -3
  57. package/src/Persistence.res +23 -4
  58. package/src/PgStorage.res +447 -100
  59. package/src/PgStorage.res.mjs +285 -99
  60. package/src/RawEvent.res +1 -2
  61. package/src/RawEvent.res.mjs +1 -1
  62. package/src/Rollback.res +18 -20
  63. package/src/Rollback.res.mjs +9 -9
  64. package/src/RollbackCommit.res +2 -2
  65. package/src/RollbackCommit.res.mjs +3 -3
  66. package/src/SafeCheckpointTracking.res +2 -2
  67. package/src/SimulateDeadInputTracker.res +17 -21
  68. package/src/SimulateDeadInputTracker.res.mjs +9 -8
  69. package/src/SimulateItems.res +8 -10
  70. package/src/SimulateItems.res.mjs +9 -10
  71. package/src/Sink.res +2 -2
  72. package/src/Sink.res.mjs +3 -2
  73. package/src/TestIndexer.res +14 -14
  74. package/src/TestIndexer.res.mjs +16 -10
  75. package/src/UserContext.res +2 -2
  76. package/src/UserContext.res.mjs +2 -1
  77. package/src/Utils.res +0 -4
  78. package/src/Utils.res.mjs +1 -6
  79. package/src/bindings/ClickHouse.res +20 -2
  80. package/src/bindings/ClickHouse.res.mjs +25 -14
  81. package/src/db/EntityFilter.res +0 -4
  82. package/src/db/EntityFilter.res.mjs +1 -8
  83. package/src/db/EntityHistory.res +1 -1
  84. package/src/db/IndexCatalog.res +248 -0
  85. package/src/db/IndexCatalog.res.mjs +222 -0
  86. package/src/db/IndexDefinition.res +131 -0
  87. package/src/db/IndexDefinition.res.mjs +138 -0
  88. package/src/db/IndexManager.res +145 -0
  89. package/src/db/IndexManager.res.mjs +130 -0
  90. package/src/db/InternalTable.res +54 -23
  91. package/src/db/InternalTable.res.mjs +38 -23
  92. package/src/db/Table.res +24 -14
  93. package/src/db/Table.res.mjs +21 -16
  94. package/src/sources/AddressSet.res +5 -4
  95. package/src/sources/AddressStore.res +25 -14
  96. package/src/sources/AddressStore.res.mjs +10 -12
  97. package/src/sources/Evm.res +2 -2
  98. package/src/sources/Evm.res.mjs +1 -1
  99. package/src/sources/EvmChain.res +3 -3
  100. package/src/sources/EvmChain.res.mjs +3 -3
  101. package/src/sources/EvmHyperSyncSource.res +7 -7
  102. package/src/sources/EvmHyperSyncSource.res.mjs +6 -6
  103. package/src/sources/Fuel.res +2 -2
  104. package/src/sources/Fuel.res.mjs +1 -1
  105. package/src/sources/FuelHyperSyncClient.res +3 -0
  106. package/src/sources/FuelHyperSyncClient.res.mjs +1 -0
  107. package/src/sources/FuelHyperSyncSource.res +5 -7
  108. package/src/sources/FuelHyperSyncSource.res.mjs +5 -5
  109. package/src/sources/HyperSync.resi +2 -2
  110. package/src/sources/HyperSyncClient.res +5 -0
  111. package/src/sources/HyperSyncClient.res.mjs +1 -0
  112. package/src/sources/RpcSource.res +13 -14
  113. package/src/sources/RpcSource.res.mjs +13 -12
  114. package/src/sources/SimulateSource.res +42 -15
  115. package/src/sources/SimulateSource.res.mjs +27 -9
  116. package/src/sources/Source.res +1 -5
  117. package/src/sources/SourceManager.res +12 -12
  118. package/src/sources/SourceManager.res.mjs +11 -10
  119. package/src/sources/SourceManager.resi +2 -2
  120. package/src/sources/Svm.res +4 -5
  121. package/src/sources/Svm.res.mjs +5 -4
  122. package/src/sources/SvmHyperSyncClient.res +3 -0
  123. package/src/sources/SvmHyperSyncClient.res.mjs +1 -0
  124. package/src/sources/SvmHyperSyncSource.res +4 -4
  125. package/src/sources/SvmHyperSyncSource.res.mjs +3 -3
  126. package/src/tui/Tui.res +1 -1
  127. package/src/tui/Tui.res.mjs +2 -1
  128. package/src/tui/components/CustomHooks.res +5 -4
  129. package/src/tui/components/CustomHooks.res.mjs +4 -2
@@ -1,78 +1,42 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Belt_Id from "@rescript/runtime/lib/es6/Belt_Id.js";
4
+ import * as ChainId from "./ChainId.res.mjs";
4
5
  import * as Belt_Map from "@rescript/runtime/lib/es6/Belt_Map.js";
5
- import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
6
- import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
6
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
8
7
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
9
8
 
10
- function toString(chainId) {
11
- return chainId.toString();
12
- }
13
-
14
- function makeUnsafe(chainId) {
15
- return chainId;
16
- }
17
-
18
- let Chain = {
19
- toString: toString,
20
- makeUnsafe: makeUnsafe
21
- };
22
-
23
- function cmp(a, b) {
24
- return Primitive_int.compare(a, b) | 0;
25
- }
26
-
27
9
  let ChainIdCmp = Belt_Id.MakeComparable({
28
- cmp: cmp
10
+ cmp: ChainId.compare
29
11
  });
30
12
 
31
13
  function fromArrayUnsafe(arr) {
32
14
  return Belt_Map.fromArray(arr, ChainIdCmp);
33
15
  }
34
16
 
35
- function get(self, chain) {
36
- let v = Belt_Map.get(self, chain);
17
+ function get(self, chainId) {
18
+ let v = Belt_Map.get(self, chainId);
37
19
  if (v !== undefined) {
38
20
  return Primitive_option.valFromOption(v);
39
21
  } else {
40
- return Stdlib_JsError.throwWithMessage("No chain with id " + chain.toString() + " found in chain map");
22
+ return Stdlib_JsError.throwWithMessage("No chain with id " + ChainId.toString(chainId) + " found in chain map");
41
23
  }
42
24
  }
43
25
 
44
- let set = Belt_Map.set;
45
-
46
26
  let values = Belt_Map.valuesToArray;
47
27
 
48
28
  let keys = Belt_Map.keysToArray;
49
29
 
50
- let entries = Belt_Map.toArray;
51
-
52
30
  let has = Belt_Map.has;
53
31
 
54
- let map = Belt_Map.map;
55
-
56
32
  let mapWithKey = Belt_Map.mapWithKey;
57
33
 
58
- let size = Belt_Map.size;
59
-
60
- function update(map, chain, updateFn) {
61
- return Belt_Map.update(map, chain, opt => Stdlib_Option.map(opt, updateFn));
62
- }
63
-
64
34
  export {
65
- Chain,
66
35
  fromArrayUnsafe,
67
36
  get,
68
- set,
69
37
  values,
70
38
  keys,
71
- entries,
72
39
  has,
73
- map,
74
40
  mapWithKey,
75
- size,
76
- update,
77
41
  }
78
42
  /* ChainIdCmp Not a pure module */
package/src/ChainMap.resi CHANGED
@@ -1,22 +1,7 @@
1
- module Chain: {
2
- type t
3
-
4
- external toChainId: t => int = "%identity"
5
-
6
- let toString: t => string
7
-
8
- let makeUnsafe: (~chainId: int) => t
9
- }
10
-
11
1
  type t<'a>
12
- let fromArrayUnsafe: array<(Chain.t, 'a)> => t<'a>
13
- let get: (t<'a>, Chain.t) => 'a
14
- let set: (t<'a>, Chain.t, 'a) => t<'a>
2
+ let fromArrayUnsafe: array<(ChainId.t, 'a)> => t<'a>
3
+ let get: (t<'a>, ChainId.t) => 'a
15
4
  let values: t<'a> => array<'a>
16
- let keys: t<'a> => array<Chain.t>
17
- let entries: t<'a> => array<(Chain.t, 'a)>
18
- let has: (t<'a>, Chain.t) => bool
19
- let map: (t<'a>, 'a => 'b) => t<'b>
20
- let mapWithKey: (t<'a>, (Chain.t, 'a) => 'b) => t<'b>
21
- let size: t<'a> => int
22
- let update: (t<'a>, Chain.t, 'a => 'a) => t<'a>
5
+ let keys: t<'a> => array<ChainId.t>
6
+ let has: (t<'a>, ChainId.t) => bool
7
+ let mapWithKey: (t<'a>, (ChainId.t, 'a) => 'b) => t<'b>
@@ -8,7 +8,7 @@ let stage = (state: IndexerState.t) => {
8
8
  ->Dict.valuesToArray
9
9
  ->Array.forEach(cs => {
10
10
  chainsData->Dict.set(
11
- (cs->ChainState.chainConfig).id->Int.toString,
11
+ (cs->ChainState.chainConfig).id->ChainId.toString,
12
12
  cs->ChainState.toChainMetadata,
13
13
  )
14
14
  })
@@ -1,5 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as ChainId from "./ChainId.res.mjs";
3
4
  import * as Writing from "./Writing.res.mjs";
4
5
  import * as ChainState from "./ChainState.res.mjs";
5
6
  import * as IndexerState from "./IndexerState.res.mjs";
@@ -7,7 +8,7 @@ import * as IndexerState from "./IndexerState.res.mjs";
7
8
  function stage(state) {
8
9
  let chainsData = {};
9
10
  Object.values(IndexerState.chainStates(state)).forEach(cs => {
10
- chainsData[ChainState.chainConfig(cs).id.toString()] = ChainState.toChainMetadata(cs);
11
+ chainsData[ChainId.toString(ChainState.chainConfig(cs).id)] = ChainState.toChainMetadata(cs);
11
12
  });
12
13
  Writing.setChainMeta(state, chainsData);
13
14
  }
@@ -15,4 +16,4 @@ function stage(state) {
15
16
  export {
16
17
  stage,
17
18
  }
18
- /* Writing Not a pure module */
19
+ /* ChainId Not a pure module */
@@ -69,13 +69,13 @@ let configAddresses = (chainConfig: Config.chain): array<Internal.indexingAddres
69
69
  }
70
70
 
71
71
  let validateOnEventRegistrations = (
72
- ~chainId: int,
72
+ ~chainId: ChainId.t,
73
73
  registrations: array<Internal.onEventRegistration>,
74
74
  ) =>
75
75
  registrations->Array.forEachWithIndex((registration, expectedIndex) => {
76
76
  if registration.index !== expectedIndex {
77
77
  JsError.throwWithMessage(
78
- `Invalid onEvent registration index for chain ${chainId->Int.toString}: ${registration.eventConfig.contractName}.${registration.eventConfig.name} has index ${registration.index->Int.toString}, but its ChainState position is ${expectedIndex->Int.toString}.`,
78
+ `Invalid onEvent registration index for chain ${chainId->ChainId.toString}: ${registration.eventConfig.contractName}.${registration.eventConfig.name} has index ${registration.index->Int.toString}, but its ChainState position is ${expectedIndex->Int.toString}.`,
79
79
  )
80
80
  }
81
81
  })
@@ -147,6 +147,7 @@ let makeInternal = (
147
147
  ~reorgCheckpoints: array<Internal.reorgCheckpoint>,
148
148
  ~maxReorgDepth,
149
149
  ~knownHeight=0,
150
+ ~isResumed=false,
150
151
  ~reducedPollingInterval=?,
151
152
  ): t => {
152
153
  // Handler binding + `where`-derived fetch state, and onBlock registrations,
@@ -154,7 +155,7 @@ let makeInternal = (
154
155
  // chain - this just looks up this chain's slice.
155
156
  let {onEventRegistrations, onBlockRegistrations} =
156
157
  registrationsByChainId
157
- ->Utils.Dict.dangerouslyGetNonOption(chainConfig.id->Int.toString)
158
+ ->Utils.Dict.dangerouslyGetNonOption(chainConfig.id->ChainId.toString)
158
159
  ->Option.getOr({onEventRegistrations: [], onBlockRegistrations: []})
159
160
 
160
161
  chainConfig.contracts->Array.forEach(contract => {
@@ -203,6 +204,7 @@ let makeInternal = (
203
204
  | Evm => Some(config.clientFilterAddressThreshold)
204
205
  | Fuel | Svm => None
205
206
  },
207
+ ~isResumed,
206
208
  )
207
209
 
208
210
  let chainReorgCheckpoints = reorgCheckpoints->Array.filterMap(reorgCheckpoint => {
@@ -214,7 +216,7 @@ let makeInternal = (
214
216
  })
215
217
 
216
218
  // Create sources lazily here - this is where API token validation happens
217
- let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)
219
+ let chainId = chainConfig.id
218
220
  let sources = switch chainConfig.sourceConfig {
219
221
  | Config.EvmSourceConfig({hypersync, rpcs}) =>
220
222
  let evmRpcs: array<EvmChain.rpc> = rpcs->Array.map((rpc): EvmChain.rpc => {
@@ -230,7 +232,7 @@ let makeInternal = (
230
232
  }
231
233
  })
232
234
  EvmChain.makeSources(
233
- ~chain,
235
+ ~chainId,
234
236
  ~onEventRegistrations=onEventRegistrations->(
235
237
  Utils.magic: array<Internal.onEventRegistration> => array<Internal.evmOnEventRegistration>
236
238
  ),
@@ -241,7 +243,7 @@ let makeInternal = (
241
243
  )
242
244
  | Config.FuelSourceConfig({hypersync}) => [
243
245
  FuelHyperSyncSource.make({
244
- chain,
246
+ chainId,
245
247
  endpointUrl: hypersync,
246
248
  apiToken: Env.envioApiToken,
247
249
  onEventRegistrations,
@@ -251,17 +253,15 @@ let makeInternal = (
251
253
  | Config.SvmSourceConfig({hypersync, rpc}) =>
252
254
  switch (hypersync, rpc) {
253
255
  | (None, None) =>
254
- JsError.throwWithMessage(
255
- `Chain ${chain->ChainMap.Chain.toChainId->Int.toString} has no SVM data source`,
256
- )
257
- | (None, Some(rpc)) => [Svm.makeRPCSource(~chain, ~rpc)]
256
+ JsError.throwWithMessage(`Chain ${chainId->ChainId.toString} has no SVM data source`)
257
+ | (None, Some(rpc)) => [Svm.makeRPCSource(~chainId, ~rpc)]
258
258
  | (Some(hypersyncUrl), _) =>
259
259
  // HyperSync drives instruction sync. A configured RPC is ignored for now
260
260
  // (RPC fallback isn't wired up yet).
261
261
  let apiToken = Env.envioApiToken
262
262
  [
263
263
  SvmHyperSyncSource.make({
264
- chain,
264
+ chainId,
265
265
  endpointUrl: hypersyncUrl,
266
266
  apiToken,
267
267
  onEventRegistrations,
@@ -270,6 +270,9 @@ let makeInternal = (
270
270
  }),
271
271
  ]
272
272
  }
273
+ | Config.SimulateSourceConfig({items, endBlock}) => [
274
+ SimulateSource.make(~items, ~endBlock, ~chainId, ~addressStore),
275
+ ]
273
276
  // For tests: use ready-to-use sources directly
274
277
  | Config.CustomSources(sources) => sources
275
278
  }
@@ -375,14 +378,13 @@ let makeFromDbState = (
375
378
  ~maxReorgDepth=resumedChainState.maxReorgDepth,
376
379
  ~firstEventBlock=resumedChainState.firstEventBlockNumber,
377
380
  ~progressBlockNumber,
378
- ~timestampCaughtUpToHeadOrEndblock=Env.updateSyncTimeOnRestart
379
- ? None
380
- : resumedChainState.timestampCaughtUpToHeadOrEndblock,
381
+ ~timestampCaughtUpToHeadOrEndblock=resumedChainState.timestampCaughtUpToHeadOrEndblock,
381
382
  ~numEventsProcessed=resumedChainState.numEventsProcessed,
382
383
  ~logger,
383
384
  ~isInReorgThreshold,
384
385
  ~isRealtime,
385
386
  ~knownHeight=resumedChainState.sourceBlockNumber,
387
+ ~isResumed=true,
386
388
  ~reducedPollingInterval?,
387
389
  )
388
390
  }
@@ -612,6 +614,24 @@ let hasProcessedToEndblock = (cs: t) => {
612
614
  }
613
615
  }
614
616
 
617
+ // Caught up as judged by persisted values alone: progress reached the endBlock,
618
+ // or the head the previous run had already observed (less the lag that holds the
619
+ // tip back). Unlike `isFetchingAtHead` this doesn't move when a fresh height
620
+ // lands, so a run resumed at the head still knows it was caught up even once the
621
+ // head has run away from it while the indexer was down.
622
+ let isDurablyCaughtUp = (cs: t) => {
623
+ let {committedProgressBlockNumber, fetchState} = cs
624
+ switch fetchState.endBlock {
625
+ | Some(endBlock) => committedProgressBlockNumber >= endBlock
626
+ | None =>
627
+ // The configured lag, not the fetch state's: pre-threshold that one also
628
+ // carries maxReorgDepth, which would read a chain a whole reorg depth behind
629
+ // the head as caught up.
630
+ fetchState.knownHeight > 0 &&
631
+ committedProgressBlockNumber >= fetchState.knownHeight - cs.chainConfig.blockLag
632
+ }
633
+ }
634
+
615
635
  let getHighestBlockBelowThreshold = (cs: t): int => {
616
636
  let highestBlockBelowThreshold = cs.fetchState.knownHeight - cs.chainConfig.maxReorgDepth
617
637
  highestBlockBelowThreshold < 0 ? 0 : highestBlockBelowThreshold
@@ -908,7 +928,7 @@ let toChainMetadata = (cs: t): InternalTable.Chains.metaFields => {
908
928
  }
909
929
 
910
930
  let toMetrics = (cs: t): Metrics.chainMetrics => {
911
- chainId: cs.chainConfig.id->Int.toFloat,
931
+ chainId: cs.chainConfig.id,
912
932
  poweredByHyperSync: (cs.sourceManager->SourceManager.getActiveSource).poweredByHyperSync,
913
933
  firstEventBlockNumber: cs.fetchState.firstEventBlock,
914
934
  latestProcessedBlock: cs.committedProgressBlockNumber === -1
@@ -958,7 +978,7 @@ let toChainBeforeBatch = (cs: t): Batch.chainBeforeBatch => {
958
978
  // Whether the chain's post-batch fetch frontier is ready to cross into the reorg
959
979
  // threshold, using the batch's progressed frontier when this chain advanced.
960
980
  let isReadyToEnterReorgThresholdAfterBatch = (cs: t, ~batch: Batch.t) => {
961
- let fetchState = switch batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
981
+ let fetchState = switch batch.progressedChainsById->ChainId.Dict.dangerouslyGetNonOption(
962
982
  cs.fetchState.chainId,
963
983
  ) {
964
984
  | Some(chainAfterBatch) => chainAfterBatch.fetchState
@@ -970,9 +990,7 @@ let isReadyToEnterReorgThresholdAfterBatch = (cs: t, ~batch: Batch.t) => {
970
990
  // Commit the post-batch fetch frontier for a chain that progressed in the batch,
971
991
  // applying blockLag when this batch also crosses into the reorg threshold.
972
992
  let advanceAfterBatch = (cs: t, ~batch: Batch.t, ~enteringReorgThreshold) =>
973
- switch batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
974
- cs.fetchState.chainId,
975
- ) {
993
+ switch batch.progressedChainsById->ChainId.Dict.dangerouslyGetNonOption(cs.fetchState.chainId) {
976
994
  | Some(chainAfterBatch) =>
977
995
  cs.fetchState = enteringReorgThreshold
978
996
  ? chainAfterBatch.fetchState->FetchState.updateInternal(~blockLag=cs.chainConfig.blockLag)
@@ -992,7 +1010,7 @@ let advanceAfterBatch = (cs: t, ~batch: Batch.t, ~enteringReorgThreshold) =>
992
1010
  let applyBatchProgress = (cs: t, ~batch: Batch.t, ~blockTimestampName: string) => {
993
1011
  let chainId = cs.chainConfig.id
994
1012
 
995
- switch batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(chainId) {
1013
+ switch batch.progressedChainsById->ChainId.Dict.dangerouslyGetNonOption(chainId) {
996
1014
  | Some(chainAfterBatch) => {
997
1015
  // Calculate and set latency metrics. The payload block is materialised or
998
1016
  // inline by processing time; its timestamp may still be absent (e.g. an
@@ -1082,11 +1100,14 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t, ~blockTimestampName: string) =
1082
1100
  }
1083
1101
 
1084
1102
  // Mark the chain caught up to head/endblock. Called by CrossChainState only once
1085
- // every chain in the indexer is caught up, so no chain flips to ready while
1086
- // another is still backfilling. Sticky: a chain stays ready once set.
1087
- let markReady = (cs: t) =>
1103
+ // every chain in the indexer is caught up and the deferred schema indexes are
1104
+ // committed, so no chain flips to ready while another is still backfilling or
1105
+ // while an index the schema promises is still missing. `readyAt` is the
1106
+ // timestamp already committed to `envio_chains.ready_at` in that same
1107
+ // transaction. Sticky: a chain stays ready once set.
1108
+ let markReady = (cs: t, ~readyAt) =>
1088
1109
  if !(cs->isReady) {
1089
- cs.timestampCaughtUpToHeadOrEndblock = Date.make()->Some
1110
+ cs.timestampCaughtUpToHeadOrEndblock = Some(readyAt)
1090
1111
  }
1091
1112
 
1092
1113
  // Roll a chain back to a reorg target. With a progress diff, restore fetch/
@@ -4,8 +4,8 @@ import * as Env from "./Env.res.mjs";
4
4
  import * as Svm from "./sources/Svm.res.mjs";
5
5
  import * as Batch from "./Batch.res.mjs";
6
6
  import * as Utils from "./Utils.res.mjs";
7
+ import * as ChainId from "./ChainId.res.mjs";
7
8
  import * as Logging from "./Logging.res.mjs";
8
- import * as ChainMap from "./ChainMap.res.mjs";
9
9
  import * as EvmChain from "./sources/EvmChain.res.mjs";
10
10
  import * as FieldMask from "./sources/FieldMask.res.mjs";
11
11
  import * as BlockStore from "./sources/BlockStore.res.mjs";
@@ -17,6 +17,7 @@ import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
17
17
  import * as SourceManager from "./sources/SourceManager.res.mjs";
18
18
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
19
19
  import * as ReorgDetection from "./ReorgDetection.res.mjs";
20
+ import * as SimulateSource from "./sources/SimulateSource.res.mjs";
20
21
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
21
22
  import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
22
23
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
@@ -42,7 +43,7 @@ function configAddresses(chainConfig) {
42
43
  function validateOnEventRegistrations(chainId, registrations) {
43
44
  registrations.forEach((registration, expectedIndex) => {
44
45
  if (registration.index !== expectedIndex) {
45
- return Stdlib_JsError.throwWithMessage(`Invalid onEvent registration index for chain ` + chainId.toString() + `: ` + registration.eventConfig.contractName + `.` + registration.eventConfig.name + ` has index ` + registration.index.toString() + `, but its ChainState position is ` + expectedIndex.toString() + `.`);
46
+ return Stdlib_JsError.throwWithMessage(`Invalid onEvent registration index for chain ` + ChainId.toString(chainId) + `: ` + registration.eventConfig.contractName + `.` + registration.eventConfig.name + ` has index ` + registration.index.toString() + `, but its ChainState position is ` + expectedIndex.toString() + `.`);
46
47
  }
47
48
  });
48
49
  }
@@ -89,10 +90,11 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
89
90
  };
90
91
  }
91
92
 
92
- function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firstEventBlockOpt, progressBlockNumber, config, registrationsByChainId, logger, timestampCaughtUpToHeadOrEndblock, numEventsProcessed, isInReorgThreshold, isRealtime, reorgCheckpoints, maxReorgDepth, knownHeightOpt, reducedPollingInterval) {
93
+ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firstEventBlockOpt, progressBlockNumber, config, registrationsByChainId, logger, timestampCaughtUpToHeadOrEndblock, numEventsProcessed, isInReorgThreshold, isRealtime, reorgCheckpoints, maxReorgDepth, knownHeightOpt, isResumedOpt, reducedPollingInterval) {
93
94
  let firstEventBlock = firstEventBlockOpt !== undefined ? Primitive_option.valFromOption(firstEventBlockOpt) : undefined;
94
95
  let knownHeight = knownHeightOpt !== undefined ? knownHeightOpt : 0;
95
- let match = Stdlib_Option.getOr(registrationsByChainId[chainConfig.id.toString()], {
96
+ let isResumed = isResumedOpt !== undefined ? isResumedOpt : false;
97
+ let match = Stdlib_Option.getOr(registrationsByChainId[ChainId.toString(chainConfig.id)], {
96
98
  onEventRegistrations: [],
97
99
  onBlockRegistrations: []
98
100
  });
@@ -116,13 +118,13 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
116
118
  tmp = undefined;
117
119
  break;
118
120
  }
119
- let fetchState = FetchState.make(startBlock, endBlock, onEventRegistrations, addressStore, indexingAddresses, config.maxAddrInPartition, chainConfig.id, (config.batchSize << 1), knownHeight, progressBlockNumber, match.onBlockRegistrations, Primitive_int.max(!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth, chainConfig.blockLag), Primitive_option.some(firstEventBlock), Primitive_option.some(tmp));
121
+ let fetchState = FetchState.make(startBlock, endBlock, onEventRegistrations, addressStore, indexingAddresses, config.maxAddrInPartition, chainConfig.id, (config.batchSize << 1), knownHeight, progressBlockNumber, match.onBlockRegistrations, Primitive_int.max(!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth, chainConfig.blockLag), Primitive_option.some(firstEventBlock), Primitive_option.some(tmp), isResumed);
120
122
  let chainReorgCheckpoints = Stdlib_Array.filterMap(reorgCheckpoints, reorgCheckpoint => {
121
123
  if (reorgCheckpoint.chain_id === chainConfig.id) {
122
124
  return reorgCheckpoint;
123
125
  }
124
126
  });
125
- let chain = ChainMap.Chain.makeUnsafe(chainConfig.id);
127
+ let chainId = chainConfig.id;
126
128
  let sources = chainConfig.sourceConfig;
127
129
  let sources$1;
128
130
  switch (sources.TAG) {
@@ -139,11 +141,11 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
139
141
  headers: headers
140
142
  };
141
143
  });
142
- sources$1 = EvmChain.makeSources(chain, onEventRegistrations, sources.hypersync, evmRpcs, lowercaseAddresses, addressStore);
144
+ sources$1 = EvmChain.makeSources(chainId, onEventRegistrations, sources.hypersync, evmRpcs, lowercaseAddresses, addressStore);
143
145
  break;
144
146
  case "FuelSourceConfig" :
145
147
  sources$1 = [FuelHyperSyncSource.make({
146
- chain: chain,
148
+ chainId: chainId,
147
149
  endpointUrl: sources.hypersync,
148
150
  apiToken: Env.envioApiToken,
149
151
  onEventRegistrations: onEventRegistrations,
@@ -154,16 +156,19 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
154
156
  let rpc = sources.rpc;
155
157
  let hypersync = sources.hypersync;
156
158
  sources$1 = hypersync !== undefined ? [SvmHyperSyncSource.make({
157
- chain: chain,
159
+ chainId: chainId,
158
160
  endpointUrl: hypersync,
159
161
  apiToken: Env.envioApiToken,
160
162
  onEventRegistrations: onEventRegistrations,
161
163
  clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
162
164
  addressStore: addressStore
163
165
  })] : (
164
- rpc !== undefined ? [Svm.makeRPCSource(chain, rpc, undefined)] : Stdlib_JsError.throwWithMessage(`Chain ` + chain.toString() + ` has no SVM data source`)
166
+ rpc !== undefined ? [Svm.makeRPCSource(chainId, rpc, undefined)] : Stdlib_JsError.throwWithMessage(`Chain ` + ChainId.toString(chainId) + ` has no SVM data source`)
165
167
  );
166
168
  break;
169
+ case "SimulateSourceConfig" :
170
+ sources$1 = [SimulateSource.make(sources.items, sources.endBlock, chainId, addressStore)];
171
+ break;
167
172
  case "CustomSources" :
168
173
  sources$1 = sources._0;
169
174
  break;
@@ -177,7 +182,7 @@ function makeFromConfig(chainConfig, config, registrationsByChainId, knownHeight
177
182
  let logger = Logging.createChild({
178
183
  chainId: chainConfig.id
179
184
  });
180
- return makeInternal(chainConfig, configAddresses(chainConfig), chainConfig.startBlock, chainConfig.endBlock, undefined, -1, config, registrationsByChainId, logger, undefined, 0, false, false, [], chainConfig.maxReorgDepth, knownHeight, undefined);
185
+ return makeInternal(chainConfig, configAddresses(chainConfig), chainConfig.startBlock, chainConfig.endBlock, undefined, -1, config, registrationsByChainId, logger, undefined, 0, false, false, [], chainConfig.maxReorgDepth, knownHeight, undefined, undefined);
181
186
  }
182
187
 
183
188
  function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInReorgThreshold, isRealtime, config, registrationsByChainId, reducedPollingInterval) {
@@ -186,7 +191,7 @@ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInR
186
191
  chainId: chainId
187
192
  });
188
193
  let progressBlockNumber = resumedChainState.progressBlockNumber >= 0 ? resumedChainState.progressBlockNumber : resumedChainState.startBlock - 1 | 0;
189
- return makeInternal(chainConfig, resumedChainState.indexingAddresses, resumedChainState.startBlock, resumedChainState.endBlock, Primitive_option.some(resumedChainState.firstEventBlockNumber), progressBlockNumber, config, registrationsByChainId, logger, Env.updateSyncTimeOnRestart ? undefined : resumedChainState.timestampCaughtUpToHeadOrEndblock, resumedChainState.numEventsProcessed, isInReorgThreshold, isRealtime, reorgCheckpoints, resumedChainState.maxReorgDepth, resumedChainState.sourceBlockNumber, reducedPollingInterval);
194
+ return makeInternal(chainConfig, resumedChainState.indexingAddresses, resumedChainState.startBlock, resumedChainState.endBlock, Primitive_option.some(resumedChainState.firstEventBlockNumber), progressBlockNumber, config, registrationsByChainId, logger, resumedChainState.timestampCaughtUpToHeadOrEndblock, resumedChainState.numEventsProcessed, isInReorgThreshold, isRealtime, reorgCheckpoints, resumedChainState.maxReorgDepth, resumedChainState.sourceBlockNumber, true, reducedPollingInterval);
190
195
  }
191
196
 
192
197
  function logger(cs) {
@@ -283,6 +288,11 @@ function startFetchingQueries(cs, queries) {
283
288
  cs.pendingBudget = cs.pendingBudget + Stdlib_Array.reduce(queries, 0, (acc, query) => acc + query.itemsEst);
284
289
  }
285
290
 
291
+ function resetPendingQueries(cs) {
292
+ cs.fetchState = FetchState.resetPendingQueries(cs.fetchState);
293
+ cs.pendingBudget = 0;
294
+ }
295
+
286
296
  function isReady(cs) {
287
297
  return cs.timestampCaughtUpToHeadOrEndblock !== undefined;
288
298
  }
@@ -389,6 +399,19 @@ function hasProcessedToEndblock(cs) {
389
399
  }
390
400
  }
391
401
 
402
+ function isDurablyCaughtUp(cs) {
403
+ let fetchState = cs.fetchState;
404
+ let committedProgressBlockNumber = cs.committedProgressBlockNumber;
405
+ let endBlock = fetchState.endBlock;
406
+ if (endBlock !== undefined) {
407
+ return committedProgressBlockNumber >= endBlock;
408
+ } else if (fetchState.knownHeight > 0) {
409
+ return committedProgressBlockNumber >= (fetchState.knownHeight - cs.chainConfig.blockLag | 0);
410
+ } else {
411
+ return false;
412
+ }
413
+ }
414
+
392
415
  function getHighestBlockBelowThreshold(cs) {
393
416
  let highestBlockBelowThreshold = cs.fetchState.knownHeight - cs.chainConfig.maxReorgDepth | 0;
394
417
  if (highestBlockBelowThreshold < 0) {
@@ -564,8 +587,7 @@ function prepareReorg(cs, eventsProcessedDiff) {
564
587
  if (eventsProcessedDiff !== undefined) {
565
588
  cs.numEventsProcessed = cs.numEventsProcessed + eventsProcessedDiff;
566
589
  }
567
- cs.fetchState = FetchState.resetPendingQueries(cs.fetchState);
568
- cs.pendingBudget = 0;
590
+ resetPendingQueries(cs);
569
591
  }
570
592
 
571
593
  function updateKnownHeight(cs, knownHeight) {
@@ -757,9 +779,9 @@ function applyBatchProgress(cs, batch, blockTimestampName) {
757
779
  }
758
780
  }
759
781
 
760
- function markReady(cs) {
782
+ function markReady(cs, readyAt) {
761
783
  if (cs.timestampCaughtUpToHeadOrEndblock === undefined) {
762
- cs.timestampCaughtUpToHeadOrEndblock = new Date();
784
+ cs.timestampCaughtUpToHeadOrEndblock = readyAt;
763
785
  return;
764
786
  }
765
787
  }
@@ -831,6 +853,7 @@ export {
831
853
  toChainBeforeBatch,
832
854
  isReadyToEnterReorgThresholdAfterBatch,
833
855
  hasProcessedToEndblock,
856
+ isDurablyCaughtUp,
834
857
  getHighestBlockBelowThreshold,
835
858
  isActivelyIndexing,
836
859
  isReady,
@@ -841,6 +864,7 @@ export {
841
864
  materializePageItems,
842
865
  registerReorgGuard,
843
866
  prepareReorg,
867
+ resetPendingQueries,
844
868
  updateKnownHeight,
845
869
  setEndBlockToFirstEvent,
846
870
  enterReorgThreshold,
@@ -98,6 +98,7 @@ let isReadyToEnterReorgThresholdAfterBatch: (t, ~batch: Batch.t) => bool
98
98
 
99
99
  // Derived (pure).
100
100
  let hasProcessedToEndblock: t => bool
101
+ let isDurablyCaughtUp: t => bool
101
102
  let getHighestBlockBelowThreshold: t => int
102
103
  let isActivelyIndexing: t => bool
103
104
  let isReady: t => bool
@@ -132,12 +133,13 @@ let registerReorgGuard: (
132
133
  ~knownHeight: int,
133
134
  ) => ReorgDetection.reorgResult
134
135
  let prepareReorg: (t, ~eventsProcessedDiff: option<float>) => unit
136
+ let resetPendingQueries: t => unit
135
137
  let updateKnownHeight: (t, ~knownHeight: int) => unit
136
138
  let setEndBlockToFirstEvent: (t, ~blockNumber: int) => unit
137
139
  let enterReorgThreshold: t => unit
138
140
  let advanceAfterBatch: (t, ~batch: Batch.t, ~enteringReorgThreshold: bool) => unit
139
141
  let applyBatchProgress: (t, ~batch: Batch.t, ~blockTimestampName: string) => unit
140
- let markReady: t => unit
142
+ let markReady: (t, ~readyAt: Date.t) => unit
141
143
  let rollback: (
142
144
  t,
143
145
  ~newProgressBlockNumber: option<int>,