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,526 @@
1
+ open Belt
2
+
3
+ //A filter should return true if the event should be kept and isValid should return
4
+ //false when the filter should be removed/cleaned up
5
+ type processingFilter = {
6
+ filter: Internal.item => bool,
7
+ isValid: (~fetchState: FetchState.t) => bool,
8
+ }
9
+
10
+ type t = {
11
+ logger: Pino.t,
12
+ fetchState: FetchState.t,
13
+ sourceManager: SourceManager.t,
14
+ chainConfig: Config.chain,
15
+ isProgressAtHead: bool,
16
+ timestampCaughtUpToHeadOrEndblock: option<Js.Date.t>,
17
+ committedProgressBlockNumber: int,
18
+ firstEventBlockNumber: option<int>,
19
+ numEventsProcessed: int,
20
+ numBatchesFetched: int,
21
+ reorgDetection: ReorgDetection.t,
22
+ safeCheckpointTracking: option<SafeCheckpointTracking.t>,
23
+ }
24
+
25
+ //CONSTRUCTION
26
+ let make = (
27
+ ~chainConfig: Config.chain,
28
+ ~dynamicContracts: array<Internal.indexingContract>,
29
+ ~startBlock,
30
+ ~endBlock,
31
+ ~firstEventBlockNumber,
32
+ ~progressBlockNumber,
33
+ ~config: Config.t,
34
+ ~registrations: EventRegister.registrations,
35
+ ~targetBufferSize,
36
+ ~logger,
37
+ ~timestampCaughtUpToHeadOrEndblock,
38
+ ~numEventsProcessed,
39
+ ~numBatchesFetched,
40
+ ~isInReorgThreshold,
41
+ ~reorgCheckpoints: array<Internal.reorgCheckpoint>,
42
+ ~maxReorgDepth,
43
+ ~knownHeight=0,
44
+ ): t => {
45
+ // We don't need the router itself, but only validation logic,
46
+ // since now event router is created for selection of events
47
+ // and validation doesn't work correctly in routers.
48
+ // Ideally to split it into two different parts.
49
+ let eventRouter = EventRouter.empty()
50
+
51
+ // Aggregate events we want to fetch
52
+ let contracts = []
53
+ let eventConfigs: array<Internal.eventConfig> = []
54
+
55
+ let notRegisteredEvents = []
56
+
57
+ chainConfig.contracts->Array.forEach(contract => {
58
+ let contractName = contract.name
59
+
60
+ contract.events->Array.forEach(eventConfig => {
61
+ let {isWildcard} = eventConfig
62
+ let hasContractRegister = eventConfig.contractRegister->Option.isSome
63
+
64
+ // Should validate the events
65
+ eventRouter->EventRouter.addOrThrow(
66
+ eventConfig.id,
67
+ (),
68
+ ~contractName,
69
+ ~chain=ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id),
70
+ ~eventName=eventConfig.name,
71
+ ~isWildcard,
72
+ )
73
+
74
+ // Filter out non-preRegistration events on preRegistration phase
75
+ // so we don't care about it in fetch state and workers anymore
76
+ let shouldBeIncluded = if config.enableRawEvents {
77
+ true
78
+ } else {
79
+ let isRegistered = hasContractRegister || eventConfig.handler->Option.isSome
80
+ if !isRegistered {
81
+ notRegisteredEvents->Array.push(eventConfig)
82
+ }
83
+ isRegistered
84
+ }
85
+
86
+ // Check if event has Static([]) filters (from eventFilters: false)
87
+ // If so, skip it entirely - it should never be fetched
88
+ let shouldSkip = try {
89
+ let getEventFiltersOrThrow = (
90
+ eventConfig->(Utils.magic: Internal.eventConfig => Internal.evmEventConfig)
91
+ ).getEventFiltersOrThrow
92
+
93
+ // Check for non-evm chains
94
+ if getEventFiltersOrThrow->Utils.magic {
95
+ switch getEventFiltersOrThrow(ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)) {
96
+ | Static([]) => true
97
+ | _ => false
98
+ }
99
+ } else {
100
+ false
101
+ }
102
+ } catch {
103
+ // Can throw when filter is invalid
104
+ // Don't skip an event in this case. Let it throw in a better place - source code
105
+ | _ => false
106
+ }
107
+
108
+ if shouldBeIncluded && !shouldSkip {
109
+ eventConfigs->Array.push(eventConfig)
110
+ }
111
+ })
112
+
113
+ switch contract.startBlock {
114
+ | Some(startBlock) if startBlock < chainConfig.startBlock =>
115
+ Js.Exn.raiseError(
116
+ `The start block for contract "${contractName}" is less than the chain start block. This is not supported yet.`,
117
+ )
118
+ | _ => ()
119
+ }
120
+
121
+ contract.addresses->Array.forEach(address => {
122
+ contracts->Array.push({
123
+ Internal.address,
124
+ contractName: contract.name,
125
+ startBlock: switch contract.startBlock {
126
+ | Some(startBlock) => startBlock
127
+ | None => chainConfig.startBlock
128
+ },
129
+ registrationBlock: None,
130
+ })
131
+ })
132
+ })
133
+
134
+ dynamicContracts->Array.forEach(dc => contracts->Array.push(dc))
135
+
136
+ if notRegisteredEvents->Utils.Array.notEmpty {
137
+ logger->Logging.childInfo(
138
+ `The event${if notRegisteredEvents->Array.length > 1 {
139
+ "s"
140
+ } else {
141
+ ""
142
+ }} ${notRegisteredEvents
143
+ ->Array.map(eventConfig => `${eventConfig.contractName}.${eventConfig.name}`)
144
+ ->Js.Array2.joinWith(", ")} don't have an event handler and skipped for indexing.`,
145
+ )
146
+ }
147
+
148
+ let onBlockConfigs =
149
+ registrations.onBlockByChainId->Utils.Dict.dangerouslyGetNonOption(chainConfig.id->Int.toString)
150
+ switch onBlockConfigs {
151
+ | Some(onBlockConfigs) =>
152
+ // TODO: Move it to the EventRegister module
153
+ // so the error is thrown with better stack trace
154
+ onBlockConfigs->Array.forEach(onBlockConfig => {
155
+ if onBlockConfig.startBlock->Option.getWithDefault(startBlock) < startBlock {
156
+ Js.Exn.raiseError(
157
+ `The start block for onBlock handler "${onBlockConfig.name}" is less than the chain start block (${startBlock->Belt.Int.toString}). This is not supported yet.`,
158
+ )
159
+ }
160
+ switch endBlock {
161
+ | Some(chainEndBlock) =>
162
+ if onBlockConfig.endBlock->Option.getWithDefault(chainEndBlock) > chainEndBlock {
163
+ Js.Exn.raiseError(
164
+ `The end block for onBlock handler "${onBlockConfig.name}" is greater than the chain end block (${chainEndBlock->Belt.Int.toString}). This is not supported yet.`,
165
+ )
166
+ }
167
+ | None => ()
168
+ }
169
+ })
170
+ | None => ()
171
+ }
172
+
173
+ let fetchState = FetchState.make(
174
+ ~maxAddrInPartition=config.maxAddrInPartition,
175
+ ~contracts,
176
+ ~progressBlockNumber,
177
+ ~startBlock,
178
+ ~endBlock,
179
+ ~eventConfigs,
180
+ ~targetBufferSize,
181
+ ~knownHeight,
182
+ ~chainId=chainConfig.id,
183
+ // FIXME: Shouldn't set with full history
184
+ ~blockLag=Pervasives.max(
185
+ !config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth,
186
+ Env.indexingBlockLag->Option.getWithDefault(0),
187
+ ),
188
+ ~onBlockConfigs?,
189
+ )
190
+
191
+ let chainReorgCheckpoints = reorgCheckpoints->Array.keepMapU(reorgCheckpoint => {
192
+ if reorgCheckpoint.chainId === chainConfig.id {
193
+ Some(reorgCheckpoint)
194
+ } else {
195
+ None
196
+ }
197
+ })
198
+
199
+ // Create sources lazily here - this is where API token validation happens
200
+ let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)
201
+ let lowercaseAddresses = config.lowercaseAddresses
202
+ let sources = switch chainConfig.sourceConfig {
203
+ | Config.EvmSourceConfig({hypersync, rpcs}) =>
204
+ // Build Internal.evmContractConfig from contracts for EvmChain.makeSources
205
+ let evmContracts: array<Internal.evmContractConfig> =
206
+ chainConfig.contracts->Array.map((contract): Internal.evmContractConfig => {
207
+ name: contract.name,
208
+ abi: contract.abi,
209
+ events: contract.events->(
210
+ Utils.magic: array<Internal.eventConfig> => array<Internal.evmEventConfig>
211
+ ),
212
+ })
213
+ // Collect all event signatures from contracts
214
+ let allEventSignatures =
215
+ chainConfig.contracts->Array.flatMap(contract => contract.eventSignatures)
216
+ // Convert rpcs to EvmChain.rpc format
217
+ let evmRpcs: array<EvmChain.rpc> =
218
+ rpcs->Array.map((rpc): EvmChain.rpc => {
219
+ let syncConfig = rpc.syncConfig
220
+ {
221
+ url: rpc.url,
222
+ sourceFor: rpc.sourceFor,
223
+ ?syncConfig,
224
+ }
225
+ })
226
+ EvmChain.makeSources(
227
+ ~chain,
228
+ ~contracts=evmContracts,
229
+ ~hyperSync=hypersync,
230
+ ~allEventSignatures,
231
+ ~rpcs=evmRpcs,
232
+ ~lowercaseAddresses,
233
+ )
234
+ | Config.FuelSourceConfig({hypersync}) => [HyperFuelSource.make({chain, endpointUrl: hypersync})]
235
+ | Config.SvmSourceConfig({rpc}) => [Svm.makeRPCSource(~chain, ~rpc)]
236
+ // For tests: use ready-to-use sources directly
237
+ | Config.CustomSources(sources) => sources
238
+ }
239
+
240
+ {
241
+ logger,
242
+ chainConfig,
243
+ sourceManager: SourceManager.make(
244
+ ~sources,
245
+ ~maxPartitionConcurrency=Env.maxPartitionConcurrency,
246
+ ),
247
+ reorgDetection: ReorgDetection.make(
248
+ ~chainReorgCheckpoints,
249
+ ~maxReorgDepth,
250
+ ~shouldRollbackOnReorg=config.shouldRollbackOnReorg,
251
+ ),
252
+ safeCheckpointTracking: SafeCheckpointTracking.make(
253
+ ~maxReorgDepth,
254
+ ~shouldRollbackOnReorg=config.shouldRollbackOnReorg,
255
+ ~chainReorgCheckpoints,
256
+ ),
257
+ isProgressAtHead: false,
258
+ fetchState,
259
+ firstEventBlockNumber,
260
+ committedProgressBlockNumber: progressBlockNumber,
261
+ timestampCaughtUpToHeadOrEndblock,
262
+ numEventsProcessed,
263
+ numBatchesFetched,
264
+ }
265
+ }
266
+
267
+ let makeFromConfig = (chainConfig: Config.chain, ~config, ~registrations, ~targetBufferSize) => {
268
+ let logger = Logging.createChild(~params={"chainId": chainConfig.id})
269
+
270
+ make(
271
+ ~chainConfig,
272
+ ~config,
273
+ ~registrations,
274
+ ~startBlock=chainConfig.startBlock,
275
+ ~endBlock=chainConfig.endBlock,
276
+ ~reorgCheckpoints=[],
277
+ ~maxReorgDepth=chainConfig.maxReorgDepth,
278
+ ~firstEventBlockNumber=None,
279
+ ~progressBlockNumber=-1,
280
+ ~timestampCaughtUpToHeadOrEndblock=None,
281
+ ~numEventsProcessed=0,
282
+ ~numBatchesFetched=0,
283
+ ~targetBufferSize,
284
+ ~logger,
285
+ ~dynamicContracts=[],
286
+ ~isInReorgThreshold=false,
287
+ )
288
+ }
289
+
290
+ /**
291
+ * This function allows a chain fetcher to be created from metadata, in particular this is useful for restarting an indexer and making sure it fetches blocks from the same place.
292
+ */
293
+ let makeFromDbState = async (
294
+ chainConfig: Config.chain,
295
+ ~resumedChainState: Persistence.initialChainState,
296
+ ~reorgCheckpoints,
297
+ ~isInReorgThreshold,
298
+ ~config,
299
+ ~registrations,
300
+ ~targetBufferSize,
301
+ ) => {
302
+ let chainId = chainConfig.id
303
+ let logger = Logging.createChild(~params={"chainId": chainId})
304
+
305
+ Prometheus.ProgressEventsCount.set(~processedCount=resumedChainState.numEventsProcessed, ~chainId)
306
+
307
+ let progressBlockNumber =
308
+ // Can be -1 when not set
309
+ resumedChainState.progressBlockNumber >= 0
310
+ ? resumedChainState.progressBlockNumber
311
+ : resumedChainState.startBlock - 1
312
+
313
+ make(
314
+ ~dynamicContracts=resumedChainState.dynamicContracts,
315
+ ~chainConfig,
316
+ ~startBlock=resumedChainState.startBlock,
317
+ ~endBlock=resumedChainState.endBlock,
318
+ ~config,
319
+ ~registrations,
320
+ ~reorgCheckpoints,
321
+ ~maxReorgDepth=resumedChainState.maxReorgDepth,
322
+ ~firstEventBlockNumber=resumedChainState.firstEventBlockNumber,
323
+ ~progressBlockNumber,
324
+ ~timestampCaughtUpToHeadOrEndblock=Env.updateSyncTimeOnRestart
325
+ ? None
326
+ : resumedChainState.timestampCaughtUpToHeadOrEndblock,
327
+ ~numEventsProcessed=resumedChainState.numEventsProcessed,
328
+ ~numBatchesFetched=0,
329
+ ~logger,
330
+ ~targetBufferSize,
331
+ ~isInReorgThreshold,
332
+ ~knownHeight=resumedChainState.sourceBlockNumber,
333
+ )
334
+ }
335
+
336
+ /**
337
+ * Helper function to get the configured start block for a contract from config
338
+ */
339
+ let getContractStartBlock = (
340
+ config: Config.t,
341
+ ~chain: ChainMap.Chain.t,
342
+ ~contractName: string,
343
+ ): option<int> => {
344
+ let chainConfig = config.chainMap->ChainMap.get(chain)
345
+ chainConfig.contracts
346
+ ->Js.Array2.find(contract => contract.name === contractName)
347
+ ->Option.flatMap(contract => contract.startBlock)
348
+ }
349
+
350
+ let runContractRegistersOrThrow = async (
351
+ ~itemsWithContractRegister: array<Internal.item>,
352
+ ~chain: ChainMap.Chain.t,
353
+ ~config: Config.t,
354
+ ) => {
355
+ let itemsWithDcs = []
356
+
357
+ let onRegister = (~item: Internal.item, ~contractAddress, ~contractName) => {
358
+ let eventItem = item->Internal.castUnsafeEventItem
359
+ let {blockNumber} = eventItem
360
+
361
+ // Use contract-specific start block if configured, otherwise fall back to registration block
362
+ let contractStartBlock = switch getContractStartBlock(config, ~chain, ~contractName) {
363
+ | Some(configuredStartBlock) => configuredStartBlock
364
+ | None => blockNumber
365
+ }
366
+
367
+ let dc: Internal.indexingContract = {
368
+ address: contractAddress,
369
+ contractName,
370
+ startBlock: contractStartBlock,
371
+ registrationBlock: Some(blockNumber),
372
+ }
373
+
374
+ switch item->Internal.getItemDcs {
375
+ | None => {
376
+ item->Internal.setItemDcs([dc])
377
+ itemsWithDcs->Array.push(item)
378
+ }
379
+ | Some(dcs) => dcs->Array.push(dc)
380
+ }
381
+ }
382
+
383
+ let promises = []
384
+ for idx in 0 to itemsWithContractRegister->Array.length - 1 {
385
+ let item = itemsWithContractRegister->Array.getUnsafe(idx)
386
+ let eventItem = item->Internal.castUnsafeEventItem
387
+ let contractRegister = switch eventItem {
388
+ | {eventConfig: {contractRegister: Some(contractRegister)}} => contractRegister
389
+ | {eventConfig: {contractRegister: None, name: eventName}} =>
390
+ // Unexpected case, since we should pass only events with contract register to this function
391
+ Js.Exn.raiseError("Contract register is not set for event " ++ eventName)
392
+ }
393
+
394
+ let errorMessage = "Event contractRegister failed, please fix the error to keep the indexer running smoothly"
395
+
396
+ // Catch sync and async errors
397
+ try {
398
+ let params: UserContext.contractRegisterParams = {
399
+ item,
400
+ onRegister,
401
+ config,
402
+ isResolved: false,
403
+ }
404
+ let result = contractRegister(UserContext.getContractRegisterArgs(params))
405
+
406
+ // Even though `contractRegister` always returns a promise,
407
+ // in the ReScript type, but it might return a non-promise value for TS API.
408
+ if result->Promise.isCatchable {
409
+ promises->Array.push(
410
+ result
411
+ ->Promise.thenResolve(r => {
412
+ params.isResolved = true
413
+ r
414
+ })
415
+ ->Promise.catch(exn => {
416
+ params.isResolved = true
417
+ exn->ErrorHandling.mkLogAndRaise(~msg=errorMessage, ~logger=item->Logging.getItemLogger)
418
+ }),
419
+ )
420
+ } else {
421
+ params.isResolved = true
422
+ }
423
+ } catch {
424
+ | exn =>
425
+ exn->ErrorHandling.mkLogAndRaise(~msg=errorMessage, ~logger=item->Logging.getItemLogger)
426
+ }
427
+ }
428
+
429
+ if promises->Utils.Array.notEmpty {
430
+ let _ = await Promise.all(promises)
431
+ }
432
+
433
+ itemsWithDcs
434
+ }
435
+
436
+ let handleQueryResult = (
437
+ chainFetcher: t,
438
+ ~query: FetchState.query,
439
+ ~newItems,
440
+ ~newItemsWithDcs,
441
+ ~latestFetchedBlock,
442
+ ~knownHeight,
443
+ ) => {
444
+ let fs = switch newItemsWithDcs {
445
+ | [] => chainFetcher.fetchState
446
+ | _ => chainFetcher.fetchState->FetchState.registerDynamicContracts(newItemsWithDcs)
447
+ }
448
+
449
+ fs
450
+ ->FetchState.handleQueryResult(~query, ~latestFetchedBlock, ~newItems)
451
+ ->Result.map(fs => {
452
+ ...chainFetcher,
453
+ fetchState: fs->FetchState.updateKnownHeight(~knownHeight),
454
+ })
455
+ }
456
+
457
+ /**
458
+ Gets the latest item on the front of the queue and returns updated fetcher
459
+ */
460
+ let hasProcessedToEndblock = (self: t) => {
461
+ let {committedProgressBlockNumber, fetchState} = self
462
+ switch fetchState.endBlock {
463
+ | Some(endBlock) => committedProgressBlockNumber >= endBlock
464
+ | None => false
465
+ }
466
+ }
467
+
468
+ let hasNoMoreEventsToProcess = (self: t) => {
469
+ self.fetchState->FetchState.bufferSize === 0
470
+ }
471
+
472
+ let getHighestBlockBelowThreshold = (cf: t): int => {
473
+ let highestBlockBelowThreshold = cf.fetchState.knownHeight - cf.chainConfig.maxReorgDepth
474
+ highestBlockBelowThreshold < 0 ? 0 : highestBlockBelowThreshold
475
+ }
476
+
477
+ /**
478
+ Finds the last known valid block number below the reorg block
479
+ If not found, returns the highest block below threshold
480
+ */
481
+ let getLastKnownValidBlock = async (
482
+ chainFetcher: t,
483
+ ~reorgBlockNumber: int,
484
+ //Parameter used for dependency injecting in tests
485
+ ~getBlockHashes=(chainFetcher.sourceManager->SourceManager.getActiveSource).getBlockHashes,
486
+ ) => {
487
+ // Improtant: It's important to not include the reorg detection block number
488
+ // because there might be different instances of the source
489
+ // with mismatching hashes between them.
490
+ // So we MUST always rollback the block number where we detected a reorg.
491
+ let scannedBlockNumbers =
492
+ chainFetcher.reorgDetection->ReorgDetection.getThresholdBlockNumbersBelowBlock(
493
+ ~blockNumber=reorgBlockNumber,
494
+ ~knownHeight=chainFetcher.fetchState.knownHeight,
495
+ )
496
+
497
+ let getBlockHashes = blockNumbers => {
498
+ getBlockHashes(~blockNumbers, ~logger=chainFetcher.logger)->Promise.thenResolve(res =>
499
+ switch res {
500
+ | Ok(v) => v
501
+ | Error(exn) =>
502
+ exn->ErrorHandling.mkLogAndRaise(
503
+ ~msg="Failed to fetch blockHashes for given blockNumbers during rollback",
504
+ )
505
+ }
506
+ )
507
+ }
508
+
509
+ switch scannedBlockNumbers {
510
+ | [] => chainFetcher->getHighestBlockBelowThreshold
511
+ | _ => {
512
+ let blockNumbersAndHashes = await getBlockHashes(scannedBlockNumbers)
513
+
514
+ switch chainFetcher.reorgDetection->ReorgDetection.getLatestValidScannedBlock(
515
+ ~blockNumbersAndHashes,
516
+ ) {
517
+ | Some(blockNumber) => blockNumber
518
+ | None => chainFetcher->getHighestBlockBelowThreshold
519
+ }
520
+ }
521
+ }
522
+ }
523
+
524
+ let isActivelyIndexing = (chainFetcher: t) => chainFetcher.fetchState->FetchState.isActivelyIndexing
525
+
526
+ let isLive = (chainFetcher: t) => chainFetcher.timestampCaughtUpToHeadOrEndblock !== None