envio 3.1.2 → 3.2.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 (134) hide show
  1. package/evm.schema.json +83 -11
  2. package/fuel.schema.json +83 -11
  3. package/index.d.ts +184 -3
  4. package/package.json +6 -6
  5. package/src/Batch.res +2 -2
  6. package/src/ChainFetcher.res +27 -3
  7. package/src/ChainFetcher.res.mjs +17 -3
  8. package/src/ChainManager.res +163 -0
  9. package/src/ChainManager.res.mjs +136 -0
  10. package/src/Config.res +213 -30
  11. package/src/Config.res.mjs +102 -41
  12. package/src/Core.res +16 -10
  13. package/src/Ecosystem.res +0 -3
  14. package/src/Env.res +2 -2
  15. package/src/Env.res.mjs +2 -2
  16. package/src/Envio.res +101 -2
  17. package/src/Envio.res.mjs +2 -3
  18. package/src/EventConfigBuilder.res +52 -0
  19. package/src/EventConfigBuilder.res.mjs +32 -0
  20. package/src/EventUtils.res +2 -2
  21. package/src/FetchState.res +23 -14
  22. package/src/FetchState.res.mjs +21 -15
  23. package/src/GlobalState.res +219 -363
  24. package/src/GlobalState.res.mjs +314 -491
  25. package/src/GlobalStateManager.res +49 -59
  26. package/src/GlobalStateManager.res.mjs +5 -4
  27. package/src/GlobalStateManager.resi +1 -1
  28. package/src/HandlerLoader.res +12 -1
  29. package/src/HandlerLoader.res.mjs +6 -1
  30. package/src/HandlerRegister.res +9 -9
  31. package/src/HandlerRegister.res.mjs +9 -9
  32. package/src/Hasura.res +102 -32
  33. package/src/Hasura.res.mjs +88 -34
  34. package/src/InMemoryStore.res +10 -1
  35. package/src/InMemoryStore.res.mjs +4 -1
  36. package/src/InMemoryTable.res +83 -136
  37. package/src/InMemoryTable.res.mjs +57 -86
  38. package/src/Internal.res +54 -5
  39. package/src/Internal.res.mjs +2 -8
  40. package/src/LazyLoader.res +2 -2
  41. package/src/LazyLoader.res.mjs +3 -3
  42. package/src/LoadLayer.res +47 -60
  43. package/src/LoadLayer.res.mjs +28 -50
  44. package/src/LoadLayer.resi +2 -5
  45. package/src/LogSelection.res +4 -4
  46. package/src/LogSelection.res.mjs +5 -7
  47. package/src/Logging.res +1 -1
  48. package/src/Main.res +61 -2
  49. package/src/Main.res.mjs +37 -1
  50. package/src/Persistence.res +3 -16
  51. package/src/PgStorage.res +125 -114
  52. package/src/PgStorage.res.mjs +112 -95
  53. package/src/Ports.res +5 -0
  54. package/src/Ports.res.mjs +9 -0
  55. package/src/Prometheus.res +3 -3
  56. package/src/Prometheus.res.mjs +4 -4
  57. package/src/ReorgDetection.res +4 -4
  58. package/src/ReorgDetection.res.mjs +4 -5
  59. package/src/SafeCheckpointTracking.res +16 -16
  60. package/src/SafeCheckpointTracking.res.mjs +2 -2
  61. package/src/SimulateItems.res +10 -14
  62. package/src/SimulateItems.res.mjs +5 -2
  63. package/src/Sink.res +1 -1
  64. package/src/Sink.res.mjs +1 -2
  65. package/src/SvmTypes.res +9 -0
  66. package/src/SvmTypes.res.mjs +14 -0
  67. package/src/TestIndexer.res +17 -57
  68. package/src/TestIndexer.res.mjs +14 -48
  69. package/src/TestIndexerProxyStorage.res +23 -23
  70. package/src/TestIndexerProxyStorage.res.mjs +12 -15
  71. package/src/Throttler.res +2 -2
  72. package/src/Time.res +2 -2
  73. package/src/Time.res.mjs +2 -2
  74. package/src/UserContext.res +19 -118
  75. package/src/UserContext.res.mjs +10 -66
  76. package/src/Utils.res +15 -15
  77. package/src/Utils.res.mjs +7 -8
  78. package/src/adapters/MarkBatchProcessedAdapter.res +5 -0
  79. package/src/adapters/MarkBatchProcessedAdapter.res.mjs +14 -0
  80. package/src/bindings/BigDecimal.res +1 -1
  81. package/src/bindings/BigDecimal.res.mjs +2 -2
  82. package/src/bindings/ClickHouse.res +8 -6
  83. package/src/bindings/ClickHouse.res.mjs +5 -5
  84. package/src/bindings/Hrtime.res +1 -1
  85. package/src/bindings/Pino.res +2 -2
  86. package/src/bindings/Pino.res.mjs +3 -4
  87. package/src/db/EntityFilter.res +410 -0
  88. package/src/db/EntityFilter.res.mjs +424 -0
  89. package/src/db/EntityHistory.res +1 -1
  90. package/src/db/EntityHistory.res.mjs +1 -1
  91. package/src/db/InternalTable.res +10 -10
  92. package/src/db/InternalTable.res.mjs +41 -45
  93. package/src/db/Schema.res +2 -2
  94. package/src/db/Schema.res.mjs +3 -3
  95. package/src/db/Table.res +106 -22
  96. package/src/db/Table.res.mjs +84 -35
  97. package/src/sources/EventRouter.res +67 -2
  98. package/src/sources/EventRouter.res.mjs +45 -3
  99. package/src/sources/Evm.res +0 -7
  100. package/src/sources/Evm.res.mjs +0 -15
  101. package/src/sources/EvmChain.res +1 -1
  102. package/src/sources/EvmChain.res.mjs +1 -2
  103. package/src/sources/EvmRpcClient.res +42 -0
  104. package/src/sources/EvmRpcClient.res.mjs +64 -0
  105. package/src/sources/Fuel.res +0 -7
  106. package/src/sources/Fuel.res.mjs +0 -15
  107. package/src/sources/HyperFuelSource.res +5 -4
  108. package/src/sources/HyperFuelSource.res.mjs +2 -2
  109. package/src/sources/HyperSyncClient.res +9 -5
  110. package/src/sources/HyperSyncClient.res.mjs +2 -2
  111. package/src/sources/HyperSyncHeightStream.res +2 -2
  112. package/src/sources/HyperSyncHeightStream.res.mjs +2 -2
  113. package/src/sources/HyperSyncSource.res +10 -9
  114. package/src/sources/HyperSyncSource.res.mjs +4 -4
  115. package/src/sources/Rpc.res +1 -5
  116. package/src/sources/Rpc.res.mjs +1 -9
  117. package/src/sources/RpcSource.res +57 -21
  118. package/src/sources/RpcSource.res.mjs +47 -20
  119. package/src/sources/RpcWebSocketHeightStream.res +1 -1
  120. package/src/sources/SourceManager.res +3 -2
  121. package/src/sources/SourceManager.res.mjs +1 -1
  122. package/src/sources/Svm.res +3 -10
  123. package/src/sources/Svm.res.mjs +4 -18
  124. package/src/sources/SvmHyperSyncClient.res +265 -0
  125. package/src/sources/SvmHyperSyncClient.res.mjs +28 -0
  126. package/src/sources/SvmHyperSyncSource.res +638 -0
  127. package/src/sources/SvmHyperSyncSource.res.mjs +557 -0
  128. package/src/tui/Tui.res +9 -2
  129. package/src/tui/Tui.res.mjs +18 -3
  130. package/src/tui/components/BufferedProgressBar.res +2 -2
  131. package/src/tui/components/TuiData.res +3 -0
  132. package/svm.schema.json +523 -14
  133. package/src/TableIndices.res +0 -115
  134. package/src/TableIndices.res.mjs +0 -144
