envio 3.3.0-alpha.6 → 3.3.0-alpha.8

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 (93) hide show
  1. package/index.d.ts +23 -5
  2. package/package.json +6 -6
  3. package/src/Address.res +5 -2
  4. package/src/Address.res.mjs +3 -1
  5. package/src/Api.res +1 -1
  6. package/src/Api.res.mjs +1 -1
  7. package/src/ChainFetching.res +34 -34
  8. package/src/ChainFetching.res.mjs +29 -29
  9. package/src/ChainState.res +328 -165
  10. package/src/ChainState.res.mjs +205 -100
  11. package/src/ChainState.resi +16 -7
  12. package/src/Config.res +55 -27
  13. package/src/Config.res.mjs +46 -7
  14. package/src/ContractRegisterContext.res +2 -12
  15. package/src/ContractRegisterContext.res.mjs +3 -5
  16. package/src/Core.res +7 -0
  17. package/src/CrossChainState.res +54 -79
  18. package/src/CrossChainState.res.mjs +45 -60
  19. package/src/CrossChainState.resi +1 -1
  20. package/src/Ecosystem.res +3 -3
  21. package/src/Ecosystem.res.mjs +3 -3
  22. package/src/Envio.res +14 -9
  23. package/src/EventConfigBuilder.res +105 -56
  24. package/src/EventConfigBuilder.res.mjs +69 -33
  25. package/src/EventProcessing.res +19 -15
  26. package/src/EventProcessing.res.mjs +13 -12
  27. package/src/FetchState.res +383 -182
  28. package/src/FetchState.res.mjs +260 -264
  29. package/src/HandlerLoader.res +7 -112
  30. package/src/HandlerLoader.res.mjs +1 -87
  31. package/src/HandlerRegister.res +209 -6
  32. package/src/HandlerRegister.res.mjs +90 -5
  33. package/src/HandlerRegister.resi +13 -2
  34. package/src/IndexerState.res +6 -3
  35. package/src/IndexerState.res.mjs +3 -3
  36. package/src/IndexerState.resi +1 -1
  37. package/src/IndexingAddresses.res +10 -7
  38. package/src/IndexingAddresses.res.mjs +8 -7
  39. package/src/IndexingAddresses.resi +3 -1
  40. package/src/Internal.res +82 -36
  41. package/src/Internal.res.mjs +11 -1
  42. package/src/Main.res +22 -23
  43. package/src/Main.res.mjs +15 -17
  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/SimulateItems.res +26 -8
  51. package/src/SimulateItems.res.mjs +11 -14
  52. package/src/TestIndexer.res +1 -1
  53. package/src/TestIndexer.res.mjs +1 -1
  54. package/src/sources/BlockStore.res +46 -0
  55. package/src/sources/BlockStore.res.mjs +24 -0
  56. package/src/sources/EventRouter.res +19 -12
  57. package/src/sources/EventRouter.res.mjs +7 -5
  58. package/src/sources/Evm.res +54 -4
  59. package/src/sources/Evm.res.mjs +43 -4
  60. package/src/sources/EvmChain.res +14 -18
  61. package/src/sources/EvmChain.res.mjs +12 -13
  62. package/src/sources/FieldMask.res +39 -0
  63. package/src/sources/FieldMask.res.mjs +42 -0
  64. package/src/sources/Fuel.res +5 -2
  65. package/src/sources/Fuel.res.mjs +4 -3
  66. package/src/sources/HyperFuelSource.res +32 -40
  67. package/src/sources/HyperFuelSource.res.mjs +53 -45
  68. package/src/sources/HyperSync.res +35 -9
  69. package/src/sources/HyperSync.res.mjs +52 -35
  70. package/src/sources/HyperSync.resi +11 -3
  71. package/src/sources/HyperSyncClient.res +15 -4
  72. package/src/sources/HyperSyncHeightStream.res +1 -8
  73. package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
  74. package/src/sources/HyperSyncSource.res +39 -58
  75. package/src/sources/HyperSyncSource.res.mjs +44 -44
  76. package/src/sources/RpcSource.res +114 -92
  77. package/src/sources/RpcSource.res.mjs +77 -46
  78. package/src/sources/RpcWebSocketHeightStream.res +0 -5
  79. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
  80. package/src/sources/SimulateSource.res +6 -3
  81. package/src/sources/SimulateSource.res.mjs +13 -5
  82. package/src/sources/Source.res +25 -5
  83. package/src/sources/SourceManager.res +66 -3
  84. package/src/sources/SourceManager.res.mjs +55 -7
  85. package/src/sources/SourceManager.resi +10 -0
  86. package/src/sources/Svm.res +15 -10
  87. package/src/sources/Svm.res.mjs +24 -7
  88. package/src/sources/SvmHyperSyncClient.res +5 -6
  89. package/src/sources/SvmHyperSyncSource.res +84 -41
  90. package/src/sources/SvmHyperSyncSource.res.mjs +90 -43
  91. package/src/sources/TransactionStore.res +6 -107
  92. package/src/sources/TransactionStore.res.mjs +5 -86
  93. package/svm.schema.json +19 -0
