envio 3.3.0-alpha.2 → 3.3.0-alpha.3

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 (64) hide show
  1. package/index.d.ts +23 -21
  2. package/package.json +6 -6
  3. package/src/ChainFetching.res +25 -0
  4. package/src/ChainFetching.res.mjs +19 -0
  5. package/src/CrossChainState.res +15 -0
  6. package/src/CrossChainState.res.mjs +12 -0
  7. package/src/Ecosystem.res +0 -2
  8. package/src/EventConfigBuilder.res +42 -18
  9. package/src/EventConfigBuilder.res.mjs +21 -9
  10. package/src/EventProcessing.res +53 -37
  11. package/src/EventProcessing.res.mjs +41 -36
  12. package/src/FetchState.res +53 -43
  13. package/src/FetchState.res.mjs +71 -54
  14. package/src/HandlerLoader.res +10 -1
  15. package/src/HandlerLoader.res.mjs +15 -8
  16. package/src/Internal.res +3 -3
  17. package/src/Internal.res.mjs +1 -1
  18. package/src/LoadLayer.res +5 -5
  19. package/src/LoadLayer.res.mjs +6 -6
  20. package/src/PgStorage.res +4 -4
  21. package/src/PgStorage.res.mjs +5 -5
  22. package/src/Prometheus.res +10 -10
  23. package/src/Prometheus.res.mjs +9 -9
  24. package/src/PruneStaleHistory.res +2 -2
  25. package/src/PruneStaleHistory.res.mjs +3 -3
  26. package/src/Rollback.res +2 -2
  27. package/src/Rollback.res.mjs +3 -3
  28. package/src/SimulateItems.res +77 -15
  29. package/src/SimulateItems.res.mjs +63 -20
  30. package/src/TestIndexer.res +48 -36
  31. package/src/TestIndexer.res.mjs +35 -28
  32. package/src/bindings/Performance.res +7 -0
  33. package/src/bindings/Performance.res.mjs +21 -0
  34. package/src/bindings/Performance.resi +7 -0
  35. package/src/sources/EventRouter.res +17 -21
  36. package/src/sources/EventRouter.res.mjs +6 -9
  37. package/src/sources/Evm.res +1 -35
  38. package/src/sources/Evm.res.mjs +2 -37
  39. package/src/sources/Fuel.res +0 -2
  40. package/src/sources/Fuel.res.mjs +0 -6
  41. package/src/sources/HyperFuelSource.res +10 -11
  42. package/src/sources/HyperFuelSource.res.mjs +11 -11
  43. package/src/sources/HyperSync.res +3 -0
  44. package/src/sources/HyperSync.res.mjs +1 -0
  45. package/src/sources/HyperSync.resi +1 -0
  46. package/src/sources/HyperSyncClient.res +5 -1
  47. package/src/sources/HyperSyncSource.res +42 -24
  48. package/src/sources/HyperSyncSource.res.mjs +30 -26
  49. package/src/sources/RpcSource.res +7 -8
  50. package/src/sources/RpcSource.res.mjs +8 -8
  51. package/src/sources/SimulateSource.res +1 -1
  52. package/src/sources/Source.res +1 -1
  53. package/src/sources/SourceManager.res +17 -16
  54. package/src/sources/SourceManager.res.mjs +10 -15
  55. package/src/sources/Svm.res +4 -10
  56. package/src/sources/Svm.res.mjs +5 -12
  57. package/src/sources/SvmHyperSyncSource.res +24 -18
  58. package/src/sources/SvmHyperSyncSource.res.mjs +22 -15
  59. package/src/sources/TransactionStore.res +59 -42
  60. package/src/sources/TransactionStore.res.mjs +21 -1
  61. package/svm.schema.json +30 -37
  62. package/src/bindings/Hrtime.res +0 -58
  63. package/src/bindings/Hrtime.res.mjs +0 -90
  64. package/src/bindings/Hrtime.resi +0 -30
package/index.d.ts CHANGED
@@ -997,24 +997,6 @@ export type SvmTokenBalance = {
997
997
  readonly postAmount?: string;
998
998
  };
999
999
 
