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
@@ -9,7 +9,6 @@ import * as Address from "../Address.res.mjs";
9
9
  import * as Logging from "../Logging.res.mjs";
10
10
  import * as Internal from "../Internal.res.mjs";
11
11
  import * as LazyLoader from "../LazyLoader.res.mjs";
12
- import * as Prometheus from "../Prometheus.res.mjs";
13
12
  import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
14
13
  import * as EventRouter from "./EventRouter.res.mjs";
15
14
  import * as Performance from "../bindings/Performance.res.mjs";
@@ -47,11 +46,11 @@ function parseBlockInfo(json) {
47
46
  };
48
47
  }
49
48
 
50
- async function getKnownRawBlockWithBackoff(client, sourceName, chain, blockNumber, backoffMsOnFailure) {
49
+ async function getKnownRawBlockWithBackoff(client, sourceName, chain, blockNumber, backoffMsOnFailure, recordRequest) {
51
50
  let currentBackoff = backoffMsOnFailure;
52
51
  let result;
53
52
  while (Stdlib_Option.isNone(result)) {
54
- Prometheus.SourceRequestCount.increment(sourceName, chain, "eth_getBlockByNumber");
53
+ let timerRef = Performance.now();
55
54
  let exit = 0;
56
55
  let json;
57
56
  try {
@@ -59,6 +58,7 @@ async function getKnownRawBlockWithBackoff(client, sourceName, chain, blockNumbe
59
58
  exit = 1;
60
59
  } catch (raw_err) {
61
60
  let err = Primitive_exceptions.internalToException(raw_err);
61
+ recordRequest("eth_getBlockByNumber", Performance.secondsSince(timerRef));
62
62
  Logging.warn({
63
63
  err: Utils.prettifyExn(err),
64
64
  msg: `Issue while running fetching batch of events from the RPC. Will wait ` + currentBackoff.toString() + `ms and try again.`,
@@ -70,6 +70,7 @@ async function getKnownRawBlockWithBackoff(client, sourceName, chain, blockNumbe
70
70
  currentBackoff = (currentBackoff << 1);
71
71
  }
72
72
  if (exit === 1) {
73
+ recordRequest("eth_getBlockByNumber", Performance.secondsSince(timerRef));
73
74
  result = json;
74
75
  }
75
76
  };
@@ -265,14 +266,14 @@ function mergeAndDedupItems(itemsPerSelection) {
265
266
  return merged;
266
267
  }
267
268
 
268
- function getNextPage(fromBlock, toBlock, logSelections, loadBlock, sc, rpcClient, mutSuggestedBlockIntervals, partitionId, sourceName, chainId) {
269
+ function getNextPage(fromBlock, toBlock, logSelections, loadBlock, sc, rpcClient, mutSuggestedBlockIntervals, partitionId, recordRequest) {
269
270
  let queryTimoutPromise = Time.resolvePromiseAfterDelay(sc.queryTimeoutMillis).then(() => Promise.reject({
270
271
  RE_EXN_ID: QueryTimout,
271
272
  _1: `Query took longer than ` + (sc.queryTimeoutMillis / 1000 | 0).toString() + ` seconds`
272
273
  }));
273
274
  let latestFetchedBlockPromise = loadBlock(toBlock);
274
275
  let queryLogs = param => {
275
- Prometheus.SourceRequestCount.increment(sourceName, chainId, "eth_getLogs");
276
+ let timerRef = Performance.now();
276
277
  return rpcClient.getLogs({
277
278
  fromBlock: fromBlock,
278
279
  toBlock: toBlock,
@@ -286,6 +287,9 @@ function getNextPage(fromBlock, toBlock, logSelections, loadBlock, sc, rpcClient
286
287
  return filter;
287
288
  }
288
289
  })
290
+ }).then(items => {
291
+ recordRequest("eth_getLogs", Performance.secondsSince(timerRef));
292
+ return items;
289
293
  });
290
294
  };
291
295
  let len = logSelections.length;
@@ -352,9 +356,9 @@ function getNextPage(fromBlock, toBlock, logSelections, loadBlock, sc, rpcClient
352
356
  });
353
357
  }
354
358
 
355
- function getSelectionConfig(selection, chain) {
356
- let evmEventConfigs = selection.eventConfigs;
357
- if (Utils.$$Array.isEmpty(evmEventConfigs)) {
359
+ function getSelectionConfig(selection) {
360
+ let evmOnEventRegistrations = selection.onEventRegistrations;
361
+ if (Utils.$$Array.isEmpty(evmOnEventRegistrations)) {
358
362
  throw {
359
363
  RE_EXN_ID: Source.GetItemsError,
360
364
  _1: {
@@ -365,24 +369,19 @@ function getSelectionConfig(selection, chain) {
365
369
  };
366
370
  }
367
371
  let noAddressTopicSelections = [];
368
- let staticByContract = {};
369
- let dynamicByContract = {};
370
- let dynamicWildcardByContract = {};
372
+ let byContract = {};
373
+ let wildcardByContract = {};
371
374
  let contractNames = new Set();
372
- evmEventConfigs.forEach(param => {
373
- let contractName = param.contractName;
374
- let eventFilters = param.getEventFiltersOrThrow(chain);
375
- if (param.dependsOnAddresses) {
375
+ evmOnEventRegistrations.forEach(reg => {
376
+ let contractName = reg.eventConfig.contractName;
377
+ let resolvedWhere = reg.resolvedWhere;
378
+ if (reg.dependsOnAddresses) {
376
379
  contractNames.add(contractName);
377
- if (eventFilters.TAG === "Static") {
378
- return Utils.Dict.pushMany(staticByContract, contractName, eventFilters._0);
379
- } else {
380
- return Utils.Dict.push(param.isWildcard ? dynamicWildcardByContract : dynamicByContract, contractName, eventFilters._0);
381
- }
380
+ return Utils.Dict.pushMany(reg.isWildcard ? wildcardByContract : byContract, contractName, resolvedWhere.topicSelections);
381
+ } else {
382
+ noAddressTopicSelections.push(...LogSelection.materializeTopicSelections(resolvedWhere.topicSelections, []));
383
+ return;
382
384
  }
383
- let tmp;
384
- tmp = eventFilters.TAG === "Static" ? eventFilters._0 : eventFilters._0([]);
385
- noAddressTopicSelections.push(...tmp);
386
385
  });
387
386
  let toLogSelections = (addresses, topicSelections) => LogSelection.compressTopicSelections(topicSelections).map(topicSelection => ({
388
387
  addresses: addresses,
@@ -399,21 +398,13 @@ function getSelectionConfig(selection, chain) {
399
398
  if (addresses.length === 0) {
400
399
  return;
401
400
  }
402
- let addressedTopicSelections = [];
403
- let s = staticByContract[contractName];
404
- if (s !== undefined) {
405
- addressedTopicSelections.push(...s);
406
- }
407
- let fns = dynamicByContract[contractName];
408
- if (fns !== undefined) {
409
- fns.forEach(fn => {
410
- addressedTopicSelections.push(...fn(addresses));
411
- });
401
+ let topicSelections = byContract[contractName];
402
+ if (topicSelections !== undefined) {
403
+ logSelections.push(...toLogSelections(addresses, LogSelection.materializeTopicSelections(topicSelections, addresses)));
412
404
  }
413
- logSelections.push(...toLogSelections(addresses, addressedTopicSelections));
414
- let fns$1 = dynamicWildcardByContract[contractName];
415
- if (fns$1 !== undefined) {
416
- logSelections.push(...toLogSelections(undefined, fns$1.flatMap(fn => fn(addresses))));
405
+ let topicSelections$1 = wildcardByContract[contractName];
406
+ if (topicSelections$1 !== undefined) {
407
+ logSelections.push(...toLogSelections(undefined, LogSelection.materializeTopicSelections(topicSelections$1, addresses)));
417
408
  return;
418
409
  }
419
410
  });
@@ -424,8 +415,8 @@ function getSelectionConfig(selection, chain) {
424
415
  };
425
416
  }
426
417
 
427
- function memoGetSelectionConfig(chain) {
428
- return Utils.$$WeakMap.memoize(selection => getSelectionConfig(selection, chain));
418
+ function memoGetSelectionConfig() {
419
+ return Utils.$$WeakMap.memoize(getSelectionConfig);
429
420
  }
430
421
 
431
422
  let lowercaseAddressSchema = S$RescriptSchema.transform(S$RescriptSchema.string, param => ({
@@ -969,13 +960,28 @@ function make(param) {
969
960
  let host = Utils.Url.getHostFromUrl(url);
970
961
  let urlHost = host !== undefined ? host : Stdlib_JsError.throwWithMessage(`The RPC url for chain ` + chain.toString() + ` is in incorrect format. The RPC url needs to start with either http:// or https://`);
971
962
  let name = `RPC (` + urlHost + `)`;
972
- let getSelectionConfig = memoGetSelectionConfig(chain);
963
+ let getSelectionConfig$1 = Utils.$$WeakMap.memoize(getSelectionConfig);
973
964
  let mutSuggestedBlockIntervals = {};
974
965
  let client = Rpc.makeClient(url, headers);
975
966
  let rpcClient = EvmRpcClient.make(url, !lowercaseAddresses, undefined, headers, param.allEventParams);
967
+ let pendingRequestStats = [];
968
+ let recordRequest = (method, seconds) => {
969
+ pendingRequestStats.push({
970
+ method: method,
971
+ seconds: seconds
972
+ });
973
+ };
974
+ let drainRequestStats = () => {
975
+ let stats = pendingRequestStats.slice();
976
+ Utils.$$Array.clearInPlace(pendingRequestStats);
977
+ return stats;
978
+ };
976
979
  let makeTransactionLoader = () => LazyLoader.make(transactionHash => {
977
- Prometheus.SourceRequestCount.increment(name, chain, "eth_getTransactionByHash");
978
- return Rest.fetch(Rpc.GetTransactionByHash.rawRoute, transactionHash, client);
980
+ let timerRef = Performance.now();
981
+ return Rest.fetch(Rpc.GetTransactionByHash.rawRoute, transactionHash, client).then(res => {
982
+ recordRequest("eth_getTransactionByHash", Performance.secondsSince(timerRef));
983
+ return res;
984
+ });
979
985
  }, (am, exn) => Logging.error({
980
986
  err: Utils.prettifyExn(exn),
981
987
  msg: `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ` + (am._retryDelayMillis / 1000 | 0).toString() + ` seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`,
@@ -986,7 +992,7 @@ function make(param) {
986
992
  suggestedFix: "This likely means the RPC url you are using is not responding correctly. Please try another RPC endipoint."
987
993
  }
988
994
  }), undefined, undefined, undefined, undefined);
989
- let makeBlockLoader = () => LazyLoader.make(blockNumber => getKnownRawBlockWithBackoff(client, name, chain, blockNumber, 1000), (am, exn) => Logging.error({
995
+ let makeBlockLoader = () => LazyLoader.make(blockNumber => getKnownRawBlockWithBackoff(client, name, chain, blockNumber, 1000, recordRequest), (am, exn) => Logging.error({
990
996
  err: Utils.prettifyExn(exn),
991
997
  msg: `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ` + (am._retryDelayMillis / 1000 | 0).toString() + ` seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`,
992
998
  source: name,
@@ -997,8 +1003,11 @@ function make(param) {
997
1003
  }
998
1004
  }), undefined, undefined, undefined, undefined);
999
1005
  let makeReceiptLoader = () => LazyLoader.make(transactionHash => {
1000
- Prometheus.SourceRequestCount.increment(name, chain, "eth_getTransactionReceipt");
1001
- return Rest.fetch(Rpc.GetTransactionReceipt.rawRoute, transactionHash, client);
1006
+ let timerRef = Performance.now();
1007
+ return Rest.fetch(Rpc.GetTransactionReceipt.rawRoute, transactionHash, client).then(res => {
1008
+ recordRequest("eth_getTransactionReceipt", Performance.secondsSince(timerRef));
1009
+ return res;
1010
+ });
1002
1011
  }, (am, exn) => Logging.error({
1003
1012
  err: Utils.prettifyExn(exn),
1004
1013
  msg: `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ` + (am._retryDelayMillis / 1000 | 0).toString() + ` seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`,
@@ -1047,9 +1056,9 @@ function make(param) {
1047
1056
  let toBlock$1 = toBlock !== undefined ? Primitive_int.min(toBlock, knownHeight) : knownHeight;
1048
1057
  let suggestedToBlock = Primitive_int.max(Primitive_int.min((fromBlock + suggestedBlockInterval | 0) - 1 | 0, toBlock$1), fromBlock);
1049
1058
  let firstBlockParentPromise = fromBlock > 0 ? LazyLoader.get(blockLoader.contents, fromBlock - 1 | 0).then(json => parseBlockInfo(json)) : Promise.resolve(undefined);
1050
- let match = getSelectionConfig(selection);
1059
+ let match = getSelectionConfig$1(selection);
1051
1060
  let logSelections = match.getLogSelectionsOrThrow(addressesByContractName);
1052
- let match$1 = await getNextPage(fromBlock, suggestedToBlock, logSelections, blockNumber => LazyLoader.get(blockLoader.contents, blockNumber).then(parseBlockInfo), syncConfig, rpcClient, mutSuggestedBlockIntervals, partitionId, name, chain);
1061
+ let match$1 = await getNextPage(fromBlock, suggestedToBlock, logSelections, blockNumber => LazyLoader.get(blockLoader.contents, blockNumber).then(parseBlockInfo), syncConfig, rpcClient, mutSuggestedBlockIntervals, partitionId, recordRequest);
1053
1062
  let latestFetchedBlockInfo = match$1.latestFetchedBlockInfo;
1054
1063
  let items = match$1.items;
1055
1064
  let executedBlockInterval = (suggestedToBlock - fromBlock | 0) + 1 | 0;
@@ -1060,10 +1069,11 @@ function make(param) {
1060
1069
  let log = param.log;
1061
1070
  let topic0 = Stdlib_Option.getOr(log.topics[0], "0x0");
1062
1071
  let routedAddress = lowercaseAddresses ? Address.Evm.fromAddressLowercaseOrThrow(log.address) : Address.Evm.fromAddressOrThrow(log.address);
1063
- let eventConfig = EventRouter.get(eventRouter, EventRouter.getEvmEventId(topic0, log.topics.length), routedAddress, contractNameByAddress);
1064
- if (eventConfig === undefined) {
1072
+ let onEventRegistration = EventRouter.get(eventRouter, EventRouter.getEvmEventId(topic0, log.topics.length), routedAddress, contractNameByAddress);
1073
+ if (onEventRegistration === undefined) {
1065
1074
  return;
1066
1075
  }
1076
+ let eventConfig = onEventRegistration.eventConfig;
1067
1077
  let decoded = Stdlib_Option.flatMap(Primitive_option.fromNullable(param.params), __x => __x[eventConfig.contractName]);
1068
1078
  if (decoded === undefined) {
1069
1079
  return;
@@ -1110,11 +1120,9 @@ function make(param) {
1110
1120
  let block = match[0];
1111
1121
  return {
1112
1122
  kind: 0,
1113
- eventConfig: eventConfig,
1114
- timestamp: block.timestamp,
1123
+ onEventRegistration: onEventRegistration,
1115
1124
  chain: chain,
1116
1125
  blockNumber: block.number,
1117
- blockHash: block.hash,
1118
1126
  logIndex: log.logIndex,
1119
1127
  transactionIndex: log.transactionIndex,
1120
1128
  payload: {
@@ -1125,7 +1133,7 @@ function make(param) {
1125
1133
  srcAddress: routedAddress,
1126
1134
  logIndex: log.logIndex,
1127
1135
  transaction: Primitive_option.some(match[1]),
1128
- block: block
1136
+ block: Primitive_option.some(block)
1129
1137
  }
1130
1138
  };
1131
1139
  })();
@@ -1162,12 +1170,14 @@ function make(param) {
1162
1170
  blockHashes: blockHashes,
1163
1171
  parsedQueueItems: parsedQueueItems,
1164
1172
  transactionStore: undefined,
1173
+ blockStore: undefined,
1165
1174
  fromBlockQueried: fromBlock,
1166
1175
  latestFetchedBlockNumber: latestFetchedBlockInfo.number,
1167
1176
  latestFetchedBlockTimestamp: latestFetchedBlockInfo.timestamp,
1168
1177
  stats: {
1169
1178
  "total time elapsed (s)": totalTimeElapsed
1170
- }
1179
+ },
1180
+ requestStats: drainRequestStats()
1171
1181
  };
1172
1182
  };
1173
1183
  let onReorg = param => {
@@ -1175,19 +1185,28 @@ function make(param) {
1175
1185
  transactionLoader.contents = makeTransactionLoader();
1176
1186
  receiptLoader.contents = makeReceiptLoader();
1177
1187
  };
1178
- let getBlockHashes = (blockNumbers, _currentlyUnusedLogger) => Stdlib_Promise.$$catch(Promise.all(blockNumbers.map(blockNum => LazyLoader.get(blockLoader.contents, blockNum))).then(rawBlocks => ({
1179
- TAG: "Ok",
1180
- _0: rawBlocks.map(json => {
1181
- let b = parseBlockInfo(json);
1182
- return {
1183
- blockHash: b.hash,
1184
- blockNumber: b.number,
1185
- blockTimestamp: b.timestamp
1186
- };
1187
- })
1188
- })), exn => Promise.resolve({
1189
- TAG: "Error",
1190
- _0: exn
1188
+ let getBlockHashes = (blockNumbers, _currentlyUnusedLogger) => Stdlib_Promise.$$catch(Promise.all(blockNumbers.map(blockNum => LazyLoader.get(blockLoader.contents, blockNum))).then(rawBlocks => {
1189
+ let result = {
1190
+ TAG: "Ok",
1191
+ _0: rawBlocks.map(json => {
1192
+ let b = parseBlockInfo(json);
1193
+ return {
1194
+ blockHash: b.hash,
1195
+ blockNumber: b.number,
1196
+ blockTimestamp: b.timestamp
1197
+ };
1198
+ })
1199
+ };
1200
+ return {
1201
+ result: result,
1202
+ requestStats: drainRequestStats()
1203
+ };
1204
+ }), exn => Promise.resolve({
1205
+ result: {
1206
+ TAG: "Error",
1207
+ _0: exn
1208
+ },
1209
+ requestStats: drainRequestStats()
1191
1210
  }));
1192
1211
  let createHeightSubscription = Stdlib_Option.map(param.ws, wsUrl => (onHeight => RpcWebSocketHeightStream.subscribe(wsUrl, chain, onHeight)));
1193
1212
  return {
@@ -1203,15 +1222,14 @@ function make(param) {
1203
1222
  try {
1204
1223
  height = await rpcClient.getHeight();
1205
1224
  } catch (exn) {
1206
- let seconds = Performance.secondsSince(timerRef);
1207
- Prometheus.SourceRequestCount.increment(name, chain, "eth_blockNumber");
1208
- Prometheus.SourceRequestCount.addSeconds(name, chain, "eth_blockNumber", seconds);
1225
+ recordRequest("eth_blockNumber", Performance.secondsSince(timerRef));
1209
1226
  throw exn;
1210
1227
  }
1211
- let seconds$1 = Performance.secondsSince(timerRef);
1212
- Prometheus.SourceRequestCount.increment(name, chain, "eth_blockNumber");
1213
- Prometheus.SourceRequestCount.addSeconds(name, chain, "eth_blockNumber", seconds$1);
1214
- return height;
1228
+ recordRequest("eth_blockNumber", Performance.secondsSince(timerRef));
1229
+ return {
1230
+ height: height,
1231
+ requestStats: drainRequestStats()
1232
+ };
1215
1233
  },
1216
1234
  getItemsOrThrow: getItemsOrThrow,
1217
1235
  createHeightSubscription: createHeightSubscription,
@@ -108,11 +108,6 @@ let subscribe = (~wsUrl, ~chainId, ~onHeight: int => unit): (unit => unit) => {
108
108
  | NewHead(blockNumber) =>
109
109
  errorCount := 0
110
110
  resetStaleTimeout()
111
- Prometheus.SourceRequestCount.increment(
112
- ~sourceName="WebSocket",
113
- ~chainId,
114
- ~method="eth_subscribe",
115
- )
116
111
  onHeight(blockNumber)
117
112
  | SubscriptionConfirmed(_) => resetStaleTimeout()
118
113
  | ErrorResponse =>
@@ -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 _,
@@ -43,14 +43,16 @@ let make = (~items: array<Internal.item>, ~endBlock: int, ~chain: ChainMap.Chain
43
43
  Source.knownHeight: reportedHeight,
44
44
  blockHashes: [],
45
45
  parsedQueueItems: result,
46
- // 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.
47
47
  transactionStore: None,
48
+ blockStore: None,
48
49
  fromBlockQueried: 0,
49
50
  latestFetchedBlockNumber: reportedHeight,
50
51
  latestFetchedBlockTimestamp: 0,
51
52
  stats: {
52
53
  totalTimeElapsed: 0.,
53
54
  },
55
+ requestStats: [],
54
56
  })
55
57
  },
56
58
  }
@@ -13,10 +13,16 @@ 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: []
21
+ }),
22
+ getHeightOrThrow: () => Promise.resolve({
23
+ height: Primitive_int.max(endBlock, 1),
24
+ requestStats: []
18
25
  }),
19
- getHeightOrThrow: () => Promise.resolve(Primitive_int.max(endBlock, 1)),
20
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);
@@ -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>,
@@ -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,16 +734,11 @@ let executeQuery = async (
680
734
  ~partitionId=query.partitionId,
681
735
  ~knownHeight,
682
736
  ~selection=query.selection,
683
- // Ceil (not truncate) so a sub-1 estimate from a sparse partition over a
684
- // small range doesn't round down to 0 and ask the backend to cap the
685
- // response at nothing — 0 items is indistinguishable from "no signal".
686
- ~itemsTarget={
687
- let est = query.estResponseSize->Math.ceil->Float.toInt
688
- est > 0 ? est : FetchState.minEstResponseSize->Float.toInt
689
- },
737
+ ~itemsTarget=query.itemsTarget,
690
738
  ~retry,
691
739
  ~logger,
692
740
  )
741
+ sourceState->recordRequestStats(response.requestStats)
693
742
  sourceState.lastFailedAt = None
694
743
  responseRef := Some(response)
695
744
  } catch {
@@ -829,7 +878,8 @@ let getBlockHashes = async (sourceManager: t, ~blockNumbers: array<int>, ~isReal
829
878
 
830
879
  try {
831
880
  let res = await source.getBlockHashes(~blockNumbers, ~logger)
832
- switch res {
881
+ sourceState->recordRequestStats(res.requestStats)
882
+ switch res.result {
833
883
  | Ok(data) =>
834
884
  sourceState.lastFailedAt = None
835
885
  responseRef := Some(data)