envio 3.1.0-rc.0 → 3.1.0-rc.2

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 (59) hide show
  1. package/package.json +6 -6
  2. package/src/Batch.res +7 -1
  3. package/src/Batch.res.mjs +2 -1
  4. package/src/ChainFetcher.res +8 -23
  5. package/src/ChainFetcher.res.mjs +2 -10
  6. package/src/ChainManager.res +3 -2
  7. package/src/ChainManager.res.mjs +3 -3
  8. package/src/Env.res +6 -7
  9. package/src/Env.res.mjs +3 -3
  10. package/src/EventProcessing.res +24 -122
  11. package/src/EventProcessing.res.mjs +24 -88
  12. package/src/FetchState.res +1 -4
  13. package/src/FetchState.res.mjs +1 -2
  14. package/src/GlobalState.res +33 -54
  15. package/src/GlobalState.res.mjs +55 -34
  16. package/src/GlobalStateManager.res +1 -3
  17. package/src/InMemoryStore.res +400 -86
  18. package/src/InMemoryStore.res.mjs +330 -70
  19. package/src/InMemoryTable.res +53 -18
  20. package/src/InMemoryTable.res.mjs +38 -18
  21. package/src/Internal.res +3 -0
  22. package/src/LoadLayer.res +9 -7
  23. package/src/LoadLayer.res.mjs +4 -10
  24. package/src/LogSelection.res +15 -19
  25. package/src/LogSelection.res.mjs +5 -6
  26. package/src/Main.res +14 -1
  27. package/src/Main.res.mjs +8 -3
  28. package/src/Persistence.res +3 -1
  29. package/src/PgStorage.res +171 -68
  30. package/src/PgStorage.res.mjs +125 -39
  31. package/src/TestIndexerProxyStorage.res +1 -1
  32. package/src/TestIndexerProxyStorage.res.mjs +1 -1
  33. package/src/Throttler.res +22 -15
  34. package/src/Throttler.res.mjs +19 -14
  35. package/src/UserContext.res +1 -0
  36. package/src/UserContext.res.mjs +3 -1
  37. package/src/bindings/NodeJs.res +1 -0
  38. package/src/sources/EnvioApiClient.res +15 -0
  39. package/src/sources/EnvioApiClient.res.mjs +24 -0
  40. package/src/sources/EvmChain.res +0 -1
  41. package/src/sources/EvmChain.res.mjs +0 -1
  42. package/src/sources/HyperFuelSource.res +1 -1
  43. package/src/sources/HyperFuelSource.res.mjs +2 -1
  44. package/src/sources/HyperSync.res +20 -1
  45. package/src/sources/HyperSync.res.mjs +26 -1
  46. package/src/sources/HyperSyncClient.res +3 -2
  47. package/src/sources/HyperSyncClient.res.mjs +2 -2
  48. package/src/sources/HyperSyncSource.res +18 -19
  49. package/src/sources/HyperSyncSource.res.mjs +40 -14
  50. package/src/sources/Source.res +2 -0
  51. package/src/sources/Source.res.mjs +3 -0
  52. package/src/sources/SourceManager.res +168 -8
  53. package/src/sources/SourceManager.res.mjs +131 -3
  54. package/src/sources/SourceManager.resi +17 -0
  55. package/src/tui/Tui.res +44 -6
  56. package/src/tui/Tui.res.mjs +56 -8
  57. package/src/tui/components/TuiData.res +3 -0
  58. package/src/sources/HyperSyncJsonApi.res +0 -390
  59. package/src/sources/HyperSyncJsonApi.res.mjs +0 -237
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.1.0-rc.0",
3
+ "version": "3.1.0-rc.2",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -70,10 +70,10 @@
70
70
  "tsx": "4.21.0"
71
71
  },
72
72
  "optionalDependencies": {
73
- "envio-linux-x64": "3.1.0-rc.0",
74
- "envio-linux-x64-musl": "3.1.0-rc.0",
75
- "envio-linux-arm64": "3.1.0-rc.0",
76
- "envio-darwin-x64": "3.1.0-rc.0",
77
- "envio-darwin-arm64": "3.1.0-rc.0"
73
+ "envio-linux-x64": "3.1.0-rc.2",
74
+ "envio-linux-x64-musl": "3.1.0-rc.2",
75
+ "envio-linux-arm64": "3.1.0-rc.2",
76
+ "envio-darwin-x64": "3.1.0-rc.2",
77
+ "envio-darwin-arm64": "3.1.0-rc.2"
78
78
  }
79
79
  }
