envio 3.0.0-alpha.0 → 3.0.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 (136) hide show
  1. package/README.md +2 -2
  2. package/evm.schema.json +53 -57
  3. package/fuel.schema.json +35 -31
  4. package/index.d.ts +446 -1
  5. package/index.js +4 -0
  6. package/package.json +23 -11
  7. package/rescript.json +4 -1
  8. package/src/Batch.res.mjs +1 -1
  9. package/src/Benchmark.res +394 -0
  10. package/src/Benchmark.res.mjs +398 -0
  11. package/src/ChainFetcher.res +526 -0
  12. package/src/ChainFetcher.res.mjs +343 -0
  13. package/src/ChainManager.res +182 -0
  14. package/src/ChainManager.res.mjs +150 -0
  15. package/src/Config.res +367 -27
  16. package/src/Config.res.mjs +338 -28
  17. package/src/{Indexer.res → Ctx.res} +1 -1
  18. package/src/Ecosystem.res +25 -0
  19. package/src/Ecosystem.res.mjs +29 -0
  20. package/src/Env.res +252 -0
  21. package/src/Env.res.mjs +271 -0
  22. package/src/Envio.gen.ts +9 -1
  23. package/src/Envio.res +12 -9
  24. package/src/EventProcessing.res +476 -0
  25. package/src/EventProcessing.res.mjs +341 -0
  26. package/src/EventRegister.res +4 -15
  27. package/src/EventRegister.res.mjs +3 -9
  28. package/src/EventRegister.resi +2 -8
  29. package/src/FetchState.res +54 -29
  30. package/src/FetchState.res.mjs +62 -35
  31. package/src/GlobalState.res +1163 -0
  32. package/src/GlobalState.res.mjs +1196 -0
  33. package/src/GlobalStateManager.res +68 -0
  34. package/src/GlobalStateManager.res.mjs +75 -0
  35. package/src/GlobalStateManager.resi +7 -0
  36. package/src/HandlerLoader.res +89 -0
  37. package/src/HandlerLoader.res.mjs +79 -0
  38. package/src/Internal.gen.ts +3 -14
  39. package/src/Internal.res +45 -13
  40. package/src/LoadLayer.res +444 -0
  41. package/src/LoadLayer.res.mjs +296 -0
  42. package/src/LoadLayer.resi +32 -0
  43. package/src/LogSelection.res +33 -27
  44. package/src/LogSelection.res.mjs +6 -0
  45. package/src/Logging.res +11 -4
  46. package/src/Logging.res.mjs +14 -6
  47. package/src/Main.res +351 -0
  48. package/src/Main.res.mjs +312 -0
  49. package/src/Persistence.res +1 -2
  50. package/src/PgStorage.gen.ts +10 -0
  51. package/src/PgStorage.res +28 -34
  52. package/src/PgStorage.res.d.mts +5 -0
  53. package/src/PgStorage.res.mjs +27 -27
  54. package/src/Prometheus.res +8 -8
  55. package/src/Prometheus.res.mjs +10 -10
  56. package/src/ReorgDetection.res +6 -10
  57. package/src/ReorgDetection.res.mjs +6 -6
  58. package/src/TestIndexer.res +536 -0
  59. package/src/TestIndexer.res.mjs +412 -0
  60. package/src/TestIndexerProxyStorage.res +210 -0
  61. package/src/TestIndexerProxyStorage.res.mjs +157 -0
  62. package/src/Types.ts +1 -1
  63. package/src/UserContext.res +355 -0
  64. package/src/UserContext.res.mjs +236 -0
  65. package/src/Utils.res +28 -0
  66. package/src/Utils.res.mjs +18 -0
  67. package/src/bindings/ClickHouse.res +31 -1
  68. package/src/bindings/ClickHouse.res.mjs +27 -1
  69. package/src/bindings/DateFns.res +71 -0
  70. package/src/bindings/DateFns.res.mjs +22 -0
  71. package/src/bindings/Ethers.res +27 -67
  72. package/src/bindings/Ethers.res.mjs +18 -70
  73. package/src/bindings/EventSource.res +13 -0
  74. package/src/bindings/EventSource.res.mjs +2 -0
  75. package/src/bindings/NodeJs.res +44 -3
  76. package/src/bindings/NodeJs.res.mjs +11 -3
  77. package/src/bindings/Pino.res +21 -7
  78. package/src/bindings/Pino.res.mjs +11 -5
  79. package/src/bindings/Postgres.gen.ts +8 -0
  80. package/src/bindings/Postgres.res +3 -0
  81. package/src/bindings/Postgres.res.d.mts +5 -0
  82. package/src/bindings/RescriptMocha.res +123 -0
  83. package/src/bindings/RescriptMocha.res.mjs +18 -0
  84. package/src/bindings/Vitest.res +134 -0
  85. package/src/bindings/Vitest.res.mjs +9 -0
  86. package/src/bindings/Yargs.res +8 -0
  87. package/src/bindings/Yargs.res.mjs +2 -0
  88. package/src/db/InternalTable.res +2 -0
  89. package/src/db/InternalTable.res.mjs +1 -1
  90. package/src/sources/Evm.res +87 -0
  91. package/src/sources/Evm.res.mjs +105 -0
  92. package/src/sources/EvmChain.res +90 -0
  93. package/src/sources/EvmChain.res.mjs +57 -0
  94. package/src/sources/Fuel.res +19 -34
  95. package/src/sources/Fuel.res.mjs +34 -16
  96. package/src/sources/FuelSDK.res +38 -0
  97. package/src/sources/FuelSDK.res.mjs +29 -0
  98. package/src/sources/HyperFuel.res +2 -2
  99. package/src/sources/HyperFuel.resi +1 -1
  100. package/src/sources/HyperFuelClient.res +2 -2
  101. package/src/sources/HyperFuelSource.res +8 -8
  102. package/src/sources/HyperFuelSource.res.mjs +5 -5
  103. package/src/sources/HyperSyncHeightStream.res +97 -0
  104. package/src/sources/HyperSyncHeightStream.res.mjs +94 -0
  105. package/src/sources/HyperSyncSource.res +64 -172
  106. package/src/sources/HyperSyncSource.res.mjs +73 -155
  107. package/src/sources/Rpc.res +43 -0
  108. package/src/sources/Rpc.res.mjs +31 -0
  109. package/src/sources/RpcSource.res +32 -130
  110. package/src/sources/RpcSource.res.mjs +47 -121
  111. package/src/sources/Source.res +3 -2
  112. package/src/sources/SourceManager.res +183 -108
  113. package/src/sources/SourceManager.res.mjs +162 -99
  114. package/src/sources/SourceManager.resi +4 -5
  115. package/src/sources/Svm.res +59 -0
  116. package/src/sources/Svm.res.mjs +79 -0
  117. package/src/tui/Tui.res +266 -0
  118. package/src/tui/Tui.res.mjs +342 -0
  119. package/src/tui/bindings/Ink.res +376 -0
  120. package/src/tui/bindings/Ink.res.mjs +75 -0
  121. package/src/tui/bindings/Style.res +123 -0
  122. package/src/tui/bindings/Style.res.mjs +2 -0
  123. package/src/tui/components/BufferedProgressBar.res +40 -0
  124. package/src/tui/components/BufferedProgressBar.res.mjs +57 -0
  125. package/src/tui/components/CustomHooks.res +122 -0
  126. package/src/tui/components/CustomHooks.res.mjs +179 -0
  127. package/src/tui/components/Messages.res +41 -0
  128. package/src/tui/components/Messages.res.mjs +75 -0
  129. package/src/tui/components/SyncETA.res +193 -0
  130. package/src/tui/components/SyncETA.res.mjs +269 -0
  131. package/src/tui/components/TuiData.res +46 -0
  132. package/src/tui/components/TuiData.res.mjs +29 -0
  133. package/src/Platform.res +0 -140
  134. package/src/Platform.res.mjs +0 -170
  135. package/src/bindings/Ethers.gen.ts +0 -14
  136. /package/src/{Indexer.res.mjs → Ctx.res.mjs} +0 -0
