envio 3.3.0-alpha.7 → 3.3.0-alpha.9

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 (95) hide show
  1. package/index.d.ts +23 -5
  2. package/package.json +6 -6
  3. package/src/Api.res +1 -1
  4. package/src/Api.res.mjs +1 -1
  5. package/src/ChainFetching.res +21 -10
  6. package/src/ChainFetching.res.mjs +11 -10
  7. package/src/ChainState.res +401 -171
  8. package/src/ChainState.res.mjs +270 -104
  9. package/src/ChainState.resi +19 -7
  10. package/src/Config.res +11 -27
  11. package/src/Config.res.mjs +8 -7
  12. package/src/Core.res +7 -0
  13. package/src/CrossChainState.res +82 -82
  14. package/src/CrossChainState.res.mjs +52 -59
  15. package/src/CrossChainState.resi +1 -1
  16. package/src/Ecosystem.res +3 -3
  17. package/src/Ecosystem.res.mjs +3 -3
  18. package/src/Envio.res +14 -9
  19. package/src/EnvioGlobal.res +53 -0
  20. package/src/EnvioGlobal.res.mjs +31 -0
  21. package/src/EventConfigBuilder.res +117 -69
  22. package/src/EventConfigBuilder.res.mjs +71 -34
  23. package/src/EventProcessing.res +19 -15
  24. package/src/EventProcessing.res.mjs +13 -12
  25. package/src/FetchState.res +410 -185
  26. package/src/FetchState.res.mjs +268 -253
  27. package/src/HandlerLoader.res +8 -113
  28. package/src/HandlerLoader.res.mjs +2 -88
  29. package/src/HandlerRegister.res +518 -144
  30. package/src/HandlerRegister.res.mjs +285 -133
  31. package/src/HandlerRegister.resi +24 -8
  32. package/src/IndexerState.res +6 -3
  33. package/src/IndexerState.res.mjs +3 -3
  34. package/src/IndexerState.resi +1 -1
  35. package/src/IndexingAddresses.res +10 -7
  36. package/src/IndexingAddresses.res.mjs +8 -7
  37. package/src/IndexingAddresses.resi +3 -1
  38. package/src/Internal.res +104 -39
  39. package/src/Internal.res.mjs +11 -1
  40. package/src/LogSelection.res +102 -165
  41. package/src/LogSelection.res.mjs +101 -116
  42. package/src/Main.res +49 -164
  43. package/src/Main.res.mjs +39 -104
  44. package/src/Metrics.res +43 -2
  45. package/src/Metrics.res.mjs +39 -3
  46. package/src/Prometheus.res +0 -35
  47. package/src/Prometheus.res.mjs +33 -68
  48. package/src/RawEvent.res +7 -2
  49. package/src/RawEvent.res.mjs +4 -4
  50. package/src/RollbackCommit.res +4 -1
  51. package/src/RollbackCommit.res.mjs +3 -2
  52. package/src/SimulateItems.res +20 -7
  53. package/src/SimulateItems.res.mjs +7 -11
  54. package/src/TestIndexer.res +1 -1
  55. package/src/TestIndexer.res.mjs +1 -1
  56. package/src/sources/BlockStore.res +46 -0
  57. package/src/sources/BlockStore.res.mjs +24 -0
  58. package/src/sources/EventRouter.res +19 -12
  59. package/src/sources/EventRouter.res.mjs +7 -5
  60. package/src/sources/Evm.res +58 -5
  61. package/src/sources/Evm.res.mjs +44 -5
  62. package/src/sources/EvmChain.res +14 -18
  63. package/src/sources/EvmChain.res.mjs +12 -13
  64. package/src/sources/FieldMask.res +39 -0
  65. package/src/sources/FieldMask.res.mjs +42 -0
  66. package/src/sources/Fuel.res +8 -3
  67. package/src/sources/Fuel.res.mjs +5 -4
  68. package/src/sources/HyperFuelSource.res +31 -40
  69. package/src/sources/HyperFuelSource.res.mjs +52 -44
  70. package/src/sources/HyperSync.res +31 -9
  71. package/src/sources/HyperSync.res.mjs +47 -31
  72. package/src/sources/HyperSync.resi +10 -3
  73. package/src/sources/HyperSyncClient.res +15 -4
  74. package/src/sources/HyperSyncHeightStream.res +1 -8
  75. package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
  76. package/src/sources/HyperSyncSource.res +58 -90
  77. package/src/sources/HyperSyncSource.res.mjs +57 -66
  78. package/src/sources/RpcSource.res +118 -120
  79. package/src/sources/RpcSource.res.mjs +92 -74
  80. package/src/sources/RpcWebSocketHeightStream.res +0 -5
  81. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
  82. package/src/sources/SimulateSource.res +5 -3
  83. package/src/sources/SimulateSource.res.mjs +12 -4
  84. package/src/sources/Source.res +19 -5
  85. package/src/sources/SourceManager.res +60 -10
  86. package/src/sources/SourceManager.res.mjs +54 -8
  87. package/src/sources/SourceManager.resi +10 -0
  88. package/src/sources/Svm.res +14 -10
  89. package/src/sources/Svm.res.mjs +23 -6
  90. package/src/sources/SvmHyperSyncClient.res +5 -6
  91. package/src/sources/SvmHyperSyncSource.res +82 -41
  92. package/src/sources/SvmHyperSyncSource.res.mjs +86 -41
  93. package/src/sources/TransactionStore.res +6 -107
  94. package/src/sources/TransactionStore.res.mjs +5 -86
  95. package/svm.schema.json +19 -0
