envio 3.3.0-alpha.1 → 3.3.0-alpha.11
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/evm.schema.json +10 -0
- package/index.d.ts +45 -25
- package/licenses/CLA.md +35 -0
- package/licenses/EULA.md +67 -0
- package/licenses/LICENSE.md +45 -0
- package/licenses/README.md +35 -0
- package/package.json +10 -9
- package/src/Address.res +5 -2
- package/src/Address.res.mjs +3 -1
- package/src/Api.res +1 -1
- package/src/Api.res.mjs +1 -1
- package/src/Batch.res +0 -6
- package/src/Batch.res.mjs +1 -12
- package/src/BatchProcessing.res +7 -12
- package/src/BatchProcessing.res.mjs +8 -7
- package/src/Bin.res +3 -0
- package/src/Bin.res.mjs +4 -0
- package/src/ChainFetching.res +53 -16
- package/src/ChainFetching.res.mjs +37 -19
- package/src/ChainMetadata.res +1 -11
- package/src/ChainMetadata.res.mjs +1 -10
- package/src/ChainState.res +630 -161
- package/src/ChainState.res.mjs +438 -101
- package/src/ChainState.resi +77 -4
- package/src/Config.res +70 -38
- package/src/Config.res.mjs +52 -12
- package/src/ContractRegisterContext.res +2 -12
- package/src/ContractRegisterContext.res.mjs +3 -5
- package/src/Core.res +34 -3
- package/src/Core.res.mjs +12 -0
- package/src/CrossChainState.res +130 -89
- package/src/CrossChainState.res.mjs +66 -61
- package/src/CrossChainState.resi +1 -1
- package/src/Ecosystem.res +5 -6
- package/src/Ecosystem.res.mjs +3 -3
- package/src/Envio.res +17 -11
- package/src/EnvioGlobal.res +53 -0
- package/src/EnvioGlobal.res.mjs +31 -0
- package/src/EventConfigBuilder.res +191 -127
- package/src/EventConfigBuilder.res.mjs +116 -66
- package/src/EventProcessing.res +77 -28
- package/src/EventProcessing.res.mjs +56 -29
- package/src/ExitOnCaughtUp.res +10 -2
- package/src/ExitOnCaughtUp.res.mjs +10 -1
- package/src/FetchState.res +684 -415
- package/src/FetchState.res.mjs +505 -486
- package/src/HandlerLoader.res +8 -104
- package/src/HandlerLoader.res.mjs +2 -81
- package/src/HandlerRegister.res +520 -144
- package/src/HandlerRegister.res.mjs +286 -133
- package/src/HandlerRegister.resi +24 -8
- package/src/IndexerState.res +10 -3
- package/src/IndexerState.res.mjs +11 -4
- package/src/IndexerState.resi +2 -1
- package/src/IndexingAddresses.res +108 -0
- package/src/IndexingAddresses.res.mjs +101 -0
- package/src/IndexingAddresses.resi +34 -0
- package/src/Internal.res +166 -48
- package/src/Internal.res.mjs +30 -2
- package/src/LoadLayer.res +5 -5
- package/src/LoadLayer.res.mjs +6 -6
- package/src/LogSelection.res +92 -217
- package/src/LogSelection.res.mjs +95 -184
- package/src/Main.res +71 -236
- package/src/Main.res.mjs +60 -145
- package/src/Metrics.res +74 -0
- package/src/Metrics.res.mjs +75 -0
- package/src/PgStorage.res +4 -4
- package/src/PgStorage.res.mjs +5 -5
- package/src/Prometheus.res +10 -63
- package/src/Prometheus.res.mjs +124 -171
- package/src/PruneStaleHistory.res +2 -2
- package/src/PruneStaleHistory.res.mjs +3 -3
- package/src/RawEvent.res +7 -2
- package/src/RawEvent.res.mjs +4 -4
- package/src/Rollback.res +3 -3
- package/src/Rollback.res.mjs +4 -4
- package/src/RollbackCommit.res +4 -1
- package/src/RollbackCommit.res.mjs +3 -2
- package/src/SimulateDeadInputTracker.res +85 -0
- package/src/SimulateDeadInputTracker.res.mjs +73 -0
- package/src/SimulateDeadInputTracker.resi +12 -0
- package/src/SimulateItems.res +128 -28
- package/src/SimulateItems.res.mjs +82 -40
- package/src/TestIndexer.res +55 -41
- package/src/TestIndexer.res.mjs +39 -32
- package/src/TopicFilter.res +1 -25
- package/src/TopicFilter.res.mjs +0 -74
- package/src/bindings/Performance.res +7 -0
- package/src/bindings/Performance.res.mjs +21 -0
- package/src/bindings/Performance.resi +7 -0
- package/src/bindings/Viem.res +0 -46
- package/src/bindings/Viem.res.mjs +1 -43
- package/src/sources/BlockStore.res +46 -0
- package/src/sources/BlockStore.res.mjs +24 -0
- package/src/sources/EventRouter.res +31 -49
- package/src/sources/EventRouter.res.mjs +12 -25
- package/src/sources/Evm.res +83 -72
- package/src/sources/Evm.res.mjs +53 -69
- package/src/sources/EvmChain.res +7 -34
- package/src/sources/EvmChain.res.mjs +6 -27
- package/src/sources/EvmRpcClient.res +71 -5
- package/src/sources/EvmRpcClient.res.mjs +13 -4
- package/src/sources/FieldMask.res +39 -0
- package/src/sources/FieldMask.res.mjs +42 -0
- package/src/sources/Fuel.res +9 -6
- package/src/sources/Fuel.res.mjs +5 -10
- package/src/sources/HyperFuelSource.res +46 -50
- package/src/sources/HyperFuelSource.res.mjs +64 -54
- package/src/sources/HyperSync.res +45 -42
- package/src/sources/HyperSync.res.mjs +68 -60
- package/src/sources/HyperSync.resi +14 -4
- package/src/sources/HyperSyncClient.res +107 -119
- package/src/sources/HyperSyncClient.res.mjs +36 -31
- package/src/sources/HyperSyncHeightStream.res +1 -8
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
- package/src/sources/HyperSyncSource.res +60 -254
- package/src/sources/HyperSyncSource.res.mjs +57 -180
- package/src/sources/Rpc.res +15 -79
- package/src/sources/Rpc.res.mjs +25 -101
- package/src/sources/RpcSource.res +221 -514
- package/src/sources/RpcSource.res.mjs +216 -411
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +44 -20
- package/src/sources/SimulateSource.res.mjs +41 -14
- package/src/sources/Source.res +34 -6
- package/src/sources/SourceManager.res +77 -19
- package/src/sources/SourceManager.res.mjs +63 -21
- package/src/sources/SourceManager.resi +10 -0
- package/src/sources/Svm.res +31 -16
- package/src/sources/Svm.res.mjs +36 -13
- package/src/sources/SvmHyperSyncClient.res +5 -32
- package/src/sources/SvmHyperSyncSource.res +159 -134
- package/src/sources/SvmHyperSyncSource.res.mjs +164 -129
- package/src/sources/TransactionStore.res +49 -0
- package/src/sources/TransactionStore.res.mjs +30 -0
- package/src/tui/Tui.res +13 -16
- package/src/tui/Tui.res.mjs +12 -14
- package/svm.schema.json +49 -37
- package/src/bindings/Hrtime.res +0 -58
- package/src/bindings/Hrtime.res.mjs +0 -90
- package/src/bindings/Hrtime.resi +0 -30
|
@@ -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);
|
|
@@ -1,52 +1,76 @@
|
|
|
1
1
|
let make = (~items: array<Internal.item>, ~endBlock: int, ~chain: ChainMap.Chain.t): Source.t => {
|
|
2
|
-
|
|
3
|
-
// Return all items on the first call and empty on subsequent calls to prevent
|
|
4
|
-
// duplicate event processing.
|
|
5
|
-
let delivered = ref(false)
|
|
2
|
+
let reportedHeight = max(endBlock, 1)
|
|
6
3
|
|
|
7
4
|
{
|
|
8
5
|
name: "SimulateSource",
|
|
6
|
+
simulateItems: items,
|
|
9
7
|
sourceFor: Sync,
|
|
10
8
|
chain,
|
|
11
9
|
poweredByHyperSync: false,
|
|
12
10
|
pollingInterval: 0,
|
|
13
11
|
getBlockHashes: (~blockNumbers as _, ~logger as _) => {
|
|
14
|
-
Promise.resolve(Ok([]))
|
|
12
|
+
Promise.resolve({Source.result: Ok([]), requestStats: []})
|
|
15
13
|
},
|
|
16
14
|
getHeightOrThrow: () => {
|
|
17
15
|
// Report at least height 1 so the engine doesn't treat 0 as "no blocks available"
|
|
18
|
-
Promise.resolve(
|
|
16
|
+
Promise.resolve({Source.height: reportedHeight, requestStats: []})
|
|
19
17
|
},
|
|
20
18
|
getItemsOrThrow: (
|
|
21
|
-
~fromBlock
|
|
22
|
-
~toBlock
|
|
19
|
+
~fromBlock,
|
|
20
|
+
~toBlock,
|
|
23
21
|
~addressesByContractName as _,
|
|
24
|
-
~
|
|
22
|
+
~contractNameByAddress,
|
|
25
23
|
~knownHeight as _,
|
|
26
24
|
~partitionId as _,
|
|
27
|
-
~selection
|
|
25
|
+
~selection: FetchState.selection,
|
|
26
|
+
~itemsTarget as _,
|
|
28
27
|
~retry as _,
|
|
29
28
|
~logger as _,
|
|
30
29
|
) => {
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
// Mirror a real backend: return only the items this query would match —
|
|
31
|
+
// in the block range, part of the selection, and (for non-wildcard events)
|
|
32
|
+
// emitted by an address the partition is querying. Wildcard events are
|
|
33
|
+
// over-fetched regardless of srcAddress, leaving the client-side address
|
|
34
|
+
// filter to gate them exactly as it does for a HyperSync response. Overlapping
|
|
35
|
+
// queries may return the same item more than once; the buffer dedups it.
|
|
36
|
+
let toBlockQueried = switch toBlock {
|
|
37
|
+
| Some(toBlock) => toBlock
|
|
38
|
+
| None => reportedHeight
|
|
37
39
|
}
|
|
40
|
+
let selectionEventIds = Utils.Set.make()
|
|
41
|
+
selection.onEventRegistrations->Array.forEach(reg =>
|
|
42
|
+
selectionEventIds->Utils.Set.add(reg.eventConfig.id)->ignore
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
let parsedQueueItems = items->Array.filter(item => {
|
|
46
|
+
let eventItem = item->Internal.castUnsafeEventItem
|
|
47
|
+
let {blockNumber, onEventRegistration} = eventItem
|
|
48
|
+
if blockNumber < fromBlock || blockNumber > toBlockQueried {
|
|
49
|
+
false
|
|
50
|
+
} else if !(selectionEventIds->Utils.Set.has(onEventRegistration.eventConfig.id)) {
|
|
51
|
+
false
|
|
52
|
+
} else if onEventRegistration.isWildcard {
|
|
53
|
+
true
|
|
54
|
+
} else {
|
|
55
|
+
let sa = eventItem.payload->Internal.getPayloadSrcAddress->Address.toString
|
|
56
|
+
contractNameByAddress->Utils.Dict.dangerouslyGetNonOption(sa)->Option.isSome
|
|
57
|
+
}
|
|
58
|
+
})
|
|
38
59
|
|
|
39
|
-
let reportedHeight = max(endBlock, 1)
|
|
40
60
|
Promise.resolve({
|
|
41
61
|
Source.knownHeight: reportedHeight,
|
|
42
62
|
blockHashes: [],
|
|
43
|
-
parsedQueueItems
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
parsedQueueItems,
|
|
64
|
+
// Simulate keeps the transaction and block inline on the payload; no store pages.
|
|
65
|
+
transactionStore: None,
|
|
66
|
+
blockStore: None,
|
|
67
|
+
fromBlockQueried: fromBlock,
|
|
68
|
+
latestFetchedBlockNumber: toBlockQueried,
|
|
46
69
|
latestFetchedBlockTimestamp: 0,
|
|
47
70
|
stats: {
|
|
48
71
|
totalTimeElapsed: 0.,
|
|
49
72
|
},
|
|
73
|
+
requestStats: [],
|
|
50
74
|
})
|
|
51
75
|
},
|
|
52
76
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
5
|
|
|
5
6
|
function make(items, endBlock, chain) {
|
|
6
|
-
let
|
|
7
|
-
contents: false
|
|
8
|
-
};
|
|
7
|
+
let reportedHeight = Primitive_int.max(endBlock, 1);
|
|
9
8
|
return {
|
|
10
9
|
name: "SimulateSource",
|
|
11
10
|
sourceFor: "Sync",
|
|
@@ -13,25 +12,53 @@ function make(items, endBlock, chain) {
|
|
|
13
12
|
poweredByHyperSync: false,
|
|
14
13
|
pollingInterval: 0,
|
|
15
14
|
getBlockHashes: (param, param$1) => Promise.resolve({
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
result: {
|
|
16
|
+
TAG: "Ok",
|
|
17
|
+
_0: []
|
|
18
|
+
},
|
|
19
|
+
requestStats: []
|
|
20
|
+
}),
|
|
21
|
+
getHeightOrThrow: () => Promise.resolve({
|
|
22
|
+
height: reportedHeight,
|
|
23
|
+
requestStats: []
|
|
18
24
|
}),
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let
|
|
22
|
-
|
|
25
|
+
getItemsOrThrow: (fromBlock, toBlock, param, contractNameByAddress, param$1, param$2, selection, param$3, param$4, param$5) => {
|
|
26
|
+
let toBlockQueried = toBlock !== undefined ? toBlock : reportedHeight;
|
|
27
|
+
let selectionEventIds = new Set();
|
|
28
|
+
selection.onEventRegistrations.forEach(reg => {
|
|
29
|
+
selectionEventIds.add(reg.eventConfig.id);
|
|
30
|
+
});
|
|
31
|
+
let parsedQueueItems = items.filter(item => {
|
|
32
|
+
let blockNumber = item.blockNumber;
|
|
33
|
+
if (blockNumber < fromBlock || blockNumber > toBlockQueried) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
let onEventRegistration = item.onEventRegistration;
|
|
37
|
+
if (!selectionEventIds.has(onEventRegistration.eventConfig.id)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (onEventRegistration.isWildcard) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
let sa = item.payload.srcAddress;
|
|
44
|
+
return Stdlib_Option.isSome(contractNameByAddress[sa]);
|
|
45
|
+
});
|
|
23
46
|
return Promise.resolve({
|
|
24
47
|
knownHeight: reportedHeight,
|
|
25
48
|
blockHashes: [],
|
|
26
|
-
parsedQueueItems:
|
|
27
|
-
|
|
28
|
-
|
|
49
|
+
parsedQueueItems: parsedQueueItems,
|
|
50
|
+
transactionStore: undefined,
|
|
51
|
+
blockStore: undefined,
|
|
52
|
+
fromBlockQueried: fromBlock,
|
|
53
|
+
latestFetchedBlockNumber: toBlockQueried,
|
|
29
54
|
latestFetchedBlockTimestamp: 0,
|
|
30
55
|
stats: {
|
|
31
56
|
"total time elapsed (s)": 0
|
|
32
|
-
}
|
|
57
|
+
},
|
|
58
|
+
requestStats: []
|
|
33
59
|
});
|
|
34
|
-
}
|
|
60
|
+
},
|
|
61
|
+
simulateItems: items
|
|
35
62
|
};
|
|
36
63
|
}
|
|
37
64
|
|
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
|
*/
|
|
@@ -18,10 +23,26 @@ type blockRangeFetchResponse = {
|
|
|
18
23
|
// a within-array hash mismatch on the same block number as a reorg.
|
|
19
24
|
blockHashes: array<ReorgDetection.blockData>,
|
|
20
25
|
parsedQueueItems: array<Internal.item>,
|
|
26
|
+
// Page of transactions for this response's items, keyed by (blockNumber,
|
|
27
|
+
// transactionIndex); merged into the chain's store on apply. `None` for
|
|
28
|
+
// sources that keep the transaction inline on the payload (RPC/Fuel/Simulate).
|
|
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>,
|
|
21
34
|
fromBlockQueried: int,
|
|
22
35
|
latestFetchedBlockNumber: int,
|
|
23
36
|
latestFetchedBlockTimestamp: int,
|
|
24
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>,
|
|
25
46
|
}
|
|
26
47
|
|
|
27
48
|
type getItemsRetry =
|
|
@@ -47,19 +68,22 @@ type t = {
|
|
|
47
68
|
poweredByHyperSync: bool,
|
|
48
69
|
/* Frequency (in ms) used when polling for new events on this network. */
|
|
49
70
|
pollingInterval: int,
|
|
50
|
-
getBlockHashes: (
|
|
51
|
-
|
|
52
|
-
~logger: Pino.t,
|
|
53
|
-
) => promise<result<array<ReorgDetection.blockDataWithTimestamp>, exn>>,
|
|
54
|
-
getHeightOrThrow: unit => promise<int>,
|
|
71
|
+
getBlockHashes: (~blockNumbers: array<int>, ~logger: Pino.t) => promise<getBlockHashesResponse>,
|
|
72
|
+
getHeightOrThrow: unit => promise<getHeightResponse>,
|
|
55
73
|
getItemsOrThrow: (
|
|
56
74
|
~fromBlock: int,
|
|
57
75
|
~toBlock: option<int>,
|
|
58
76
|
~addressesByContractName: dict<array<Address.t>>,
|
|
59
|
-
~
|
|
77
|
+
~contractNameByAddress: dict<string>,
|
|
60
78
|
~knownHeight: int,
|
|
61
79
|
~partitionId: string,
|
|
62
80
|
~selection: FetchState.selection,
|
|
81
|
+
// Soft cap on the number of primary items (logs/instructions/receipts) the
|
|
82
|
+
// source should ask its backend for, from the query's own estResponseSize.
|
|
83
|
+
// A HyperSync-backed source enforces it server-side, so a wrong estimate
|
|
84
|
+
// truncates the response instead of overshooting the shared buffer. Sources
|
|
85
|
+
// without an equivalent lever (RPC, Fuel, Simulate) ignore it.
|
|
86
|
+
~itemsTarget: int,
|
|
63
87
|
~retry: int,
|
|
64
88
|
~logger: Pino.t,
|
|
65
89
|
) => promise<blockRangeFetchResponse>,
|
|
@@ -67,4 +91,8 @@ type t = {
|
|
|
67
91
|
// Invoked by SourceManager once a rollback target is known so the source can
|
|
68
92
|
// drop any state that may now point at an orphaned chain (e.g. RPC block cache).
|
|
69
93
|
onReorg?: (~rollbackTargetBlock: int) => unit,
|
|
94
|
+
// Present only on the simulate source: the items a test fed in. The chain
|
|
95
|
+
// tracks which of these never reach a handler so the run can report dead
|
|
96
|
+
// simulate inputs on completion.
|
|
97
|
+
simulateItems?: array<Internal.item>,
|
|
70
98
|
}
|
|
@@ -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,13 +14,35 @@ 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.
|
|
15
42
|
// with a mutable state for easier reasoning and testing.
|
|
16
43
|
type t = {
|
|
17
44
|
sourcesState: array<sourceState>,
|
|
18
|
-
mutable statusStart:
|
|
45
|
+
mutable statusStart: Performance.timeRef,
|
|
19
46
|
mutable status: sourceManagerStatus,
|
|
20
47
|
newBlockStallTimeout: int,
|
|
21
48
|
newBlockStallTimeoutRealtime: int,
|
|
@@ -24,6 +51,10 @@ type t = {
|
|
|
24
51
|
getHeightRetryInterval: (~retry: int) => int,
|
|
25
52
|
mutable activeSource: Source.t,
|
|
26
53
|
mutable waitingForNewBlockStateId: option<int>,
|
|
54
|
+
// Dedupes the "waiting for new blocks" trace so it fires once per contiguous
|
|
55
|
+
// wait period instead of on every epoch that re-enters the wait before any
|
|
56
|
+
// new block is found. Reset when blocks are found.
|
|
57
|
+
mutable waitingLogged: bool,
|
|
27
58
|
// Should take into consideration partitions fetching for previous states (before rollback)
|
|
28
59
|
mutable fetchingPartitionsCount: int,
|
|
29
60
|
recoveryTimeout: float,
|
|
@@ -42,6 +73,25 @@ type t = {
|
|
|
42
73
|
|
|
43
74
|
let getActiveSource = sourceManager => sourceManager.activeSource
|
|
44
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
|
+
|
|
45
95
|
// Partition queries currently in flight on this chain's sources. Summed across
|
|
46
96
|
// chains by CrossChainState to enforce the indexer-wide concurrency budget.
|
|
47
97
|
let inFlightCount = sourceManager => sourceManager.fetchingPartitionsCount
|
|
@@ -183,9 +233,11 @@ let make = (
|
|
|
183
233
|
pendingHeightResolvers: [],
|
|
184
234
|
disabled: false,
|
|
185
235
|
lastFailedAt: None,
|
|
236
|
+
requestStats: Dict.make(),
|
|
186
237
|
}),
|
|
187
238
|
activeSource: initialActiveSource,
|
|
188
239
|
waitingForNewBlockStateId: None,
|
|
240
|
+
waitingLogged: false,
|
|
189
241
|
fetchingPartitionsCount: 0,
|
|
190
242
|
newBlockStallTimeout,
|
|
191
243
|
newBlockStallTimeoutRealtime,
|
|
@@ -193,7 +245,7 @@ let make = (
|
|
|
193
245
|
reducedPollingInterval,
|
|
194
246
|
getHeightRetryInterval,
|
|
195
247
|
recoveryTimeout,
|
|
196
|
-
statusStart:
|
|
248
|
+
statusStart: Performance.now(),
|
|
197
249
|
status: Idle,
|
|
198
250
|
hasRealtime,
|
|
199
251
|
committedRateLimitTimeMs: 0.0,
|
|
@@ -211,9 +263,9 @@ let trackNewStatus = (sourceManager: t, ~newStatus) => {
|
|
|
211
263
|
}
|
|
212
264
|
promCounter->Prometheus.SafeCounter.handleFloat(
|
|
213
265
|
~labels=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
|
|
214
|
-
~value=sourceManager.statusStart->
|
|
266
|
+
~value=sourceManager.statusStart->Performance.secondsSince,
|
|
215
267
|
)
|
|
216
|
-
sourceManager.statusStart =
|
|
268
|
+
sourceManager.statusStart = Performance.now()
|
|
217
269
|
sourceManager.status = newStatus
|
|
218
270
|
}
|
|
219
271
|
|
|
@@ -339,7 +391,9 @@ let getSourceNewHeight = async (
|
|
|
339
391
|
let h = ref(initialHeight)
|
|
340
392
|
while h.contents <= knownHeight && !(newHeight.contents > initialHeight) {
|
|
341
393
|
try {
|
|
342
|
-
|
|
394
|
+
let res = await source.getHeightOrThrow()
|
|
395
|
+
sourceState->recordRequestStats(res.requestStats)
|
|
396
|
+
h := res.height
|
|
343
397
|
} catch {
|
|
344
398
|
| _ => ()
|
|
345
399
|
}
|
|
@@ -358,7 +412,9 @@ let getSourceNewHeight = async (
|
|
|
358
412
|
| None =>
|
|
359
413
|
// No subscription, use REST polling
|
|
360
414
|
try {
|
|
361
|
-
let
|
|
415
|
+
let res = await source.getHeightOrThrow()
|
|
416
|
+
sourceState->recordRequestStats(res.requestStats)
|
|
417
|
+
let height = res.height
|
|
362
418
|
|
|
363
419
|
newHeight := height
|
|
364
420
|
if height <= knownHeight {
|
|
@@ -380,6 +436,9 @@ let getSourceNewHeight = async (
|
|
|
380
436
|
}
|
|
381
437
|
})
|
|
382
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.}])
|
|
383
442
|
| _ =>
|
|
384
443
|
// Slowdown polling when the chain isn't progressing
|
|
385
444
|
let pollingInterval = if reducedPolling {
|
|
@@ -496,13 +555,14 @@ let waitForNewBlock = async (sourceManager: t, ~knownHeight, ~isRealtime, ~reduc
|
|
|
496
555
|
"knownHeight": knownHeight,
|
|
497
556
|
},
|
|
498
557
|
)
|
|
499
|
-
if
|
|
558
|
+
if !sourceManager.waitingLogged {
|
|
500
559
|
logger->Logging.childTrace(
|
|
501
|
-
|
|
502
|
-
|
|
560
|
+
reducedPolling
|
|
561
|
+
? `Waiting for new blocks with reduced polling (${(sourceManager.reducedPollingInterval / 1000)
|
|
562
|
+
->Int.toString}s). Chain is caught up, waiting for other chains to backfill.`
|
|
563
|
+
: "Initiating check for new blocks.",
|
|
503
564
|
)
|
|
504
|
-
|
|
505
|
-
logger->Logging.childTrace("Initiating check for new blocks.")
|
|
565
|
+
sourceManager.waitingLogged = true
|
|
506
566
|
}
|
|
507
567
|
|
|
508
568
|
let mainSources = sourceManager->getNextSources(~isRealtime)
|
|
@@ -600,6 +660,7 @@ let waitForNewBlock = async (sourceManager: t, ~knownHeight, ~isRealtime, ~reduc
|
|
|
600
660
|
"source": source.name,
|
|
601
661
|
"newBlockHeight": newBlockHeight,
|
|
602
662
|
})
|
|
663
|
+
sourceManager.waitingLogged = false
|
|
603
664
|
|
|
604
665
|
status := Done
|
|
605
666
|
|
|
@@ -669,19 +730,15 @@ let executeQuery = async (
|
|
|
669
730
|
~fromBlock=query.fromBlock,
|
|
670
731
|
~toBlock,
|
|
671
732
|
~addressesByContractName=query.addressesByContractName,
|
|
672
|
-
~
|
|
733
|
+
~contractNameByAddress=query.addressesByContractName->FetchState.deriveContractNameByAddress,
|
|
673
734
|
~partitionId=query.partitionId,
|
|
674
735
|
~knownHeight,
|
|
675
736
|
~selection=query.selection,
|
|
737
|
+
~itemsTarget=query.itemsTarget,
|
|
676
738
|
~retry,
|
|
677
739
|
~logger,
|
|
678
740
|
)
|
|
679
|
-
|
|
680
|
-
"msg": "Fetched block range from server",
|
|
681
|
-
"toBlock": response.latestFetchedBlockNumber,
|
|
682
|
-
"numEvents": response.parsedQueueItems->Array.length,
|
|
683
|
-
"stats": response.stats,
|
|
684
|
-
})
|
|
741
|
+
sourceState->recordRequestStats(response.requestStats)
|
|
685
742
|
sourceState.lastFailedAt = None
|
|
686
743
|
responseRef := Some(response)
|
|
687
744
|
} catch {
|
|
@@ -821,7 +878,8 @@ let getBlockHashes = async (sourceManager: t, ~blockNumbers: array<int>, ~isReal
|
|
|
821
878
|
|
|
822
879
|
try {
|
|
823
880
|
let res = await source.getBlockHashes(~blockNumbers, ~logger)
|
|
824
|
-
|
|
881
|
+
sourceState->recordRequestStats(res.requestStats)
|
|
882
|
+
switch res.result {
|
|
825
883
|
| Ok(data) =>
|
|
826
884
|
sourceState.lastFailedAt = None
|
|
827
885
|
responseRef := Some(data)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Utils from "../Utils.res.mjs";
|
|
4
|
-
import * as Hrtime from "../bindings/Hrtime.res.mjs";
|
|
5
4
|
import * as Source from "./Source.res.mjs";
|
|
6
5
|
import * as Logging from "../Logging.res.mjs";
|
|
7
6
|
import * as FetchState from "../FetchState.res.mjs";
|
|
8
7
|
import * as Prometheus from "../Prometheus.res.mjs";
|
|
8
|
+
import * as Performance from "../bindings/Performance.res.mjs";
|
|
9
9
|
import * as ErrorHandling from "../ErrorHandling.res.mjs";
|
|
10
10
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
11
11
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
@@ -14,10 +14,44 @@ import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
|
|
|
14
14
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
15
15
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
16
16
|
|
|
17
|
+
function recordRequestStats(sourceState, requestStats) {
|
|
18
|
+
requestStats.forEach(param => {
|
|
19
|
+
let seconds = param.seconds;
|
|
20
|
+
let method = param.method;
|
|
21
|
+
let agg = sourceState.requestStats[method];
|
|
22
|
+
if (agg !== undefined) {
|
|
23
|
+
agg.count = agg.count + 1 | 0;
|
|
24
|
+
agg.seconds = agg.seconds + seconds;
|
|
25
|
+
} else {
|
|
26
|
+
sourceState.requestStats[method] = {
|
|
27
|
+
count: 1,
|
|
28
|
+
seconds: seconds
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
function getActiveSource(sourceManager) {
|
|
18
35
|
return sourceManager.activeSource;
|
|
19
36
|
}
|
|
20
37
|
|
|
38
|
+
function getRequestStatSamples(sourceManager) {
|
|
39
|
+
let samples = [];
|
|
40
|
+
sourceManager.sourcesState.forEach(sourceState => {
|
|
41
|
+
let chainId = sourceState.source.chain;
|
|
42
|
+
Utils.Dict.forEachWithKey(sourceState.requestStats, (agg, method) => {
|
|
43
|
+
samples.push({
|
|
44
|
+
sourceName: sourceState.source.name,
|
|
45
|
+
chainId: chainId,
|
|
46
|
+
method: method,
|
|
47
|
+
count: agg.count,
|
|
48
|
+
seconds: agg.seconds
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return samples;
|
|
53
|
+
}
|
|
54
|
+
|
|
21
55
|
function inFlightCount(sourceManager) {
|
|
22
56
|
return sourceManager.fetchingPartitionsCount;
|
|
23
57
|
}
|
|
@@ -137,9 +171,10 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
|
|
|
137
171
|
unsubscribe: undefined,
|
|
138
172
|
pendingHeightResolvers: [],
|
|
139
173
|
disabled: false,
|
|
140
|
-
lastFailedAt: undefined
|
|
174
|
+
lastFailedAt: undefined,
|
|
175
|
+
requestStats: {}
|
|
141
176
|
})),
|
|
142
|
-
statusStart:
|
|
177
|
+
statusStart: Performance.now(),
|
|
143
178
|
status: "Idle",
|
|
144
179
|
newBlockStallTimeout: newBlockStallTimeout,
|
|
145
180
|
newBlockStallTimeoutRealtime: newBlockStallTimeoutRealtime,
|
|
@@ -148,6 +183,7 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
|
|
|
148
183
|
getHeightRetryInterval: getHeightRetryInterval,
|
|
149
184
|
activeSource: initialActiveSource,
|
|
150
185
|
waitingForNewBlockStateId: undefined,
|
|
186
|
+
waitingLogged: false,
|
|
151
187
|
fetchingPartitionsCount: 0,
|
|
152
188
|
recoveryTimeout: recoveryTimeout,
|
|
153
189
|
hasRealtime: hasRealtime,
|
|
@@ -172,8 +208,8 @@ function trackNewStatus(sourceManager, newStatus) {
|
|
|
172
208
|
promCounter = Prometheus.IndexingQueryTime.counter;
|
|
173
209
|
break;
|
|
174
210
|
}
|
|
175
|
-
Prometheus.SafeCounter.handleFloat(promCounter, sourceManager.activeSource.chain,
|
|
176
|
-
sourceManager.statusStart =
|
|
211
|
+
Prometheus.SafeCounter.handleFloat(promCounter, sourceManager.activeSource.chain, Performance.secondsSince(sourceManager.statusStart));
|
|
212
|
+
sourceManager.statusStart = Performance.now();
|
|
177
213
|
sourceManager.status = newStatus;
|
|
178
214
|
}
|
|
179
215
|
|
|
@@ -262,7 +298,9 @@ async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stall
|
|
|
262
298
|
let h = initialHeight;
|
|
263
299
|
while (h <= knownHeight && newHeight.contents <= initialHeight) {
|
|
264
300
|
try {
|
|
265
|
-
|
|
301
|
+
let res = await source.getHeightOrThrow();
|
|
302
|
+
recordRequestStats(sourceState, res.requestStats);
|
|
303
|
+
h = res.height;
|
|
266
304
|
} catch (exn) {
|
|
267
305
|
|
|
268
306
|
}
|
|
@@ -281,7 +319,9 @@ async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stall
|
|
|
281
319
|
}
|
|
282
320
|
} else {
|
|
283
321
|
try {
|
|
284
|
-
let
|
|
322
|
+
let res = await source.getHeightOrThrow();
|
|
323
|
+
recordRequestStats(sourceState, res.requestStats);
|
|
324
|
+
let height$1 = res.height;
|
|
285
325
|
newHeight.contents = height$1;
|
|
286
326
|
if (height$1 <= knownHeight) {
|
|
287
327
|
retry = 0;
|
|
@@ -298,6 +338,10 @@ async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stall
|
|
|
298
338
|
resolvers.forEach(resolve => resolve(newHeight));
|
|
299
339
|
});
|
|
300
340
|
sourceState.unsubscribe = unsubscribe;
|
|
341
|
+
recordRequestStats(sourceState, [{
|
|
342
|
+
method: "heightSubscription",
|
|
343
|
+
seconds: 0
|
|
344
|
+
}]);
|
|
301
345
|
} else {
|
|
302
346
|
exit = 1;
|
|
303
347
|
}
|
|
@@ -408,10 +452,9 @@ async function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPo
|
|
|
408
452
|
chainId: sourceManager.activeSource.chain,
|
|
409
453
|
knownHeight: knownHeight
|
|
410
454
|
});
|
|
411
|
-
if (
|
|
412
|
-
Logging.childTrace(logger, `Waiting for new blocks with reduced polling (` + (sourceManager.reducedPollingInterval / 1000 | 0).toString() + `s). Chain is caught up, waiting for other chains to backfill.`);
|
|
413
|
-
|
|
414
|
-
Logging.childTrace(logger, "Initiating check for new blocks.");
|
|
455
|
+
if (!sourceManager.waitingLogged) {
|
|
456
|
+
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.");
|
|
457
|
+
sourceManager.waitingLogged = true;
|
|
415
458
|
}
|
|
416
459
|
let mainSources = getNextSources(sourceManager, isRealtime, undefined);
|
|
417
460
|
let status = {
|
|
@@ -453,6 +496,7 @@ async function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPo
|
|
|
453
496
|
source: source.name,
|
|
454
497
|
newBlockHeight: newBlockHeight
|
|
455
498
|
});
|
|
499
|
+
sourceManager.waitingLogged = false;
|
|
456
500
|
status.contents = "Done";
|
|
457
501
|
return newBlockHeight;
|
|
458
502
|
}
|
|
@@ -499,13 +543,8 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
|
499
543
|
retry: retry
|
|
500
544
|
});
|
|
501
545
|
try {
|
|
502
|
-
let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, query.
|
|
503
|
-
|
|
504
|
-
msg: "Fetched block range from server",
|
|
505
|
-
toBlock: response.latestFetchedBlockNumber,
|
|
506
|
-
numEvents: response.parsedQueueItems.length,
|
|
507
|
-
stats: response.stats
|
|
508
|
-
});
|
|
546
|
+
let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, FetchState.deriveContractNameByAddress(query.addressesByContractName), knownHeight, query.partitionId, query.selection, query.itemsTarget, retry, logger$2);
|
|
547
|
+
recordRequestStats(sourceState, response.requestStats);
|
|
509
548
|
sourceState.lastFailedAt = undefined;
|
|
510
549
|
responseRef = response;
|
|
511
550
|
} catch (raw_error) {
|
|
@@ -641,11 +680,13 @@ async function getBlockHashes(sourceManager, blockNumbers, isRealtime) {
|
|
|
641
680
|
});
|
|
642
681
|
try {
|
|
643
682
|
let res = await source.getBlockHashes(blockNumbers, logger$1);
|
|
644
|
-
|
|
683
|
+
recordRequestStats(sourceState, res.requestStats);
|
|
684
|
+
let data = res.result;
|
|
685
|
+
if (data.TAG === "Ok") {
|
|
645
686
|
sourceState.lastFailedAt = undefined;
|
|
646
|
-
responseRef =
|
|
687
|
+
responseRef = data._0;
|
|
647
688
|
} else {
|
|
648
|
-
throw
|
|
689
|
+
throw data._0;
|
|
649
690
|
}
|
|
650
691
|
} catch (raw_exn) {
|
|
651
692
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
@@ -677,6 +718,7 @@ export {
|
|
|
677
718
|
getSourceRole,
|
|
678
719
|
make,
|
|
679
720
|
getActiveSource,
|
|
721
|
+
getRequestStatSamples,
|
|
680
722
|
inFlightCount,
|
|
681
723
|
onReorg,
|
|
682
724
|
dispatch,
|