envio 3.3.0-alpha.0 → 3.3.0-alpha.10

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 (140) hide show
  1. package/evm.schema.json +10 -0
  2. package/index.d.ts +45 -25
  3. package/package.json +7 -7
  4. package/src/Address.res +5 -2
  5. package/src/Address.res.mjs +3 -1
  6. package/src/Api.res +1 -1
  7. package/src/Api.res.mjs +1 -1
  8. package/src/Batch.res +0 -6
  9. package/src/Batch.res.mjs +1 -12
  10. package/src/BatchProcessing.res +7 -12
  11. package/src/BatchProcessing.res.mjs +8 -7
  12. package/src/Bin.res +3 -0
  13. package/src/Bin.res.mjs +4 -0
  14. package/src/ChainFetching.res +48 -16
  15. package/src/ChainFetching.res.mjs +36 -17
  16. package/src/ChainMetadata.res +1 -11
  17. package/src/ChainMetadata.res.mjs +1 -10
  18. package/src/ChainState.res +628 -149
  19. package/src/ChainState.res.mjs +440 -102
  20. package/src/ChainState.resi +74 -4
  21. package/src/Config.res +61 -33
  22. package/src/Config.res.mjs +50 -10
  23. package/src/ContractRegisterContext.res +2 -12
  24. package/src/ContractRegisterContext.res.mjs +3 -5
  25. package/src/Core.res +14 -3
  26. package/src/CrossChainState.res +142 -93
  27. package/src/CrossChainState.res.mjs +88 -60
  28. package/src/CrossChainState.resi +2 -10
  29. package/src/Ecosystem.res +5 -6
  30. package/src/Ecosystem.res.mjs +3 -3
  31. package/src/Env.res +0 -7
  32. package/src/Env.res.mjs +0 -6
  33. package/src/Envio.res +17 -11
  34. package/src/EnvioGlobal.res +53 -0
  35. package/src/EnvioGlobal.res.mjs +31 -0
  36. package/src/EventConfigBuilder.res +177 -88
  37. package/src/EventConfigBuilder.res.mjs +99 -42
  38. package/src/EventProcessing.res +74 -28
  39. package/src/EventProcessing.res.mjs +55 -29
  40. package/src/ExitOnCaughtUp.res +10 -2
  41. package/src/ExitOnCaughtUp.res.mjs +10 -1
  42. package/src/FetchState.res +558 -300
  43. package/src/FetchState.res.mjs +437 -423
  44. package/src/HandlerLoader.res +8 -104
  45. package/src/HandlerLoader.res.mjs +2 -81
  46. package/src/HandlerRegister.res +518 -144
  47. package/src/HandlerRegister.res.mjs +285 -133
  48. package/src/HandlerRegister.resi +24 -8
  49. package/src/IndexerLoop.res +2 -3
  50. package/src/IndexerLoop.res.mjs +1 -1
  51. package/src/IndexerState.res +10 -11
  52. package/src/IndexerState.res.mjs +14 -11
  53. package/src/IndexerState.resi +2 -5
  54. package/src/IndexingAddresses.res +108 -0
  55. package/src/IndexingAddresses.res.mjs +101 -0
  56. package/src/IndexingAddresses.resi +34 -0
  57. package/src/Internal.res +155 -46
  58. package/src/Internal.res.mjs +30 -2
  59. package/src/LoadLayer.res +5 -5
  60. package/src/LoadLayer.res.mjs +6 -6
  61. package/src/LogSelection.res +102 -165
  62. package/src/LogSelection.res.mjs +101 -116
  63. package/src/Main.res +71 -236
  64. package/src/Main.res.mjs +60 -145
  65. package/src/Metrics.res +74 -0
  66. package/src/Metrics.res.mjs +75 -0
  67. package/src/PgStorage.res +4 -4
  68. package/src/PgStorage.res.mjs +5 -5
  69. package/src/Prometheus.res +10 -74
  70. package/src/Prometheus.res.mjs +124 -186
  71. package/src/PruneStaleHistory.res +2 -2
  72. package/src/PruneStaleHistory.res.mjs +3 -3
  73. package/src/RawEvent.res +7 -2
  74. package/src/RawEvent.res.mjs +4 -4
  75. package/src/Rollback.res +3 -3
  76. package/src/Rollback.res.mjs +4 -4
  77. package/src/RollbackCommit.res +4 -1
  78. package/src/RollbackCommit.res.mjs +3 -2
  79. package/src/SimulateDeadInputTracker.res +85 -0
  80. package/src/SimulateDeadInputTracker.res.mjs +73 -0
  81. package/src/SimulateDeadInputTracker.resi +12 -0
  82. package/src/SimulateItems.res +91 -23
  83. package/src/SimulateItems.res.mjs +55 -32
  84. package/src/TestIndexer.res +54 -40
  85. package/src/TestIndexer.res.mjs +37 -30
  86. package/src/bindings/Performance.res +7 -0
  87. package/src/bindings/Performance.res.mjs +21 -0
  88. package/src/bindings/Performance.resi +7 -0
  89. package/src/bindings/Viem.res +0 -41
  90. package/src/bindings/Viem.res.mjs +1 -43
  91. package/src/sources/BlockStore.res +46 -0
  92. package/src/sources/BlockStore.res.mjs +24 -0
  93. package/src/sources/EventRouter.res +36 -33
  94. package/src/sources/EventRouter.res.mjs +13 -14
  95. package/src/sources/Evm.res +83 -72
  96. package/src/sources/Evm.res.mjs +53 -69
  97. package/src/sources/EvmChain.res +18 -20
  98. package/src/sources/EvmChain.res.mjs +15 -15
  99. package/src/sources/EvmRpcClient.res +74 -5
  100. package/src/sources/EvmRpcClient.res.mjs +13 -4
  101. package/src/sources/FieldMask.res +39 -0
  102. package/src/sources/FieldMask.res.mjs +42 -0
  103. package/src/sources/Fuel.res +9 -6
  104. package/src/sources/Fuel.res.mjs +5 -10
  105. package/src/sources/HyperFuelSource.res +46 -50
  106. package/src/sources/HyperFuelSource.res.mjs +64 -54
  107. package/src/sources/HyperSync.res +39 -7
  108. package/src/sources/HyperSync.res.mjs +57 -37
  109. package/src/sources/HyperSync.resi +12 -2
  110. package/src/sources/HyperSyncClient.res +22 -111
  111. package/src/sources/HyperSyncClient.res.mjs +2 -30
  112. package/src/sources/HyperSyncHeightStream.res +1 -8
  113. package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
  114. package/src/sources/HyperSyncSource.res +94 -107
  115. package/src/sources/HyperSyncSource.res.mjs +82 -86
  116. package/src/sources/Rpc.res +15 -47
  117. package/src/sources/Rpc.res.mjs +25 -56
  118. package/src/sources/RpcSource.res +320 -467
  119. package/src/sources/RpcSource.res.mjs +268 -385
  120. package/src/sources/RpcWebSocketHeightStream.res +0 -5
  121. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
  122. package/src/sources/SimulateSource.res +10 -4
  123. package/src/sources/SimulateSource.res.mjs +16 -6
  124. package/src/sources/Source.res +34 -6
  125. package/src/sources/SourceManager.res +86 -27
  126. package/src/sources/SourceManager.res.mjs +84 -47
  127. package/src/sources/SourceManager.resi +12 -3
  128. package/src/sources/Svm.res +31 -16
  129. package/src/sources/Svm.res.mjs +36 -13
  130. package/src/sources/SvmHyperSyncClient.res +5 -32
  131. package/src/sources/SvmHyperSyncSource.res +159 -134
  132. package/src/sources/SvmHyperSyncSource.res.mjs +164 -129
  133. package/src/sources/TransactionStore.res +49 -0
  134. package/src/sources/TransactionStore.res.mjs +30 -0
  135. package/src/tui/Tui.res +13 -16
  136. package/src/tui/Tui.res.mjs +12 -14
  137. package/svm.schema.json +49 -37
  138. package/src/bindings/Hrtime.res +0 -58
  139. package/src/bindings/Hrtime.res.mjs +0 -90
  140. package/src/bindings/Hrtime.resi +0 -30
