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.
Files changed (61) hide show
  1. package/package.json +6 -6
  2. package/src/Api.res +1 -8
  3. package/src/Api.res.mjs +1 -5
  4. package/src/Bin.res +2 -2
  5. package/src/ChainState.res +6 -1
  6. package/src/ChainState.res.mjs +4 -3
  7. package/src/Config.res +38 -5
  8. package/src/Config.res.mjs +35 -6
  9. package/src/Core.res +65 -20
  10. package/src/Core.res.mjs +36 -6
  11. package/src/EnvioGlobal.res +0 -2
  12. package/src/EnvioGlobal.res.mjs +0 -1
  13. package/src/ExitOnCaughtUp.res +6 -2
  14. package/src/ExitOnCaughtUp.res.mjs +4 -0
  15. package/src/FetchState.res +3 -3
  16. package/src/HandlerRegister.res +357 -434
  17. package/src/HandlerRegister.res.mjs +202 -242
  18. package/src/HandlerRegister.resi +7 -15
  19. package/src/IndexerState.res +10 -0
  20. package/src/IndexerState.res.mjs +9 -3
  21. package/src/IndexerState.resi +3 -0
  22. package/src/Internal.res +10 -1
  23. package/src/Main.res.mjs +4 -4
  24. package/src/PgStorage.res +16 -1
  25. package/src/PgStorage.res.mjs +9 -1
  26. package/src/SimulateItems.res +61 -40
  27. package/src/SimulateItems.res.mjs +37 -21
  28. package/src/TestIndexer.res +453 -454
  29. package/src/TestIndexer.res.mjs +321 -344
  30. package/src/bindings/ClickHouse.res +68 -2
  31. package/src/bindings/ClickHouse.res.mjs +47 -2
  32. package/src/sources/EvmChain.res +1 -1
  33. package/src/sources/EvmChain.res.mjs +2 -2
  34. package/src/sources/FuelHyperSync.res +74 -0
  35. package/src/sources/FuelHyperSync.res.mjs +80 -0
  36. package/src/sources/FuelHyperSync.resi +22 -0
  37. package/src/sources/FuelHyperSyncClient.res +120 -0
  38. package/src/sources/FuelHyperSyncClient.res.mjs +71 -0
  39. package/src/sources/FuelHyperSyncSource.res +257 -0
  40. package/src/sources/FuelHyperSyncSource.res.mjs +252 -0
  41. package/src/sources/HyperSyncClient.res +2 -2
  42. package/src/sources/HyperSyncClient.res.mjs +1 -1
  43. package/src/sources/SvmHyperSyncClient.res +139 -102
  44. package/src/sources/SvmHyperSyncClient.res.mjs +45 -5
  45. package/src/sources/SvmHyperSyncSource.res +60 -440
  46. package/src/sources/SvmHyperSyncSource.res.mjs +42 -363
  47. package/src/TestIndexerProxyStorage.res +0 -196
  48. package/src/TestIndexerProxyStorage.res.mjs +0 -121
  49. package/src/TestIndexerWorker.res +0 -4
  50. package/src/TestIndexerWorker.res.mjs +0 -7
  51. package/src/sources/EventRouter.res +0 -165
  52. package/src/sources/EventRouter.res.mjs +0 -153
  53. package/src/sources/HyperFuel.res +0 -179
  54. package/src/sources/HyperFuel.res.mjs +0 -146
  55. package/src/sources/HyperFuel.resi +0 -36
  56. package/src/sources/HyperFuelClient.res +0 -127
  57. package/src/sources/HyperFuelClient.res.mjs +0 -20
  58. package/src/sources/HyperFuelSource.res +0 -502
  59. package/src/sources/HyperFuelSource.res.mjs +0 -481
  60. /package/src/sources/{HyperSyncSource.res → EvmHyperSyncSource.res} +0 -0
  61. /package/src/sources/{HyperSyncSource.res.mjs → EvmHyperSyncSource.res.mjs} +0 -0
