envio 3.6.1 → 3.7.0
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 +197 -9
- package/package.json +6 -6
- package/src/Batch.res +64 -25
- package/src/Batch.res.mjs +69 -46
- package/src/ChainFetching.res +13 -17
- package/src/ChainFetching.res.mjs +9 -14
- package/src/ChainState.res +160 -119
- package/src/ChainState.res.mjs +75 -46
- package/src/ChainState.resi +15 -12
- package/src/Config.res +9 -2
- package/src/Config.res.mjs +4 -4
- package/src/Core.res +5 -0
- package/src/Ecosystem.res +0 -1
- package/src/Envio.res +16 -3
- package/src/EventConfigBuilder.res +155 -40
- package/src/EventConfigBuilder.res.mjs +76 -24
- package/src/EventProcessing.res +4 -8
- package/src/EventProcessing.res.mjs +4 -4
- package/src/EventUtils.res +3 -47
- package/src/FetchState.res +124 -104
- package/src/FetchState.res.mjs +116 -100
- package/src/HandlerRegister.res +108 -2
- package/src/HandlerRegister.res.mjs +59 -10
- package/src/HandlerRegister.resi +4 -0
- package/src/Internal.res +84 -24
- package/src/Internal.res.mjs +33 -2
- package/src/LazyLoader.res +12 -11
- package/src/LazyLoader.res.mjs +13 -7
- package/src/Main.res +22 -9
- package/src/Main.res.mjs +18 -5
- package/src/MemoryStorage.res +724 -0
- package/src/MemoryStorage.res.mjs +601 -0
- package/src/PgStorage.res +18 -7
- package/src/PgStorage.res.mjs +14 -11
- package/src/ReorgDetection.res +0 -222
- package/src/ReorgDetection.res.mjs +0 -163
- package/src/Rollback.res +14 -15
- package/src/Rollback.res.mjs +4 -5
- package/src/SimulateItems.res +2 -2
- package/src/Utils.res +3 -0
- package/src/bindings/ClickHouse.res +16 -11
- package/src/bindings/ClickHouse.res.mjs +10 -10
- package/src/bindings/Vitest.res +1 -1
- package/src/sources/BlockStore.res +115 -5
- package/src/sources/BlockStore.res.mjs +25 -0
- package/src/sources/Evm.res +0 -1
- package/src/sources/Evm.res.mjs +0 -1
- package/src/sources/EvmHyperSyncSource.res +19 -84
- package/src/sources/EvmHyperSyncSource.res.mjs +24 -60
- package/src/sources/Fuel.res +24 -4
- package/src/sources/Fuel.res.mjs +23 -3
- package/src/sources/FuelHyperSync.res +8 -3
- package/src/sources/FuelHyperSync.res.mjs +5 -4
- package/src/sources/FuelHyperSync.resi +3 -1
- package/src/sources/FuelHyperSyncClient.res +7 -10
- package/src/sources/FuelHyperSyncSource.res +18 -45
- package/src/sources/FuelHyperSyncSource.res.mjs +18 -35
- package/src/sources/HyperSync.res +49 -229
- package/src/sources/HyperSync.res.mjs +74 -191
- package/src/sources/HyperSync.resi +11 -35
- package/src/sources/HyperSyncClient.res +9 -79
- package/src/sources/HyperSyncClient.res.mjs +2 -6
- package/src/sources/RequestStat.res +5 -0
- package/src/sources/RequestStat.res.mjs +2 -0
- package/src/sources/RpcSource.res +149 -43
- package/src/sources/RpcSource.res.mjs +104 -41
- package/src/sources/SimulateSource.res +8 -5
- package/src/sources/SimulateSource.res.mjs +6 -6
- package/src/sources/Source.res +89 -16
- package/src/sources/Source.res.mjs +50 -1
- package/src/sources/SourceManager.res +255 -50
- package/src/sources/SourceManager.res.mjs +149 -55
- package/src/sources/SourceManager.resi +2 -6
- package/src/sources/Svm.res +0 -7
- package/src/sources/Svm.res.mjs +0 -8
- package/src/sources/SvmHyperSyncClient.res +21 -16
- package/src/sources/SvmHyperSyncClient.res.mjs +3 -4
- package/src/sources/SvmHyperSyncSource.res +25 -117
- package/src/sources/SvmHyperSyncSource.res.mjs +8 -121
- package/svm.schema.json +3 -2
package/src/HandlerRegister.res
CHANGED
|
@@ -82,6 +82,36 @@ let startRegistration = (~config: Config.t) => {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// A registration a caller owns, rather than the single implicit one
|
|
86
|
+
// `startRegistration` installs. Tests run several indexers in one process, each
|
|
87
|
+
// with its own config, and handlers must register against the config they were
|
|
88
|
+
// written for.
|
|
89
|
+
type registration = activeRegistration
|
|
90
|
+
|
|
91
|
+
let makeRegistration = (~config: Config.t): registration => {
|
|
92
|
+
config,
|
|
93
|
+
registrationsByChainId: Dict.make(),
|
|
94
|
+
finished: false,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Makes `registration` the target of `indexer.onEvent` & co. for the duration
|
|
98
|
+
// of `fn`, restoring whatever was active before — so one caller's handlers
|
|
99
|
+
// never land in another's registration. Concurrent scopes would still clobber
|
|
100
|
+
// each other: the pointer they swap is process-global.
|
|
101
|
+
let useRegistration = async (registration: registration, fn) => {
|
|
102
|
+
let previous = EnvioGlobal.value.activeRegistration
|
|
103
|
+
EnvioGlobal.value.activeRegistration = Some(registration->(Utils.magic: registration => unknown))
|
|
104
|
+
let restore = () => EnvioGlobal.value.activeRegistration = previous
|
|
105
|
+
switch await fn() {
|
|
106
|
+
| result =>
|
|
107
|
+
restore()
|
|
108
|
+
result
|
|
109
|
+
| exception exn =>
|
|
110
|
+
restore()
|
|
111
|
+
throw(exn)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
let getChainRegistrations = (r: activeRegistration, ~chainId: ChainId.t): chainRegistrations => {
|
|
86
116
|
let key = chainId->ChainId.toString
|
|
87
117
|
switch r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(key) {
|
|
@@ -104,6 +134,7 @@ let buildOnEventRegistrationWith = (
|
|
|
104
134
|
~handler: option<Internal.handler>,
|
|
105
135
|
~contractRegister: option<Internal.contractRegister>,
|
|
106
136
|
~where: option<JSON.t>,
|
|
137
|
+
~fieldSelection: option<Internal.fieldSelection>,
|
|
107
138
|
~startBlock=?,
|
|
108
139
|
): Internal.onEventRegistration => {
|
|
109
140
|
switch config.ecosystem.name {
|
|
@@ -134,6 +165,7 @@ let buildOnEventRegistrationWith = (
|
|
|
134
165
|
~where,
|
|
135
166
|
~chainId,
|
|
136
167
|
~onEventBlockFilterSchema=config.ecosystem.onEventBlockFilterSchema,
|
|
168
|
+
~fieldSelection?,
|
|
137
169
|
~startBlock?,
|
|
138
170
|
) :> Internal.onEventRegistration)
|
|
139
171
|
}
|
|
@@ -163,6 +195,19 @@ let sameEventAndFilter = (
|
|
|
163
195
|
| Fuel | Svm => true
|
|
164
196
|
}
|
|
165
197
|
|
|
198
|
+
// The merged registration keeps `base`'s handler and slot, picks up the
|
|
199
|
+
// contractRegister from `other`, and carries the union of what both callbacks
|
|
200
|
+
// read. Relies on `onEventRegistration` having an optional field so the spread
|
|
201
|
+
// stays a runtime spread and keeps `other`'s ecosystem-only fields.
|
|
202
|
+
let mergeInto = (
|
|
203
|
+
base: Internal.onEventRegistration,
|
|
204
|
+
~other: Internal.onEventRegistration,
|
|
205
|
+
): Internal.onEventRegistration => {
|
|
206
|
+
...base,
|
|
207
|
+
contractRegister: other.contractRegister,
|
|
208
|
+
fieldSelection: Internal.unionFieldSelection(base.fieldSelection, other.fieldSelection),
|
|
209
|
+
}
|
|
210
|
+
|
|
166
211
|
// Merge each contractRegister into a matching handler registration (either
|
|
167
212
|
// registration order; the merged registration takes the handler's slot so
|
|
168
213
|
// dispatch order follows handler registration order). Two handlers (or two
|
|
@@ -188,7 +233,7 @@ let mergeRegistrations = (resolved: array<Internal.onEventRegistration>, ~config
|
|
|
188
233
|
merged :=
|
|
189
234
|
merged.contents
|
|
190
235
|
->Array.filterWithIndex((_, j) => j !== i)
|
|
191
|
-
->Array.concat([
|
|
236
|
+
->Array.concat([reg->mergeInto(~other=target)])
|
|
192
237
|
}
|
|
193
238
|
} else {
|
|
194
239
|
// A contractRegister merges into a matching handler registration,
|
|
@@ -202,7 +247,7 @@ let mergeRegistrations = (resolved: array<Internal.onEventRegistration>, ~config
|
|
|
202
247
|
| i =>
|
|
203
248
|
let target = merged.contents->Array.getUnsafe(i)
|
|
204
249
|
let next = merged.contents->Array.copy
|
|
205
|
-
next->Array.setUnsafe(i,
|
|
250
|
+
next->Array.setUnsafe(i, target->mergeInto(~other=reg))
|
|
206
251
|
merged := next
|
|
207
252
|
}
|
|
208
253
|
}
|
|
@@ -249,6 +294,27 @@ let addOnEventRegistration = (
|
|
|
249
294
|
) => {
|
|
250
295
|
let isWildcard = eventOptions->Option.flatMap(v => v.wildcard)->Option.getOr(false)
|
|
251
296
|
let where = eventOptions->Option.flatMap(v => v.where)
|
|
297
|
+
// The inline selection doesn't vary by chain: resolve it once here and share
|
|
298
|
+
// the one value (and its sets) across every chain's registration.
|
|
299
|
+
let fieldSelection = switch eventOptions->Option.flatMap(v => v.fields) {
|
|
300
|
+
| None => None
|
|
301
|
+
| Some(fields) =>
|
|
302
|
+
// Only EVM resolves a registration selection of its own; Fuel and SVM take
|
|
303
|
+
// theirs from the event config, so an inline one would be silently dropped.
|
|
304
|
+
if registration.config.ecosystem.name !== Evm {
|
|
305
|
+
JsError.throwWithMessage(
|
|
306
|
+
`The fields option of the "${eventName}" event registration on contract "${contractName}" is only supported on EVM. Select the fields in your config instead.`,
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
Some(
|
|
310
|
+
EventConfigBuilder.resolveInlineFieldSelection(
|
|
311
|
+
fields,
|
|
312
|
+
~contractName,
|
|
313
|
+
~eventName,
|
|
314
|
+
~enableRawEvents=registration.config.enableRawEvents,
|
|
315
|
+
),
|
|
316
|
+
)
|
|
317
|
+
}
|
|
252
318
|
let matched = ref(false)
|
|
253
319
|
registration.config.chainMap
|
|
254
320
|
->ChainMap.values
|
|
@@ -268,6 +334,7 @@ let addOnEventRegistration = (
|
|
|
268
334
|
~handler,
|
|
269
335
|
~contractRegister,
|
|
270
336
|
~where,
|
|
337
|
+
~fieldSelection,
|
|
271
338
|
~startBlock=?contract.startBlock,
|
|
272
339
|
)
|
|
273
340
|
(registration->getChainRegistrations(~chainId=chainConfig.id)).onEventRegistrations
|
|
@@ -397,11 +464,47 @@ let getSimulateOnEventRegistrations = (
|
|
|
397
464
|
~handler=None,
|
|
398
465
|
~contractRegister=None,
|
|
399
466
|
~where=None,
|
|
467
|
+
~fieldSelection=None,
|
|
400
468
|
),
|
|
401
469
|
]
|
|
402
470
|
}
|
|
403
471
|
}
|
|
404
472
|
|
|
473
|
+
// An RPC source can only deliver the fields it knows how to parse; the rest are
|
|
474
|
+
// silently skipped at materialisation. Every RPC on the chain counts, whatever
|
|
475
|
+
// it's for — a fallback or realtime source runs the same parsers as a sync one,
|
|
476
|
+
// so a field it can't deliver would go missing for whichever blocks it served.
|
|
477
|
+
// The selection can come from either `config.yaml` or the inline `fields`
|
|
478
|
+
// option, so the message names neither. Runs on the registrations a chain
|
|
479
|
+
// actually keeps, so a handler whose `where` opts out of this chain isn't held
|
|
480
|
+
// to its limits.
|
|
481
|
+
//
|
|
482
|
+
// The `config.yaml` half of this is also rejected at codegen, by the
|
|
483
|
+
// `RpcTransactionField` subenum in `system_config.rs`. That one reports every
|
|
484
|
+
// offending field at once and before the project builds; this one is the only
|
|
485
|
+
// check an inline selection reaches. `RpcFieldSelection_test.res` pins the two
|
|
486
|
+
// to the same field set.
|
|
487
|
+
let validateRpcFieldSelection = (
|
|
488
|
+
chainConfig: Config.chain,
|
|
489
|
+
registrations: array<Internal.onEventRegistration>,
|
|
490
|
+
) => {
|
|
491
|
+
let hasRpc = switch chainConfig.sourceConfig {
|
|
492
|
+
| EvmSourceConfig({rpcs}) => !(rpcs->Utils.Array.isEmpty)
|
|
493
|
+
| _ => false
|
|
494
|
+
}
|
|
495
|
+
if hasRpc {
|
|
496
|
+
registrations->Array.forEach(reg =>
|
|
497
|
+
reg.fieldSelection.transactionFields->Utils.Set.forEach(name =>
|
|
498
|
+
if !RpcSource.isRpcTransactionField(name) {
|
|
499
|
+
JsError.throwWithMessage(
|
|
500
|
+
`The "${name}" transaction field selected for the "${reg.eventConfig.name}" event on contract "${reg.eventConfig.contractName}" is unavailable for indexing via RPC. Remove it from the field selection, or remove chain ${chainConfig.id->ChainId.toString}'s RPC source — even an RPC the chain only falls back to has to deliver the selection.`,
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
405
508
|
let finishRegistration = (~config: Config.t): registrationsByChainId => {
|
|
406
509
|
switch getActiveRegistration() {
|
|
407
510
|
| Some(r) => {
|
|
@@ -452,6 +555,7 @@ let finishRegistration = (~config: Config.t): registrationsByChainId => {
|
|
|
452
555
|
~handler=None,
|
|
453
556
|
~contractRegister=None,
|
|
454
557
|
~where=None,
|
|
558
|
+
~fieldSelection=None,
|
|
455
559
|
~startBlock=?contract.startBlock,
|
|
456
560
|
),
|
|
457
561
|
)
|
|
@@ -487,6 +591,8 @@ let finishRegistration = (~config: Config.t): registrationsByChainId => {
|
|
|
487
591
|
}
|
|
488
592
|
})
|
|
489
593
|
|
|
594
|
+
validateRpcFieldSelection(chainConfig, onEventRegistrations)
|
|
595
|
+
|
|
490
596
|
registrationsByChainId->Dict.set(
|
|
491
597
|
key,
|
|
492
598
|
{
|
|
@@ -4,6 +4,8 @@ import * as Utils from "./Utils.res.mjs";
|
|
|
4
4
|
import * as ChainId from "./ChainId.res.mjs";
|
|
5
5
|
import * as Logging from "./Logging.res.mjs";
|
|
6
6
|
import * as ChainMap from "./ChainMap.res.mjs";
|
|
7
|
+
import * as Internal from "./Internal.res.mjs";
|
|
8
|
+
import * as RpcSource from "./sources/RpcSource.res.mjs";
|
|
7
9
|
import * as EnvioGlobal from "./EnvioGlobal.res.mjs";
|
|
8
10
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
9
11
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
@@ -54,6 +56,28 @@ function startRegistration(config) {
|
|
|
54
56
|
queued.forEach(fn => fn(r));
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
function makeRegistration(config) {
|
|
60
|
+
return {
|
|
61
|
+
config: config,
|
|
62
|
+
registrationsByChainId: {},
|
|
63
|
+
finished: false
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function useRegistration(registration, fn) {
|
|
68
|
+
let previous = EnvioGlobal.value.activeRegistration;
|
|
69
|
+
EnvioGlobal.value.activeRegistration = Primitive_option.some(registration);
|
|
70
|
+
let result;
|
|
71
|
+
try {
|
|
72
|
+
result = await fn();
|
|
73
|
+
} catch (exn) {
|
|
74
|
+
EnvioGlobal.value.activeRegistration = previous;
|
|
75
|
+
throw exn;
|
|
76
|
+
}
|
|
77
|
+
EnvioGlobal.value.activeRegistration = previous;
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
57
81
|
function getChainRegistrations(r, chainId) {
|
|
58
82
|
let key = ChainId.toString(chainId);
|
|
59
83
|
let existing = r.registrationsByChainId[key];
|
|
@@ -70,11 +94,11 @@ function getChainRegistrations(r, chainId) {
|
|
|
70
94
|
return fresh;
|
|
71
95
|
}
|
|
72
96
|
|
|
73
|
-
function buildOnEventRegistrationWith(config, chainId, eventConfig, isWildcard, handler, contractRegister, where, startBlock) {
|
|
97
|
+
function buildOnEventRegistrationWith(config, chainId, eventConfig, isWildcard, handler, contractRegister, where, fieldSelection, startBlock) {
|
|
74
98
|
let match = config.ecosystem.name;
|
|
75
99
|
switch (match) {
|
|
76
100
|
case "evm" :
|
|
77
|
-
return EventConfigBuilder.buildEvmOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, where, chainId, config.ecosystem.onEventBlockFilterSchema, startBlock);
|
|
101
|
+
return EventConfigBuilder.buildEvmOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, where, chainId, config.ecosystem.onEventBlockFilterSchema, fieldSelection, startBlock);
|
|
78
102
|
case "fuel" :
|
|
79
103
|
return EventConfigBuilder.buildFuelOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, startBlock);
|
|
80
104
|
case "svm" :
|
|
@@ -96,6 +120,13 @@ function sameEventAndFilter(a, b, config) {
|
|
|
96
120
|
}
|
|
97
121
|
}
|
|
98
122
|
|
|
123
|
+
function mergeInto(base, other) {
|
|
124
|
+
let newrecord = {...base};
|
|
125
|
+
newrecord.fieldSelection = Internal.unionFieldSelection(base.fieldSelection, other.fieldSelection);
|
|
126
|
+
newrecord.contractRegister = other.contractRegister;
|
|
127
|
+
return newrecord;
|
|
128
|
+
}
|
|
129
|
+
|
|
99
130
|
function mergeRegistrations(resolved, config) {
|
|
100
131
|
let merged = {
|
|
101
132
|
contents: []
|
|
@@ -114,8 +145,7 @@ function mergeRegistrations(resolved, config) {
|
|
|
114
145
|
return;
|
|
115
146
|
}
|
|
116
147
|
let target = merged.contents[i];
|
|
117
|
-
|
|
118
|
-
merged.contents = merged.contents.filter((param, j) => j !== i).concat([(newrecord.contractRegister = target.contractRegister, newrecord)]);
|
|
148
|
+
merged.contents = merged.contents.filter((param, j) => j !== i).concat([mergeInto(reg, target)]);
|
|
119
149
|
return;
|
|
120
150
|
}
|
|
121
151
|
let i$1 = merged.contents.findIndex(m => {
|
|
@@ -131,9 +161,7 @@ function mergeRegistrations(resolved, config) {
|
|
|
131
161
|
}
|
|
132
162
|
let target$1 = merged.contents[i$1];
|
|
133
163
|
let next = merged.contents.slice();
|
|
134
|
-
|
|
135
|
-
newrecord$1.contractRegister = reg.contractRegister;
|
|
136
|
-
next[i$1] = newrecord$1;
|
|
164
|
+
next[i$1] = mergeInto(target$1, reg);
|
|
137
165
|
merged.contents = next;
|
|
138
166
|
});
|
|
139
167
|
return merged.contents;
|
|
@@ -171,6 +199,8 @@ function describeConfigured(registration, contractName) {
|
|
|
171
199
|
function addOnEventRegistration(registration, contractName, eventName, handler, contractRegister, eventOptions) {
|
|
172
200
|
let isWildcard = Stdlib_Option.getOr(Stdlib_Option.flatMap(eventOptions, v => v.wildcard), false);
|
|
173
201
|
let where = Stdlib_Option.flatMap(eventOptions, v => v.where);
|
|
202
|
+
let fields = Stdlib_Option.flatMap(eventOptions, v => v.fields);
|
|
203
|
+
let fieldSelection = fields !== undefined ? (registration.config.ecosystem.name !== "evm" ? Stdlib_JsError.throwWithMessage(`The fields option of the "` + eventName + `" event registration on contract "` + contractName + `" is only supported on EVM. Select the fields in your config instead.`) : undefined, EventConfigBuilder.resolveInlineFieldSelection(fields, contractName, eventName, registration.config.enableRawEvents)) : undefined;
|
|
174
204
|
let matched = {
|
|
175
205
|
contents: false
|
|
176
206
|
};
|
|
@@ -184,7 +214,7 @@ function addOnEventRegistration(registration, contractName, eventName, handler,
|
|
|
184
214
|
return;
|
|
185
215
|
}
|
|
186
216
|
matched.contents = true;
|
|
187
|
-
let reg = buildOnEventRegistrationWith(registration.config, chainConfig.id, eventConfig, isWildcard, handler, contractRegister, where, contract.startBlock);
|
|
217
|
+
let reg = buildOnEventRegistrationWith(registration.config, chainConfig.id, eventConfig, isWildcard, handler, contractRegister, where, fieldSelection, contract.startBlock);
|
|
188
218
|
getChainRegistrations(registration, chainConfig.id).onEventRegistrations.push(reg);
|
|
189
219
|
});
|
|
190
220
|
if (matched.contents) {
|
|
@@ -257,7 +287,23 @@ function getSimulateOnEventRegistrations(config, chainId, eventConfig) {
|
|
|
257
287
|
if (Utils.$$Array.notEmpty(matching)) {
|
|
258
288
|
return matching;
|
|
259
289
|
} else {
|
|
260
|
-
return [buildOnEventRegistrationWith(config, chainId, eventConfig, false, undefined, undefined, undefined, undefined)];
|
|
290
|
+
return [buildOnEventRegistrationWith(config, chainId, eventConfig, false, undefined, undefined, undefined, undefined, undefined)];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function validateRpcFieldSelection(chainConfig, registrations) {
|
|
295
|
+
let match = chainConfig.sourceConfig;
|
|
296
|
+
let hasRpc;
|
|
297
|
+
hasRpc = match.TAG === "EvmSourceConfig" ? !Utils.$$Array.isEmpty(match.rpcs) : false;
|
|
298
|
+
if (hasRpc) {
|
|
299
|
+
registrations.forEach(reg => {
|
|
300
|
+
reg.fieldSelection.transactionFields.forEach(name => {
|
|
301
|
+
if (!RpcSource.isRpcTransactionField(name)) {
|
|
302
|
+
return Stdlib_JsError.throwWithMessage(`The "` + name + `" transaction field selected for the "` + reg.eventConfig.name + `" event on contract "` + reg.eventConfig.contractName + `" is unavailable for indexing via RPC. Remove it from the field selection, or remove chain ` + ChainId.toString(chainConfig.id) + `'s RPC source — even an RPC the chain only falls back to has to deliver the selection.`);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
return;
|
|
261
307
|
}
|
|
262
308
|
}
|
|
263
309
|
|
|
@@ -284,7 +330,7 @@ function finishRegistration(config) {
|
|
|
284
330
|
return;
|
|
285
331
|
}
|
|
286
332
|
if (config.enableRawEvents) {
|
|
287
|
-
rawEventRegs.push(buildOnEventRegistrationWith(config, chainId, eventConfig, false, undefined, undefined, undefined, contract.startBlock));
|
|
333
|
+
rawEventRegs.push(buildOnEventRegistrationWith(config, chainId, eventConfig, false, undefined, undefined, undefined, undefined, contract.startBlock));
|
|
288
334
|
return;
|
|
289
335
|
}
|
|
290
336
|
let set = notRegisteredEventsByContract[contract.name];
|
|
@@ -307,6 +353,7 @@ function finishRegistration(config) {
|
|
|
307
353
|
let newrecord = {...reg};
|
|
308
354
|
onEventRegistrations.push((newrecord.index = onEventRegistrations.length, newrecord));
|
|
309
355
|
});
|
|
356
|
+
validateRpcFieldSelection(chainConfig, onEventRegistrations);
|
|
310
357
|
let chainRegs = r.registrationsByChainId[key];
|
|
311
358
|
registrationsByChainId[key] = {
|
|
312
359
|
onEventRegistrations: onEventRegistrations,
|
|
@@ -435,6 +482,8 @@ function registerOnBlock(name, where, handler, getChainsObject) {
|
|
|
435
482
|
|
|
436
483
|
export {
|
|
437
484
|
startRegistration,
|
|
485
|
+
makeRegistration,
|
|
486
|
+
useRegistration,
|
|
438
487
|
resetOnEventRegistrations,
|
|
439
488
|
isPendingRegistration,
|
|
440
489
|
finishRegistration,
|
package/src/HandlerRegister.resi
CHANGED
|
@@ -5,6 +5,10 @@ type chainRegistrations = {
|
|
|
5
5
|
type registrationsByChainId = dict<chainRegistrations>
|
|
6
6
|
|
|
7
7
|
let startRegistration: (~config: Config.t) => unit
|
|
8
|
+
|
|
9
|
+
type registration
|
|
10
|
+
let makeRegistration: (~config: Config.t) => registration
|
|
11
|
+
let useRegistration: (registration, unit => promise<'a>) => promise<'a>
|
|
8
12
|
let resetOnEventRegistrations: unit => unit
|
|
9
13
|
let isPendingRegistration: unit => bool
|
|
10
14
|
let finishRegistration: (~config: Config.t) => registrationsByChainId
|
package/src/Internal.res
CHANGED
|
@@ -140,7 +140,7 @@ let evmTransactionFieldSchema = S.enum(allEvmTransactionFields)
|
|
|
140
140
|
// position in the selection mask) and `Svm.res` `transactionFields`.
|
|
141
141
|
type svmTransactionField =
|
|
142
142
|
| @as("transactionIndex") TransactionIndex
|
|
143
|
-
| @as("
|
|
143
|
+
| @as("signature") Signature
|
|
144
144
|
| @as("feePayer") FeePayer
|
|
145
145
|
| @as("success") Success
|
|
146
146
|
| @as("err") Err
|
|
@@ -150,10 +150,11 @@ type svmTransactionField =
|
|
|
150
150
|
| @as("recentBlockhash") RecentBlockhash
|
|
151
151
|
| @as("version") Version
|
|
152
152
|
| @as("tokenBalances") TokenBalances
|
|
153
|
+
| @as("allSignatures") AllSignatures
|
|
153
154
|
|
|
154
155
|
let allSvmTransactionFields: array<svmTransactionField> = [
|
|
155
156
|
TransactionIndex,
|
|
156
|
-
|
|
157
|
+
Signature,
|
|
157
158
|
FeePayer,
|
|
158
159
|
Success,
|
|
159
160
|
Err,
|
|
@@ -163,6 +164,7 @@ let allSvmTransactionFields: array<svmTransactionField> = [
|
|
|
163
164
|
RecentBlockhash,
|
|
164
165
|
Version,
|
|
165
166
|
TokenBalances,
|
|
167
|
+
AllSignatures,
|
|
166
168
|
]
|
|
167
169
|
let svmTransactionFieldSchema = S.enum(allSvmTransactionFields)
|
|
168
170
|
|
|
@@ -416,6 +418,51 @@ type indexingContract = {
|
|
|
416
418
|
effectiveStartBlock: int,
|
|
417
419
|
}
|
|
418
420
|
|
|
421
|
+
// What a single registration fetches and materialises. Field names are strings
|
|
422
|
+
// so every ecosystem shares one type — the typed field variants are strings at
|
|
423
|
+
// runtime. Always built through `makeFieldSelection`/`unionFieldSelection`
|
|
424
|
+
// below, so a set and its mask are never derived from different inputs. Unlike
|
|
425
|
+
// `eventConfig`, not `private` — that would block those two constructors too,
|
|
426
|
+
// since a private record can only be coerced from a distinct record type.
|
|
427
|
+
type fieldSelection = {
|
|
428
|
+
blockFields: Utils.Set.t<string>,
|
|
429
|
+
transactionFields: Utils.Set.t<string>,
|
|
430
|
+
// The sets precompiled to the store selections `ChainState` materialises with.
|
|
431
|
+
blockMask: float,
|
|
432
|
+
transactionMask: float,
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// `~blockMaskFn`/`~transactionMaskFn` are the ecosystem's `Evm`/`Svm`/`Fuel`
|
|
436
|
+
// mask functions, which the ecosystem modules pass in (they depend on this
|
|
437
|
+
// module, so it can't reach them).
|
|
438
|
+
let makeFieldSelection = (
|
|
439
|
+
~blockFields: Utils.Set.t<string>,
|
|
440
|
+
~transactionFields: Utils.Set.t<string>,
|
|
441
|
+
~blockMaskFn: Utils.Set.t<string> => float,
|
|
442
|
+
~transactionMaskFn: Utils.Set.t<string> => float,
|
|
443
|
+
): fieldSelection => {
|
|
444
|
+
blockFields,
|
|
445
|
+
transactionFields,
|
|
446
|
+
blockMask: blockMaskFn(blockFields),
|
|
447
|
+
transactionMask: transactionMaskFn(transactionFields),
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Two registrations merged into one dispatch off a single item, so it has to
|
|
451
|
+
// carry the union of what both callbacks read. Each callback's type only claims
|
|
452
|
+
// its own selection, so the extra fields are unread rather than unsound.
|
|
453
|
+
//
|
|
454
|
+
// The overwhelmingly common case is two registrations that never named fields
|
|
455
|
+
// inline and so share their event config's sets by reference — returning those
|
|
456
|
+
// unchanged keeps the merge allocation-free.
|
|
457
|
+
let unionFields = (a, b) => a === b ? a : a->Utils.Set.union(b)
|
|
458
|
+
|
|
459
|
+
let unionFieldSelection = (a: fieldSelection, b: fieldSelection): fieldSelection => {
|
|
460
|
+
blockFields: unionFields(a.blockFields, b.blockFields),
|
|
461
|
+
transactionFields: unionFields(a.transactionFields, b.transactionFields),
|
|
462
|
+
blockMask: FieldMask.orMask(a.blockMask, b.blockMask),
|
|
463
|
+
transactionMask: FieldMask.orMask(a.transactionMask, b.transactionMask),
|
|
464
|
+
}
|
|
465
|
+
|
|
419
466
|
// Definition of an event/instruction we know how to decode: identity + decode
|
|
420
467
|
// schemas + chain-independent field selection. A pure function of the ABI +
|
|
421
468
|
// config, shared across chains. `private` so it can only be coerced from an
|
|
@@ -427,22 +474,12 @@ type eventConfig = private {
|
|
|
427
474
|
contractName: string,
|
|
428
475
|
paramsRawEventSchema: S.schema<eventParams>,
|
|
429
476
|
simulateParamsSchema: S.schema<eventParams>,
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
//
|
|
433
|
-
// the
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// bitmask (bit per ecosystem field code). Materialisation reads this per item
|
|
437
|
-
// so each transaction decodes only the fields its event selected. `0.` when
|
|
438
|
-
// nothing is selected or the ecosystem carries the transaction inline (Fuel).
|
|
439
|
-
transactionFieldMask: float,
|
|
440
|
-
// Selected block fields precompiled to the block-store selection bitmask (bit
|
|
441
|
-
// per ecosystem field code). `0.` for ecosystems that carry the block fully
|
|
442
|
-
// inline (RPC/Fuel). The EVM selection always includes number/timestamp/hash,
|
|
443
|
-
// so an EVM mask always has their bits set; SVM stamps slot/time/hash inline
|
|
444
|
-
// from the response and its mask is the user's selection alone.
|
|
445
|
-
blockFieldMask: float,
|
|
477
|
+
// The `config.yaml` selection, which every registration of this event
|
|
478
|
+
// inherits unless it names its own fields inline. Held by reference, so two
|
|
479
|
+
// un-customized registrations share one set — which is what keeps a merge
|
|
480
|
+
// allocation-free and the per-source parser caches (keyed on set identity)
|
|
481
|
+
// effective.
|
|
482
|
+
fieldSelection: fieldSelection,
|
|
446
483
|
}
|
|
447
484
|
|
|
448
485
|
type fuelEventKind =
|
|
@@ -497,7 +534,6 @@ type onEventWhereArgs<'chain> = {chain: 'chain}
|
|
|
497
534
|
|
|
498
535
|
type evmEventConfig = {
|
|
499
536
|
...eventConfig,
|
|
500
|
-
selectedBlockFields: Utils.Set.t<evmBlockField>,
|
|
501
537
|
sighash: string,
|
|
502
538
|
topicCount: int,
|
|
503
539
|
paramsMetadata: array<paramMeta>,
|
|
@@ -525,10 +561,6 @@ type svmAccountFilterGroup = array<svmAccountFilter>
|
|
|
525
561
|
|
|
526
562
|
type svmInstructionEventConfig = {
|
|
527
563
|
...eventConfig,
|
|
528
|
-
/** Block fields selected via `field_selection.block_fields` (`slot` is always
|
|
529
|
-
included and excluded from this set). Drives the block query columns;
|
|
530
|
-
precompiled to `blockFieldMask` for store materialisation. */
|
|
531
|
-
selectedBlockFields: Utils.Set.t<svmBlockField>,
|
|
532
564
|
/** Base58 Solana program id this instruction belongs to. */
|
|
533
565
|
programId: SvmTypes.Pubkey.t,
|
|
534
566
|
/** Hex-encoded discriminator. `None` matches every instruction in the program. */
|
|
@@ -589,6 +621,10 @@ type onEventRegistration = {
|
|
|
589
621
|
// Final start block: the contract/chain config value, overridden by a
|
|
590
622
|
// `where.block.number._gte` when the registered `where` supplies one.
|
|
591
623
|
startBlock: option<int>,
|
|
624
|
+
// The `eventConfig` selection unless the registration passed an inline
|
|
625
|
+
// `fields` option, which replaces it. Two registrations of one event can
|
|
626
|
+
// select different fields, so this is per-registration rather than shared.
|
|
627
|
+
fieldSelection: fieldSelection,
|
|
592
628
|
}
|
|
593
629
|
|
|
594
630
|
type evmOnEventRegistration = {
|
|
@@ -625,6 +661,7 @@ type eventItem = private {
|
|
|
625
661
|
chainId: ChainId.t,
|
|
626
662
|
blockNumber: int,
|
|
627
663
|
logIndex: int,
|
|
664
|
+
orderPath?: array<int>,
|
|
628
665
|
// Within-block transaction index — the key into the per-chain transaction
|
|
629
666
|
// store. Unused (0) for ecosystems that carry the transaction inline (Fuel).
|
|
630
667
|
transactionIndex: int,
|
|
@@ -678,17 +715,31 @@ type item =
|
|
|
678
715
|
chainId: ChainId.t,
|
|
679
716
|
blockNumber: int,
|
|
680
717
|
logIndex: int,
|
|
718
|
+
// Ordering tiebreak for ecosystems whose within-block order isn't a
|
|
719
|
+
// scalar. SVM keys an instruction by `(transactionIndex, path)`: the
|
|
720
|
+
// logIndex above is the transaction, this is its position in that
|
|
721
|
+
// transaction's CPI tree. Absent on EVM and Fuel, whose log/receipt
|
|
722
|
+
// index already totally orders a block.
|
|
723
|
+
orderPath?: array<int>,
|
|
681
724
|
transactionIndex: int,
|
|
682
725
|
payload: eventPayload,
|
|
683
726
|
})
|
|
684
|
-
| @as(1) Block({onBlockRegistration: onBlockRegistration, blockNumber: int
|
|
727
|
+
| @as(1) Block({onBlockRegistration: onBlockRegistration, blockNumber: int})
|
|
685
728
|
|
|
686
729
|
external castUnsafeEventItem: item => eventItem = "%identity"
|
|
687
730
|
|
|
688
731
|
@get
|
|
689
732
|
external getItemBlockNumber: item => int = "blockNumber"
|
|
733
|
+
// Only meaningful on an `Event`: a block item has no log index, and the buffer
|
|
734
|
+
// comparator reads this only after the kinds match.
|
|
690
735
|
@get
|
|
691
736
|
external getItemLogIndex: item => int = "logIndex"
|
|
737
|
+
@get
|
|
738
|
+
external getItemOrderPath: item => Nullable.t<array<int>> = "orderPath"
|
|
739
|
+
// The variant tag. Read directly so the comparator can order every event of a
|
|
740
|
+
// block ahead of that block's handlers without a `switch`.
|
|
741
|
+
@get
|
|
742
|
+
external getItemKind: item => int = "kind"
|
|
692
743
|
|
|
693
744
|
let getItemChainId = item =>
|
|
694
745
|
switch item {
|
|
@@ -696,9 +747,18 @@ let getItemChainId = item =>
|
|
|
696
747
|
| Block({onBlockRegistration: {chainId}}) => chainId
|
|
697
748
|
}
|
|
698
749
|
|
|
750
|
+
// The `fields` option of an EVM `onEvent`/`contractRegister` registration:
|
|
751
|
+
// the block and transaction fields the handler reads. Replaces the config
|
|
752
|
+
// `field_selection` for this registration.
|
|
753
|
+
type evmFieldsSelection = {
|
|
754
|
+
block?: array<string>,
|
|
755
|
+
transaction?: array<string>,
|
|
756
|
+
}
|
|
757
|
+
|
|
699
758
|
type eventOptions<'where> = {
|
|
700
759
|
wildcard?: bool,
|
|
701
760
|
where?: 'where,
|
|
761
|
+
fields?: evmFieldsSelection,
|
|
702
762
|
}
|
|
703
763
|
|
|
704
764
|
type fuelSupplyParams = {
|
package/src/Internal.res.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import * as Table from "./db/Table.res.mjs";
|
|
|
4
4
|
import * as Utils from "./Utils.res.mjs";
|
|
5
5
|
import * as Address from "./Address.res.mjs";
|
|
6
6
|
import * as ChainId from "./ChainId.res.mjs";
|
|
7
|
+
import * as FieldMask from "./sources/FieldMask.res.mjs";
|
|
7
8
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
8
9
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
9
10
|
|
|
@@ -78,7 +79,7 @@ let evmTransactionFieldSchema = S$RescriptSchema.$$enum(allEvmTransactionFields)
|
|
|
78
79
|
|
|
79
80
|
let allSvmTransactionFields = [
|
|
80
81
|
"transactionIndex",
|
|
81
|
-
"
|
|
82
|
+
"signature",
|
|
82
83
|
"feePayer",
|
|
83
84
|
"success",
|
|
84
85
|
"err",
|
|
@@ -87,7 +88,8 @@ let allSvmTransactionFields = [
|
|
|
87
88
|
"accountKeys",
|
|
88
89
|
"recentBlockhash",
|
|
89
90
|
"version",
|
|
90
|
-
"tokenBalances"
|
|
91
|
+
"tokenBalances",
|
|
92
|
+
"allSignatures"
|
|
91
93
|
];
|
|
92
94
|
|
|
93
95
|
let svmTransactionFieldSchema = S$RescriptSchema.$$enum(allSvmTransactionFields);
|
|
@@ -139,6 +141,32 @@ let evmNullableTransactionFields = new Set([
|
|
|
139
141
|
"type"
|
|
140
142
|
]);
|
|
141
143
|
|
|
144
|
+
function makeFieldSelection(blockFields, transactionFields, blockMaskFn, transactionMaskFn) {
|
|
145
|
+
return {
|
|
146
|
+
blockFields: blockFields,
|
|
147
|
+
transactionFields: transactionFields,
|
|
148
|
+
blockMask: blockMaskFn(blockFields),
|
|
149
|
+
transactionMask: transactionMaskFn(transactionFields)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function unionFields(a, b) {
|
|
154
|
+
if (a === b) {
|
|
155
|
+
return a;
|
|
156
|
+
} else {
|
|
157
|
+
return a.union(b);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function unionFieldSelection(a, b) {
|
|
162
|
+
return {
|
|
163
|
+
blockFields: unionFields(a.blockFields, b.blockFields),
|
|
164
|
+
transactionFields: unionFields(a.transactionFields, b.transactionFields),
|
|
165
|
+
blockMask: FieldMask.orMask(a.blockMask, b.blockMask),
|
|
166
|
+
transactionMask: FieldMask.orMask(a.transactionMask, b.transactionMask)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
142
170
|
function dependsOnAddresses(isWildcard, filterByAddresses) {
|
|
143
171
|
if (isWildcard) {
|
|
144
172
|
return filterByAddresses;
|
|
@@ -306,6 +334,9 @@ export {
|
|
|
306
334
|
svmBlockFieldSchema,
|
|
307
335
|
evmNullableBlockFields,
|
|
308
336
|
evmNullableTransactionFields,
|
|
337
|
+
makeFieldSelection,
|
|
338
|
+
unionFields,
|
|
339
|
+
unionFieldSelection,
|
|
309
340
|
dependsOnAddresses,
|
|
310
341
|
getItemChainId,
|
|
311
342
|
fuelSupplyParamsSchema,
|
package/src/LazyLoader.res
CHANGED
|
@@ -58,6 +58,17 @@ let timeoutAfter = timeoutMillis =>
|
|
|
58
58
|
)
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
+
// Remember `k` as cached, evicting the oldest entry once the cache is full.
|
|
62
|
+
let rememberLoaded = (am: asyncMap<'key, 'value>, k: 'key) => {
|
|
63
|
+
if am.loadedKeys->SDSL.Queue.push(k) > am._cacheSize {
|
|
64
|
+
switch am.loadedKeys->SDSL.Queue.pop {
|
|
65
|
+
| None => ()
|
|
66
|
+
| Some(old) =>
|
|
67
|
+
let _ = am.externalPromises->Utils.Map.delete(old)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
let rec loadNext = async (am: asyncMap<'key, 'value>, k: 'key) => {
|
|
62
73
|
// Track that we are loading it now
|
|
63
74
|
let _ = am.inProgress->Utils.Set.add(k)
|
|
@@ -75,17 +86,7 @@ let rec loadNext = async (am: asyncMap<'key, 'value>, k: 'key) => {
|
|
|
75
86
|
// Track that it is no longer in progress
|
|
76
87
|
let _ = am.inProgress->Utils.Set.delete(k)
|
|
77
88
|
|
|
78
|
-
|
|
79
|
-
let loadedKeysNumber = am.loadedKeys->SDSL.Queue.push(k)
|
|
80
|
-
|
|
81
|
-
// Delete the oldest key if the cache is overly full
|
|
82
|
-
if loadedKeysNumber > am._cacheSize {
|
|
83
|
-
switch am.loadedKeys->SDSL.Queue.pop {
|
|
84
|
-
| None => ()
|
|
85
|
-
| Some(old) =>
|
|
86
|
-
let _ = am.externalPromises->Utils.Map.delete(old)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
+
am->rememberLoaded(k)
|
|
89
90
|
|
|
90
91
|
// Load the next one, if there is anything in the queue
|
|
91
92
|
switch am.loaderQueue->SDSL.Queue.pop {
|