@@ -14,10 +14,44 @@ import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
14
14
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
15
15
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
16
16
 
17
+ function recordRequestStats(sourceState, requestStats) {
18
+ requestStats.forEach(param => {
19
+ let seconds = param.seconds;
20
+ let method = param.method;
21
+ let agg = sourceState.requestStats[method];
22
+ if (agg !== undefined) {
23
+ agg.count = agg.count + 1 | 0;
24
+ agg.seconds = agg.seconds + seconds;
25
+ } else {
26
+ sourceState.requestStats[method] = {
27
+ count: 1,
28
+ seconds: seconds
29
+ };
30
+ }
31
+ });
32
+ }
33
+
17
34
  function getActiveSource(sourceManager) {
18
35
  return sourceManager.activeSource;
19
36
  }
20
37
 
38
+ function getRequestStatSamples(sourceManager) {
39
+ let samples = [];
40
+ sourceManager.sourcesState.forEach(sourceState => {
41
+ let chainId = sourceState.source.chain;
42
+ Utils.Dict.forEachWithKey(sourceState.requestStats, (agg, method) => {
43
+ samples.push({
44
+ sourceName: sourceState.source.name,
45
+ chainId: chainId,
46
+ method: method,
47
+ count: agg.count,
48
+ seconds: agg.seconds
49
+ });
50
+ });
51
+ });
52
+ return samples;
53
+ }
54
+
21
55
  function inFlightCount(sourceManager) {
22
56
  return sourceManager.fetchingPartitionsCount;
23
57
  }
@@ -137,7 +171,8 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
137
171
  unsubscribe: undefined,
138
172
  pendingHeightResolvers: [],
139
173
  disabled: false,
140
- lastFailedAt: undefined
174
+ lastFailedAt: undefined,
175
+ requestStats: {}
141
176
  })),
142
177
  statusStart: Performance.now(),
143
178
  status: "Idle",
@@ -263,7 +298,9 @@ async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stall
263
298
  let h = initialHeight;
264
299
  while (h <= knownHeight && newHeight.contents <= initialHeight) {
265
300
  try {
266
- h = await source.getHeightOrThrow();
301
+ let res = await source.getHeightOrThrow();
302
+ recordRequestStats(sourceState, res.requestStats);
303
+ h = res.height;
267
304
  } catch (exn) {
268
305
 
269
306
  }
@@ -282,7 +319,9 @@ async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stall
282
319
  }
283
320
  } else {
284
321
  try {
285
- let height$1 = await source.getHeightOrThrow();
322
+ let res = await source.getHeightOrThrow();
323
+ recordRequestStats(sourceState, res.requestStats);
324
+ let height$1 = res.height;
286
325
  newHeight.contents = height$1;
287
326
  if (height$1 <= knownHeight) {
288
327
  retry = 0;
@@ -299,6 +338,10 @@ async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stall
299
338
  resolvers.forEach(resolve => resolve(newHeight));
300
339
  });
301
340
  sourceState.unsubscribe = unsubscribe;
341
+ recordRequestStats(sourceState, [{
342
+ method: "heightSubscription",
343
+ seconds: 0
344
+ }]);
302
345
  } else {
303
346
  exit = 1;
304
347
  }