@@ -1,502 +0,0 @@
1
- open Source
2
-
3
- exception EventRoutingFailed
4
-
5
- let isUnauthorizedError = (message: string) => message->String.includes("401 Unauthorized")
6
-
7
- let mintEventTag = "mint"
8
- let burnEventTag = "burn"
9
- let transferEventTag = "transfer"
10
- let callEventTag = "call"
11
-
12
- type selectionConfig = {
13
- getRecieptsSelection: (
14
- ~addressesByContractName: dict<array<Address.t>>,
15
- ) => array<HyperFuelClient.QueryTypes.receiptSelection>,
16
- eventRouter: EventRouter.t<Internal.fuelOnEventRegistration>,
17
- }
18
-
19
- let logDataReceiptTypeSelection: array<FuelSDK.receiptType> = [LogData]
20
-
21
- // only transactions with status 1 (success)
22
- let txStatusSelection = [1]
23
-
24
- let makeGetNormalRecieptsSelection = (
25
- ~nonWildcardLogDataRbsByContract,
26
- ~nonLogDataReceiptTypesByContract,
27
- ~contractNames,
28
- ) => {
29
- (~addressesByContractName) => {
30
- let selection: array<HyperFuelClient.QueryTypes.receiptSelection> = []
31
-
32
- //Instantiate each time to add new registered contract addresses
33
- contractNames->Utils.Set.forEach(contractName => {
34
- switch addressesByContractName->Utils.Dict.dangerouslyGetNonOption(contractName) {
35
- | None
36
- | Some([]) => ()
37
- | Some(addresses) => {
38
- switch nonLogDataReceiptTypesByContract->Utils.Dict.dangerouslyGetNonOption(
39
- contractName,
40
- ) {
41
- | Some(receiptTypes) =>
42
- selection
43
- ->Array.push({
44
- rootContractId: addresses,
45
- receiptType: receiptTypes,
46
- txStatus: txStatusSelection,
47
- })
48
- ->ignore
49
- | None => ()
50
- }
51
- switch nonWildcardLogDataRbsByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
52
- | None
53
- | Some([]) => ()
54
- | Some(nonWildcardLogDataRbs) =>
55
- selection
56
- ->Array.push({
57
- rootContractId: addresses,
58
- receiptType: logDataReceiptTypeSelection,
59
- txStatus: txStatusSelection,
60
- rb: nonWildcardLogDataRbs,
61
- })
62
- ->ignore
63
- }
64
- }
65
- }
66
- })
67
-
68
- selection
69
- }
70
- }
71
-
72
- let makeWildcardRecieptsSelection = (~wildcardLogDataRbs, ~nonLogDataWildcardReceiptTypes) => {
73
- let selection: array<HyperFuelClient.QueryTypes.receiptSelection> = []
74
-
75
- switch nonLogDataWildcardReceiptTypes {
76
- | [] => ()
77
- | nonLogDataWildcardReceiptTypes =>
78
- selection
79
- ->Array.push(
80
- (
81
- {
82
- receiptType: nonLogDataWildcardReceiptTypes,
83
- txStatus: txStatusSelection,
84
- }: HyperFuelClient.QueryTypes.receiptSelection
85
- ),
86
- )
87
- ->ignore
88
- }
89
-
90
- switch wildcardLogDataRbs {
91
- | [] => ()
92
- | wildcardLogDataRbs =>
93
- selection
94
- ->Array.push(
95
- (
96
- {
97
- receiptType: logDataReceiptTypeSelection,
98
- txStatus: txStatusSelection,
99
- rb: wildcardLogDataRbs,
100
- }: HyperFuelClient.QueryTypes.receiptSelection
101
- ),
102
- )
103
- ->ignore
104
- }
105
-
106
- selection
107
- }
108
-
109
- let getSelectionConfig = (selection: FetchState.selection, ~chain) => {
110
- let eventRouter = EventRouter.empty()
111
- let nonWildcardLogDataRbsByContract = Dict.make()
112
- let wildcardLogDataRbs = []
113
-
114
- // This is for non-LogData events, since they don't have rb filter and can be grouped
115
- let nonLogDataReceiptTypesByContract = Dict.make()
116
- let nonLogDataWildcardReceiptTypes = []
117
-
118
- let addNonLogDataWildcardReceiptTypes = (receiptType: FuelSDK.receiptType) => {
119
- nonLogDataWildcardReceiptTypes->Array.push(receiptType)->ignore
120
- }
121
- let addNonLogDataReceiptType = (contractName, receiptType: FuelSDK.receiptType) => {
122
- switch nonLogDataReceiptTypesByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
123
- | None => nonLogDataReceiptTypesByContract->Dict.set(contractName, [receiptType])
124
- | Some(receiptTypes) => receiptTypes->Array.push(receiptType)->ignore // Duplication prevented by EventRouter
125
- }
126
- }
127
-
128
- let contractNames = Utils.Set.make()
129
-
130
- selection.onEventRegistrations->Array.forEach(reg => {
131
- let eventConfig =
132
- reg.eventConfig->(Utils.magic: Internal.eventConfig => Internal.fuelEventConfig)
133
- let contractName = eventConfig.contractName
134
- let isWildcard = reg.isWildcard
135
- if !isWildcard {
136
- let _ = contractNames->Utils.Set.add(contractName)
137
- }
138
- eventRouter->EventRouter.addOrThrow(
139
- eventConfig.id,
140
- reg,
141
- ~contractName,
142
- ~eventName=eventConfig.name,
143
- ~chain,
144
- ~isWildcard,
145
- )
146
-
147
- switch (eventConfig.kind, isWildcard) {
148
- | (Mint, true) => addNonLogDataWildcardReceiptTypes(Mint)
149
- | (Mint, false) => addNonLogDataReceiptType(contractName, Mint)
150
- | (Burn, true) => addNonLogDataWildcardReceiptTypes(Burn)
151
- | (Burn, false) => addNonLogDataReceiptType(contractName, Burn)
152
- | (Transfer, true) => {
153
- addNonLogDataWildcardReceiptTypes(Transfer)
154
- addNonLogDataWildcardReceiptTypes(TransferOut)
155
- }
156
- | (Transfer, false) => {
157
- addNonLogDataReceiptType(contractName, Transfer)
158
- addNonLogDataReceiptType(contractName, TransferOut)
159
- }
160
- | (Call, true) => addNonLogDataWildcardReceiptTypes(Call)
161
- | (Call, false) =>
162
- JsError.throwWithMessage("Call receipt indexing currently supported only in wildcard mode")
163
- | (LogData({logId}), _) => {
164
- let rb = logId->BigInt.fromStringOrThrow
165
- if isWildcard {
166
- wildcardLogDataRbs->Array.push(rb)->ignore
167
- } else {
168
- switch nonWildcardLogDataRbsByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
169
- | Some(arr) => arr->Array.push(rb)
170
- | None => nonWildcardLogDataRbsByContract->Dict.set(contractName, [rb])
171
- }
172
- }
173
- }
174
- }
175
- })
176
-
177
- {
178
- getRecieptsSelection: switch selection.dependsOnAddresses {
179
- | false => {
180
- let recieptsSelection = makeWildcardRecieptsSelection(
181
- ~wildcardLogDataRbs,
182
- ~nonLogDataWildcardReceiptTypes,
183
- )
184
- (~addressesByContractName as _) => recieptsSelection
185
- }
186
- | true =>
187
- makeGetNormalRecieptsSelection(
188
- ~nonWildcardLogDataRbsByContract,
189
- ~nonLogDataReceiptTypesByContract,
190
- ~contractNames,
191
- )
192
- },
193
- eventRouter,
194
- }
195
- }
196
-
197
- let memoGetSelectionConfig = (~chain) => {
198
- let cache = Utils.WeakMap.make()
199
- selection =>
200
- switch cache->Utils.WeakMap.get(selection) {
201
- | Some(c) => c
202
- | None => {
203
- let c = selection->getSelectionConfig(~chain)
204
- let _ = cache->Utils.WeakMap.set(selection, c)
205
- c
206
- }
207
- }
208
- }
209
-
210
- type options = {
211
- chain: ChainMap.Chain.t,
212
- endpointUrl: string,
213
- apiToken: option<string>,
214
- }
215
-
216
- let make = ({chain, endpointUrl, apiToken}: options): t => {
217
- let name = "HyperFuel"
218
-
219
- let apiToken = switch apiToken {
220
- | Some(token) => token
221
- | None =>
222
- JsError.throwWithMessage(`An Envio API token is required for using HyperFuel as a data-source.
223
- Set the ENVIO_API_TOKEN environment variable in your .env file.
224
- Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
225
- }
226
-
227
- let client = switch HyperFuelClient.make({url: endpointUrl, apiToken}) {
228
- | client => client
229
- | exception exn =>
230
- exn->ErrorHandling.mkLogAndRaise(~msg="Failed to instantiate the HyperFuel client")
231
- }
232
-
233
- let getSelectionConfig = memoGetSelectionConfig(~chain)
234
-
235
- let getItemsOrThrow = async (
236
- ~fromBlock,
237
- ~toBlock,
238
- ~addressesByContractName,
239
- ~contractNameByAddress,
240
- ~knownHeight,
241
- ~partitionId as _,
242
- ~selection: FetchState.selection,
243
- ~itemsTarget as _,
244
- ~retry,
245
- ~logger,
246
- ) => {
247
- let totalTimeRef = Performance.now()
248
-
249
- let selectionConfig = getSelectionConfig(selection)
250
- let recieptsSelection = selectionConfig.getRecieptsSelection(~addressesByContractName)
251
-
252
- let startFetchingBatchTimeRef = Performance.now()
253
-
254
- //fetch batch
255
- let pageUnsafe = try await HyperFuel.GetLogs.query(
256
- ~client,
257
- ~fromBlock,
258
- ~toBlock,
259
- ~recieptsSelection,
260
- ) catch {
261
- | HyperFuel.GetLogs.Error(error) =>
262
- throw(
263
- Source.GetItemsError(
264
- Source.FailedGettingItems({
265
- exn: %raw(`null`),
266
- attemptedToBlock: toBlock->Option.getOr(knownHeight),
267
- retry: switch error {
268
- | WrongInstance =>
269
- let backoffMillis = switch retry {
270
- | 0 => 100
271
- | _ => 500 * retry
272
- }
273
- WithBackoff({
274
- message: `Block #${fromBlock->Int.toString} not found in HyperFuel. HyperFuel 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.`,
275
- backoffMillis,
276
- })
277
- | UnexpectedMissingParams({missingParams}) =>
278
- ImpossibleForTheQuery({
279
- message: `Source returned invalid data with missing required fields: ${missingParams->Array.joinUnsafe(
280
- ", ",
281
- )}`,
282
- })
283
- },
284
- }),
285
- ),
286
- )
287
- | exn =>
288
- throw(
289
- Source.GetItemsError(
290
- Source.FailedGettingItems({
291
- exn,
292
- attemptedToBlock: toBlock->Option.getOr(knownHeight),
293
- retry: WithBackoff({
294
- message: `Unexpected issue while fetching events from HyperFuel client. Attempt a retry.`,
295
- backoffMillis: switch retry {
296
- | 0 => 500
297
- | _ => 1000 * retry
298
- },
299
- }),
300
- }),
301
- ),
302
- )
303
- }
304
-
305
- let pageFetchTime = startFetchingBatchTimeRef->Performance.secondsSince
306
- let requestStats = [{Source.method: "getLogs", seconds: pageFetchTime}]
307
-
308
- //set height and next from block
309
- let knownHeight = pageUnsafe.archiveHeight
310
-
311
- //The heighest (biggest) blocknumber that was accounted for in
312
- //Our query. Not necessarily the blocknumber of the last log returned
313
- //In the query
314
- let heighestBlockQueried = pageUnsafe.nextBlock - 1
315
-
316
- let parsingTimeRef = Performance.now()
317
-
318
- let parsedQueueItems = pageUnsafe.items->Array.map(item => {
319
- let {contractId: contractAddress, receipt, block, receiptIndex} = item
320
-
321
- let chainId = chain->ChainMap.Chain.toChainId
322
- let eventId = switch receipt {
323
- | LogData({rb}) => BigInt.toString(rb)
324
- | Mint(_) => mintEventTag
325
- | Burn(_) => burnEventTag
326
- | Transfer(_)
327
- | TransferOut(_) => transferEventTag
328
- | Call(_) => callEventTag
329
- }
330
-
331
- let onEventRegistration = switch selectionConfig.eventRouter->EventRouter.get(
332
- ~tag=eventId,
333
- ~contractNameByAddress,
334
- ~contractAddress,
335
- ) {
336
- | None => {
337
- let logger = Logging.createChildFrom(
338
- ~logger,
339
- ~params={
340
- "chainId": chainId,
341
- "blockNumber": block.height,
342
- "logIndex": receiptIndex,
343
- "contractAddress": contractAddress,
344
- "eventId": eventId,
345
- },
346
- )
347
- EventRoutingFailed->ErrorHandling.mkLogAndRaise(
348
- ~msg="Failed to route registered event",
349
- ~logger,
350
- )
351
- }
352
- | Some(onEventRegistration) => onEventRegistration
353
- }
354
-
355
- let eventConfig =
356
- onEventRegistration.eventConfig->(
357
- Utils.magic: Internal.eventConfig => Internal.fuelEventConfig
358
- )
359
-
360
- let params = switch (eventConfig, receipt) {
361
- | ({kind: LogData({decode})}, LogData({data})) =>
362
- try decode(data) catch {
363
- | exn => {
364
- let params = {
365
- "chainId": chainId,
366
- "blockNumber": block.height,
367
- "logIndex": receiptIndex,
368
- }
369
- let logger = Logging.createChildFrom(~logger, ~params)
370
- exn->ErrorHandling.mkLogAndRaise(
371
- ~msg="Failed to decode Fuel LogData receipt, please double check your ABI.",
372
- ~logger,
373
- )
374
- }
375
- }
376
- | (_, Mint({val, subId}))
377
- | (_, Burn({val, subId})) =>
378
- (
379
- {
380
- subId,
381
- amount: val,
382
- }: Internal.fuelSupplyParams
383
- )->Obj.magic
384
- | (_, Transfer({amount, assetId, to})) =>
385
- (
386
- {
387
- to: to->Address.unsafeFromString,
388
- assetId,
389
- amount,
390
- }: Internal.fuelTransferParams
391
- )->Obj.magic
392
- | (_, TransferOut({amount, assetId, toAddress})) =>
393
- (
394
- {
395
- to: toAddress->Address.unsafeFromString,
396
- assetId,
397
- amount,
398
- }: Internal.fuelTransferParams
399
- )->Obj.magic
400
- | (_, Call({amount, assetId, to})) =>
401
- (
402
- {
403
- to: to->Address.unsafeFromString,
404
- assetId,
405
- amount,
406
- }: Internal.fuelTransferParams
407
- )->Obj.magic
408
- // This should never happen unless there's a bug in the routing logic
409
- | _ => JsError.throwWithMessage("Unexpected bug in the event routing logic")
410
- }
411
-
412
- Internal.Event({
413
- onEventRegistration: (onEventRegistration :> Internal.onEventRegistration),
414
- chain,
415
- blockNumber: block.height,
416
- logIndex: receiptIndex,
417
- // Fuel carries the transaction inline on the payload; the store key is
418
- // unused (Fuel identifies transactions by hash, kept on the payload).
419
- transactionIndex: 0,
420
- payload: {
421
- contractName: eventConfig.contractName,
422
- eventName: eventConfig.name,
423
- chainId,
424
- params,
425
- transaction: {
426
- "id": item.transactionId,
427
- }->Obj.magic, // TODO: Obj.magic needed until the field selection types are not configurable for Fuel and Evm separately
428
- block: block->Obj.magic,
429
- srcAddress: contractAddress,
430
- logIndex: receiptIndex,
431
- }->Fuel.fromPayload,
432
- })
433
- })
434
-
435
- let parsingTimeElapsed = parsingTimeRef->Performance.secondsSince
436
-
437
- // Fuel never rolls back on reorg, so block hashes here are purely informational
438
- // for detect-only logging via ReorgDetection.
439
- let blockHashes = pageUnsafe.items->Array.map(({block}) => {
440
- ReorgDetection.blockNumber: block.height,
441
- blockHash: block.id,
442
- })
443
-
444
- let latestFetchedBlockTimestamp = switch pageUnsafe.items->Array.get(
445
- pageUnsafe.items->Array.length - 1,
446
- ) {
447
- | Some({block}) if block.height == heighestBlockQueried => block.time
448
- | _ => 0
449
- }
450
-
451
- let totalTimeElapsed = totalTimeRef->Performance.secondsSince
452
-
453
- let stats = {
454
- totalTimeElapsed,
455
- parsingTimeElapsed,
456
- pageFetchTime,
457
- }
458
-
459
- {
460
- latestFetchedBlockTimestamp,
461
- parsedQueueItems,
462
- // Fuel keeps transaction and block on the payload; no store pages.
463
- transactionStore: None,
464
- blockStore: None,
465
- latestFetchedBlockNumber: heighestBlockQueried,
466
- stats,
467
- knownHeight,
468
- blockHashes,
469
- fromBlockQueried: fromBlock,
470
- requestStats,
471
- }
472
- }
473
-
474
- let getBlockHashes = (~blockNumbers as _, ~logger as _) =>
475
- JsError.throwWithMessage("HyperFuel does not support getting block hashes")
476
-
477
- {
478
- name,
479
- sourceFor: Sync,
480
- chain,
481
- getBlockHashes,
482
- pollingInterval: 100,
483
- poweredByHyperSync: true,
484
- getHeightOrThrow: async () => {
485
- let timerRef = Performance.now()
486
- let height = try await client->HyperFuelClient.getHeight catch {
487
- | JsExn(e) =>
488
- switch e->JsExn.message {
489
- | Some(message) if message->isUnauthorizedError =>
490
- Logging.error(`Your ENVIO_API_TOKEN was rejected by HyperFuel (401 Unauthorized). The indexer will not be able to fetch events. Update the token and try again using 'envio start' or 'envio dev'. For more info: https://docs.envio.dev/docs/HyperSync/api-tokens`)
491
- // Retrying an unauthorized request can never succeed, so block forever
492
- let _ = await Promise.make((_, _) => ())
493
- 0
494
- | _ => throw(JsExn(e))
495
- }
496
- }
497
- let seconds = timerRef->Performance.secondsSince
498
- {height, requestStats: [{method: "getHeight", seconds}]}
499
- },
500
- getItemsOrThrow,
501
- }
502
- }