envio 3.2.0 → 3.3.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/package.json +6 -7
  2. package/src/Batch.res +12 -15
  3. package/src/Batch.res.mjs +4 -5
  4. package/src/BatchProcessing.res +199 -0
  5. package/src/BatchProcessing.res.mjs +125 -0
  6. package/src/ChainFetching.res +375 -0
  7. package/src/ChainFetching.res.mjs +206 -0
  8. package/src/ChainMetadata.res +27 -0
  9. package/src/ChainMetadata.res.mjs +27 -0
  10. package/src/ChainState.res +618 -0
  11. package/src/ChainState.res.mjs +461 -0
  12. package/src/ChainState.resi +85 -0
  13. package/src/Config.res +13 -4
  14. package/src/Config.res.mjs +8 -3
  15. package/src/ContractRegisterContext.res +106 -0
  16. package/src/ContractRegisterContext.res.mjs +76 -0
  17. package/src/Core.res +3 -0
  18. package/src/CrossChainState.res +269 -0
  19. package/src/CrossChainState.res.mjs +197 -0
  20. package/src/CrossChainState.resi +47 -0
  21. package/src/Ecosystem.res +58 -0
  22. package/src/Ecosystem.res.mjs +43 -1
  23. package/src/Env.res +7 -2
  24. package/src/Env.res.mjs +5 -2
  25. package/src/EventConfigBuilder.res +35 -0
  26. package/src/EventConfigBuilder.res.mjs +21 -0
  27. package/src/EventProcessing.res +60 -46
  28. package/src/EventProcessing.res.mjs +33 -32
  29. package/src/EventUtils.res +0 -4
  30. package/src/EventUtils.res.mjs +0 -4
  31. package/src/ExitOnCaughtUp.res +10 -0
  32. package/src/ExitOnCaughtUp.res.mjs +22 -0
  33. package/src/FetchState.res +162 -113
  34. package/src/FetchState.res.mjs +181 -119
  35. package/src/HandlerLoader.res +6 -1
  36. package/src/HandlerLoader.res.mjs +13 -36
  37. package/src/InMemoryStore.res +36 -451
  38. package/src/InMemoryStore.res.mjs +20 -369
  39. package/src/IndexerLoop.res +44 -0
  40. package/src/IndexerLoop.res.mjs +46 -0
  41. package/src/IndexerState.res +544 -0
  42. package/src/IndexerState.res.mjs +547 -0
  43. package/src/IndexerState.resi +132 -0
  44. package/src/Internal.res +44 -7
  45. package/src/LoadLayer.res +20 -18
  46. package/src/LoadLayer.res.mjs +14 -12
  47. package/src/LoadLayer.resi +6 -3
  48. package/src/LogSelection.res +86 -17
  49. package/src/LogSelection.res.mjs +67 -14
  50. package/src/Logging.res +11 -44
  51. package/src/Logging.res.mjs +10 -42
  52. package/src/Main.res +54 -79
  53. package/src/Main.res.mjs +44 -56
  54. package/src/PgStorage.res +8 -71
  55. package/src/PgStorage.res.mjs +3 -47
  56. package/src/Prometheus.res +7 -8
  57. package/src/Prometheus.res.mjs +7 -4
  58. package/src/PruneStaleHistory.res +45 -0
  59. package/src/PruneStaleHistory.res.mjs +46 -0
  60. package/src/RawEvent.res +73 -0
  61. package/src/RawEvent.res.mjs +51 -0
  62. package/src/Rollback.res +204 -0
  63. package/src/Rollback.res.mjs +118 -0
  64. package/src/SimulateItems.res +12 -10
  65. package/src/SimulateItems.res.mjs +1 -1
  66. package/src/TestIndexer.res +34 -27
  67. package/src/TestIndexer.res.mjs +4 -1
  68. package/src/UserContext.res +26 -114
  69. package/src/UserContext.res.mjs +15 -78
  70. package/src/Writing.res +242 -0
  71. package/src/Writing.res.mjs +215 -0
  72. package/src/db/InternalTable.res +14 -27
  73. package/src/sources/Evm.res +40 -1
  74. package/src/sources/Evm.res.mjs +94 -84
  75. package/src/sources/Fuel.res +40 -1
  76. package/src/sources/Fuel.res.mjs +36 -26
  77. package/src/sources/HyperFuel.res +41 -120
  78. package/src/sources/HyperFuel.res.mjs +42 -80
  79. package/src/sources/HyperFuel.resi +1 -24
  80. package/src/sources/HyperFuelClient.res +16 -297
  81. package/src/sources/HyperFuelClient.res.mjs +5 -4
  82. package/src/sources/HyperFuelSource.res +33 -8
  83. package/src/sources/HyperFuelSource.res.mjs +56 -10
  84. package/src/sources/HyperSyncSource.res +7 -5
  85. package/src/sources/HyperSyncSource.res.mjs +3 -3
  86. package/src/sources/RpcSource.res +2 -2
  87. package/src/sources/RpcSource.res.mjs +1 -1
  88. package/src/sources/SourceManager.res +8 -18
  89. package/src/sources/SourceManager.res.mjs +8 -5
  90. package/src/sources/SourceManager.resi +4 -1
  91. package/src/sources/Svm.res +18 -1
  92. package/src/sources/Svm.res.mjs +30 -22
  93. package/src/sources/SvmHyperSyncSource.res +1 -1
  94. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  95. package/src/tui/Tui.res +38 -36
  96. package/src/tui/Tui.res.mjs +51 -51
  97. package/src/ChainFetcher.res +0 -519
  98. package/src/ChainFetcher.res.mjs +0 -314
  99. package/src/ChainManager.res +0 -358
  100. package/src/ChainManager.res.mjs +0 -291
  101. package/src/Ctx.res +0 -6
  102. package/src/Ctx.res.mjs +0 -2
  103. package/src/GlobalState.res +0 -1056
  104. package/src/GlobalState.res.mjs +0 -1086
  105. package/src/GlobalStateManager.res +0 -57
  106. package/src/GlobalStateManager.res.mjs +0 -68
  107. package/src/GlobalStateManager.resi +0 -7
  108. package/src/Ports.res +0 -5
  109. package/src/Ports.res.mjs +0 -9
  110. package/src/adapters/MarkBatchProcessedAdapter.res +0 -5
  111. package/src/adapters/MarkBatchProcessedAdapter.res.mjs +0 -14
  112. package/src/sources/EnvioApiClient.res +0 -15
  113. package/src/sources/EnvioApiClient.res.mjs +0 -24
