envio 3.11.0 → 3.12.0

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 (89) 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/ChainState.res +72 -19
  8. package/src/ChainState.res.mjs +50 -12
  9. package/src/ChainState.resi +5 -0
  10. package/src/Config.res +58 -37
  11. package/src/Config.res.mjs +63 -53
  12. package/src/CrossChainState.res +49 -52
  13. package/src/CrossChainState.res.mjs +25 -23
  14. package/src/CrossChainState.resi +6 -13
  15. package/src/EffectState.res +13 -4
  16. package/src/EffectState.resi +7 -1
  17. package/src/EventProcessing.res +6 -6
  18. package/src/EventProcessing.res.mjs +6 -6
  19. package/src/FetchState.res +14 -0
  20. package/src/FetchState.res.mjs +14 -0
  21. package/src/FinalizeBackfill.res +16 -12
  22. package/src/FinalizeBackfill.res.mjs +3 -3
  23. package/src/HistoryPolicy.res +46 -0
  24. package/src/HistoryPolicy.res.mjs +50 -0
  25. package/src/InMemoryStore.res +30 -32
  26. package/src/InMemoryStore.res.mjs +33 -28
  27. package/src/IndexerState.res +66 -45
  28. package/src/IndexerState.res.mjs +57 -29
  29. package/src/IndexerState.resi +8 -13
  30. package/src/Internal.res +4 -2
  31. package/src/LoadLayer.res +11 -4
  32. package/src/LoadLayer.res.mjs +8 -5
  33. package/src/Main.res +25 -24
  34. package/src/Main.res.mjs +24 -25
  35. package/src/Metrics.res +49 -1
  36. package/src/Metrics.res.mjs +26 -3
  37. package/src/Persistence.res +55 -16
  38. package/src/Persistence.res.mjs +18 -3
  39. package/src/PgStorage.res +194 -118
  40. package/src/PgStorage.res.mjs +132 -72
  41. package/src/PruneStaleHistory.res +9 -15
  42. package/src/PruneStaleHistory.res.mjs +11 -20
  43. package/src/Rollback.res +9 -14
  44. package/src/Rollback.res.mjs +5 -3
  45. package/src/Sink.res +7 -5
  46. package/src/Sink.res.mjs +4 -4
  47. package/src/Staging.res +298 -0
  48. package/src/Staging.res.mjs +257 -0
  49. package/src/TestIndexer.res +5 -5
  50. package/src/TestIndexer.res.mjs +7 -6
  51. package/src/UserContext.res +7 -4
  52. package/src/UserContext.res.mjs +5 -2
  53. package/src/Utils.res +9 -0
  54. package/src/Utils.res.mjs +9 -0
  55. package/src/Writing.res +34 -20
  56. package/src/Writing.res.mjs +15 -20
  57. package/src/bindings/ClickHouse.res +41 -34
  58. package/src/bindings/ClickHouse.res.mjs +36 -30
  59. package/src/bindings/ClickHouseSink.res +45 -186
  60. package/src/bindings/ClickHouseSink.res.mjs +21 -174
  61. package/src/db/CheckpointSequence.res +109 -0
  62. package/src/db/CheckpointSequence.res.mjs +99 -0
  63. package/src/db/EntityHistory.res +6 -5
  64. package/src/db/EntityHistory.res.mjs +5 -5
  65. package/src/db/Frontier.res +86 -0
  66. package/src/db/Frontier.res.mjs +126 -0
  67. package/src/db/InternalTable.res +145 -27
  68. package/src/db/InternalTable.res.mjs +115 -29
  69. package/src/db/RollbackFloors.res +34 -41
  70. package/src/db/RollbackFloors.res.mjs +27 -71
  71. package/src/sources/BlockStore.res +3 -0
  72. package/src/sources/EvmHyperSyncSource.res +3 -1
  73. package/src/sources/EvmHyperSyncSource.res.mjs +2 -1
  74. package/src/sources/HyperSync.res +2 -0
  75. package/src/sources/HyperSync.res.mjs +1 -0
  76. package/src/sources/HyperSync.resi +1 -0
  77. package/src/sources/HyperSyncClient.res +3 -0
  78. package/src/sources/RequestStat.res +8 -1
  79. package/src/sources/Source.res +4 -3
  80. package/src/sources/SourceManager.res +30 -10
  81. package/src/sources/SourceManager.res.mjs +24 -7
  82. package/src/sources/SourceManager.resi +2 -0
  83. package/src/tui/Tui.res +56 -143
  84. package/src/tui/Tui.res.mjs +70 -166
  85. package/src/tui/components/TuiData.res +61 -16
  86. package/src/tui/components/TuiData.res.mjs +53 -15
  87. package/svm.schema.json +0 -7
  88. package/src/db/CheckpointBounds.res +0 -53
  89. package/src/db/CheckpointBounds.res.mjs +0 -44
