envio 3.3.2 → 3.5.0-alpha.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/package.json +6 -6
- package/src/Api.res +1 -8
- package/src/Api.res.mjs +1 -5
- package/src/Bin.res +2 -2
- package/src/ChainState.res +6 -1
- package/src/ChainState.res.mjs +4 -3
- package/src/Config.res +38 -5
- package/src/Config.res.mjs +35 -6
- package/src/Core.res +65 -20
- package/src/Core.res.mjs +36 -6
- package/src/EnvioGlobal.res +0 -2
- package/src/EnvioGlobal.res.mjs +0 -1
- package/src/ExitOnCaughtUp.res +6 -2
- package/src/ExitOnCaughtUp.res.mjs +4 -0
- package/src/FetchState.res +3 -3
- package/src/HandlerRegister.res +357 -434
- package/src/HandlerRegister.res.mjs +202 -242
- package/src/HandlerRegister.resi +7 -15
- package/src/IndexerState.res +10 -0
- package/src/IndexerState.res.mjs +9 -3
- package/src/IndexerState.resi +3 -0
- package/src/Internal.res +10 -1
- package/src/Main.res.mjs +4 -4
- package/src/PgStorage.res +16 -1
- package/src/PgStorage.res.mjs +9 -1
- package/src/SimulateItems.res +61 -40
- package/src/SimulateItems.res.mjs +37 -21
- package/src/TestIndexer.res +453 -454
- package/src/TestIndexer.res.mjs +321 -344
- package/src/bindings/ClickHouse.res +68 -2
- package/src/bindings/ClickHouse.res.mjs +47 -2
- package/src/sources/EvmChain.res +1 -1
- package/src/sources/EvmChain.res.mjs +2 -2
- package/src/sources/FuelHyperSync.res +74 -0
- package/src/sources/FuelHyperSync.res.mjs +80 -0
- package/src/sources/FuelHyperSync.resi +22 -0
- package/src/sources/FuelHyperSyncClient.res +120 -0
- package/src/sources/FuelHyperSyncClient.res.mjs +71 -0
- package/src/sources/FuelHyperSyncSource.res +257 -0
- package/src/sources/FuelHyperSyncSource.res.mjs +252 -0
- package/src/sources/HyperSyncClient.res +2 -2
- package/src/sources/HyperSyncClient.res.mjs +1 -1
- package/src/sources/SvmHyperSyncClient.res +139 -102
- package/src/sources/SvmHyperSyncClient.res.mjs +45 -5
- package/src/sources/SvmHyperSyncSource.res +60 -440
- package/src/sources/SvmHyperSyncSource.res.mjs +42 -363
- package/src/TestIndexerProxyStorage.res +0 -196
- package/src/TestIndexerProxyStorage.res.mjs +0 -121
- package/src/TestIndexerWorker.res +0 -4
- package/src/TestIndexerWorker.res.mjs +0 -7
- package/src/sources/EventRouter.res +0 -165
- package/src/sources/EventRouter.res.mjs +0 -153
- package/src/sources/HyperFuel.res +0 -179
- package/src/sources/HyperFuel.res.mjs +0 -146
- package/src/sources/HyperFuel.resi +0 -36
- package/src/sources/HyperFuelClient.res +0 -127
- package/src/sources/HyperFuelClient.res.mjs +0 -20
- package/src/sources/HyperFuelSource.res +0 -502
- package/src/sources/HyperFuelSource.res.mjs +0 -481
- /package/src/sources/{HyperSyncSource.res → EvmHyperSyncSource.res} +0 -0
- /package/src/sources/{HyperSyncSource.res.mjs → EvmHyperSyncSource.res.mjs} +0 -0
package/src/HandlerRegister.res
CHANGED
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
let empty = {
|
|
8
|
-
handler: None,
|
|
9
|
-
contractRegister: None,
|
|
10
|
-
eventOptions: None,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Per-chain onEventRegistrations built from the event definitions in
|
|
14
|
-
// `Config.t` plus whatever handler/contractRegister/eventOptions got
|
|
15
|
-
// registered for them, and the onBlock registrations collected during
|
|
16
|
-
// registration.
|
|
1
|
+
// Per-chain onEvent + onBlock registrations. Used both as the live registration
|
|
2
|
+
// store (`activeRegistration`, where onEvent regs are raw: registration order,
|
|
3
|
+
// unmerged, unindexed, `where:false` ones kept) and as the finished output of
|
|
4
|
+
// `finishRegistration` (merged, backfilled, indexed).
|
|
17
5
|
type chainRegistrations = {
|
|
18
6
|
onEventRegistrations: array<Internal.onEventRegistration>,
|
|
19
7
|
onBlockRegistrations: array<Internal.onBlockRegistration>,
|
|
@@ -22,38 +10,24 @@ type chainRegistrations = {
|
|
|
22
10
|
// The finished registration state returned by `finishRegistration`.
|
|
23
11
|
type registrationsByChainId = dict<chainRegistrations>
|
|
24
12
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
onBlockRegistrations: array<Internal.onBlockRegistration>,
|
|
32
|
-
}
|
|
33
|
-
|
|
13
|
+
// The one registration, resolved once and reused. Handlers register into it at
|
|
14
|
+
// `onEvent`/`onBlock` call time (resolving for every chain in `config`, which is
|
|
15
|
+
// the full chain set), and it persists in `EnvioGlobal` across the many
|
|
16
|
+
// `finishRegistration` calls a single isolate makes (handler modules are
|
|
17
|
+
// import-cached and register only once). `finishRegistration` reads it and
|
|
18
|
+
// builds a fresh per-config output, never mutating the store.
|
|
34
19
|
type activeRegistration = {
|
|
35
20
|
config: Config.t,
|
|
36
|
-
registrationsByChainId: dict<
|
|
21
|
+
registrationsByChainId: dict<chainRegistrations>,
|
|
37
22
|
mutable finished: bool,
|
|
38
23
|
}
|
|
39
24
|
|
|
40
|
-
// Registration state lives in the process-wide `EnvioGlobal` record (shared
|
|
41
|
-
// across duplicate envio module instances); the slots are opaque there, so
|
|
42
|
-
// cast them to the real types once here.
|
|
43
|
-
let eventRegistrations =
|
|
44
|
-
EnvioGlobal.value.eventRegistrations->(Utils.magic: dict<unknown> => dict<eventRegistration>)
|
|
45
|
-
|
|
46
25
|
let getKey = (~contractName, ~eventName) => contractName ++ "." ++ eventName
|
|
47
26
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let set = (~contractName, ~eventName, registration) => {
|
|
56
|
-
eventRegistrations->Dict.set(getKey(~contractName, ~eventName), registration)
|
|
27
|
+
// Test-only: reset to fresh-import state so a new registration cycle starts
|
|
28
|
+
// empty (production starts each isolate empty and registers once).
|
|
29
|
+
let resetOnEventRegistrations = () => {
|
|
30
|
+
EnvioGlobal.value.activeRegistration = None
|
|
57
31
|
}
|
|
58
32
|
|
|
59
33
|
let getActiveRegistration = () =>
|
|
@@ -85,29 +59,36 @@ let withRegistration = (fn: activeRegistration => unit) => {
|
|
|
85
59
|
}
|
|
86
60
|
}
|
|
87
61
|
|
|
62
|
+
// Idempotent: handlers register once (import-cached), so the first call builds
|
|
63
|
+
// the registration and every later call reuses it. `config` must be the full
|
|
64
|
+
// chain set — registrations resolve for all its chains here, and
|
|
65
|
+
// `finishRegistration` later narrows to whatever config it's given.
|
|
88
66
|
let startRegistration = (~config: Config.t) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Loop + cleanup in one go
|
|
97
|
-
switch preRegistered->Array.pop {
|
|
98
|
-
| Some(fn) => fn(r)
|
|
99
|
-
| None => ()
|
|
67
|
+
switch getActiveRegistration() {
|
|
68
|
+
| Some(_) => ()
|
|
69
|
+
| None =>
|
|
70
|
+
let r = {
|
|
71
|
+
config,
|
|
72
|
+
registrationsByChainId: Dict.make(),
|
|
73
|
+
finished: false,
|
|
100
74
|
}
|
|
75
|
+
EnvioGlobal.value.activeRegistration = Some(r->(Utils.magic: activeRegistration => unknown))
|
|
76
|
+
// Replay pre-registered callbacks in source (FIFO) order, then clear. For
|
|
77
|
+
// multiple handlers on one event this replay order is the dispatch order, so
|
|
78
|
+
// it must not reverse (which `Array.pop` would).
|
|
79
|
+
let queued = preRegistered->Array.copy
|
|
80
|
+
preRegistered->Array.splice(~start=0, ~remove=preRegistered->Array.length, ~insert=[])
|
|
81
|
+
queued->Array.forEach(fn => fn(r))
|
|
101
82
|
}
|
|
102
83
|
}
|
|
103
84
|
|
|
104
|
-
let
|
|
85
|
+
let getChainRegistrations = (r: activeRegistration, ~chainId: int): chainRegistrations => {
|
|
105
86
|
let key = chainId->Int.toString
|
|
106
87
|
switch r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(key) {
|
|
107
|
-
| Some(
|
|
88
|
+
| Some(existing) => existing
|
|
108
89
|
| None =>
|
|
109
|
-
let fresh = {
|
|
110
|
-
onEventRegistrations:
|
|
90
|
+
let fresh: chainRegistrations = {
|
|
91
|
+
onEventRegistrations: [],
|
|
111
92
|
onBlockRegistrations: [],
|
|
112
93
|
}
|
|
113
94
|
r.registrationsByChainId->Dict.set(key, fresh)
|
|
@@ -158,268 +139,367 @@ let buildOnEventRegistrationWith = (
|
|
|
158
139
|
}
|
|
159
140
|
}
|
|
160
141
|
|
|
161
|
-
// Enrich one event definition into its (event, chain) registration using
|
|
162
|
-
// whatever handler/contractRegister/where the user registered for it. Shared
|
|
163
|
-
// by the incremental per-chain sync below, `simulate`, and test helpers so
|
|
164
|
-
// they stay in sync instead of re-deriving the per-ecosystem dispatch each
|
|
165
|
-
// place.
|
|
166
|
-
let buildOnEventRegistration = (
|
|
167
|
-
~config: Config.t,
|
|
168
|
-
~chainId: int,
|
|
169
|
-
~eventConfig: Internal.eventConfig,
|
|
170
|
-
~startBlock=?,
|
|
171
|
-
): Internal.onEventRegistration => {
|
|
172
|
-
let t = get(~contractName=eventConfig.contractName, ~eventName=eventConfig.name)
|
|
173
|
-
buildOnEventRegistrationWith(
|
|
174
|
-
~config,
|
|
175
|
-
~chainId,
|
|
176
|
-
~eventConfig,
|
|
177
|
-
~isWildcard=t.eventOptions->Option.flatMap(v => v.wildcard)->Option.getOr(false),
|
|
178
|
-
~handler=t.handler,
|
|
179
|
-
~contractRegister=t.contractRegister,
|
|
180
|
-
~where=t.eventOptions->Option.flatMap(v => v.where),
|
|
181
|
-
~startBlock?,
|
|
182
|
-
)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let getHandler = (~contractName, ~eventName) => get(~contractName, ~eventName).handler
|
|
186
|
-
|
|
187
|
-
let getContractRegister = (~contractName, ~eventName) =>
|
|
188
|
-
get(~contractName, ~eventName).contractRegister
|
|
189
|
-
|
|
190
|
-
let isWildcard = (~contractName, ~eventName) =>
|
|
191
|
-
get(~contractName, ~eventName).eventOptions
|
|
192
|
-
->Option.flatMap(value => value.wildcard)
|
|
193
|
-
->Option.getOr(false)
|
|
194
|
-
|
|
195
|
-
let hasRegistration = (~contractName, ~eventName) => {
|
|
196
|
-
let r = get(~contractName, ~eventName)
|
|
197
|
-
r.handler->Option.isSome || r.contractRegister->Option.isSome
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
type eventNamespace = {contractName: string, eventName: string}
|
|
201
|
-
|
|
202
|
-
let raiseDuplicateRegistration = (~contractName, ~eventName, ~msg, ~logger) => {
|
|
203
|
-
let fullMsg = msg ++ " for " ++ contractName ++ "." ++ eventName
|
|
204
|
-
Logging.createChildFrom(~logger, ~params={contractName, eventName})->Logging.childError(fullMsg)
|
|
205
|
-
JsError.throwWithMessage(fullMsg)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// `where` equality is checked per chain on the resolved structure (see
|
|
209
|
-
// `syncOnEventRegistrations`), so registration options only need to agree on
|
|
210
|
-
// `wildcard` here — two callbacks that resolve to identical filters count as
|
|
211
|
-
// identical options even when the function references differ.
|
|
212
|
-
let eventOptionsMatch = (
|
|
213
|
-
existing: option<Internal.eventOptions<JSON.t>>,
|
|
214
|
-
incoming: option<Internal.eventOptions<JSON.t>>,
|
|
215
|
-
) => {
|
|
216
|
-
switch (existing, incoming) {
|
|
217
|
-
| (None, None) => true
|
|
218
|
-
| (Some(a), Some(b)) => a.wildcard === b.wildcard
|
|
219
|
-
| _ => false
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
142
|
let getResolvedWhere = (reg: Internal.onEventRegistration) =>
|
|
224
143
|
(
|
|
225
144
|
reg->(Utils.magic: Internal.onEventRegistration => Internal.evmOnEventRegistration)
|
|
226
145
|
).resolvedWhere
|
|
227
146
|
|
|
228
|
-
//
|
|
229
|
-
// and
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
~
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
let key = getKey(~contractName, ~eventName)
|
|
147
|
+
// Two chain registrations target the same fetched log when they share the
|
|
148
|
+
// event, the wildcard flag, and (on EVM) the resolved `where` — a handler and
|
|
149
|
+
// a contractRegister that agree on all three can be merged into one
|
|
150
|
+
// registration (so one item per log runs both). `where` is compared on the
|
|
151
|
+
// resolved structure (`Values` by hex arrays, `ContractAddresses` by contract
|
|
152
|
+
// name, plus `startBlock`); differing filters stay separate registrations.
|
|
153
|
+
let sameEventAndFilter = (
|
|
154
|
+
a: Internal.onEventRegistration,
|
|
155
|
+
b: Internal.onEventRegistration,
|
|
156
|
+
~config: Config.t,
|
|
157
|
+
) =>
|
|
158
|
+
a.eventConfig.contractName === b.eventConfig.contractName &&
|
|
159
|
+
a.eventConfig.name === b.eventConfig.name &&
|
|
160
|
+
a.isWildcard === b.isWildcard &&
|
|
161
|
+
switch config.ecosystem.name {
|
|
162
|
+
| Evm => getResolvedWhere(a) == getResolvedWhere(b)
|
|
163
|
+
| Fuel | Svm => true
|
|
164
|
+
}
|
|
247
165
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
pending.onEventRegistrations->Dict.set(key, newRegistration)
|
|
275
|
-
}
|
|
166
|
+
// Merge each contractRegister into a matching handler registration (either
|
|
167
|
+
// registration order; the merged registration takes the handler's slot so
|
|
168
|
+
// dispatch order follows handler registration order). Two handlers (or two
|
|
169
|
+
// contractRegisters) for one event never merge. Operates on the raw per-chain
|
|
170
|
+
// registrations stored at `onEvent` time; shared by `finishRegistration` and
|
|
171
|
+
// simulate so both see the same registrations.
|
|
172
|
+
let mergeRegistrations = (resolved: array<Internal.onEventRegistration>, ~config: Config.t): array<
|
|
173
|
+
Internal.onEventRegistration,
|
|
174
|
+
> => {
|
|
175
|
+
let merged: ref<array<Internal.onEventRegistration>> = ref([])
|
|
176
|
+
resolved->Array.forEach((reg: Internal.onEventRegistration) => {
|
|
177
|
+
if reg.handler->Option.isSome {
|
|
178
|
+
// A handler absorbs a matching contractRegister-only registration,
|
|
179
|
+
// dropping it and taking its own (handler) slot.
|
|
180
|
+
switch merged.contents->Array.findIndex(m =>
|
|
181
|
+
m.handler->Option.isNone &&
|
|
182
|
+
m.contractRegister->Option.isSome &&
|
|
183
|
+
sameEventAndFilter(m, reg, ~config)
|
|
184
|
+
) {
|
|
185
|
+
| -1 => merged := merged.contents->Array.concat([reg])
|
|
186
|
+
| i =>
|
|
187
|
+
let target = merged.contents->Array.getUnsafe(i)
|
|
188
|
+
merged :=
|
|
189
|
+
merged.contents
|
|
190
|
+
->Array.filterWithIndex((_, j) => j !== i)
|
|
191
|
+
->Array.concat([{...reg, contractRegister: target.contractRegister}])
|
|
276
192
|
}
|
|
277
|
-
}
|
|
193
|
+
} else {
|
|
194
|
+
// A contractRegister merges into a matching handler registration,
|
|
195
|
+
// keeping the handler's slot.
|
|
196
|
+
switch merged.contents->Array.findIndex(m =>
|
|
197
|
+
m.handler->Option.isSome &&
|
|
198
|
+
m.contractRegister->Option.isNone &&
|
|
199
|
+
sameEventAndFilter(m, reg, ~config)
|
|
200
|
+
) {
|
|
201
|
+
| -1 => merged := merged.contents->Array.concat([reg])
|
|
202
|
+
| i =>
|
|
203
|
+
let target = merged.contents->Array.getUnsafe(i)
|
|
204
|
+
let next = merged.contents->Array.copy
|
|
205
|
+
next->Array.setUnsafe(i, {...target, contractRegister: reg.contractRegister})
|
|
206
|
+
merged := next
|
|
207
|
+
}
|
|
208
|
+
}
|
|
278
209
|
})
|
|
210
|
+
merged.contents
|
|
279
211
|
}
|
|
280
212
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
213
|
+
// A `where` that resolved to no topic selections (`false` for this chain)
|
|
214
|
+
// should never be fetched or dispatched here — drop it. Only meaningful on EVM.
|
|
215
|
+
let isDroppedByWhere = (~config: Config.t, reg: Internal.onEventRegistration) =>
|
|
216
|
+
config.ecosystem.name === Evm && (reg->getResolvedWhere).topicSelections->Utils.Array.isEmpty
|
|
217
|
+
|
|
218
|
+
// Resolve one `onEvent`/`contractRegister` call into a registration for every
|
|
219
|
+
// chain in the config (the full chain set) and store it in registration order.
|
|
220
|
+
// Building runs the user's `where` callback here — once per chain — so a broken
|
|
221
|
+
// filter throws at the call site. A chain that doesn't define the event is
|
|
222
|
+
// skipped.
|
|
223
|
+
let addOnEventRegistration = (
|
|
224
|
+
registration: activeRegistration,
|
|
225
|
+
~contractName,
|
|
226
|
+
~eventName,
|
|
227
|
+
~handler: option<Internal.handler>,
|
|
228
|
+
~contractRegister: option<Internal.contractRegister>,
|
|
229
|
+
~eventOptions: option<Internal.eventOptions<JSON.t>>,
|
|
230
|
+
) => {
|
|
231
|
+
let isWildcard = eventOptions->Option.flatMap(v => v.wildcard)->Option.getOr(false)
|
|
232
|
+
let where = eventOptions->Option.flatMap(v => v.where)
|
|
233
|
+
registration.config.chainMap
|
|
234
|
+
->ChainMap.values
|
|
235
|
+
->Array.forEach(chainConfig =>
|
|
236
|
+
switch chainConfig.contracts->Array.find(c => c.name === contractName) {
|
|
237
|
+
| None => ()
|
|
238
|
+
| Some(contract) =>
|
|
239
|
+
switch contract.events->Array.find(e => e.name === eventName) {
|
|
240
|
+
| None => ()
|
|
241
|
+
| Some(eventConfig) =>
|
|
242
|
+
let reg = buildOnEventRegistrationWith(
|
|
243
|
+
~config=registration.config,
|
|
244
|
+
~chainId=chainConfig.id,
|
|
245
|
+
~eventConfig,
|
|
246
|
+
~isWildcard,
|
|
247
|
+
~handler,
|
|
248
|
+
~contractRegister,
|
|
249
|
+
~where,
|
|
250
|
+
~startBlock=?contract.startBlock,
|
|
295
251
|
)
|
|
252
|
+
(registration->getChainRegistrations(~chainId=chainConfig.id)).onEventRegistrations
|
|
253
|
+
->Array.push(reg)
|
|
254
|
+
->ignore
|
|
296
255
|
}
|
|
297
256
|
}
|
|
298
|
-
|
|
299
|
-
}
|
|
257
|
+
)
|
|
300
258
|
}
|
|
301
259
|
|
|
302
|
-
let setHandler = (
|
|
303
|
-
~contractName,
|
|
304
|
-
~eventName,
|
|
305
|
-
handler,
|
|
306
|
-
~eventOptions,
|
|
307
|
-
~logger=Logging.getLogger(),
|
|
308
|
-
) => {
|
|
260
|
+
let setHandler = (~contractName, ~eventName, handler, ~eventOptions) => {
|
|
309
261
|
withRegistration(registration => {
|
|
310
|
-
let t = get(~contractName, ~eventName)
|
|
311
262
|
let newHandler = handler->(Utils.magic: Internal.genericHandler<'args> => Internal.handler)
|
|
312
|
-
let
|
|
263
|
+
let eventOptions =
|
|
313
264
|
eventOptions->Option.map(v =>
|
|
314
265
|
v->(Utils.magic: Internal.eventOptions<'where> => Internal.eventOptions<JSON.t>)
|
|
315
266
|
)
|
|
316
|
-
|
|
317
|
-
| None =>
|
|
318
|
-
setEventOptions(~contractName, ~eventName, ~eventOptions, ~logger)
|
|
319
|
-
let t = get(~contractName, ~eventName)
|
|
320
|
-
set(
|
|
321
|
-
~contractName,
|
|
322
|
-
~eventName,
|
|
323
|
-
{
|
|
324
|
-
...t,
|
|
325
|
-
handler: Some(newHandler),
|
|
326
|
-
},
|
|
327
|
-
)
|
|
328
|
-
| Some(prevHandler) =>
|
|
329
|
-
if eventOptionsMatch(t.eventOptions, incomingEventOptions) {
|
|
330
|
-
let composedHandler: Internal.handler = async args => {
|
|
331
|
-
await prevHandler(args)
|
|
332
|
-
await newHandler(args)
|
|
333
|
-
}
|
|
334
|
-
set(
|
|
335
|
-
~contractName,
|
|
336
|
-
~eventName,
|
|
337
|
-
{
|
|
338
|
-
...t,
|
|
339
|
-
handler: Some(composedHandler),
|
|
340
|
-
},
|
|
341
|
-
)
|
|
342
|
-
} else {
|
|
343
|
-
raiseDuplicateRegistration(
|
|
344
|
-
~contractName,
|
|
345
|
-
~eventName,
|
|
346
|
-
~msg="Cannot register a second handler with different options. Make sure all handlers for the same event use identical options (wildcard, where)",
|
|
347
|
-
~logger,
|
|
348
|
-
)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
registration->syncOnEventRegistrations(
|
|
267
|
+
registration->addOnEventRegistration(
|
|
352
268
|
~contractName,
|
|
353
269
|
~eventName,
|
|
354
|
-
~
|
|
355
|
-
~
|
|
356
|
-
~
|
|
270
|
+
~handler=Some(newHandler),
|
|
271
|
+
~contractRegister=None,
|
|
272
|
+
~eventOptions,
|
|
357
273
|
)
|
|
358
274
|
})
|
|
359
275
|
}
|
|
360
276
|
|
|
361
|
-
let setContractRegister = (
|
|
362
|
-
~contractName,
|
|
363
|
-
~eventName,
|
|
364
|
-
contractRegister,
|
|
365
|
-
~eventOptions,
|
|
366
|
-
~logger=Logging.getLogger(),
|
|
367
|
-
) => {
|
|
277
|
+
let setContractRegister = (~contractName, ~eventName, contractRegister, ~eventOptions) => {
|
|
368
278
|
withRegistration(registration => {
|
|
369
|
-
let t = get(~contractName, ~eventName)
|
|
370
279
|
let newContractRegister =
|
|
371
280
|
contractRegister->(
|
|
372
281
|
Utils.magic: Internal.genericContractRegister<
|
|
373
282
|
Internal.genericContractRegisterArgs<'event, 'context>,
|
|
374
283
|
> => Internal.contractRegister
|
|
375
284
|
)
|
|
376
|
-
let
|
|
285
|
+
let eventOptions =
|
|
377
286
|
eventOptions->Option.map(v =>
|
|
378
287
|
v->(Utils.magic: Internal.eventOptions<'where> => Internal.eventOptions<JSON.t>)
|
|
379
288
|
)
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
289
|
+
registration->addOnEventRegistration(
|
|
290
|
+
~contractName,
|
|
291
|
+
~eventName,
|
|
292
|
+
~handler=None,
|
|
293
|
+
~contractRegister=Some(newContractRegister),
|
|
294
|
+
~eventOptions,
|
|
295
|
+
)
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Raw onEvent registrations stored for a chain (empty if the chain has none).
|
|
300
|
+
let storedOnEventRegistrations = (r: activeRegistration, ~chainId: int): array<
|
|
301
|
+
Internal.onEventRegistration,
|
|
302
|
+
> =>
|
|
303
|
+
switch r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(chainId->Int.toString) {
|
|
304
|
+
| Some(chainRegs) => chainRegs.onEventRegistrations
|
|
305
|
+
| None => []
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// True when any registration for the event is a wildcard. Used by simulate to
|
|
309
|
+
// decide whether a src address needs deriving.
|
|
310
|
+
let isWildcard = (~contractName, ~eventName) =>
|
|
311
|
+
switch getActiveRegistration() {
|
|
312
|
+
| Some(r) =>
|
|
313
|
+
r.registrationsByChainId
|
|
314
|
+
->Dict.valuesToArray
|
|
315
|
+
->Array.some(chainRegs =>
|
|
316
|
+
chainRegs.onEventRegistrations->Array.some(reg =>
|
|
317
|
+
reg.eventConfig.contractName === contractName &&
|
|
318
|
+
reg.eventConfig.name === eventName &&
|
|
319
|
+
reg.isWildcard
|
|
391
320
|
)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
321
|
+
)
|
|
322
|
+
| None => false
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Every registration for one event on a chain, so simulate fans a simulated
|
|
326
|
+
// event out to each the way real routing does. Falls back to a bare
|
|
327
|
+
// registration when the event has no handler/contractRegister, so a simulated
|
|
328
|
+
// item still produces an item to run.
|
|
329
|
+
let getSimulateOnEventRegistrations = (
|
|
330
|
+
~config: Config.t,
|
|
331
|
+
~chainId: int,
|
|
332
|
+
~eventConfig: Internal.eventConfig,
|
|
333
|
+
): array<Internal.onEventRegistration> => {
|
|
334
|
+
let stored = switch getActiveRegistration() {
|
|
335
|
+
| Some(r) => r->storedOnEventRegistrations(~chainId)
|
|
336
|
+
| None => []
|
|
337
|
+
}
|
|
338
|
+
let matching =
|
|
339
|
+
mergeRegistrations(stored, ~config)->Array.filter(reg =>
|
|
340
|
+
reg.eventConfig.contractName === eventConfig.contractName &&
|
|
341
|
+
reg.eventConfig.name === eventConfig.name
|
|
342
|
+
)
|
|
343
|
+
if matching->Utils.Array.notEmpty {
|
|
344
|
+
matching
|
|
345
|
+
} else {
|
|
346
|
+
[
|
|
347
|
+
buildOnEventRegistrationWith(
|
|
348
|
+
~config,
|
|
349
|
+
~chainId,
|
|
350
|
+
~eventConfig,
|
|
351
|
+
~isWildcard=false,
|
|
352
|
+
~handler=None,
|
|
353
|
+
~contractRegister=None,
|
|
354
|
+
~where=None,
|
|
355
|
+
),
|
|
356
|
+
]
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
let finishRegistration = (~config: Config.t): registrationsByChainId => {
|
|
361
|
+
switch getActiveRegistration() {
|
|
362
|
+
| Some(r) => {
|
|
363
|
+
r.finished = true
|
|
364
|
+
let notRegisteredEventsByContract: dict<Utils.Set.t<string>> = Dict.make()
|
|
365
|
+
let registrationsByChainId: registrationsByChainId = Dict.make()
|
|
366
|
+
config.chainMap
|
|
367
|
+
->ChainMap.values
|
|
368
|
+
->Array.forEach(chainConfig => {
|
|
369
|
+
let chainId = chainConfig.id
|
|
370
|
+
let key = chainId->Int.toString
|
|
371
|
+
|
|
372
|
+
let builtRegs = mergeRegistrations(r->storedOnEventRegistrations(~chainId), ~config)
|
|
373
|
+
let registeredKeys = Utils.Set.make()
|
|
374
|
+
builtRegs->Array.forEach(reg =>
|
|
375
|
+
registeredKeys
|
|
376
|
+
->Utils.Set.add(
|
|
377
|
+
getKey(~contractName=reg.eventConfig.contractName, ~eventName=reg.eventConfig.name),
|
|
378
|
+
)
|
|
379
|
+
->ignore
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
// Events with no handler/contractRegister aren't fetched or dispatched
|
|
383
|
+
// unless raw events are enabled, in which case a bare registration is
|
|
384
|
+
// added to fetch them. Otherwise they're reported once below. Keyed on
|
|
385
|
+
// the resolved registrations (before the where-empty drop) so a
|
|
386
|
+
// `where: false` event still counts as registered — its handler opted
|
|
387
|
+
// out of this chain, so it gets no raw-event registration either.
|
|
388
|
+
let rawEventRegs = []
|
|
389
|
+
chainConfig.contracts->Array.forEach(contract => {
|
|
390
|
+
contract.events->Array.forEach(
|
|
391
|
+
eventConfig => {
|
|
392
|
+
if (
|
|
393
|
+
!(
|
|
394
|
+
registeredKeys->Utils.Set.has(
|
|
395
|
+
getKey(~contractName=contract.name, ~eventName=eventConfig.name),
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
) {
|
|
399
|
+
if config.enableRawEvents {
|
|
400
|
+
rawEventRegs
|
|
401
|
+
->Array.push(
|
|
402
|
+
buildOnEventRegistrationWith(
|
|
403
|
+
~config,
|
|
404
|
+
~chainId,
|
|
405
|
+
~eventConfig,
|
|
406
|
+
~isWildcard=false,
|
|
407
|
+
~handler=None,
|
|
408
|
+
~contractRegister=None,
|
|
409
|
+
~where=None,
|
|
410
|
+
~startBlock=?contract.startBlock,
|
|
411
|
+
),
|
|
412
|
+
)
|
|
413
|
+
->ignore
|
|
414
|
+
} else {
|
|
415
|
+
let eventNames = switch notRegisteredEventsByContract->Utils.Dict.dangerouslyGetNonOption(
|
|
416
|
+
contract.name,
|
|
417
|
+
) {
|
|
418
|
+
| Some(set) => set
|
|
419
|
+
| None => {
|
|
420
|
+
let set = Utils.Set.make()
|
|
421
|
+
notRegisteredEventsByContract->Dict.set(contract.name, set)
|
|
422
|
+
set
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
eventNames->Utils.Set.add(eventConfig.name)->ignore
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
)
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
// Drop registrations whose `where` opts out of this chain, then assign
|
|
433
|
+
// each survivor its chain-scoped index by position.
|
|
434
|
+
let onEventRegistrations: array<Internal.onEventRegistration> = []
|
|
435
|
+
builtRegs
|
|
436
|
+
->Array.concat(rawEventRegs)
|
|
437
|
+
->Array.forEach(reg => {
|
|
438
|
+
if !isDroppedByWhere(~config, reg) {
|
|
439
|
+
onEventRegistrations
|
|
440
|
+
->Array.push({...reg, index: onEventRegistrations->Array.length})
|
|
441
|
+
->ignore
|
|
442
|
+
}
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
registrationsByChainId->Dict.set(
|
|
446
|
+
key,
|
|
401
447
|
{
|
|
402
|
-
|
|
403
|
-
|
|
448
|
+
onEventRegistrations,
|
|
449
|
+
// Copy so a consumer appending to the output (e.g. simulate source
|
|
450
|
+
// registration) never mutates the persistent store.
|
|
451
|
+
onBlockRegistrations: switch r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(
|
|
452
|
+
key,
|
|
453
|
+
) {
|
|
454
|
+
| Some(chainRegs) => chainRegs.onBlockRegistrations->Array.copy
|
|
455
|
+
| None => []
|
|
456
|
+
},
|
|
404
457
|
},
|
|
405
458
|
)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
// Reported once for the whole indexer (a shared contract on multiple
|
|
462
|
+
// chains would otherwise repeat the same message per chain).
|
|
463
|
+
let notRegisteredEntries = notRegisteredEventsByContract->Dict.toArray
|
|
464
|
+
if notRegisteredEntries->Utils.Array.notEmpty {
|
|
465
|
+
let groups =
|
|
466
|
+
notRegisteredEntries
|
|
467
|
+
->Array.map(((contractName, eventNames)) =>
|
|
468
|
+
`${contractName} (${eventNames->Utils.Set.toArray->Array.joinUnsafe(", ")})`
|
|
469
|
+
)
|
|
470
|
+
->Array.joinUnsafe(", ")
|
|
471
|
+
Logging.getLogger()->Logging.childInfo(
|
|
472
|
+
`Events without a handler, skipped for indexing: ${groups}`,
|
|
412
473
|
)
|
|
413
474
|
}
|
|
475
|
+
|
|
476
|
+
registrationsByChainId
|
|
414
477
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
~where=incomingEventOptions->Option.flatMap(v => v.where),
|
|
419
|
-
~duplicateMsg="Cannot register a second contractRegister with different options. Make sure all handlers for the same event use identical options (wildcard, where)",
|
|
420
|
-
~logger,
|
|
478
|
+
| None =>
|
|
479
|
+
JsError.throwWithMessage(
|
|
480
|
+
"The indexer has not started registering handlers, so can't finish it.",
|
|
421
481
|
)
|
|
422
|
-
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
let isPendingRegistration = () => {
|
|
486
|
+
switch getActiveRegistration() {
|
|
487
|
+
| Some(r) => !r.finished
|
|
488
|
+
| None => false
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Early guard called from `indexer.onEvent` / `.contractRegister` / `.onBlock` /
|
|
493
|
+
// `.onSlot` so the user sees a method-specific error at the call site, instead
|
|
494
|
+
// of hitting the generic `withRegistration` throw deep inside `setHandler` etc.
|
|
495
|
+
let throwIfFinishedRegistration = (~methodName) => {
|
|
496
|
+
switch getActiveRegistration() {
|
|
497
|
+
| Some({finished: true}) =>
|
|
498
|
+
JsError.throwWithMessage(
|
|
499
|
+
`Cannot call \`indexer.${methodName}\` after the indexer has started. Make sure all handlers are registered at the top level of your handler module.`,
|
|
500
|
+
)
|
|
501
|
+
| _ => ()
|
|
502
|
+
}
|
|
423
503
|
}
|
|
424
504
|
|
|
425
505
|
// Shape of the user-returned `{_gte?, _lte?, _every?}` filter chunk after
|
|
@@ -557,12 +637,12 @@ let registerOnBlock = (
|
|
|
557
637
|
}
|
|
558
638
|
| None => ()
|
|
559
639
|
}
|
|
560
|
-
let
|
|
561
|
-
|
|
640
|
+
let chainRegs = registration->getChainRegistrations(~chainId)
|
|
641
|
+
chainRegs.onBlockRegistrations
|
|
562
642
|
->Array.push(
|
|
563
643
|
(
|
|
564
644
|
{
|
|
565
|
-
index:
|
|
645
|
+
index: chainRegs.onBlockRegistrations->Array.length,
|
|
566
646
|
name,
|
|
567
647
|
startBlock: range._gte,
|
|
568
648
|
endBlock: range._lte,
|
|
@@ -587,160 +667,3 @@ let registerOnBlock = (
|
|
|
587
667
|
}
|
|
588
668
|
})
|
|
589
669
|
}
|
|
590
|
-
|
|
591
|
-
let finishRegistration = (~config: Config.t): registrationsByChainId => {
|
|
592
|
-
switch getActiveRegistration() {
|
|
593
|
-
| Some(r) => {
|
|
594
|
-
r.finished = true
|
|
595
|
-
let notRegisteredEventsByContract: dict<Utils.Set.t<string>> = Dict.make()
|
|
596
|
-
let registrationsByChainId: registrationsByChainId = Dict.make()
|
|
597
|
-
config.chainMap
|
|
598
|
-
->ChainMap.values
|
|
599
|
-
->Array.forEach(chainConfig => {
|
|
600
|
-
let key = chainConfig.id->Int.toString
|
|
601
|
-
let pending = r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(key)
|
|
602
|
-
|
|
603
|
-
// We don't need the router itself, but only validation logic,
|
|
604
|
-
// since now event router is created for selection of events
|
|
605
|
-
// and validation doesn't work correctly in routers.
|
|
606
|
-
// Ideally to split it into two different parts.
|
|
607
|
-
let eventRouter = EventRouter.empty()
|
|
608
|
-
|
|
609
|
-
let onEventRegistrations: array<Internal.onEventRegistration> = []
|
|
610
|
-
|
|
611
|
-
chainConfig.contracts->Array.forEach(contract => {
|
|
612
|
-
let contractName = contract.name
|
|
613
|
-
|
|
614
|
-
contract.events->Array.forEach(
|
|
615
|
-
eventConfig => {
|
|
616
|
-
let eventName = eventConfig.name
|
|
617
|
-
let registration =
|
|
618
|
-
pending->Option.flatMap(
|
|
619
|
-
pending =>
|
|
620
|
-
pending.onEventRegistrations->Utils.Dict.dangerouslyGetNonOption(
|
|
621
|
-
getKey(~contractName, ~eventName),
|
|
622
|
-
),
|
|
623
|
-
)
|
|
624
|
-
|
|
625
|
-
// Should validate the events
|
|
626
|
-
eventRouter->EventRouter.addOrThrow(
|
|
627
|
-
eventConfig.id,
|
|
628
|
-
(),
|
|
629
|
-
~contractName,
|
|
630
|
-
~chain=ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id),
|
|
631
|
-
~eventName,
|
|
632
|
-
~isWildcard=switch registration {
|
|
633
|
-
| Some(registration) => registration.isWildcard
|
|
634
|
-
| None => isWildcard(~contractName, ~eventName)
|
|
635
|
-
},
|
|
636
|
-
)
|
|
637
|
-
|
|
638
|
-
let registration = switch registration {
|
|
639
|
-
| Some(_) as registration => registration
|
|
640
|
-
| None =>
|
|
641
|
-
// No entry in the incremental store, but the persistent dict
|
|
642
|
-
// may still hold a handler: handler modules are import-cached,
|
|
643
|
-
// so a repeated registration cycle in the same process (tests
|
|
644
|
-
// restarting the indexer) never re-runs the `indexer.onEvent`
|
|
645
|
-
// calls. Rebuild from the dict in that case. Events without a
|
|
646
|
-
// handler/contractRegister aren't fetched or dispatched
|
|
647
|
-
// (unless raw events are enabled).
|
|
648
|
-
if hasRegistration(~contractName, ~eventName) || config.enableRawEvents {
|
|
649
|
-
Some(
|
|
650
|
-
buildOnEventRegistration(
|
|
651
|
-
~config,
|
|
652
|
-
~chainId=chainConfig.id,
|
|
653
|
-
~eventConfig,
|
|
654
|
-
~startBlock=?contract.startBlock,
|
|
655
|
-
),
|
|
656
|
-
)
|
|
657
|
-
} else {
|
|
658
|
-
let eventNames = switch notRegisteredEventsByContract->Utils.Dict.dangerouslyGetNonOption(
|
|
659
|
-
contractName,
|
|
660
|
-
) {
|
|
661
|
-
| Some(set) => set
|
|
662
|
-
| None => {
|
|
663
|
-
let set = Utils.Set.make()
|
|
664
|
-
notRegisteredEventsByContract->Dict.set(contractName, set)
|
|
665
|
-
set
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
eventNames->Utils.Set.add(eventName)->ignore
|
|
669
|
-
None
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
switch registration {
|
|
674
|
-
| Some(registration) =>
|
|
675
|
-
// A `where` that resolved to no topic selections (`false` for
|
|
676
|
-
// this chain) drops the chain's registration entirely — the
|
|
677
|
-
// event should never be fetched here.
|
|
678
|
-
let isDroppedByWhere =
|
|
679
|
-
config.ecosystem.name === Evm &&
|
|
680
|
-
(registration->getResolvedWhere).topicSelections->Utils.Array.isEmpty
|
|
681
|
-
if !isDroppedByWhere {
|
|
682
|
-
onEventRegistrations
|
|
683
|
-
->Array.push({...registration, index: onEventRegistrations->Array.length})
|
|
684
|
-
->ignore
|
|
685
|
-
}
|
|
686
|
-
| None => ()
|
|
687
|
-
}
|
|
688
|
-
},
|
|
689
|
-
)
|
|
690
|
-
})
|
|
691
|
-
|
|
692
|
-
registrationsByChainId->Dict.set(
|
|
693
|
-
key,
|
|
694
|
-
{
|
|
695
|
-
onEventRegistrations,
|
|
696
|
-
onBlockRegistrations: switch pending {
|
|
697
|
-
| Some(pending) => pending.onBlockRegistrations
|
|
698
|
-
| None => []
|
|
699
|
-
},
|
|
700
|
-
},
|
|
701
|
-
)
|
|
702
|
-
})
|
|
703
|
-
|
|
704
|
-
// Reported once for the whole indexer (a shared contract on multiple
|
|
705
|
-
// chains would otherwise repeat the same message per chain).
|
|
706
|
-
let notRegisteredEntries = notRegisteredEventsByContract->Dict.toArray
|
|
707
|
-
if notRegisteredEntries->Utils.Array.notEmpty {
|
|
708
|
-
let groups =
|
|
709
|
-
notRegisteredEntries
|
|
710
|
-
->Array.map(((contractName, eventNames)) =>
|
|
711
|
-
`${contractName} (${eventNames->Utils.Set.toArray->Array.joinUnsafe(", ")})`
|
|
712
|
-
)
|
|
713
|
-
->Array.joinUnsafe(", ")
|
|
714
|
-
Logging.getLogger()->Logging.childInfo(
|
|
715
|
-
`Events without a handler, skipped for indexing: ${groups}`,
|
|
716
|
-
)
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
registrationsByChainId
|
|
720
|
-
}
|
|
721
|
-
| None =>
|
|
722
|
-
JsError.throwWithMessage(
|
|
723
|
-
"The indexer has not started registering handlers, so can't finish it.",
|
|
724
|
-
)
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
let isPendingRegistration = () => {
|
|
729
|
-
switch getActiveRegistration() {
|
|
730
|
-
| Some(r) => !r.finished
|
|
731
|
-
| None => false
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// Early guard called from `indexer.onEvent` / `.contractRegister` / `.onBlock` /
|
|
736
|
-
// `.onSlot` so the user sees a method-specific error at the call site, instead
|
|
737
|
-
// of hitting the generic `withRegistration` throw deep inside `setHandler` etc.
|
|
738
|
-
let throwIfFinishedRegistration = (~methodName) => {
|
|
739
|
-
switch getActiveRegistration() {
|
|
740
|
-
| Some({finished: true}) =>
|
|
741
|
-
JsError.throwWithMessage(
|
|
742
|
-
`Cannot call \`indexer.${methodName}\` after the indexer has started. Make sure all handlers are registered at the top level of your handler module.`,
|
|
743
|
-
)
|
|
744
|
-
| _ => ()
|
|
745
|
-
}
|
|
746
|
-
}
|