envio 3.2.1 → 3.3.0-alpha.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 (106) hide show
  1. package/package.json +6 -7
  2. package/src/Batch.res +12 -15
  3. package/src/Batch.res.mjs +4 -5
  4. package/src/BatchProcessing.res +199 -0
  5. package/src/BatchProcessing.res.mjs +125 -0
  6. package/src/ChainFetching.res +375 -0
  7. package/src/ChainFetching.res.mjs +206 -0
  8. package/src/ChainMetadata.res +27 -0
  9. package/src/ChainMetadata.res.mjs +27 -0
  10. package/src/ChainState.res +618 -0
  11. package/src/ChainState.res.mjs +461 -0
  12. package/src/ChainState.resi +85 -0
  13. package/src/Config.res +13 -4
  14. package/src/Config.res.mjs +8 -3
  15. package/src/ContractRegisterContext.res +106 -0
  16. package/src/ContractRegisterContext.res.mjs +76 -0
  17. package/src/Core.res +3 -0
  18. package/src/CrossChainState.res +269 -0
  19. package/src/CrossChainState.res.mjs +197 -0
  20. package/src/CrossChainState.resi +47 -0
  21. package/src/Ecosystem.res +58 -0
  22. package/src/Ecosystem.res.mjs +43 -1
  23. package/src/Env.res +7 -2
  24. package/src/Env.res.mjs +5 -2
  25. package/src/EventConfigBuilder.res +2 -2
  26. package/src/EventProcessing.res +60 -46
  27. package/src/EventProcessing.res.mjs +33 -32
  28. package/src/EventUtils.res +0 -4
  29. package/src/EventUtils.res.mjs +0 -4
  30. package/src/ExitOnCaughtUp.res +10 -0
  31. package/src/ExitOnCaughtUp.res.mjs +22 -0
  32. package/src/FetchState.res +124 -62
  33. package/src/FetchState.res.mjs +159 -93
  34. package/src/InMemoryStore.res +36 -451
  35. package/src/InMemoryStore.res.mjs +20 -369
  36. package/src/IndexerLoop.res +44 -0
  37. package/src/IndexerLoop.res.mjs +46 -0
  38. package/src/IndexerState.res +544 -0
  39. package/src/IndexerState.res.mjs +547 -0
  40. package/src/IndexerState.resi +132 -0
  41. package/src/Internal.res +29 -8
  42. package/src/LoadLayer.res +20 -18
  43. package/src/LoadLayer.res.mjs +14 -12
  44. package/src/LoadLayer.resi +6 -3
  45. package/src/Logging.res +11 -44
  46. package/src/Logging.res.mjs +10 -42
  47. package/src/Main.res +54 -79
  48. package/src/Main.res.mjs +44 -56
  49. package/src/PgStorage.res +8 -71
  50. package/src/PgStorage.res.mjs +3 -47
  51. package/src/Prometheus.res +7 -8
  52. package/src/Prometheus.res.mjs +7 -4
  53. package/src/PruneStaleHistory.res +45 -0
  54. package/src/PruneStaleHistory.res.mjs +46 -0
  55. package/src/RawEvent.res +73 -0
  56. package/src/RawEvent.res.mjs +51 -0
  57. package/src/Rollback.res +204 -0
  58. package/src/Rollback.res.mjs +118 -0
  59. package/src/SimulateItems.res +12 -10
  60. package/src/SimulateItems.res.mjs +1 -1
  61. package/src/UserContext.res +26 -114
  62. package/src/UserContext.res.mjs +15 -78
  63. package/src/Writing.res +242 -0
  64. package/src/Writing.res.mjs +215 -0
  65. package/src/db/InternalTable.res +14 -27
  66. package/src/sources/Evm.res +40 -1
  67. package/src/sources/Evm.res.mjs +94 -84
  68. package/src/sources/Fuel.res +40 -1
  69. package/src/sources/Fuel.res.mjs +36 -26
  70. package/src/sources/HyperFuel.res +41 -120
  71. package/src/sources/HyperFuel.res.mjs +42 -80
  72. package/src/sources/HyperFuel.resi +1 -24
  73. package/src/sources/HyperFuelClient.res +16 -297
  74. package/src/sources/HyperFuelClient.res.mjs +5 -4
  75. package/src/sources/HyperFuelSource.res +33 -8
  76. package/src/sources/HyperFuelSource.res.mjs +56 -10
  77. package/src/sources/HyperSyncSource.res +5 -3
  78. package/src/sources/HyperSyncSource.res.mjs +1 -1
  79. package/src/sources/RpcSource.res +2 -2
  80. package/src/sources/RpcSource.res.mjs +1 -1
  81. package/src/sources/SourceManager.res +8 -18
  82. package/src/sources/SourceManager.res.mjs +8 -5
  83. package/src/sources/SourceManager.resi +4 -1
  84. package/src/sources/Svm.res +18 -1
  85. package/src/sources/Svm.res.mjs +30 -22
  86. package/src/sources/SvmHyperSyncSource.res +1 -1
  87. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  88. package/src/tui/Tui.res +38 -36
  89. package/src/tui/Tui.res.mjs +51 -51
  90. package/src/ChainFetcher.res +0 -519
  91. package/src/ChainFetcher.res.mjs +0 -314
  92. package/src/ChainManager.res +0 -358
  93. package/src/ChainManager.res.mjs +0 -291
  94. package/src/Ctx.res +0 -6
  95. package/src/Ctx.res.mjs +0 -2
  96. package/src/GlobalState.res +0 -1056
  97. package/src/GlobalState.res.mjs +0 -1086
  98. package/src/GlobalStateManager.res +0 -57
  99. package/src/GlobalStateManager.res.mjs +0 -68
  100. package/src/GlobalStateManager.resi +0 -7
  101. package/src/Ports.res +0 -5
  102. package/src/Ports.res.mjs +0 -9
  103. package/src/adapters/MarkBatchProcessedAdapter.res +0 -5
  104. package/src/adapters/MarkBatchProcessedAdapter.res.mjs +0 -14
  105. package/src/sources/EnvioApiClient.res +0 -15
  106. package/src/sources/EnvioApiClient.res.mjs +0 -24
