envio 3.11.0 → 3.12.1

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 (109) hide show
  1. package/index.d.ts +3 -0
  2. package/package.json +6 -6
  3. package/src/Batch.res +111 -78
  4. package/src/Batch.res.mjs +53 -46
  5. package/src/BatchProcessing.res +1 -11
  6. package/src/BatchProcessing.res.mjs +1 -4
  7. package/src/ChainFetching.res +7 -3
  8. package/src/ChainFetching.res.mjs +1 -1
  9. package/src/ChainState.res +82 -20
  10. package/src/ChainState.res.mjs +51 -13
  11. package/src/ChainState.resi +6 -1
  12. package/src/Config.res +58 -37
  13. package/src/Config.res.mjs +63 -53
  14. package/src/CrossChainState.res +50 -53
  15. package/src/CrossChainState.res.mjs +25 -23
  16. package/src/CrossChainState.resi +6 -13
  17. package/src/EffectState.res +13 -4
  18. package/src/EffectState.resi +7 -1
  19. package/src/EventProcessing.res +6 -6
  20. package/src/EventProcessing.res.mjs +6 -6
  21. package/src/FetchState.res +65 -46
  22. package/src/FetchState.res.mjs +68 -36
  23. package/src/FinalizeBackfill.res +16 -12
  24. package/src/FinalizeBackfill.res.mjs +3 -3
  25. package/src/HistoryPolicy.res +46 -0
  26. package/src/HistoryPolicy.res.mjs +50 -0
  27. package/src/InMemoryStore.res +30 -32
  28. package/src/InMemoryStore.res.mjs +33 -28
  29. package/src/InMemoryTable.res +155 -67
  30. package/src/InMemoryTable.res.mjs +151 -60
  31. package/src/IndexerState.res +66 -45
  32. package/src/IndexerState.res.mjs +57 -29
  33. package/src/IndexerState.resi +8 -13
  34. package/src/Internal.res +4 -2
  35. package/src/LoadLayer.res +68 -38
  36. package/src/LoadLayer.res.mjs +50 -64
  37. package/src/LoadLayer.resi +1 -1
  38. package/src/Main.res +25 -24
  39. package/src/Main.res.mjs +24 -25
  40. package/src/Metrics.res +49 -1
  41. package/src/Metrics.res.mjs +26 -3
  42. package/src/Persistence.res +55 -16
  43. package/src/Persistence.res.mjs +18 -3
  44. package/src/PgStorage.res +286 -180
  45. package/src/PgStorage.res.mjs +209 -122
  46. package/src/PruneStaleHistory.res +9 -15
  47. package/src/PruneStaleHistory.res.mjs +11 -20
  48. package/src/Rollback.res +9 -14
  49. package/src/Rollback.res.mjs +5 -3
  50. package/src/Sink.res +7 -5
  51. package/src/Sink.res.mjs +4 -4
  52. package/src/Staging.res +298 -0
  53. package/src/Staging.res.mjs +257 -0
  54. package/src/TestIndexer.res +14 -30
  55. package/src/TestIndexer.res.mjs +11 -9
  56. package/src/UserContext.res +20 -36
  57. package/src/UserContext.res.mjs +6 -9
  58. package/src/Utils.res +10 -4
  59. package/src/Utils.res.mjs +16 -16
  60. package/src/Writing.res +34 -20
  61. package/src/Writing.res.mjs +15 -20
  62. package/src/bindings/ClickHouse.res +41 -34
  63. package/src/bindings/ClickHouse.res.mjs +36 -30
  64. package/src/bindings/ClickHouseSink.res +45 -186
  65. package/src/bindings/ClickHouseSink.res.mjs +21 -174
  66. package/src/db/CheckpointSequence.res +109 -0
  67. package/src/db/CheckpointSequence.res.mjs +99 -0
  68. package/src/db/EntityFilter.res +487 -275
  69. package/src/db/EntityFilter.res.mjs +557 -309
  70. package/src/db/EntityHistory.res +6 -5
  71. package/src/db/EntityHistory.res.mjs +5 -5
  72. package/src/db/Frontier.res +86 -0
  73. package/src/db/Frontier.res.mjs +126 -0
  74. package/src/db/InternalTable.res +145 -27
  75. package/src/db/InternalTable.res.mjs +115 -29
  76. package/src/db/RollbackFloors.res +34 -41
  77. package/src/db/RollbackFloors.res.mjs +27 -71
  78. package/src/db/Table.res +21 -6
  79. package/src/db/Table.res.mjs +13 -4
  80. package/src/sources/BlockStore.res +8 -0
  81. package/src/sources/EvmHyperSyncSource.res +5 -1
  82. package/src/sources/EvmHyperSyncSource.res.mjs +4 -3
  83. package/src/sources/FuelHyperSyncSource.res +1 -0
  84. package/src/sources/FuelHyperSyncSource.res.mjs +1 -1
  85. package/src/sources/HyperSync.res +6 -0
  86. package/src/sources/HyperSync.res.mjs +5 -2
  87. package/src/sources/HyperSync.resi +2 -0
  88. package/src/sources/HyperSyncClient.res +6 -0
  89. package/src/sources/HyperSyncSSE.res +1 -1
  90. package/src/sources/HyperSyncSSE.res.mjs +4 -10
  91. package/src/sources/RequestStat.res +8 -1
  92. package/src/sources/RpcSource.res +1 -0
  93. package/src/sources/RpcSource.res.mjs +1 -1
  94. package/src/sources/SimulateSource.res +1 -0
  95. package/src/sources/SimulateSource.res.mjs +1 -1
  96. package/src/sources/Source.res +11 -3
  97. package/src/sources/SourceManager.res +34 -13
  98. package/src/sources/SourceManager.res.mjs +26 -9
  99. package/src/sources/SourceManager.resi +2 -0
  100. package/src/sources/SvmHyperSyncClient.res +5 -0
  101. package/src/sources/SvmHyperSyncSource.res +3 -0
  102. package/src/sources/SvmHyperSyncSource.res.mjs +4 -2
  103. package/src/tui/Tui.res +56 -143
  104. package/src/tui/Tui.res.mjs +70 -166
  105. package/src/tui/components/TuiData.res +61 -16
  106. package/src/tui/components/TuiData.res.mjs +53 -15
  107. package/svm.schema.json +0 -7
  108. package/src/db/CheckpointBounds.res +0 -53
  109. package/src/db/CheckpointBounds.res.mjs +0 -44
