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
|
@@ -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,8 +543,8 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
|
500
543
|
retry: retry
|
|
501
544
|
});
|
|
502
545
|
try {
|
|
503
|
-
let
|
|
504
|
-
|
|
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);
|
|
505
548
|
sourceState.lastFailedAt = undefined;
|
|
506
549
|
responseRef = response;
|
|
507
550
|
} catch (raw_error) {
|
|
@@ -637,11 +680,13 @@ async function getBlockHashes(sourceManager, blockNumbers, isRealtime) {
|
|
|
637
680
|
});
|
|
638
681
|
try {
|
|
639
682
|
let res = await source.getBlockHashes(blockNumbers, logger$1);
|
|
640
|
-
|
|
683
|
+
recordRequestStats(sourceState, res.requestStats);
|
|
684
|
+
let data = res.result;
|
|
685
|
+
if (data.TAG === "Ok") {
|
|
641
686
|
sourceState.lastFailedAt = undefined;
|
|
642
|
-
responseRef =
|
|
687
|
+
responseRef = data._0;
|
|
643
688
|
} else {
|
|
644
|
-
throw
|
|
689
|
+
throw data._0;
|
|
645
690
|
}
|
|
646
691
|
} catch (raw_exn) {
|
|
647
692
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
@@ -673,6 +718,7 @@ export {
|
|
|
673
718
|
getSourceRole,
|
|
674
719
|
make,
|
|
675
720
|
getActiveSource,
|
|
721
|
+
getRequestStatSamples,
|
|
676
722
|
inFlightCount,
|
|
677
723
|
onReorg,
|
|
678
724
|
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 _,
|
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,9 +83,13 @@ 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
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
|
};
|
|
@@ -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
|
|
@@ -4,7 +4,7 @@ type options = {
|
|
|
4
4
|
chain: ChainMap.Chain.t,
|
|
5
5
|
endpointUrl: string,
|
|
6
6
|
apiToken: option<string>,
|
|
7
|
-
|
|
7
|
+
onEventRegistrations: array<Internal.svmOnEventRegistration>,
|
|
8
8
|
clientTimeoutMillis: int,
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -184,12 +184,12 @@ let parseDecoded = (
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// `block` is omitted; it's materialised from the block store at batch prep.
|
|
187
188
|
let toSvmInstruction = (
|
|
188
189
|
instr: SvmHyperSyncClient.ResponseTypes.instruction,
|
|
189
190
|
~programName,
|
|
190
191
|
~instructionName,
|
|
191
192
|
~logs,
|
|
192
|
-
~block,
|
|
193
193
|
): Envio.svmInstruction => {
|
|
194
194
|
programName,
|
|
195
195
|
instructionName,
|
|
@@ -204,14 +204,13 @@ let toSvmInstruction = (
|
|
|
204
204
|
d8: ?instr.d8,
|
|
205
205
|
params: ?(instr.decoded->Option.map(parseDecoded)),
|
|
206
206
|
?logs,
|
|
207
|
-
block,
|
|
208
207
|
}
|
|
209
208
|
|
|
210
209
|
// Probe the discriminator byte-length ordering longest-first. Stops at the
|
|
211
210
|
// first router hit. Falls back to the `_none` key (program-wide handler) when
|
|
212
211
|
// no discriminator-keyed handler matches.
|
|
213
212
|
let probeRouter = (
|
|
214
|
-
router: EventRouter.t<Internal.
|
|
213
|
+
router: EventRouter.t<Internal.svmOnEventRegistration>,
|
|
215
214
|
programId: SvmTypes.Pubkey.t,
|
|
216
215
|
instr: SvmHyperSyncClient.ResponseTypes.instruction,
|
|
217
216
|
byteLengthsDesc: array<int>,
|
|
@@ -268,9 +267,30 @@ let toQueryTxField = (field: Internal.svmTransactionField): option<
|
|
|
268
267
|
| TokenBalances => None
|
|
269
268
|
}
|
|
270
269
|
|
|
271
|
-
|
|
270
|
+
// Map a selected block field to its HyperSync query column. Slot/Blockhash/
|
|
271
|
+
// BlockTime are requested unconditionally (needed for reorg detection and the
|
|
272
|
+
// item's slot/timestamp), so selecting `slot`/`time`/`hash` adds no extra column.
|
|
273
|
+
let toQueryBlockField = (field: Internal.svmBlockField): option<
|
|
274
|
+
SvmHyperSyncClient.QueryTypes.blockField,
|
|
275
|
+
> =>
|
|
276
|
+
switch field {
|
|
277
|
+
| Slot | Time | Hash => None
|
|
278
|
+
| Height => Some(BlockHeight)
|
|
279
|
+
| ParentSlot => Some(ParentSlot)
|
|
280
|
+
| ParentHash => Some(ParentBlockhash)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let make = (
|
|
284
|
+
{chain, endpointUrl, apiToken, onEventRegistrations, clientTimeoutMillis}: options,
|
|
285
|
+
): t => {
|
|
272
286
|
let name = "SvmHyperSync"
|
|
273
|
-
|
|
287
|
+
|
|
288
|
+
// Definitions drive query/decode building; the registrations drive routing
|
|
289
|
+
// (they carry `isWildcard` and become each decoded item's `onEventRegistration`).
|
|
290
|
+
let eventConfigs =
|
|
291
|
+
onEventRegistrations->Array.map(reg =>
|
|
292
|
+
reg.eventConfig->(Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig)
|
|
293
|
+
)
|
|
274
294
|
|
|
275
295
|
// Built once at startup and handed to the client so `get` decodes matching
|
|
276
296
|
// instructions in Rust rather than per-instruction over the napi boundary.
|
|
@@ -285,7 +305,10 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
285
305
|
},
|
|
286
306
|
)
|
|
287
307
|
|
|
288
|
-
let (eventRouter, programOrderings) = EventRouter.fromSvmEventConfigsOrThrow(
|
|
308
|
+
let (eventRouter, programOrderings) = EventRouter.fromSvmEventConfigsOrThrow(
|
|
309
|
+
onEventRegistrations,
|
|
310
|
+
~chain,
|
|
311
|
+
)
|
|
289
312
|
|
|
290
313
|
// programId.toString -> sorted-desc byte lengths
|
|
291
314
|
let orderingByProgram = Dict.make()
|
|
@@ -332,6 +355,24 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
332
355
|
}
|
|
333
356
|
)
|
|
334
357
|
|
|
358
|
+
// Union of selected block fields across the chain's events. `slot`/`time`/
|
|
359
|
+
// `hash` are always fetched (as Slot/BlockTime/Blockhash); the rest are added
|
|
360
|
+
// only when an instruction selected them.
|
|
361
|
+
let blockQueryFields = {
|
|
362
|
+
let fields: array<SvmHyperSyncClient.QueryTypes.blockField> = [Slot, Blockhash, BlockTime]
|
|
363
|
+
let selected = Utils.Set.make()
|
|
364
|
+
eventConfigs->Array.forEach(cfg =>
|
|
365
|
+
cfg.selectedBlockFields->Utils.Set.forEach(field => selected->Utils.Set.add(field)->ignore)
|
|
366
|
+
)
|
|
367
|
+
selected->Utils.Set.forEach(field =>
|
|
368
|
+
switch field->toQueryBlockField {
|
|
369
|
+
| Some(queryField) => fields->Array.push(queryField)->ignore
|
|
370
|
+
| None => ()
|
|
371
|
+
}
|
|
372
|
+
)
|
|
373
|
+
fields
|
|
374
|
+
}
|
|
375
|
+
|
|
335
376
|
let getItemsOrThrow = async (
|
|
336
377
|
~fromBlock,
|
|
337
378
|
~toBlock,
|
|
@@ -353,7 +394,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
353
394
|
// field list returns no rows (instructions and blocks are exempt), so each
|
|
354
395
|
// opted-into table needs its columns spelled out here.
|
|
355
396
|
let fields: SvmHyperSyncClient.QueryTypes.fieldSelection = {
|
|
356
|
-
block:
|
|
397
|
+
block: blockQueryFields,
|
|
357
398
|
transaction: ?(needsTransactions ? Some(txQueryFields) : None),
|
|
358
399
|
log: ?(needsLogs ? Some([Slot, TransactionIndex, InstructionAddress, Kind, Message]) : None),
|
|
359
400
|
tokenBalance: ?(
|
|
@@ -372,9 +413,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
372
413
|
maxNumInstructions: itemsTarget,
|
|
373
414
|
}
|
|
374
415
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
let (resp, transactionStore) = try await client.get(~query) catch {
|
|
416
|
+
let (resp, transactionStore, blockStore) = try await client.get(~query) catch {
|
|
378
417
|
| exn =>
|
|
379
418
|
throw(
|
|
380
419
|
Source.GetItemsError(
|
|
@@ -393,11 +432,13 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
393
432
|
)
|
|
394
433
|
}
|
|
395
434
|
let pageFetchTime = pageFetchRef->Performance.secondsSince
|
|
435
|
+
let requestStats = [{Source.method: "getInstructions", seconds: pageFetchTime}]
|
|
396
436
|
|
|
397
437
|
let parsingRef = Performance.now()
|
|
398
438
|
|
|
399
|
-
// Per-slot
|
|
400
|
-
// without a block row (rare;
|
|
439
|
+
// Per-slot blockTime lookup from the response's `blocks` table, for the
|
|
440
|
+
// batch's `latestFetchedBlockTimestamp`. Slots without a block row (rare;
|
|
441
|
+
// usually skipped slots) fall back to `None`.
|
|
401
442
|
let blockTimeBySlot = Dict.make()
|
|
402
443
|
resp.data.blocks->Array.forEach(b => {
|
|
403
444
|
switch b.blockTime {
|
|
@@ -447,7 +488,11 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
447
488
|
|
|
448
489
|
switch maybeConfig {
|
|
449
490
|
| None => ()
|
|
450
|
-
| Some(
|
|
491
|
+
| Some(onEventRegistration) =>
|
|
492
|
+
let eventConfig =
|
|
493
|
+
onEventRegistration.eventConfig->(
|
|
494
|
+
Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig
|
|
495
|
+
)
|
|
451
496
|
let logKey =
|
|
452
497
|
instr.slot->Int.toString ++
|
|
453
498
|
":" ++
|
|
@@ -466,28 +511,19 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
466
511
|
)
|
|
467
512
|
)
|
|
468
513
|
|
|
469
|
-
let slotKey = instr.slot->Int.toString
|
|
470
|
-
let blockTime = blockTimeBySlot->Utils.Dict.dangerouslyGetNonOption(slotKey)
|
|
471
514
|
let payload = toSvmInstruction(
|
|
472
515
|
instr,
|
|
473
516
|
~programName=eventConfig.contractName,
|
|
474
517
|
~instructionName=eventConfig.name,
|
|
475
518
|
~logs=eventConfig.includeLogs ? maybeLogs : None,
|
|
476
|
-
~block={
|
|
477
|
-
slot: instr.slot,
|
|
478
|
-
time: blockTime->Option.getOr(0),
|
|
479
|
-
hash: "",
|
|
480
|
-
},
|
|
481
519
|
)
|
|
482
520
|
|
|
483
521
|
parsedQueueItems
|
|
484
522
|
->Array.push(
|
|
485
523
|
Internal.Event({
|
|
486
|
-
|
|
487
|
-
timestamp: blockTime->Option.getOr(0),
|
|
524
|
+
onEventRegistration,
|
|
488
525
|
chain,
|
|
489
526
|
blockNumber: instr.slot,
|
|
490
|
-
blockHash: "",
|
|
491
527
|
logIndex: synthLogIndex(instr),
|
|
492
528
|
// The parent transaction is materialised from the store at batch prep.
|
|
493
529
|
transactionIndex: instr.transactionIndex,
|
|
@@ -522,11 +558,14 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
522
558
|
parsedQueueItems,
|
|
523
559
|
// Raw transactions kept in Rust; materialised (selected fields) at batch prep.
|
|
524
560
|
transactionStore: Some(transactionStore),
|
|
561
|
+
// Raw blocks kept in Rust; materialised onto the payload at batch prep.
|
|
562
|
+
blockStore: Some(blockStore),
|
|
525
563
|
latestFetchedBlockNumber: highestSlot,
|
|
526
564
|
stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
|
|
527
565
|
knownHeight,
|
|
528
566
|
blockHashes,
|
|
529
567
|
fromBlockQueried: fromBlock,
|
|
568
|
+
requestStats,
|
|
530
569
|
}
|
|
531
570
|
}
|
|
532
571
|
|
|
@@ -535,6 +574,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
535
574
|
// wire, so we request `maxSlot + 1`; the caller filters to the exact slots.
|
|
536
575
|
let queryBlockDataRange = async (~fromSlot, ~toSlot) => {
|
|
537
576
|
let blockDatas = []
|
|
577
|
+
let requestStats = []
|
|
538
578
|
let fromRef = ref(fromSlot)
|
|
539
579
|
let keepGoing = ref(true)
|
|
540
580
|
while keepGoing.contents {
|
|
@@ -545,9 +585,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
545
585
|
fields: {block: [Slot, Blockhash, BlockTime]},
|
|
546
586
|
maxNumBlocks: 1000,
|
|
547
587
|
}
|
|
548
|
-
|
|
549
|
-
// Block-only query; the store
|
|
550
|
-
let (resp, _) = await client.get(~query)
|
|
588
|
+
let timerRef = Performance.now()
|
|
589
|
+
// Block-only query; the store pages are empty.
|
|
590
|
+
let (resp, _, _) = await client.get(~query)
|
|
591
|
+
requestStats
|
|
592
|
+
->Array.push({Source.method: "getBlockHashes", seconds: timerRef->Performance.secondsSince})
|
|
593
|
+
->ignore
|
|
551
594
|
resp.data.blocks->Array.forEach(b =>
|
|
552
595
|
blockDatas
|
|
553
596
|
->Array.push({
|
|
@@ -566,12 +609,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
566
609
|
fromRef := resp.nextSlot
|
|
567
610
|
}
|
|
568
611
|
}
|
|
569
|
-
blockDatas
|
|
612
|
+
(blockDatas, requestStats)
|
|
570
613
|
}
|
|
571
614
|
|
|
572
615
|
let getBlockHashes = async (~blockNumbers, ~logger as _) =>
|
|
573
616
|
switch blockNumbers->Array.get(0) {
|
|
574
|
-
| None => Ok([])
|
|
617
|
+
| None => {Source.result: Ok([]), requestStats: []}
|
|
575
618
|
| Some(firstSlot) =>
|
|
576
619
|
try {
|
|
577
620
|
let minSlot = ref(firstSlot)
|
|
@@ -586,14 +629,19 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
586
629
|
}
|
|
587
630
|
requested->Utils.Set.add(slot)->ignore
|
|
588
631
|
})
|
|
589
|
-
let blockDatas = await queryBlockDataRange(
|
|
632
|
+
let (blockDatas, requestStats) = await queryBlockDataRange(
|
|
590
633
|
~fromSlot=minSlot.contents,
|
|
591
634
|
~toSlot=maxSlot.contents,
|
|
592
635
|
)
|
|
593
636
|
// Keep one entry per requested slot; drop duplicates and unrelated slots.
|
|
594
|
-
|
|
637
|
+
{
|
|
638
|
+
Source.result: Ok(
|
|
639
|
+
blockDatas->Array.filter(data => requested->Utils.Set.delete(data.blockNumber)),
|
|
640
|
+
),
|
|
641
|
+
requestStats,
|
|
642
|
+
}
|
|
595
643
|
} catch {
|
|
596
|
-
| exn => Error(exn)
|
|
644
|
+
| exn => {Source.result: Error(exn), requestStats: []}
|
|
597
645
|
}
|
|
598
646
|
}
|
|
599
647
|
|
|
@@ -606,16 +654,9 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
606
654
|
getBlockHashes,
|
|
607
655
|
getHeightOrThrow: async () => {
|
|
608
656
|
let timer = Performance.now()
|
|
609
|
-
let
|
|
657
|
+
let height = await client.getHeight()
|
|
610
658
|
let seconds = timer->Performance.secondsSince
|
|
611
|
-
|
|
612
|
-
Prometheus.SourceRequestCount.addSeconds(
|
|
613
|
-
~sourceName=name,
|
|
614
|
-
~chainId,
|
|
615
|
-
~method="getHeight",
|
|
616
|
-
~seconds,
|
|
617
|
-
)
|
|
618
|
-
h
|
|
659
|
+
{height, requestStats: [{method: "getHeight", seconds}]}
|
|
619
660
|
},
|
|
620
661
|
getItemsOrThrow,
|
|
621
662
|
}
|