@@ -3,7 +3,6 @@
3
3
  import * as Rpc from "./Rpc.res.mjs";
4
4
  import * as Utils from "../Utils.res.mjs";
5
5
  import * as Logging from "../Logging.res.mjs";
6
- import * as Prometheus from "../Prometheus.res.mjs";
7
6
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
8
7
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
9
8
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
@@ -105,7 +104,6 @@ function subscribe(wsUrl, chainId, onHeight) {
105
104
  }
106
105
  errorCount.contents = 0;
107
106
  resetStaleTimeout();
108
- Prometheus.SourceRequestCount.increment("WebSocket", chainId, "eth_subscribe");
109
107
  return onHeight(blockNumber._0);
110
108
  } catch (raw_e) {
111
109
  let e = Primitive_exceptions.internalToException(raw_e);
@@ -12,11 +12,11 @@ let make = (~items: array<Internal.item>, ~endBlock: int, ~chain: ChainMap.Chain
12
12
  poweredByHyperSync: false,
13
13
  pollingInterval: 0,
14
14
  getBlockHashes: (~blockNumbers as _, ~logger as _) => {
15
- Promise.resolve(Ok([]))
15
+ Promise.resolve({Source.result: Ok([]), requestStats: []})
16
16
  },
17
17
  getHeightOrThrow: () => {
18
18
  // Report at least height 1 so the engine doesn't treat 0 as "no blocks available"
19
- Promise.resolve(max(endBlock, 1))
19
+ Promise.resolve({Source.height: max(endBlock, 1), requestStats: []})
20
20
  },
21
21
  getItemsOrThrow: (
22
22
  ~fromBlock as _,
@@ -26,6 +26,7 @@ let make = (~items: array<Internal.item>, ~endBlock: int, ~chain: ChainMap.Chain
26
26
  ~knownHeight as _,
27
27
  ~partitionId as _,
28
28
  ~selection as _,
29
+ ~itemsTarget as _,
29
30
  ~retry as _,
30
31
  ~logger as _,
31
32
  ) => {
@@ -42,14 +43,16 @@ let make = (~items: array<Internal.item>, ~endBlock: int, ~chain: ChainMap.Chain
42
43
  Source.knownHeight: reportedHeight,
43
44
  blockHashes: [],
44
45
  parsedQueueItems: result,
45
- // Simulate keeps the transaction inline on the payload; no store page.
46
+ // Simulate keeps the transaction and block inline on the payload; no store pages.
46
47
  transactionStore: None,
48
+ blockStore: None,
47
49
  fromBlockQueried: 0,
48
50
  latestFetchedBlockNumber: reportedHeight,
49
51
  latestFetchedBlockTimestamp: 0,
50
52
  stats: {
51
53
  totalTimeElapsed: 0.,
52
54
  },
55
+ requestStats: [],
53
56
  })
54
57
  },
55
58
  }
@@ -13,11 +13,17 @@ function make(items, endBlock, chain) {
13
13
  poweredByHyperSync: false,
14
14
  pollingInterval: 0,
15
15
  getBlockHashes: (param, param$1) => Promise.resolve({
16
- TAG: "Ok",
17
- _0: []
16
+ result: {
17
+ TAG: "Ok",
18
+ _0: []
19
+ },
20
+ requestStats: []
18
21
  }),
19
- getHeightOrThrow: () => Promise.resolve(Primitive_int.max(endBlock, 1)),
20
- getItemsOrThrow: (param, param$1, param$2, param$3, param$4, param$5, param$6, param$7, param$8) => {
22
+ getHeightOrThrow: () => Promise.resolve({
23
+ height: Primitive_int.max(endBlock, 1),
24
+ requestStats: []
25
+ }),
26
+ getItemsOrThrow: (param, param$1, param$2, param$3, param$4, param$5, param$6, param$7, param$8, param$9) => {
21
27
  let result = delivered.contents ? [] : (delivered.contents = true, items);
22
28
  let reportedHeight = Primitive_int.max(endBlock, 1);
23
29
  return Promise.resolve({
@@ -25,12 +31,14 @@ function make(items, endBlock, chain) {
25
31
  blockHashes: [],
26
32
  parsedQueueItems: result,
27
33
  transactionStore: undefined,
34
+ blockStore: undefined,
28
35
  fromBlockQueried: 0,
29
36
  latestFetchedBlockNumber: reportedHeight,
30
37
  latestFetchedBlockTimestamp: 0,
31
38
  stats: {
32
39
  "total time elapsed (s)": 0
33
- }
40
+ },
41
+ requestStats: []
34
42
  });
35
43
  },
36
44
  simulateItems: items
@@ -7,6 +7,11 @@ type blockRangeFetchStats = {
7
7
  @as("page fetch time (s)") pageFetchTime?: float,
8
8
  }
9
9
 
10
+ // A single backend request a source method actually made (cache/dedup hits
11
+ // aren't requests), with the time it took. SourceManager aggregates these
12
+ // per (source, method) into the envio_source_request_* metrics.
13
+ type requestStat = {method: string, seconds: float}
14
+
10
15
  /**
11
16
  Thes response returned from a block range fetch
12
17
  */
@@ -22,10 +27,22 @@ type blockRangeFetchResponse = {
22
27
  // transactionIndex); merged into the chain's store on apply. `None` for
23
28
  // sources that keep the transaction inline on the payload (RPC/Fuel/Simulate).
24
29
  transactionStore: option<TransactionStore.t>,
30
+ // Page of blocks for this response's items, keyed by block number; merged into
31
+ // the chain's store on apply. `None` for sources that keep the block fully
32
+ // inline on the payload (RPC/Fuel/Simulate).
33
+ blockStore: option<BlockStore.t>,
25
34
  fromBlockQueried: int,
26
35
  latestFetchedBlockNumber: int,
27
36
  latestFetchedBlockTimestamp: int,
28
37
  stats: blockRangeFetchStats,
38
+ requestStats: array<requestStat>,
39
+ }
40
+
41
+ type getHeightResponse = {height: int, requestStats: array<requestStat>}
42
+
43
+ type getBlockHashesResponse = {
44
+ result: result<array<ReorgDetection.blockDataWithTimestamp>, exn>,
45
+ requestStats: array<requestStat>,
29
46
  }
30
47
 
31
48
  type getItemsRetry =
@@ -51,11 +68,8 @@ type t = {
51
68
  poweredByHyperSync: bool,
52
69
  /* Frequency (in ms) used when polling for new events on this network. */
53
70
  pollingInterval: int,
54
- getBlockHashes: (
55
- ~blockNumbers: array<int>,
56
- ~logger: Pino.t,
57
- ) => promise<result<array<ReorgDetection.blockDataWithTimestamp>, exn>>,
58
- getHeightOrThrow: unit => promise<int>,
71
+ getBlockHashes: (~blockNumbers: array<int>, ~logger: Pino.t) => promise<getBlockHashesResponse>,
72
+ getHeightOrThrow: unit => promise<getHeightResponse>,
59
73
  getItemsOrThrow: (
60
74
  ~fromBlock: int,
61
75
  ~toBlock: option<int>,
@@ -64,6 +78,12 @@ type t = {
64
78
  ~knownHeight: int,
65
79
  ~partitionId: string,
66
80
  ~selection: FetchState.selection,
81
+ // Soft cap on the number of primary items (logs/instructions/receipts) the
82
+ // source should ask its backend for, from the query's own estResponseSize.
83
+ // A HyperSync-backed source enforces it server-side, so a wrong estimate
84
+ // truncates the response instead of overshooting the shared buffer. Sources
85
+ // without an equivalent lever (RPC, Fuel, Simulate) ignore it.
86
+ ~itemsTarget: int,
67
87
  ~retry: int,
68
88
  ~logger: Pino.t,
69
89
  ) => promise<blockRangeFetchResponse>,
@@ -1,5 +1,10 @@
1
1
  type sourceManagerStatus = Idle | WaitingForNewBlock | Querieng
2
2
 
3
+ // Cumulative per-method request count/time for a source, aggregated from the
4
+ // requestStat arrays returned by its methods. Rendered into
5
+ // envio_source_request_* by Metrics.renderSourceRequests.
6
+ type requestStatAgg = {mutable count: int, mutable seconds: float}
7
+
3
8
  type sourceState = {
4
9
  source: Source.t,
5
10
  mutable knownHeight: int,
@@ -9,6 +14,28 @@ type sourceState = {
9
14
  // Timestamp (ms) when this source last failed during executeQuery.
10
15
  // Used to decide when to attempt recovery to this source.
11
16
  mutable lastFailedAt: option<float>,
17
+ requestStats: dict<requestStatAgg>,
18
+ }
19
+
20
+ let recordRequestStats = (sourceState: sourceState, requestStats: array<Source.requestStat>) => {
21
+ requestStats->Array.forEach(({method, seconds}) => {
22
+ switch sourceState.requestStats->Utils.Dict.dangerouslyGetNonOption(method) {
23
+ | Some(agg) =>
24
+ agg.count = agg.count + 1
25
+ agg.seconds = agg.seconds +. seconds
26
+ | None => sourceState.requestStats->Dict.set(method, {count: 1, seconds})
27
+ }
28
+ })
29
+ }
30
+
31
+ // Flattened (source, method) aggregates for Metrics.renderSourceRequests to
32
+ // inline into the /metrics response.
33
+ type requestStatSample = {
34
+ sourceName: string,
35
+ chainId: int,
36
+ method: string,
37
+ count: int,
38
+ seconds: float,
12
39
  }
13
40
 
14
41
  // Encapsulates the fetching logic for a chain's sources.
@@ -46,6 +73,25 @@ type t = {
46
73
 
47
74
  let getActiveSource = sourceManager => sourceManager.activeSource
48
75
 
76
+ let getRequestStatSamples = (sourceManager: t): array<requestStatSample> => {
77
+ let samples = []
78
+ sourceManager.sourcesState->Array.forEach(sourceState => {
79
+ let chainId = sourceState.source.chain->ChainMap.Chain.toChainId
80
+ sourceState.requestStats->Utils.Dict.forEachWithKey((agg, method) => {
81
+ samples
82
+ ->Array.push({
83
+ sourceName: sourceState.source.name,
84
+ chainId,
85
+ method,
86
+ count: agg.count,
87
+ seconds: agg.seconds,
88
+ })
89
+ ->ignore
90
+ })
91
+ })
92
+ samples
93
+ }
94
+
49
95
  // Partition queries currently in flight on this chain's sources. Summed across
50
96
  // chains by CrossChainState to enforce the indexer-wide concurrency budget.
51
97
  let inFlightCount = sourceManager => sourceManager.fetchingPartitionsCount
@@ -187,6 +233,7 @@ let make = (
187
233
  pendingHeightResolvers: [],
188
234
  disabled: false,
189
235
  lastFailedAt: None,
236
+ requestStats: Dict.make(),
190
237
  }),
191
238
  activeSource: initialActiveSource,
192
239
  waitingForNewBlockStateId: None,
@@ -344,7 +391,9 @@ let getSourceNewHeight = async (
344
391
  let h = ref(initialHeight)
345
392
  while h.contents <= knownHeight && !(newHeight.contents > initialHeight) {
346
393
  try {
347
- h := (await source.getHeightOrThrow())
394
+ let res = await source.getHeightOrThrow()
395
+ sourceState->recordRequestStats(res.requestStats)
396
+ h := res.height
348
397
  } catch {
349
398
  | _ => ()
350
399
  }
@@ -363,7 +412,9 @@ let getSourceNewHeight = async (
363
412
  | None =>
364
413
  // No subscription, use REST polling
365
414
  try {
366
- let height = await source.getHeightOrThrow()
415
+ let res = await source.getHeightOrThrow()
416
+ sourceState->recordRequestStats(res.requestStats)
417
+ let height = res.height
367
418
 
368
419
  newHeight := height
369
420
  if height <= knownHeight {
@@ -385,6 +436,9 @@ let getSourceNewHeight = async (
385
436
  }
386
437
  })
387
438
  sourceState.unsubscribe = Some(unsubscribe)
439
+ // Count a subscription (re)start rather than every pushed height —
440
+ // there's no request/response to time here.
441
+ sourceState->recordRequestStats([{Source.method: "heightSubscription", seconds: 0.}])
388
442
  | _ =>
389
443
  // Slowdown polling when the chain isn't progressing
390
444
  let pollingInterval = if reducedPolling {
@@ -680,9 +734,17 @@ let executeQuery = async (
680
734
  ~partitionId=query.partitionId,
681
735
  ~knownHeight,
682
736
  ~selection=query.selection,
737
+ // Ceil (not truncate) so a sub-1 target from a sparse partition over a
738
+ // small range doesn't round down to 0 and ask the backend to cap the
739
+ // response at nothing — 0 items is indistinguishable from "no signal".
740
+ ~itemsTarget={
741
+ let target = query.itemsTarget->Math.ceil->Float.toInt
742
+ target > 0 ? target : FetchState.minItemsTarget->Float.toInt
743
+ },
683
744
  ~retry,
684
745
  ~logger,
685
746
  )
747
+ sourceState->recordRequestStats(response.requestStats)
686
748
  sourceState.lastFailedAt = None
687
749
  responseRef := Some(response)
688
750
  } catch {
@@ -822,7 +884,8 @@ let getBlockHashes = async (sourceManager: t, ~blockNumbers: array<int>, ~isReal
822
884
 
823
885
  try {
824
886
  let res = await source.getBlockHashes(~blockNumbers, ~logger)
825
- switch res {
887
+ sourceState->recordRequestStats(res.requestStats)
888
+ switch res.result {
826
889
  | Ok(data) =>
827
890
  sourceState.lastFailedAt = None
828
891
  responseRef := Some(data)
@@ -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,7 +543,9 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
500
543
  retry: retry
501
544
  });
502
545
  try {
503
- let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, FetchState.deriveContractNameByAddress(query.addressesByContractName), knownHeight, query.partitionId, query.selection, retry, logger$2);
546
+ let target = Math.ceil(query.itemsTarget) | 0;
547
+ let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, FetchState.deriveContractNameByAddress(query.addressesByContractName), knownHeight, query.partitionId, query.selection, target > 0 ? target : FetchState.minItemsTarget | 0, retry, logger$2);
548
+ recordRequestStats(sourceState, response.requestStats);
504
549
  sourceState.lastFailedAt = undefined;
505
550
  responseRef = response;
506
551
  } catch (raw_error) {
@@ -636,11 +681,13 @@ async function getBlockHashes(sourceManager, blockNumbers, isRealtime) {
636
681
  });
637
682
  try {
638
683
  let res = await source.getBlockHashes(blockNumbers, logger$1);
639
- if (res.TAG === "Ok") {
684
+ recordRequestStats(sourceState, res.requestStats);
685
+ let data = res.result;
686
+ if (data.TAG === "Ok") {
640
687
  sourceState.lastFailedAt = undefined;
641
- responseRef = res._0;
688
+ responseRef = data._0;
642
689
  } else {
643
- throw res._0;
690
+ throw data._0;
644
691
  }
645
692
  } catch (raw_exn) {
646
693
  let exn = Primitive_exceptions.internalToException(raw_exn);
@@ -672,6 +719,7 @@ export {
672
719
  getSourceRole,
673
720
  make,
674
721
  getActiveSource,
722
+ getRequestStatSamples,
675
723
  inFlightCount,
676
724
  onReorg,
677
725
  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 _,
@@ -101,6 +105,7 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S
101
105
  ~knownHeight as _,
102
106
  ~partitionId as _,
103
107
  ~selection as _,
108
+ ~itemsTarget as _,
104
109
  ~retry as _,
105
110
  ~logger as _,
106
111
  ) => JsError.throwWithMessage("Svm does not support getting items"),
@@ -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,11 +83,15 @@ 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
- getItemsOrThrow: (param, param$1, param$2, param$3, param$4, param$5, param$6, param$7, param$8) => Stdlib_JsError.throwWithMessage("Svm does not support getting items")
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
  };
81
96
  }
82
97
 
@@ -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