@@ -6,10 +6,10 @@ import * as Ink$1 from "ink";
6
6
  import * as React from "react";
7
7
  import * as SyncETA from "./components/SyncETA.res.mjs";
8
8
  import * as TuiData from "./components/TuiData.res.mjs";
9
- import * as ChainMap from "../ChainMap.res.mjs";
10
9
  import * as Messages from "./components/Messages.res.mjs";
10
+ import * as ChainState from "../ChainState.res.mjs";
11
11
  import * as FetchState from "../FetchState.res.mjs";
12
- import * as ChainFetcher from "../ChainFetcher.res.mjs";
12
+ import * as IndexerState from "../IndexerState.res.mjs";
13
13
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
14
14
  import InkBigText from "ink-big-text";
15
15
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
@@ -207,47 +207,47 @@ function Tui$App(props) {
207
207
  clearInterval(intervalId);
208
208
  };
209
209
  }, [getState]);
210
- let chains = ChainMap.values(state.chainManager.chainFetchers).map(cf => {
211
- let numEventsProcessed = cf.numEventsProcessed;
212
- let latestFetchedBlockNumber = Primitive_int.max(FetchState.bufferBlockNumber(cf.fetchState), 0);
213
- let hasProcessedToEndblock = ChainFetcher.hasProcessedToEndblock(cf);
214
- let knownHeight = ChainFetcher.hasProcessedToEndblock(cf) ? Stdlib_Option.getOr(cf.fetchState.endBlock, cf.fetchState.knownHeight) : cf.fetchState.knownHeight;
215
- let firstEventBlock = cf.fetchState.firstEventBlock;
216
- let progress;
217
- if (hasProcessedToEndblock) {
218
- progress = {
210
+ let chains = Object.values(IndexerState.chainStates(state)).map(cs => {
211
+ let numEventsProcessed = ChainState.numEventsProcessed(cs);
212
+ let fetchState = ChainState.fetchState(cs);
213
+ let committedProgressBlockNumber = ChainState.committedProgressBlockNumber(cs);
214
+ let timestampCaughtUpToHeadOrEndblock = ChainState.timestampCaughtUpToHeadOrEndblock(cs);
215
+ let sourceManager = ChainState.sourceManager(cs);
216
+ let latestFetchedBlockNumber = Primitive_int.max(FetchState.bufferBlockNumber(fetchState), 0);
217
+ let hasProcessedToEndblock = ChainState.hasProcessedToEndblock(cs);
218
+ let knownHeight = hasProcessedToEndblock ? Stdlib_Option.getOr(fetchState.endBlock, fetchState.knownHeight) : fetchState.knownHeight;
219
+ let firstEventBlock = fetchState.firstEventBlock;
220
+ let progress = hasProcessedToEndblock ? ({
219
221
  TAG: "Synced",
220
222
  _0: {
221
223
  firstEventBlockNumber: Stdlib_Option.getOr(firstEventBlock, 0),
222
- latestProcessedBlock: cf.committedProgressBlockNumber,
224
+ latestProcessedBlock: committedProgressBlockNumber,
223
225
  numEventsProcessed: numEventsProcessed,
224
- timestampCaughtUpToHeadOrEndblock: Stdlib_Option.getOr(cf.timestampCaughtUpToHeadOrEndblock, new Date(Date.now()))
226
+ timestampCaughtUpToHeadOrEndblock: Stdlib_Option.getOr(timestampCaughtUpToHeadOrEndblock, new Date(Date.now()))
225
227
  }
226
- };
227
- } else {
228
- let match = cf.timestampCaughtUpToHeadOrEndblock;
229
- progress = firstEventBlock !== undefined ? (
230
- match !== undefined ? ({
231
- TAG: "Synced",
232
- _0: {
233
- firstEventBlockNumber: firstEventBlock,
234
- latestProcessedBlock: cf.committedProgressBlockNumber,
235
- numEventsProcessed: numEventsProcessed,
236
- timestampCaughtUpToHeadOrEndblock: match
237
- }
238
- }) : ({
239
- TAG: "Syncing",
240
- _0: {
241
- firstEventBlockNumber: firstEventBlock,
242
- latestProcessedBlock: cf.committedProgressBlockNumber,
243
- numEventsProcessed: numEventsProcessed
244
- }
245
- })
246
- ) : "SearchingForEvents";
247
- }
248
- let match$1 = state.ctx.config.ecosystem.name;
228
+ }) : (
229
+ firstEventBlock !== undefined ? (
230
+ timestampCaughtUpToHeadOrEndblock !== undefined ? ({
231
+ TAG: "Synced",
232
+ _0: {
233
+ firstEventBlockNumber: firstEventBlock,
234
+ latestProcessedBlock: committedProgressBlockNumber,
235
+ numEventsProcessed: numEventsProcessed,
236
+ timestampCaughtUpToHeadOrEndblock: timestampCaughtUpToHeadOrEndblock
237
+ }
238
+ }) : ({
239
+ TAG: "Syncing",
240
+ _0: {
241
+ firstEventBlockNumber: firstEventBlock,
242
+ latestProcessedBlock: committedProgressBlockNumber,
243
+ numEventsProcessed: numEventsProcessed
244
+ }
245
+ })
246
+ ) : "SearchingForEvents"
247
+ );
248
+ let match = IndexerState.config(state).ecosystem.name;
249
249
  let tmp;
250
- switch (match$1) {
250
+ switch (match) {
251
251
  case "evm" :
252
252
  case "fuel" :
253
253
  tmp = "Block";
@@ -257,22 +257,22 @@ function Tui$App(props) {
257
257
  break;
258
258
  }
259
259
  return {
260
- chainId: cf.chainConfig.id.toString(),
260
+ chainId: ChainState.chainConfig(cs).id.toString(),
261
261
  eventsProcessed: numEventsProcessed,
262
- progressBlock: cf.committedProgressBlockNumber < cf.fetchState.startBlock ? cf.fetchState.startBlock : cf.committedProgressBlockNumber,
262
+ progressBlock: committedProgressBlockNumber < fetchState.startBlock ? fetchState.startBlock : committedProgressBlockNumber,
263
263
  bufferBlock: latestFetchedBlockNumber,
264
- sourceBlock: cf.fetchState.knownHeight,
265
- startBlock: cf.fetchState.startBlock,
266
- endBlock: cf.fetchState.endBlock,
267
- firstEventBlockNumber: cf.fetchState.firstEventBlock,
268
- poweredByHyperSync: SourceManager.getActiveSource(cf.sourceManager).poweredByHyperSync,
264
+ sourceBlock: fetchState.knownHeight,
265
+ startBlock: fetchState.startBlock,
266
+ endBlock: fetchState.endBlock,
267
+ firstEventBlockNumber: fetchState.firstEventBlock,
268
+ poweredByHyperSync: SourceManager.getActiveSource(sourceManager).poweredByHyperSync,
269
269
  progress: progress,
270
270
  latestFetchedBlockNumber: latestFetchedBlockNumber,
271
271
  knownHeight: knownHeight,
272
272
  blockUnit: tmp,
273
- rateLimitTimeMs: SourceManager.getRateLimitTimeMs(cf.sourceManager),
274
- isRateLimited: SourceManager.isRateLimited(cf.sourceManager),
275
- rateLimitResetInMs: SourceManager.getRateLimitResetInMs(cf.sourceManager)
273
+ rateLimitTimeMs: SourceManager.getRateLimitTimeMs(sourceManager),
274
+ isRateLimited: SourceManager.isRateLimited(sourceManager),
275
+ rateLimitResetInMs: SourceManager.getRateLimitResetInMs(sourceManager)
276
276
  };
277
277
  });
278
278
  let totalEventsProcessed = Stdlib_Array.reduce(chains, 0, (acc, chain) => acc + chain.eventsProcessed);
@@ -329,7 +329,7 @@ function Tui$App(props) {
329
329
  tmp = null;
330
330
  }
331
331
  let defaultPassword = "testing";
332
- let match$1 = state.ctx.config.storage.clickhouse;
332
+ let match$1 = IndexerState.config(state).storage.clickhouse;
333
333
  let match$2 = Env.ClickHouse.host();
334
334
  return JsxRuntime.jsxs(Ink$1.Box, {
335
335
  children: [
@@ -362,7 +362,7 @@ function Tui$App(props) {
362
362
  }),
363
363
  JsxRuntime.jsx(SyncETA.make, {
364
364
  chains: chains,
365
- indexerStartTime: state.indexerStartTime
365
+ indexerStartTime: IndexerState.indexerStartTime(state)
366
366
  }),
367
367
  tmp,
368
368
  JsxRuntime.jsx(Ink.Newline.make, {}),
@@ -383,7 +383,7 @@ function Tui$App(props) {
383
383
  ],
384
384
  flexDirection: "row"
385
385
  }),
386
- state.ctx.config.isDev ? JsxRuntime.jsxs(Ink$1.Box, {
386
+ IndexerState.config(state).isDev ? JsxRuntime.jsxs(Ink$1.Box, {
387
387
  children: [
388
388
  JsxRuntime.jsx(Ink$1.Text, {
389
389
  children: "Dev Console: "
@@ -410,7 +410,7 @@ function Tui$App(props) {
410
410
  flexDirection: "row"
411
411
  }) : null,
412
412
  JsxRuntime.jsx(Messages.make, {
413
- config: state.ctx.config
413
+ config: IndexerState.config(state)
414
414
  })
415
415
  ],
416
416
  flexDirection: "column"
@@ -1,519 +0,0 @@
1
- //A filter should return true if the event should be kept and isValid should return
2
- //false when the filter should be removed/cleaned up
3
- type processingFilter = {
4
- filter: Internal.item => bool,
5
- isValid: (~fetchState: FetchState.t) => bool,
6
- }
7
-
8
- type t = {
9
- logger: Pino.t,
10
- fetchState: FetchState.t,
11
- sourceManager: SourceManager.t,
12
- chainConfig: Config.chain,
13
- isProgressAtHead: bool,
14
- timestampCaughtUpToHeadOrEndblock: option<Date.t>,
15
- committedProgressBlockNumber: int,
16
- numEventsProcessed: float,
17
- reorgDetection: ReorgDetection.t,
18
- safeCheckpointTracking: option<SafeCheckpointTracking.t>,
19
- }
20
-
21
- //CONSTRUCTION
22
- let configAddresses = (chainConfig: Config.chain): array<Internal.indexingAddress> => {
23
- let addresses = []
24
- chainConfig.contracts->Array.forEach(contract => {
25
- contract.addresses->Array.forEach(address => {
26
- addresses->Array.push({
27
- Internal.address,
28
- contractName: contract.name,
29
- registrationBlock: -1,
30
- })
31
- })
32
- })
33
- addresses
34
- }
35
-
36
- let make = (
37
- ~chainConfig: Config.chain,
38
- ~indexingAddresses: array<Internal.indexingAddress>,
39
- ~startBlock,
40
- ~endBlock,
41
- ~firstEventBlock=None,
42
- ~progressBlockNumber,
43
- ~config: Config.t,
44
- ~registrations: HandlerRegister.registrations,
45
- ~targetBufferSize,
46
- ~logger,
47
- ~timestampCaughtUpToHeadOrEndblock,
48
- ~numEventsProcessed,
49
- ~isInReorgThreshold,
50
- ~isRealtime,
51
- ~reorgCheckpoints: array<Internal.reorgCheckpoint>,
52
- ~maxReorgDepth,
53
- ~knownHeight=0,
54
- ~reducedPollingInterval=?,
55
- ): t => {
56
- // We don't need the router itself, but only validation logic,
57
- // since now event router is created for selection of events
58
- // and validation doesn't work correctly in routers.
59
- // Ideally to split it into two different parts.
60
- let eventRouter = EventRouter.empty()
61
-
62
- // Aggregate events we want to fetch
63
- let eventConfigs: array<Internal.eventConfig> = []
64
-
65
- let notRegisteredEvents = []
66
-
67
- chainConfig.contracts->Array.forEach(contract => {
68
- let contractName = contract.name
69
-
70
- contract.events->Array.forEach(eventConfig => {
71
- let {isWildcard} = eventConfig
72
- let hasContractRegister = eventConfig.contractRegister->Option.isSome
73
-
74
- // Should validate the events
75
- eventRouter->EventRouter.addOrThrow(
76
- eventConfig.id,
77
- (),
78
- ~contractName,
79
- ~chain=ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id),
80
- ~eventName=eventConfig.name,
81
- ~isWildcard,
82
- )
83
-
84
- // Filter out non-preRegistration events on preRegistration phase
85
- // so we don't care about it in fetch state and workers anymore
86
- let shouldBeIncluded = if config.enableRawEvents {
87
- true
88
- } else {
89
- let isRegistered = hasContractRegister || eventConfig.handler->Option.isSome
90
- if !isRegistered {
91
- notRegisteredEvents->Array.push(eventConfig)
92
- }
93
- isRegistered
94
- }
95
-
96
- // Check if event has Static([]) filters (from a dynamic where
97
- // callback returning `false` / SkipAll for this chain).
98
- // If so, skip it entirely - it should never be fetched
99
- let shouldSkip = try {
100
- let getEventFiltersOrThrow = (
101
- eventConfig->(Utils.magic: Internal.eventConfig => Internal.evmEventConfig)
102
- ).getEventFiltersOrThrow
103
-
104
- // Check for non-evm chains
105
- if (
106
- getEventFiltersOrThrow->(Utils.magic: (ChainMap.Chain.t => Internal.eventFilters) => bool)
107
- ) {
108
- switch getEventFiltersOrThrow(ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)) {
109
- | Static([]) => true
110
- | _ => false
111
- }
112
- } else {
113
- false
114
- }
115
- } catch {
116
- // Can throw when filter is invalid
117
- // Don't skip an event in this case. Let it throw in a better place - source code
118
- | _ => false
119
- }
120
-
121
- if shouldBeIncluded && !shouldSkip {
122
- eventConfigs->Array.push(eventConfig)
123
- }
124
- })
125
-
126
- switch contract.startBlock {
127
- | Some(startBlock) if startBlock < chainConfig.startBlock =>
128
- JsError.throwWithMessage(
129
- `The start block for contract "${contractName}" is less than the chain start block. This is not supported yet.`,
130
- )
131
- | _ => ()
132
- }
133
- })
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
- ->Array.joinUnsafe(", ")} 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.getOr(startBlock) < startBlock {
155
- JsError.throwWithMessage(
156
- `The start block for onBlock handler "${onBlockConfig.name}" is less than the chain start block (${startBlock->Int.toString}). This is not supported yet.`,
157
- )
158
- }
159
- switch endBlock {
160
- | Some(chainEndBlock) =>
161
- if onBlockConfig.endBlock->Option.getOr(chainEndBlock) > chainEndBlock {
162
- JsError.throwWithMessage(
163
- `The end block for onBlock handler "${onBlockConfig.name}" is greater than the chain end block (${chainEndBlock->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
- ~addresses=indexingAddresses,
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.filterMap(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
- let evmRpcs: array<EvmChain.rpc> = rpcs->Array.map((rpc): EvmChain.rpc => {
215
- let syncConfig = rpc.syncConfig
216
- let ws = rpc.ws
217
- {
218
- url: rpc.url,
219
- sourceFor: rpc.sourceFor,
220
- ?syncConfig,
221
- ?ws,
222
- }
223
- })
224
- EvmChain.makeSources(
225
- ~chain,
226
- ~contracts=evmContracts,
227
- ~hyperSync=hypersync,
228
- ~rpcs=evmRpcs,
229
- ~lowercaseAddresses,
230
- )
231
- | Config.FuelSourceConfig({hypersync}) => [HyperFuelSource.make({chain, endpointUrl: hypersync})]
232
- | Config.SvmSourceConfig({hypersync, rpc}) =>
233
- switch (hypersync, rpc) {
234
- | (None, None) =>
235
- JsError.throwWithMessage(
236
- `Chain ${chain->ChainMap.Chain.toChainId->Int.toString} has no SVM data source`,
237
- )
238
- | (None, Some(rpc)) => [Svm.makeRPCSource(~chain, ~rpc)]
239
- | (Some(hypersyncUrl), _) =>
240
- // HyperSync drives instruction sync. A configured RPC is ignored for now
241
- // (RPC fallback isn't wired up yet).
242
- let svmEventConfigs =
243
- chainConfig.contracts
244
- ->Array.flatMap(contract => contract.events)
245
- ->(Utils.magic: array<Internal.eventConfig> => array<Internal.svmInstructionEventConfig>)
246
- let apiToken = Env.envioApiToken
247
- [
248
- SvmHyperSyncSource.make({
249
- chain,
250
- endpointUrl: hypersyncUrl,
251
- apiToken,
252
- eventConfigs: svmEventConfigs,
253
- clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
254
- }),
255
- ]
256
- }
257
- // For tests: use ready-to-use sources directly
258
- | Config.CustomSources(sources) => sources
259
- }
260
-
261
- {
262
- logger,
263
- chainConfig,
264
- sourceManager: SourceManager.make(
265
- ~sources,
266
- ~maxPartitionConcurrency=Env.maxPartitionConcurrency,
267
- ~isRealtime,
268
- ~reducedPollingInterval?,
269
- ),
270
- reorgDetection: ReorgDetection.make(
271
- ~chainReorgCheckpoints,
272
- ~maxReorgDepth,
273
- ~shouldRollbackOnReorg=config.shouldRollbackOnReorg,
274
- ),
275
- safeCheckpointTracking: SafeCheckpointTracking.make(
276
- ~maxReorgDepth,
277
- ~shouldRollbackOnReorg=config.shouldRollbackOnReorg,
278
- ~chainReorgCheckpoints,
279
- ),
280
- isProgressAtHead: false,
281
- fetchState,
282
- committedProgressBlockNumber: progressBlockNumber,
283
- timestampCaughtUpToHeadOrEndblock,
284
- numEventsProcessed,
285
- }
286
- }
287
-
288
- let makeFromConfig = (
289
- chainConfig: Config.chain,
290
- ~config,
291
- ~registrations,
292
- ~targetBufferSize,
293
- ~knownHeight,
294
- ) => {
295
- let logger = Logging.createChild(~params={"chainId": chainConfig.id})
296
-
297
- make(
298
- ~chainConfig,
299
- ~config,
300
- ~registrations,
301
- ~startBlock=chainConfig.startBlock,
302
- ~endBlock=chainConfig.endBlock,
303
- ~reorgCheckpoints=[],
304
- ~maxReorgDepth=chainConfig.maxReorgDepth,
305
- ~progressBlockNumber=-1,
306
- ~timestampCaughtUpToHeadOrEndblock=None,
307
- ~numEventsProcessed=0.,
308
- ~targetBufferSize,
309
- ~logger,
310
- ~indexingAddresses=configAddresses(chainConfig),
311
- ~isInReorgThreshold=false,
312
- ~isRealtime=false,
313
- ~knownHeight,
314
- )
315
- }
316
-
317
- /**
318
- * 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.
319
- */
320
- let makeFromDbState = (
321
- chainConfig: Config.chain,
322
- ~resumedChainState: Persistence.initialChainState,
323
- ~reorgCheckpoints,
324
- ~isInReorgThreshold,
325
- ~isRealtime,
326
- ~config,
327
- ~registrations,
328
- ~targetBufferSize,
329
- ~reducedPollingInterval=?,
330
- ) => {
331
- let chainId = chainConfig.id
332
- let logger = Logging.createChild(~params={"chainId": chainId})
333
-
334
- Prometheus.ProgressEventsCount.set(~processedCount=resumedChainState.numEventsProcessed, ~chainId)
335
-
336
- let progressBlockNumber =
337
- // Can be -1 when not set
338
- resumedChainState.progressBlockNumber >= 0
339
- ? resumedChainState.progressBlockNumber
340
- : resumedChainState.startBlock - 1
341
-
342
- make(
343
- ~indexingAddresses=resumedChainState.indexingAddresses,
344
- ~chainConfig,
345
- ~startBlock=resumedChainState.startBlock,
346
- ~endBlock=resumedChainState.endBlock,
347
- ~config,
348
- ~registrations,
349
- ~reorgCheckpoints,
350
- ~maxReorgDepth=resumedChainState.maxReorgDepth,
351
- ~firstEventBlock=resumedChainState.firstEventBlockNumber,
352
- ~progressBlockNumber,
353
- ~timestampCaughtUpToHeadOrEndblock=Env.updateSyncTimeOnRestart
354
- ? None
355
- : resumedChainState.timestampCaughtUpToHeadOrEndblock,
356
- ~numEventsProcessed=resumedChainState.numEventsProcessed,
357
- ~logger,
358
- ~targetBufferSize,
359
- ~isInReorgThreshold,
360
- ~isRealtime,
361
- ~knownHeight=resumedChainState.sourceBlockNumber,
362
- ~reducedPollingInterval?,
363
- )
364
- }
365
-
366
- let runContractRegistersOrThrow = async (
367
- ~itemsWithContractRegister: array<Internal.item>,
368
- ~config: Config.t,
369
- ) => {
370
- let itemsWithDcs = []
371
-
372
- let onRegister = (~item: Internal.item, ~contractAddress, ~contractName) => {
373
- let eventItem = item->Internal.castUnsafeEventItem
374
- let {blockNumber} = eventItem
375
-
376
- let dc: Internal.indexingAddress = {
377
- address: contractAddress,
378
- contractName,
379
- registrationBlock: blockNumber,
380
- }
381
-
382
- switch item->Internal.getItemDcs {
383
- | None => {
384
- item->Internal.setItemDcs([dc])
385
- itemsWithDcs->Array.push(item)
386
- }
387
- | Some(dcs) => dcs->Array.push(dc)
388
- }
389
- }
390
-
391
- let promises = []
392
- for idx in 0 to itemsWithContractRegister->Array.length - 1 {
393
- let item = itemsWithContractRegister->Array.getUnsafe(idx)
394
- let eventItem = item->Internal.castUnsafeEventItem
395
- let contractRegister = switch eventItem {
396
- | {eventConfig: {contractRegister: Some(contractRegister)}} => contractRegister
397
- | {eventConfig: {contractRegister: None, name: eventName}} =>
398
- // Unexpected case, since we should pass only events with contract register to this function
399
- JsError.throwWithMessage("Contract register is not set for event " ++ eventName)
400
- }
401
-
402
- let errorMessage = "Event contractRegister failed, please fix the error to keep the indexer running smoothly"
403
-
404
- // Catch sync and async errors
405
- try {
406
- let params: UserContext.contractRegisterParams = {
407
- item,
408
- onRegister,
409
- config,
410
- isResolved: false,
411
- }
412
- let result = contractRegister(UserContext.getContractRegisterArgs(params))
413
-
414
- // Even though `contractRegister` always returns a promise,
415
- // in the ReScript type, but it might return a non-promise value for TS API.
416
- if result->Utils.Promise.isCatchable {
417
- promises->Array.push(
418
- result
419
- ->Promise.thenResolve(r => {
420
- params.isResolved = true
421
- r
422
- })
423
- ->Promise.catch(exn => {
424
- params.isResolved = true
425
- exn->ErrorHandling.mkLogAndRaise(~msg=errorMessage, ~logger=item->Logging.getItemLogger)
426
- }),
427
- )
428
- } else {
429
- params.isResolved = true
430
- }
431
- } catch {
432
- | exn =>
433
- exn->ErrorHandling.mkLogAndRaise(~msg=errorMessage, ~logger=item->Logging.getItemLogger)
434
- }
435
- }
436
-
437
- if promises->Utils.Array.notEmpty {
438
- let _ = await Promise.all(promises)
439
- }
440
-
441
- itemsWithDcs
442
- }
443
-
444
- let handleQueryResult = (
445
- chainFetcher: t,
446
- ~query: FetchState.query,
447
- ~newItems,
448
- ~newItemsWithDcs,
449
- ~latestFetchedBlock,
450
- ~knownHeight,
451
- ) => {
452
- let fs = switch newItemsWithDcs {
453
- | [] => chainFetcher.fetchState
454
- | _ => chainFetcher.fetchState->FetchState.registerDynamicContracts(newItemsWithDcs)
455
- }
456
-
457
- {
458
- ...chainFetcher,
459
- fetchState: fs
460
- ->FetchState.handleQueryResult(~query, ~latestFetchedBlock, ~newItems)
461
- ->FetchState.updateKnownHeight(~knownHeight),
462
- }
463
- }
464
-
465
- /**
466
- Gets the latest item on the front of the queue and returns updated fetcher
467
- */
468
- let hasProcessedToEndblock = (self: t) => {
469
- let {committedProgressBlockNumber, fetchState} = self
470
- switch fetchState.endBlock {
471
- | Some(endBlock) => committedProgressBlockNumber >= endBlock
472
- | None => false
473
- }
474
- }
475
-
476
- let hasNoMoreEventsToProcess = (self: t) => {
477
- self.fetchState->FetchState.bufferSize === 0
478
- }
479
-
480
- let getHighestBlockBelowThreshold = (cf: t): int => {
481
- let highestBlockBelowThreshold = cf.fetchState.knownHeight - cf.chainConfig.maxReorgDepth
482
- highestBlockBelowThreshold < 0 ? 0 : highestBlockBelowThreshold
483
- }
484
-
485
- /**
486
- Finds the last known valid block number below the reorg block
487
- If not found, returns the highest block below threshold
488
- */
489
- let getLastKnownValidBlock = async (chainFetcher: t, ~reorgBlockNumber: int, ~isRealtime: bool) => {
490
- // Don't include the reorg block itself — different source instances
491
- // may have mismatching hashes at the head, so we always rollback
492
- // the block where we detected the reorg.
493
- let scannedBlockNumbers =
494
- chainFetcher.reorgDetection->ReorgDetection.getThresholdBlockNumbersBelowBlock(
495
- ~blockNumber=reorgBlockNumber,
496
- ~knownHeight=chainFetcher.fetchState.knownHeight,
497
- )
498
-
499
- switch scannedBlockNumbers {
500
- | [] => chainFetcher->getHighestBlockBelowThreshold
501
- | _ => {
502
- let blockNumbersAndHashes = await chainFetcher.sourceManager->SourceManager.getBlockHashes(
503
- ~blockNumbers=scannedBlockNumbers,
504
- ~isRealtime,
505
- )
506
-
507
- switch chainFetcher.reorgDetection->ReorgDetection.getLatestValidScannedBlock(
508
- ~blockNumbersAndHashes,
509
- ) {
510
- | Some(blockNumber) => blockNumber
511
- | None => chainFetcher->getHighestBlockBelowThreshold
512
- }
513
- }
514
- }
515
- }
516
-
517
- let isActivelyIndexing = (chainFetcher: t) => chainFetcher.fetchState->FetchState.isActivelyIndexing
518
-
519
- let isReady = (chainFetcher: t) => chainFetcher.timestampCaughtUpToHeadOrEndblock !== None