@@ -1,1056 +0,0 @@
1
- type chain = ChainMap.Chain.t
2
- type rollbackState =
3
- | NoRollback
4
- | ReorgDetected({chain: chain, blockNumber: int})
5
- | FindingReorgDepth
6
- | FoundReorgDepth({chain: chain, rollbackTargetBlockNumber: int})
7
- | RollbackReady({eventsProcessedDiffByChain: dict<float>})
8
-
9
- module WriteThrottlers = {
10
- type t = {pruneStaleEntityHistory: Throttler.t}
11
- let make = (): t => {
12
- let pruneStaleEntityHistory = {
13
- let intervalMillis = Env.ThrottleWrites.pruneStaleDataIntervalMillis
14
- let logger = Logging.createChild(
15
- ~params={
16
- "context": "Throttler for pruning stale entity history data",
17
- "intervalMillis": intervalMillis,
18
- },
19
- )
20
- Throttler.make(~intervalMillis, ~logger)
21
- }
22
- {pruneStaleEntityHistory: pruneStaleEntityHistory}
23
- }
24
- }
25
-
26
- type t = {
27
- ctx: Ctx.t,
28
- chainManager: ChainManager.t,
29
- rollbackState: rollbackState,
30
- indexerStartTime: Date.t,
31
- writeThrottlers: WriteThrottlers.t,
32
- loadManager: LoadManager.t,
33
- keepProcessAlive: bool,
34
- exitAfterFirstEventBlock: bool,
35
- // The single fatal-error handler ErrorExit delegates to.
36
- onError: ErrorHandling.t => unit,
37
- //Initialized as 0, increments, when rollbacks occur to invalidate
38
- //responses based on the wrong stateId
39
- id: int,
40
- }
41
-
42
- let make = (
43
- ~ctx: Ctx.t,
44
- ~chainManager: ChainManager.t,
45
- ~isDevelopmentMode=false,
46
- ~shouldUseTui=false,
47
- ~exitAfterFirstEventBlock=false,
48
- ~onError: ErrorHandling.t => unit,
49
- ) => {
50
- {
51
- ctx,
52
- chainManager,
53
- indexerStartTime: Date.make(),
54
- rollbackState: NoRollback,
55
- writeThrottlers: WriteThrottlers.make(),
56
- loadManager: LoadManager.make(),
57
- keepProcessAlive: isDevelopmentMode || shouldUseTui,
58
- exitAfterFirstEventBlock,
59
- onError,
60
- id: 0,
61
- }
62
- }
63
-
64
- let getId = self => self.id
65
- let setChainManager = (self, chainManager) => {
66
- ...self,
67
- chainManager,
68
- }
69
-
70
- let isPreparingRollback = state =>
71
- switch state.rollbackState {
72
- | NoRollback
73
- | // We already updated fetch states here
74
- // so we treat it as not rolling back
75
- RollbackReady(_) => false
76
- | FindingReorgDepth
77
- | ReorgDetected(_)
78
- | FoundReorgDepth(_) => true
79
- }
80
-
81
- type partitionQueryResponse = {
82
- chain: chain,
83
- response: Source.blockRangeFetchResponse,
84
- query: FetchState.query,
85
- }
86
-
87
- type shouldExit = ExitWithSuccess | ExitWithError(string) | NoExit
88
-
89
- // Need to dispatch an action for every async operation
90
- // to get access to the latest state.
91
- type action =
92
- // After a response is received, we validate it with the new state
93
- // if there's no reorg to continue processing the response.
94
- | ValidatePartitionQueryResponse(partitionQueryResponse)
95
- // This should be a separate action from ValidatePartitionQueryResponse
96
- // because when processing the response, there might be an async contract registration.
97
- // So after it's finished we dispatch the submit action to get the latest fetch state.
98
- | SubmitPartitionQueryResponse({
99
- newItems: array<Internal.item>,
100
- newItemsWithDcs: array<Internal.item>,
101
- knownHeight: int,
102
- latestFetchedBlock: FetchState.blockNumberAndTimestamp,
103
- query: FetchState.query,
104
- chain: chain,
105
- })
106
- | FinishWaitingForNewBlock({chain: chain, knownHeight: int})
107
- | EventBatchProcessed({batch: Batch.t})
108
- | StartFindingReorgDepth
109
- | FindReorgDepth({chain: chain, rollbackTargetBlockNumber: int})
110
- | EnterReorgThreshold
111
- | UpdateQueues({
112
- progressedChainsById: dict<Batch.chainAfterBatch>,
113
- // Needed to prevent overwriting the blockLag
114
- // set by EnterReorgThreshold
115
- shouldEnterReorgThreshold: bool,
116
- })
117
- | SuccessExit
118
- | ErrorExit(ErrorHandling.t)
119
- | SetRollbackState({
120
- rollbackedChainManager: ChainManager.t,
121
- eventsProcessedDiffByChain: dict<float>,
122
- })
123
-
124
- type queryChain = CheckAllChains | Chain(chain)
125
- type task =
126
- | NextQuery(queryChain)
127
- | ProcessPartitionQueryResponse(partitionQueryResponse)
128
- | ProcessEventBatch
129
- | UpdateChainMetaDataAndCheckForExit(shouldExit)
130
- | Rollback
131
- | PruneStaleEntityHistory
132
-
133
- let updateChainMetadataTable = (cm: ChainManager.t, ~inMemoryStore: InMemoryStore.t) => {
134
- let chainsData: dict<InternalTable.Chains.metaFields> = Dict.make()
135
-
136
- cm.chainFetchers
137
- ->ChainMap.values
138
- ->Array.forEach(cf => {
139
- chainsData->Dict.set(
140
- cf.chainConfig.id->Int.toString,
141
- {
142
- firstEventBlockNumber: cf.fetchState.firstEventBlock->Null.fromOption,
143
- isHyperSync: (cf.sourceManager->SourceManager.getActiveSource).poweredByHyperSync,
144
- latestFetchedBlockNumber: cf.fetchState->FetchState.bufferBlockNumber,
145
- timestampCaughtUpToHeadOrEndblock: cf.timestampCaughtUpToHeadOrEndblock->Null.fromOption,
146
- },
147
- )
148
- })
149
-
150
- // Staged; the cycle folds the stale diff into the next batch write.
151
- inMemoryStore->InMemoryStore.setChainMeta(chainsData)
152
- }
153
-
154
- let validatePartitionQueryResponse = (
155
- state,
156
- {chain, response} as partitionQueryResponse: partitionQueryResponse,
157
- ) => {
158
- let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
159
- let {
160
- parsedQueueItems,
161
- latestFetchedBlockNumber,
162
- stats,
163
- knownHeight,
164
- blockHashes,
165
- fromBlockQueried,
166
- } = response
167
-
168
- if knownHeight > chainFetcher.fetchState.knownHeight {
169
- Prometheus.SourceHeight.set(
170
- ~blockNumber=knownHeight,
171
- ~chainId=chainFetcher.chainConfig.id,
172
- // The knownHeight from response won't necessarily
173
- // belong to the currently active source.
174
- // But for simplicity, assume it does.
175
- ~sourceName=(chainFetcher.sourceManager->SourceManager.getActiveSource).name,
176
- )
177
- }
178
-
179
- Prometheus.FetchingBlockRange.increment(
180
- ~chainId=chain->ChainMap.Chain.toChainId,
181
- ~totalTimeElapsed=stats.totalTimeElapsed,
182
- ~parsingTimeElapsed=stats.parsingTimeElapsed->Option.getOr(0.),
183
- ~numEvents=parsedQueueItems->Array.length,
184
- ~blockRangeSize=latestFetchedBlockNumber - fromBlockQueried + 1,
185
- )
186
-
187
- let (updatedReorgDetection, reorgResult: ReorgDetection.reorgResult) =
188
- chainFetcher.reorgDetection->ReorgDetection.registerReorgGuard(~blockHashes, ~knownHeight)
189
-
190
- let updatedChainFetcher = {
191
- ...chainFetcher,
192
- reorgDetection: updatedReorgDetection,
193
- }
194
-
195
- let nextState = {
196
- ...state,
197
- chainManager: {
198
- ...state.chainManager,
199
- chainFetchers: state.chainManager.chainFetchers->ChainMap.set(chain, updatedChainFetcher),
200
- },
201
- }
202
-
203
- let rollbackWithReorgDetectedBlockNumber = switch reorgResult {
204
- | ReorgDetected(reorgDetected) => {
205
- chainFetcher.logger->Logging.childInfo(
206
- reorgDetected->ReorgDetection.reorgDetectedToLogParams(
207
- ~shouldRollbackOnReorg=state.ctx.config.shouldRollbackOnReorg,
208
- ),
209
- )
210
- Prometheus.ReorgCount.increment(~chain)
211
- Prometheus.ReorgDetectionBlockNumber.set(
212
- ~blockNumber=reorgDetected.scannedBlock.blockNumber,
213
- ~chain,
214
- )
215
- if state.ctx.config.shouldRollbackOnReorg {
216
- Some(reorgDetected.scannedBlock.blockNumber)
217
- } else {
218
- None
219
- }
220
- }
221
- | NoReorg => None
222
- }
223
-
224
- switch rollbackWithReorgDetectedBlockNumber {
225
- | None => (nextState, [ProcessPartitionQueryResponse(partitionQueryResponse)])
226
- | Some(reorgDetectedBlockNumber) => {
227
- let chainManager = switch state.rollbackState {
228
- | RollbackReady({eventsProcessedDiffByChain}) => {
229
- ...state.chainManager,
230
- // Restore event counters for ALL chains, not just the reorg chain.
231
- // The previous rollback subtracted from all chains' counters,
232
- // but was never committed to DB. So we must undo the subtraction
233
- // for every chain before the new rollback subtracts again.
234
- chainFetchers: state.chainManager.chainFetchers->ChainMap.mapWithKey((
235
- c,
236
- chainFetcher,
237
- ) => {
238
- switch eventsProcessedDiffByChain->Utils.Dict.dangerouslyGetByIntNonOption(
239
- c->ChainMap.Chain.toChainId,
240
- ) {
241
- | Some(eventsProcessedDiff) => {
242
- ...chainFetcher,
243
- // Since we detected a reorg, until rollback wasn't completed in the db
244
- // We return the events processed counter to the pre-rollback value,
245
- // to decrease it once more for the new rollback.
246
- numEventsProcessed: chainFetcher.numEventsProcessed +. eventsProcessedDiff,
247
- }
248
- | None => chainFetcher
249
- }
250
- }),
251
- }
252
- | _ => state.chainManager
253
- }
254
- (
255
- {
256
- ...nextState,
257
- id: nextState.id + 1,
258
- chainManager: {
259
- ...chainManager,
260
- chainFetchers: chainManager.chainFetchers->ChainMap.map(chainFetcher => {
261
- ...chainFetcher,
262
- // TODO: It's not optimal to abort pending queries for all chains,
263
- // this is how it always worked, but we should consider a better approach.
264
- fetchState: chainFetcher.fetchState->FetchState.resetPendingQueries,
265
- }),
266
- },
267
- rollbackState: ReorgDetected({
268
- chain,
269
- blockNumber: reorgDetectedBlockNumber,
270
- }),
271
- },
272
- [Rollback],
273
- )
274
- }
275
- }
276
- }
277
-
278
- let submitPartitionQueryResponse = (
279
- state,
280
- ~newItems,
281
- ~newItemsWithDcs,
282
- ~knownHeight,
283
- ~latestFetchedBlock,
284
- ~query,
285
- ~chain,
286
- ) => {
287
- let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
288
-
289
- let updatedChainFetcher =
290
- chainFetcher->ChainFetcher.handleQueryResult(
291
- ~query,
292
- ~latestFetchedBlock,
293
- ~newItems,
294
- ~newItemsWithDcs,
295
- ~knownHeight,
296
- )
297
-
298
- // In auto-exit mode, set endBlock to the first event's block when events arrive.
299
- // Also update if a partition returns events at an earlier block than current endBlock.
300
- let updatedChainFetcher = if state.exitAfterFirstEventBlock && newItems->Array.length > 0 {
301
- let firstEventBlock = newItems->Array.getUnsafe(0)->Internal.getItemBlockNumber
302
- switch updatedChainFetcher.fetchState.endBlock {
303
- | None => {
304
- ...updatedChainFetcher,
305
- fetchState: {...updatedChainFetcher.fetchState, endBlock: Some(firstEventBlock)},
306
- }
307
- | Some(currentEndBlock) if firstEventBlock < currentEndBlock => {
308
- ...updatedChainFetcher,
309
- fetchState: {...updatedChainFetcher.fetchState, endBlock: Some(firstEventBlock)},
310
- }
311
- | Some(_) => updatedChainFetcher
312
- }
313
- } else {
314
- updatedChainFetcher
315
- }
316
-
317
- if !chainFetcher.isProgressAtHead && updatedChainFetcher.isProgressAtHead {
318
- updatedChainFetcher.logger->Logging.childInfo("All events have been fetched")
319
- }
320
-
321
- let nextState = {
322
- ...state,
323
- chainManager: {
324
- ...state.chainManager,
325
- chainFetchers: state.chainManager.chainFetchers->ChainMap.set(chain, updatedChainFetcher),
326
- },
327
- }
328
-
329
- (
330
- nextState,
331
- [UpdateChainMetaDataAndCheckForExit(NoExit), ProcessEventBatch, NextQuery(Chain(chain))],
332
- )
333
- }
334
-
335
- let processPartitionQueryResponse = async (
336
- state,
337
- {chain, response, query}: partitionQueryResponse,
338
- ~dispatchAction,
339
- ) => {
340
- let {
341
- parsedQueueItems,
342
- latestFetchedBlockNumber,
343
- knownHeight,
344
- latestFetchedBlockTimestamp,
345
- } = response
346
-
347
- let itemsWithContractRegister = []
348
- let newItems = []
349
-
350
- for idx in 0 to parsedQueueItems->Array.length - 1 {
351
- let item = parsedQueueItems->Array.getUnsafe(idx)
352
- let eventItem = item->Internal.castUnsafeEventItem
353
- if eventItem.eventConfig.contractRegister !== None {
354
- itemsWithContractRegister->Array.push(item)
355
- }
356
-
357
- // TODO: Don't really need to keep it in the queue
358
- // when there's no handler (besides raw_events, processed counter, and dcsToStore consuming)
359
- newItems->Array.push(item)
360
- }
361
-
362
- let newItemsWithDcs = switch itemsWithContractRegister {
363
- | [] as empty => empty
364
- | _ =>
365
- await ChainFetcher.runContractRegistersOrThrow(
366
- ~itemsWithContractRegister,
367
- ~config=state.ctx.config,
368
- )
369
- }
370
-
371
- dispatchAction(
372
- SubmitPartitionQueryResponse({
373
- newItems,
374
- newItemsWithDcs,
375
- knownHeight,
376
- latestFetchedBlock: {
377
- blockNumber: latestFetchedBlockNumber,
378
- blockTimestamp: latestFetchedBlockTimestamp,
379
- },
380
- chain,
381
- query,
382
- }),
383
- )
384
- }
385
-
386
- let updateChainFetcher = (chainFetcherUpdate, ~state, ~chain) => {
387
- (
388
- {
389
- ...state,
390
- chainManager: {
391
- ...state.chainManager,
392
- chainFetchers: state.chainManager.chainFetchers->ChainMap.update(chain, chainFetcherUpdate),
393
- },
394
- },
395
- [],
396
- )
397
- }
398
-
399
- let onEnterReorgThreshold = (~state: t) => {
400
- Logging.info("Reorg threshold reached")
401
- Prometheus.ReorgThreshold.set(~isInReorgThreshold=true)
402
-
403
- let chainFetchers = state.chainManager.chainFetchers->ChainMap.map(chainFetcher => {
404
- {
405
- ...chainFetcher,
406
- fetchState: chainFetcher.fetchState->FetchState.updateInternal(
407
- ~blockLag=chainFetcher.chainConfig.blockLag,
408
- ),
409
- }
410
- })
411
-
412
- {
413
- ...state,
414
- chainManager: {
415
- ...state.chainManager,
416
- chainFetchers,
417
- isInReorgThreshold: true,
418
- },
419
- }
420
- }
421
-
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
438
- }
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,
484
- )
485
- | EventBatchProcessed({batch}) =>
486
- let maybePruneEntityHistory =
487
- state.ctx.config->Config.shouldPruneHistory(
488
- ~isInReorgThreshold=state.chainManager.isInReorgThreshold,
489
- )
490
- ? [PruneStaleEntityHistory]
491
- : []
492
-
493
- markBatchProcessed()
494
-
495
- let state = {
496
- ...state,
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),
501
- }
502
-
503
- let shouldExit = EventProcessing.allChainsEventsProcessedToEndblock(
504
- state.chainManager.chainFetchers,
505
- )
506
- ? {
507
- Logging.info("All chains are caught up to end blocks.")
508
-
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
- )
527
- } else {
528
- NoExit
529
- }
530
-
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],
553
- )
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
- })
570
-
571
- let chainManager = {
572
- ...state.chainManager,
573
- chainFetchers,
574
- }
575
-
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)
600
- (state, [])
601
- }
602
- }
603
-
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
- }
628
-
629
- let checkAndFetchForChain = (
630
- //Used for dependency injection for tests
631
- ~waitForNewBlock,
632
- ~executeQuery,
633
- //required args
634
- ~state,
635
- ~dispatchAction,
636
- ) =>
637
- async chain => {
638
- let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
639
- if !isPreparingRollback(state) {
640
- let {fetchState} = chainFetcher
641
- let isRealtime = state.chainManager.isRealtime
642
-
643
- // Only affects the WaitingForNewBlock branch of fetchNext, where
644
- // there's nothing to fetch. During backfill any such chain is idle.
645
- let reducedPolling = !isRealtime
646
-
647
- await chainFetcher.sourceManager->SourceManager.fetchNext(
648
- ~fetchState,
649
- ~waitForNewBlock=(~knownHeight) =>
650
- chainFetcher.sourceManager->waitForNewBlock(~knownHeight, ~isRealtime, ~reducedPolling),
651
- ~onNewBlock=(~knownHeight) =>
652
- dispatchAction(FinishWaitingForNewBlock({chain, knownHeight})),
653
- ~executeQuery=async query => {
654
- try {
655
- let response = await chainFetcher.sourceManager->executeQuery(
656
- ~query,
657
- ~knownHeight=fetchState.knownHeight,
658
- ~isRealtime,
659
- )
660
- dispatchAction(ValidatePartitionQueryResponse({chain, response, query}))
661
- } catch {
662
- | exn => dispatchAction(ErrorExit(exn->ErrorHandling.make))
663
- }
664
- },
665
- ~stateId=state.id,
666
- )
667
- }
668
- }
669
-
670
- let injectedTaskReducer = (
671
- //Used for dependency injection for tests
672
- ~waitForNewBlock,
673
- ~executeQuery,
674
- ~getLastKnownValidBlock,
675
- ) =>
676
- async (
677
- //required args
678
- state: t,
679
- task: task,
680
- ~dispatchAction,
681
- ) => {
682
- switch task {
683
- | ProcessPartitionQueryResponse(partitionQueryResponse) =>
684
- state->processPartitionQueryResponse(partitionQueryResponse, ~dispatchAction)->Promise.ignore
685
- | PruneStaleEntityHistory =>
686
- let runPrune = async () => {
687
- switch state.chainManager->ChainManager.getSafeCheckpointId {
688
- | None => ()
689
- | Some(safeCheckpointId) =>
690
- await state.ctx.persistence.storage.pruneStaleCheckpoints(~safeCheckpointId)
691
-
692
- for idx in 0 to state.ctx.persistence.allEntities->Array.length - 1 {
693
- if idx !== 0 {
694
- // Add some delay between entities
695
- // To unblock the pg client if it's needed for something else
696
- await Utils.delay(1000)
697
- }
698
- let entityConfig = state.ctx.persistence.allEntities->Array.getUnsafe(idx)
699
- let timeRef = Hrtime.makeTimer()
700
- try {
701
- let () = await state.ctx.persistence.storage.pruneStaleEntityHistory(
702
- ~entityName=entityConfig.name,
703
- ~entityIndex=entityConfig.index,
704
- ~safeCheckpointId,
705
- )
706
- } catch {
707
- | exn =>
708
- exn->ErrorHandling.mkLogAndRaise(
709
- ~msg=`Failed to prune stale entity history`,
710
- ~logger=Logging.createChild(
711
- ~params={
712
- "entityName": entityConfig.name,
713
- "safeCheckpointId": safeCheckpointId,
714
- },
715
- ),
716
- )
717
- }
718
- Prometheus.RollbackHistoryPrune.increment(
719
- ~timeSeconds=Hrtime.timeSince(timeRef)->Hrtime.toSecondsFloat,
720
- ~entityName=entityConfig.name,
721
- )
722
- }
723
- }
724
- }
725
- state.writeThrottlers.pruneStaleEntityHistory->Throttler.schedule(runPrune)
726
-
727
- | UpdateChainMetaDataAndCheckForExit(shouldExit) =>
728
- let {chainManager} = state
729
- switch shouldExit {
730
- | ExitWithSuccess =>
731
- updateChainMetadataTable(chainManager, ~inMemoryStore=state.ctx.inMemoryStore)
732
- await state.ctx.inMemoryStore->InMemoryStore.flush
733
- dispatchAction(SuccessExit)
734
- | ExitWithError(msg) =>
735
- dispatchAction(ErrorExit(ErrorHandling.make(JsError.throwWithMessage(msg))))
736
- | NoExit => updateChainMetadataTable(chainManager, ~inMemoryStore=state.ctx.inMemoryStore)
737
- }
738
- | NextQuery(chainCheck) =>
739
- let fetchForChain = checkAndFetchForChain(
740
- ~waitForNewBlock,
741
- ~executeQuery,
742
- ~state,
743
- ~dispatchAction,
744
- )
745
-
746
- switch chainCheck {
747
- | Chain(chain) => await chain->fetchForChain
748
- | CheckAllChains =>
749
- //Mapping from the states chainManager so we can construct tests that don't use
750
- //all chains
751
- let _ = await state.chainManager.chainFetchers
752
- ->ChainMap.keys
753
- ->Array.map(fetchForChain(_))
754
- ->Promise.all
755
- }
756
- | ProcessEventBatch =>
757
- if !state.ctx.inMemoryStore.isProcessing && !isPreparingRollback(state) {
758
- let isRollbackBatch = switch state.rollbackState {
759
- | RollbackReady(_) => true
760
- | _ => false
761
- }
762
-
763
- let batch =
764
- state.chainManager->ChainManager.createBatch(
765
- ~processedCheckpointId=state.ctx.inMemoryStore.processedCheckpointId,
766
- ~batchSizeTarget=state.ctx.config.batchSize,
767
- ~isRollback=isRollbackBatch,
768
- )
769
-
770
- let progressedChainsById = batch.progressedChainsById
771
-
772
- let isBelowReorgThreshold =
773
- !state.chainManager.isInReorgThreshold && state.ctx.config.shouldRollbackOnReorg
774
- let shouldEnterReorgThreshold =
775
- isBelowReorgThreshold &&
776
- state.chainManager.chainFetchers
777
- ->ChainMap.values
778
- ->Array.every(chainFetcher => {
779
- let fetchState = switch progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
780
- chainFetcher.fetchState.chainId,
781
- ) {
782
- | Some(chainAfterBatch) => chainAfterBatch.fetchState
783
- | None => chainFetcher.fetchState
784
- }
785
- fetchState->FetchState.isReadyToEnterReorgThreshold
786
- })
787
-
788
- if shouldEnterReorgThreshold {
789
- dispatchAction(EnterReorgThreshold)
790
- }
791
-
792
- if progressedChainsById->Utils.Dict.isEmpty {
793
- // When resuming from persisted state, all events may already be processed.
794
- // Log the same completion message and handle exit just like EventBatchProcessed does.
795
- if EventProcessing.allChainsEventsProcessedToEndblock(state.chainManager.chainFetchers) {
796
- Logging.info("All chains are caught up to end blocks.")
797
- if !state.keepProcessAlive {
798
- updateChainMetadataTable(state.chainManager, ~inMemoryStore=state.ctx.inMemoryStore)
799
- await state.ctx.inMemoryStore->InMemoryStore.flush
800
- dispatchAction(SuccessExit)
801
- }
802
- }
803
- } else {
804
- let inMemoryStore = state.ctx.inMemoryStore
805
- inMemoryStore.isProcessing = true
806
- dispatchAction(UpdateQueues({progressedChainsById, shouldEnterReorgThreshold}))
807
-
808
- inMemoryStore->InMemoryStore.setBatchDcs(~batch)
809
-
810
- switch await EventProcessing.processEventBatch(
811
- ~batch,
812
- ~inMemoryStore,
813
- ~loadManager=state.loadManager,
814
- ~ctx=state.ctx,
815
- ~chainFetchers=state.chainManager.chainFetchers,
816
- ) {
817
- | exception exn =>
818
- //All casese should be handled/caught before this with better user messaging.
819
- //This is just a safety in case something unexpected happens
820
- let errHandler =
821
- exn->ErrorHandling.make(
822
- ~msg="A top level unexpected error occurred during processing",
823
- )
824
- dispatchAction(ErrorExit(errHandler))
825
- | res =>
826
- switch res {
827
- | Ok() => dispatchAction(EventBatchProcessed({batch: batch}))
828
- | Error(errHandler) => dispatchAction(ErrorExit(errHandler))
829
- }
830
- }
831
- }
832
- }
833
- | Rollback =>
834
- //If it isn't processing a batch currently continue with rollback otherwise wait for current batch to finish processing
835
- switch state {
836
- | {rollbackState: NoRollback | RollbackReady(_)} =>
837
- JsError.throwWithMessage("Internal error: Rollback initiated with invalid state")
838
- | {rollbackState: ReorgDetected({chain, blockNumber: reorgBlockNumber})} => {
839
- let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
840
-
841
- dispatchAction(StartFindingReorgDepth)
842
- let rollbackTargetBlockNumber = await chainFetcher->getLastKnownValidBlock(
843
- ~reorgBlockNumber,
844
- ~isRealtime=state.chainManager.isRealtime,
845
- )
846
-
847
- chainFetcher.sourceManager->SourceManager.onReorg(
848
- ~rollbackTargetBlock=rollbackTargetBlockNumber,
849
- )
850
-
851
- dispatchAction(FindReorgDepth({chain, rollbackTargetBlockNumber}))
852
- }
853
- // We can come to this case when event batch finished processing
854
- // while we are still finding the reorg depth
855
- // Do nothing here, just wait for reorg depth to be found
856
- | {rollbackState: FindingReorgDepth} => ()
857
- | {rollbackState: FoundReorgDepth(_)} if state.ctx.inMemoryStore.isProcessing =>
858
- Logging.info("Waiting for batch to finish processing before executing rollback")
859
- | {rollbackState: FoundReorgDepth({chain: reorgChain, rollbackTargetBlockNumber})} =>
860
- let startTime = Hrtime.makeTimer()
861
-
862
- let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(reorgChain)
863
-
864
- let logger = Logging.createChildFrom(
865
- ~logger=chainFetcher.logger,
866
- ~params={
867
- "action": "Rollback",
868
- "reorgChain": reorgChain,
869
- "targetBlockNumber": rollbackTargetBlockNumber,
870
- },
871
- )
872
- logger->Logging.childInfo("Started rollback on reorg")
873
- Prometheus.RollbackTargetBlockNumber.set(
874
- ~blockNumber=rollbackTargetBlockNumber,
875
- ~chain=reorgChain,
876
- )
877
-
878
- let reorgChainId = reorgChain->ChainMap.Chain.toChainId
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
-
887
- let rollbackTargetCheckpointId = {
888
- switch await state.ctx.persistence.storage.getRollbackTargetCheckpoint(
889
- ~reorgChainId,
890
- ~lastKnownValidBlockNumber=rollbackTargetBlockNumber,
891
- ) {
892
- | Some(checkpointId) => checkpointId
893
- | None => 0n
894
- }
895
- }
896
-
897
- let eventsProcessedDiffByChain = Dict.make()
898
- let newProgressBlockNumberPerChain = Dict.make()
899
- let rollbackedProcessedEvents = ref(0.)
900
-
901
- {
902
- let rollbackProgressDiff = await state.ctx.persistence.storage.getRollbackProgressDiff(
903
- ~rollbackTargetCheckpointId,
904
- )
905
- for idx in 0 to rollbackProgressDiff->Array.length - 1 {
906
- let diff = rollbackProgressDiff->Array.getUnsafe(idx)
907
- eventsProcessedDiffByChain->Utils.Dict.setByInt(
908
- diff["chain_id"],
909
- {
910
- let eventsProcessedDiff =
911
- Float.fromString(diff["events_processed_diff"])->Option.getOrThrow
912
- rollbackedProcessedEvents :=
913
- rollbackedProcessedEvents.contents +. eventsProcessedDiff
914
- eventsProcessedDiff
915
- },
916
- )
917
- newProgressBlockNumberPerChain->Utils.Dict.setByInt(
918
- diff["chain_id"],
919
- if rollbackTargetCheckpointId === 0n && diff["chain_id"] === reorgChainId {
920
- Pervasives.min(diff["new_progress_block_number"], rollbackTargetBlockNumber)
921
- } else {
922
- diff["new_progress_block_number"]
923
- },
924
- )
925
- }
926
- }
927
-
928
- let chainFetchers = state.chainManager.chainFetchers->ChainMap.mapWithKey((chain, cf) => {
929
- switch newProgressBlockNumberPerChain->Utils.Dict.dangerouslyGetByIntNonOption(
930
- chain->ChainMap.Chain.toChainId,
931
- ) {
932
- | Some(newProgressBlockNumber) =>
933
- let fetchState =
934
- cf.fetchState->FetchState.rollback(~targetBlockNumber=newProgressBlockNumber)
935
- let newTotalEventsProcessed =
936
- cf.numEventsProcessed -.
937
- eventsProcessedDiffByChain
938
- ->Utils.Dict.dangerouslyGetByIntNonOption(chain->ChainMap.Chain.toChainId)
939
- ->Option.getUnsafe
940
-
941
- if cf.committedProgressBlockNumber !== newProgressBlockNumber {
942
- Prometheus.ProgressBlockNumber.set(
943
- ~blockNumber=newProgressBlockNumber,
944
- ~chainId=chain->ChainMap.Chain.toChainId,
945
- )
946
- }
947
- if cf.numEventsProcessed !== newTotalEventsProcessed {
948
- Prometheus.ProgressEventsCount.set(
949
- ~processedCount=newTotalEventsProcessed,
950
- ~chainId=chain->ChainMap.Chain.toChainId,
951
- )
952
- }
953
-
954
- {
955
- ...cf,
956
- reorgDetection: chain == reorgChain
957
- ? cf.reorgDetection->ReorgDetection.rollbackToValidBlockNumber(
958
- ~blockNumber=rollbackTargetBlockNumber,
959
- )
960
- : cf.reorgDetection,
961
- safeCheckpointTracking: switch cf.safeCheckpointTracking {
962
- | Some(safeCheckpointTracking) =>
963
- Some(
964
- safeCheckpointTracking->SafeCheckpointTracking.rollback(
965
- ~targetBlockNumber=newProgressBlockNumber,
966
- ),
967
- )
968
- | None => None
969
- },
970
- fetchState,
971
- committedProgressBlockNumber: newProgressBlockNumber,
972
- numEventsProcessed: newTotalEventsProcessed,
973
- }
974
-
975
- | None =>
976
- // Even without a progress diff entry, the reorg chain must have its
977
- // reorgDetection and fetchState rolled back. Otherwise the stale block hash
978
- // stays in dataByBlockNumber and the same reorg is re-detected on the next
979
- // fetch, causing an infinite reorg→rollback loop.
980
- if chain == reorgChain {
981
- {
982
- ...cf,
983
- reorgDetection: cf.reorgDetection->ReorgDetection.rollbackToValidBlockNumber(
984
- ~blockNumber=rollbackTargetBlockNumber,
985
- ),
986
- fetchState: cf.fetchState->FetchState.rollback(
987
- ~targetBlockNumber=rollbackTargetBlockNumber,
988
- ),
989
- committedProgressBlockNumber: Pervasives.min(
990
- cf.committedProgressBlockNumber,
991
- rollbackTargetBlockNumber,
992
- ),
993
- }
994
- } else {
995
- cf
996
- }
997
- }
998
- })
999
-
1000
- let diff = await state.ctx.inMemoryStore->InMemoryStore.prepareRollbackDiff(
1001
- ~persistence=state.ctx.persistence,
1002
- ~rollbackTargetCheckpointId,
1003
- ~rollbackDiffCheckpointId=state.ctx.inMemoryStore.committedCheckpointId->BigInt.add(1n),
1004
- ~progressBlockNumberByChainId=newProgressBlockNumberPerChain,
1005
- )
1006
-
1007
- let chainManager = {
1008
- ...state.chainManager,
1009
- chainFetchers,
1010
- }
1011
-
1012
- logger->Logging.childTrace({
1013
- "msg": "Finished rollback on reorg",
1014
- "entityChanges": {
1015
- "deleted": diff["deletedEntities"],
1016
- "upserted": diff["setEntities"],
1017
- },
1018
- "rollbackedEvents": rollbackedProcessedEvents.contents,
1019
- "beforeCheckpointId": state.ctx.inMemoryStore.committedCheckpointId,
1020
- "targetCheckpointId": rollbackTargetCheckpointId,
1021
- })
1022
- Prometheus.RollbackSuccess.increment(
1023
- ~timeSeconds=Hrtime.timeSince(startTime)->Hrtime.toSecondsFloat,
1024
- ~rollbackedProcessedEvents=rollbackedProcessedEvents.contents,
1025
- )
1026
-
1027
- dispatchAction(
1028
- SetRollbackState({
1029
- rollbackedChainManager: chainManager,
1030
- eventsProcessedDiffByChain,
1031
- }),
1032
- )
1033
- }
1034
- }
1035
- }
1036
-
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 = (
1044
- ~waitForNewBlock=SourceManager.waitForNewBlock,
1045
- ~executeQuery=SourceManager.executeQuery,
1046
- ~getLastKnownValidBlock=(chainFetcher, ~reorgBlockNumber, ~isRealtime) =>
1047
- chainFetcher->ChainFetcher.getLastKnownValidBlock(~reorgBlockNumber, ~isRealtime),
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
- }