@@ -26,8 +26,6 @@ module WriteThrottlers = {
26
26
  type t = {
27
27
  ctx: Ctx.t,
28
28
  chainManager: ChainManager.t,
29
- processedBatches: int,
30
- currentlyProcessingBatch: bool,
31
29
  rollbackState: rollbackState,
32
30
  indexerStartTime: Date.t,
33
31
  writeThrottlers: WriteThrottlers.t,
@@ -51,8 +49,6 @@ let make = (
51
49
  ) => {
52
50
  {
53
51
  ctx,
54
- currentlyProcessingBatch: false,
55
- processedBatches: 0,
56
52
  chainManager,
57
53
  indexerStartTime: Date.make(),
58
54
  rollbackState: NoRollback,
@@ -109,7 +105,6 @@ type action =
109
105
  })
110
106
  | FinishWaitingForNewBlock({chain: chain, knownHeight: int})
111
107
  | EventBatchProcessed({batch: Batch.t})
112
- | StartProcessingBatch
113
108
  | StartFindingReorgDepth
114
109
  | FindReorgDepth({chain: chain, rollbackTargetBlockNumber: int})
115
110
  | EnterReorgThreshold
@@ -140,9 +135,9 @@ let updateChainMetadataTable = (cm: ChainManager.t, ~inMemoryStore: InMemoryStor
140
135
 
141
136
  cm.chainFetchers
142
137
  ->ChainMap.values
143
- ->Belt.Array.forEach(cf => {
138
+ ->Array.forEach(cf => {
144
139
  chainsData->Dict.set(
145
- cf.chainConfig.id->Belt.Int.toString,
140
+ cf.chainConfig.id->Int.toString,
146
141
  {
147
142
  firstEventBlockNumber: cf.fetchState.firstEventBlock->Null.fromOption,
148
143
  isHyperSync: (cf.sourceManager->SourceManager.getActiveSource).poweredByHyperSync,
@@ -156,169 +151,6 @@ let updateChainMetadataTable = (cm: ChainManager.t, ~inMemoryStore: InMemoryStor
156
151
  inMemoryStore->InMemoryStore.setChainMeta(chainsData)
157
152
  }
158
153
 
159
- /**
160
- Takes in a chain manager and sets all chains timestamp caught up to head
161
- when valid state lines up and returns an updated chain manager
162
- */
163
- let updateProgressedChains = (chainManager: ChainManager.t, ~batch: Batch.t, ~ctx: Ctx.t) => {
164
- let nextQueueItemIsNone = chainManager->ChainManager.nextItemIsNone
165
-
166
- let allChainsAtHead = chainManager->ChainManager.isProgressAtHead
167
- //Update the timestampCaughtUpToHeadOrEndblock values
168
- let allChainsReady = ref(true)
169
- let chainFetchers = chainManager.chainFetchers->ChainMap.map(prev => {
170
- let cf = prev
171
- let chain = ChainMap.Chain.makeUnsafe(~chainId=cf.chainConfig.id)
172
-
173
- let maybeChainAfterBatch =
174
- batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
175
- chain->ChainMap.Chain.toChainId,
176
- )
177
-
178
- let cf = switch maybeChainAfterBatch {
179
- | Some(chainAfterBatch) => {
180
- if cf.committedProgressBlockNumber !== chainAfterBatch.progressBlockNumber {
181
- Prometheus.ProgressBlockNumber.set(
182
- ~blockNumber=chainAfterBatch.progressBlockNumber,
183
- ~chainId=chain->ChainMap.Chain.toChainId,
184
- )
185
- }
186
- if cf.numEventsProcessed !== chainAfterBatch.totalEventsProcessed {
187
- Prometheus.ProgressEventsCount.set(
188
- ~processedCount=chainAfterBatch.totalEventsProcessed,
189
- ~chainId=chain->ChainMap.Chain.toChainId,
190
- )
191
- }
192
-
193
- // Calculate and set latency metrics
194
- switch batch->Batch.findLastEventItem(~chainId=chain->ChainMap.Chain.toChainId) {
195
- | Some(eventItem) => {
196
- let blockTimestamp = eventItem.event.block->ctx.config.ecosystem.getTimestamp
197
- let currentTimeMs = Date.now()->Float.toInt
198
- let blockTimestampMs = blockTimestamp * 1000
199
- let latencyMs = currentTimeMs - blockTimestampMs
200
-
201
- Prometheus.ProgressLatency.set(~latencyMs, ~chainId=chain->ChainMap.Chain.toChainId)
202
- }
203
- | None => ()
204
- }
205
-
206
- {
207
- ...cf,
208
- // Since we process per chain always in order,
209
- // we need to calculate it once, by using the first item in a batch
210
- fetchState: switch cf.fetchState.firstEventBlock {
211
- | Some(_) => cf.fetchState
212
- | None =>
213
- switch batch->Batch.findFirstEventBlockNumber(
214
- ~chainId=chain->ChainMap.Chain.toChainId,
215
- ) {
216
- | Some(_) as firstEventBlock => {...cf.fetchState, firstEventBlock}
217
- | None => cf.fetchState
218
- }
219
- },
220
- committedProgressBlockNumber: chainAfterBatch.progressBlockNumber,
221
- numEventsProcessed: chainAfterBatch.totalEventsProcessed,
222
- isProgressAtHead: cf.isProgressAtHead || chainAfterBatch.isProgressAtHeadWhenBatchCreated,
223
- safeCheckpointTracking: switch cf.safeCheckpointTracking {
224
- | Some(safeCheckpointTracking) =>
225
- Some(
226
- safeCheckpointTracking->SafeCheckpointTracking.updateOnNewBatch(
227
- ~sourceBlockNumber=cf.fetchState.knownHeight,
228
- ~chainId=chain->ChainMap.Chain.toChainId,
229
- ~batchCheckpointIds=batch.checkpointIds,
230
- ~batchCheckpointBlockNumbers=batch.checkpointBlockNumbers,
231
- ~batchCheckpointChainIds=batch.checkpointChainIds,
232
- ),
233
- )
234
- | None => None
235
- },
236
- }
237
- }
238
- | None => cf
239
- }
240
-
241
- /* strategy for TUI synced status:
242
- * Firstly -> only update synced status after batch is processed (not on batch creation). But also set when a batch tries to be created and there is no batch
243
- *
244
- * Secondly -> reset timestampCaughtUpToHead and isFetching at head when dynamic contracts get registered to a chain if they are not within 0.001 percent of the current block height
245
- *
246
- * New conditions for valid synced:
247
- *
248
- * CASE 1 (chains are being synchronised at the head)
249
- *
250
- * All chain fetchers are fetching at the head AND
251
- * No events that can be processed on the queue (even if events still exist on the individual queues)
252
- * CASE 2 (chain finishes earlier than any other chain)
253
- *
254
- * CASE 3 endblock has been reached and latest processed block is greater than or equal to endblock (both fields must be Some)
255
- *
256
- * The given chain fetcher is fetching at the head or latest processed block >= endblock
257
- * The given chain has processed all events on the queue
258
- * see https://github.com/Float-Capital/indexer/pull/1388 */
259
- let cf = if cf->ChainFetcher.hasProcessedToEndblock {
260
- // in the case this is already set, don't reset and instead propagate the existing value
261
- let timestampCaughtUpToHeadOrEndblock =
262
- cf->ChainFetcher.isReady ? cf.timestampCaughtUpToHeadOrEndblock : Date.make()->Some
263
- {
264
- ...cf,
265
- timestampCaughtUpToHeadOrEndblock,
266
- }
267
- } else if !(cf->ChainFetcher.isReady) && cf.isProgressAtHead {
268
- //Only calculate and set timestampCaughtUpToHeadOrEndblock if chain fetcher is at the head and
269
- //its not already set
270
- //CASE1
271
- //All chains are caught up to head chainManager queue returns None
272
- //Meaning we are busy synchronizing chains at the head
273
- if nextQueueItemIsNone && allChainsAtHead {
274
- {
275
- ...cf,
276
- timestampCaughtUpToHeadOrEndblock: Date.make()->Some,
277
- }
278
- } else {
279
- //CASE2 -> Only calculate if case1 fails
280
- //All events have been processed on the chain fetchers queue
281
- //Other chains may be busy syncing
282
- let hasNoMoreEventsToProcess = cf->ChainFetcher.hasNoMoreEventsToProcess
283
-
284
- if hasNoMoreEventsToProcess {
285
- {
286
- ...cf,
287
- timestampCaughtUpToHeadOrEndblock: Date.make()->Some,
288
- }
289
- } else {
290
- //Default to just returning cf
291
- cf
292
- }
293
- }
294
- } else {
295
- //Default to just returning cf
296
- cf
297
- }
298
-
299
- // Set envio_progress_ready per-chain when it first becomes ready
300
- if cf->ChainFetcher.isReady {
301
- if !(prev->ChainFetcher.isReady) {
302
- Prometheus.ProgressReady.set(~chainId=chain->ChainMap.Chain.toChainId)
303
- }
304
- } else {
305
- allChainsReady := false
306
- }
307
-
308
- cf
309
- })
310
-
311
- if allChainsReady.contents {
312
- Prometheus.ProgressReady.setAllReady()
313
- }
314
-
315
- {
316
- ...chainManager,
317
- chainFetchers,
318
- isRealtime: chainManager.isRealtime || allChainsReady.contents,
319
- }
320
- }
321
-
322
154
  let validatePartitionQueryResponse = (
323
155
  state,
324
156
  {chain, response} as partitionQueryResponse: partitionQueryResponse,
@@ -347,7 +179,7 @@ let validatePartitionQueryResponse = (
347
179
  Prometheus.FetchingBlockRange.increment(
348
180
  ~chainId=chain->ChainMap.Chain.toChainId,
349
181
  ~totalTimeElapsed=stats.totalTimeElapsed,
350
- ~parsingTimeElapsed=stats.parsingTimeElapsed->Belt.Option.getWithDefault(0.),
182
+ ~parsingTimeElapsed=stats.parsingTimeElapsed->Option.getOr(0.),
351
183
  ~numEvents=parsedQueueItems->Array.length,
352
184
  ~blockRangeSize=latestFetchedBlockNumber - fromBlockQueried + 1,
353
185
  )
@@ -587,209 +419,212 @@ let onEnterReorgThreshold = (~state: t) => {
587
419
  }
588
420
  }
589
421
 
590
- let actionReducer = (state: t, action: action) => {
591
- switch action {
592
- | FinishWaitingForNewBlock({chain, knownHeight}) => {
593
- let updatedChainFetchers = state.chainManager.chainFetchers->ChainMap.update(
594
- chain,
595
- chainFetcher => {
596
- let updatedFetchState =
597
- chainFetcher.fetchState->FetchState.updateKnownHeight(~knownHeight)
598
- if updatedFetchState !== chainFetcher.fetchState {
599
- {
600
- ...chainFetcher,
601
- fetchState: updatedFetchState,
422
+ let injectedActionReducer = (~markBatchProcessed: Ports.MarkBatchProcessed.t) =>
423
+ (state: t, action: action) => {
424
+ switch action {
425
+ | FinishWaitingForNewBlock({chain, knownHeight}) => {
426
+ let updatedChainFetchers = state.chainManager.chainFetchers->ChainMap.update(
427
+ chain,
428
+ chainFetcher => {
429
+ let updatedFetchState =
430
+ chainFetcher.fetchState->FetchState.updateKnownHeight(~knownHeight)
431
+ if updatedFetchState !== chainFetcher.fetchState {
432
+ {
433
+ ...chainFetcher,
434
+ fetchState: updatedFetchState,
435
+ }
436
+ } else {
437
+ chainFetcher
602
438
  }
603
- } else {
604
- chainFetcher
605
- }
606
- },
439
+ },
440
+ )
441
+
442
+ let isBelowReorgThreshold =
443
+ !state.chainManager.isInReorgThreshold && state.ctx.config.shouldRollbackOnReorg
444
+ let shouldEnterReorgThreshold =
445
+ isBelowReorgThreshold &&
446
+ updatedChainFetchers
447
+ ->ChainMap.values
448
+ ->Array.every(chainFetcher => {
449
+ chainFetcher.fetchState->FetchState.isReadyToEnterReorgThreshold
450
+ })
451
+
452
+ let state = {
453
+ ...state,
454
+ chainManager: {
455
+ ...state.chainManager,
456
+ chainFetchers: updatedChainFetchers,
457
+ },
458
+ }
459
+
460
+ // Attempt ProcessEventBatch in case if we have block handlers to run
461
+ if shouldEnterReorgThreshold {
462
+ (onEnterReorgThreshold(~state), [NextQuery(CheckAllChains), ProcessEventBatch])
463
+ } else {
464
+ (state, [NextQuery(Chain(chain)), ProcessEventBatch])
465
+ }
466
+ }
467
+ | ValidatePartitionQueryResponse(partitionQueryResponse) =>
468
+ state->validatePartitionQueryResponse(partitionQueryResponse)
469
+ | SubmitPartitionQueryResponse({
470
+ newItems,
471
+ newItemsWithDcs,
472
+ knownHeight,
473
+ latestFetchedBlock,
474
+ query,
475
+ chain,
476
+ }) =>
477
+ state->submitPartitionQueryResponse(
478
+ ~newItems,
479
+ ~newItemsWithDcs,
480
+ ~knownHeight,
481
+ ~latestFetchedBlock,
482
+ ~query,
483
+ ~chain,
607
484
  )
485
+ | EventBatchProcessed({batch}) =>
486
+ let maybePruneEntityHistory =
487
+ state.ctx.config->Config.shouldPruneHistory(
488
+ ~isInReorgThreshold=state.chainManager.isInReorgThreshold,
489
+ )
490
+ ? [PruneStaleEntityHistory]
491
+ : []
608
492
 
609
- let isBelowReorgThreshold =
610
- !state.chainManager.isInReorgThreshold && state.ctx.config.shouldRollbackOnReorg
611
- let shouldEnterReorgThreshold =
612
- isBelowReorgThreshold &&
613
- updatedChainFetchers
614
- ->ChainMap.values
615
- ->Array.every(chainFetcher => {
616
- chainFetcher.fetchState->FetchState.isReadyToEnterReorgThreshold
617
- })
493
+ markBatchProcessed()
618
494
 
619
495
  let state = {
620
496
  ...state,
621
- chainManager: {
622
- ...state.chainManager,
623
- chainFetchers: updatedChainFetchers,
624
- },
497
+ // Can safely reset rollback state, since overwrite is not possible.
498
+ // If rollback is pending, the EventBatchProcessed will be handled by the invalid action reducer instead.
499
+ rollbackState: NoRollback,
500
+ chainManager: state.chainManager->ChainManager.updateProgressedChains(~batch),
625
501
  }
626
502
 
627
- // Attempt ProcessEventBatch in case if we have block handlers to run
628
- if shouldEnterReorgThreshold {
629
- (onEnterReorgThreshold(~state), [NextQuery(CheckAllChains), ProcessEventBatch])
630
- } else {
631
- (state, [NextQuery(Chain(chain)), ProcessEventBatch])
632
- }
633
- }
634
- | ValidatePartitionQueryResponse(partitionQueryResponse) =>
635
- state->validatePartitionQueryResponse(partitionQueryResponse)
636
- | SubmitPartitionQueryResponse({
637
- newItems,
638
- newItemsWithDcs,
639
- knownHeight,
640
- latestFetchedBlock,
641
- query,
642
- chain,
643
- }) =>
644
- state->submitPartitionQueryResponse(
645
- ~newItems,
646
- ~newItemsWithDcs,
647
- ~knownHeight,
648
- ~latestFetchedBlock,
649
- ~query,
650
- ~chain,
651
- )
652
- | EventBatchProcessed({batch}) =>
653
- let maybePruneEntityHistory =
654
- state.ctx.config->Config.shouldPruneHistory(
655
- ~isInReorgThreshold=state.chainManager.isInReorgThreshold,
503
+ let shouldExit = EventProcessing.allChainsEventsProcessedToEndblock(
504
+ state.chainManager.chainFetchers,
656
505
  )
657
- ? [PruneStaleEntityHistory]
658
- : []
659
-
660
- let state = {
661
- ...state,
662
- // Can safely reset rollback state, since overwrite is not possible.
663
- // If rollback is pending, the EventBatchProcessed will be handled by the invalid action reducer instead.
664
- rollbackState: NoRollback,
665
- chainManager: state.chainManager->updateProgressedChains(~batch, ~ctx=state.ctx),
666
- currentlyProcessingBatch: false,
667
- processedBatches: state.processedBatches + 1,
668
- }
669
-
670
- let shouldExit = EventProcessing.allChainsEventsProcessedToEndblock(
671
- state.chainManager.chainFetchers,
672
- )
673
- ? {
674
- Logging.info("All chains are caught up to end blocks.")
506
+ ? {
507
+ Logging.info("All chains are caught up to end blocks.")
675
508
 
676
- // Keep the indexer process running when in development mode (for Dev Console)
677
- // or when TUI is enabled (for display)
678
- if state.keepProcessAlive {
679
- NoExit
509
+ // Keep the indexer process running when in development mode (for Dev Console)
510
+ // or when TUI is enabled (for display)
511
+ if state.keepProcessAlive {
512
+ NoExit
513
+ } else {
514
+ ExitWithSuccess
515
+ }
516
+ }
517
+ : if (
518
+ // In auto-exit mode, error if all chains reached head with no events found
519
+ state.exitAfterFirstEventBlock &&
520
+ state.chainManager.chainFetchers
521
+ ->ChainMap.values
522
+ ->Array.every(cf => cf.isProgressAtHead && cf.fetchState.endBlock->Option.isNone)
523
+ ) {
524
+ ExitWithError(
525
+ "No events found between startBlock and chain head. Cannot auto-detect endBlock.",
526
+ )
680
527
  } else {
681
- ExitWithSuccess
528
+ NoExit
682
529
  }
683
- }
684
- : if (
685
- // In auto-exit mode, error if all chains reached head with no events found
686
- state.exitAfterFirstEventBlock &&
687
- state.chainManager.chainFetchers
688
- ->ChainMap.values
689
- ->Array.every(cf => cf.isProgressAtHead && cf.fetchState.endBlock->Belt.Option.isNone)
690
- ) {
691
- ExitWithError(
692
- "No events found between startBlock and chain head. Cannot auto-detect endBlock.",
693
- )
694
- } else {
695
- NoExit
696
- }
697
530
 
698
- // On exit, stop dispatching ProcessEventBatch: the flush is async and would
699
- // otherwise keep processing further batches while it runs.
700
- let tasks = switch shouldExit {
701
- | ExitWithSuccess
702
- | ExitWithError(_) => [UpdateChainMetaDataAndCheckForExit(shouldExit)]
703
- | NoExit =>
704
- [UpdateChainMetaDataAndCheckForExit(shouldExit), ProcessEventBatch]->Array.concat(
705
- maybePruneEntityHistory,
531
+ // On exit, stop dispatching ProcessEventBatch: the flush is async and would
532
+ // otherwise keep processing further batches while it runs.
533
+ let tasks = switch shouldExit {
534
+ | ExitWithSuccess
535
+ | ExitWithError(_) => [UpdateChainMetaDataAndCheckForExit(shouldExit)]
536
+ | NoExit =>
537
+ [UpdateChainMetaDataAndCheckForExit(shouldExit), ProcessEventBatch]->Array.concat(
538
+ maybePruneEntityHistory,
539
+ )
540
+ }
541
+ (state, tasks)
542
+
543
+ | StartFindingReorgDepth => ({...state, rollbackState: FindingReorgDepth}, [])
544
+ | FindReorgDepth({chain, rollbackTargetBlockNumber}) => (
545
+ {
546
+ ...state,
547
+ rollbackState: FoundReorgDepth({
548
+ chain,
549
+ rollbackTargetBlockNumber,
550
+ }),
551
+ },
552
+ [Rollback],
706
553
  )
707
- }
708
- (state, tasks)
554
+ | EnterReorgThreshold => (onEnterReorgThreshold(~state), [NextQuery(CheckAllChains)])
555
+ | UpdateQueues({progressedChainsById, shouldEnterReorgThreshold}) =>
556
+ let chainFetchers = state.chainManager.chainFetchers->ChainMap.mapWithKey((chain, cf) => {
557
+ let fs = switch progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
558
+ chain->ChainMap.Chain.toChainId,
559
+ ) {
560
+ | Some(chainAfterBatch) => chainAfterBatch.fetchState
561
+ | None => cf.fetchState
562
+ }
563
+ {
564
+ ...cf,
565
+ fetchState: shouldEnterReorgThreshold
566
+ ? fs->FetchState.updateInternal(~blockLag=cf.chainConfig.blockLag)
567
+ : fs,
568
+ }
569
+ })
709
570
 
710
- | StartProcessingBatch => ({...state, currentlyProcessingBatch: true}, [])
711
- | StartFindingReorgDepth => ({...state, rollbackState: FindingReorgDepth}, [])
712
- | FindReorgDepth({chain, rollbackTargetBlockNumber}) => (
713
- {
714
- ...state,
715
- rollbackState: FoundReorgDepth({
716
- chain,
717
- rollbackTargetBlockNumber,
718
- }),
719
- },
720
- [Rollback],
721
- )
722
- | EnterReorgThreshold => (onEnterReorgThreshold(~state), [NextQuery(CheckAllChains)])
723
- | UpdateQueues({progressedChainsById, shouldEnterReorgThreshold}) =>
724
- let chainFetchers = state.chainManager.chainFetchers->ChainMap.mapWithKey((chain, cf) => {
725
- let fs = switch progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
726
- chain->ChainMap.Chain.toChainId,
727
- ) {
728
- | Some(chainAfterBatch) => chainAfterBatch.fetchState
729
- | None => cf.fetchState
730
- }
731
- {
732
- ...cf,
733
- fetchState: shouldEnterReorgThreshold
734
- ? fs->FetchState.updateInternal(~blockLag=cf.chainConfig.blockLag)
735
- : fs,
571
+ let chainManager = {
572
+ ...state.chainManager,
573
+ chainFetchers,
736
574
  }
737
- })
738
-
739
- let chainManager = {
740
- ...state.chainManager,
741
- chainFetchers,
742
- }
743
575
 
744
- (
745
- {
746
- ...state,
747
- chainManager,
748
- },
749
- [NextQuery(CheckAllChains)],
750
- )
751
- | SetRollbackState({rollbackedChainManager, eventsProcessedDiffByChain}) => (
752
- {
753
- ...state,
754
- rollbackState: RollbackReady({
755
- eventsProcessedDiffByChain: eventsProcessedDiffByChain,
756
- }),
757
- chainManager: rollbackedChainManager,
758
- },
759
- [NextQuery(CheckAllChains), ProcessEventBatch],
760
- )
761
- | SuccessExit => {
762
- Logging.info("Exiting with success")
763
- NodeJs.process->NodeJs.exitWithCode(Success)
576
+ (
577
+ {
578
+ ...state,
579
+ chainManager,
580
+ },
581
+ [NextQuery(CheckAllChains)],
582
+ )
583
+ | SetRollbackState({rollbackedChainManager, eventsProcessedDiffByChain}) => (
584
+ {
585
+ ...state,
586
+ rollbackState: RollbackReady({
587
+ eventsProcessedDiffByChain: eventsProcessedDiffByChain,
588
+ }),
589
+ chainManager: rollbackedChainManager,
590
+ },
591
+ [NextQuery(CheckAllChains), ProcessEventBatch],
592
+ )
593
+ | SuccessExit => {
594
+ Logging.info("Exiting with success")
595
+ NodeJs.process->NodeJs.exitWithCode(Success)
596
+ (state, [])
597
+ }
598
+ | ErrorExit(errHandler) =>
599
+ state.onError(errHandler)
764
600
  (state, [])
765
601
  }
766
- | ErrorExit(errHandler) =>
767
- state.onError(errHandler)
768
- (state, [])
769
602
  }
770
- }
771
603
 
772
- let invalidatedActionReducer = (state: t, action: action) =>
773
- switch action {
774
- | EventBatchProcessed({batch}) if state->isPreparingRollback =>
775
- Logging.info("Finished processing batch before rollback, actioning rollback")
776
- (
777
- {
778
- ...state,
779
- chainManager: state.chainManager->updateProgressedChains(~batch, ~ctx=state.ctx),
780
- currentlyProcessingBatch: false,
781
- processedBatches: state.processedBatches + 1,
782
- },
783
- [Rollback],
784
- )
785
- | ErrorExit(_) => actionReducer(state, action)
786
- | _ =>
787
- Logging.trace({
788
- "msg": "Invalidated action discarded",
789
- "action": action->S.convertOrThrow(Utils.Schema.variantTag),
790
- })
791
- (state, [])
792
- }
604
+ let injectedInvalidatedActionReducer = (
605
+ ~markBatchProcessed: Ports.MarkBatchProcessed.t,
606
+ ~actionReducer: (t, action) => (t, array<task>),
607
+ ) =>
608
+ (state: t, action: action) =>
609
+ switch action {
610
+ | EventBatchProcessed({batch}) if state->isPreparingRollback =>
611
+ Logging.info("Finished processing batch before rollback, actioning rollback")
612
+ markBatchProcessed()
613
+ (
614
+ {
615
+ ...state,
616
+ chainManager: state.chainManager->ChainManager.updateProgressedChains(~batch),
617
+ },
618
+ [Rollback],
619
+ )
620
+ | ErrorExit(_) => actionReducer(state, action)
621
+ | _ =>
622
+ Logging.trace({
623
+ "msg": "Invalidated action discarded",
624
+ "action": action->S.convertOrThrow(Utils.Schema.variantTag),
625
+ })
626
+ (state, [])
627
+ }
793
628
 
794
629
  let checkAndFetchForChain = (
795
630
  //Used for dependency injection for tests
@@ -919,7 +754,7 @@ let injectedTaskReducer = (
919
754
  ->Promise.all
920
755
  }
921
756
  | ProcessEventBatch =>
922
- if !state.currentlyProcessingBatch && !isPreparingRollback(state) {
757
+ if !state.ctx.inMemoryStore.isProcessing && !isPreparingRollback(state) {
923
758
  let isRollbackBatch = switch state.rollbackState {
924
759
  | RollbackReady(_) => true
925
760
  | _ => false
@@ -966,10 +801,10 @@ let injectedTaskReducer = (
966
801
  }
967
802
  }
968
803
  } else {
969
- dispatchAction(StartProcessingBatch)
804
+ let inMemoryStore = state.ctx.inMemoryStore
805
+ inMemoryStore.isProcessing = true
970
806
  dispatchAction(UpdateQueues({progressedChainsById, shouldEnterReorgThreshold}))
971
807
 
972
- let inMemoryStore = state.ctx.inMemoryStore
973
808
  inMemoryStore->InMemoryStore.setBatchDcs(~batch)
974
809
 
975
810
  switch await EventProcessing.processEventBatch(
@@ -1019,7 +854,7 @@ let injectedTaskReducer = (
1019
854
  // while we are still finding the reorg depth
1020
855
  // Do nothing here, just wait for reorg depth to be found
1021
856
  | {rollbackState: FindingReorgDepth} => ()
1022
- | {rollbackState: FoundReorgDepth(_), currentlyProcessingBatch: true} =>
857
+ | {rollbackState: FoundReorgDepth(_)} if state.ctx.inMemoryStore.isProcessing =>
1023
858
  Logging.info("Waiting for batch to finish processing before executing rollback")
1024
859
  | {rollbackState: FoundReorgDepth({chain: reorgChain, rollbackTargetBlockNumber})} =>
1025
860
  let startTime = Hrtime.makeTimer()
@@ -1042,6 +877,13 @@ let injectedTaskReducer = (
1042
877
 
1043
878
  let reorgChainId = reorgChain->ChainMap.Chain.toChainId
1044
879
 
880
+ // Finish pending batch writes first: the target checkpoint, the progress
881
+ // diff and the rollback diff below must all be computed from the same db
882
+ // state. Otherwise an in-flight batch lands after the progress reads and
883
+ // its entity changes get reverted without the chain progress being
884
+ // rolled back, so the events are never reprocessed.
885
+ await state.ctx.inMemoryStore->InMemoryStore.flush
886
+
1045
887
  let rollbackTargetCheckpointId = {
1046
888
  switch await state.ctx.persistence.storage.getRollbackTargetCheckpoint(
1047
889
  ~reorgChainId,
@@ -1144,6 +986,10 @@ let injectedTaskReducer = (
1144
986
  fetchState: cf.fetchState->FetchState.rollback(
1145
987
  ~targetBlockNumber=rollbackTargetBlockNumber,
1146
988
  ),
989
+ committedProgressBlockNumber: Pervasives.min(
990
+ cf.committedProgressBlockNumber,
991
+ rollbackTargetBlockNumber,
992
+ ),
1147
993
  }
1148
994
  } else {
1149
995
  cf
@@ -1151,10 +997,6 @@ let injectedTaskReducer = (
1151
997
  }
1152
998
  })
1153
999
 
1154
- // Finish pending writes so committedCheckpointId reflects the db before
1155
- // computing the rollback diff against it.
1156
- await state.ctx.inMemoryStore->InMemoryStore.flush
1157
-
1158
1000
  let diff = await state.ctx.inMemoryStore->InMemoryStore.prepareRollbackDiff(
1159
1001
  ~persistence=state.ctx.persistence,
1160
1002
  ~rollbackTargetCheckpointId,
@@ -1192,9 +1034,23 @@ let injectedTaskReducer = (
1192
1034
  }
1193
1035
  }
1194
1036
 
1195
- let taskReducer = injectedTaskReducer(
1037
+ type reducers = {
1038
+ actionReducer: (t, action) => (t, array<task>),
1039
+ invalidatedActionReducer: (t, action) => (t, array<task>),
1040
+ taskReducer: (t, task, ~dispatchAction: action => unit) => promise<unit>,
1041
+ }
1042
+
1043
+ let makeReducers = (
1196
1044
  ~waitForNewBlock=SourceManager.waitForNewBlock,
1197
1045
  ~executeQuery=SourceManager.executeQuery,
1198
1046
  ~getLastKnownValidBlock=(chainFetcher, ~reorgBlockNumber, ~isRealtime) =>
1199
1047
  chainFetcher->ChainFetcher.getLastKnownValidBlock(~reorgBlockNumber, ~isRealtime),
1200
- )
1048
+ ~markBatchProcessed: Ports.MarkBatchProcessed.t,
1049
+ ) => {
1050
+ let actionReducer = injectedActionReducer(~markBatchProcessed)
1051
+ {
1052
+ actionReducer,
1053
+ invalidatedActionReducer: injectedInvalidatedActionReducer(~markBatchProcessed, ~actionReducer),
1054
+ taskReducer: injectedTaskReducer(~waitForNewBlock, ~executeQuery, ~getLastKnownValidBlock),
1055
+ }
1056
+ }