@@ -1,55 +1,48 @@
1
- // How far back a reorg rollback takes each chain: the last checkpoint each keeps,
2
- // and for a chain whose own reorg set its floor, the last block that reorg left
3
- // valid. Superseding an unwritten rollback with a new one is a pointwise
4
- // minimum, so a merge can neither lose a chain's floor nor raise it.
1
+ // How far back a reorg rollback takes each chain: the last checkpoint each
2
+ // keeps, and for a chain whose own reorg set its floor, the last block that
3
+ // reorg left valid. Superseding an unwritten rollback with a new one is a
4
+ // pointwise minimum, so a merge can neither lose a chain's floor nor raise it.
5
5
  type t = {
6
- floors: CheckpointBounds.t,
6
+ checkpointBounds: CheckpointSequence.checkpointBoundsByChain,
7
7
  // Progress recomputed from the surviving checkpoints alone can land above the
8
8
  // fork: the blocks between it and the chain's next checkpoint carried no
9
9
  // events on the orphaned chain, but the chain replacing it can have its own.
10
10
  // Absent for a chain dragged along by another chain's reorg.
11
- forkBlockNumberByChain: array<(ChainId.t, int)>,
11
+ forkBlockNumberByChain: dict<int>,
12
12
  }
13
13
 
14
- // The rollback of a schema whose chains can't have written each other's rows.
15
- let isolated = (~chainId, ~floorCheckpointId, ~forkBlockNumber) => {
16
- floors: PerChain([(chainId, floorCheckpointId)]),
17
- forkBlockNumberByChain: [(chainId, forkBlockNumber)],
18
- }
19
-
20
- // The rollback of a schema with a cross-chain entity: every chain goes back to
21
- // the same checkpoint.
22
- let global = (~floorCheckpointId, ~reorgChainId, ~forkBlockNumber) => {
23
- floors: EveryChain(floorCheckpointId),
24
- forkBlockNumberByChain: [(reorgChainId, forkBlockNumber)],
25
- }
26
-
27
- let mergeByChain = (pending: array<(ChainId.t, 'a)>, next: array<(ChainId.t, 'a)>) => {
28
- let merged = pending->Array.copy
29
- next->Array.forEach(((chainId, value)) =>
30
- switch merged->Array.findIndexOpt(((mergedChainId, _)) => mergedChainId === chainId) {
31
- | Some(idx) =>
32
- let (_, pendingValue) = merged->Array.getUnsafe(idx)
33
- merged->Array.setUnsafe(idx, (chainId, Pervasives.min(pendingValue, value)))
34
- | None => merged->Array.push((chainId, value))
35
- }
36
- )
37
- merged
14
+ // Under one shared sequence a reorg on any chain can have changed any chain's
15
+ // rows, so every chain goes back to the same checkpoint; with a counter per
16
+ // chain the floor names the reorg chain alone and every sibling is left alone.
17
+ let make = (
18
+ ~sequence: CheckpointSequence.t,
19
+ ~chainIds: array<ChainId.t>,
20
+ ~reorgChainId,
21
+ ~floorCheckpointId,
22
+ ~forkBlockNumber,
23
+ ) => {
24
+ checkpointBounds: {
25
+ CheckpointSequence.sequence,
26
+ byChain: switch sequence {
27
+ | SharedAcrossChains =>
28
+ chainIds->Array.map(chainId => (chainId, floorCheckpointId))->Frontier.fromEntries
29
+ | PerChain => Frontier.fromEntries([(reorgChainId, floorCheckpointId)])
30
+ },
31
+ },
32
+ forkBlockNumberByChain: Dict.fromArray([(reorgChainId->ChainId.toString, forkBlockNumber)]),
38
33
  }
39
34
 
40
35
  let merge = (pending: t, next: t) => {
41
- floors: switch (pending.floors, next.floors) {
42
- | (EveryChain(pending), EveryChain(next)) => EveryChain(Pervasives.min(pending, next))
43
- | (PerChain(pending), PerChain(next)) => PerChain(mergeByChain(pending, next))
44
- | (EveryChain(_), PerChain(_)) | (PerChain(_), EveryChain(_)) =>
45
- JsError.throwWithMessage(
46
- "Internal error: a rollback reaching every chain can't be merged with one isolated to a chain. The schema decides which a run builds, and it doesn't change.",
47
- )
36
+ checkpointBounds: {
37
+ CheckpointSequence.sequence: next.checkpointBounds.sequence,
38
+ byChain: Frontier.mergeMin(pending.checkpointBounds.byChain, next.checkpointBounds.byChain),
48
39
  },
49
- forkBlockNumberByChain: mergeByChain(pending.forkBlockNumberByChain, next.forkBlockNumberByChain),
40
+ forkBlockNumberByChain: Utils.Dict.mergeWith(
41
+ pending.forkBlockNumberByChain,
42
+ next.forkBlockNumberByChain,
43
+ Pervasives.min,
44
+ ),
50
45
  }
51
46
 
52
47
  let forkBlockNumber = (floors: t, chainId) =>
53
- floors.forkBlockNumberByChain
54
- ->Array.find(((forkChainId, _)) => forkChainId === chainId)
55
- ->Option.map(((_, forkBlockNumber)) => forkBlockNumber)
48
+ floors.forkBlockNumberByChain->ChainId.Dict.dangerouslyGetNonOption(chainId)
@@ -1,92 +1,48 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
- import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
- import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
6
- import * as Primitive_bigint from "@rescript/runtime/lib/es6/Primitive_bigint.js";
7
- import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
3
+ import * as Utils from "../Utils.res.mjs";
4
+ import * as ChainId from "../ChainId.res.mjs";
5
+ import * as Frontier from "./Frontier.res.mjs";
6
+ import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
8
7
 
9
- function isolated(chainId, floorCheckpointId, forkBlockNumber) {
10
- return {
11
- floors: {
12
- TAG: "PerChain",
13
- _0: [[
14
- chainId,
15
- floorCheckpointId
16
- ]]
17
- },
18
- forkBlockNumberByChain: [[
19
- chainId,
20
- forkBlockNumber
21
- ]]
22
- };
23
- }
24
-
25
- function global(floorCheckpointId, reorgChainId, forkBlockNumber) {
8
+ function make(sequence, chainIds, reorgChainId, floorCheckpointId, forkBlockNumber) {
9
+ let tmp;
10
+ tmp = sequence === "SharedAcrossChains" ? Frontier.fromEntries(chainIds.map(chainId => [
11
+ chainId,
12
+ floorCheckpointId
13
+ ])) : Frontier.fromEntries([[
14
+ reorgChainId,
15
+ floorCheckpointId
16
+ ]]);
26
17
  return {
27
- floors: {
28
- TAG: "EveryChain",
29
- _0: floorCheckpointId
18
+ checkpointBounds: {
19
+ sequence: sequence,
20
+ byChain: tmp
30
21
  },
31
- forkBlockNumberByChain: [[
32
- reorgChainId,
22
+ forkBlockNumberByChain: Object.fromEntries([[
23
+ ChainId.toString(reorgChainId),
33
24
  forkBlockNumber
34
- ]]
25
+ ]])
35
26
  };
36
27
  }
37
28
 
38
- function mergeByChain(pending, next) {
39
- let merged = pending.slice();
40
- next.forEach(param => {
41
- let value = param[1];
42
- let chainId = param[0];
43
- let idx = Stdlib_Array.findIndexOpt(merged, param => param[0] === chainId);
44
- if (idx !== undefined) {
45
- let match = merged[idx];
46
- merged[idx] = [
47
- chainId,
48
- Primitive_object.min(match[1], value)
49
- ];
50
- return;
51
- }
52
- merged.push([
53
- chainId,
54
- value
55
- ]);
56
- });
57
- return merged;
58
- }
59
-
60
29
  function merge(pending, next) {
61
- let match = pending.floors;
62
- let match$1 = next.floors;
63
- let tmp;
64
- tmp = match.TAG === "EveryChain" ? (
65
- match$1.TAG === "EveryChain" ? ({
66
- TAG: "EveryChain",
67
- _0: Primitive_bigint.min(match._0, match$1._0)
68
- }) : Stdlib_JsError.throwWithMessage("Internal error: a rollback reaching every chain can't be merged with one isolated to a chain. The schema decides which a run builds, and it doesn't change.")
69
- ) : (
70
- match$1.TAG === "EveryChain" ? Stdlib_JsError.throwWithMessage("Internal error: a rollback reaching every chain can't be merged with one isolated to a chain. The schema decides which a run builds, and it doesn't change.") : ({
71
- TAG: "PerChain",
72
- _0: mergeByChain(match._0, match$1._0)
73
- })
74
- );
75
30
  return {
76
- floors: tmp,
77
- forkBlockNumberByChain: mergeByChain(pending.forkBlockNumberByChain, next.forkBlockNumberByChain)
31
+ checkpointBounds: {
32
+ sequence: next.checkpointBounds.sequence,
33
+ byChain: Frontier.mergeMin(pending.checkpointBounds.byChain, next.checkpointBounds.byChain)
34
+ },
35
+ forkBlockNumberByChain: Utils.Dict.mergeWith(pending.forkBlockNumberByChain, next.forkBlockNumberByChain, Primitive_int.min)
78
36
  };
79
37
  }
80
38
 
81
39
  function forkBlockNumber(floors, chainId) {
82
- return Stdlib_Option.map(floors.forkBlockNumberByChain.find(param => param[0] === chainId), param => param[1]);
40
+ return floors.forkBlockNumberByChain[chainId];
83
41
  }
84
42
 
85
43
  export {
86
- isolated,
87
- global,
88
- mergeByChain,
44
+ make,
89
45
  merge,
90
46
  forkBlockNumber,
91
47
  }
92
- /* No side effect */
48
+ /* Utils Not a pure module */
@@ -142,6 +142,9 @@ external materialize: (
142
142
  // Hash of a stored block, if the store still holds it.
143
143
  @send external getHash: (t, int) => Null.t<string> = "getHash"
144
144
 
145
+ // Unix timestamp of a stored block - exactly that block, on every ecosystem.
146
+ @send external getTimestamp: (t, int) => Null.t<int> = "getTimestamp"
147
+
145
148
  // Block numbers in `[fromBlock, belowBlock)` with a stored hash, ascending.
146
149
  @send
147
150
  external getHashedBlockNumbers: (t, ~fromBlock: int, ~belowBlock: int) => array<int> =
@@ -141,7 +141,9 @@ let make = (
141
141
  }
142
142
 
143
143
  let pageFetchTime = startFetchingBatchTimeRef->Performance.secondsSince
144
- let requestStats = [{Source.method: "getLogs", seconds: pageFetchTime}]
144
+ let requestStats = [
145
+ {Source.method: "getLogs", seconds: pageFetchTime, responseBlocks: pageUnsafe.responseBlocks},
146
+ ]
145
147
 
146
148
  //set height and next from block
147
149
  let knownHeight = pageUnsafe.archiveHeight
@@ -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;
@@ -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.
@@ -165,6 +166,7 @@ module GetLogs = {
165
166
  {
166
167
  items: res.items,
167
168
  nextBlock: res.nextBlock,
169
+ responseBlocks: res.responseBlocks,
168
170
  archiveHeight: res.archiveHeight->Option.getOr(0), //Archive Height is only None if height is 0
169
171
  transactionStore,
170
172
  blockStore,
@@ -176,6 +176,7 @@ async function query(client, fromBlock, toBlock, maxNumLogs, registrationIndexes
176
176
  items: res.items,
177
177
  nextBlock: res.nextBlock,
178
178
  archiveHeight: Stdlib_Option.getOr(res.archiveHeight, 0),
179
+ responseBlocks: res.responseBlocks,
179
180
  transactionStore: match[1],
180
181
  blockStore: match[2]
181
182
  };
@@ -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
  }
@@ -283,6 +283,9 @@ module EventItems = {
283
283
  archiveHeight: option<int>,
284
284
  nextBlock: int,
285
285
  items: array<item>,
286
+ // Blocks the server returned for the query, counted before routing drops
287
+ // the ones no item joins to.
288
+ responseBlocks: int,
286
289
  }
287
290
  }
288
291
 
@@ -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
+ }
@@ -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
@@ -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
  })
@@ -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
  });
@@ -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>