envio 3.3.0-alpha.6 → 3.3.0-alpha.8
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/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/ChainFetching.res +34 -34
- package/src/ChainFetching.res.mjs +29 -29
- package/src/ChainState.res +328 -165
- package/src/ChainState.res.mjs +205 -100
- package/src/ChainState.resi +16 -7
- package/src/Config.res +55 -27
- package/src/Config.res.mjs +46 -7
- package/src/ContractRegisterContext.res +2 -12
- package/src/ContractRegisterContext.res.mjs +3 -5
- package/src/Core.res +7 -0
- package/src/CrossChainState.res +54 -79
- package/src/CrossChainState.res.mjs +45 -60
- 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/EventConfigBuilder.res +105 -56
- package/src/EventConfigBuilder.res.mjs +69 -33
- package/src/EventProcessing.res +19 -15
- package/src/EventProcessing.res.mjs +13 -12
- package/src/FetchState.res +383 -182
- package/src/FetchState.res.mjs +260 -264
- package/src/HandlerLoader.res +7 -112
- package/src/HandlerLoader.res.mjs +1 -87
- package/src/HandlerRegister.res +209 -6
- package/src/HandlerRegister.res.mjs +90 -5
- package/src/HandlerRegister.resi +13 -2
- 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 +82 -36
- package/src/Internal.res.mjs +11 -1
- package/src/Main.res +22 -23
- package/src/Main.res.mjs +15 -17
- 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/SimulateItems.res +26 -8
- package/src/SimulateItems.res.mjs +11 -14
- 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 +54 -4
- package/src/sources/Evm.res.mjs +43 -4
- 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 +5 -2
- package/src/sources/Fuel.res.mjs +4 -3
- package/src/sources/HyperFuelSource.res +32 -40
- package/src/sources/HyperFuelSource.res.mjs +53 -45
- package/src/sources/HyperSync.res +35 -9
- package/src/sources/HyperSync.res.mjs +52 -35
- package/src/sources/HyperSync.resi +11 -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 +39 -58
- package/src/sources/HyperSyncSource.res.mjs +44 -44
- package/src/sources/RpcSource.res +114 -92
- package/src/sources/RpcSource.res.mjs +77 -46
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +6 -3
- package/src/sources/SimulateSource.res.mjs +13 -5
- package/src/sources/Source.res +25 -5
- package/src/sources/SourceManager.res +66 -3
- package/src/sources/SourceManager.res.mjs +55 -7
- package/src/sources/SourceManager.resi +10 -0
- package/src/sources/Svm.res +15 -10
- package/src/sources/Svm.res.mjs +24 -7
- package/src/sources/SvmHyperSyncClient.res +5 -6
- package/src/sources/SvmHyperSyncSource.res +84 -41
- package/src/sources/SvmHyperSyncSource.res.mjs +90 -43
- package/src/sources/TransactionStore.res +6 -107
- package/src/sources/TransactionStore.res.mjs +5 -86
- package/svm.schema.json +19 -0
|
@@ -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 _,
|
|
@@ -26,6 +26,7 @@ let make = (~items: array<Internal.item>, ~endBlock: int, ~chain: ChainMap.Chain
|
|
|
26
26
|
~knownHeight as _,
|
|
27
27
|
~partitionId as _,
|
|
28
28
|
~selection as _,
|
|
29
|
+
~itemsTarget as _,
|
|
29
30
|
~retry as _,
|
|
30
31
|
~logger as _,
|
|
31
32
|
) => {
|
|
@@ -42,14 +43,16 @@ let make = (~items: array<Internal.item>, ~endBlock: int, ~chain: ChainMap.Chain
|
|
|
42
43
|
Source.knownHeight: reportedHeight,
|
|
43
44
|
blockHashes: [],
|
|
44
45
|
parsedQueueItems: result,
|
|
45
|
-
// Simulate keeps the transaction inline on the payload; no store
|
|
46
|
+
// Simulate keeps the transaction and block inline on the payload; no store pages.
|
|
46
47
|
transactionStore: None,
|
|
48
|
+
blockStore: None,
|
|
47
49
|
fromBlockQueried: 0,
|
|
48
50
|
latestFetchedBlockNumber: reportedHeight,
|
|
49
51
|
latestFetchedBlockTimestamp: 0,
|
|
50
52
|
stats: {
|
|
51
53
|
totalTimeElapsed: 0.,
|
|
52
54
|
},
|
|
55
|
+
requestStats: [],
|
|
53
56
|
})
|
|
54
57
|
},
|
|
55
58
|
}
|
|
@@ -13,11 +13,17 @@ 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: []
|
|
18
21
|
}),
|
|
19
|
-
getHeightOrThrow: () => Promise.resolve(
|
|
20
|
-
|
|
22
|
+
getHeightOrThrow: () => Promise.resolve({
|
|
23
|
+
height: Primitive_int.max(endBlock, 1),
|
|
24
|
+
requestStats: []
|
|
25
|
+
}),
|
|
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);
|
|
23
29
|
return Promise.resolve({
|
|
@@ -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>,
|
|
@@ -64,6 +78,12 @@ type t = {
|
|
|
64
78
|
~knownHeight: int,
|
|
65
79
|
~partitionId: string,
|
|
66
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,
|
|
67
87
|
~retry: int,
|
|
68
88
|
~logger: Pino.t,
|
|
69
89
|
) => promise<blockRangeFetchResponse>,
|
|
@@ -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,9 +734,17 @@ let executeQuery = async (
|
|
|
680
734
|
~partitionId=query.partitionId,
|
|
681
735
|
~knownHeight,
|
|
682
736
|
~selection=query.selection,
|
|
737
|
+
// Ceil (not truncate) so a sub-1 target from a sparse partition over a
|
|
738
|
+
// small range doesn't round down to 0 and ask the backend to cap the
|
|
739
|
+
// response at nothing — 0 items is indistinguishable from "no signal".
|
|
740
|
+
~itemsTarget={
|
|
741
|
+
let target = query.itemsTarget->Math.ceil->Float.toInt
|
|
742
|
+
target > 0 ? target : FetchState.minItemsTarget->Float.toInt
|
|
743
|
+
},
|
|
683
744
|
~retry,
|
|
684
745
|
~logger,
|
|
685
746
|
)
|
|
747
|
+
sourceState->recordRequestStats(response.requestStats)
|
|
686
748
|
sourceState.lastFailedAt = None
|
|
687
749
|
responseRef := Some(response)
|
|
688
750
|
} catch {
|
|
@@ -822,7 +884,8 @@ let getBlockHashes = async (sourceManager: t, ~blockNumbers: array<int>, ~isReal
|
|
|
822
884
|
|
|
823
885
|
try {
|
|
824
886
|
let res = await source.getBlockHashes(~blockNumbers, ~logger)
|
|
825
|
-
|
|
887
|
+
sourceState->recordRequestStats(res.requestStats)
|
|
888
|
+
switch res.result {
|
|
826
889
|
| Ok(data) =>
|
|
827
890
|
sourceState.lastFailedAt = None
|
|
828
891
|
responseRef := Some(data)
|
|
@@ -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,7 +171,8 @@ 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
177
|
statusStart: Performance.now(),
|
|
143
178
|
status: "Idle",
|
|
@@ -263,7 +298,9 @@ async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stall
|
|
|
263
298
|
let h = initialHeight;
|
|
264
299
|
while (h <= knownHeight && newHeight.contents <= initialHeight) {
|
|
265
300
|
try {
|
|
266
|
-
|
|
301
|
+
let res = await source.getHeightOrThrow();
|
|
302
|
+
recordRequestStats(sourceState, res.requestStats);
|
|
303
|
+
h = res.height;
|
|
267
304
|
} catch (exn) {
|
|
268
305
|
|
|
269
306
|
}
|
|
@@ -282,7 +319,9 @@ async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stall
|
|
|
282
319
|
}
|
|
283
320
|
} else {
|
|
284
321
|
try {
|
|
285
|
-
let
|
|
322
|
+
let res = await source.getHeightOrThrow();
|
|
323
|
+
recordRequestStats(sourceState, res.requestStats);
|
|
324
|
+
let height$1 = res.height;
|
|
286
325
|
newHeight.contents = height$1;
|
|
287
326
|
if (height$1 <= knownHeight) {
|
|
288
327
|
retry = 0;
|
|
@@ -299,6 +338,10 @@ async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stall
|
|
|
299
338
|
resolvers.forEach(resolve => resolve(newHeight));
|
|
300
339
|
});
|
|
301
340
|
sourceState.unsubscribe = unsubscribe;
|
|
341
|
+
recordRequestStats(sourceState, [{
|
|
342
|
+
method: "heightSubscription",
|
|
343
|
+
seconds: 0
|
|
344
|
+
}]);
|
|
302
345
|
} else {
|
|
303
346
|
exit = 1;
|
|
304
347
|
}
|
|
@@ -500,7 +543,9 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
|
500
543
|
retry: retry
|
|
501
544
|
});
|
|
502
545
|
try {
|
|
503
|
-
let
|
|
546
|
+
let target = Math.ceil(query.itemsTarget) | 0;
|
|
547
|
+
let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, FetchState.deriveContractNameByAddress(query.addressesByContractName), knownHeight, query.partitionId, query.selection, target > 0 ? target : FetchState.minItemsTarget | 0, retry, logger$2);
|
|
548
|
+
recordRequestStats(sourceState, response.requestStats);
|
|
504
549
|
sourceState.lastFailedAt = undefined;
|
|
505
550
|
responseRef = response;
|
|
506
551
|
} catch (raw_error) {
|
|
@@ -636,11 +681,13 @@ async function getBlockHashes(sourceManager, blockNumbers, isRealtime) {
|
|
|
636
681
|
});
|
|
637
682
|
try {
|
|
638
683
|
let res = await source.getBlockHashes(blockNumbers, logger$1);
|
|
639
|
-
|
|
684
|
+
recordRequestStats(sourceState, res.requestStats);
|
|
685
|
+
let data = res.result;
|
|
686
|
+
if (data.TAG === "Ok") {
|
|
640
687
|
sourceState.lastFailedAt = undefined;
|
|
641
|
-
responseRef =
|
|
688
|
+
responseRef = data._0;
|
|
642
689
|
} else {
|
|
643
|
-
throw
|
|
690
|
+
throw data._0;
|
|
644
691
|
}
|
|
645
692
|
} catch (raw_exn) {
|
|
646
693
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
@@ -672,6 +719,7 @@ export {
|
|
|
672
719
|
getSourceRole,
|
|
673
720
|
make,
|
|
674
721
|
getActiveSource,
|
|
722
|
+
getRequestStatSamples,
|
|
675
723
|
inFlightCount,
|
|
676
724
|
onReorg,
|
|
677
725
|
dispatch,
|
|
@@ -21,6 +21,16 @@ let make: (
|
|
|
21
21
|
|
|
22
22
|
let getActiveSource: t => Source.t
|
|
23
23
|
|
|
24
|
+
type requestStatSample = {
|
|
25
|
+
sourceName: string,
|
|
26
|
+
chainId: int,
|
|
27
|
+
method: string,
|
|
28
|
+
count: int,
|
|
29
|
+
seconds: float,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let getRequestStatSamples: t => array<requestStatSample>
|
|
33
|
+
|
|
24
34
|
let inFlightCount: t => int
|
|
25
35
|
|
|
26
36
|
let onReorg: (t, ~rollbackTargetBlock: int) => unit
|
package/src/sources/Svm.res
CHANGED
|
@@ -16,6 +16,17 @@ let transactionFields =
|
|
|
16
16
|
// Computed per event at config build and cached on the event config.
|
|
17
17
|
let eventTransactionFieldMask = TransactionStore.makeMaskFn(transactionFields)
|
|
18
18
|
|
|
19
|
+
// Ordered block field names. The index of each is the field code shared with the
|
|
20
|
+
// Rust store (`SvmBlockField`) — keep this order in sync.
|
|
21
|
+
let blockFields = ["slot", "time", "hash", "height", "parentSlot", "parentHash"]
|
|
22
|
+
|
|
23
|
+
// `slot`/`time`/`hash` are always included; every other block field is opt-in
|
|
24
|
+
// via `field_selection.block_fields`. All are materialised from the store.
|
|
25
|
+
//
|
|
26
|
+
// One instruction's selected block fields → store selection bitmask. Computed per
|
|
27
|
+
// event at config build and cached on the event config.
|
|
28
|
+
let eventBlockFieldMask = BlockStore.makeMaskFn(blockFields)
|
|
29
|
+
|
|
19
30
|
let make = (~logger: Pino.t): Ecosystem.t => {
|
|
20
31
|
name: Svm,
|
|
21
32
|
blockNumberName: "height",
|
|
@@ -37,8 +48,8 @@ let make = (~logger: Pino.t): Ecosystem.t => {
|
|
|
37
48
|
Logging.createChildFrom(
|
|
38
49
|
~logger,
|
|
39
50
|
~params={
|
|
40
|
-
"program": eventItem.eventConfig.contractName,
|
|
41
|
-
"instruction": eventItem.eventConfig.name,
|
|
51
|
+
"program": eventItem.onEventRegistration.eventConfig.contractName,
|
|
52
|
+
"instruction": eventItem.onEventRegistration.eventConfig.name,
|
|
42
53
|
"chainId": eventItem.chain->ChainMap.Chain.toChainId,
|
|
43
54
|
"slot": eventItem.blockNumber,
|
|
44
55
|
"programId": instruction.programId,
|
|
@@ -84,14 +95,7 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S
|
|
|
84
95
|
let timerRef = Performance.now()
|
|
85
96
|
let height = await GetFinalizedSlot.route->Rest.fetch((), ~client)
|
|
86
97
|
let seconds = timerRef->Performance.secondsSince
|
|
87
|
-
|
|
88
|
-
Prometheus.SourceRequestCount.addSeconds(
|
|
89
|
-
~sourceName=name,
|
|
90
|
-
~chainId,
|
|
91
|
-
~method="getSlot",
|
|
92
|
-
~seconds,
|
|
93
|
-
)
|
|
94
|
-
height
|
|
98
|
+
{Source.height, requestStats: [{Source.method: "getSlot", seconds}]}
|
|
95
99
|
},
|
|
96
100
|
getItemsOrThrow: (
|
|
97
101
|
~fromBlock as _,
|
|
@@ -101,6 +105,7 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S
|
|
|
101
105
|
~knownHeight as _,
|
|
102
106
|
~partitionId as _,
|
|
103
107
|
~selection as _,
|
|
108
|
+
~itemsTarget as _,
|
|
104
109
|
~retry as _,
|
|
105
110
|
~logger as _,
|
|
106
111
|
) => JsError.throwWithMessage("Svm does not support getting items"),
|
package/src/sources/Svm.res.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import * as Rest from "../vendored/Rest.res.mjs";
|
|
|
5
5
|
import * as Utils from "../Utils.res.mjs";
|
|
6
6
|
import * as Logging from "../Logging.res.mjs";
|
|
7
7
|
import * as Internal from "../Internal.res.mjs";
|
|
8
|
-
import * as
|
|
8
|
+
import * as BlockStore from "./BlockStore.res.mjs";
|
|
9
9
|
import * as Performance from "../bindings/Performance.res.mjs";
|
|
10
10
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
11
11
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
@@ -19,6 +19,17 @@ let cleanUpRawEventFieldsInPlace = (fields => {
|
|
|
19
19
|
|
|
20
20
|
let eventTransactionFieldMask = TransactionStore.makeMaskFn(Internal.allSvmTransactionFields);
|
|
21
21
|
|
|
22
|
+
let blockFields = [
|
|
23
|
+
"slot",
|
|
24
|
+
"time",
|
|
25
|
+
"hash",
|
|
26
|
+
"height",
|
|
27
|
+
"parentSlot",
|
|
28
|
+
"parentHash"
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
let eventBlockFieldMask = BlockStore.makeMaskFn(blockFields);
|
|
32
|
+
|
|
22
33
|
function make(logger) {
|
|
23
34
|
return {
|
|
24
35
|
name: "svm",
|
|
@@ -34,8 +45,8 @@ function make(logger) {
|
|
|
34
45
|
toEventLogger: eventItem => {
|
|
35
46
|
let instruction = eventItem.payload;
|
|
36
47
|
return Logging.createChildFrom(logger, {
|
|
37
|
-
program: eventItem.eventConfig.contractName,
|
|
38
|
-
instruction: eventItem.eventConfig.name,
|
|
48
|
+
program: eventItem.onEventRegistration.eventConfig.contractName,
|
|
49
|
+
instruction: eventItem.onEventRegistration.eventConfig.name,
|
|
39
50
|
chainId: eventItem.chain,
|
|
40
51
|
slot: eventItem.blockNumber,
|
|
41
52
|
programId: instruction.programId
|
|
@@ -72,11 +83,15 @@ function makeRPCSource(chain, rpc, sourceForOpt) {
|
|
|
72
83
|
let timerRef = Performance.now();
|
|
73
84
|
let height = await Rest.fetch(route, undefined, client);
|
|
74
85
|
let seconds = Performance.secondsSince(timerRef);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
return {
|
|
87
|
+
height: height,
|
|
88
|
+
requestStats: [{
|
|
89
|
+
method: "getSlot",
|
|
90
|
+
seconds: seconds
|
|
91
|
+
}]
|
|
92
|
+
};
|
|
78
93
|
},
|
|
79
|
-
getItemsOrThrow: (param, param$1, param$2, param$3, param$4, param$5, param$6, param$7, param$8) => Stdlib_JsError.throwWithMessage("Svm does not support getting items")
|
|
94
|
+
getItemsOrThrow: (param, param$1, param$2, param$3, param$4, param$5, param$6, param$7, param$8, param$9) => Stdlib_JsError.throwWithMessage("Svm does not support getting items")
|
|
80
95
|
};
|
|
81
96
|
}
|
|
82
97
|
|
|
@@ -86,6 +101,8 @@ export {
|
|
|
86
101
|
cleanUpRawEventFieldsInPlace,
|
|
87
102
|
transactionFields,
|
|
88
103
|
eventTransactionFieldMask,
|
|
104
|
+
blockFields,
|
|
105
|
+
eventBlockFieldMask,
|
|
89
106
|
make,
|
|
90
107
|
GetFinalizedSlot,
|
|
91
108
|
makeRPCSource,
|
|
@@ -138,13 +138,12 @@ module QueryTypes = {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
module ResponseTypes = {
|
|
141
|
+
// Lean per-slot header for reorg detection and each item's slot/time; the
|
|
142
|
+
// selectable fields live in the block store and are materialised on demand.
|
|
141
143
|
type block = {
|
|
142
144
|
slot: int,
|
|
143
145
|
blockhash: string,
|
|
144
|
-
parentSlot?: int,
|
|
145
|
-
parentBlockhash?: string,
|
|
146
146
|
blockTime?: int,
|
|
147
|
-
blockHeight?: int,
|
|
148
147
|
}
|
|
149
148
|
|
|
150
149
|
/// Borsh-decoded view attached by the Rust client. `argsJson`/`accountsJson`
|
|
@@ -206,9 +205,9 @@ type queryResponse = ResponseTypes.queryResponse
|
|
|
206
205
|
|
|
207
206
|
type t = {
|
|
208
207
|
getHeight: unit => promise<int>,
|
|
209
|
-
// Returns the response plus
|
|
210
|
-
// keyed by (slot, transactionIndex), materialised at batch prep.
|
|
211
|
-
get: (~query: query) => promise<(queryResponse, TransactionStore.t)>,
|
|
208
|
+
// Returns the response plus pages of raw transactions and blocks (kept in
|
|
209
|
+
// Rust), keyed by (slot, transactionIndex) / slot, materialised at batch prep.
|
|
210
|
+
get: (~query: query) => promise<(queryResponse, TransactionStore.t, BlockStore.t)>,
|
|
212
211
|
}
|
|
213
212
|
|
|
214
213
|
@send
|