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.
- package/index.d.ts +23 -5
- package/package.json +6 -6
- package/src/Api.res +1 -1
- package/src/Api.res.mjs +1 -1
- package/src/ChainFetching.res +21 -10
- package/src/ChainFetching.res.mjs +11 -10
- package/src/ChainState.res +401 -171
- package/src/ChainState.res.mjs +270 -104
- package/src/ChainState.resi +19 -7
- package/src/Config.res +11 -27
- package/src/Config.res.mjs +8 -7
- package/src/Core.res +7 -0
- package/src/CrossChainState.res +82 -82
- package/src/CrossChainState.res.mjs +52 -59
- package/src/CrossChainState.resi +1 -1
- package/src/Ecosystem.res +3 -3
- package/src/Ecosystem.res.mjs +3 -3
- package/src/Envio.res +14 -9
- package/src/EnvioGlobal.res +53 -0
- package/src/EnvioGlobal.res.mjs +31 -0
- package/src/EventConfigBuilder.res +117 -69
- package/src/EventConfigBuilder.res.mjs +71 -34
- package/src/EventProcessing.res +19 -15
- package/src/EventProcessing.res.mjs +13 -12
- package/src/FetchState.res +410 -185
- package/src/FetchState.res.mjs +268 -253
- package/src/HandlerLoader.res +8 -113
- package/src/HandlerLoader.res.mjs +2 -88
- package/src/HandlerRegister.res +518 -144
- package/src/HandlerRegister.res.mjs +285 -133
- package/src/HandlerRegister.resi +24 -8
- package/src/IndexerState.res +6 -3
- package/src/IndexerState.res.mjs +3 -3
- package/src/IndexerState.resi +1 -1
- package/src/IndexingAddresses.res +10 -7
- package/src/IndexingAddresses.res.mjs +8 -7
- package/src/IndexingAddresses.resi +3 -1
- package/src/Internal.res +104 -39
- package/src/Internal.res.mjs +11 -1
- package/src/LogSelection.res +102 -165
- package/src/LogSelection.res.mjs +101 -116
- package/src/Main.res +49 -164
- package/src/Main.res.mjs +39 -104
- package/src/Metrics.res +43 -2
- package/src/Metrics.res.mjs +39 -3
- package/src/Prometheus.res +0 -35
- package/src/Prometheus.res.mjs +33 -68
- package/src/RawEvent.res +7 -2
- package/src/RawEvent.res.mjs +4 -4
- package/src/RollbackCommit.res +4 -1
- package/src/RollbackCommit.res.mjs +3 -2
- package/src/SimulateItems.res +20 -7
- package/src/SimulateItems.res.mjs +7 -11
- package/src/TestIndexer.res +1 -1
- package/src/TestIndexer.res.mjs +1 -1
- package/src/sources/BlockStore.res +46 -0
- package/src/sources/BlockStore.res.mjs +24 -0
- package/src/sources/EventRouter.res +19 -12
- package/src/sources/EventRouter.res.mjs +7 -5
- package/src/sources/Evm.res +58 -5
- package/src/sources/Evm.res.mjs +44 -5
- package/src/sources/EvmChain.res +14 -18
- package/src/sources/EvmChain.res.mjs +12 -13
- package/src/sources/FieldMask.res +39 -0
- package/src/sources/FieldMask.res.mjs +42 -0
- package/src/sources/Fuel.res +8 -3
- package/src/sources/Fuel.res.mjs +5 -4
- package/src/sources/HyperFuelSource.res +31 -40
- package/src/sources/HyperFuelSource.res.mjs +52 -44
- package/src/sources/HyperSync.res +31 -9
- package/src/sources/HyperSync.res.mjs +47 -31
- package/src/sources/HyperSync.resi +10 -3
- package/src/sources/HyperSyncClient.res +15 -4
- package/src/sources/HyperSyncHeightStream.res +1 -8
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
- package/src/sources/HyperSyncSource.res +58 -90
- package/src/sources/HyperSyncSource.res.mjs +57 -66
- package/src/sources/RpcSource.res +118 -120
- package/src/sources/RpcSource.res.mjs +92 -74
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +5 -3
- package/src/sources/SimulateSource.res.mjs +12 -4
- package/src/sources/Source.res +19 -5
- package/src/sources/SourceManager.res +60 -10
- package/src/sources/SourceManager.res.mjs +54 -8
- package/src/sources/SourceManager.resi +10 -0
- package/src/sources/Svm.res +14 -10
- package/src/sources/Svm.res.mjs +23 -6
- package/src/sources/SvmHyperSyncClient.res +5 -6
- package/src/sources/SvmHyperSyncSource.res +82 -41
- package/src/sources/SvmHyperSyncSource.res.mjs +86 -41
- package/src/sources/TransactionStore.res +6 -107
- package/src/sources/TransactionStore.res.mjs +5 -86
- 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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
356
|
-
let
|
|
357
|
-
if (Utils.$$Array.isEmpty(
|
|
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
|
|
369
|
-
let
|
|
370
|
-
let dynamicWildcardByContract = {};
|
|
372
|
+
let byContract = {};
|
|
373
|
+
let wildcardByContract = {};
|
|
371
374
|
let contractNames = new Set();
|
|
372
|
-
|
|
373
|
-
let contractName =
|
|
374
|
-
let
|
|
375
|
-
if (
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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(
|
|
428
|
-
return Utils.$$WeakMap.memoize(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
1064
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
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
|
package/src/sources/Source.res
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
881
|
+
sourceState->recordRequestStats(res.requestStats)
|
|
882
|
+
switch res.result {
|
|
833
883
|
| Ok(data) =>
|
|
834
884
|
sourceState.lastFailedAt = None
|
|
835
885
|
responseRef := Some(data)
|