package/src/Batch.res CHANGED
@@ -23,6 +23,9 @@ type t = {
23
23
  totalBatchSize: int,
24
24
  items: array<Internal.item>,
25
25
  progressedChainsById: dict<chainAfterBatch>,
26
+ // Processed inside the reorg threshold. Drives whether history is saved, so
27
+ // writes never merge across a change in this value.
28
+ isInReorgThreshold: bool,
26
29
  // Unnest-like checkpoint fields:
27
30
  checkpointIds: array<bigint>,
28
31
  checkpointChainIds: array<int>,
@@ -165,6 +168,7 @@ let prepareBatch = (
165
168
  ~checkpointIdBeforeBatch,
166
169
  ~chainsBeforeBatch: ChainMap.t<chainBeforeBatch>,
167
170
  ~batchSizeTarget,
171
+ ~isInReorgThreshold,
168
172
  ) => {
169
173
  let preparedFetchStates =
170
174
  chainsBeforeBatch
@@ -286,6 +290,7 @@ let prepareBatch = (
286
290
  ~batchSizePerChain=mutBatchSizePerChain,
287
291
  ~progressBlockNumberPerChain=mutProgressBlockNumberPerChain,
288
292
  ),
293
+ isInReorgThreshold,
289
294
  checkpointIds,
290
295
  checkpointChainIds,
291
296
  checkpointBlockNumbers,
@@ -298,8 +303,9 @@ let make = (
298
303
  ~checkpointIdBeforeBatch,
299
304
  ~chainsBeforeBatch: ChainMap.t<chainBeforeBatch>,
300
305
  ~batchSizeTarget,
306
+ ~isInReorgThreshold,
301
307
  ) => {
302
- prepareBatch(~checkpointIdBeforeBatch, ~chainsBeforeBatch, ~batchSizeTarget)
308
+ prepareBatch(~checkpointIdBeforeBatch, ~chainsBeforeBatch, ~batchSizeTarget, ~isInReorgThreshold)
303
309
  }
304
310
 
305
311
  let findFirstEventBlockNumber = (batch: t, ~chainId) => {
package/src/Batch.res.mjs CHANGED
@@ -70,7 +70,7 @@ function addReorgCheckpoints(prevCheckpointId, reorgDetection, fromBlockExclusiv
70
70
  return prevCheckpointId$1;
71
71
  }
72
72
 
73
- function prepareBatch(checkpointIdBeforeBatch, chainsBeforeBatch, batchSizeTarget) {
73
+ function prepareBatch(checkpointIdBeforeBatch, chainsBeforeBatch, batchSizeTarget, isInReorgThreshold) {
74
74
  let preparedFetchStates = FetchState.sortForUnorderedBatch(ChainMap.values(chainsBeforeBatch).map(chainBeforeBatch => chainBeforeBatch.fetchState), batchSizeTarget);
75
75
  let chainIdx = 0;
76
76
  let preparedNumber = preparedFetchStates.length;
@@ -168,6 +168,7 @@ function prepareBatch(checkpointIdBeforeBatch, chainsBeforeBatch, batchSizeTarge
168
168
  totalBatchSize: totalBatchSize,
169
169
  items: items,
170
170
  progressedChainsById: getProgressedChainsById(chainsBeforeBatch, mutBatchSizePerChain, mutProgressBlockNumberPerChain),
171
+ isInReorgThreshold: isInReorgThreshold,
171
172
  checkpointIds: checkpointIds,
172
173
  checkpointChainIds: checkpointChainIds,
173
174
  checkpointBlockNumbers: checkpointBlockNumbers,
@@ -462,38 +462,23 @@ let getHighestBlockBelowThreshold = (cf: t): int => {
462
462
  Finds the last known valid block number below the reorg block
463
463
  If not found, returns the highest block below threshold
464
464
  */
465
- let getLastKnownValidBlock = async (
466
- chainFetcher: t,
467
- ~reorgBlockNumber: int,
468
- //Parameter used for dependency injecting in tests
469
- ~getBlockHashes=(chainFetcher.sourceManager->SourceManager.getActiveSource).getBlockHashes,
470
- ) => {
471
- // Improtant: It's important to not include the reorg detection block number
472
- // because there might be different instances of the source
473
- // with mismatching hashes between them.
474
- // So we MUST always rollback the block number where we detected a reorg.
465
+ let getLastKnownValidBlock = async (chainFetcher: t, ~reorgBlockNumber: int, ~isRealtime: bool) => {
466
+ // Don't include the reorg block itself — different source instances
467
+ // may have mismatching hashes at the head, so we always rollback
468
+ // the block where we detected the reorg.
475
469
  let scannedBlockNumbers =
476
470
  chainFetcher.reorgDetection->ReorgDetection.getThresholdBlockNumbersBelowBlock(
477
471
  ~blockNumber=reorgBlockNumber,
478
472
  ~knownHeight=chainFetcher.fetchState.knownHeight,
479
473
  )
480
474
 
481
- let getBlockHashes = blockNumbers => {
482
- getBlockHashes(~blockNumbers, ~logger=chainFetcher.logger)->Promise.thenResolve(res =>
483
- switch res {
484
- | Ok(v) => v
485
- | Error(exn) =>
486
- exn->ErrorHandling.mkLogAndRaise(
487
- ~msg="Failed to fetch blockHashes for given blockNumbers during rollback",
488
- )
489
- }
490
- )
491
- }
492
-
493
475
  switch scannedBlockNumbers {
494
476
  | [] => chainFetcher->getHighestBlockBelowThreshold
495
477
  | _ => {
496
- let blockNumbersAndHashes = await getBlockHashes(scannedBlockNumbers)
478
+ let blockNumbersAndHashes = await chainFetcher.sourceManager->SourceManager.getBlockHashes(
479
+ ~blockNumbers=scannedBlockNumbers,
480
+ ~isRealtime,
481
+ )
497
482
 
498
483
  switch chainFetcher.reorgDetection->ReorgDetection.getLatestValidScannedBlock(
499
484
  ~blockNumbersAndHashes,
@@ -261,20 +261,12 @@ function getHighestBlockBelowThreshold(cf) {
261
261
  }
262
262
  }
263
263
 
264
- async function getLastKnownValidBlock(chainFetcher, reorgBlockNumber, getBlockHashesOpt) {
265
- let getBlockHashes = getBlockHashesOpt !== undefined ? getBlockHashesOpt : SourceManager.getActiveSource(chainFetcher.sourceManager).getBlockHashes;
264
+ async function getLastKnownValidBlock(chainFetcher, reorgBlockNumber, isRealtime) {
266
265
  let scannedBlockNumbers = ReorgDetection.getThresholdBlockNumbersBelowBlock(chainFetcher.reorgDetection, reorgBlockNumber, chainFetcher.fetchState.knownHeight);
267
- let getBlockHashes$1 = blockNumbers => getBlockHashes(blockNumbers, chainFetcher.logger).then(res => {
268
- if (res.TAG === "Ok") {
269
- return res._0;
270
- } else {
271
- return ErrorHandling.mkLogAndRaise(undefined, "Failed to fetch blockHashes for given blockNumbers during rollback", res._0);
272
- }
273
- });
274
266
  if (scannedBlockNumbers.length === 0) {
275
267
  return getHighestBlockBelowThreshold(chainFetcher);
276
268
  }
277
- let blockNumbersAndHashes = await getBlockHashes$1(scannedBlockNumbers);
269
+ let blockNumbersAndHashes = await SourceManager.getBlockHashes(chainFetcher.sourceManager, scannedBlockNumbers, isRealtime);
278
270
  let blockNumber = ReorgDetection.getLatestValidScannedBlock(chainFetcher.reorgDetection, blockNumbersAndHashes);
279
271
  if (blockNumber !== undefined) {
280
272
  return blockNumber;
@@ -135,12 +135,13 @@ let nextItemIsNone = (chainManager: t): bool => {
135
135
 
136
136
  let createBatch = (
137
137
  chainManager: t,
138
- ~committedCheckpointId,
138
+ ~processedCheckpointId,
139
139
  ~batchSizeTarget: int,
140
140
  ~isRollback: bool,
141
141
  ): Batch.t => {
142
142
  Batch.make(
143
- ~checkpointIdBeforeBatch=committedCheckpointId->BigInt.add(
143
+ ~isInReorgThreshold=chainManager.isInReorgThreshold,
144
+ ~checkpointIdBeforeBatch=processedCheckpointId->BigInt.add(
144
145
  // Since for rollback we have a diff checkpoint id.
145
146
  // This is needed to currectly overwrite old state
146
147
  // in an append-only ClickHouse insert.
@@ -99,8 +99,8 @@ function nextItemIsNone(chainManager) {
99
99
  return !Batch.hasReadyItem(ChainMap.map(chainManager.chainFetchers, cf => cf.fetchState));
100
100
  }
101
101
 
102
- function createBatch(chainManager, committedCheckpointId, batchSizeTarget, isRollback) {
103
- return Batch.make(committedCheckpointId + (
102
+ function createBatch(chainManager, processedCheckpointId, batchSizeTarget, isRollback) {
103
+ return Batch.make(processedCheckpointId + (
104
104
  isRollback ? 1n : 0n
105
105
  ), ChainMap.map(chainManager.chainFetchers, cf => ({
106
106
  fetchState: cf.fetchState,
@@ -109,7 +109,7 @@ function createBatch(chainManager, committedCheckpointId, batchSizeTarget, isRol
109
109
  sourceBlockNumber: cf.fetchState.knownHeight,
110
110
  totalEventsProcessed: cf.numEventsProcessed,
111
111
  chainConfig: cf.chainConfig
112
- })), batchSizeTarget);
112
+ })), batchSizeTarget, chainManager.isInReorgThreshold);
113
113
  }
114
114
 
115
115
  function isProgressAtHead(chainManager) {
package/src/Env.res CHANGED
@@ -11,6 +11,12 @@ let maxAddrInPartition = envSafe->EnvSafe.get("MAX_PARTITION_SIZE", S.int, ~fall
11
11
  let maxPartitionConcurrency =
12
12
  envSafe->EnvSafe.get("ENVIO_MAX_PARTITION_CONCURRENCY", S.int, ~fallback=10)
13
13
 
14
+ // Target number of in-memory objects (uncommitted entity/effect changes plus
15
+ // unwritten batch items) the store holds before processing waits for the write
16
+ // cycle to catch up.
17
+ let inMemoryObjectsTarget =
18
+ envSafe->EnvSafe.get("ENVIO_IN_MEMORY_OBJECTS_TARGET", S.int, ~fallback=100_000)->Belt.Int.toFloat
19
+
14
20
  // FIXME: This broke HS grafana dashboard. Should investigate it later. Maybe we should use :: as a default value?
15
21
  // We want to be able to set it to 0.0.0.0
16
22
  // to allow to passthrough the port from a Docker container
@@ -47,13 +53,6 @@ let envioApiToken = envSafe->EnvSafe.get("ENVIO_API_TOKEN", S.option(S.string))
47
53
  let hyperSyncClientTimeoutMillis =
48
54
  envSafe->EnvSafe.get("ENVIO_HYPERSYNC_CLIENT_TIMEOUT_MILLIS", S.int, ~fallback=120_000)
49
55
 
50
- /**
51
- This is the number of retries that the binary client makes before rejecting the promise with an error
52
- Default is 0 so that the indexer can handle retries internally
53
- */
54
- let hyperSyncClientMaxRetries =
55
- envSafe->EnvSafe.get("ENVIO_HYPERSYNC_CLIENT_MAX_RETRIES", S.int, ~fallback=0)
56
-
57
56
  let hypersyncClientSerializationFormat =
58
57
  envSafe->EnvSafe.get(
59
58
  "ENVIO_HYPERSYNC_CLIENT_SERIALIZATION_FORMAT",
package/src/Env.res.mjs CHANGED
@@ -21,6 +21,8 @@ let maxAddrInPartition = EnvSafe.get(envSafe, "MAX_PARTITION_SIZE", S$RescriptSc
21
21
 
22
22
  let maxPartitionConcurrency = EnvSafe.get(envSafe, "ENVIO_MAX_PARTITION_CONCURRENCY", S$RescriptSchema.int, undefined, 10, undefined, undefined);
23
23
 
24
+ let inMemoryObjectsTarget = EnvSafe.get(envSafe, "ENVIO_IN_MEMORY_OBJECTS_TARGET", S$RescriptSchema.int, undefined, 100000, undefined, undefined);
25
+
24
26
  let serverPort = EnvSafe.get(envSafe, "ENVIO_INDEXER_PORT", S$RescriptSchema.port(S$RescriptSchema.int, undefined), undefined, EnvSafe.get(envSafe, "METRICS_PORT", S$RescriptSchema.port(S$RescriptSchema.int, undefined), undefined, 9898, undefined, undefined), undefined, undefined);
25
27
 
26
28
  let tuiEnvVar = EnvSafe.get(envSafe, "ENVIO_TUI", S$RescriptSchema.option(S$RescriptSchema.bool), undefined, undefined, undefined, undefined);
@@ -53,8 +55,6 @@ let envioApiToken = EnvSafe.get(envSafe, "ENVIO_API_TOKEN", S$RescriptSchema.opt
53
55
 
54
56
  let hyperSyncClientTimeoutMillis = EnvSafe.get(envSafe, "ENVIO_HYPERSYNC_CLIENT_TIMEOUT_MILLIS", S$RescriptSchema.int, undefined, 120000, undefined, undefined);
55
57
 
56
- let hyperSyncClientMaxRetries = EnvSafe.get(envSafe, "ENVIO_HYPERSYNC_CLIENT_MAX_RETRIES", S$RescriptSchema.int, undefined, 0, undefined, undefined);
57
-
58
58
  let hypersyncClientSerializationFormat = EnvSafe.get(envSafe, "ENVIO_HYPERSYNC_CLIENT_SERIALIZATION_FORMAT", HyperSyncClient.serializationFormatSchema, undefined, "CapnProto", undefined, undefined);
59
59
 
60
60
  let hypersyncClientEnableQueryCaching = EnvSafe.get(envSafe, "ENVIO_HYPERSYNC_CLIENT_ENABLE_QUERY_CACHING", S$RescriptSchema.bool, undefined, true, undefined, undefined);
@@ -223,6 +223,7 @@ export {
223
223
  targetBufferSize,
224
224
  maxAddrInPartition,
225
225
  maxPartitionConcurrency,
226
+ inMemoryObjectsTarget,
226
227
  serverPort,
227
228
  tuiEnvVar,
228
229
  logLevelSchema,
@@ -233,7 +234,6 @@ export {
233
234
  envioAppUrl,
234
235
  envioApiToken,
235
236
  hyperSyncClientTimeoutMillis,
236
- hyperSyncClientMaxRetries,
237
237
  hypersyncClientSerializationFormat,
238
238
  hypersyncClientEnableQueryCaching,
239
239
  hypersyncLogLevel,
@@ -25,77 +25,6 @@ let computeChainsState = (chainFetchers: ChainMap.t<ChainFetcher.t>): Internal.c
25
25
  chains
26
26
  }
27
27
 
28
- let convertFieldsToJson = (fields: option<dict<unknown>>) => {
29
- switch fields {
30
- | None => %raw(`{}`)
31
- | Some(fields) =>
32
- // Convert bigint fields to string. There are no fields with nested
33
- // bigints, so iterating only the top level is safe.
34
- fields
35
- ->Utils.Dict.mapValues(value =>
36
- typeof(value) === #bigint
37
- ? value
38
- ->(Utils.magic: unknown => bigint)
39
- ->BigInt.toString
40
- ->(Utils.magic: string => unknown)
41
- : value
42
- )
43
- ->(Utils.magic: dict<unknown> => JSON.t)
44
- }
45
- }
46
-
47
- let addItemToRawEvents = (
48
- eventItem: Internal.eventItem,
49
- ~inMemoryStore: InMemoryStore.t,
50
- ~config: Config.t,
51
- ) => {
52
- let {event, eventConfig, chain, blockNumber, timestamp: blockTimestamp} = eventItem
53
- let {block, transaction, params, logIndex, srcAddress} = event
54
- let chainId = chain->ChainMap.Chain.toChainId
55
- let eventId = EventUtils.packEventIndex(~logIndex, ~blockNumber)
56
- let blockFields =
57
- block
58
- ->(Utils.magic: Internal.eventBlock => option<dict<unknown>>)
59
- ->convertFieldsToJson
60
- let transactionFields =
61
- transaction
62
- ->(Utils.magic: Internal.eventTransaction => option<dict<unknown>>)
63
- ->convertFieldsToJson
64
-
65
- blockFields->config.ecosystem.cleanUpRawEventFieldsInPlace
66
-
67
- // Serialize to unknown, because serializing to Js.Json.t fails for Bytes Fuel type, since it has unknown schema
68
- let params =
69
- params
70
- ->S.reverseConvertOrThrow(eventConfig.paramsRawEventSchema)
71
- ->(Utils.magic: unknown => JSON.t)
72
- let params = if params === %raw(`null`) {
73
- // Should probably make the params field nullable
74
- // But this is currently needed to make events
75
- // with empty params work
76
- %raw(`"null"`)
77
- } else {
78
- params
79
- }
80
-
81
- let rawEvent: InternalTable.RawEvents.t = {
82
- chainId,
83
- eventId,
84
- eventName: eventConfig.name,
85
- contractName: eventConfig.contractName,
86
- blockNumber,
87
- logIndex,
88
- srcAddress,
89
- blockHash: block->config.ecosystem.getId,
90
- blockTimestamp,
91
- blockFields,
92
- transactionFields,
93
- params,
94
- }
95
-
96
- inMemoryStore.rawEvents->Array.push(rawEvent)
97
- }
98
-
99
28
  exception ProcessingError({message: string, exn: exn, item: Internal.item})
100
29
 
101
30
  let runEventHandlerOrThrow = async (
@@ -192,26 +121,18 @@ let runHandlerOrThrow = async (
192
121
  }),
193
122
  )
194
123
  }
195
- | Event({eventConfig}) => {
196
- switch eventConfig.handler {
197
- | Some(handler) =>
198
- await item->runEventHandlerOrThrow(
199
- ~handler,
200
- ~checkpointId,
201
- ~inMemoryStore,
202
- ~loadManager,
203
- ~persistence=ctx.persistence,
204
- ~chains,
205
- ~config=ctx.config,
206
- )
207
- | None => ()
208
- }
209
-
210
- if ctx.config.enableRawEvents {
211
- item
212
- ->Internal.castUnsafeEventItem
213
- ->addItemToRawEvents(~inMemoryStore, ~config=ctx.config)
214
- }
124
+ | Event({eventConfig}) => switch eventConfig.handler {
125
+ | Some(handler) =>
126
+ await item->runEventHandlerOrThrow(
127
+ ~handler,
128
+ ~checkpointId,
129
+ ~inMemoryStore,
130
+ ~loadManager,
131
+ ~persistence=ctx.persistence,
132
+ ~chains,
133
+ ~config=ctx.config,
134
+ )
135
+ | None => ()
215
136
  }
216
137
  }
217
138
  }
@@ -333,17 +254,11 @@ let runBatchHandlersOrThrow = async (
333
254
  }
334
255
  }
335
256
 
336
- let registerProcessEventBatchMetrics = (
337
- ~logger,
338
- ~loadDuration,
339
- ~handlerDuration,
340
- ~dbWriteDuration,
341
- ) => {
257
+ let registerProcessEventBatchMetrics = (~logger, ~loadDuration, ~handlerDuration) => {
342
258
  logger->Logging.childTrace({
343
259
  "msg": "Finished processing batch",
344
260
  "loader_time_elapsed": loadDuration,
345
261
  "handlers_time_elapsed": handlerDuration,
346
- "write_time_elapsed": dbWriteDuration,
347
262
  })
348
263
 
349
264
  Prometheus.ProcessingBatch.registerMetrics(~loadDuration, ~handlerDuration)
@@ -359,7 +274,6 @@ type logPartitionInfo = {
359
274
  let processEventBatch = async (
360
275
  ~batch: Batch.t,
361
276
  ~inMemoryStore: InMemoryStore.t,
362
- ~isInReorgThreshold,
363
277
  ~loadManager,
364
278
  ~ctx: Ctx.t,
365
279
  ~chainFetchers: ChainMap.t<ChainFetcher.t>,
@@ -381,6 +295,9 @@ let processEventBatch = async (
381
295
  })
382
296
 
383
297
  try {
298
+ // Backpressure: keep processing within keepLatestChangesLimit of the cycle.
299
+ await inMemoryStore->InMemoryStore.awaitCapacity
300
+
384
301
  let timeRef = Hrtime.makeTimer()
385
302
 
386
303
  if batch.items->Utils.Array.notEmpty {
@@ -401,34 +318,19 @@ let processEventBatch = async (
401
318
 
402
319
  let elapsedTimeAfterProcessing = timeRef->Hrtime.timeSince->Hrtime.toSecondsFloat
403
320
 
404
- try {
405
- await inMemoryStore->InMemoryStore.writeBatch(
406
- ~persistence=ctx.persistence,
407
- ~batch,
408
- ~config=ctx.config,
409
- ~isInReorgThreshold,
410
- )
321
+ inMemoryStore->InMemoryStore.commitBatch(~batch)
411
322
 
412
- let elapsedTimeAfterDbWrite = timeRef->Hrtime.timeSince->Hrtime.toSecondsFloat
413
- let loaderDuration = elapsedTimeAfterLoaders
414
- let handlerDuration = elapsedTimeAfterProcessing -. loaderDuration
415
- let dbWriteDuration = elapsedTimeAfterDbWrite -. elapsedTimeAfterProcessing
416
- registerProcessEventBatchMetrics(
417
- ~logger,
418
- ~loadDuration=loaderDuration,
419
- ~handlerDuration,
420
- ~dbWriteDuration,
421
- )
422
- Ok()
423
- } catch {
424
- | Persistence.StorageError({message, reason}) =>
425
- reason->ErrorHandling.make(~msg=message, ~logger)->Error
426
- | exn => exn->ErrorHandling.make(~msg="Failed writing batch to database", ~logger)->Error
427
- }
323
+ let loaderDuration = elapsedTimeAfterLoaders
324
+ let handlerDuration = elapsedTimeAfterProcessing -. loaderDuration
325
+ registerProcessEventBatchMetrics(~logger, ~loadDuration=loaderDuration, ~handlerDuration)
326
+ Ok()
428
327
  } catch {
328
+ | Persistence.StorageError({message, reason}) =>
329
+ reason->ErrorHandling.make(~msg=message, ~logger)->Error
429
330
  | ProcessingError({message, exn, item}) =>
430
331
  exn
431
332
  ->ErrorHandling.make(~msg=message, ~logger=item->Logging.getItemLogger)
432
333
  ->Error
334
+ | exn => exn->ErrorHandling.make(~msg="Failed processing batch", ~logger)->Error
433
335
  }
434
336
  }
@@ -5,14 +5,12 @@ import * as Hrtime from "./bindings/Hrtime.res.mjs";
5
5
  import * as Logging from "./Logging.res.mjs";
6
6
  import * as ChainMap from "./ChainMap.res.mjs";
7
7
  import * as Ecosystem from "./Ecosystem.res.mjs";
8
- import * as EventUtils from "./EventUtils.res.mjs";
9
8
  import * as Prometheus from "./Prometheus.res.mjs";
10
9
  import * as Persistence from "./Persistence.res.mjs";
11
10
  import * as UserContext from "./UserContext.res.mjs";
12
11
  import * as ChainFetcher from "./ChainFetcher.res.mjs";
13
12
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
14
13
  import * as InMemoryStore from "./InMemoryStore.res.mjs";
15
- import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
16
14
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
17
15
 
18
16
  function allChainsEventsProcessedToEndblock(chainFetchers) {
@@ -31,55 +29,6 @@ function computeChainsState(chainFetchers) {
31
29
  return chains;
32
30
  }
33
31
 
34
- function convertFieldsToJson(fields) {
35
- if (fields !== undefined) {
36
- return Utils.Dict.mapValues(fields, value => {
37
- if (typeof value === "bigint") {
38
- return value.toString();
39
- } else {
40
- return value;
41
- }
42
- });
43
- } else {
44
- return {};
45
- }
46
- }
47
-
48
- function addItemToRawEvents(eventItem, inMemoryStore, config) {
49
- let event = eventItem.event;
50
- let block = event.block;
51
- let logIndex = event.logIndex;
52
- let blockNumber = eventItem.blockNumber;
53
- let eventConfig = eventItem.eventConfig;
54
- let eventId = EventUtils.packEventIndex(blockNumber, logIndex);
55
- let blockFields = convertFieldsToJson(block);
56
- let transactionFields = convertFieldsToJson(event.transaction);
57
- config.ecosystem.cleanUpRawEventFieldsInPlace(blockFields);
58
- let params = S$RescriptSchema.reverseConvertOrThrow(event.params, eventConfig.paramsRawEventSchema);
59
- let params$1 = params === null ? "null" : params;
60
- let rawEvent_chain_id = eventItem.chain;
61
- let rawEvent_event_name = eventConfig.name;
62
- let rawEvent_contract_name = eventConfig.contractName;
63
- let rawEvent_src_address = event.srcAddress;
64
- let rawEvent_block_hash = config.ecosystem.getId(block);
65
- let rawEvent_block_timestamp = eventItem.timestamp;
66
- let rawEvent = {
67
- chain_id: rawEvent_chain_id,
68
- event_id: eventId,
69
- event_name: rawEvent_event_name,
70
- contract_name: rawEvent_contract_name,
71
- block_number: blockNumber,
72
- log_index: logIndex,
73
- src_address: rawEvent_src_address,
74
- block_hash: rawEvent_block_hash,
75
- block_timestamp: rawEvent_block_timestamp,
76
- block_fields: blockFields,
77
- transaction_fields: transactionFields,
78
- params: params$1
79
- };
80
- inMemoryStore.rawEvents.push(rawEvent);
81
- }
82
-
83
32
  let ProcessingError = /* @__PURE__ */Primitive_exceptions.create("EventProcessing.ProcessingError");
84
33
 
85
34
  async function runEventHandlerOrThrow(item, checkpointId, handler, inMemoryStore, loadManager, persistence, chains, config) {
@@ -119,10 +68,7 @@ async function runHandlerOrThrow(item, checkpointId, inMemoryStore, loadManager,
119
68
  if (item.kind === 0) {
120
69
  let handler = item.eventConfig.handler;
121
70
  if (handler !== undefined) {
122
- await runEventHandlerOrThrow(item, checkpointId, handler, inMemoryStore, loadManager, ctx.persistence, chains, ctx.config);
123
- }
124
- if (ctx.config.enableRawEvents) {
125
- return addItemToRawEvents(item, inMemoryStore, ctx.config);
71
+ return await runEventHandlerOrThrow(item, checkpointId, handler, inMemoryStore, loadManager, ctx.persistence, chains, ctx.config);
126
72
  } else {
127
73
  return;
128
74
  }
@@ -224,17 +170,16 @@ async function runBatchHandlersOrThrow(batch, inMemoryStore, loadManager, ctx, c
224
170
  }
225
171
  }
226
172
 
227
- function registerProcessEventBatchMetrics(logger, loadDuration, handlerDuration, dbWriteDuration) {
173
+ function registerProcessEventBatchMetrics(logger, loadDuration, handlerDuration) {
228
174
  Logging.childTrace(logger, {
229
175
  msg: "Finished processing batch",
230
176
  loader_time_elapsed: loadDuration,
231
- handlers_time_elapsed: handlerDuration,
232
- write_time_elapsed: dbWriteDuration
177
+ handlers_time_elapsed: handlerDuration
233
178
  });
234
179
  Prometheus.ProcessingBatch.registerMetrics(loadDuration, handlerDuration);
235
180
  }
236
181
 
237
- async function processEventBatch(batch, inMemoryStore, isInReorgThreshold, loadManager, ctx, chainFetchers) {
182
+ async function processEventBatch(batch, inMemoryStore, loadManager, ctx, chainFetchers) {
238
183
  let totalBatchSize = batch.totalBatchSize;
239
184
  let chains = computeChainsState(chainFetchers);
240
185
  let logger = Logging.getLogger();
@@ -247,6 +192,7 @@ async function processEventBatch(batch, inMemoryStore, isInReorgThreshold, loadM
247
192
  }))
248
193
  });
249
194
  try {
195
+ await InMemoryStore.awaitCapacity(inMemoryStore);
250
196
  let timeRef = Hrtime.makeTimer();
251
197
  if (Utils.$$Array.notEmpty(batch.items)) {
252
198
  await preloadBatchOrThrow(batch, loadManager, ctx.persistence, ctx.config, inMemoryStore, chains);
@@ -256,47 +202,37 @@ async function processEventBatch(batch, inMemoryStore, isInReorgThreshold, loadM
256
202
  await runBatchHandlersOrThrow(batch, inMemoryStore, loadManager, ctx, chains);
257
203
  }
258
204
  let elapsedTimeAfterProcessing = Hrtime.toSecondsFloat(Hrtime.timeSince(timeRef));
259
- try {
260
- await InMemoryStore.writeBatch(inMemoryStore, ctx.persistence, batch, ctx.config, isInReorgThreshold);
261
- let elapsedTimeAfterDbWrite = Hrtime.toSecondsFloat(Hrtime.timeSince(timeRef));
262
- let handlerDuration = elapsedTimeAfterProcessing - elapsedTimeAfterLoaders;
263
- let dbWriteDuration = elapsedTimeAfterDbWrite - elapsedTimeAfterProcessing;
264
- registerProcessEventBatchMetrics(logger, elapsedTimeAfterLoaders, handlerDuration, dbWriteDuration);
205
+ InMemoryStore.commitBatch(inMemoryStore, batch);
206
+ let handlerDuration = elapsedTimeAfterProcessing - elapsedTimeAfterLoaders;
207
+ registerProcessEventBatchMetrics(logger, elapsedTimeAfterLoaders, handlerDuration);
208
+ return {
209
+ TAG: "Ok",
210
+ _0: undefined
211
+ };
212
+ } catch (raw_exn) {
213
+ let exn = Primitive_exceptions.internalToException(raw_exn);
214
+ if (exn.RE_EXN_ID === Persistence.StorageError) {
265
215
  return {
266
- TAG: "Ok",
267
- _0: undefined
216
+ TAG: "Error",
217
+ _0: ErrorHandling.make(exn.reason, logger, exn.message)
268
218
  };
269
- } catch (raw_exn) {
270
- let exn = Primitive_exceptions.internalToException(raw_exn);
271
- if (exn.RE_EXN_ID === Persistence.StorageError) {
272
- return {
273
- TAG: "Error",
274
- _0: ErrorHandling.make(exn.reason, logger, exn.message)
275
- };
276
- } else {
277
- return {
278
- TAG: "Error",
279
- _0: ErrorHandling.make(exn, logger, "Failed writing batch to database")
280
- };
281
- }
282
- }
283
- } catch (raw_exn$1) {
284
- let exn$1 = Primitive_exceptions.internalToException(raw_exn$1);
285
- if (exn$1.RE_EXN_ID === ProcessingError) {
219
+ } else if (exn.RE_EXN_ID === ProcessingError) {
220
+ return {
221
+ TAG: "Error",
222
+ _0: ErrorHandling.make(exn.exn, Logging.getItemLogger(exn.item), exn.message)
223
+ };
224
+ } else {
286
225
  return {
287
226
  TAG: "Error",
288
- _0: ErrorHandling.make(exn$1.exn, Logging.getItemLogger(exn$1.item), exn$1.message)
227
+ _0: ErrorHandling.make(exn, logger, "Failed processing batch")
289
228
  };
290
229
  }
291
- throw exn$1;
292
230
  }
293
231
  }
294
232
 
295
233
  export {
296
234
  allChainsEventsProcessedToEndblock,
297
235
  computeChainsState,
298
- convertFieldsToJson,
299
- addItemToRawEvents,
300
236
  ProcessingError,
301
237
  runEventHandlerOrThrow,
302
238
  runHandlerOrThrow,
@@ -1730,10 +1730,7 @@ let rollback = (fetchState: t, ~targetBlockNumber) => {
1730
1730
  let addressesToRemove = Utils.Set.make()
1731
1731
  let indexingAddresses = Dict.make()
1732
1732
 
1733
- fetchState.indexingAddresses
1734
- ->Dict.keysToArray
1735
- ->Array.forEach(address => {
1736
- let indexingContract = fetchState.indexingAddresses->Dict.getUnsafe(address)
1733
+ fetchState.indexingAddresses->Utils.Dict.forEachWithKey((indexingContract, address) => {
1737
1734
  if indexingContract.registrationBlock > targetBlockNumber {
1738
1735
  let _ = addressesToRemove->Utils.Set.add(address->Address.unsafeFromString)
1739
1736
  } else {
@@ -1360,8 +1360,7 @@ function rollbackPendingQueries(mutPendingQueries, targetBlockNumber) {
1360
1360
  function rollback(fetchState, targetBlockNumber) {
1361
1361
  let addressesToRemove = new Set();
1362
1362
  let indexingAddresses = {};
1363
- Object.keys(fetchState.indexingAddresses).forEach(address => {
1364
- let indexingContract = fetchState.indexingAddresses[address];
1363
+ Utils.Dict.forEachWithKey(fetchState.indexingAddresses, (indexingContract, address) => {
1365
1364
  if (indexingContract.registrationBlock > targetBlockNumber) {
1366
1365
  addressesToRemove.add(address);
1367
1366
  } else {