envio 3.0.0-alpha.2 → 3.0.0-alpha.21

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