envio 3.3.0-alpha.1 → 3.3.0-alpha.3
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 -21
- package/package.json +6 -6
- package/src/Batch.res +0 -6
- package/src/Batch.res.mjs +0 -11
- package/src/BatchProcessing.res +2 -12
- package/src/BatchProcessing.res.mjs +2 -7
- package/src/ChainFetching.res +42 -6
- package/src/ChainFetching.res.mjs +30 -11
- package/src/ChainMetadata.res +1 -11
- package/src/ChainMetadata.res.mjs +1 -10
- package/src/ChainState.res +150 -1
- package/src/ChainState.res.mjs +138 -9
- package/src/ChainState.resi +51 -1
- package/src/Config.res +3 -6
- package/src/Config.res.mjs +2 -3
- package/src/Core.res +7 -0
- package/src/CrossChainState.res +24 -26
- package/src/CrossChainState.res.mjs +19 -16
- package/src/Ecosystem.res +6 -3
- package/src/Envio.res +3 -2
- package/src/EventConfigBuilder.res +61 -23
- package/src/EventConfigBuilder.res.mjs +27 -13
- package/src/EventProcessing.res +63 -21
- package/src/EventProcessing.res.mjs +47 -22
- package/src/FetchState.res +53 -43
- package/src/FetchState.res.mjs +71 -54
- package/src/HandlerLoader.res +10 -1
- package/src/HandlerLoader.res.mjs +15 -8
- package/src/Internal.res +46 -7
- package/src/Internal.res.mjs +19 -1
- package/src/LoadLayer.res +5 -5
- package/src/LoadLayer.res.mjs +6 -6
- package/src/Main.res +3 -50
- package/src/Main.res.mjs +2 -23
- package/src/PgStorage.res +4 -4
- package/src/PgStorage.res.mjs +5 -5
- package/src/Prometheus.res +10 -10
- package/src/Prometheus.res.mjs +9 -9
- package/src/PruneStaleHistory.res +2 -2
- package/src/PruneStaleHistory.res.mjs +3 -3
- package/src/Rollback.res +3 -3
- package/src/Rollback.res.mjs +4 -4
- package/src/SimulateItems.res +80 -15
- package/src/SimulateItems.res.mjs +65 -21
- package/src/TestIndexer.res +48 -36
- package/src/TestIndexer.res.mjs +35 -28
- package/src/bindings/Performance.res +7 -0
- package/src/bindings/Performance.res.mjs +21 -0
- package/src/bindings/Performance.resi +7 -0
- package/src/sources/EventRouter.res +17 -21
- package/src/sources/EventRouter.res.mjs +6 -9
- package/src/sources/Evm.res +47 -67
- package/src/sources/Evm.res.mjs +42 -65
- package/src/sources/Fuel.res +3 -3
- package/src/sources/Fuel.res.mjs +1 -6
- package/src/sources/HyperFuelSource.res +15 -11
- package/src/sources/HyperFuelSource.res.mjs +13 -11
- package/src/sources/HyperSync.res +7 -1
- package/src/sources/HyperSync.res.mjs +6 -3
- package/src/sources/HyperSync.resi +2 -0
- package/src/sources/HyperSyncClient.res +14 -79
- package/src/sources/HyperSyncClient.res.mjs +1 -22
- package/src/sources/HyperSyncSource.res +44 -27
- package/src/sources/HyperSyncSource.res.mjs +32 -27
- package/src/sources/RpcSource.res +14 -10
- package/src/sources/RpcSource.res.mjs +11 -9
- package/src/sources/SimulateSource.res +4 -2
- package/src/sources/SimulateSource.res.mjs +1 -0
- package/src/sources/Source.res +5 -1
- package/src/sources/SourceManager.res +17 -16
- package/src/sources/SourceManager.res.mjs +10 -15
- package/src/sources/Svm.res +23 -6
- package/src/sources/Svm.res.mjs +24 -6
- package/src/sources/SvmHyperSyncClient.res +3 -29
- package/src/sources/SvmHyperSyncSource.res +78 -96
- package/src/sources/SvmHyperSyncSource.res.mjs +79 -91
- package/src/sources/TransactionStore.res +128 -0
- package/src/sources/TransactionStore.res.mjs +97 -0
- package/src/tui/Tui.res +13 -16
- package/src/tui/Tui.res.mjs +12 -14
- package/svm.schema.json +30 -37
- package/src/bindings/Hrtime.res +0 -58
- package/src/bindings/Hrtime.res.mjs +0 -90
- package/src/bindings/Hrtime.resi +0 -30
|
@@ -15,7 +15,7 @@ type sourceState = {
|
|
|
15
15
|
// with a mutable state for easier reasoning and testing.
|
|
16
16
|
type t = {
|
|
17
17
|
sourcesState: array<sourceState>,
|
|
18
|
-
mutable statusStart:
|
|
18
|
+
mutable statusStart: Performance.timeRef,
|
|
19
19
|
mutable status: sourceManagerStatus,
|
|
20
20
|
newBlockStallTimeout: int,
|
|
21
21
|
newBlockStallTimeoutRealtime: int,
|
|
@@ -24,6 +24,10 @@ type t = {
|
|
|
24
24
|
getHeightRetryInterval: (~retry: int) => int,
|
|
25
25
|
mutable activeSource: Source.t,
|
|
26
26
|
mutable waitingForNewBlockStateId: option<int>,
|
|
27
|
+
// Dedupes the "waiting for new blocks" trace so it fires once per contiguous
|
|
28
|
+
// wait period instead of on every epoch that re-enters the wait before any
|
|
29
|
+
// new block is found. Reset when blocks are found.
|
|
30
|
+
mutable waitingLogged: bool,
|
|
27
31
|
// Should take into consideration partitions fetching for previous states (before rollback)
|
|
28
32
|
mutable fetchingPartitionsCount: int,
|
|
29
33
|
recoveryTimeout: float,
|
|
@@ -186,6 +190,7 @@ let make = (
|
|
|
186
190
|
}),
|
|
187
191
|
activeSource: initialActiveSource,
|
|
188
192
|
waitingForNewBlockStateId: None,
|
|
193
|
+
waitingLogged: false,
|
|
189
194
|
fetchingPartitionsCount: 0,
|
|
190
195
|
newBlockStallTimeout,
|
|
191
196
|
newBlockStallTimeoutRealtime,
|
|
@@ -193,7 +198,7 @@ let make = (
|
|
|
193
198
|
reducedPollingInterval,
|
|
194
199
|
getHeightRetryInterval,
|
|
195
200
|
recoveryTimeout,
|
|
196
|
-
statusStart:
|
|
201
|
+
statusStart: Performance.now(),
|
|
197
202
|
status: Idle,
|
|
198
203
|
hasRealtime,
|
|
199
204
|
committedRateLimitTimeMs: 0.0,
|
|
@@ -211,9 +216,9 @@ let trackNewStatus = (sourceManager: t, ~newStatus) => {
|
|
|
211
216
|
}
|
|
212
217
|
promCounter->Prometheus.SafeCounter.handleFloat(
|
|
213
218
|
~labels=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
|
|
214
|
-
~value=sourceManager.statusStart->
|
|
219
|
+
~value=sourceManager.statusStart->Performance.secondsSince,
|
|
215
220
|
)
|
|
216
|
-
sourceManager.statusStart =
|
|
221
|
+
sourceManager.statusStart = Performance.now()
|
|
217
222
|
sourceManager.status = newStatus
|
|
218
223
|
}
|
|
219
224
|
|
|
@@ -496,13 +501,14 @@ let waitForNewBlock = async (sourceManager: t, ~knownHeight, ~isRealtime, ~reduc
|
|
|
496
501
|
"knownHeight": knownHeight,
|
|
497
502
|
},
|
|
498
503
|
)
|
|
499
|
-
if
|
|
504
|
+
if !sourceManager.waitingLogged {
|
|
500
505
|
logger->Logging.childTrace(
|
|
501
|
-
|
|
502
|
-
|
|
506
|
+
reducedPolling
|
|
507
|
+
? `Waiting for new blocks with reduced polling (${(sourceManager.reducedPollingInterval / 1000)
|
|
508
|
+
->Int.toString}s). Chain is caught up, waiting for other chains to backfill.`
|
|
509
|
+
: "Initiating check for new blocks.",
|
|
503
510
|
)
|
|
504
|
-
|
|
505
|
-
logger->Logging.childTrace("Initiating check for new blocks.")
|
|
511
|
+
sourceManager.waitingLogged = true
|
|
506
512
|
}
|
|
507
513
|
|
|
508
514
|
let mainSources = sourceManager->getNextSources(~isRealtime)
|
|
@@ -600,6 +606,7 @@ let waitForNewBlock = async (sourceManager: t, ~knownHeight, ~isRealtime, ~reduc
|
|
|
600
606
|
"source": source.name,
|
|
601
607
|
"newBlockHeight": newBlockHeight,
|
|
602
608
|
})
|
|
609
|
+
sourceManager.waitingLogged = false
|
|
603
610
|
|
|
604
611
|
status := Done
|
|
605
612
|
|
|
@@ -669,19 +676,13 @@ let executeQuery = async (
|
|
|
669
676
|
~fromBlock=query.fromBlock,
|
|
670
677
|
~toBlock,
|
|
671
678
|
~addressesByContractName=query.addressesByContractName,
|
|
672
|
-
~
|
|
679
|
+
~contractNameByAddress=query.contractNameByAddress,
|
|
673
680
|
~partitionId=query.partitionId,
|
|
674
681
|
~knownHeight,
|
|
675
682
|
~selection=query.selection,
|
|
676
683
|
~retry,
|
|
677
684
|
~logger,
|
|
678
685
|
)
|
|
679
|
-
logger->Logging.childTrace({
|
|
680
|
-
"msg": "Fetched block range from server",
|
|
681
|
-
"toBlock": response.latestFetchedBlockNumber,
|
|
682
|
-
"numEvents": response.parsedQueueItems->Array.length,
|
|
683
|
-
"stats": response.stats,
|
|
684
|
-
})
|
|
685
686
|
sourceState.lastFailedAt = None
|
|
686
687
|
responseRef := Some(response)
|
|
687
688
|
} catch {
|
|
@@ -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";
|
|
@@ -139,7 +139,7 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
|
|
|
139
139
|
disabled: false,
|
|
140
140
|
lastFailedAt: undefined
|
|
141
141
|
})),
|
|
142
|
-
statusStart:
|
|
142
|
+
statusStart: Performance.now(),
|
|
143
143
|
status: "Idle",
|
|
144
144
|
newBlockStallTimeout: newBlockStallTimeout,
|
|
145
145
|
newBlockStallTimeoutRealtime: newBlockStallTimeoutRealtime,
|
|
@@ -148,6 +148,7 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
|
|
|
148
148
|
getHeightRetryInterval: getHeightRetryInterval,
|
|
149
149
|
activeSource: initialActiveSource,
|
|
150
150
|
waitingForNewBlockStateId: undefined,
|
|
151
|
+
waitingLogged: false,
|
|
151
152
|
fetchingPartitionsCount: 0,
|
|
152
153
|
recoveryTimeout: recoveryTimeout,
|
|
153
154
|
hasRealtime: hasRealtime,
|
|
@@ -172,8 +173,8 @@ function trackNewStatus(sourceManager, newStatus) {
|
|
|
172
173
|
promCounter = Prometheus.IndexingQueryTime.counter;
|
|
173
174
|
break;
|
|
174
175
|
}
|
|
175
|
-
Prometheus.SafeCounter.handleFloat(promCounter, sourceManager.activeSource.chain,
|
|
176
|
-
sourceManager.statusStart =
|
|
176
|
+
Prometheus.SafeCounter.handleFloat(promCounter, sourceManager.activeSource.chain, Performance.secondsSince(sourceManager.statusStart));
|
|
177
|
+
sourceManager.statusStart = Performance.now();
|
|
177
178
|
sourceManager.status = newStatus;
|
|
178
179
|
}
|
|
179
180
|
|
|
@@ -408,10 +409,9 @@ async function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPo
|
|
|
408
409
|
chainId: sourceManager.activeSource.chain,
|
|
409
410
|
knownHeight: knownHeight
|
|
410
411
|
});
|
|
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.");
|
|
412
|
+
if (!sourceManager.waitingLogged) {
|
|
413
|
+
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.");
|
|
414
|
+
sourceManager.waitingLogged = true;
|
|
415
415
|
}
|
|
416
416
|
let mainSources = getNextSources(sourceManager, isRealtime, undefined);
|
|
417
417
|
let status = {
|
|
@@ -453,6 +453,7 @@ async function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPo
|
|
|
453
453
|
source: source.name,
|
|
454
454
|
newBlockHeight: newBlockHeight
|
|
455
455
|
});
|
|
456
|
+
sourceManager.waitingLogged = false;
|
|
456
457
|
status.contents = "Done";
|
|
457
458
|
return newBlockHeight;
|
|
458
459
|
}
|
|
@@ -499,13 +500,7 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
|
499
500
|
retry: retry
|
|
500
501
|
});
|
|
501
502
|
try {
|
|
502
|
-
let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, query.
|
|
503
|
-
Logging.childTrace(logger$2, {
|
|
504
|
-
msg: "Fetched block range from server",
|
|
505
|
-
toBlock: response.latestFetchedBlockNumber,
|
|
506
|
-
numEvents: response.parsedQueueItems.length,
|
|
507
|
-
stats: response.stats
|
|
508
|
-
});
|
|
503
|
+
let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, query.contractNameByAddress, knownHeight, query.partitionId, query.selection, retry, logger$2);
|
|
509
504
|
sourceState.lastFailedAt = undefined;
|
|
510
505
|
responseRef = response;
|
|
511
506
|
} catch (raw_error) {
|
package/src/sources/Svm.res
CHANGED
|
@@ -4,10 +4,26 @@ let cleanUpRawEventFieldsInPlace: JSON.t => unit = %raw(`fields => {
|
|
|
4
4
|
delete fields.time
|
|
5
5
|
}`)
|
|
6
6
|
|
|
7
|
+
// Ordered transaction field names. The index of each is the field code shared
|
|
8
|
+
// with the Rust store (`SvmTxField`) — keep this order in sync.
|
|
9
|
+
let transactionFields = [
|
|
10
|
+
"transactionIndex",
|
|
11
|
+
"signatures",
|
|
12
|
+
"feePayer",
|
|
13
|
+
"success",
|
|
14
|
+
"err",
|
|
15
|
+
"fee",
|
|
16
|
+
"computeUnitsConsumed",
|
|
17
|
+
"accountKeys",
|
|
18
|
+
"recentBlockhash",
|
|
19
|
+
"version",
|
|
20
|
+
"tokenBalances",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
let transactionFieldMask = TransactionStore.makeMaskFn(transactionFields)
|
|
24
|
+
|
|
7
25
|
let make = (~logger: Pino.t): Ecosystem.t => {
|
|
8
26
|
name: Svm,
|
|
9
|
-
blockFields: ["slot"],
|
|
10
|
-
transactionFields: [],
|
|
11
27
|
blockNumberName: "height",
|
|
12
28
|
blockTimestampName: "time",
|
|
13
29
|
blockHashName: "hash",
|
|
@@ -20,7 +36,8 @@ let make = (~logger: Pino.t): Ecosystem.t => {
|
|
|
20
36
|
// parse. The schema is a no-op object that always surfaces `None`.
|
|
21
37
|
onEventBlockFilterSchema: S.object(_ => None),
|
|
22
38
|
logger,
|
|
23
|
-
|
|
39
|
+
transactionFieldMask,
|
|
40
|
+
toEvent: eventItem => eventItem.payload->(Utils.magic: Internal.eventPayload => Internal.event),
|
|
24
41
|
toEventLogger: eventItem => {
|
|
25
42
|
let instruction =
|
|
26
43
|
eventItem.payload->(Utils.magic: Internal.eventPayload => Envio.svmInstruction)
|
|
@@ -71,9 +88,9 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S
|
|
|
71
88
|
getBlockHashes: (~blockNumbers as _, ~logger as _) =>
|
|
72
89
|
JsError.throwWithMessage("Svm does not support getting block hashes"),
|
|
73
90
|
getHeightOrThrow: async () => {
|
|
74
|
-
let timerRef =
|
|
91
|
+
let timerRef = Performance.now()
|
|
75
92
|
let height = await GetFinalizedSlot.route->Rest.fetch((), ~client)
|
|
76
|
-
let seconds = timerRef->
|
|
93
|
+
let seconds = timerRef->Performance.secondsSince
|
|
77
94
|
Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getSlot")
|
|
78
95
|
Prometheus.SourceRequestCount.addSeconds(
|
|
79
96
|
~sourceName=name,
|
|
@@ -87,7 +104,7 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S
|
|
|
87
104
|
~fromBlock as _,
|
|
88
105
|
~toBlock as _,
|
|
89
106
|
~addressesByContractName as _,
|
|
90
|
-
~
|
|
107
|
+
~contractNameByAddress as _,
|
|
91
108
|
~knownHeight as _,
|
|
92
109
|
~partitionId as _,
|
|
93
110
|
~selection as _,
|
package/src/sources/Svm.res.mjs
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
import * as Rpc from "./Rpc.res.mjs";
|
|
4
4
|
import * as Rest from "../vendored/Rest.res.mjs";
|
|
5
5
|
import * as Utils from "../Utils.res.mjs";
|
|
6
|
-
import * as Hrtime from "../bindings/Hrtime.res.mjs";
|
|
7
6
|
import * as Logging from "../Logging.res.mjs";
|
|
8
7
|
import * as Prometheus from "../Prometheus.res.mjs";
|
|
8
|
+
import * as Performance from "../bindings/Performance.res.mjs";
|
|
9
9
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
10
10
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
11
|
+
import * as TransactionStore from "./TransactionStore.res.mjs";
|
|
11
12
|
|
|
12
13
|
let cleanUpRawEventFieldsInPlace = (fields => {
|
|
13
14
|
delete fields.hash
|
|
@@ -15,11 +16,25 @@ let cleanUpRawEventFieldsInPlace = (fields => {
|
|
|
15
16
|
delete fields.time
|
|
16
17
|
});
|
|
17
18
|
|
|
19
|
+
let transactionFields = [
|
|
20
|
+
"transactionIndex",
|
|
21
|
+
"signatures",
|
|
22
|
+
"feePayer",
|
|
23
|
+
"success",
|
|
24
|
+
"err",
|
|
25
|
+
"fee",
|
|
26
|
+
"computeUnitsConsumed",
|
|
27
|
+
"accountKeys",
|
|
28
|
+
"recentBlockhash",
|
|
29
|
+
"version",
|
|
30
|
+
"tokenBalances"
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
let transactionFieldMask = TransactionStore.makeMaskFn(transactionFields);
|
|
34
|
+
|
|
18
35
|
function make(logger) {
|
|
19
36
|
return {
|
|
20
37
|
name: "svm",
|
|
21
|
-
blockFields: ["slot"],
|
|
22
|
-
transactionFields: [],
|
|
23
38
|
blockNumberName: "height",
|
|
24
39
|
blockTimestampName: "time",
|
|
25
40
|
blockHashName: "hash",
|
|
@@ -29,6 +44,7 @@ function make(logger) {
|
|
|
29
44
|
onEventBlockFilterSchema: S$RescriptSchema.object(param => {}),
|
|
30
45
|
logger: logger,
|
|
31
46
|
toEvent: eventItem => eventItem.payload,
|
|
47
|
+
transactionFieldMask: transactionFieldMask,
|
|
32
48
|
toEventLogger: eventItem => {
|
|
33
49
|
let instruction = eventItem.payload;
|
|
34
50
|
return Logging.createChildFrom(logger, {
|
|
@@ -67,9 +83,9 @@ function makeRPCSource(chain, rpc, sourceForOpt) {
|
|
|
67
83
|
pollingInterval: 10000,
|
|
68
84
|
getBlockHashes: (param, param$1) => Stdlib_JsError.throwWithMessage("Svm does not support getting block hashes"),
|
|
69
85
|
getHeightOrThrow: async () => {
|
|
70
|
-
let timerRef =
|
|
86
|
+
let timerRef = Performance.now();
|
|
71
87
|
let height = await Rest.fetch(route, undefined, client);
|
|
72
|
-
let seconds =
|
|
88
|
+
let seconds = Performance.secondsSince(timerRef);
|
|
73
89
|
Prometheus.SourceRequestCount.increment(name, chain, "getSlot");
|
|
74
90
|
Prometheus.SourceRequestCount.addSeconds(name, chain, "getSlot", seconds);
|
|
75
91
|
return height;
|
|
@@ -80,8 +96,10 @@ function makeRPCSource(chain, rpc, sourceForOpt) {
|
|
|
80
96
|
|
|
81
97
|
export {
|
|
82
98
|
cleanUpRawEventFieldsInPlace,
|
|
99
|
+
transactionFields,
|
|
100
|
+
transactionFieldMask,
|
|
83
101
|
make,
|
|
84
102
|
GetFinalizedSlot,
|
|
85
103
|
makeRPCSource,
|
|
86
104
|
}
|
|
87
|
-
/*
|
|
105
|
+
/* transactionFieldMask Not a pure module */
|
|
@@ -147,22 +147,6 @@ module ResponseTypes = {
|
|
|
147
147
|
blockHeight?: int,
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
type transaction = {
|
|
151
|
-
slot: int,
|
|
152
|
-
transactionIndex: int,
|
|
153
|
-
signatures: array<string>,
|
|
154
|
-
feePayer?: string,
|
|
155
|
-
success?: bool,
|
|
156
|
-
err?: string,
|
|
157
|
-
fee?: int,
|
|
158
|
-
computeUnitsConsumed?: int,
|
|
159
|
-
accountKeys: array<string>,
|
|
160
|
-
recentBlockhash?: string,
|
|
161
|
-
version?: string,
|
|
162
|
-
loadedAddressesWritable: array<string>,
|
|
163
|
-
loadedAddressesReadonly: array<string>,
|
|
164
|
-
}
|
|
165
|
-
|
|
166
150
|
/// Borsh-decoded view attached by the Rust client. `argsJson`/`accountsJson`
|
|
167
151
|
/// are stringified to side-step napi-rs's lack of native JSON passthrough.
|
|
168
152
|
/** Solana instruction record.
|
|
@@ -204,22 +188,10 @@ module ResponseTypes = {
|
|
|
204
188
|
message?: string,
|
|
205
189
|
}
|
|
206
190
|
|
|
207
|
-
type tokenBalance = {
|
|
208
|
-
slot: int,
|
|
209
|
-
transactionIndex?: int,
|
|
210
|
-
account?: string,
|
|
211
|
-
mint?: string,
|
|
212
|
-
owner?: string,
|
|
213
|
-
preAmount?: string,
|
|
214
|
-
postAmount?: string,
|
|
215
|
-
}
|
|
216
|
-
|
|
217
191
|
type queryResponseData = {
|
|
218
192
|
blocks: array<block>,
|
|
219
|
-
transactions: array<transaction>,
|
|
220
193
|
instructions: array<instruction>,
|
|
221
194
|
logs: array<log>,
|
|
222
|
-
tokenBalances: array<tokenBalance>,
|
|
223
195
|
}
|
|
224
196
|
|
|
225
197
|
type queryResponse = {
|
|
@@ -234,7 +206,9 @@ type queryResponse = ResponseTypes.queryResponse
|
|
|
234
206
|
|
|
235
207
|
type t = {
|
|
236
208
|
getHeight: unit => promise<int>,
|
|
237
|
-
|
|
209
|
+
// Returns the response plus a page of raw transactions (kept in Rust),
|
|
210
|
+
// keyed by (slot, transactionIndex), materialised at batch prep.
|
|
211
|
+
get: (~query: query) => promise<(queryResponse, TransactionStore.t)>,
|
|
238
212
|
}
|
|
239
213
|
|
|
240
214
|
@send
|
|
@@ -188,7 +188,6 @@ let toSvmInstruction = (
|
|
|
188
188
|
instr: SvmHyperSyncClient.ResponseTypes.instruction,
|
|
189
189
|
~programName,
|
|
190
190
|
~instructionName,
|
|
191
|
-
~transaction,
|
|
192
191
|
~logs,
|
|
193
192
|
~block,
|
|
194
193
|
): Envio.svmInstruction => {
|
|
@@ -204,35 +203,10 @@ let toSvmInstruction = (
|
|
|
204
203
|
d4: ?instr.d4,
|
|
205
204
|
d8: ?instr.d8,
|
|
206
205
|
params: ?(instr.decoded->Option.map(parseDecoded)),
|
|
207
|
-
?transaction,
|
|
208
206
|
?logs,
|
|
209
207
|
block,
|
|
210
208
|
}
|
|
211
209
|
|
|
212
|
-
let toSvmTransaction = (tx: SvmHyperSyncClient.ResponseTypes.transaction): Envio.svmTransaction => {
|
|
213
|
-
signatures: tx.signatures,
|
|
214
|
-
accountKeys: tx.accountKeys->SvmTypes.Pubkey.fromStringsUnsafe,
|
|
215
|
-
feePayer: ?(tx.feePayer->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
216
|
-
success: ?tx.success,
|
|
217
|
-
err: ?tx.err,
|
|
218
|
-
// u64 lamports / compute units arrive as `int` over napi. Convert to
|
|
219
|
-
// `bigint` so the public type stays defensible even for pathological values.
|
|
220
|
-
fee: ?(tx.fee->Option.map(BigInt.fromInt)),
|
|
221
|
-
computeUnitsConsumed: ?(tx.computeUnitsConsumed->Option.map(BigInt.fromInt)),
|
|
222
|
-
recentBlockhash: ?tx.recentBlockhash,
|
|
223
|
-
version: ?tx.version,
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
let toSvmTokenBalance = (
|
|
227
|
-
tb: SvmHyperSyncClient.ResponseTypes.tokenBalance,
|
|
228
|
-
): Envio.svmTokenBalance => {
|
|
229
|
-
account: ?(tb.account->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
230
|
-
mint: ?(tb.mint->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
231
|
-
owner: ?(tb.owner->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
232
|
-
preAmount: ?tb.preAmount,
|
|
233
|
-
postAmount: ?tb.postAmount,
|
|
234
|
-
}
|
|
235
|
-
|
|
236
210
|
// Probe the discriminator byte-length ordering longest-first. Stops at the
|
|
237
211
|
// first router hit. Falls back to the `_none` key (program-wide handler) when
|
|
238
212
|
// no discriminator-keyed handler matches.
|
|
@@ -242,11 +216,11 @@ let probeRouter = (
|
|
|
242
216
|
instr: SvmHyperSyncClient.ResponseTypes.instruction,
|
|
243
217
|
byteLengthsDesc: array<int>,
|
|
244
218
|
~contractAddress,
|
|
245
|
-
~
|
|
219
|
+
~contractNameByAddress,
|
|
246
220
|
) => {
|
|
247
221
|
let probe = (dN: option<string>) => {
|
|
248
222
|
let tag = EventRouter.getSvmEventId(~programId, ~discriminator=dN)
|
|
249
|
-
router->EventRouter.get(~tag, ~contractAddress, ~
|
|
223
|
+
router->EventRouter.get(~tag, ~contractAddress, ~contractNameByAddress)
|
|
250
224
|
}
|
|
251
225
|
|
|
252
226
|
let result = byteLengthsDesc->Array.reduce(None, (acc, len) =>
|
|
@@ -273,6 +247,27 @@ let probeRouter = (
|
|
|
273
247
|
}
|
|
274
248
|
}
|
|
275
249
|
|
|
250
|
+
// Map a selected transaction field to the extra query-side column it needs.
|
|
251
|
+
// `transactionIndex` is always fetched as the store key, and `tokenBalances`
|
|
252
|
+
// lives in a separate table (requested via `needsTokenBalances`), so neither
|
|
253
|
+
// adds a transaction column here.
|
|
254
|
+
let toQueryTxField = (field: Internal.svmTransactionField): option<
|
|
255
|
+
SvmHyperSyncClient.QueryTypes.transactionField,
|
|
256
|
+
> =>
|
|
257
|
+
switch field {
|
|
258
|
+
| TransactionIndex => None
|
|
259
|
+
| Signatures => Some(Signatures)
|
|
260
|
+
| FeePayer => Some(FeePayer)
|
|
261
|
+
| Success => Some(Success)
|
|
262
|
+
| Err => Some(Err)
|
|
263
|
+
| Fee => Some(Fee)
|
|
264
|
+
| ComputeUnitsConsumed => Some(ComputeUnitsConsumed)
|
|
265
|
+
| AccountKeys => Some(AccountKeys)
|
|
266
|
+
| RecentBlockhash => Some(RecentBlockhash)
|
|
267
|
+
| Version => Some(Version)
|
|
268
|
+
| TokenBalances => None
|
|
269
|
+
}
|
|
270
|
+
|
|
276
271
|
let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: options): t => {
|
|
277
272
|
let name = "SvmHyperSync"
|
|
278
273
|
let chainId = chain->ChainMap.Chain.toChainId
|
|
@@ -298,23 +293,58 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
298
293
|
orderingByProgram->Dict.set(o.programId->SvmTypes.Pubkey.toString, o.byteLengthsDesc)
|
|
299
294
|
)
|
|
300
295
|
|
|
301
|
-
let needsTransactions = eventConfigs->Array.some(cfg => cfg.includeTransaction)
|
|
302
296
|
let needsLogs = eventConfigs->Array.some(cfg => cfg.includeLogs)
|
|
303
|
-
|
|
297
|
+
|
|
298
|
+
// Union of selected transaction fields across the chain's events. Drives both
|
|
299
|
+
// the query column selection (fetch only what's used) and the materialisation
|
|
300
|
+
// mask. `slot`/`transactionIndex` are always fetched as the store key.
|
|
301
|
+
let selectedTxFields = Utils.Set.make()
|
|
302
|
+
eventConfigs->Array.forEach(cfg =>
|
|
303
|
+
cfg.selectedTransactionFields
|
|
304
|
+
->(Utils.magic: Utils.Set.t<string> => Utils.Set.t<Internal.svmTransactionField>)
|
|
305
|
+
->Utils.Set.forEach(field => selectedTxFields->Utils.Set.add(field)->ignore)
|
|
306
|
+
)
|
|
307
|
+
let needsTokenBalances = selectedTxFields->Utils.Set.has(Internal.TokenBalances)
|
|
308
|
+
let txQueryFields = {
|
|
309
|
+
// Slot + TransactionIndex are always fetched so the store can be keyed by
|
|
310
|
+
// (slot, transactionIndex).
|
|
311
|
+
let fields: array<SvmHyperSyncClient.QueryTypes.transactionField> = [Slot, TransactionIndex]
|
|
312
|
+
selectedTxFields
|
|
313
|
+
->Utils.Set.toArray
|
|
314
|
+
->Array.forEach(field =>
|
|
315
|
+
switch toQueryTxField(field) {
|
|
316
|
+
| Some(queryField) => fields->Array.push(queryField)
|
|
317
|
+
| None => ()
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
fields
|
|
321
|
+
}
|
|
322
|
+
// The transaction table is fetched only when a selected field is actually read
|
|
323
|
+
// off a stored transaction record. `transactionIndex` materialises from the
|
|
324
|
+
// store key and `tokenBalances` lives in its own table, so neither requires it.
|
|
325
|
+
let needsTransactions =
|
|
326
|
+
selectedTxFields
|
|
327
|
+
->Utils.Set.toArray
|
|
328
|
+
->Array.some(field =>
|
|
329
|
+
switch field {
|
|
330
|
+
| Internal.TransactionIndex | Internal.TokenBalances => false
|
|
331
|
+
| _ => true
|
|
332
|
+
}
|
|
333
|
+
)
|
|
304
334
|
|
|
305
335
|
let getItemsOrThrow = async (
|
|
306
336
|
~fromBlock,
|
|
307
337
|
~toBlock,
|
|
308
338
|
~addressesByContractName as _,
|
|
309
|
-
~
|
|
339
|
+
~contractNameByAddress,
|
|
310
340
|
~knownHeight,
|
|
311
341
|
~partitionId as _,
|
|
312
342
|
~selection as _,
|
|
313
343
|
~retry,
|
|
314
344
|
~logger,
|
|
315
345
|
) => {
|
|
316
|
-
let totalTimeRef =
|
|
317
|
-
let pageFetchRef =
|
|
346
|
+
let totalTimeRef = Performance.now()
|
|
347
|
+
let pageFetchRef = Performance.now()
|
|
318
348
|
|
|
319
349
|
let instructionSelections = buildInstructionSelections(eventConfigs)
|
|
320
350
|
// Under the server's default merge mode, requesting a table's columns is
|
|
@@ -323,23 +353,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
323
353
|
// opted-into table needs its columns spelled out here.
|
|
324
354
|
let fields: SvmHyperSyncClient.QueryTypes.fieldSelection = {
|
|
325
355
|
block: [Slot, Blockhash, BlockTime],
|
|
326
|
-
transaction: ?(
|
|
327
|
-
needsTransactions
|
|
328
|
-
? Some([
|
|
329
|
-
Slot,
|
|
330
|
-
TransactionIndex,
|
|
331
|
-
Signatures,
|
|
332
|
-
FeePayer,
|
|
333
|
-
Success,
|
|
334
|
-
Err,
|
|
335
|
-
Fee,
|
|
336
|
-
ComputeUnitsConsumed,
|
|
337
|
-
AccountKeys,
|
|
338
|
-
RecentBlockhash,
|
|
339
|
-
Version,
|
|
340
|
-
])
|
|
341
|
-
: None
|
|
342
|
-
),
|
|
356
|
+
transaction: ?(needsTransactions ? Some(txQueryFields) : None),
|
|
343
357
|
log: ?(needsLogs ? Some([Slot, TransactionIndex, InstructionAddress, Kind, Message]) : None),
|
|
344
358
|
tokenBalance: ?(
|
|
345
359
|
needsTokenBalances
|
|
@@ -358,7 +372,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
358
372
|
|
|
359
373
|
Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getInstructions")
|
|
360
374
|
|
|
361
|
-
let resp = try await client.get(~query) catch {
|
|
375
|
+
let (resp, transactionStore) = try await client.get(~query) catch {
|
|
362
376
|
| exn =>
|
|
363
377
|
throw(
|
|
364
378
|
Source.GetItemsError(
|
|
@@ -376,9 +390,9 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
376
390
|
),
|
|
377
391
|
)
|
|
378
392
|
}
|
|
379
|
-
let pageFetchTime = pageFetchRef->
|
|
393
|
+
let pageFetchTime = pageFetchRef->Performance.secondsSince
|
|
380
394
|
|
|
381
|
-
let parsingRef =
|
|
395
|
+
let parsingRef = Performance.now()
|
|
382
396
|
|
|
383
397
|
// Per-slot unix timestamp lookup from the response's `blocks` table. Slots
|
|
384
398
|
// without a block row (rare; usually skipped slots) fall back to `None`.
|
|
@@ -390,13 +404,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
390
404
|
}
|
|
391
405
|
})
|
|
392
406
|
|
|
393
|
-
// Per (slot, transaction_index) lookup for parent transactions.
|
|
394
|
-
let txByKey = Dict.make()
|
|
395
|
-
resp.data.transactions->Array.forEach(tx => {
|
|
396
|
-
let key = tx.slot->Int.toString ++ ":" ++ tx.transactionIndex->Int.toString
|
|
397
|
-
txByKey->Dict.set(key, tx)
|
|
398
|
-
})
|
|
399
|
-
|
|
400
407
|
// Per (slot, transaction_index, instruction_address) lookup for logs
|
|
401
408
|
// scoped to a single instruction. `instructionAddress: None` logs are
|
|
402
409
|
// attached to no instruction (rare; usually only system messages).
|
|
@@ -418,21 +425,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
418
425
|
}
|
|
419
426
|
})
|
|
420
427
|
|
|
421
|
-
let tokenBalancesByTx = Dict.make()
|
|
422
|
-
if needsTokenBalances {
|
|
423
|
-
resp.data.tokenBalances->Array.forEach(tb => {
|
|
424
|
-
switch tb.transactionIndex {
|
|
425
|
-
| Some(txIdx) =>
|
|
426
|
-
let key = tb.slot->Int.toString ++ ":" ++ txIdx->Int.toString
|
|
427
|
-
switch tokenBalancesByTx->Dict.get(key) {
|
|
428
|
-
| Some(existing) => existing->Array.push(tb)
|
|
429
|
-
| None => tokenBalancesByTx->Dict.set(key, [tb])
|
|
430
|
-
}
|
|
431
|
-
| None => ()
|
|
432
|
-
}
|
|
433
|
-
})
|
|
434
|
-
}
|
|
435
|
-
|
|
436
428
|
let parsedQueueItems = []
|
|
437
429
|
resp.data.instructions->Array.forEach(instr => {
|
|
438
430
|
let programId = instr.programId->SvmTypes.Pubkey.fromStringUnsafe
|
|
@@ -448,26 +440,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
448
440
|
instr,
|
|
449
441
|
byteLengths,
|
|
450
442
|
~contractAddress,
|
|
451
|
-
~
|
|
443
|
+
~contractNameByAddress,
|
|
452
444
|
)
|
|
453
445
|
|
|
454
446
|
switch maybeConfig {
|
|
455
447
|
| None => ()
|
|
456
448
|
| Some(eventConfig) =>
|
|
457
|
-
let txKey = instr.slot->Int.toString ++ ":" ++ instr.transactionIndex->Int.toString
|
|
458
|
-
let maybeTx =
|
|
459
|
-
txByKey->Utils.Dict.dangerouslyGetNonOption(txKey)->Option.map(toSvmTransaction)
|
|
460
|
-
let maybeTx = if eventConfig.includeTokenBalances {
|
|
461
|
-
maybeTx->Option.map(tx => {
|
|
462
|
-
let maybeBalances =
|
|
463
|
-
tokenBalancesByTx
|
|
464
|
-
->Utils.Dict.dangerouslyGetNonOption(txKey)
|
|
465
|
-
->Option.map(bals => bals->Array.map(toSvmTokenBalance))
|
|
466
|
-
{...tx, tokenBalances: ?maybeBalances}
|
|
467
|
-
})
|
|
468
|
-
} else {
|
|
469
|
-
maybeTx
|
|
470
|
-
}
|
|
471
449
|
let logKey =
|
|
472
450
|
instr.slot->Int.toString ++
|
|
473
451
|
":" ++
|
|
@@ -492,7 +470,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
492
470
|
instr,
|
|
493
471
|
~programName=eventConfig.contractName,
|
|
494
472
|
~instructionName=eventConfig.name,
|
|
495
|
-
~transaction=eventConfig.includeTransaction ? maybeTx : None,
|
|
496
473
|
~logs=eventConfig.includeLogs ? maybeLogs : None,
|
|
497
474
|
~block={
|
|
498
475
|
slot: instr.slot,
|
|
@@ -510,6 +487,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
510
487
|
blockNumber: instr.slot,
|
|
511
488
|
blockHash: "",
|
|
512
489
|
logIndex: synthLogIndex(instr),
|
|
490
|
+
// The parent transaction is materialised from the store at batch prep.
|
|
491
|
+
transactionIndex: instr.transactionIndex,
|
|
513
492
|
payload: payload->(Utils.magic: Envio.svmInstruction => Internal.eventPayload),
|
|
514
493
|
}),
|
|
515
494
|
)
|
|
@@ -519,7 +498,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
519
498
|
let _ = logger
|
|
520
499
|
})
|
|
521
500
|
|
|
522
|
-
let parsingTimeElapsed = parsingRef->
|
|
501
|
+
let parsingTimeElapsed = parsingRef->Performance.secondsSince
|
|
523
502
|
let highestSlot = resp.nextSlot - 1
|
|
524
503
|
let latestBlockTime =
|
|
525
504
|
blockTimeBySlot
|
|
@@ -534,11 +513,13 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
534
513
|
blockHash: b.blockhash,
|
|
535
514
|
})
|
|
536
515
|
|
|
537
|
-
let totalTimeElapsed = totalTimeRef->
|
|
516
|
+
let totalTimeElapsed = totalTimeRef->Performance.secondsSince
|
|
538
517
|
|
|
539
518
|
{
|
|
540
519
|
latestFetchedBlockTimestamp: latestBlockTime,
|
|
541
520
|
parsedQueueItems,
|
|
521
|
+
// Raw transactions kept in Rust; materialised (selected fields) at batch prep.
|
|
522
|
+
transactionStore: Some(transactionStore),
|
|
542
523
|
latestFetchedBlockNumber: highestSlot,
|
|
543
524
|
stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
|
|
544
525
|
knownHeight,
|
|
@@ -563,7 +544,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
563
544
|
maxNumBlocks: 1000,
|
|
564
545
|
}
|
|
565
546
|
Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getBlockHashes")
|
|
566
|
-
|
|
547
|
+
// Block-only query; the store page is empty.
|
|
548
|
+
let (resp, _) = await client.get(~query)
|
|
567
549
|
resp.data.blocks->Array.forEach(b =>
|
|
568
550
|
blockDatas
|
|
569
551
|
->Array.push({
|
|
@@ -621,9 +603,9 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
621
603
|
poweredByHyperSync: true,
|
|
622
604
|
getBlockHashes,
|
|
623
605
|
getHeightOrThrow: async () => {
|
|
624
|
-
let timer =
|
|
606
|
+
let timer = Performance.now()
|
|
625
607
|
let h = await client.getHeight()
|
|
626
|
-
let seconds = timer->
|
|
608
|
+
let seconds = timer->Performance.secondsSince
|
|
627
609
|
Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getHeight")
|
|
628
610
|
Prometheus.SourceRequestCount.addSeconds(
|
|
629
611
|
~sourceName=name,
|