@@ -0,0 +1,1163 @@
1
+ open Belt
2
+
3
+ type chain = ChainMap.Chain.t
4
+ type rollbackState =
5
+ | NoRollback
6
+ | ReorgDetected({chain: chain, blockNumber: int})
7
+ | FindingReorgDepth
8
+ | FoundReorgDepth({chain: chain, rollbackTargetBlockNumber: int})
9
+ | RollbackReady({diffInMemoryStore: InMemoryStore.t, eventsProcessedDiffByChain: dict<int>})
10
+
11
+ module WriteThrottlers = {
12
+ type t = {
13
+ chainMetaData: Throttler.t,
14
+ pruneStaleEntityHistory: Throttler.t,
15
+ }
16
+ let make = (): t => {
17
+ let chainMetaData = {
18
+ let intervalMillis = Env.ThrottleWrites.chainMetadataIntervalMillis
19
+ let logger = Logging.createChild(
20
+ ~params={
21
+ "context": "Throttler for chain metadata writes",
22
+ "intervalMillis": intervalMillis,
23
+ },
24
+ )
25
+ Throttler.make(~intervalMillis, ~logger)
26
+ }
27
+
28
+ let pruneStaleEntityHistory = {
29
+ let intervalMillis = Env.ThrottleWrites.pruneStaleDataIntervalMillis
30
+ let logger = Logging.createChild(
31
+ ~params={
32
+ "context": "Throttler for pruning stale entity history data",
33
+ "intervalMillis": intervalMillis,
34
+ },
35
+ )
36
+ Throttler.make(~intervalMillis, ~logger)
37
+ }
38
+ {chainMetaData, pruneStaleEntityHistory}
39
+ }
40
+ }
41
+
42
+ type t = {
43
+ ctx: Ctx.t,
44
+ chainManager: ChainManager.t,
45
+ processedBatches: int,
46
+ currentlyProcessingBatch: bool,
47
+ rollbackState: rollbackState,
48
+ indexerStartTime: Js.Date.t,
49
+ writeThrottlers: WriteThrottlers.t,
50
+ loadManager: LoadManager.t,
51
+ keepProcessAlive: bool,
52
+ //Initialized as 0, increments, when rollbacks occur to invalidate
53
+ //responses based on the wrong stateId
54
+ id: int,
55
+ }
56
+
57
+ let make = (
58
+ ~ctx: Ctx.t,
59
+ ~chainManager: ChainManager.t,
60
+ ~isDevelopmentMode=false,
61
+ ~shouldUseTui=false,
62
+ ) => {
63
+ {
64
+ ctx,
65
+ currentlyProcessingBatch: false,
66
+ processedBatches: 0,
67
+ chainManager,
68
+ indexerStartTime: Js.Date.make(),
69
+ rollbackState: NoRollback,
70
+ writeThrottlers: WriteThrottlers.make(),
71
+ loadManager: LoadManager.make(),
72
+ keepProcessAlive: isDevelopmentMode || shouldUseTui,
73
+ id: 0,
74
+ }
75
+ }
76
+
77
+ let getId = self => self.id
78
+ let incrementId = self => {...self, id: self.id + 1}
79
+ let setChainManager = (self, chainManager) => {
80
+ ...self,
81
+ chainManager,
82
+ }
83
+
84
+ let isPreparingRollback = state =>
85
+ switch state.rollbackState {
86
+ | NoRollback
87
+ | // We already updated fetch states here
88
+ // so we treat it as not rolling back
89
+ RollbackReady(_) => false
90
+ | FindingReorgDepth
91
+ | ReorgDetected(_)
92
+ | FoundReorgDepth(_) => true
93
+ }
94
+
95
+ type partitionQueryResponse = {
96
+ chain: chain,
97
+ response: Source.blockRangeFetchResponse,
98
+ query: FetchState.query,
99
+ }
100
+
101
+ type shouldExit = ExitWithSuccess | NoExit
102
+
103
+ // Need to dispatch an action for every async operation
104
+ // to get access to the latest state.
105
+ type action =
106
+ // After a response is received, we validate it with the new state
107
+ // if there's no reorg to continue processing the response.
108
+ | ValidatePartitionQueryResponse(partitionQueryResponse)
109
+ // This should be a separate action from ValidatePartitionQueryResponse
110
+ // because when processing the response, there might be an async contract registration.
111
+ // So after it's finished we dispatch the submit action to get the latest fetch state.
112
+ | SubmitPartitionQueryResponse({
113
+ newItems: array<Internal.item>,
114
+ newItemsWithDcs: array<Internal.item>,
115
+ knownHeight: int,
116
+ latestFetchedBlock: FetchState.blockNumberAndTimestamp,
117
+ query: FetchState.query,
118
+ chain: chain,
119
+ })
120
+ | FinishWaitingForNewBlock({chain: chain, knownHeight: int})
121
+ | EventBatchProcessed({batch: Batch.t})
122
+ | StartProcessingBatch
123
+ | StartFindingReorgDepth
124
+ | FindReorgDepth({chain: chain, rollbackTargetBlockNumber: int})
125
+ | EnterReorgThreshold
126
+ | UpdateQueues({
127
+ progressedChainsById: dict<Batch.chainAfterBatch>,
128
+ // Needed to prevent overwriting the blockLag
129
+ // set by EnterReorgThreshold
130
+ shouldEnterReorgThreshold: bool,
131
+ })
132
+ | SuccessExit
133
+ | ErrorExit(ErrorHandling.t)
134
+ | SetRollbackState({
135
+ diffInMemoryStore: InMemoryStore.t,
136
+ rollbackedChainManager: ChainManager.t,
137
+ eventsProcessedDiffByChain: dict<int>,
138
+ })
139
+
140
+ type queryChain = CheckAllChains | Chain(chain)
141
+ type task =
142
+ | NextQuery(queryChain)
143
+ | ProcessPartitionQueryResponse(partitionQueryResponse)
144
+ | ProcessEventBatch
145
+ | UpdateChainMetaDataAndCheckForExit(shouldExit)
146
+ | Rollback
147
+ | PruneStaleEntityHistory
148
+
149
+ let updateChainMetadataTable = (
150
+ cm: ChainManager.t,
151
+ ~persistence: Persistence.t,
152
+ ~throttler: Throttler.t,
153
+ ) => {
154
+ let chainsData: dict<InternalTable.Chains.metaFields> = Js.Dict.empty()
155
+
156
+ cm.chainFetchers
157
+ ->ChainMap.values
158
+ ->Belt.Array.forEach(cf => {
159
+ chainsData->Js.Dict.set(
160
+ cf.chainConfig.id->Belt.Int.toString,
161
+ {
162
+ blockHeight: cf.fetchState.knownHeight,
163
+ firstEventBlockNumber: cf.firstEventBlockNumber->Js.Null.fromOption,
164
+ isHyperSync: (cf.sourceManager->SourceManager.getActiveSource).poweredByHyperSync,
165
+ latestFetchedBlockNumber: cf.fetchState->FetchState.bufferBlockNumber,
166
+ timestampCaughtUpToHeadOrEndblock: cf.timestampCaughtUpToHeadOrEndblock->Js.Null.fromOption,
167
+ numBatchesFetched: cf.numBatchesFetched,
168
+ },
169
+ )
170
+ })
171
+
172
+ //Don't await this set, it can happen in its own time
173
+ throttler->Throttler.schedule(() =>
174
+ persistence.storage.setChainMeta(chainsData)->Promise.ignoreValue
175
+ )
176
+ }
177
+
178
+ /**
179
+ Takes in a chain manager and sets all chains timestamp caught up to head
180
+ when valid state lines up and returns an updated chain manager
181
+ */
182
+ let updateProgressedChains = (chainManager: ChainManager.t, ~batch: Batch.t, ~ctx: Ctx.t) => {
183
+ Prometheus.ProgressBatchCount.increment()
184
+
185
+ let nextQueueItemIsNone = chainManager->ChainManager.nextItemIsNone
186
+
187
+ let allChainsAtHead = chainManager->ChainManager.isProgressAtHead
188
+ //Update the timestampCaughtUpToHeadOrEndblock values
189
+ let chainFetchers = chainManager.chainFetchers->ChainMap.map(cf => {
190
+ let chain = ChainMap.Chain.makeUnsafe(~chainId=cf.chainConfig.id)
191
+
192
+ let maybeChainAfterBatch =
193
+ batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
194
+ chain->ChainMap.Chain.toChainId,
195
+ )
196
+
197
+ let cf = switch maybeChainAfterBatch {
198
+ | Some(chainAfterBatch) => {
199
+ if cf.committedProgressBlockNumber !== chainAfterBatch.progressBlockNumber {
200
+ Prometheus.ProgressBlockNumber.set(
201
+ ~blockNumber=chainAfterBatch.progressBlockNumber,
202
+ ~chainId=chain->ChainMap.Chain.toChainId,
203
+ )
204
+ }
205
+ if cf.numEventsProcessed !== chainAfterBatch.totalEventsProcessed {
206
+ Prometheus.ProgressEventsCount.set(
207
+ ~processedCount=chainAfterBatch.totalEventsProcessed,
208
+ ~chainId=chain->ChainMap.Chain.toChainId,
209
+ )
210
+ }
211
+
212
+ // Calculate and set latency metrics
213
+ switch batch->Batch.findLastEventItem(~chainId=chain->ChainMap.Chain.toChainId) {
214
+ | Some(eventItem) => {
215
+ let blockTimestamp = eventItem.event.block->ctx.config.ecosystem.getTimestamp
216
+ let currentTimeMs = Js.Date.now()->Float.toInt
217
+ let blockTimestampMs = blockTimestamp * 1000
218
+ let latencyMs = currentTimeMs - blockTimestampMs
219
+
220
+ Prometheus.ProgressLatency.set(~latencyMs, ~chainId=chain->ChainMap.Chain.toChainId)
221
+ }
222
+ | None => ()
223
+ }
224
+
225
+ {
226
+ ...cf,
227
+ // Since we process per chain always in order,
228
+ // we need to calculate it once, by using the first item in a batch
229
+ firstEventBlockNumber: switch cf.firstEventBlockNumber {
230
+ | Some(_) => cf.firstEventBlockNumber
231
+ | None => batch->Batch.findFirstEventBlockNumber(~chainId=chain->ChainMap.Chain.toChainId)
232
+ },
233
+ committedProgressBlockNumber: chainAfterBatch.progressBlockNumber,
234
+ numEventsProcessed: chainAfterBatch.totalEventsProcessed,
235
+ isProgressAtHead: cf.isProgressAtHead || chainAfterBatch.isProgressAtHeadWhenBatchCreated,
236
+ safeCheckpointTracking: switch cf.safeCheckpointTracking {
237
+ | Some(safeCheckpointTracking) =>
238
+ Some(
239
+ safeCheckpointTracking->SafeCheckpointTracking.updateOnNewBatch(
240
+ ~sourceBlockNumber=cf.fetchState.knownHeight,
241
+ ~chainId=chain->ChainMap.Chain.toChainId,
242
+ ~batchCheckpointIds=batch.checkpointIds,
243
+ ~batchCheckpointBlockNumbers=batch.checkpointBlockNumbers,
244
+ ~batchCheckpointChainIds=batch.checkpointChainIds,
245
+ ),
246
+ )
247
+ | None => None
248
+ },
249
+ }
250
+ }
251
+ | None => cf
252
+ }
253
+
254
+ /* strategy for TUI synced status:
255
+ * 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
256
+ *
257
+ * 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
258
+ *
259
+ * New conditions for valid synced:
260
+ *
261
+ * CASE 1 (chains are being synchronised at the head)
262
+ *
263
+ * All chain fetchers are fetching at the head AND
264
+ * No events that can be processed on the queue (even if events still exist on the individual queues)
265
+ * CASE 2 (chain finishes earlier than any other chain)
266
+ *
267
+ * CASE 3 endblock has been reached and latest processed block is greater than or equal to endblock (both fields must be Some)
268
+ *
269
+ * The given chain fetcher is fetching at the head or latest processed block >= endblock
270
+ * The given chain has processed all events on the queue
271
+ * see https://github.com/Float-Capital/indexer/pull/1388 */
272
+ if cf->ChainFetcher.hasProcessedToEndblock {
273
+ // in the case this is already set, don't reset and instead propagate the existing value
274
+ let timestampCaughtUpToHeadOrEndblock =
275
+ cf->ChainFetcher.isLive ? cf.timestampCaughtUpToHeadOrEndblock : Js.Date.make()->Some
276
+ {
277
+ ...cf,
278
+ timestampCaughtUpToHeadOrEndblock,
279
+ }
280
+ } else if !(cf->ChainFetcher.isLive) && cf.isProgressAtHead {
281
+ //Only calculate and set timestampCaughtUpToHeadOrEndblock if chain fetcher is at the head and
282
+ //its not already set
283
+ //CASE1
284
+ //All chains are caught up to head chainManager queue returns None
285
+ //Meaning we are busy synchronizing chains at the head
286
+ if nextQueueItemIsNone && allChainsAtHead {
287
+ {
288
+ ...cf,
289
+ timestampCaughtUpToHeadOrEndblock: Js.Date.make()->Some,
290
+ }
291
+ } else {
292
+ //CASE2 -> Only calculate if case1 fails
293
+ //All events have been processed on the chain fetchers queue
294
+ //Other chains may be busy syncing
295
+ let hasNoMoreEventsToProcess = cf->ChainFetcher.hasNoMoreEventsToProcess
296
+
297
+ if hasNoMoreEventsToProcess {
298
+ {
299
+ ...cf,
300
+ timestampCaughtUpToHeadOrEndblock: Js.Date.make()->Some,
301
+ }
302
+ } else {
303
+ //Default to just returning cf
304
+ cf
305
+ }
306
+ }
307
+ } else {
308
+ //Default to just returning cf
309
+ cf
310
+ }
311
+ })
312
+
313
+ let allChainsSyncedAtHead =
314
+ chainFetchers
315
+ ->ChainMap.values
316
+ ->Array.every(cf => cf->ChainFetcher.isLive)
317
+
318
+ if allChainsSyncedAtHead {
319
+ Prometheus.setAllChainsSyncedToHead()
320
+ }
321
+
322
+ {
323
+ ...chainManager,
324
+ committedCheckpointId: switch batch.checkpointIds->Utils.Array.last {
325
+ | Some(checkpointId) => checkpointId
326
+ | None => chainManager.committedCheckpointId
327
+ },
328
+ chainFetchers,
329
+ }
330
+ }
331
+
332
+ let validatePartitionQueryResponse = (
333
+ state,
334
+ {chain, response, query} as partitionQueryResponse: partitionQueryResponse,
335
+ ) => {
336
+ let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
337
+ let {
338
+ parsedQueueItems,
339
+ latestFetchedBlockNumber,
340
+ stats,
341
+ knownHeight,
342
+ reorgGuard,
343
+ fromBlockQueried,
344
+ } = response
345
+
346
+ if knownHeight > chainFetcher.fetchState.knownHeight {
347
+ Prometheus.SourceHeight.set(
348
+ ~blockNumber=knownHeight,
349
+ ~chainId=chainFetcher.chainConfig.id,
350
+ // The knownHeight from response won't necessarily
351
+ // belong to the currently active source.
352
+ // But for simplicity, assume it does.
353
+ ~sourceName=(chainFetcher.sourceManager->SourceManager.getActiveSource).name,
354
+ )
355
+ }
356
+
357
+ if Env.Benchmark.shouldSaveData {
358
+ Benchmark.addBlockRangeFetched(
359
+ ~totalTimeElapsed=stats.totalTimeElapsed,
360
+ ~parsingTimeElapsed=stats.parsingTimeElapsed->Belt.Option.getWithDefault(0),
361
+ ~pageFetchTime=stats.pageFetchTime->Belt.Option.getWithDefault(0),
362
+ ~chainId=chain->ChainMap.Chain.toChainId,
363
+ ~fromBlock=fromBlockQueried,
364
+ ~toBlock=latestFetchedBlockNumber,
365
+ ~numEvents=parsedQueueItems->Array.length,
366
+ ~numAddresses=query.addressesByContractName->FetchState.addressesByContractNameCount,
367
+ ~queryName=switch query {
368
+ | {target: Merge(_)} => `Merge Query`
369
+ | {selection: {dependsOnAddresses: false}} => `Wildcard Query`
370
+ | {selection: {dependsOnAddresses: true}} => `Normal Query`
371
+ },
372
+ )
373
+ }
374
+
375
+ let (updatedReorgDetection, reorgResult: ReorgDetection.reorgResult) =
376
+ chainFetcher.reorgDetection->ReorgDetection.registerReorgGuard(~reorgGuard, ~knownHeight)
377
+
378
+ let updatedChainFetcher = {
379
+ ...chainFetcher,
380
+ reorgDetection: updatedReorgDetection,
381
+ }
382
+
383
+ let nextState = {
384
+ ...state,
385
+ chainManager: {
386
+ ...state.chainManager,
387
+ chainFetchers: state.chainManager.chainFetchers->ChainMap.set(chain, updatedChainFetcher),
388
+ },
389
+ }
390
+
391
+ let rollbackWithReorgDetectedBlockNumber = switch reorgResult {
392
+ | ReorgDetected(reorgDetected) => {
393
+ chainFetcher.logger->Logging.childInfo(
394
+ reorgDetected->ReorgDetection.reorgDetectedToLogParams(
395
+ ~shouldRollbackOnReorg=state.ctx.config.shouldRollbackOnReorg,
396
+ ),
397
+ )
398
+ Prometheus.ReorgCount.increment(~chain)
399
+ Prometheus.ReorgDetectionBlockNumber.set(
400
+ ~blockNumber=reorgDetected.scannedBlock.blockNumber,
401
+ ~chain,
402
+ )
403
+ if state.ctx.config.shouldRollbackOnReorg {
404
+ Some(reorgDetected.scannedBlock.blockNumber)
405
+ } else {
406
+ None
407
+ }
408
+ }
409
+ | NoReorg => None
410
+ }
411
+
412
+ switch rollbackWithReorgDetectedBlockNumber {
413
+ | None => (nextState, [ProcessPartitionQueryResponse(partitionQueryResponse)])
414
+ | Some(reorgDetectedBlockNumber) => {
415
+ let chainManager = switch state.rollbackState {
416
+ | RollbackReady({eventsProcessedDiffByChain}) => {
417
+ ...state.chainManager,
418
+ chainFetchers: state.chainManager.chainFetchers->ChainMap.update(chain, chainFetcher => {
419
+ switch eventsProcessedDiffByChain->Utils.Dict.dangerouslyGetByIntNonOption(
420
+ chain->ChainMap.Chain.toChainId,
421
+ ) {
422
+ | Some(eventsProcessedDiff) => {
423
+ ...chainFetcher,
424
+ // Since we detected a reorg, until rollback wasn't completed in the db
425
+ // We return the events processed counter to the pre-rollback value,
426
+ // to decrease it once more for the new rollback.
427
+ numEventsProcessed: chainFetcher.numEventsProcessed + eventsProcessedDiff,
428
+ }
429
+ | None => chainFetcher
430
+ }
431
+ }),
432
+ }
433
+ | _ => state.chainManager
434
+ }
435
+ (
436
+ {
437
+ ...nextState->incrementId,
438
+ chainManager,
439
+ rollbackState: ReorgDetected({
440
+ chain,
441
+ blockNumber: reorgDetectedBlockNumber,
442
+ }),
443
+ },
444
+ [Rollback],
445
+ )
446
+ }
447
+ }
448
+ }
449
+
450
+ let submitPartitionQueryResponse = (
451
+ state,
452
+ ~newItems,
453
+ ~newItemsWithDcs,
454
+ ~knownHeight,
455
+ ~latestFetchedBlock,
456
+ ~query,
457
+ ~chain,
458
+ ) => {
459
+ let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
460
+
461
+ let updatedChainFetcher =
462
+ chainFetcher
463
+ ->ChainFetcher.handleQueryResult(
464
+ ~query,
465
+ ~latestFetchedBlock,
466
+ ~newItems,
467
+ ~newItemsWithDcs,
468
+ ~knownHeight,
469
+ )
470
+ ->Utils.unwrapResultExn
471
+
472
+ let updatedChainFetcher = {
473
+ ...updatedChainFetcher,
474
+ numBatchesFetched: updatedChainFetcher.numBatchesFetched + 1,
475
+ }
476
+
477
+ if !chainFetcher.isProgressAtHead && updatedChainFetcher.isProgressAtHead {
478
+ updatedChainFetcher.logger->Logging.childInfo("All events have been fetched")
479
+ }
480
+
481
+ let nextState = {
482
+ ...state,
483
+ chainManager: {
484
+ ...state.chainManager,
485
+ chainFetchers: state.chainManager.chainFetchers->ChainMap.set(chain, updatedChainFetcher),
486
+ },
487
+ }
488
+
489
+ (
490
+ nextState,
491
+ [UpdateChainMetaDataAndCheckForExit(NoExit), ProcessEventBatch, NextQuery(Chain(chain))],
492
+ )
493
+ }
494
+
495
+ let processPartitionQueryResponse = async (
496
+ state,
497
+ {chain, response, query}: partitionQueryResponse,
498
+ ~dispatchAction,
499
+ ) => {
500
+ let {
501
+ parsedQueueItems,
502
+ latestFetchedBlockNumber,
503
+ knownHeight,
504
+ latestFetchedBlockTimestamp,
505
+ } = response
506
+
507
+ let itemsWithContractRegister = []
508
+ let newItems = []
509
+
510
+ for idx in 0 to parsedQueueItems->Array.length - 1 {
511
+ let item = parsedQueueItems->Array.getUnsafe(idx)
512
+ let eventItem = item->Internal.castUnsafeEventItem
513
+ if eventItem.eventConfig.contractRegister !== None {
514
+ itemsWithContractRegister->Array.push(item)
515
+ }
516
+
517
+ // TODO: Don't really need to keep it in the queue
518
+ // when there's no handler (besides raw_events, processed counter, and dcsToStore consuming)
519
+ newItems->Array.push(item)
520
+ }
521
+
522
+ let newItemsWithDcs = switch itemsWithContractRegister {
523
+ | [] as empty => empty
524
+ | _ =>
525
+ await ChainFetcher.runContractRegistersOrThrow(
526
+ ~itemsWithContractRegister,
527
+ ~chain,
528
+ ~config=state.ctx.config,
529
+ )
530
+ }
531
+
532
+ dispatchAction(
533
+ SubmitPartitionQueryResponse({
534
+ newItems,
535
+ newItemsWithDcs,
536
+ knownHeight,
537
+ latestFetchedBlock: {
538
+ blockNumber: latestFetchedBlockNumber,
539
+ blockTimestamp: latestFetchedBlockTimestamp,
540
+ },
541
+ chain,
542
+ query,
543
+ }),
544
+ )
545
+ }
546
+
547
+ let updateChainFetcher = (chainFetcherUpdate, ~state, ~chain) => {
548
+ (
549
+ {
550
+ ...state,
551
+ chainManager: {
552
+ ...state.chainManager,
553
+ chainFetchers: state.chainManager.chainFetchers->ChainMap.update(chain, chainFetcherUpdate),
554
+ },
555
+ },
556
+ [],
557
+ )
558
+ }
559
+
560
+ let onEnterReorgThreshold = (~state: t) => {
561
+ Logging.info("Reorg threshold reached")
562
+ Prometheus.ReorgThreshold.set(~isInReorgThreshold=true)
563
+
564
+ let chainFetchers = state.chainManager.chainFetchers->ChainMap.map(chainFetcher => {
565
+ {
566
+ ...chainFetcher,
567
+ fetchState: chainFetcher.fetchState->FetchState.updateInternal(
568
+ ~blockLag=Env.indexingBlockLag->Option.getWithDefault(0),
569
+ ),
570
+ }
571
+ })
572
+
573
+ {
574
+ ...state,
575
+ chainManager: {
576
+ ...state.chainManager,
577
+ chainFetchers,
578
+ isInReorgThreshold: true,
579
+ },
580
+ }
581
+ }
582
+
583
+ let actionReducer = (state: t, action: action) => {
584
+ switch action {
585
+ | FinishWaitingForNewBlock({chain, knownHeight}) => {
586
+ let updatedChainFetchers = state.chainManager.chainFetchers->ChainMap.update(
587
+ chain,
588
+ chainFetcher => {
589
+ let updatedFetchState =
590
+ chainFetcher.fetchState->FetchState.updateKnownHeight(~knownHeight)
591
+ if updatedFetchState !== chainFetcher.fetchState {
592
+ {
593
+ ...chainFetcher,
594
+ fetchState: updatedFetchState,
595
+ }
596
+ } else {
597
+ chainFetcher
598
+ }
599
+ },
600
+ )
601
+
602
+ let isBelowReorgThreshold =
603
+ !state.chainManager.isInReorgThreshold && state.ctx.config.shouldRollbackOnReorg
604
+ let shouldEnterReorgThreshold =
605
+ isBelowReorgThreshold &&
606
+ updatedChainFetchers
607
+ ->ChainMap.values
608
+ ->Array.every(chainFetcher => {
609
+ chainFetcher.fetchState->FetchState.isReadyToEnterReorgThreshold
610
+ })
611
+
612
+ let state = {
613
+ ...state,
614
+ chainManager: {
615
+ ...state.chainManager,
616
+ chainFetchers: updatedChainFetchers,
617
+ },
618
+ }
619
+
620
+ // Attempt ProcessEventBatch in case if we have block handlers to run
621
+ if shouldEnterReorgThreshold {
622
+ (onEnterReorgThreshold(~state), [NextQuery(CheckAllChains), ProcessEventBatch])
623
+ } else {
624
+ (state, [NextQuery(Chain(chain)), ProcessEventBatch])
625
+ }
626
+ }
627
+ | ValidatePartitionQueryResponse(partitionQueryResponse) =>
628
+ state->validatePartitionQueryResponse(partitionQueryResponse)
629
+ | SubmitPartitionQueryResponse({
630
+ newItems,
631
+ newItemsWithDcs,
632
+ knownHeight,
633
+ latestFetchedBlock,
634
+ query,
635
+ chain,
636
+ }) =>
637
+ state->submitPartitionQueryResponse(
638
+ ~newItems,
639
+ ~newItemsWithDcs,
640
+ ~knownHeight,
641
+ ~latestFetchedBlock,
642
+ ~query,
643
+ ~chain,
644
+ )
645
+ | EventBatchProcessed({batch}) =>
646
+ let maybePruneEntityHistory =
647
+ state.ctx.config->Config.shouldPruneHistory(
648
+ ~isInReorgThreshold=state.chainManager.isInReorgThreshold,
649
+ )
650
+ ? [PruneStaleEntityHistory]
651
+ : []
652
+
653
+ let state = {
654
+ ...state,
655
+ // Can safely reset rollback state, since overwrite is not possible.
656
+ // If rollback is pending, the EventBatchProcessed will be handled by the invalid action reducer instead.
657
+ rollbackState: NoRollback,
658
+ chainManager: state.chainManager->updateProgressedChains(~batch, ~ctx=state.ctx),
659
+ currentlyProcessingBatch: false,
660
+ processedBatches: state.processedBatches + 1,
661
+ }
662
+
663
+ let shouldExit = EventProcessing.allChainsEventsProcessedToEndblock(
664
+ state.chainManager.chainFetchers,
665
+ )
666
+ ? {
667
+ Logging.info("All chains are caught up to end blocks.")
668
+
669
+ // Keep the indexer process running when in development mode (for Dev Console)
670
+ // or when TUI is enabled (for display)
671
+ if state.keepProcessAlive {
672
+ NoExit
673
+ } else {
674
+ ExitWithSuccess
675
+ }
676
+ }
677
+ : NoExit
678
+
679
+ (
680
+ state,
681
+ [UpdateChainMetaDataAndCheckForExit(shouldExit), ProcessEventBatch]->Array.concat(
682
+ maybePruneEntityHistory,
683
+ ),
684
+ )
685
+
686
+ | StartProcessingBatch => ({...state, currentlyProcessingBatch: true}, [])
687
+ | StartFindingReorgDepth => ({...state, rollbackState: FindingReorgDepth}, [])
688
+ | FindReorgDepth({chain, rollbackTargetBlockNumber}) => (
689
+ {
690
+ ...state,
691
+ rollbackState: FoundReorgDepth({
692
+ chain,
693
+ rollbackTargetBlockNumber,
694
+ }),
695
+ },
696
+ [Rollback],
697
+ )
698
+ | EnterReorgThreshold => (onEnterReorgThreshold(~state), [NextQuery(CheckAllChains)])
699
+ | UpdateQueues({progressedChainsById, shouldEnterReorgThreshold}) =>
700
+ let chainFetchers = state.chainManager.chainFetchers->ChainMap.mapWithKey((chain, cf) => {
701
+ let fs = switch progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
702
+ chain->ChainMap.Chain.toChainId,
703
+ ) {
704
+ | Some(chainAfterBatch) => chainAfterBatch.fetchState
705
+ | None => cf.fetchState
706
+ }
707
+ {
708
+ ...cf,
709
+ fetchState: shouldEnterReorgThreshold
710
+ ? fs->FetchState.updateInternal(~blockLag=Env.indexingBlockLag->Option.getWithDefault(0))
711
+ : fs,
712
+ }
713
+ })
714
+
715
+ let chainManager = {
716
+ ...state.chainManager,
717
+ chainFetchers,
718
+ }
719
+
720
+ (
721
+ {
722
+ ...state,
723
+ chainManager,
724
+ },
725
+ [NextQuery(CheckAllChains)],
726
+ )
727
+ | SetRollbackState({diffInMemoryStore, rollbackedChainManager, eventsProcessedDiffByChain}) => (
728
+ {
729
+ ...state,
730
+ rollbackState: RollbackReady({
731
+ diffInMemoryStore,
732
+ eventsProcessedDiffByChain,
733
+ }),
734
+ chainManager: rollbackedChainManager,
735
+ },
736
+ [NextQuery(CheckAllChains), ProcessEventBatch],
737
+ )
738
+ | SuccessExit => {
739
+ Logging.info("Exiting with success")
740
+ NodeJs.process->NodeJs.exitWithCode(Success)
741
+ (state, [])
742
+ }
743
+ | ErrorExit(errHandler) =>
744
+ errHandler->ErrorHandling.log
745
+ NodeJs.process->NodeJs.exitWithCode(Failure)
746
+ (state, [])
747
+ }
748
+ }
749
+
750
+ let invalidatedActionReducer = (state: t, action: action) =>
751
+ switch action {
752
+ | EventBatchProcessed({batch}) if state->isPreparingRollback =>
753
+ Logging.info("Finished processing batch before rollback, actioning rollback")
754
+ (
755
+ {
756
+ ...state,
757
+ chainManager: state.chainManager->updateProgressedChains(~batch, ~ctx=state.ctx),
758
+ currentlyProcessingBatch: false,
759
+ processedBatches: state.processedBatches + 1,
760
+ },
761
+ [Rollback],
762
+ )
763
+ | ErrorExit(_) => actionReducer(state, action)
764
+ | _ =>
765
+ Logging.trace({
766
+ "msg": "Invalidated action discarded",
767
+ "action": action->S.convertOrThrow(Utils.Schema.variantTag),
768
+ })
769
+ (state, [])
770
+ }
771
+
772
+ let checkAndFetchForChain = (
773
+ //Used for dependency injection for tests
774
+ ~waitForNewBlock,
775
+ ~executeQuery,
776
+ //required args
777
+ ~state,
778
+ ~dispatchAction,
779
+ ) => async chain => {
780
+ let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
781
+ if !isPreparingRollback(state) {
782
+ let {fetchState} = chainFetcher
783
+
784
+ await chainFetcher.sourceManager->SourceManager.fetchNext(
785
+ ~fetchState,
786
+ ~waitForNewBlock=(~knownHeight) => chainFetcher.sourceManager->waitForNewBlock(~knownHeight),
787
+ ~onNewBlock=(~knownHeight) => dispatchAction(FinishWaitingForNewBlock({chain, knownHeight})),
788
+ ~executeQuery=async query => {
789
+ try {
790
+ let response =
791
+ await chainFetcher.sourceManager->executeQuery(
792
+ ~query,
793
+ ~knownHeight=fetchState.knownHeight,
794
+ )
795
+ dispatchAction(ValidatePartitionQueryResponse({chain, response, query}))
796
+ } catch {
797
+ | exn => dispatchAction(ErrorExit(exn->ErrorHandling.make))
798
+ }
799
+ },
800
+ ~stateId=state.id,
801
+ )
802
+ }
803
+ }
804
+
805
+ let injectedTaskReducer = (
806
+ //Used for dependency injection for tests
807
+ ~waitForNewBlock,
808
+ ~executeQuery,
809
+ ~getLastKnownValidBlock,
810
+ ) => async (
811
+ //required args
812
+ state: t,
813
+ task: task,
814
+ ~dispatchAction,
815
+ ) => {
816
+ switch task {
817
+ | ProcessPartitionQueryResponse(partitionQueryResponse) =>
818
+ state->processPartitionQueryResponse(partitionQueryResponse, ~dispatchAction)->Promise.done
819
+ | PruneStaleEntityHistory =>
820
+ let runPrune = async () => {
821
+ switch state.chainManager->ChainManager.getSafeCheckpointId {
822
+ | None => ()
823
+ | Some(safeCheckpointId) =>
824
+ await state.ctx.persistence.storage.pruneStaleCheckpoints(~safeCheckpointId)
825
+
826
+ for idx in 0 to state.ctx.persistence.allEntities->Array.length - 1 {
827
+ if idx !== 0 {
828
+ // Add some delay between entities
829
+ // To unblock the pg client if it's needed for something else
830
+ await Utils.delay(1000)
831
+ }
832
+ let entityConfig = state.ctx.persistence.allEntities->Array.getUnsafe(idx)
833
+ let timeRef = Hrtime.makeTimer()
834
+ try {
835
+ let () = await state.ctx.persistence.storage.pruneStaleEntityHistory(
836
+ ~entityName=entityConfig.name,
837
+ ~entityIndex=entityConfig.index,
838
+ ~safeCheckpointId,
839
+ )
840
+ } catch {
841
+ | exn =>
842
+ exn->ErrorHandling.mkLogAndRaise(
843
+ ~msg=`Failed to prune stale entity history`,
844
+ ~logger=Logging.createChild(
845
+ ~params={
846
+ "entityName": entityConfig.name,
847
+ "safeCheckpointId": safeCheckpointId,
848
+ },
849
+ ),
850
+ )
851
+ }
852
+ Prometheus.RollbackHistoryPrune.increment(
853
+ ~timeMillis=Hrtime.timeSince(timeRef)->Hrtime.toMillis,
854
+ ~entityName=entityConfig.name,
855
+ )
856
+ }
857
+ }
858
+ }
859
+ state.writeThrottlers.pruneStaleEntityHistory->Throttler.schedule(runPrune)
860
+
861
+ | UpdateChainMetaDataAndCheckForExit(shouldExit) =>
862
+ let {chainManager, writeThrottlers} = state
863
+ switch shouldExit {
864
+ | ExitWithSuccess =>
865
+ updateChainMetadataTable(
866
+ chainManager,
867
+ ~throttler=writeThrottlers.chainMetaData,
868
+ ~persistence=state.ctx.persistence,
869
+ )
870
+ dispatchAction(SuccessExit)
871
+ | NoExit =>
872
+ updateChainMetadataTable(
873
+ chainManager,
874
+ ~throttler=writeThrottlers.chainMetaData,
875
+ ~persistence=state.ctx.persistence,
876
+ )->ignore
877
+ }
878
+ | NextQuery(chainCheck) =>
879
+ let fetchForChain = checkAndFetchForChain(
880
+ ~waitForNewBlock,
881
+ ~executeQuery,
882
+ ~state,
883
+ ~dispatchAction,
884
+ )
885
+
886
+ switch chainCheck {
887
+ | Chain(chain) => await chain->fetchForChain
888
+ | CheckAllChains =>
889
+ //Mapping from the states chainManager so we can construct tests that don't use
890
+ //all chains
891
+ let _ =
892
+ await state.chainManager.chainFetchers
893
+ ->ChainMap.keys
894
+ ->Array.map(fetchForChain(_))
895
+ ->Promise.all
896
+ }
897
+ | ProcessEventBatch =>
898
+ if !state.currentlyProcessingBatch && !isPreparingRollback(state) {
899
+ //In the case of a rollback, use the provided in memory store
900
+ //With rolled back values
901
+ let rollbackInMemStore = switch state.rollbackState {
902
+ | RollbackReady({diffInMemoryStore}) => Some(diffInMemoryStore)
903
+ | _ => None
904
+ }
905
+
906
+ let batch =
907
+ state.chainManager->ChainManager.createBatch(
908
+ ~batchSizeTarget=state.ctx.config.batchSize,
909
+ ~isRollback=rollbackInMemStore !== None,
910
+ )
911
+
912
+ let progressedChainsById = batch.progressedChainsById
913
+ let totalBatchSize = batch.totalBatchSize
914
+
915
+ let isInReorgThreshold = state.chainManager.isInReorgThreshold
916
+ let shouldSaveHistory = state.ctx.config->Config.shouldSaveHistory(~isInReorgThreshold)
917
+
918
+ let isBelowReorgThreshold =
919
+ !state.chainManager.isInReorgThreshold && state.ctx.config.shouldRollbackOnReorg
920
+ let shouldEnterReorgThreshold =
921
+ isBelowReorgThreshold &&
922
+ state.chainManager.chainFetchers
923
+ ->ChainMap.values
924
+ ->Array.every(chainFetcher => {
925
+ let fetchState = switch progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
926
+ chainFetcher.fetchState.chainId,
927
+ ) {
928
+ | Some(chainAfterBatch) => chainAfterBatch.fetchState
929
+ | None => chainFetcher.fetchState
930
+ }
931
+ fetchState->FetchState.isReadyToEnterReorgThreshold
932
+ })
933
+
934
+ if shouldEnterReorgThreshold {
935
+ dispatchAction(EnterReorgThreshold)
936
+ }
937
+
938
+ if progressedChainsById->Utils.Dict.isEmpty {
939
+ ()
940
+ } else {
941
+ if Env.Benchmark.shouldSaveData {
942
+ let group = "Other"
943
+ Benchmark.addSummaryData(
944
+ ~group,
945
+ ~label=`Batch Size`,
946
+ ~value=totalBatchSize->Belt.Int.toFloat,
947
+ )
948
+ }
949
+
950
+ dispatchAction(StartProcessingBatch)
951
+ dispatchAction(UpdateQueues({progressedChainsById, shouldEnterReorgThreshold}))
952
+
953
+ let inMemoryStore =
954
+ rollbackInMemStore->Option.getWithDefault(
955
+ InMemoryStore.make(~entities=state.ctx.persistence.allEntities),
956
+ )
957
+
958
+ inMemoryStore->InMemoryStore.setBatchDcs(~batch, ~shouldSaveHistory)
959
+
960
+ switch await EventProcessing.processEventBatch(
961
+ ~batch,
962
+ ~inMemoryStore,
963
+ ~isInReorgThreshold,
964
+ ~loadManager=state.loadManager,
965
+ ~ctx=state.ctx,
966
+ ~chainFetchers=state.chainManager.chainFetchers,
967
+ ) {
968
+ | exception exn =>
969
+ //All casese should be handled/caught before this with better user messaging.
970
+ //This is just a safety in case something unexpected happens
971
+ let errHandler =
972
+ exn->ErrorHandling.make(~msg="A top level unexpected error occurred during processing")
973
+ dispatchAction(ErrorExit(errHandler))
974
+ | res =>
975
+ switch res {
976
+ | Ok() => dispatchAction(EventBatchProcessed({batch: batch}))
977
+ | Error(errHandler) => dispatchAction(ErrorExit(errHandler))
978
+ }
979
+ }
980
+ }
981
+ }
982
+ | Rollback =>
983
+ //If it isn't processing a batch currently continue with rollback otherwise wait for current batch to finish processing
984
+ switch state {
985
+ | {rollbackState: NoRollback | RollbackReady(_)} =>
986
+ Js.Exn.raiseError("Internal error: Rollback initiated with invalid state")
987
+ | {rollbackState: ReorgDetected({chain, blockNumber: reorgBlockNumber})} => {
988
+ let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(chain)
989
+
990
+ dispatchAction(StartFindingReorgDepth)
991
+ let rollbackTargetBlockNumber =
992
+ await chainFetcher->getLastKnownValidBlock(~reorgBlockNumber)
993
+
994
+ dispatchAction(FindReorgDepth({chain, rollbackTargetBlockNumber}))
995
+ }
996
+ // We can come to this case when event batch finished processing
997
+ // while we are still finding the reorg depth
998
+ // Do nothing here, just wait for reorg depth to be found
999
+ | {rollbackState: FindingReorgDepth} => ()
1000
+ | {rollbackState: FoundReorgDepth(_), currentlyProcessingBatch: true} =>
1001
+ Logging.info("Waiting for batch to finish processing before executing rollback")
1002
+ | {rollbackState: FoundReorgDepth({chain: reorgChain, rollbackTargetBlockNumber})} =>
1003
+ let startTime = Hrtime.makeTimer()
1004
+
1005
+ let chainFetcher = state.chainManager.chainFetchers->ChainMap.get(reorgChain)
1006
+
1007
+ let logger = Logging.createChildFrom(
1008
+ ~logger=chainFetcher.logger,
1009
+ ~params={
1010
+ "action": "Rollback",
1011
+ "reorgChain": reorgChain,
1012
+ "targetBlockNumber": rollbackTargetBlockNumber,
1013
+ },
1014
+ )
1015
+ logger->Logging.childInfo("Started rollback on reorg")
1016
+ Prometheus.RollbackTargetBlockNumber.set(
1017
+ ~blockNumber=rollbackTargetBlockNumber,
1018
+ ~chain=reorgChain,
1019
+ )
1020
+
1021
+ let reorgChainId = reorgChain->ChainMap.Chain.toChainId
1022
+
1023
+ let rollbackTargetCheckpointId = {
1024
+ switch await state.ctx.persistence.storage.getRollbackTargetCheckpoint(
1025
+ ~reorgChainId,
1026
+ ~lastKnownValidBlockNumber=rollbackTargetBlockNumber,
1027
+ ) {
1028
+ | [checkpoint] => checkpoint["id"]
1029
+ | _ => 0.
1030
+ }
1031
+ }
1032
+
1033
+ let eventsProcessedDiffByChain = Js.Dict.empty()
1034
+ let newProgressBlockNumberPerChain = Js.Dict.empty()
1035
+ let rollbackedProcessedEvents = ref(0)
1036
+
1037
+ {
1038
+ let rollbackProgressDiff = await state.ctx.persistence.storage.getRollbackProgressDiff(
1039
+ ~rollbackTargetCheckpointId,
1040
+ )
1041
+ for idx in 0 to rollbackProgressDiff->Js.Array2.length - 1 {
1042
+ let diff = rollbackProgressDiff->Js.Array2.unsafe_get(idx)
1043
+ eventsProcessedDiffByChain->Utils.Dict.setByInt(
1044
+ diff["chain_id"],
1045
+ switch diff["events_processed_diff"]->Int.fromString {
1046
+ | Some(eventsProcessedDiff) => {
1047
+ rollbackedProcessedEvents :=
1048
+ rollbackedProcessedEvents.contents + eventsProcessedDiff
1049
+ eventsProcessedDiff
1050
+ }
1051
+ | None =>
1052
+ Js.Exn.raiseError(
1053
+ `Unexpedted case: Invalid events processed diff ${diff["events_processed_diff"]}`,
1054
+ )
1055
+ },
1056
+ )
1057
+ newProgressBlockNumberPerChain->Utils.Dict.setByInt(
1058
+ diff["chain_id"],
1059
+ if rollbackTargetCheckpointId === 0. && diff["chain_id"] === reorgChainId {
1060
+ Pervasives.min(diff["new_progress_block_number"], rollbackTargetBlockNumber)
1061
+ } else {
1062
+ diff["new_progress_block_number"]
1063
+ },
1064
+ )
1065
+ }
1066
+ }
1067
+
1068
+ let chainFetchers = state.chainManager.chainFetchers->ChainMap.mapWithKey((chain, cf) => {
1069
+ switch newProgressBlockNumberPerChain->Utils.Dict.dangerouslyGetByIntNonOption(
1070
+ chain->ChainMap.Chain.toChainId,
1071
+ ) {
1072
+ | Some(newProgressBlockNumber) =>
1073
+ let fetchState =
1074
+ cf.fetchState->FetchState.rollback(~targetBlockNumber=newProgressBlockNumber)
1075
+ let newTotalEventsProcessed =
1076
+ cf.numEventsProcessed -
1077
+ eventsProcessedDiffByChain
1078
+ ->Utils.Dict.dangerouslyGetByIntNonOption(chain->ChainMap.Chain.toChainId)
1079
+ ->Option.getUnsafe
1080
+
1081
+ if cf.committedProgressBlockNumber !== newProgressBlockNumber {
1082
+ Prometheus.ProgressBlockNumber.set(
1083
+ ~blockNumber=newProgressBlockNumber,
1084
+ ~chainId=chain->ChainMap.Chain.toChainId,
1085
+ )
1086
+ }
1087
+ if cf.numEventsProcessed !== newTotalEventsProcessed {
1088
+ Prometheus.ProgressEventsCount.set(
1089
+ ~processedCount=newTotalEventsProcessed,
1090
+ ~chainId=chain->ChainMap.Chain.toChainId,
1091
+ )
1092
+ }
1093
+
1094
+ {
1095
+ ...cf,
1096
+ reorgDetection: chain == reorgChain
1097
+ ? cf.reorgDetection->ReorgDetection.rollbackToValidBlockNumber(
1098
+ ~blockNumber=rollbackTargetBlockNumber,
1099
+ )
1100
+ : cf.reorgDetection,
1101
+ safeCheckpointTracking: switch cf.safeCheckpointTracking {
1102
+ | Some(safeCheckpointTracking) =>
1103
+ Some(
1104
+ safeCheckpointTracking->SafeCheckpointTracking.rollback(
1105
+ ~targetBlockNumber=newProgressBlockNumber,
1106
+ ),
1107
+ )
1108
+ | None => None
1109
+ },
1110
+ fetchState,
1111
+ committedProgressBlockNumber: newProgressBlockNumber,
1112
+ numEventsProcessed: newTotalEventsProcessed,
1113
+ }
1114
+
1115
+ | None => //If no change was produced on the given chain after the reorged chain, no need to rollback anything
1116
+ cf
1117
+ }
1118
+ })
1119
+
1120
+ // Construct in Memory store with rollback diff
1121
+ let diff =
1122
+ await state.ctx.persistence->Persistence.prepareRollbackDiff(
1123
+ ~rollbackTargetCheckpointId,
1124
+ ~rollbackDiffCheckpointId=state.chainManager.committedCheckpointId +. 1.,
1125
+ )
1126
+
1127
+ let chainManager = {
1128
+ ...state.chainManager,
1129
+ chainFetchers,
1130
+ }
1131
+
1132
+ logger->Logging.childTrace({
1133
+ "msg": "Finished rollback on reorg",
1134
+ "entityChanges": {
1135
+ "deleted": diff["deletedEntities"],
1136
+ "upserted": diff["setEntities"],
1137
+ },
1138
+ "rollbackedEvents": rollbackedProcessedEvents.contents,
1139
+ "beforeCheckpointId": state.chainManager.committedCheckpointId,
1140
+ "targetCheckpointId": rollbackTargetCheckpointId,
1141
+ })
1142
+ Prometheus.RollbackSuccess.increment(
1143
+ ~timeMillis=Hrtime.timeSince(startTime)->Hrtime.toMillis,
1144
+ ~rollbackedProcessedEvents=rollbackedProcessedEvents.contents,
1145
+ )
1146
+
1147
+ dispatchAction(
1148
+ SetRollbackState({
1149
+ diffInMemoryStore: diff["inMemStore"],
1150
+ rollbackedChainManager: chainManager,
1151
+ eventsProcessedDiffByChain,
1152
+ }),
1153
+ )
1154
+ }
1155
+ }
1156
+ }
1157
+
1158
+ let taskReducer = injectedTaskReducer(
1159
+ ~waitForNewBlock=SourceManager.waitForNewBlock,
1160
+ ~executeQuery=SourceManager.executeQuery,
1161
+ ~getLastKnownValidBlock=(chainFetcher, ~reorgBlockNumber) =>
1162
+ chainFetcher->ChainFetcher.getLastKnownValidBlock(~reorgBlockNumber),
1163
+ )