@@ -500,8 +543,8 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
500
543
  retry: retry
501
544
  });
502
545
  try {
503
- let est = Math.ceil(query.estResponseSize) | 0;
504
- let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, FetchState.deriveContractNameByAddress(query.addressesByContractName), knownHeight, query.partitionId, query.selection, est > 0 ? est : FetchState.minEstResponseSize | 0, retry, logger$2);
546
+ let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, FetchState.deriveContractNameByAddress(query.addressesByContractName), knownHeight, query.partitionId, query.selection, query.itemsTarget, retry, logger$2);
547
+ recordRequestStats(sourceState, response.requestStats);
505
548
  sourceState.lastFailedAt = undefined;
506
549
  responseRef = response;
507
550
  } catch (raw_error) {
@@ -637,11 +680,13 @@ async function getBlockHashes(sourceManager, blockNumbers, isRealtime) {
637
680
  });
638
681
  try {
639
682
  let res = await source.getBlockHashes(blockNumbers, logger$1);
640
- if (res.TAG === "Ok") {
683
+ recordRequestStats(sourceState, res.requestStats);
684
+ let data = res.result;
685
+ if (data.TAG === "Ok") {
641
686
  sourceState.lastFailedAt = undefined;
642
- responseRef = res._0;
687
+ responseRef = data._0;
643
688
  } else {
644
- throw res._0;
689
+ throw data._0;
645
690
  }
646
691
  } catch (raw_exn) {
647
692
  let exn = Primitive_exceptions.internalToException(raw_exn);
@@ -673,6 +718,7 @@ export {
673
718
  getSourceRole,
674
719
  make,
675
720
  getActiveSource,
721
+ getRequestStatSamples,
676
722
  inFlightCount,
677
723
  onReorg,
678
724
  dispatch,
@@ -21,6 +21,16 @@ let make: (
21
21
 
22
22
  let getActiveSource: t => Source.t
23
23
 
24
+ type requestStatSample = {
25
+ sourceName: string,
26
+ chainId: int,
27
+ method: string,
28
+ count: int,
29
+ seconds: float,
30
+ }
31
+
32
+ let getRequestStatSamples: t => array<requestStatSample>
33
+
24
34
  let inFlightCount: t => int
25
35
 
26
36
  let onReorg: (t, ~rollbackTargetBlock: int) => unit
@@ -16,6 +16,17 @@ let transactionFields =
16
16
  // Computed per event at config build and cached on the event config.
17
17
  let eventTransactionFieldMask = TransactionStore.makeMaskFn(transactionFields)
18
18
 
19
+ // Ordered block field names. The index of each is the field code shared with the
20
+ // Rust store (`SvmBlockField`) — keep this order in sync.
21
+ let blockFields = ["slot", "time", "hash", "height", "parentSlot", "parentHash"]
22
+
23
+ // `slot`/`time`/`hash` are always included; every other block field is opt-in
24
+ // via `field_selection.block_fields`. All are materialised from the store.
25
+ //
26
+ // One instruction's selected block fields → store selection bitmask. Computed per
27
+ // event at config build and cached on the event config.
28
+ let eventBlockFieldMask = BlockStore.makeMaskFn(blockFields)
29
+
19
30
  let make = (~logger: Pino.t): Ecosystem.t => {
20
31
  name: Svm,
21
32
  blockNumberName: "height",
@@ -37,8 +48,8 @@ let make = (~logger: Pino.t): Ecosystem.t => {
37
48
  Logging.createChildFrom(
38
49
  ~logger,
39
50
  ~params={
40
- "program": eventItem.eventConfig.contractName,
41
- "instruction": eventItem.eventConfig.name,
51
+ "program": eventItem.onEventRegistration.eventConfig.contractName,
52
+ "instruction": eventItem.onEventRegistration.eventConfig.name,
42
53
  "chainId": eventItem.chain->ChainMap.Chain.toChainId,
43
54
  "slot": eventItem.blockNumber,
44
55
  "programId": instruction.programId,
@@ -84,14 +95,7 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S
84
95
  let timerRef = Performance.now()
85
96
  let height = await GetFinalizedSlot.route->Rest.fetch((), ~client)
86
97
  let seconds = timerRef->Performance.secondsSince
87
- Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getSlot")
88
- Prometheus.SourceRequestCount.addSeconds(
89
- ~sourceName=name,
90
- ~chainId,
91
- ~method="getSlot",
92
- ~seconds,
93
- )
94
- height
98
+ {Source.height, requestStats: [{Source.method: "getSlot", seconds}]}
95
99
  },
96
100
  getItemsOrThrow: (
97
101
  ~fromBlock as _,
@@ -5,7 +5,7 @@ import * as Rest from "../vendored/Rest.res.mjs";
5
5
  import * as Utils from "../Utils.res.mjs";
6
6
  import * as Logging from "../Logging.res.mjs";
7
7
  import * as Internal from "../Internal.res.mjs";
8
- import * as Prometheus from "../Prometheus.res.mjs";
8
+ import * as BlockStore from "./BlockStore.res.mjs";
9
9
  import * as Performance from "../bindings/Performance.res.mjs";
10
10
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
11
11
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -19,6 +19,17 @@ let cleanUpRawEventFieldsInPlace = (fields => {
19
19
 
20
20
  let eventTransactionFieldMask = TransactionStore.makeMaskFn(Internal.allSvmTransactionFields);
21
21
 
22
+ let blockFields = [
23
+ "slot",
24
+ "time",
25
+ "hash",
26
+ "height",
27
+ "parentSlot",
28
+ "parentHash"
29
+ ];
30
+
31
+ let eventBlockFieldMask = BlockStore.makeMaskFn(blockFields);
32
+
22
33
  function make(logger) {
23
34
  return {
24
35
  name: "svm",
@@ -34,8 +45,8 @@ function make(logger) {
34
45
  toEventLogger: eventItem => {
35
46
  let instruction = eventItem.payload;
36
47
  return Logging.createChildFrom(logger, {
37
- program: eventItem.eventConfig.contractName,
38
- instruction: eventItem.eventConfig.name,
48
+ program: eventItem.onEventRegistration.eventConfig.contractName,
49
+ instruction: eventItem.onEventRegistration.eventConfig.name,
39
50
  chainId: eventItem.chain,
40
51
  slot: eventItem.blockNumber,
41
52
  programId: instruction.programId
@@ -72,9 +83,13 @@ function makeRPCSource(chain, rpc, sourceForOpt) {
72
83
  let timerRef = Performance.now();
73
84
  let height = await Rest.fetch(route, undefined, client);
74
85
  let seconds = Performance.secondsSince(timerRef);
75
- Prometheus.SourceRequestCount.increment(name, chain, "getSlot");
76
- Prometheus.SourceRequestCount.addSeconds(name, chain, "getSlot", seconds);
77
- return height;
86
+ return {
87
+ height: height,
88
+ requestStats: [{
89
+ method: "getSlot",
90
+ seconds: seconds
91
+ }]
92
+ };
78
93
  },
79
94
  getItemsOrThrow: (param, param$1, param$2, param$3, param$4, param$5, param$6, param$7, param$8, param$9) => Stdlib_JsError.throwWithMessage("Svm does not support getting items")
80
95
  };
@@ -86,6 +101,8 @@ export {
86
101
  cleanUpRawEventFieldsInPlace,
87
102
  transactionFields,
88
103
  eventTransactionFieldMask,
104
+ blockFields,
105
+ eventBlockFieldMask,
89
106
  make,
90
107
  GetFinalizedSlot,
91
108
  makeRPCSource,
@@ -138,13 +138,12 @@ module QueryTypes = {
138
138
  }
139
139
 
140
140
  module ResponseTypes = {
141
+ // Lean per-slot header for reorg detection and each item's slot/time; the
142
+ // selectable fields live in the block store and are materialised on demand.
141
143
  type block = {
142
144
  slot: int,
143
145
  blockhash: string,
144
- parentSlot?: int,
145
- parentBlockhash?: string,
146
146
  blockTime?: int,
147
- blockHeight?: int,
148
147
  }
149
148
 
150
149
  /// Borsh-decoded view attached by the Rust client. `argsJson`/`accountsJson`
@@ -206,9 +205,9 @@ type queryResponse = ResponseTypes.queryResponse
206
205
 
207
206
  type t = {
208
207
  getHeight: unit => promise<int>,
209
- // Returns the response plus a page of raw transactions (kept in Rust),
210
- // keyed by (slot, transactionIndex), materialised at batch prep.
211
- get: (~query: query) => promise<(queryResponse, TransactionStore.t)>,
208
+ // Returns the response plus pages of raw transactions and blocks (kept in
209
+ // Rust), keyed by (slot, transactionIndex) / slot, materialised at batch prep.
210
+ get: (~query: query) => promise<(queryResponse, TransactionStore.t, BlockStore.t)>,
212
211
  }
213
212
 
214
213
  @send
@@ -4,7 +4,7 @@ type options = {
4
4
  chain: ChainMap.Chain.t,
5
5
  endpointUrl: string,
6
6
  apiToken: option<string>,
7
- eventConfigs: array<Internal.svmInstructionEventConfig>,
7
+ onEventRegistrations: array<Internal.svmOnEventRegistration>,
8
8
  clientTimeoutMillis: int,
9
9
  }
10
10
 
@@ -184,12 +184,12 @@ let parseDecoded = (
184
184
  }
185
185
  }
186
186
 
187
+ // `block` is omitted; it's materialised from the block store at batch prep.
187
188
  let toSvmInstruction = (
188
189
  instr: SvmHyperSyncClient.ResponseTypes.instruction,
189
190
  ~programName,
190
191
  ~instructionName,
191
192
  ~logs,
192
- ~block,
193
193
  ): Envio.svmInstruction => {
194
194
  programName,
195
195
  instructionName,
@@ -204,14 +204,13 @@ let toSvmInstruction = (
204
204
  d8: ?instr.d8,
205
205
  params: ?(instr.decoded->Option.map(parseDecoded)),
206
206
  ?logs,
207
- block,
208
207
  }
209
208
 
210
209
  // Probe the discriminator byte-length ordering longest-first. Stops at the
211
210
  // first router hit. Falls back to the `_none` key (program-wide handler) when
212
211
  // no discriminator-keyed handler matches.
213
212
  let probeRouter = (
214
- router: EventRouter.t<Internal.svmInstructionEventConfig>,
213
+ router: EventRouter.t<Internal.svmOnEventRegistration>,
215
214
  programId: SvmTypes.Pubkey.t,
216
215
  instr: SvmHyperSyncClient.ResponseTypes.instruction,
217
216
  byteLengthsDesc: array<int>,
@@ -268,9 +267,30 @@ let toQueryTxField = (field: Internal.svmTransactionField): option<
268
267
  | TokenBalances => None
269
268
  }
270
269
 
271
- let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: options): t => {
270
+ // Map a selected block field to its HyperSync query column. Slot/Blockhash/
271
+ // BlockTime are requested unconditionally (needed for reorg detection and the
272
+ // item's slot/timestamp), so selecting `slot`/`time`/`hash` adds no extra column.
273
+ let toQueryBlockField = (field: Internal.svmBlockField): option<
274
+ SvmHyperSyncClient.QueryTypes.blockField,
275
+ > =>
276
+ switch field {
277
+ | Slot | Time | Hash => None
278
+ | Height => Some(BlockHeight)
279
+ | ParentSlot => Some(ParentSlot)
280
+ | ParentHash => Some(ParentBlockhash)
281
+ }
282
+
283
+ let make = (
284
+ {chain, endpointUrl, apiToken, onEventRegistrations, clientTimeoutMillis}: options,
285
+ ): t => {
272
286
  let name = "SvmHyperSync"
273
- let chainId = chain->ChainMap.Chain.toChainId
287
+
288
+ // Definitions drive query/decode building; the registrations drive routing
289
+ // (they carry `isWildcard` and become each decoded item's `onEventRegistration`).
290
+ let eventConfigs =
291
+ onEventRegistrations->Array.map(reg =>
292
+ reg.eventConfig->(Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig)
293
+ )
274
294
 
275
295
  // Built once at startup and handed to the client so `get` decodes matching
276
296
  // instructions in Rust rather than per-instruction over the napi boundary.
@@ -285,7 +305,10 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
285
305
  },
286
306
  )
287
307
 
288
- let (eventRouter, programOrderings) = EventRouter.fromSvmEventConfigsOrThrow(eventConfigs, ~chain)
308
+ let (eventRouter, programOrderings) = EventRouter.fromSvmEventConfigsOrThrow(
309
+ onEventRegistrations,
310
+ ~chain,
311
+ )
289
312
 
290
313
  // programId.toString -> sorted-desc byte lengths
291
314
  let orderingByProgram = Dict.make()
@@ -332,6 +355,24 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
332
355
  }
333
356
  )
334
357
 
358
+ // Union of selected block fields across the chain's events. `slot`/`time`/
359
+ // `hash` are always fetched (as Slot/BlockTime/Blockhash); the rest are added
360
+ // only when an instruction selected them.
361
+ let blockQueryFields = {
362
+ let fields: array<SvmHyperSyncClient.QueryTypes.blockField> = [Slot, Blockhash, BlockTime]
363
+ let selected = Utils.Set.make()
364
+ eventConfigs->Array.forEach(cfg =>
365
+ cfg.selectedBlockFields->Utils.Set.forEach(field => selected->Utils.Set.add(field)->ignore)
366
+ )
367
+ selected->Utils.Set.forEach(field =>
368
+ switch field->toQueryBlockField {
369
+ | Some(queryField) => fields->Array.push(queryField)->ignore
370
+ | None => ()
371
+ }
372
+ )
373
+ fields
374
+ }
375
+
335
376
  let getItemsOrThrow = async (
336
377
  ~fromBlock,
337
378
  ~toBlock,
@@ -353,7 +394,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
353
394
  // field list returns no rows (instructions and blocks are exempt), so each
354
395
  // opted-into table needs its columns spelled out here.
355
396
  let fields: SvmHyperSyncClient.QueryTypes.fieldSelection = {
356
- block: [Slot, Blockhash, BlockTime],
397
+ block: blockQueryFields,
357
398
  transaction: ?(needsTransactions ? Some(txQueryFields) : None),
358
399
  log: ?(needsLogs ? Some([Slot, TransactionIndex, InstructionAddress, Kind, Message]) : None),
359
400
  tokenBalance: ?(
@@ -372,9 +413,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
372
413
  maxNumInstructions: itemsTarget,
373
414
  }
374
415
 
375
- Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getInstructions")
376
-
377
- let (resp, transactionStore) = try await client.get(~query) catch {
416
+ let (resp, transactionStore, blockStore) = try await client.get(~query) catch {
378
417
  | exn =>
379
418
  throw(
380
419
  Source.GetItemsError(
@@ -393,11 +432,13 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
393
432
  )
394
433
  }
395
434
  let pageFetchTime = pageFetchRef->Performance.secondsSince
435
+ let requestStats = [{Source.method: "getInstructions", seconds: pageFetchTime}]
396
436
 
397
437
  let parsingRef = Performance.now()
398
438
 
399
- // Per-slot unix timestamp lookup from the response's `blocks` table. Slots
400
- // without a block row (rare; usually skipped slots) fall back to `None`.
439
+ // Per-slot blockTime lookup from the response's `blocks` table, for the
440
+ // batch's `latestFetchedBlockTimestamp`. Slots without a block row (rare;
441
+ // usually skipped slots) fall back to `None`.
401
442
  let blockTimeBySlot = Dict.make()
402
443
  resp.data.blocks->Array.forEach(b => {
403
444
  switch b.blockTime {
@@ -447,7 +488,11 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
447
488
 
448
489
  switch maybeConfig {
449
490
  | None => ()
450
- | Some(eventConfig) =>
491
+ | Some(onEventRegistration) =>
492
+ let eventConfig =
493
+ onEventRegistration.eventConfig->(
494
+ Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig
495
+ )
451
496
  let logKey =
452
497
  instr.slot->Int.toString ++
453
498
  ":" ++
@@ -466,28 +511,19 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
466
511
  )
467
512
  )
468
513
 
469
- let slotKey = instr.slot->Int.toString
470
- let blockTime = blockTimeBySlot->Utils.Dict.dangerouslyGetNonOption(slotKey)
471
514
  let payload = toSvmInstruction(
472
515
  instr,
473
516
  ~programName=eventConfig.contractName,
474
517
  ~instructionName=eventConfig.name,
475
518
  ~logs=eventConfig.includeLogs ? maybeLogs : None,
476
- ~block={
477
- slot: instr.slot,
478
- time: blockTime->Option.getOr(0),
479
- hash: "",
480
- },
481
519
  )
482
520
 
483
521
  parsedQueueItems
484
522
  ->Array.push(
485
523
  Internal.Event({
486
- eventConfig: (eventConfig :> Internal.eventConfig),
487
- timestamp: blockTime->Option.getOr(0),
524
+ onEventRegistration,
488
525
  chain,
489
526
  blockNumber: instr.slot,
490
- blockHash: "",
491
527
  logIndex: synthLogIndex(instr),
492
528
  // The parent transaction is materialised from the store at batch prep.
493
529
  transactionIndex: instr.transactionIndex,
@@ -522,11 +558,14 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
522
558
  parsedQueueItems,
523
559
  // Raw transactions kept in Rust; materialised (selected fields) at batch prep.
524
560
  transactionStore: Some(transactionStore),
561
+ // Raw blocks kept in Rust; materialised onto the payload at batch prep.
562
+ blockStore: Some(blockStore),
525
563
  latestFetchedBlockNumber: highestSlot,
526
564
  stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
527
565
  knownHeight,
528
566
  blockHashes,
529
567
  fromBlockQueried: fromBlock,
568
+ requestStats,
530
569
  }
531
570
  }
532
571
 
@@ -535,6 +574,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
535
574
  // wire, so we request `maxSlot + 1`; the caller filters to the exact slots.
536
575
  let queryBlockDataRange = async (~fromSlot, ~toSlot) => {
537
576
  let blockDatas = []
577
+ let requestStats = []
538
578
  let fromRef = ref(fromSlot)
539
579
  let keepGoing = ref(true)
540
580
  while keepGoing.contents {
@@ -545,9 +585,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
545
585
  fields: {block: [Slot, Blockhash, BlockTime]},
546
586
  maxNumBlocks: 1000,
547
587
  }
548
- Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getBlockHashes")
549
- // Block-only query; the store page is empty.
550
- let (resp, _) = await client.get(~query)
588
+ let timerRef = Performance.now()
589
+ // Block-only query; the store pages are empty.
590
+ let (resp, _, _) = await client.get(~query)
591
+ requestStats
592
+ ->Array.push({Source.method: "getBlockHashes", seconds: timerRef->Performance.secondsSince})
593
+ ->ignore
551
594
  resp.data.blocks->Array.forEach(b =>
552
595
  blockDatas
553
596
  ->Array.push({
@@ -566,12 +609,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
566
609
  fromRef := resp.nextSlot
567
610
  }
568
611
  }
569
- blockDatas
612
+ (blockDatas, requestStats)
570
613
  }
571
614
 
572
615
  let getBlockHashes = async (~blockNumbers, ~logger as _) =>
573
616
  switch blockNumbers->Array.get(0) {
574
- | None => Ok([])
617
+ | None => {Source.result: Ok([]), requestStats: []}
575
618
  | Some(firstSlot) =>
576
619
  try {
577
620
  let minSlot = ref(firstSlot)
@@ -586,14 +629,19 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
586
629
  }
587
630
  requested->Utils.Set.add(slot)->ignore
588
631
  })
589
- let blockDatas = await queryBlockDataRange(
632
+ let (blockDatas, requestStats) = await queryBlockDataRange(
590
633
  ~fromSlot=minSlot.contents,
591
634
  ~toSlot=maxSlot.contents,
592
635
  )
593
636
  // Keep one entry per requested slot; drop duplicates and unrelated slots.
594
- Ok(blockDatas->Array.filter(data => requested->Utils.Set.delete(data.blockNumber)))
637
+ {
638
+ Source.result: Ok(
639
+ blockDatas->Array.filter(data => requested->Utils.Set.delete(data.blockNumber)),
640
+ ),
641
+ requestStats,
642
+ }
595
643
  } catch {
596
- | exn => Error(exn)
644
+ | exn => {Source.result: Error(exn), requestStats: []}
597
645
  }
598
646
  }
599
647
 
@@ -606,16 +654,9 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
606
654
  getBlockHashes,
607
655
  getHeightOrThrow: async () => {
608
656
  let timer = Performance.now()
609
- let h = await client.getHeight()
657
+ let height = await client.getHeight()
610
658
  let seconds = timer->Performance.secondsSince
611
- Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getHeight")
612
- Prometheus.SourceRequestCount.addSeconds(
613
- ~sourceName=name,
614
- ~chainId,
615
- ~method="getHeight",
616
- ~seconds,
617
- )
618
- h
659
+ {height, requestStats: [{method: "getHeight", seconds}]}
619
660
  },
620
661
  getItemsOrThrow,
621
662
  }