envio 2.28.0 → 2.29.0-alpha.1
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 +3 -0
- package/package.json +5 -5
- package/src/Batch.res +4 -14
- package/src/Batch.res.js +3 -14
- package/src/Envio.gen.ts +10 -0
- package/src/Envio.res +21 -0
- package/src/EventRegister.res +150 -31
- package/src/EventRegister.res.js +120 -33
- package/src/EventRegister.resi +11 -0
- package/src/EventUtils.res +52 -58
- package/src/EventUtils.res.js +15 -51
- package/src/FetchState.res +68 -56
- package/src/FetchState.res.js +66 -53
- package/src/Internal.gen.ts +2 -0
- package/src/Internal.res +44 -3
- package/src/InternalConfig.res +18 -0
- package/src/Logging.res +36 -24
- package/src/Logging.res.js +35 -21
- package/src/sources/HyperFuelSource.res +506 -0
- package/src/sources/HyperFuelSource.res.js +451 -0
- package/src/sources/HyperSync.res +1 -1
- package/src/sources/HyperSync.resi +1 -1
- package/src/sources/HyperSyncSource.res +569 -0
- package/src/sources/HyperSyncSource.res.js +413 -0
- package/src/sources/RpcSource.res +18 -20
- package/src/sources/RpcSource.res.js +1 -0
- package/src/sources/Source.res +1 -1
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
open Source
|
|
2
|
+
open Belt
|
|
3
|
+
|
|
4
|
+
type selectionConfig = {
|
|
5
|
+
getLogSelectionOrThrow: (
|
|
6
|
+
~addressesByContractName: dict<array<Address.t>>,
|
|
7
|
+
) => array<LogSelection.t>,
|
|
8
|
+
fieldSelection: HyperSyncClient.QueryTypes.fieldSelection,
|
|
9
|
+
nonOptionalBlockFieldNames: array<string>,
|
|
10
|
+
nonOptionalTransactionFieldNames: array<string>,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let getSelectionConfig = (selection: FetchState.selection, ~chain) => {
|
|
14
|
+
let nonOptionalBlockFieldNames = Utils.Set.make()
|
|
15
|
+
let nonOptionalTransactionFieldNames = Utils.Set.make()
|
|
16
|
+
let capitalizedBlockFields = Utils.Set.make()
|
|
17
|
+
let capitalizedTransactionFields = Utils.Set.make()
|
|
18
|
+
|
|
19
|
+
let staticTopicSelectionsByContract = Js.Dict.empty()
|
|
20
|
+
let dynamicEventFiltersByContract = Js.Dict.empty()
|
|
21
|
+
let dynamicWildcardEventFiltersByContract = Js.Dict.empty()
|
|
22
|
+
let noAddressesTopicSelections = []
|
|
23
|
+
let contractNames = Utils.Set.make()
|
|
24
|
+
|
|
25
|
+
selection.eventConfigs
|
|
26
|
+
->(Utils.magic: array<Internal.eventConfig> => array<Internal.evmEventConfig>)
|
|
27
|
+
->Array.forEach(({
|
|
28
|
+
dependsOnAddresses,
|
|
29
|
+
contractName,
|
|
30
|
+
getEventFiltersOrThrow,
|
|
31
|
+
blockSchema,
|
|
32
|
+
transactionSchema,
|
|
33
|
+
isWildcard,
|
|
34
|
+
}) => {
|
|
35
|
+
nonOptionalBlockFieldNames->Utils.Set.addMany(
|
|
36
|
+
blockSchema->Utils.Schema.getNonOptionalFieldNames,
|
|
37
|
+
)
|
|
38
|
+
nonOptionalTransactionFieldNames->Utils.Set.addMany(
|
|
39
|
+
transactionSchema->Utils.Schema.getNonOptionalFieldNames,
|
|
40
|
+
)
|
|
41
|
+
capitalizedBlockFields->Utils.Set.addMany(blockSchema->Utils.Schema.getCapitalizedFieldNames)
|
|
42
|
+
capitalizedTransactionFields->Utils.Set.addMany(
|
|
43
|
+
transactionSchema->Utils.Schema.getCapitalizedFieldNames,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
let eventFilters = getEventFiltersOrThrow(chain)
|
|
47
|
+
if dependsOnAddresses {
|
|
48
|
+
let _ = contractNames->Utils.Set.add(contractName)
|
|
49
|
+
switch eventFilters {
|
|
50
|
+
| Static(topicSelections) =>
|
|
51
|
+
staticTopicSelectionsByContract->Utils.Dict.pushMany(contractName, topicSelections)
|
|
52
|
+
| Dynamic(fn) =>
|
|
53
|
+
(
|
|
54
|
+
isWildcard ? dynamicWildcardEventFiltersByContract : dynamicEventFiltersByContract
|
|
55
|
+
)->Utils.Dict.push(contractName, fn)
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
noAddressesTopicSelections
|
|
59
|
+
->Js.Array2.pushMany(
|
|
60
|
+
switch eventFilters {
|
|
61
|
+
| Static(s) => s
|
|
62
|
+
| Dynamic(fn) => fn([])
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
->ignore
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
let fieldSelection: HyperSyncClient.QueryTypes.fieldSelection = {
|
|
70
|
+
log: [Address, Data, LogIndex, Topic0, Topic1, Topic2, Topic3],
|
|
71
|
+
block: capitalizedBlockFields
|
|
72
|
+
->Utils.Set.toArray
|
|
73
|
+
->(Utils.magic: array<string> => array<HyperSyncClient.QueryTypes.blockField>),
|
|
74
|
+
transaction: capitalizedTransactionFields
|
|
75
|
+
->Utils.Set.toArray
|
|
76
|
+
->(Utils.magic: array<string> => array<HyperSyncClient.QueryTypes.transactionField>),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let noAddressesLogSelection = LogSelection.make(
|
|
80
|
+
~addresses=[],
|
|
81
|
+
~topicSelections=noAddressesTopicSelections,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
let getLogSelectionOrThrow = (~addressesByContractName): array<LogSelection.t> => {
|
|
85
|
+
let logSelections = []
|
|
86
|
+
if noAddressesLogSelection.topicSelections->Utils.Array.isEmpty->not {
|
|
87
|
+
logSelections->Array.push(noAddressesLogSelection)
|
|
88
|
+
}
|
|
89
|
+
contractNames->Utils.Set.forEach(contractName => {
|
|
90
|
+
switch addressesByContractName->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
91
|
+
| None
|
|
92
|
+
| Some([]) => ()
|
|
93
|
+
| Some(addresses) =>
|
|
94
|
+
switch staticTopicSelectionsByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
95
|
+
| None => ()
|
|
96
|
+
| Some(topicSelections) =>
|
|
97
|
+
logSelections->Array.push(LogSelection.make(~addresses, ~topicSelections))
|
|
98
|
+
}
|
|
99
|
+
switch dynamicEventFiltersByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
100
|
+
| None => ()
|
|
101
|
+
| Some(fns) =>
|
|
102
|
+
logSelections->Array.push(
|
|
103
|
+
LogSelection.make(
|
|
104
|
+
~addresses,
|
|
105
|
+
~topicSelections=fns->Array.flatMapU(fn => fn(addresses)),
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
switch dynamicWildcardEventFiltersByContract->Utils.Dict.dangerouslyGetNonOption(
|
|
110
|
+
contractName,
|
|
111
|
+
) {
|
|
112
|
+
| None => ()
|
|
113
|
+
| Some(fns) =>
|
|
114
|
+
logSelections->Array.push(
|
|
115
|
+
LogSelection.make(
|
|
116
|
+
~addresses=[],
|
|
117
|
+
~topicSelections=fns->Array.flatMapU(fn => fn(addresses)),
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
logSelections
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
getLogSelectionOrThrow,
|
|
128
|
+
fieldSelection,
|
|
129
|
+
nonOptionalBlockFieldNames: nonOptionalBlockFieldNames->Utils.Set.toArray,
|
|
130
|
+
nonOptionalTransactionFieldNames: nonOptionalTransactionFieldNames->Utils.Set.toArray,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let memoGetSelectionConfig = (~chain) => {
|
|
135
|
+
let cache = Utils.WeakMap.make()
|
|
136
|
+
selection =>
|
|
137
|
+
switch cache->Utils.WeakMap.get(selection) {
|
|
138
|
+
| Some(c) => c
|
|
139
|
+
| None => {
|
|
140
|
+
let c = selection->getSelectionConfig(~chain)
|
|
141
|
+
let _ = cache->Utils.WeakMap.set(selection, c)
|
|
142
|
+
c
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
type options = {
|
|
148
|
+
contracts: array<Internal.evmContractConfig>,
|
|
149
|
+
chain: ChainMap.Chain.t,
|
|
150
|
+
endpointUrl: string,
|
|
151
|
+
allEventSignatures: array<string>,
|
|
152
|
+
shouldUseHypersyncClientDecoder: bool,
|
|
153
|
+
eventRouter: EventRouter.t<Internal.evmEventConfig>,
|
|
154
|
+
apiToken: option<string>,
|
|
155
|
+
clientMaxRetries: int,
|
|
156
|
+
clientTimeoutMillis: int,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let make = (
|
|
160
|
+
{
|
|
161
|
+
contracts,
|
|
162
|
+
chain,
|
|
163
|
+
endpointUrl,
|
|
164
|
+
allEventSignatures,
|
|
165
|
+
shouldUseHypersyncClientDecoder,
|
|
166
|
+
eventRouter,
|
|
167
|
+
apiToken,
|
|
168
|
+
clientMaxRetries,
|
|
169
|
+
clientTimeoutMillis,
|
|
170
|
+
}: options,
|
|
171
|
+
): t => {
|
|
172
|
+
let name = "HyperSync"
|
|
173
|
+
|
|
174
|
+
let getSelectionConfig = memoGetSelectionConfig(~chain)
|
|
175
|
+
|
|
176
|
+
let apiToken = apiToken->Belt.Option.getWithDefault("3dc856dd-b0ea-494f-b27e-017b8b6b7e07")
|
|
177
|
+
|
|
178
|
+
let client = HyperSyncClient.make(
|
|
179
|
+
~url=endpointUrl,
|
|
180
|
+
~apiToken,
|
|
181
|
+
~maxNumRetries=clientMaxRetries,
|
|
182
|
+
~httpReqTimeoutMillis=clientTimeoutMillis,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
let hscDecoder: ref<option<HyperSyncClient.Decoder.t>> = ref(None)
|
|
186
|
+
let getHscDecoder = () => {
|
|
187
|
+
switch hscDecoder.contents {
|
|
188
|
+
| Some(decoder) => decoder
|
|
189
|
+
| None =>
|
|
190
|
+
switch HyperSyncClient.Decoder.fromSignatures(allEventSignatures) {
|
|
191
|
+
| exception exn =>
|
|
192
|
+
exn->ErrorHandling.mkLogAndRaise(
|
|
193
|
+
~msg="Failed to instantiate a decoder from hypersync client, please double check your ABI or try using 'event_decoder: viem' config option",
|
|
194
|
+
)
|
|
195
|
+
| decoder =>
|
|
196
|
+
decoder.enableChecksummedAddresses()
|
|
197
|
+
decoder
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
exception UndefinedValue
|
|
203
|
+
|
|
204
|
+
let makeEventBatchQueueItem = (
|
|
205
|
+
item: HyperSync.logsQueryPageItem,
|
|
206
|
+
~params: Internal.eventParams,
|
|
207
|
+
~eventConfig: Internal.evmEventConfig,
|
|
208
|
+
): Internal.item => {
|
|
209
|
+
let {block, log, transaction} = item
|
|
210
|
+
let chainId = chain->ChainMap.Chain.toChainId
|
|
211
|
+
|
|
212
|
+
Internal.Event({
|
|
213
|
+
eventConfig: (eventConfig :> Internal.eventConfig),
|
|
214
|
+
timestamp: block.timestamp->Belt.Option.getUnsafe,
|
|
215
|
+
chain,
|
|
216
|
+
blockNumber: block.number->Belt.Option.getUnsafe,
|
|
217
|
+
logIndex: log.logIndex,
|
|
218
|
+
event: {
|
|
219
|
+
chainId,
|
|
220
|
+
params,
|
|
221
|
+
transaction,
|
|
222
|
+
block: block->(Utils.magic: HyperSyncClient.ResponseTypes.block => Internal.eventBlock),
|
|
223
|
+
srcAddress: log.address,
|
|
224
|
+
logIndex: log.logIndex,
|
|
225
|
+
}->Internal.fromGenericEvent,
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let contractNameAbiMapping = Js.Dict.empty()
|
|
230
|
+
contracts->Belt.Array.forEach(contract => {
|
|
231
|
+
contractNameAbiMapping->Js.Dict.set(contract.name, contract.abi)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
let getItemsOrThrow = async (
|
|
235
|
+
~fromBlock,
|
|
236
|
+
~toBlock,
|
|
237
|
+
~addressesByContractName,
|
|
238
|
+
~indexingContracts,
|
|
239
|
+
~currentBlockHeight,
|
|
240
|
+
~partitionId as _,
|
|
241
|
+
~selection,
|
|
242
|
+
~retry,
|
|
243
|
+
~logger,
|
|
244
|
+
) => {
|
|
245
|
+
let mkLogAndRaise = ErrorHandling.mkLogAndRaise(~logger, ...)
|
|
246
|
+
let totalTimeRef = Hrtime.makeTimer()
|
|
247
|
+
|
|
248
|
+
let selectionConfig = selection->getSelectionConfig
|
|
249
|
+
|
|
250
|
+
let logSelections = try selectionConfig.getLogSelectionOrThrow(~addressesByContractName) catch {
|
|
251
|
+
| exn =>
|
|
252
|
+
exn->ErrorHandling.mkLogAndRaise(~logger, ~msg="Failed getting log selection for the query")
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let startFetchingBatchTimeRef = Hrtime.makeTimer()
|
|
256
|
+
|
|
257
|
+
//fetch batch
|
|
258
|
+
let pageUnsafe = try await HyperSync.GetLogs.query(
|
|
259
|
+
~client,
|
|
260
|
+
~fromBlock,
|
|
261
|
+
~toBlock,
|
|
262
|
+
~logSelections,
|
|
263
|
+
~fieldSelection=selectionConfig.fieldSelection,
|
|
264
|
+
~nonOptionalBlockFieldNames=selectionConfig.nonOptionalBlockFieldNames,
|
|
265
|
+
~nonOptionalTransactionFieldNames=selectionConfig.nonOptionalTransactionFieldNames,
|
|
266
|
+
) catch {
|
|
267
|
+
| HyperSync.GetLogs.Error(error) =>
|
|
268
|
+
raise(
|
|
269
|
+
Source.GetItemsError(
|
|
270
|
+
Source.FailedGettingItems({
|
|
271
|
+
exn: %raw(`null`),
|
|
272
|
+
attemptedToBlock: toBlock->Option.getWithDefault(currentBlockHeight),
|
|
273
|
+
retry: switch error {
|
|
274
|
+
| WrongInstance =>
|
|
275
|
+
let backoffMillis = switch retry {
|
|
276
|
+
| 0 => 100
|
|
277
|
+
| _ => 500 * retry
|
|
278
|
+
}
|
|
279
|
+
WithBackoff({
|
|
280
|
+
message: `Block #${fromBlock->Int.toString} not found in HyperSync. HyperSync has multiple instances and it's possible that they drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${backoffMillis->Int.toString}ms.`,
|
|
281
|
+
backoffMillis,
|
|
282
|
+
})
|
|
283
|
+
| UnexpectedMissingParams({missingParams}) =>
|
|
284
|
+
WithBackoff({
|
|
285
|
+
message: `Received page response with invalid data. Attempt a retry. Missing params: ${missingParams->Js.Array2.joinWith(
|
|
286
|
+
",",
|
|
287
|
+
)}`,
|
|
288
|
+
backoffMillis: switch retry {
|
|
289
|
+
| 0 => 1000
|
|
290
|
+
| _ => 4000 * retry
|
|
291
|
+
},
|
|
292
|
+
})
|
|
293
|
+
},
|
|
294
|
+
}),
|
|
295
|
+
),
|
|
296
|
+
)
|
|
297
|
+
| exn =>
|
|
298
|
+
raise(
|
|
299
|
+
Source.GetItemsError(
|
|
300
|
+
Source.FailedGettingItems({
|
|
301
|
+
exn,
|
|
302
|
+
attemptedToBlock: toBlock->Option.getWithDefault(currentBlockHeight),
|
|
303
|
+
retry: WithBackoff({
|
|
304
|
+
message: `Unexpected issue while fetching events from HyperSync client. Attempt a retry.`,
|
|
305
|
+
backoffMillis: switch retry {
|
|
306
|
+
| 0 => 500
|
|
307
|
+
| _ => 1000 * retry
|
|
308
|
+
},
|
|
309
|
+
}),
|
|
310
|
+
}),
|
|
311
|
+
),
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let pageFetchTime =
|
|
316
|
+
startFetchingBatchTimeRef->Hrtime.timeSince->Hrtime.toMillis->Hrtime.intFromMillis
|
|
317
|
+
|
|
318
|
+
//set height and next from block
|
|
319
|
+
let currentBlockHeight = pageUnsafe.archiveHeight
|
|
320
|
+
|
|
321
|
+
//The heighest (biggest) blocknumber that was accounted for in
|
|
322
|
+
//Our query. Not necessarily the blocknumber of the last log returned
|
|
323
|
+
//In the query
|
|
324
|
+
let heighestBlockQueried = pageUnsafe.nextBlock - 1
|
|
325
|
+
|
|
326
|
+
let lastBlockQueriedPromise = switch pageUnsafe.rollbackGuard {
|
|
327
|
+
//In the case a rollbackGuard is returned (this only happens at the head for unconfirmed blocks)
|
|
328
|
+
//use these values
|
|
329
|
+
| Some({blockNumber, timestamp, hash}) =>
|
|
330
|
+
(
|
|
331
|
+
{
|
|
332
|
+
blockNumber,
|
|
333
|
+
blockTimestamp: timestamp,
|
|
334
|
+
blockHash: hash,
|
|
335
|
+
}: ReorgDetection.blockDataWithTimestamp
|
|
336
|
+
)->Promise.resolve
|
|
337
|
+
| None =>
|
|
338
|
+
//The optional block and timestamp of the last item returned by the query
|
|
339
|
+
//(Optional in the case that there are no logs returned in the query)
|
|
340
|
+
switch pageUnsafe.items->Belt.Array.get(pageUnsafe.items->Belt.Array.length - 1) {
|
|
341
|
+
| Some({block}) if block.number->Belt.Option.getUnsafe == heighestBlockQueried =>
|
|
342
|
+
//If the last log item in the current page is equal to the
|
|
343
|
+
//heighest block acounted for in the query. Simply return this
|
|
344
|
+
//value without making an extra query
|
|
345
|
+
|
|
346
|
+
(
|
|
347
|
+
{
|
|
348
|
+
blockNumber: block.number->Belt.Option.getUnsafe,
|
|
349
|
+
blockTimestamp: block.timestamp->Belt.Option.getUnsafe,
|
|
350
|
+
blockHash: block.hash->Belt.Option.getUnsafe,
|
|
351
|
+
}: ReorgDetection.blockDataWithTimestamp
|
|
352
|
+
)->Promise.resolve
|
|
353
|
+
//If it does not match it means that there were no matching logs in the last
|
|
354
|
+
//block so we should fetch the block data
|
|
355
|
+
| Some(_)
|
|
356
|
+
| None =>
|
|
357
|
+
//If there were no logs at all in the current page query then fetch the
|
|
358
|
+
//timestamp of the heighest block accounted for
|
|
359
|
+
HyperSync.queryBlockData(
|
|
360
|
+
~serverUrl=endpointUrl,
|
|
361
|
+
~apiToken,
|
|
362
|
+
~blockNumber=heighestBlockQueried,
|
|
363
|
+
~logger,
|
|
364
|
+
)->Promise.thenResolve(res =>
|
|
365
|
+
switch res {
|
|
366
|
+
| Ok(Some(blockData)) => blockData
|
|
367
|
+
| Ok(None) =>
|
|
368
|
+
mkLogAndRaise(
|
|
369
|
+
Not_found,
|
|
370
|
+
~msg=`Failure, blockData for block ${heighestBlockQueried->Int.toString} unexpectedly returned None`,
|
|
371
|
+
)
|
|
372
|
+
| Error(e) =>
|
|
373
|
+
HyperSync.queryErrorToMsq(e)
|
|
374
|
+
->Obj.magic
|
|
375
|
+
->mkLogAndRaise(
|
|
376
|
+
~msg=`Failed to query blockData for block ${heighestBlockQueried->Int.toString}`,
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
let parsingTimeRef = Hrtime.makeTimer()
|
|
384
|
+
|
|
385
|
+
//Parse page items into queue items
|
|
386
|
+
let parsedQueueItems = []
|
|
387
|
+
|
|
388
|
+
let handleDecodeFailure = (
|
|
389
|
+
~eventConfig: Internal.evmEventConfig,
|
|
390
|
+
~decoder,
|
|
391
|
+
~logIndex,
|
|
392
|
+
~blockNumber,
|
|
393
|
+
~chainId,
|
|
394
|
+
~exn,
|
|
395
|
+
) => {
|
|
396
|
+
if !eventConfig.isWildcard {
|
|
397
|
+
//Wildcard events can be parsed as undefined if the number of topics
|
|
398
|
+
//don't match the event with the given topic0
|
|
399
|
+
//Non wildcard events should be expected to be parsed
|
|
400
|
+
let msg = `Event ${eventConfig.name} was unexpectedly parsed as undefined`
|
|
401
|
+
let logger = Logging.createChildFrom(
|
|
402
|
+
~logger,
|
|
403
|
+
~params={
|
|
404
|
+
"chainId": chainId,
|
|
405
|
+
"blockNumber": blockNumber,
|
|
406
|
+
"logIndex": logIndex,
|
|
407
|
+
"decoder": decoder,
|
|
408
|
+
},
|
|
409
|
+
)
|
|
410
|
+
exn->ErrorHandling.mkLogAndRaise(~msg, ~logger)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if shouldUseHypersyncClientDecoder {
|
|
414
|
+
//Currently there are still issues with decoder for some cases so
|
|
415
|
+
//this can only be activated with a flag
|
|
416
|
+
|
|
417
|
+
//Parse page items into queue items
|
|
418
|
+
let parsedEvents = switch await getHscDecoder().decodeEvents(pageUnsafe.events) {
|
|
419
|
+
| exception exn =>
|
|
420
|
+
exn->mkLogAndRaise(
|
|
421
|
+
~msg="Failed to parse events using hypersync client, please double check your ABI.",
|
|
422
|
+
)
|
|
423
|
+
| parsedEvents => parsedEvents
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
pageUnsafe.items->Belt.Array.forEachWithIndex((index, item) => {
|
|
427
|
+
let {block, log} = item
|
|
428
|
+
let chainId = chain->ChainMap.Chain.toChainId
|
|
429
|
+
let topic0 = log.topics->Js.Array2.unsafe_get(0)
|
|
430
|
+
let maybeEventConfig =
|
|
431
|
+
eventRouter->EventRouter.get(
|
|
432
|
+
~tag=EventRouter.getEvmEventId(
|
|
433
|
+
~sighash=topic0->EvmTypes.Hex.toString,
|
|
434
|
+
~topicCount=log.topics->Array.length,
|
|
435
|
+
),
|
|
436
|
+
~indexingContracts,
|
|
437
|
+
~contractAddress=log.address,
|
|
438
|
+
~blockNumber=block.number->Belt.Option.getUnsafe,
|
|
439
|
+
)
|
|
440
|
+
let maybeDecodedEvent = parsedEvents->Js.Array2.unsafe_get(index)
|
|
441
|
+
|
|
442
|
+
switch (maybeEventConfig, maybeDecodedEvent) {
|
|
443
|
+
| (Some(eventConfig), Value(decoded)) =>
|
|
444
|
+
parsedQueueItems
|
|
445
|
+
->Js.Array2.push(
|
|
446
|
+
makeEventBatchQueueItem(
|
|
447
|
+
item,
|
|
448
|
+
~params=decoded->eventConfig.convertHyperSyncEventArgs,
|
|
449
|
+
~eventConfig,
|
|
450
|
+
),
|
|
451
|
+
)
|
|
452
|
+
->ignore
|
|
453
|
+
| (Some(eventConfig), Null | Undefined) =>
|
|
454
|
+
handleDecodeFailure(
|
|
455
|
+
~eventConfig,
|
|
456
|
+
~decoder="hypersync-client",
|
|
457
|
+
~logIndex=log.logIndex,
|
|
458
|
+
~blockNumber=block.number->Belt.Option.getUnsafe,
|
|
459
|
+
~chainId,
|
|
460
|
+
~exn=UndefinedValue,
|
|
461
|
+
)
|
|
462
|
+
| (None, _) => () //ignore events that aren't registered
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
} else {
|
|
466
|
+
//Parse with viem -> slower than the HyperSyncClient
|
|
467
|
+
pageUnsafe.items->Array.forEach(item => {
|
|
468
|
+
let {block, log} = item
|
|
469
|
+
let chainId = chain->ChainMap.Chain.toChainId
|
|
470
|
+
let topic0 = log.topics->Js.Array2.unsafe_get(0)
|
|
471
|
+
|
|
472
|
+
switch eventRouter->EventRouter.get(
|
|
473
|
+
~tag=EventRouter.getEvmEventId(
|
|
474
|
+
~sighash=topic0->EvmTypes.Hex.toString,
|
|
475
|
+
~topicCount=log.topics->Array.length,
|
|
476
|
+
),
|
|
477
|
+
~indexingContracts,
|
|
478
|
+
~contractAddress=log.address,
|
|
479
|
+
~blockNumber=block.number->Belt.Option.getUnsafe,
|
|
480
|
+
) {
|
|
481
|
+
| Some(eventConfig) =>
|
|
482
|
+
switch contractNameAbiMapping->Viem.parseLogOrThrow(
|
|
483
|
+
~contractName=eventConfig.contractName,
|
|
484
|
+
~topics=log.topics,
|
|
485
|
+
~data=log.data,
|
|
486
|
+
) {
|
|
487
|
+
| exception exn =>
|
|
488
|
+
handleDecodeFailure(
|
|
489
|
+
~eventConfig,
|
|
490
|
+
~decoder="viem",
|
|
491
|
+
~logIndex=log.logIndex,
|
|
492
|
+
~blockNumber=block.number->Belt.Option.getUnsafe,
|
|
493
|
+
~chainId,
|
|
494
|
+
~exn,
|
|
495
|
+
)
|
|
496
|
+
| decodedEvent =>
|
|
497
|
+
parsedQueueItems
|
|
498
|
+
->Js.Array2.push(makeEventBatchQueueItem(item, ~params=decodedEvent.args, ~eventConfig))
|
|
499
|
+
->ignore
|
|
500
|
+
}
|
|
501
|
+
| None => () //Ignore events that aren't registered
|
|
502
|
+
}
|
|
503
|
+
})
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
let parsingTimeElapsed = parsingTimeRef->Hrtime.timeSince->Hrtime.toMillis->Hrtime.intFromMillis
|
|
507
|
+
|
|
508
|
+
let rangeLastBlock = await lastBlockQueriedPromise
|
|
509
|
+
|
|
510
|
+
let reorgGuard: ReorgDetection.reorgGuard = {
|
|
511
|
+
rangeLastBlock: rangeLastBlock->ReorgDetection.generalizeBlockDataWithTimestamp,
|
|
512
|
+
prevRangeLastBlock: pageUnsafe.rollbackGuard->Option.map(v => {
|
|
513
|
+
ReorgDetection.blockHash: v.firstParentHash,
|
|
514
|
+
blockNumber: v.firstBlockNumber - 1,
|
|
515
|
+
}),
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
let totalTimeElapsed = totalTimeRef->Hrtime.timeSince->Hrtime.toMillis->Hrtime.intFromMillis
|
|
519
|
+
|
|
520
|
+
let stats = {
|
|
521
|
+
totalTimeElapsed,
|
|
522
|
+
parsingTimeElapsed,
|
|
523
|
+
pageFetchTime,
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
{
|
|
527
|
+
latestFetchedBlockTimestamp: rangeLastBlock.blockTimestamp,
|
|
528
|
+
parsedQueueItems,
|
|
529
|
+
latestFetchedBlockNumber: rangeLastBlock.blockNumber,
|
|
530
|
+
stats,
|
|
531
|
+
currentBlockHeight,
|
|
532
|
+
reorgGuard,
|
|
533
|
+
fromBlockQueried: fromBlock,
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
let getBlockHashes = (~blockNumbers, ~logger) =>
|
|
538
|
+
HyperSync.queryBlockDataMulti(
|
|
539
|
+
~serverUrl=endpointUrl,
|
|
540
|
+
~apiToken,
|
|
541
|
+
~blockNumbers,
|
|
542
|
+
~logger,
|
|
543
|
+
)->Promise.thenResolve(HyperSync.mapExn)
|
|
544
|
+
|
|
545
|
+
let jsonApiClient = Rest.client(endpointUrl)
|
|
546
|
+
|
|
547
|
+
let malformedTokenMessage = `Your token is malformed. For more info: https://docs.envio.dev/docs/HyperSync/api-tokens.`
|
|
548
|
+
|
|
549
|
+
{
|
|
550
|
+
name,
|
|
551
|
+
sourceFor: Sync,
|
|
552
|
+
chain,
|
|
553
|
+
pollingInterval: 100,
|
|
554
|
+
poweredByHyperSync: true,
|
|
555
|
+
getBlockHashes,
|
|
556
|
+
getHeightOrThrow: async () =>
|
|
557
|
+
switch await HyperSyncJsonApi.heightRoute->Rest.fetch(apiToken, ~client=jsonApiClient) {
|
|
558
|
+
| Value(height) => height
|
|
559
|
+
| ErrorMessage(m) if m === malformedTokenMessage =>
|
|
560
|
+
Logging.error(`Your ENVIO_API_TOKEN is malformed. The indexer will not be able to fetch events. Update the token and restart the indexer using 'pnpm envio start'. For more info: https://docs.envio.dev/docs/HyperSync/api-tokens`)
|
|
561
|
+
// Don't want to retry if the token is malformed
|
|
562
|
+
// So just block forever
|
|
563
|
+
let _ = await Promise.make((_, _) => ())
|
|
564
|
+
0
|
|
565
|
+
| ErrorMessage(m) => Js.Exn.raiseError(m)
|
|
566
|
+
},
|
|
567
|
+
getItemsOrThrow,
|
|
568
|
+
}
|
|
569
|
+
}
|