@@ -44,12 +44,12 @@ function make(param) {
44
44
  }
45
45
  };
46
46
  };
47
- let getItemsOrThrow = async (fromBlock, toBlock, addressSet, knownHeight, param, selection, itemsTarget, retry, param$1) => {
47
+ let getItemsOrThrow = async (fromBlock, toBlock, addressSet, includeAllBlocks, knownHeight, param, selection, itemsTarget, retry, param$1) => {
48
48
  let totalTimeRef = Performance.now();
49
49
  let startFetchingBatchTimeRef = Performance.now();
50
50
  let pageUnsafe;
51
51
  try {
52
- pageUnsafe = await HyperSync.GetLogs.query(client, fromBlock, toBlock, itemsTarget, selection.onEventRegistrations.map(reg => reg.index), addressSet, selection.clientFilteredContracts);
52
+ pageUnsafe = await HyperSync.GetLogs.query(client, fromBlock, toBlock, itemsTarget, selection.onEventRegistrations.map(reg => reg.index), addressSet, selection.clientFilteredContracts, includeAllBlocks);
53
53
  } catch (raw_exn) {
54
54
  let exn = Primitive_exceptions.internalToException(raw_exn);
55
55
  if (exn.RE_EXN_ID === HyperSync.GetLogs.$$Error) {
@@ -100,7 +100,8 @@ function make(param) {
100
100
  let pageFetchTime = Performance.secondsSince(startFetchingBatchTimeRef);
101
101
  let requestStats = [{
102
102
  method: "getLogs",
103
- seconds: pageFetchTime
103
+ seconds: pageFetchTime,
104
+ responseBlocks: pageUnsafe.responseBlocks
104
105
  }];
105
106
  let knownHeight$1 = pageUnsafe.archiveHeight;
106
107
  let heighestBlockQueried = pageUnsafe.nextBlock - 1 | 0;
@@ -35,6 +35,7 @@ let make = ({chainId, endpointUrl, apiToken, onEventRegistrations, addressStore}
35
35
  ~fromBlock,
36
36
  ~toBlock,
37
37
  ~addressSet,
38
+ ~includeAllBlocks as _,
38
39
  ~knownHeight,
39
40
  ~partitionId as _,
40
41
  ~selection: FetchState.selection,
@@ -28,7 +28,7 @@ function make(param) {
28
28
  let exn = Primitive_exceptions.internalToException(raw_exn);
29
29
  client = ErrorHandling.mkLogAndRaise(undefined, "Failed to instantiate the HyperFuel client", exn);
30
30
  }
31
- let getItemsOrThrow = async (fromBlock, toBlock, addressSet, knownHeight, param, selection, param$1, retry, logger) => {
31
+ let getItemsOrThrow = async (fromBlock, toBlock, addressSet, param, knownHeight, param$1, selection, param$2, retry, logger) => {
32
32
  let totalTimeRef = Performance.now();
33
33
  let startFetchingBatchTimeRef = Performance.now();
34
34
  let pageUnsafe;
@@ -90,6 +90,7 @@ type logsQueryPage = {
90
90
  items: array<HyperSyncClient.EventItems.item>,
91
91
  nextBlock: int,
92
92
  archiveHeight: int,
93
+ responseBlocks: int,
93
94
  // Page store owning this page's raw transactions.
94
95
  transactionStore: TransactionStore.t,
95
96
  // Page store owning this page's raw blocks.
@@ -136,6 +137,7 @@ module GetLogs = {
136
137
  ~registrationIndexes,
137
138
  ~addressSet,
138
139
  ~clientFilteredContracts,
140
+ ~includeAllBlocks,
139
141
  ): logsQueryPage => {
140
142
  let query: HyperSyncClient.EventItems.query = {
141
143
  fromBlock,
@@ -143,6 +145,9 @@ module GetLogs = {
143
145
  ?maxNumLogs,
144
146
  registrationIndexes,
145
147
  clientFilteredContracts,
148
+ // Absent rather than false, so a range that wants only the blocks its
149
+ // logs came from sends the query it always sent.
150
+ includeAllBlocks: ?(includeAllBlocks ? Some(true) : None),
146
151
  }
147
152
 
148
153
  let (res, transactionStore, blockStore) = switch await client.getEventItems(
@@ -165,6 +170,7 @@ module GetLogs = {
165
170
  {
166
171
  items: res.items,
167
172
  nextBlock: res.nextBlock,
173
+ responseBlocks: res.responseBlocks,
168
174
  archiveHeight: res.archiveHeight->Option.getOr(0), //Archive Height is only None if height is 0
169
175
  transactionStore,
170
176
  blockStore,
@@ -137,13 +137,15 @@ function extractMissingParams(exn) {
137
137
  }
138
138
  }
139
139
 
140
- async function query(client, fromBlock, toBlock, maxNumLogs, registrationIndexes, addressSet, clientFilteredContracts) {
140
+ async function query(client, fromBlock, toBlock, maxNumLogs, registrationIndexes, addressSet, clientFilteredContracts, includeAllBlocks) {
141
+ let query_includeAllBlocks = includeAllBlocks ? true : undefined;
141
142
  let query$1 = {
142
143
  fromBlock: fromBlock,
143
144
  toBlock: toBlock,
144
145
  maxNumLogs: maxNumLogs,
145
146
  registrationIndexes: registrationIndexes,
146
- clientFilteredContracts: clientFilteredContracts
147
+ clientFilteredContracts: clientFilteredContracts,
148
+ includeAllBlocks: query_includeAllBlocks
147
149
  };
148
150
  let match;
149
151
  try {
@@ -176,6 +178,7 @@ async function query(client, fromBlock, toBlock, maxNumLogs, registrationIndexes
176
178
  items: res.items,
177
179
  nextBlock: res.nextBlock,
178
180
  archiveHeight: Stdlib_Option.getOr(res.archiveHeight, 0),
181
+ responseBlocks: res.responseBlocks,
179
182
  transactionStore: match[1],
180
183
  blockStore: match[2]
181
184
  };
@@ -2,6 +2,7 @@ type logsQueryPage = {
2
2
  items: array<HyperSyncClient.EventItems.item>,
3
3
  nextBlock: int,
4
4
  archiveHeight: int,
5
+ responseBlocks: int,
5
6
  transactionStore: TransactionStore.t,
6
7
  blockStore: BlockStore.t,
7
8
  }
@@ -56,5 +57,6 @@ module GetLogs: {
56
57
  ~registrationIndexes: array<int>,
57
58
  ~addressSet: AddressSet.t,
58
59
  ~clientFilteredContracts: option<array<string>>,
60
+ ~includeAllBlocks: bool,
59
61
  ) => promise<logsQueryPage>
60
62
  }
@@ -262,6 +262,9 @@ module EventItems = {
262
262
  // depend on addresses (client-side filtering). None/empty means
263
263
  // every address-dependent contract is filtered server-side.
264
264
  clientFilteredContracts: option<array<string>>,
265
+ // Return a header for every block in the range, not only the ones a log
266
+ // landed on. Absent means only the blocks logs came from.
267
+ includeAllBlocks?: bool,
265
268
  }
266
269
 
267
270
  type item = {
@@ -283,6 +286,9 @@ module EventItems = {
283
286
  archiveHeight: option<int>,
284
287
  nextBlock: int,
285
288
  items: array<item>,
289
+ // Blocks the server returned for the query, counted before routing drops
290
+ // the ones no item joins to.
291
+ responseBlocks: int,
286
292
  }
287
293
  }
288
294
 
@@ -25,7 +25,7 @@ let subscribe = (~hyperSyncUrl, ~apiToken, ~onHeight, ~onStatus) =>
25
25
  args.headers
26
26
  ->Option.getOr(Dict.make())
27
27
  ->Utils.Dict.merge(
28
- Dict.fromArray([("Authorization", `Bearer ${apiToken}`), ("User-Agent", userAgent)]),
28
+ dict{"Authorization": `Bearer ${apiToken}`, "User-Agent": userAgent},
29
29
  )
30
30
  EventSource.Fetch.fetch(url, ~args={...args, headers: headers})
31
31
  },
@@ -35,16 +35,10 @@ function subscribe(hyperSyncUrl, apiToken, onHeight, onStatus) {
35
35
  let userAgent = `hyperindex/` + Utils.EnvioPackage.value.version;
36
36
  let es = new Eventsource.EventSource(hyperSyncUrl + `/height/sse`, {
37
37
  fetch: (url, args) => {
38
- let headers = Utils.Dict.merge(Stdlib_Option.getOr(args.headers, {}), Object.fromEntries([
39
- [
40
- "Authorization",
41
- `Bearer ` + apiToken
42
- ],
43
- [
44
- "User-Agent",
45
- userAgent
46
- ]
47
- ]));
38
+ let headers = Utils.Dict.merge(Stdlib_Option.getOr(args.headers, {}), {
39
+ Authorization: `Bearer ` + apiToken,
40
+ "User-Agent": userAgent
41
+ });
48
42
  let newrecord = {...args};
49
43
  return fetch(url, (newrecord.headers = headers, newrecord));
50
44
  }
@@ -2,4 +2,11 @@
2
2
  // in its own leaf module (rather than on `Source`) so the client FFI bindings
3
3
  // can name it without importing `Source` — which would form a dependency cycle
4
4
  // through `Env → HyperSyncClient → Source → FetchState → Env`.
5
- type t = {method: string, seconds: float}
5
+ type t = {
6
+ method: string,
7
+ seconds: float,
8
+ // Blocks the backend returned for this request, before any client-side
9
+ // routing drops them. Absent for methods whose responses aren't measured in
10
+ // blocks, so a zero always means the request came back empty.
11
+ responseBlocks?: int,
12
+ }
@@ -877,6 +877,7 @@ let make = (
877
877
  ~fromBlock,
878
878
  ~toBlock,
879
879
  ~addressSet,
880
+ ~includeAllBlocks as _,
880
881
  ~knownHeight,
881
882
  ~partitionId,
882
883
  ~selection: FetchState.selection,
@@ -874,7 +874,7 @@ function make(param) {
874
874
  Error: new Error()
875
875
  };
876
876
  }, lowercaseAddresses);
877
- let getItemsOrThrow = async (fromBlock, toBlock, addressSet, knownHeight, partitionId, selection, param, retry, param$1) => {
877
+ let getItemsOrThrow = async (fromBlock, toBlock, addressSet, param, knownHeight, partitionId, selection, param$1, retry, param$2) => {
878
878
  let startFetchingBatchTimeRef = Performance.now();
879
879
  let toBlock$1 = toBlock !== undefined ? Primitive_int.min(toBlock, knownHeight) : knownHeight;
880
880
  let firstBlockPromise = fromBlock > 0 && fromBlock <= toBlock$1 ? LazyLoader.get(blockLoader.contents, fromBlock).then(json => parseBlockInfo(json)) : Promise.resolve(undefined);
@@ -29,6 +29,7 @@ let make = (
29
29
  ~fromBlock,
30
30
  ~toBlock,
31
31
  ~addressSet,
32
+ ~includeAllBlocks as _,
32
33
  ~knownHeight as _,
33
34
  ~partitionId as _,
34
35
  ~selection: FetchState.selection,
@@ -27,7 +27,7 @@ function make(items, endBlock, chainId, addressStore, ecosystemOpt, transactionS
27
27
  height: reportedHeight,
28
28
  requestStats: []
29
29
  }),
30
- getItemsOrThrow: (fromBlock, toBlock, addressSet, param, param$1, selection, param$2, param$3, param$4) => {
30
+ getItemsOrThrow: (fromBlock, toBlock, addressSet, param, param$1, param$2, selection, param$3, param$4, param$5) => {
31
31
  let toBlockQueried = toBlock !== undefined ? toBlock : reportedHeight;
32
32
  let selectionRegistrationIndexes = new Set();
33
33
  selection.onEventRegistrations.forEach(reg => {
@@ -8,9 +8,10 @@ type blockRangeFetchStats = {
8
8
  }
9
9
 
10
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 = RequestStat.t = {method: string, seconds: float}
11
+ // aren't requests), with the time it took and how much it brought back.
12
+ // SourceManager aggregates these per (source, method) into the
13
+ // envio_source_request_* and envio_source_response_* metrics.
14
+ type requestStat = RequestStat.t = {method: string, seconds: float, responseBlocks?: int}
14
15
 
15
16
  // Native clients wrap a failure of a multi-request operation in a structured
16
17
  // payload, so the source can still return timings when SourceManager retries
@@ -196,6 +197,13 @@ type t = {
196
197
  // straight to its Rust client, which builds the query's address filter from
197
198
  // it and gates every returned item against the chain-wide store.
198
199
  ~addressSet: AddressSet.t,
200
+ // Return a header for every block in the range, not only the ones an item
201
+ // landed on. The progress block's timestamp is what measures how far behind
202
+ // chain time the indexer is, and at the head that block often carries no
203
+ // item of its own. Only set once the chain is at the head, where the range
204
+ // is a handful of blocks; over a backfill range it would be a header per
205
+ // block for no gain.
206
+ ~includeAllBlocks: bool,
199
207
  ~knownHeight: int,
200
208
  ~partitionId: string,
201
209
  ~selection: FetchState.selection,
@@ -1,9 +1,14 @@
1
1
  type sourceManagerStatus = Idle | WaitingForNewBlock | Querying
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}
3
+ // Cumulative per-method request stats for a source, aggregated from the
4
+ // requestStat arrays returned by its methods. Rendered into the
5
+ // envio_source_request_* and envio_source_response_* metrics.
6
+ type requestStatAgg = {
7
+ mutable count: int,
8
+ mutable seconds: float,
9
+ mutable responseBlocks: option<int>,
10
+ mutable emptyResponseCount: int,
11
+ }
7
12
 
8
13
  type sourceState = {
9
14
  source: Source.t,
@@ -19,12 +24,23 @@ let recordStatsInto = (
19
24
  aggregates: dict<requestStatAgg>,
20
25
  requestStats: array<Source.requestStat>,
21
26
  ) => {
22
- requestStats->Array.forEach(({method, seconds}) => {
23
- switch aggregates->Utils.Dict.dangerouslyGetNonOption(method) {
24
- | Some(agg) =>
25
- agg.count = agg.count + 1
26
- agg.seconds = agg.seconds +. seconds
27
- | None => aggregates->Dict.set(method, {count: 1, seconds})
27
+ requestStats->Array.forEach(({method, seconds, responseBlocks: ?responseBlocks}) => {
28
+ let agg = switch aggregates->Utils.Dict.dangerouslyGetNonOption(method) {
29
+ | Some(agg) => agg
30
+ | None =>
31
+ let agg = {count: 0, seconds: 0., responseBlocks: None, emptyResponseCount: 0}
32
+ aggregates->Dict.set(method, agg)
33
+ agg
34
+ }
35
+ agg.count = agg.count + 1
36
+ agg.seconds = agg.seconds +. seconds
37
+ switch responseBlocks {
38
+ | Some(responseBlocks) =>
39
+ agg.responseBlocks = Some(agg.responseBlocks->Option.getOr(0) + responseBlocks)
40
+ if responseBlocks === 0 {
41
+ agg.emptyResponseCount = agg.emptyResponseCount + 1
42
+ }
43
+ | None => ()
28
44
  }
29
45
  })
30
46
  }
@@ -40,6 +56,8 @@ type requestStatSample = {
40
56
  method: string,
41
57
  count: int,
42
58
  seconds: float,
59
+ responseBlocks: option<int>,
60
+ emptyResponseCount: int,
43
61
  }
44
62
 
45
63
  // Encapsulates the fetching logic for a chain's sources.
@@ -93,6 +111,8 @@ let getRequestStatSamples = (sourceManager: t): array<requestStatSample> => {
93
111
  method,
94
112
  count: agg.count,
95
113
  seconds: agg.seconds,
114
+ responseBlocks: agg.responseBlocks,
115
+ emptyResponseCount: agg.emptyResponseCount,
96
116
  })
97
117
  ->ignore
98
118
  })
@@ -685,7 +705,7 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
685
705
  logger->Logging.childTrace(
686
706
  reducedPolling
687
707
  ? `Waiting for new blocks with reduced polling (${(sourceManager.reducedPollingInterval / 1000)
688
- ->Int.toString}s). Chain is caught up, waiting for other chains to backfill.`
708
+ ->Int.toString}s) until the indexer enters realtime mode.`
689
709
  : "Initiating check for new blocks.",
690
710
  )
691
711
  sourceManager.waitingLogged = true
@@ -697,8 +717,8 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
697
717
  // cadence the sources poll at and the level the closing line is logged at.
698
718
  let stalled = ref(false)
699
719
 
700
- // Use a much longer stall timeout when reduced polling is active
701
- // to avoid spurious stall warnings while waiting for other chains to backfill
720
+ // Use a much longer stall timeout when reduced polling is active, so a chain
721
+ // deliberately asking rarely doesn't report itself stalled between polls.
702
722
  let stallTimeout = if reducedPolling {
703
723
  sourceManager.reducedPollingInterval * 2
704
724
  } else if isRealtime {
@@ -928,6 +948,7 @@ let executeQuery = async (
928
948
  ~fromBlock=query.fromBlock,
929
949
  ~toBlock,
930
950
  ~addressSet=query.addresses,
951
+ ~includeAllBlocks=isRealtime,
931
952
  ~partitionId=query.partitionId,
932
953
  ~knownHeight,
933
954
  ~selection=query.selection->FetchState.narrowSelectionToRange(~toBlock),
@@ -16,17 +16,32 @@ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_excep
16
16
 
17
17
  function recordStatsInto(aggregates, requestStats) {
18
18
  requestStats.forEach(param => {
19
- let seconds = param.seconds;
19
+ let responseBlocks = param.responseBlocks;
20
20
  let method = param.method;
21
21
  let agg = aggregates[method];
22
+ let agg$1;
22
23
  if (agg !== undefined) {
23
- agg.count = agg.count + 1 | 0;
24
- agg.seconds = agg.seconds + seconds;
24
+ agg$1 = agg;
25
25
  } else {
26
- aggregates[method] = {
27
- count: 1,
28
- seconds: seconds
26
+ let agg$2 = {
27
+ count: 0,
28
+ seconds: 0,
29
+ responseBlocks: undefined,
30
+ emptyResponseCount: 0
29
31
  };
32
+ aggregates[method] = agg$2;
33
+ agg$1 = agg$2;
34
+ }
35
+ agg$1.count = agg$1.count + 1 | 0;
36
+ agg$1.seconds = agg$1.seconds + param.seconds;
37
+ if (responseBlocks !== undefined) {
38
+ agg$1.responseBlocks = Stdlib_Option.getOr(agg$1.responseBlocks, 0) + responseBlocks | 0;
39
+ if (responseBlocks === 0) {
40
+ agg$1.emptyResponseCount = agg$1.emptyResponseCount + 1 | 0;
41
+ return;
42
+ } else {
43
+ return;
44
+ }
30
45
  }
31
46
  });
32
47
  }
@@ -45,7 +60,9 @@ function getRequestStatSamples(sourceManager) {
45
60
  chainId: chainId,
46
61
  method: method,
47
62
  count: agg.count,
48
- seconds: agg.seconds
63
+ seconds: agg.seconds,
64
+ responseBlocks: agg.responseBlocks,
65
+ emptyResponseCount: agg.emptyResponseCount
49
66
  });
50
67
  });
51
68
  });
@@ -520,7 +537,7 @@ function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling)
520
537
  knownHeight: knownHeight
521
538
  });
522
539
  if (!sourceManager.waitingLogged) {
523
- Logging.childTrace(logger, reducedPolling ? `Waiting for new blocks with reduced polling (` + (sourceManager.reducedPollingInterval / 1000 | 0).toString() + `s). Chain is caught up, waiting for other chains to backfill.` : "Initiating check for new blocks.");
540
+ Logging.childTrace(logger, reducedPolling ? `Waiting for new blocks with reduced polling (` + (sourceManager.reducedPollingInterval / 1000 | 0).toString() + `s) until the indexer enters realtime mode.` : "Initiating check for new blocks.");
524
541
  sourceManager.waitingLogged = true;
525
542
  }
526
543
  let mainSources = getNextSources(sourceManager, isRealtime, undefined);
@@ -674,7 +691,7 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
674
691
  retry: retry
675
692
  });
676
693
  try {
677
- let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addresses, knownHeight, query.partitionId, FetchState.narrowSelectionToRange(query.selection, toBlock), query.itemsTarget, retry, logger$2);
694
+ let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addresses, isRealtime, knownHeight, query.partitionId, FetchState.narrowSelectionToRange(query.selection, toBlock), query.itemsTarget, retry, logger$2);
678
695
  recordStatsInto(sourceState.requestStats, response.requestStats);
679
696
  validateResponseBlockStore("getItems", response.blockStore, undefined);
680
697
  sourceState.lastFailedAt = undefined;
@@ -27,6 +27,8 @@ type requestStatSample = {
27
27
  method: string,
28
28
  count: int,
29
29
  seconds: float,
30
+ responseBlocks: option<int>,
31
+ emptyResponseCount: int,
30
32
  }
31
33
 
32
34
  let getRequestStatSamples: t => array<requestStatSample>
@@ -143,6 +143,11 @@ module EventItems = {
143
143
  // depend on addresses (client-side filtering). None/empty means every
144
144
  // address-dependent program is filtered server-side.
145
145
  clientFilteredContracts: option<array<string>>,
146
+ // Return a block for every slot in the range, not only the ones an
147
+ // instruction landed on. Absent means only the slots instructions came
148
+ // from, which leaves a skipped slot indistinguishable from an unfetched
149
+ // one.
150
+ includeAllBlocks?: bool,
146
151
  }
147
152
 
148
153
  // NAPI encodes Rust `None` as `null`, never `undefined`, so an unselected
@@ -147,6 +147,7 @@ let make = (
147
147
  ~fromBlock,
148
148
  ~toBlock,
149
149
  ~addressSet,
150
+ ~includeAllBlocks,
150
151
  ~knownHeight,
151
152
  ~partitionId as _,
152
153
  ~selection: FetchState.selection,
@@ -163,6 +164,8 @@ let make = (
163
164
  maxNumInstructions: ?itemsTarget,
164
165
  registrationIndexes: selection.onEventRegistrations->Array.map(reg => reg.index),
165
166
  clientFilteredContracts: selection.clientFilteredContracts,
167
+ // Absent rather than false, so a backfill query is the one it always was.
168
+ includeAllBlocks: ?(includeAllBlocks ? Some(true) : None),
166
169
  }
167
170
 
168
171
  let (resp, transactionStore, blockStore) = try await client.getEventItems(
@@ -106,17 +106,19 @@ function make(param) {
106
106
  let chainId = param.chainId;
107
107
  let apiToken = HyperSync.requireApiToken(param.apiToken);
108
108
  let client = SvmHyperSyncClient.make(endpointUrl, apiToken, param.clientTimeoutMillis, undefined, undefined, SvmHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations), param.addressStore);
109
- let getItemsOrThrow = async (fromBlock, toBlock, addressSet, knownHeight, param, selection, itemsTarget, retry, param$1) => {
109
+ let getItemsOrThrow = async (fromBlock, toBlock, addressSet, includeAllBlocks, knownHeight, param, selection, itemsTarget, retry, param$1) => {
110
110
  let totalTimeRef = Performance.now();
111
111
  let pageFetchRef = Performance.now();
112
112
  let query_registrationIndexes = selection.onEventRegistrations.map(reg => reg.index);
113
113
  let query_clientFilteredContracts = selection.clientFilteredContracts;
114
+ let query_includeAllBlocks = includeAllBlocks ? true : undefined;
114
115
  let query = {
115
116
  fromSlot: fromBlock,
116
117
  toSlot: toBlock,
117
118
  maxNumInstructions: itemsTarget,
118
119
  registrationIndexes: query_registrationIndexes,
119
- clientFilteredContracts: query_clientFilteredContracts
120
+ clientFilteredContracts: query_clientFilteredContracts,
121
+ includeAllBlocks: query_includeAllBlocks
120
122
  };
121
123
  let match;
122
124
  try {