@@ -11,10 +11,6 @@ type t = {
11
11
  // True once every chain has caught up to head/endBlock. Monotonic during a run.
12
12
  mutable isRealtime: bool,
13
13
  mutable isInReorgThreshold: bool,
14
- // Indexer-wide caps on concurrent data-source queries, shared across all
15
- // chains. The realtime budget applies once every chain is at head.
16
- maxBackfillConcurrency: int,
17
- maxRealtimeConcurrency: int,
18
14
  // Indexer-wide fetch buffer pool (item count), shared across all chains.
19
15
  targetBufferSize: int,
20
16
  }
@@ -26,18 +22,10 @@ let calculateTargetBufferSize = () =>
26
22
  | None => 100_000
27
23
  }
28
24
 
29
- // The concurrency budget in force for the current phase.
30
- let maxConcurrency = (crossChainState: t) =>
31
- crossChainState.isRealtime
32
- ? crossChainState.maxRealtimeConcurrency
33
- : crossChainState.maxBackfillConcurrency
34
-
35
25
  let make = (
36
26
  ~chainStates,
37
27
  ~isInReorgThreshold,
38
28
  ~isRealtime,
39
- ~maxBackfillConcurrency=Env.maxBackfillConcurrency,
40
- ~maxRealtimeConcurrency=Env.maxRealtimeConcurrency,
41
29
  ~targetBufferSize=calculateTargetBufferSize(),
42
30
  ): t => {
43
31
  let crossChainState = {
@@ -45,11 +33,8 @@ let make = (
45
33
  chainIds: chainStates->Dict.valuesToArray->Array.map(cs => (cs->ChainState.chainConfig).id),
46
34
  isRealtime,
47
35
  isInReorgThreshold,
48
- maxBackfillConcurrency,
49
- maxRealtimeConcurrency,
50
36
  targetBufferSize,
51
37
  }
52
- Prometheus.IndexingMaxConcurrency.set(~maxConcurrency=crossChainState->maxConcurrency)
53
38
  Prometheus.IndexingTargetBufferSize.set(~targetBufferSize)
54
39
  crossChainState
55
40
  }
@@ -65,17 +50,6 @@ let chainStates = (crossChainState: t) => crossChainState.chainStates
65
50
  let isRealtime = (crossChainState: t) => crossChainState.isRealtime
66
51
  let isInReorgThreshold = (crossChainState: t) => crossChainState.isInReorgThreshold
67
52
 
68
- // Partition queries in flight across every chain — the live draw against
69
- // maxConcurrency.
70
- let inFlight = (crossChainState: t) => {
71
- let total = ref(0)
72
- for i in 0 to crossChainState.chainIds->Array.length - 1 {
73
- let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
74
- total := total.contents + cs->ChainState.sourceManager->SourceManager.inFlightCount
75
- }
76
- total.contents
77
- }
78
-
79
53
  // Ready-to-process items across every chain — the live draw against
80
54
  // targetBufferSize, which is a budget of processable events (items stuck behind
81
55
  // a gap don't count toward the goal of keeping ~targetBufferSize ready).
@@ -83,7 +57,7 @@ let totalReadyCount = (crossChainState: t) => {
83
57
  let total = ref(0)
84
58
  for i in 0 to crossChainState.chainIds->Array.length - 1 {
85
59
  let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
86
- total := total.contents + cs->ChainState.fetchState->FetchState.bufferReadyCount
60
+ total := total.contents + cs->ChainState.bufferReadyCount
87
61
  }
88
62
  total.contents
89
63
  }
@@ -91,9 +65,7 @@ let totalReadyCount = (crossChainState: t) => {
91
65
  // --- Derived (pure). ---
92
66
 
93
67
  let nextItemIsNone = (crossChainState: t): bool =>
94
- !Batch.hasReadyItem(
95
- crossChainState.chainStates->Dict.valuesToArray->Array.map(ChainState.fetchState),
96
- )
68
+ !(crossChainState.chainStates->Dict.valuesToArray->Array.some(ChainState.hasReadyItem))
97
69
 
98
70
  let getSafeCheckpointId = (crossChainState: t) => {
99
71
  let result: ref<option<bigint>> = ref(None)
@@ -105,7 +77,7 @@ let getSafeCheckpointId = (crossChainState: t) => {
105
77
  | Some(safeCheckpointTracking) =>
106
78
  let safeCheckpointId =
107
79
  safeCheckpointTracking->SafeCheckpointTracking.getSafeCheckpointId(
108
- ~sourceBlockNumber=(cs->ChainState.fetchState).knownHeight,
80
+ ~sourceBlockNumber=cs->ChainState.knownHeight,
109
81
  )
110
82
  switch result.contents {
111
83
  | None => result := Some(safeCheckpointId)
@@ -137,16 +109,9 @@ let createBatch = (
137
109
  // in an append-only ClickHouse insert.
138
110
  isRollback ? 1n : 0n,
139
111
  ),
140
- ~chainsBeforeBatch=crossChainState.chainStates->Utils.Dict.mapValues((
141
- cs
142
- ): Batch.chainBeforeBatch => {
143
- fetchState: cs->ChainState.fetchState,
144
- progressBlockNumber: cs->ChainState.committedProgressBlockNumber,
145
- totalEventsProcessed: cs->ChainState.numEventsProcessed,
146
- sourceBlockNumber: (cs->ChainState.fetchState).knownHeight,
147
- reorgDetection: cs->ChainState.reorgDetection,
148
- chainConfig: cs->ChainState.chainConfig,
149
- }),
112
+ ~chainsBeforeBatch=crossChainState.chainStates->Utils.Dict.mapValues(
113
+ ChainState.toChainBeforeBatch,
114
+ ),
150
115
  ~batchSizeTarget,
151
116
  )
152
117
  }
@@ -170,13 +135,13 @@ let enterReorgThreshold = (crossChainState: t) => {
170
135
  // whole indexer. A chain is marked caught up only once EVERY chain is caught up
171
136
  // (reached endblock or fetched/processed to head) with no processable events
172
137
  // left — so no chain flips to ready while another is still backfilling.
173
- let applyBatchProgress = (crossChainState: t, ~batch: Batch.t) => {
138
+ let applyBatchProgress = (crossChainState: t, ~batch: Batch.t, ~blockTimestampName: string) => {
174
139
  let chainIds = crossChainState.chainIds
175
140
 
176
141
  let everyChainCaughtUp = ref(true)
177
142
  for i in 0 to chainIds->Array.length - 1 {
178
143
  let cs = crossChainState->getChainState(chainIds->Array.getUnsafe(i))
179
- cs->ChainState.applyBatchProgress(~batch)
144
+ cs->ChainState.applyBatchProgress(~batch, ~blockTimestampName)
180
145
  if !(cs->ChainState.hasProcessedToEndblock || cs->ChainState.isProgressAtHead) {
181
146
  everyChainCaughtUp := false
182
147
  }
@@ -199,71 +164,155 @@ let applyBatchProgress = (crossChainState: t, ~batch: Batch.t) => {
199
164
  Prometheus.ProgressReady.setAllReady()
200
165
  }
201
166
 
202
- let wasRealtime = crossChainState.isRealtime
203
167
  crossChainState.isRealtime = crossChainState.isRealtime || allChainsReady.contents
204
- if !wasRealtime && crossChainState.isRealtime {
205
- // The realtime budget takes over now that every chain is at head.
206
- Prometheus.IndexingMaxConcurrency.set(~maxConcurrency=crossChainState->maxConcurrency)
207
- }
208
168
  }
209
169
 
210
170
  // --- Fetch control. ---
211
171
 
212
- // Chains ordered furthest-behind first, so the shared concurrency and buffer
213
- // pools go to the chains with the most backfill work before the rest.
172
+ // Chains ordered furthest-behind first, so the shared buffer pool goes to the
173
+ // chains with the most backfill work before the rest.
214
174
  let priorityOrder = (crossChainState: t) =>
215
175
  crossChainState.chainStates
216
176
  ->Dict.valuesToArray
217
177
  ->Array.toSorted((a, b) =>
218
- Float.compare(
219
- a->ChainState.fetchState->FetchState.getProgressPercentage,
220
- b->ChainState.fetchState->FetchState.getProgressPercentage,
221
- )
178
+ Float.compare(a->ChainState.getProgressPercentage, b->ChainState.getProgressPercentage)
222
179
  )
223
180
 
224
- // Dispatch a fetch tick across every chain in priority order, drawing from the
225
- // shared concurrency and buffer pools. Chains are visited in turn; fetchChain
226
- // bumps the in-flight count synchronously before it suspends, so a later chain
227
- // sees the slots an earlier one already claimed and the budget is honored
228
- // indexer-wide.
229
- //
230
- // bufferLimit is each chain's slice of the shared pool: it may grow its buffer
231
- // into whatever the other chains leave free, so a lone backfilling chain can use
232
- // the whole pool while head-following chains stay shallow.
181
+ // In-flight estimated items across every chain the live draw against
182
+ // targetBufferSize alongside totalReadyCount, so the pool isn't re-dispatched
183
+ // while queries are still being fetched.
184
+ let totalReservedSize = (crossChainState: t) => {
185
+ let total = ref(0.)
186
+ for i in 0 to crossChainState.chainIds->Array.length - 1 {
187
+ let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
188
+ total := total.contents +. cs->ChainState.pendingBudget
189
+ }
190
+ total.contents
191
+ }
192
+
193
+ // Dispatch a fetch tick across the whole indexer from one shared pool of
194
+ // ~targetBufferSize ready events, as a waterfall: visit chains furthest-behind
195
+ // first, hand each the budget remaining at that point (plus its own
196
+ // already-reserved share, since a chain's pending queries aren't "spent" —
197
+ // they're this chain's), let it turn that into queries sized against its own
198
+ // chain-density-derived target block, then subtract what it actually used
199
+ // before moving to the next chain. So a chain that can only use a little
200
+ // (density too low, or already caught up) leaves the rest for the others
201
+ // automatically. A chain visited after the budget ran out simply doesn't
202
+ // query this round — the leader's reservations release as its responses land,
203
+ // so the next tick redistributes. Chains that do get budget are additionally
204
+ // capped at the most-behind chain's target progress mapped onto their own
205
+ // range, so no chain runs further ahead than the chain the pool is
206
+ // prioritizing.
233
207
  let checkAndFetch = async (
234
208
  crossChainState: t,
235
- ~fetchChain: (
236
- ~chain: ChainMap.Chain.t,
237
- ~concurrencyLimit: int,
238
- ~bufferLimit: int,
239
- ) => promise<unit>,
209
+ ~dispatchChain: (~chain: ChainMap.Chain.t, ~action: FetchState.nextQuery) => promise<unit>,
240
210
  ) => {
241
- let maxConcurrency = crossChainState->maxConcurrency
242
- // Pool is a budget of ready-to-process items, but the cap fed to getNextQuery
243
- // is still a buffer position (it tracks the sorted buffer, ready items first),
244
- // so add this chain's own buffer size back rather than its ready count. A chain
245
- // with a gap therefore gets extra headroom to fetch the not-ready overhang
246
- // while still aiming for its share of ~targetBufferSize ready events.
247
- let totalReady = crossChainState->totalReadyCount
248
- // Track the in-flight total as a running counter (summed once up front, then
249
- // adjusted by each chain's delta) instead of re-summing every chain — O(chains)
250
- // per tick rather than O(chains^2). fetchChain bumps the chain's count
251
- // synchronously, so the delta is observable right after the call.
252
- let inFlight = ref(crossChainState->inFlight)
253
- let priorityOrdered = crossChainState->priorityOrder
211
+ let remaining = ref(
212
+ Pervasives.max(
213
+ 0.,
214
+ crossChainState.targetBufferSize->Int.toFloat -.
215
+ crossChainState->totalReadyCount->Int.toFloat -.
216
+ crossChainState->totalReservedSize,
217
+ ),
218
+ )
219
+
220
+ // A chain with no density signal probes blind, so it only gets a bounded
221
+ // slice of the pool one unknown chain shouldn't hold the whole budget
222
+ // while it takes its first measurements.
223
+ let coldChainBudget = Pervasives.min(5_000., crossChainState.targetBufferSize->Int.toFloat)
224
+
225
+ // Chunk reservations get headroom over the density estimate so a
226
+ // denser-than-expected range doesn't truncate at the server cap; realtime
227
+ // gets more since a forced catch-up query there costs a head-poll roundtrip.
228
+ let chunkItemsMultiplier = crossChainState.isRealtime ? 3. : 1.5
229
+
230
+ let actionByChain = Dict.make()
231
+ let maxProgress = ref(None)
232
+ crossChainState
233
+ ->priorityOrder
234
+ ->Array.forEach(cs => {
235
+ let chainId = (cs->ChainState.chainConfig).id
236
+ if cs->ChainState.knownHeight == 0 {
237
+ // No height yet — nothing to size a budget or an alignment line
238
+ // against. Skip without consuming budget or claiming leadership, so a
239
+ // chain whose source hasn't reported doesn't unconstrain everyone else.
240
+ actionByChain->Utils.Dict.setByInt(chainId, FetchState.WaitingForNewBlock)
241
+ } else {
242
+ let isCold = cs->ChainState.effectiveDensity === None
243
+ let chainTargetItems =
244
+ (isCold ? Pervasives.min(remaining.contents, coldChainBudget) : remaining.contents) +.
245
+ cs->ChainState.pendingBudget
246
+ let maxTargetBlock = switch maxProgress.contents {
247
+ | None if isCold =>
248
+ // A cold chain's target is a guess — it doesn't set the alignment
249
+ // line; the first density-bearing chain after it does.
250
+ None
251
+ | None =>
252
+ // The most-behind chain sets the alignment line for everyone after it.
253
+ maxProgress :=
254
+ Some(
255
+ cs->ChainState.progressAtBlock(
256
+ ~blockNumber=cs->ChainState.targetBlock(~chainTargetItems),
257
+ ),
258
+ )
259
+ None
260
+ // 5% margin past the leader's line: chains whose progress tracks the
261
+ // leader closely would otherwise flap in and out of the clamp as
262
+ // leadership alternates, stalling their pipeline every other tick.
263
+ | Some(progress) => Some(cs->ChainState.blockAtProgress(~progress=progress +. 0.05))
264
+ }
265
+ switch cs->ChainState.getNextQuery(
266
+ ~chainTargetItems,
267
+ ~chunkItemsMultiplier,
268
+ ~maxTargetBlock?,
269
+ ) {
270
+ | (WaitingForNewBlock | NothingToQuery) as action =>
271
+ actionByChain->Utils.Dict.setByInt(chainId, action)
272
+ | Ready(queries) => {
273
+ let consumed =
274
+ queries->Array.reduce(0., (acc, query: FetchState.query) =>
275
+ acc +. query.itemsEst->Int.toFloat
276
+ )
277
+
278
+ let partitions = Dict.make()
279
+ queries->Array.forEach((query: FetchState.query) =>
280
+ partitions->Dict.set(
281
+ query.partitionId,
282
+ {
283
+ "fromBlock": query.fromBlock,
284
+ "targetBlock": query.toBlock,
285
+ "targetEvents": query.itemsTarget,
286
+ },
287
+ )
288
+ )
289
+ Logging.trace({
290
+ "msg": "Started querying",
291
+ "chainId": chainId,
292
+ "partitions": partitions,
293
+ })
294
+
295
+ actionByChain->Utils.Dict.setByInt(chainId, FetchState.Ready(queries))
296
+ // Mark the queries in flight and reserve their size against the
297
+ // shared budget; released as each response lands in
298
+ // handleQueryResult.
299
+ cs->ChainState.startFetchingQueries(~queries)
300
+ remaining := Pervasives.max(0., remaining.contents -. consumed)
301
+ }
302
+ }
303
+ }
304
+ })
305
+
254
306
  let promises = []
255
- for i in 0 to priorityOrdered->Array.length - 1 {
256
- let cs = priorityOrdered->Array.getUnsafe(i)
257
- let chain = ChainMap.Chain.makeUnsafe(~chainId=(cs->ChainState.chainConfig).id)
258
- let sourceManager = cs->ChainState.sourceManager
259
- let concurrencyLimit = Pervasives.max(0, maxConcurrency - inFlight.contents)
260
- let bufferLimit =
261
- crossChainState.targetBufferSize -
262
- (totalReady -
263
- cs->ChainState.fetchState->FetchState.bufferSize)
264
- let inFlightBefore = sourceManager->SourceManager.inFlightCount
265
- promises->Array.push(fetchChain(~chain, ~concurrencyLimit, ~bufferLimit))
266
- inFlight := inFlight.contents + (sourceManager->SourceManager.inFlightCount - inFlightBefore)
307
+ for i in 0 to crossChainState.chainIds->Array.length - 1 {
308
+ let chainId = crossChainState.chainIds->Array.getUnsafe(i)
309
+ switch actionByChain->Utils.Dict.dangerouslyGetByIntNonOption(chainId) {
310
+ | Some(NothingToQuery)
311
+ | None => ()
312
+ | Some(action) =>
313
+ let chain = ChainMap.Chain.makeUnsafe(~chainId)
314
+ promises->Array.push(dispatchChain(~chain, ~action))
315
+ }
267
316
  }
268
317
  let _ = await promises->Promise.all
269
318
  }
@@ -6,10 +6,8 @@ import * as Utils from "./Utils.res.mjs";
6
6
  import * as Logging from "./Logging.res.mjs";
7
7
  import * as ChainMap from "./ChainMap.res.mjs";
8
8
  import * as ChainState from "./ChainState.res.mjs";
9
- import * as FetchState from "./FetchState.res.mjs";
10
9
  import * as Prometheus from "./Prometheus.res.mjs";
11
- import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
12
- import * as SourceManager from "./sources/SourceManager.res.mjs";
10
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
13
11
  import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
14
12
  import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
15
13
 
@@ -21,17 +19,7 @@ function calculateTargetBufferSize() {
21
19
  }
22
20
  }
23
21
 
24
- function maxConcurrency(crossChainState) {
25
- if (crossChainState.isRealtime) {
26
- return crossChainState.maxRealtimeConcurrency;
27
- } else {
28
- return crossChainState.maxBackfillConcurrency;
29
- }
30
- }
31
-
32
- function make(chainStates, isInReorgThreshold, isRealtime, maxBackfillConcurrencyOpt, maxRealtimeConcurrencyOpt, targetBufferSizeOpt) {
33
- let maxBackfillConcurrency = maxBackfillConcurrencyOpt !== undefined ? maxBackfillConcurrencyOpt : Env.maxBackfillConcurrency;
34
- let maxRealtimeConcurrency = maxRealtimeConcurrencyOpt !== undefined ? maxRealtimeConcurrencyOpt : Env.maxRealtimeConcurrency;
22
+ function make(chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt) {
35
23
  let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : (
36
24
  Env.targetBufferSize !== undefined ? Env.targetBufferSize : 100000
37
25
  );
@@ -40,11 +28,8 @@ function make(chainStates, isInReorgThreshold, isRealtime, maxBackfillConcurrenc
40
28
  chainIds: Object.values(chainStates).map(cs => ChainState.chainConfig(cs).id),
41
29
  isRealtime: isRealtime,
42
30
  isInReorgThreshold: isInReorgThreshold,
43
- maxBackfillConcurrency: maxBackfillConcurrency,
44
- maxRealtimeConcurrency: maxRealtimeConcurrency,
45
31
  targetBufferSize: targetBufferSize
46
32
  };
47
- Prometheus.IndexingMaxConcurrency.set(maxConcurrency(crossChainState));
48
33
  Prometheus.IndexingTargetBufferSize.set(targetBufferSize);
49
34
  return crossChainState;
50
35
  }
@@ -61,26 +46,17 @@ function isInReorgThreshold(crossChainState) {
61
46
  return crossChainState.isInReorgThreshold;
62
47
  }
63
48
 
64
- function inFlight(crossChainState) {
65
- let total = 0;
66
- for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
67
- let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
68
- total = total + SourceManager.inFlightCount(ChainState.sourceManager(cs)) | 0;
69
- }
70
- return total;
71
- }
72
-
73
49
  function totalReadyCount(crossChainState) {
74
50
  let total = 0;
75
51
  for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
76
52
  let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
77
- total = total + FetchState.bufferReadyCount(ChainState.fetchState(cs)) | 0;
53
+ total = total + ChainState.bufferReadyCount(cs) | 0;
78
54
  }
79
55
  return total;
80
56
  }
81
57
 
82
58
  function nextItemIsNone(crossChainState) {
83
- return !Batch.hasReadyItem(Object.values(crossChainState.chainStates).map(ChainState.fetchState));
59
+ return !Object.values(crossChainState.chainStates).some(ChainState.hasReadyItem);
84
60
  }
85
61
 
86
62
  function getSafeCheckpointId(crossChainState) {
@@ -89,7 +65,7 @@ function getSafeCheckpointId(crossChainState) {
89
65
  let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
90
66
  let safeCheckpointTracking = ChainState.safeCheckpointTracking(cs);
91
67
  if (safeCheckpointTracking !== undefined) {
92
- let safeCheckpointId = SafeCheckpointTracking.getSafeCheckpointId(safeCheckpointTracking, ChainState.fetchState(cs).knownHeight);
68
+ let safeCheckpointId = SafeCheckpointTracking.getSafeCheckpointId(safeCheckpointTracking, ChainState.knownHeight(cs));
93
69
  let current = result;
94
70
  if (!(current !== undefined && safeCheckpointId >= current)) {
95
71
  result = safeCheckpointId;
@@ -105,14 +81,7 @@ function getSafeCheckpointId(crossChainState) {
105
81
  function createBatch(crossChainState, processedCheckpointId, batchSizeTarget, isRollback) {
106
82
  return Batch.make(processedCheckpointId + (
107
83
  isRollback ? 1n : 0n
108
- ), Utils.Dict.mapValues(crossChainState.chainStates, cs => ({
109
- fetchState: ChainState.fetchState(cs),
110
- reorgDetection: ChainState.reorgDetection(cs),
111
- progressBlockNumber: ChainState.committedProgressBlockNumber(cs),
112
- sourceBlockNumber: ChainState.fetchState(cs).knownHeight,
113
- totalEventsProcessed: ChainState.numEventsProcessed(cs),
114
- chainConfig: ChainState.chainConfig(cs)
115
- })), batchSizeTarget, crossChainState.isInReorgThreshold);
84
+ ), Utils.Dict.mapValues(crossChainState.chainStates, ChainState.toChainBeforeBatch), batchSizeTarget, crossChainState.isInReorgThreshold);
116
85
  }
117
86
 
118
87
  function enterReorgThreshold(crossChainState) {
@@ -124,12 +93,12 @@ function enterReorgThreshold(crossChainState) {
124
93
  crossChainState.isInReorgThreshold = true;
125
94
  }
126
95
 
127
- function applyBatchProgress(crossChainState, batch) {
96
+ function applyBatchProgress(crossChainState, batch, blockTimestampName) {
128
97
  let chainIds = crossChainState.chainIds;
129
98
  let everyChainCaughtUp = true;
130
99
  for (let i = 0, i_finish = chainIds.length; i < i_finish; ++i) {
131
100
  let cs = crossChainState.chainStates[chainIds[i]];
132
- ChainState.applyBatchProgress(cs, batch);
101
+ ChainState.applyBatchProgress(cs, batch, blockTimestampName);
133
102
  if (!(ChainState.hasProcessedToEndblock(cs) || ChainState.isProgressAtHead(cs))) {
134
103
  everyChainCaughtUp = false;
135
104
  }
@@ -148,32 +117,93 @@ function applyBatchProgress(crossChainState, batch) {
148
117
  if (allChainsReady) {
149
118
  Prometheus.ProgressReady.setAllReady();
150
119
  }
151
- let wasRealtime = crossChainState.isRealtime;
152
120
  crossChainState.isRealtime = crossChainState.isRealtime || allChainsReady;
153
- if (!wasRealtime && crossChainState.isRealtime) {
154
- return Prometheus.IndexingMaxConcurrency.set(maxConcurrency(crossChainState));
155
- }
156
121
  }
157
122
 
158
123
  function priorityOrder(crossChainState) {
159
- return Object.values(crossChainState.chainStates).toSorted((a, b) => Primitive_float.compare(FetchState.getProgressPercentage(ChainState.fetchState(a)), FetchState.getProgressPercentage(ChainState.fetchState(b))));
124
+ return Object.values(crossChainState.chainStates).toSorted((a, b) => Primitive_float.compare(ChainState.getProgressPercentage(a), ChainState.getProgressPercentage(b)));
125
+ }
126
+
127
+ function totalReservedSize(crossChainState) {
128
+ let total = 0;
129
+ for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
130
+ let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
131
+ total = total + ChainState.pendingBudget(cs);
132
+ }
133
+ return total;
160
134
  }
161
135
 
162
- async function checkAndFetch(crossChainState, fetchChain) {
163
- let maxConcurrency$1 = maxConcurrency(crossChainState);
164
- let totalReady = totalReadyCount(crossChainState);
165
- let inFlight$1 = inFlight(crossChainState);
166
- let priorityOrdered = priorityOrder(crossChainState);
136
+ async function checkAndFetch(crossChainState, dispatchChain) {
137
+ let remaining = {
138
+ contents: Primitive_float.max(0, crossChainState.targetBufferSize - totalReadyCount(crossChainState) - totalReservedSize(crossChainState))
139
+ };
140
+ let coldChainBudget = Primitive_float.min(5000, crossChainState.targetBufferSize);
141
+ let chunkItemsMultiplier = crossChainState.isRealtime ? 3 : 1.5;
142
+ let actionByChain = {};
143
+ let maxProgress = {
144
+ contents: undefined
145
+ };
146
+ priorityOrder(crossChainState).forEach(cs => {
147
+ let chainId = ChainState.chainConfig(cs).id;
148
+ if (ChainState.knownHeight(cs) === 0) {
149
+ actionByChain[chainId] = "WaitingForNewBlock";
150
+ return;
151
+ }
152
+ let isCold = ChainState.effectiveDensity(cs) === undefined;
153
+ let chainTargetItems = (
154
+ isCold ? Primitive_float.min(remaining.contents, coldChainBudget) : remaining.contents
155
+ ) + ChainState.pendingBudget(cs);
156
+ let progress = maxProgress.contents;
157
+ let maxTargetBlock = progress !== undefined ? ChainState.blockAtProgress(cs, progress + 0.05) : (
158
+ isCold ? undefined : (maxProgress.contents = ChainState.progressAtBlock(cs, ChainState.targetBlock(cs, chainTargetItems)), undefined)
159
+ );
160
+ let action = ChainState.getNextQuery(cs, chainTargetItems, chunkItemsMultiplier, maxTargetBlock);
161
+ if (typeof action !== "object") {
162
+ if (action === "WaitingForNewBlock") {
163
+ actionByChain[chainId] = action;
164
+ return;
165
+ }
166
+ actionByChain[chainId] = action;
167
+ return;
168
+ } else {
169
+ let queries = action._0;
170
+ let consumed = Stdlib_Array.reduce(queries, 0, (acc, query) => acc + query.itemsEst);
171
+ let partitions = {};
172
+ queries.forEach(query => {
173
+ partitions[query.partitionId] = {
174
+ fromBlock: query.fromBlock,
175
+ targetBlock: query.toBlock,
176
+ targetEvents: query.itemsTarget
177
+ };
178
+ });
179
+ Logging.trace({
180
+ msg: "Started querying",
181
+ chainId: chainId,
182
+ partitions: partitions
183
+ });
184
+ actionByChain[chainId] = {
185
+ TAG: "Ready",
186
+ _0: queries
187
+ };
188
+ ChainState.startFetchingQueries(cs, queries);
189
+ remaining.contents = Primitive_float.max(0, remaining.contents - consumed);
190
+ return;
191
+ }
192
+ });
167
193
  let promises = [];
168
- for (let i = 0, i_finish = priorityOrdered.length; i < i_finish; ++i) {
169
- let cs = priorityOrdered[i];
170
- let chain = ChainMap.Chain.makeUnsafe(ChainState.chainConfig(cs).id);
171
- let sourceManager = ChainState.sourceManager(cs);
172
- let concurrencyLimit = Primitive_int.max(0, maxConcurrency$1 - inFlight$1 | 0);
173
- let bufferLimit = crossChainState.targetBufferSize - (totalReady - FetchState.bufferSize(ChainState.fetchState(cs)) | 0) | 0;
174
- let inFlightBefore = SourceManager.inFlightCount(sourceManager);
175
- promises.push(fetchChain(chain, concurrencyLimit, bufferLimit));
176
- inFlight$1 = inFlight$1 + (SourceManager.inFlightCount(sourceManager) - inFlightBefore | 0) | 0;
194
+ for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
195
+ let chainId = crossChainState.chainIds[i];
196
+ let action = actionByChain[chainId];
197
+ if (action !== undefined) {
198
+ let exit = 0;
199
+ if (typeof action === "object" || action === "WaitingForNewBlock") {
200
+ exit = 1;
201
+ }
202
+ if (exit === 1) {
203
+ let chain = ChainMap.Chain.makeUnsafe(chainId);
204
+ promises.push(dispatchChain(chain, action));
205
+ }
206
+ }
177
207
  }
178
208
  await Promise.all(promises);
179
209
  }
@@ -184,8 +214,6 @@ export {
184
214
  chainStates,
185
215
  isRealtime,
186
216
  isInReorgThreshold,
187
- maxConcurrency,
188
- inFlight,
189
217
  nextItemIsNone,
190
218
  getSafeCheckpointId,
191
219
  createBatch,
@@ -9,8 +9,6 @@ let make: (
9
9
  ~chainStates: dict<ChainState.t>,
10
10
  ~isInReorgThreshold: bool,
11
11
  ~isRealtime: bool,
12
- ~maxBackfillConcurrency: int=?,
13
- ~maxRealtimeConcurrency: int=?,
14
12
  ~targetBufferSize: int=?,
15
13
  ) => t
16
14
 
@@ -18,8 +16,6 @@ let make: (
18
16
  let chainStates: t => dict<ChainState.t>
19
17
  let isRealtime: t => bool
20
18
  let isInReorgThreshold: t => bool
21
- let maxConcurrency: t => int
22
- let inFlight: t => int
23
19
 
24
20
  // Derived (pure).
25
21
  let nextItemIsNone: t => bool
@@ -33,15 +29,11 @@ let createBatch: (
33
29
  ~isRollback: bool,
34
30
  ) => Batch.t
35
31
  let enterReorgThreshold: t => unit
36
- let applyBatchProgress: (t, ~batch: Batch.t) => unit
32
+ let applyBatchProgress: (t, ~batch: Batch.t, ~blockTimestampName: string) => unit
37
33
 
38
34
  // Fetch control.
39
35
  let priorityOrder: t => array<ChainState.t>
40
36
  let checkAndFetch: (
41
37
  t,
42
- ~fetchChain: (
43
- ~chain: ChainMap.Chain.t,
44
- ~concurrencyLimit: int,
45
- ~bufferLimit: int,
46
- ) => promise<unit>,
38
+ ~dispatchChain: (~chain: ChainMap.Chain.t, ~action: FetchState.nextQuery) => promise<unit>,
47
39
  ) => promise<unit>
package/src/Ecosystem.res CHANGED
@@ -2,8 +2,6 @@ type name = | @as("evm") Evm | @as("fuel") Fuel | @as("svm") Svm
2
2
 
3
3
  type t = {
4
4
  name: name,
5
- blockFields: array<string>,
6
- transactionFields: array<string>,
7
5
  blockNumberName: string,
8
6
  blockTimestampName: string,
9
7
  blockHashName: string,
@@ -33,7 +31,8 @@ type t = {
33
31
  loggers (see `getItemLogger`). */
34
32
  logger: Pino.t,
35
33
  /** Materialise the user-facing event handed to handlers and contract
36
- registration from an item's opaque payload. */
34
+ registration from an item's opaque payload. `event.transaction` is written
35
+ onto the payload at batch prep (HyperSync) or inline (RPC/simulate). */
37
36
  toEvent: Internal.eventItem => Internal.event,
38
37
  /** Build the per-item child logger for an event item, with
39
38
  ecosystem-specific log fields (EVM/Fuel: contract/event/address; SVM:
@@ -70,12 +69,12 @@ let getItemLogger = {
70
69
  | None =>
71
70
  let logger = switch item {
72
71
  | Internal.Event(_) => ecosystem.toEventLogger(item->Internal.castUnsafeEventItem)
73
- | Block({blockNumber, onBlockConfig}) =>
72
+ | Block({blockNumber, onBlockRegistration}) =>
74
73
  Logging.createChildFrom(
75
74
  ~logger=ecosystem.logger,
76
75
  ~params={
77
- "onBlock": onBlockConfig.name,
78
- "chainId": onBlockConfig.chainId,
76
+ "onBlock": onBlockRegistration.name,
77
+ "chainId": onBlockRegistration.chainId,
79
78
  "block": blockNumber,
80
79
  },
81
80
  )
@@ -26,10 +26,10 @@ function getItemLogger(item, ecosystem) {
26
26
  if (item.kind === 0) {
27
27
  logger$1 = ecosystem.toEventLogger(item);
28
28
  } else {
29
- let onBlockConfig = item.onBlockConfig;
29
+ let onBlockRegistration = item.onBlockRegistration;
30
30
  logger$1 = Logging.createChildFrom(ecosystem.logger, {
31
- onBlock: onBlockConfig.name,
32
- chainId: onBlockConfig.chainId,
31
+ onBlock: onBlockRegistration.name,
32
+ chainId: onBlockRegistration.chainId,
33
33
  block: item.blockNumber
34
34
  });
35
35
  }
package/src/Env.res CHANGED
@@ -8,13 +8,6 @@ let updateSyncTimeOnRestart =
8
8
  envSafe->EnvSafe.get("UPDATE_SYNC_TIME_ON_RESTART", S.bool, ~fallback=true)
9
9
  let targetBufferSize = envSafe->EnvSafe.get("ENVIO_INDEXING_MAX_BUFFER_SIZE", S.option(S.int))
10
10
  let maxAddrInPartition = envSafe->EnvSafe.get("MAX_PARTITION_SIZE", S.int, ~fallback=5_000)
11
- // Global caps on concurrent data-source queries across the whole indexer (all
12
- // chains share the budget), not per chain. Realtime allows far more since head
13
- // queries are small and many chains follow the head at once.
14
- let maxBackfillConcurrency =
15
- envSafe->EnvSafe.get("ENVIO_MAX_BACKFILL_CONCURRENCY", S.int, ~fallback=30)
16
- let maxRealtimeConcurrency =
17
- envSafe->EnvSafe.get("ENVIO_MAX_REALTIME_CONCURRENCY", S.int, ~fallback=200)
18
11
 
19
12
  // Target number of in-memory objects (uncommitted entity/effect changes plus
20
13
  // unwritten batch items) the store holds before processing waits for the write