1000
- /** Parent transaction surfaced when an instruction's
1001
- * `include_transaction` flag is `true`. */
1002
- export type SvmTransaction = {
1003
- readonly signatures: readonly string[];
1004
- readonly feePayer?: string;
1005
- readonly success?: boolean;
1006
- readonly err?: string;
1007
- /** Lamports. */
1008
- readonly fee?: bigint;
1009
- readonly computeUnitsConsumed?: bigint;
1010
- readonly accountKeys: readonly string[];
1011
- readonly recentBlockhash?: string;
1012
- readonly version?: string;
1013
- /** SPL Token / Token-2022 balance snapshots for this transaction.
1014
- * Present when `include_token_balances` is `true`. */
1015
- readonly tokenBalances?: readonly SvmTokenBalance[];
1016
- };
1017
-
1018
1000
  export type SvmLog = {
1019
1001
  readonly kind: string;
1020
1002
  readonly message: string;
@@ -1033,6 +1015,7 @@ export type SvmLog = {
1033
1015
  * are base58 strings. */
1034
1016
  export type SvmInstruction<
1035
1017
  Params extends SvmInstructionParams = SvmInstructionParams,
1018
+ Tx = SvmTransaction,
1036
1019
  > = {
1037
1020
  /** Program name as declared under `programs[].name` in `config.yaml`. */
1038
1021
  readonly programName: string;
@@ -1050,8 +1033,11 @@ export type SvmInstruction<
1050
1033
  readonly d8?: string;
1051
1034
  /** Borsh-decoded params. Present when a schema is configured and matched. */
1052
1035
  readonly params?: Params;
1053
- /** Present when the instruction's `include_transaction` is `true`. */
1054
- readonly transaction?: SvmTransaction;
1036
+ /** Parent transaction. Carries only the fields selected via this
1037
+ * instruction's `field_selection`; unselected fields are typed as
1038
+ * `FieldNotSelected<...>` so reading them is a compile error. Always present
1039
+ * (`{}` when no fields are selected). */
1040
+ readonly transaction: Tx;
1055
1041
  /** Present when the instruction's `include_logs` is `true`; only logs
1056
1042
  * scoped to this exact instruction (matching `instruction_address`). */
1057
1043
  readonly logs?: readonly SvmLog[];
@@ -1327,7 +1313,10 @@ type SvmEcosystem<Config extends IndexerConfigTypes = GlobalConfig> =
1327
1313
  handler: (
1328
1314
  args: SvmOnInstructionHandlerArgs<
1329
1315
  Config,
1330
- SvmInstruction<SvmParamsFromProgramTable<Programs[P][I]>>
1316
+ SvmInstruction<
1317
+ SvmParamsFromProgramTable<Programs[P][I]>,
1318
+ Programs[P][I]["transaction"]
1319
+ >
1331
1320
  >,
1332
1321
  ) => Promise<void>,
1333
1322
  ) => void;
@@ -1695,6 +1684,7 @@ type EvmContractsT = GlobalConfig extends { evm: { contracts: infer X extends
1695
1684
  type FuelChainsT = GlobalConfig extends { fuel: { chains: infer X extends Record<string, { id: number }> } } ? X : {};
1696
1685
  type FuelContractsT = GlobalConfig extends { fuel: { contracts: infer X extends Record<string, Record<string, any>> } } ? X : {};
1697
1686
  type SvmChainsT = GlobalConfig extends { svm: { chains: infer X extends Record<string, { id: number }> } } ? X : {};
1687
+ type SvmProgramsT = GlobalConfig extends { svm: { programs: infer X extends Record<string, Record<string, any>> } } ? X : {};
1698
1688
  type EntitiesT = GlobalConfig extends { entities: infer X extends Record<string, object> } ? X : {};
1699
1689
  type EnumsT = GlobalConfig extends { enums: infer X extends Record<string, any> } ? X : {};
1700
1690
 
@@ -1716,6 +1706,18 @@ export type FuelChainId = IsEmptyObject<FuelChainsT> extends true ? NotConfigure
1716
1706
  /** Union of all configured SVM chain IDs. */
1717
1707
  export type SvmChainId = IsEmptyObject<SvmChainsT> extends true ? NotConfigured<"SvmChainId", "Configure SVM chains"> : SvmChainsT [keyof SvmChainsT ]["id"];
1718
1708
 
1709
+ /** The SVM parent-transaction type generated from this project's
1710
+ * `field_selection`: the union of every instruction's `transaction` shape,
1711
+ * with unselected fields typed as `FieldNotSelected<...>`. Resolves to a
1712
+ * `NotConfigured` hint until `envio codegen` augments {@link Global}. */
1713
+ export type SvmTransaction = IsEmptyObject<SvmProgramsT> extends true
1714
+ ? NotConfigured<"SvmTransaction", "Configure SVM programs">
1715
+ : {
1716
+ [P in keyof SvmProgramsT]: {
1717
+ [I in keyof SvmProgramsT[P]]: SvmProgramsT[P][I]["transaction"];
1718
+ }[keyof SvmProgramsT[P]];
1719
+ }[keyof SvmProgramsT];
1720
+
1719
1721
  /** Lookup an EVM event type by contract and event name. Without generics,
1720
1722
  * resolves to the discriminated union of every EVM event in the project. */
1721
1723
  export type EvmEvent<
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.3.0-alpha.2",
3
+ "version": "3.3.0-alpha.3",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -69,10 +69,10 @@
69
69
  "tsx": "4.21.0"
70
70
  },
71
71
  "optionalDependencies": {
72
- "envio-linux-x64": "3.3.0-alpha.2",
73
- "envio-linux-x64-musl": "3.3.0-alpha.2",
74
- "envio-linux-arm64": "3.3.0-alpha.2",
75
- "envio-darwin-x64": "3.3.0-alpha.2",
76
- "envio-darwin-arm64": "3.3.0-alpha.2"
72
+ "envio-linux-x64": "3.3.0-alpha.3",
73
+ "envio-linux-x64-musl": "3.3.0-alpha.3",
74
+ "envio-linux-arm64": "3.3.0-alpha.3",
75
+ "envio-darwin-x64": "3.3.0-alpha.3",
76
+ "envio-darwin-arm64": "3.3.0-alpha.3"
77
77
  }
78
78
  }
@@ -336,6 +336,15 @@ let fetchChain = async (
336
336
  // there's nothing to fetch. During backfill any such chain is idle.
337
337
  let reducedPolling = !isRealtime
338
338
 
339
+ // Accumulated across all queries dispatched in this tick so their per-query
340
+ // results collapse into a single completion log instead of one per query.
341
+ let dispatchedCount = switch action {
342
+ | FetchState.Ready(queries) => queries->Array.length
343
+ | WaitingForNewBlock | NothingToQuery => 0
344
+ }
345
+ let fetchedByPartition = Dict.make()
346
+ let fetchedCount = ref(0)
347
+
339
348
  // Owns its error boundary: launch doesn't catch, so any failure here (the
340
349
  // query, response handling, or dispatch itself) must stop the indexer.
341
350
  try {
@@ -361,6 +370,22 @@ let fetchChain = async (
361
370
  ~knownHeight=chainState->ChainState.knownHeight,
362
371
  ~isRealtime,
363
372
  )
373
+ fetchedCount := fetchedCount.contents + 1
374
+ fetchedByPartition->Dict.set(
375
+ query.partitionId,
376
+ {
377
+ "fromBlock": response.fromBlockQueried,
378
+ "toBlock": response.latestFetchedBlockNumber,
379
+ "numEvents": response.parsedQueueItems->Array.length,
380
+ },
381
+ )
382
+ if dispatchedCount > 0 && fetchedCount.contents === dispatchedCount {
383
+ Logging.trace({
384
+ "msg": "Finished querying",
385
+ "chainId": chain->ChainMap.Chain.toChainId,
386
+ "partitions": fetchedByPartition,
387
+ })
388
+ }
364
389
  await onQueryResponse(
365
390
  state,
366
391
  {chain, response, query},
@@ -176,10 +176,29 @@ async function fetchChain(state, chain, action, stateId, scheduleFetch, schedule
176
176
  let isRealtime = IndexerState.isRealtime(state);
177
177
  let sourceManager = ChainState.sourceManager(chainState);
178
178
  let reducedPolling = !isRealtime;
179
+ let dispatchedCount;
180
+ dispatchedCount = typeof action !== "object" ? 0 : action._0.length;
181
+ let fetchedByPartition = {};
182
+ let fetchedCount = {
183
+ contents: 0
184
+ };
179
185
  try {
180
186
  return await ChainState.dispatch(chainState, async query => {
181
187
  try {
182
188
  let response = await SourceManager.executeQuery(sourceManager, query, ChainState.knownHeight(chainState), isRealtime);
189
+ fetchedCount.contents = fetchedCount.contents + 1 | 0;
190
+ fetchedByPartition[query.partitionId] = {
191
+ fromBlock: response.fromBlockQueried,
192
+ toBlock: response.latestFetchedBlockNumber,
193
+ numEvents: response.parsedQueueItems.length
194
+ };
195
+ if (dispatchedCount > 0 && fetchedCount.contents === dispatchedCount) {
196
+ Logging.trace({
197
+ msg: "Finished querying",
198
+ chainId: chain,
199
+ partitions: fetchedByPartition
200
+ });
201
+ }
183
202
  return await onQueryResponse(state, {
184
203
  chain: chain,
185
204
  response: response,
@@ -254,6 +254,21 @@ let checkAndFetch = async (
254
254
  idx := idx.contents + 1
255
255
  }
256
256
  admittedByChain->Dict.forEachWithKey((queries, chainId) => {
257
+ let partitions = Dict.make()
258
+ queries->Array.forEach((query: FetchState.query) =>
259
+ partitions->Dict.set(
260
+ query.partitionId,
261
+ {
262
+ "fromBlock": query.fromBlock,
263
+ "targetBlock": query.toBlock,
264
+ },
265
+ )
266
+ )
267
+ Logging.trace({
268
+ "msg": "Started querying",
269
+ "chainId": chainId->Int.fromString->Option.getUnsafe,
270
+ "partitions": partitions,
271
+ })
257
272
  actionByChain->Dict.set(chainId, FetchState.Ready(queries))
258
273
  // Mark the admitted queries in flight and reserve their size against the
259
274
  // shared budget; released as each response lands in handleQueryResult.
@@ -170,6 +170,18 @@ async function checkAndFetch(crossChainState, dispatchChain) {
170
170
  idx = idx + 1 | 0;
171
171
  };
172
172
  Stdlib_Dict.forEachWithKey(admittedByChain, (queries, chainId) => {
173
+ let partitions = {};
174
+ queries.forEach(query => {
175
+ partitions[query.partitionId] = {
176
+ fromBlock: query.fromBlock,
177
+ targetBlock: query.toBlock
178
+ };
179
+ });
180
+ Logging.trace({
181
+ msg: "Started querying",
182
+ chainId: Stdlib_Int.fromString(chainId, undefined),
183
+ partitions: partitions
184
+ });
173
185
  actionByChain[chainId] = {
174
186
  TAG: "Ready",
175
187
  _0: queries
package/src/Ecosystem.res CHANGED
@@ -2,8 +2,6 @@ type name = | @as("evm") Evm | @as("fuel") Fuel | @as("svm") Svm
2
2
 
3
3
  type t = {
4
4
  name: name,
5
- blockFields: array<string>,
6
- transactionFields: array<string>,
7
5
  blockNumberName: string,
8
6
  blockTimestampName: string,
9
7
  blockHashName: string,
@@ -326,28 +326,50 @@ let compileAddressFilter: string => (
326
326
  return new Function("event", "blockNumber", "indexingAddresses", body);
327
327
  }`)
328
328
 
329
- // Body of the client-side address filter for a DNF of address-filtered param
330
- // names (OR of AND-groups): keep the event only if some group's params are all
331
- // registered at or before the log's block. The DNF is fixed here, so it's
332
- // unrolled into one boolean expression no per-event closure, loop, or array.
333
- // `None` when there's no address-param filter. Exposed for snapshotting.
334
- let buildAddressFilterBody = (groups: array<array<string>>): option<string> => {
335
- switch groups {
329
+ // Body of the client-side address filter. Two analogous registered-at-or-before
330
+ // checks, ANDed: (1) for non-wildcard events, the log's srcAddress must itself be
331
+ // registered (ownership is resolved structurally by partition, but the temporal
332
+ // `effectiveStartBlock` gate lives here now); (2) a DNF of address-filtered param
333
+ // names (OR of AND-groups) for events that filter an indexed address param. The
334
+ // DNF is fixed here, so it's unrolled into one boolean expression — no per-event
335
+ // closure, loop, or array. `None` only for wildcard events without a param
336
+ // filter. Exposed for snapshotting.
337
+ // `srcAddressExpr` is the JS expression for the event's owning address: EVM and
338
+ // Fuel events expose `event.srcAddress`; SVM instructions expose `event.programId`.
339
+ let buildAddressFilterBody = (
340
+ groups: array<array<string>>,
341
+ ~isWildcard: bool,
342
+ ~srcAddressExpr: string="event.srcAddress",
343
+ ): option<string> => {
344
+ let paramLeaf = name =>
345
+ `(ic = indexingAddresses[p[${JSON.stringify(
346
+ JSON.String(name),
347
+ )}]]) !== undefined && ic.effectiveStartBlock <= blockNumber`
348
+ let paramDnf = switch groups {
336
349
  | [] => None
337
350
  | _ =>
338
- let leaf = name =>
339
- `(ic = indexingAddresses[p[${JSON.stringify(
340
- JSON.String(name),
341
- )}]]) !== undefined && ic.effectiveStartBlock <= blockNumber`
342
- let groupExprs =
343
- groups->Array.map(group => "(" ++ group->Array.map(leaf)->Array.join(" && ") ++ ")")
344
- Some("var p = event.params, ic; return " ++ groupExprs->Array.join(" || ") ++ ";")
351
+ Some(
352
+ groups
353
+ ->Array.map(group => "(" ++ group->Array.map(paramLeaf)->Array.join(" && ") ++ ")")
354
+ ->Array.join(" || "),
355
+ )
356
+ }
357
+ let srcLeaf = `(ic = indexingAddresses[${srcAddressExpr}]) !== undefined && ic.effectiveStartBlock <= blockNumber`
358
+ switch (isWildcard, paramDnf) {
359
+ | (true, None) => None
360
+ | (true, Some(dnf)) => Some("var p = event.params, ic; return " ++ dnf ++ ";")
361
+ | (false, None) => Some("var ic; return " ++ srcLeaf ++ ";")
362
+ | (false, Some(dnf)) =>
363
+ Some("var p = event.params, ic; return " ++ srcLeaf ++ " && (" ++ dnf ++ ");")
345
364
  }
346
365
  }
347
366
 
348
- let buildAddressFilter = (groups: array<array<string>>): option<
349
- (Internal.eventPayload, int, dict<Internal.indexingContract>) => bool,
350
- > => buildAddressFilterBody(groups)->Option.map(compileAddressFilter)
367
+ let buildAddressFilter = (
368
+ groups: array<array<string>>,
369
+ ~isWildcard: bool,
370
+ ~srcAddressExpr: string="event.srcAddress",
371
+ ): option<(Internal.eventPayload, int, dict<Internal.indexingContract>) => bool> =>
372
+ buildAddressFilterBody(groups, ~isWildcard, ~srcAddressExpr)->Option.map(compileAddressFilter)
351
373
 
352
374
  // ============== Build complete EVM event config ==============
353
375
 
@@ -415,7 +437,7 @@ let buildEvmEventConfig = (
415
437
  simulateParamsSchema: buildSimulateParamsSchema(params),
416
438
  getEventFiltersOrThrow,
417
439
  filterByAddresses,
418
- clientAddressFilter: ?buildAddressFilter(addressFilterParamGroups),
440
+ clientAddressFilter: ?buildAddressFilter(addressFilterParamGroups, ~isWildcard),
419
441
  dependsOnAddresses: !isWildcard || filterByAddresses,
420
442
  startBlock: resolvedStartBlock,
421
443
  selectedBlockFields,
@@ -471,6 +493,7 @@ let buildSvmInstructionEventConfig = (
471
493
  simulateParamsSchema: paramsSchema,
472
494
  filterByAddresses: false,
473
495
  dependsOnAddresses: !isWildcard,
496
+ clientAddressFilter: ?buildAddressFilter([], ~isWildcard, ~srcAddressExpr="event.programId"),
474
497
  startBlock,
475
498
  programId,
476
499
  discriminator,
@@ -541,6 +564,7 @@ let buildFuelEventConfig = (
541
564
  simulateParamsSchema: paramsSchema,
542
565
  filterByAddresses: false,
543
566
  dependsOnAddresses: !isWildcard,
567
+ clientAddressFilter: ?buildAddressFilter([], ~isWildcard),
544
568
  startBlock,
545
569
  // Fuel keeps the transaction inline on the payload; nothing to materialise.
546
570
  selectedTransactionFields: Utils.Set.make(),
@@ -255,17 +255,27 @@ let compileAddressFilter = (function (body) {
255
255
  return new Function("event", "blockNumber", "indexingAddresses", body);
256
256
  });
257
257
 
258
- function buildAddressFilterBody(groups) {
259
- if (groups.length === 0) {
260
- return;
258
+ function buildAddressFilterBody(groups, isWildcard, srcAddressExprOpt) {
259
+ let srcAddressExpr = srcAddressExprOpt !== undefined ? srcAddressExprOpt : "event.srcAddress";
260
+ let paramLeaf = name => `(ic = indexingAddresses[p[` + JSON.stringify(name) + `]]) !== undefined && ic.effectiveStartBlock <= blockNumber`;
261
+ let paramDnf = groups.length !== 0 ? groups.map(group => "(" + group.map(paramLeaf).join(" && ") + ")").join(" || ") : undefined;
262
+ let srcLeaf = `(ic = indexingAddresses[` + srcAddressExpr + `]) !== undefined && ic.effectiveStartBlock <= blockNumber`;
263
+ if (isWildcard) {
264
+ if (paramDnf !== undefined) {
265
+ return "var p = event.params, ic; return " + paramDnf + ";";
266
+ } else {
267
+ return;
268
+ }
269
+ } else if (paramDnf !== undefined) {
270
+ return "var p = event.params, ic; return " + srcLeaf + " && (" + paramDnf + ");";
271
+ } else {
272
+ return "var ic; return " + srcLeaf + ";";
261
273
  }
262
- let leaf = name => `(ic = indexingAddresses[p[` + JSON.stringify(name) + `]]) !== undefined && ic.effectiveStartBlock <= blockNumber`;
263
- let groupExprs = groups.map(group => "(" + group.map(leaf).join(" && ") + ")");
264
- return "var p = event.params, ic; return " + groupExprs.join(" || ") + ";";
265
274
  }
266
275
 
267
- function buildAddressFilter(groups) {
268
- return Stdlib_Option.map(buildAddressFilterBody(groups), compileAddressFilter);
276
+ function buildAddressFilter(groups, isWildcard, srcAddressExprOpt) {
277
+ let srcAddressExpr = srcAddressExprOpt !== undefined ? srcAddressExprOpt : "event.srcAddress";
278
+ return Stdlib_Option.map(buildAddressFilterBody(groups, isWildcard, srcAddressExpr), compileAddressFilter);
269
279
  }
270
280
 
271
281
  function buildEvmEventConfig(contractName, eventName, sighash, params, isWildcard, handler, contractRegister, eventFilters, probeChainId, onEventBlockFilterSchema, blockFields, transactionFields, startBlock, globalBlockFieldsSetOpt, globalTransactionFieldsSetOpt) {
@@ -291,7 +301,7 @@ function buildEvmEventConfig(contractName, eventName, sighash, params, isWildcar
291
301
  isWildcard: isWildcard,
292
302
  filterByAddresses: filterByAddresses,
293
303
  dependsOnAddresses: !isWildcard || filterByAddresses,
294
- clientAddressFilter: Stdlib_Option.map(buildAddressFilterBody(match.addressFilterParamGroups), compileAddressFilter),
304
+ clientAddressFilter: buildAddressFilter(match.addressFilterParamGroups, isWildcard, undefined),
295
305
  handler: handler,
296
306
  contractRegister: contractRegister,
297
307
  paramsRawEventSchema: buildParamsSchema(params),
@@ -320,6 +330,7 @@ function buildSvmInstructionEventConfig(contractName, instructionName, programId
320
330
  isWildcard: isWildcard,
321
331
  filterByAddresses: false,
322
332
  dependsOnAddresses: !isWildcard,
333
+ clientAddressFilter: buildAddressFilter([], isWildcard, "event.programId"),
323
334
  handler: handler,
324
335
  contractRegister: contractRegister,
325
336
  paramsRawEventSchema: paramsSchema,
@@ -388,6 +399,7 @@ function buildFuelEventConfig(contractName, eventName, kind, sighash, rawAbi, is
388
399
  isWildcard: isWildcard,
389
400
  filterByAddresses: false,
390
401
  dependsOnAddresses: !isWildcard,
402
+ clientAddressFilter: buildAddressFilter([], isWildcard, undefined),
391
403
  handler: handler,
392
404
  contractRegister: contractRegister,
393
405
  paramsRawEventSchema: paramsSchema,
@@ -39,7 +39,7 @@ let runEventHandlerOrThrow = async (
39
39
  let eventItem = item->Internal.castUnsafeEventItem
40
40
 
41
41
  //Include the load in time before handler
42
- let timeBeforeHandler = Hrtime.makeTimer()
42
+ let timeBeforeHandler = Performance.now()
43
43
 
44
44
  try {
45
45
  let contextParams: UserContext.contextParams = {
@@ -72,7 +72,7 @@ let runEventHandlerOrThrow = async (
72
72
  }),
73
73
  )
74
74
  }
75
- let handlerDuration = timeBeforeHandler->Hrtime.timeSince->Hrtime.toSecondsFloat
75
+ let handlerDuration = timeBeforeHandler->Performance.secondsSince
76
76
  Prometheus.ProcessingHandler.increment(
77
77
  ~contract=eventItem.eventConfig.contractName,
78
78
  ~event=eventItem.eventConfig.name,
@@ -264,11 +264,19 @@ let runBatchHandlersOrThrow = async (
264
264
  }
265
265
  }
266
266
 
267
- let registerProcessEventBatchMetrics = (~logger, ~loadDuration, ~handlerDuration) => {
268
- logger->Logging.childTrace({
269
- "msg": "Finished processing batch",
270
- "loader_time_elapsed": loadDuration,
271
- "handlers_time_elapsed": handlerDuration,
267
+ let registerProcessEventBatchMetrics = (
268
+ ~logger,
269
+ ~batch: Batch.t,
270
+ ~loadDuration,
271
+ ~handlerDuration,
272
+ ) => {
273
+ batch.progressedChainsById->Dict.forEachWithKey((chainAfterBatch, chainId) => {
274
+ logger->Logging.childTrace({
275
+ "msg": "Finished processing",
276
+ "chainId": chainId->Int.fromString->Option.getUnsafe,
277
+ "batchSize": chainAfterBatch.batchSize,
278
+ "progress": chainAfterBatch.progressBlockNumber,
279
+ })
272
280
  })
273
281
 
274
282
  Prometheus.ProcessingBatch.registerMetrics(~loadDuration, ~handlerDuration)
@@ -286,22 +294,28 @@ type logPartitionInfo = {
286
294
  // handlers read plain objects. A batch can span chains, each with its own store
287
295
  // and field mask, so group items by chain before materialising.
288
296
  let materializeBatchTransactions = async (batch: Batch.t, ~chainStates: dict<ChainState.t>) => {
289
- let itemsByChain: dict<array<Internal.item>> = Dict.make()
290
- batch.items->Array.forEach(item => {
291
- let chainId = item->Internal.getItemChainId->Int.toString
292
- switch itemsByChain->Utils.Dict.dangerouslyGetNonOption(chainId) {
293
- | Some(items) => items->Array.push(item)
294
- | None => itemsByChain->Dict.set(chainId, [item])
295
- }
296
- })
297
-
298
- let _ = await itemsByChain
299
- ->Dict.toArray
300
- ->Array.map(async ((chainId, items)) => {
301
- let cs = chainStates->Dict.getUnsafe(chainId)
302
- await cs->ChainState.materializeBatchItems(~items)
303
- })
304
- ->Promise.all
297
+ switch chainStates->Dict.valuesToArray {
298
+ // Single-chain indexers (the common case): every item belongs to the one
299
+ // chain, so skip the per-chain grouping and its allocations.
300
+ | [cs] => await cs->ChainState.materializeBatchItems(~items=batch.items)
301
+ | _ =>
302
+ let itemsByChain: dict<array<Internal.item>> = Dict.make()
303
+ batch.items->Array.forEach(item => {
304
+ let chainId = item->Internal.getItemChainId->Int.toString
305
+ switch itemsByChain->Utils.Dict.dangerouslyGetNonOption(chainId) {
306
+ | Some(items) => items->Array.push(item)
307
+ | None => itemsByChain->Dict.set(chainId, [item])
308
+ }
309
+ })
310
+
311
+ let _ = await itemsByChain
312
+ ->Dict.toArray
313
+ ->Array.map(async ((chainId, items)) => {
314
+ let cs = chainStates->Dict.getUnsafe(chainId)
315
+ await cs->ChainState.materializeBatchItems(~items)
316
+ })
317
+ ->Promise.all
318
+ }
305
319
  }
306
320
 
307
321
  let processEventBatch = async (
@@ -312,27 +326,24 @@ let processEventBatch = async (
312
326
  ~config: Config.t,
313
327
  ~chainStates: dict<ChainState.t>,
314
328
  ) => {
315
- let totalBatchSize = batch.totalBatchSize
316
329
  // Compute chains state for this batch
317
330
  let chains: Internal.chains = chainStates->computeChainsState
318
331
 
319
332
  let logger = Logging.getLogger()
320
- logger->Logging.childTrace({
321
- "msg": "Started processing batch",
322
- "totalBatchSize": totalBatchSize,
323
- "chains": batch.progressedChainsById->Utils.Dict.mapValues(chainAfterBatch => {
324
- {
325
- "batchSize": chainAfterBatch.batchSize,
326
- "progress": chainAfterBatch.progressBlockNumber,
327
- }
328
- }),
333
+
334
+ batch.progressedChainsById->Dict.forEachWithKey((chainAfterBatch, chainId) => {
335
+ logger->Logging.childTrace({
336
+ "msg": "Started processing",
337
+ "chainId": chainId->Int.fromString->Option.getUnsafe,
338
+ "batchSize": chainAfterBatch.batchSize,
339
+ })
329
340
  })
330
341
 
331
342
  try {
332
343
  // Backpressure: keep processing within keepLatestChangesLimit of the cycle.
333
344
  await indexerState->Writing.awaitCapacity
334
345
 
335
- let timeRef = Hrtime.makeTimer()
346
+ let timeRef = Performance.now()
336
347
 
337
348
  if batch.items->Utils.Array.notEmpty {
338
349
  // Materialise store-backed transactions onto payloads before any handler
@@ -341,7 +352,7 @@ let processEventBatch = async (
341
352
  await batch->preloadBatchOrThrow(~loadManager, ~persistence, ~indexerState, ~chains, ~config)
342
353
  }
343
354
 
344
- let elapsedTimeAfterLoaders = timeRef->Hrtime.timeSince->Hrtime.toSecondsFloat
355
+ let elapsedTimeAfterLoaders = timeRef->Performance.secondsSince
345
356
 
346
357
  if batch.items->Utils.Array.notEmpty {
347
358
  await batch->runBatchHandlersOrThrow(
@@ -353,13 +364,18 @@ let processEventBatch = async (
353
364
  )
354
365
  }
355
366
 
356
- let elapsedTimeAfterProcessing = timeRef->Hrtime.timeSince->Hrtime.toSecondsFloat
367
+ let elapsedTimeAfterProcessing = timeRef->Performance.secondsSince
357
368
 
358
369
  indexerState->Writing.commitBatch(~batch)
359
370
 
360
371
  let loaderDuration = elapsedTimeAfterLoaders
361
372
  let handlerDuration = elapsedTimeAfterProcessing -. loaderDuration
362
- registerProcessEventBatchMetrics(~logger, ~loadDuration=loaderDuration, ~handlerDuration)
373
+ registerProcessEventBatchMetrics(
374
+ ~logger,
375
+ ~batch,
376
+ ~loadDuration=loaderDuration,
377
+ ~handlerDuration,
378
+ )
363
379
  Ok()
364
380
  } catch {
365
381
  | Persistence.StorageError({message, reason}) =>