envio 3.3.0-alpha.7 → 3.3.0-alpha.8

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 (89) hide show
  1. package/index.d.ts +23 -5
  2. package/package.json +6 -6
  3. package/src/Api.res +1 -1
  4. package/src/Api.res.mjs +1 -1
  5. package/src/ChainFetching.res +21 -10
  6. package/src/ChainFetching.res.mjs +11 -10
  7. package/src/ChainState.res +328 -165
  8. package/src/ChainState.res.mjs +205 -100
  9. package/src/ChainState.resi +16 -7
  10. package/src/Config.res +11 -27
  11. package/src/Config.res.mjs +8 -7
  12. package/src/Core.res +7 -0
  13. package/src/CrossChainState.res +53 -80
  14. package/src/CrossChainState.res.mjs +43 -59
  15. package/src/CrossChainState.resi +1 -1
  16. package/src/Ecosystem.res +3 -3
  17. package/src/Ecosystem.res.mjs +3 -3
  18. package/src/Envio.res +14 -9
  19. package/src/EventConfigBuilder.res +105 -56
  20. package/src/EventConfigBuilder.res.mjs +69 -33
  21. package/src/EventProcessing.res +19 -15
  22. package/src/EventProcessing.res.mjs +13 -12
  23. package/src/FetchState.res +375 -171
  24. package/src/FetchState.res.mjs +249 -251
  25. package/src/HandlerLoader.res +7 -112
  26. package/src/HandlerLoader.res.mjs +1 -87
  27. package/src/HandlerRegister.res +209 -6
  28. package/src/HandlerRegister.res.mjs +90 -5
  29. package/src/HandlerRegister.resi +13 -2
  30. package/src/IndexerState.res +6 -3
  31. package/src/IndexerState.res.mjs +3 -3
  32. package/src/IndexerState.resi +1 -1
  33. package/src/IndexingAddresses.res +10 -7
  34. package/src/IndexingAddresses.res.mjs +8 -7
  35. package/src/IndexingAddresses.resi +3 -1
  36. package/src/Internal.res +82 -36
  37. package/src/Internal.res.mjs +11 -1
  38. package/src/Main.res +22 -23
  39. package/src/Main.res.mjs +15 -17
  40. package/src/Metrics.res +43 -2
  41. package/src/Metrics.res.mjs +39 -3
  42. package/src/Prometheus.res +0 -35
  43. package/src/Prometheus.res.mjs +33 -68
  44. package/src/RawEvent.res +7 -2
  45. package/src/RawEvent.res.mjs +4 -4
  46. package/src/SimulateItems.res +20 -7
  47. package/src/SimulateItems.res.mjs +7 -11
  48. package/src/TestIndexer.res +1 -1
  49. package/src/TestIndexer.res.mjs +1 -1
  50. package/src/sources/BlockStore.res +46 -0
  51. package/src/sources/BlockStore.res.mjs +24 -0
  52. package/src/sources/EventRouter.res +19 -12
  53. package/src/sources/EventRouter.res.mjs +7 -5
  54. package/src/sources/Evm.res +54 -4
  55. package/src/sources/Evm.res.mjs +43 -4
  56. package/src/sources/EvmChain.res +14 -18
  57. package/src/sources/EvmChain.res.mjs +12 -13
  58. package/src/sources/FieldMask.res +39 -0
  59. package/src/sources/FieldMask.res.mjs +42 -0
  60. package/src/sources/Fuel.res +5 -2
  61. package/src/sources/Fuel.res.mjs +4 -3
  62. package/src/sources/HyperFuelSource.res +31 -40
  63. package/src/sources/HyperFuelSource.res.mjs +52 -44
  64. package/src/sources/HyperSync.res +31 -9
  65. package/src/sources/HyperSync.res.mjs +47 -31
  66. package/src/sources/HyperSync.resi +10 -3
  67. package/src/sources/HyperSyncClient.res +15 -4
  68. package/src/sources/HyperSyncHeightStream.res +1 -8
  69. package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
  70. package/src/sources/HyperSyncSource.res +37 -58
  71. package/src/sources/HyperSyncSource.res.mjs +42 -42
  72. package/src/sources/RpcSource.res +91 -82
  73. package/src/sources/RpcSource.res.mjs +76 -45
  74. package/src/sources/RpcWebSocketHeightStream.res +0 -5
  75. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
  76. package/src/sources/SimulateSource.res +5 -3
  77. package/src/sources/SimulateSource.res.mjs +12 -4
  78. package/src/sources/Source.res +19 -5
  79. package/src/sources/SourceManager.res +62 -6
  80. package/src/sources/SourceManager.res.mjs +55 -8
  81. package/src/sources/SourceManager.resi +10 -0
  82. package/src/sources/Svm.res +14 -10
  83. package/src/sources/Svm.res.mjs +23 -6
  84. package/src/sources/SvmHyperSyncClient.res +5 -6
  85. package/src/sources/SvmHyperSyncSource.res +82 -41
  86. package/src/sources/SvmHyperSyncSource.res.mjs +86 -41
  87. package/src/sources/TransactionStore.res +6 -107
  88. package/src/sources/TransactionStore.res.mjs +5 -86
  89. package/svm.schema.json +19 -0
@@ -4,7 +4,7 @@ type options = {
4
4
  chain: ChainMap.Chain.t,
5
5
  endpointUrl: string,
6
6
  apiToken: option<string>,
7
- eventConfigs: array<Internal.svmInstructionEventConfig>,
7
+ onEventRegistrations: array<Internal.svmOnEventRegistration>,
8
8
  clientTimeoutMillis: int,
9
9
  }
10
10
 
@@ -184,12 +184,12 @@ let parseDecoded = (
184
184
  }
185
185
  }
186
186
 
187
+ // `block` is omitted; it's materialised from the block store at batch prep.
187
188
  let toSvmInstruction = (
188
189
  instr: SvmHyperSyncClient.ResponseTypes.instruction,
189
190
  ~programName,
190
191
  ~instructionName,
191
192
  ~logs,
192
- ~block,
193
193
  ): Envio.svmInstruction => {
194
194
  programName,
195
195
  instructionName,
@@ -204,14 +204,13 @@ let toSvmInstruction = (
204
204
  d8: ?instr.d8,
205
205
  params: ?(instr.decoded->Option.map(parseDecoded)),
206
206
  ?logs,
207
- block,
208
207
  }
209
208
 
210
209
  // Probe the discriminator byte-length ordering longest-first. Stops at the
211
210
  // first router hit. Falls back to the `_none` key (program-wide handler) when
212
211
  // no discriminator-keyed handler matches.
213
212
  let probeRouter = (
214
- router: EventRouter.t<Internal.svmInstructionEventConfig>,
213
+ router: EventRouter.t<Internal.svmOnEventRegistration>,
215
214
  programId: SvmTypes.Pubkey.t,
216
215
  instr: SvmHyperSyncClient.ResponseTypes.instruction,
217
216
  byteLengthsDesc: array<int>,
@@ -268,9 +267,30 @@ let toQueryTxField = (field: Internal.svmTransactionField): option<
268
267
  | TokenBalances => None
269
268
  }
270
269
 
271
- let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: options): t => {
270
+ // Map a selected block field to its HyperSync query column. Slot/Blockhash/
271
+ // BlockTime are requested unconditionally (needed for reorg detection and the
272
+ // item's slot/timestamp), so selecting `slot`/`time`/`hash` adds no extra column.
273
+ let toQueryBlockField = (field: Internal.svmBlockField): option<
274
+ SvmHyperSyncClient.QueryTypes.blockField,
275
+ > =>
276
+ switch field {
277
+ | Slot | Time | Hash => None
278
+ | Height => Some(BlockHeight)
279
+ | ParentSlot => Some(ParentSlot)
280
+ | ParentHash => Some(ParentBlockhash)
281
+ }
282
+
283
+ let make = (
284
+ {chain, endpointUrl, apiToken, onEventRegistrations, clientTimeoutMillis}: options,
285
+ ): t => {
272
286
  let name = "SvmHyperSync"
273
- let chainId = chain->ChainMap.Chain.toChainId
287
+
288
+ // Definitions drive query/decode building; the registrations drive routing
289
+ // (they carry `isWildcard` and become each decoded item's `onEventRegistration`).
290
+ let eventConfigs =
291
+ onEventRegistrations->Array.map(reg =>
292
+ reg.eventConfig->(Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig)
293
+ )
274
294
 
275
295
  // Built once at startup and handed to the client so `get` decodes matching
276
296
  // instructions in Rust rather than per-instruction over the napi boundary.
@@ -285,7 +305,10 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
285
305
  },
286
306
  )
287
307
 
288
- let (eventRouter, programOrderings) = EventRouter.fromSvmEventConfigsOrThrow(eventConfigs, ~chain)
308
+ let (eventRouter, programOrderings) = EventRouter.fromSvmEventConfigsOrThrow(
309
+ onEventRegistrations,
310
+ ~chain,
311
+ )
289
312
 
290
313
  // programId.toString -> sorted-desc byte lengths
291
314
  let orderingByProgram = Dict.make()
@@ -332,6 +355,24 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
332
355
  }
333
356
  )
334
357
 
358
+ // Union of selected block fields across the chain's events. `slot`/`time`/
359
+ // `hash` are always fetched (as Slot/BlockTime/Blockhash); the rest are added
360
+ // only when an instruction selected them.
361
+ let blockQueryFields = {
362
+ let fields: array<SvmHyperSyncClient.QueryTypes.blockField> = [Slot, Blockhash, BlockTime]
363
+ let selected = Utils.Set.make()
364
+ eventConfigs->Array.forEach(cfg =>
365
+ cfg.selectedBlockFields->Utils.Set.forEach(field => selected->Utils.Set.add(field)->ignore)
366
+ )
367
+ selected->Utils.Set.forEach(field =>
368
+ switch field->toQueryBlockField {
369
+ | Some(queryField) => fields->Array.push(queryField)->ignore
370
+ | None => ()
371
+ }
372
+ )
373
+ fields
374
+ }
375
+
335
376
  let getItemsOrThrow = async (
336
377
  ~fromBlock,
337
378
  ~toBlock,
@@ -353,7 +394,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
353
394
  // field list returns no rows (instructions and blocks are exempt), so each
354
395
  // opted-into table needs its columns spelled out here.
355
396
  let fields: SvmHyperSyncClient.QueryTypes.fieldSelection = {
356
- block: [Slot, Blockhash, BlockTime],
397
+ block: blockQueryFields,
357
398
  transaction: ?(needsTransactions ? Some(txQueryFields) : None),
358
399
  log: ?(needsLogs ? Some([Slot, TransactionIndex, InstructionAddress, Kind, Message]) : None),
359
400
  tokenBalance: ?(
@@ -372,9 +413,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
372
413
  maxNumInstructions: itemsTarget,
373
414
  }
374
415
 
375
- Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getInstructions")
376
-
377
- let (resp, transactionStore) = try await client.get(~query) catch {
416
+ let (resp, transactionStore, blockStore) = try await client.get(~query) catch {
378
417
  | exn =>
379
418
  throw(
380
419
  Source.GetItemsError(
@@ -393,11 +432,13 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
393
432
  )
394
433
  }
395
434
  let pageFetchTime = pageFetchRef->Performance.secondsSince
435
+ let requestStats = [{Source.method: "getInstructions", seconds: pageFetchTime}]
396
436
 
397
437
  let parsingRef = Performance.now()
398
438
 
399
- // Per-slot unix timestamp lookup from the response's `blocks` table. Slots
400
- // without a block row (rare; usually skipped slots) fall back to `None`.
439
+ // Per-slot blockTime lookup from the response's `blocks` table, for the
440
+ // batch's `latestFetchedBlockTimestamp`. Slots without a block row (rare;
441
+ // usually skipped slots) fall back to `None`.
401
442
  let blockTimeBySlot = Dict.make()
402
443
  resp.data.blocks->Array.forEach(b => {
403
444
  switch b.blockTime {
@@ -447,7 +488,11 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
447
488
 
448
489
  switch maybeConfig {
449
490
  | None => ()
450
- | Some(eventConfig) =>
491
+ | Some(onEventRegistration) =>
492
+ let eventConfig =
493
+ onEventRegistration.eventConfig->(
494
+ Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig
495
+ )
451
496
  let logKey =
452
497
  instr.slot->Int.toString ++
453
498
  ":" ++
@@ -466,28 +511,19 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
466
511
  )
467
512
  )
468
513
 
469
- let slotKey = instr.slot->Int.toString
470
- let blockTime = blockTimeBySlot->Utils.Dict.dangerouslyGetNonOption(slotKey)
471
514
  let payload = toSvmInstruction(
472
515
  instr,
473
516
  ~programName=eventConfig.contractName,
474
517
  ~instructionName=eventConfig.name,
475
518
  ~logs=eventConfig.includeLogs ? maybeLogs : None,
476
- ~block={
477
- slot: instr.slot,
478
- time: blockTime->Option.getOr(0),
479
- hash: "",
480
- },
481
519
  )
482
520
 
483
521
  parsedQueueItems
484
522
  ->Array.push(
485
523
  Internal.Event({
486
- eventConfig: (eventConfig :> Internal.eventConfig),
487
- timestamp: blockTime->Option.getOr(0),
524
+ onEventRegistration,
488
525
  chain,
489
526
  blockNumber: instr.slot,
490
- blockHash: "",
491
527
  logIndex: synthLogIndex(instr),
492
528
  // The parent transaction is materialised from the store at batch prep.
493
529
  transactionIndex: instr.transactionIndex,
@@ -522,11 +558,14 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
522
558
  parsedQueueItems,
523
559
  // Raw transactions kept in Rust; materialised (selected fields) at batch prep.
524
560
  transactionStore: Some(transactionStore),
561
+ // Raw blocks kept in Rust; materialised onto the payload at batch prep.
562
+ blockStore: Some(blockStore),
525
563
  latestFetchedBlockNumber: highestSlot,
526
564
  stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
527
565
  knownHeight,
528
566
  blockHashes,
529
567
  fromBlockQueried: fromBlock,
568
+ requestStats,
530
569
  }
531
570
  }
532
571
 
@@ -535,6 +574,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
535
574
  // wire, so we request `maxSlot + 1`; the caller filters to the exact slots.
536
575
  let queryBlockDataRange = async (~fromSlot, ~toSlot) => {
537
576
  let blockDatas = []
577
+ let requestStats = []
538
578
  let fromRef = ref(fromSlot)
539
579
  let keepGoing = ref(true)
540
580
  while keepGoing.contents {
@@ -545,9 +585,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
545
585
  fields: {block: [Slot, Blockhash, BlockTime]},
546
586
  maxNumBlocks: 1000,
547
587
  }
548
- Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getBlockHashes")
549
- // Block-only query; the store page is empty.
550
- let (resp, _) = await client.get(~query)
588
+ let timerRef = Performance.now()
589
+ // Block-only query; the store pages are empty.
590
+ let (resp, _, _) = await client.get(~query)
591
+ requestStats
592
+ ->Array.push({Source.method: "getBlockHashes", seconds: timerRef->Performance.secondsSince})
593
+ ->ignore
551
594
  resp.data.blocks->Array.forEach(b =>
552
595
  blockDatas
553
596
  ->Array.push({
@@ -566,12 +609,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
566
609
  fromRef := resp.nextSlot
567
610
  }
568
611
  }
569
- blockDatas
612
+ (blockDatas, requestStats)
570
613
  }
571
614
 
572
615
  let getBlockHashes = async (~blockNumbers, ~logger as _) =>
573
616
  switch blockNumbers->Array.get(0) {
574
- | None => Ok([])
617
+ | None => {Source.result: Ok([]), requestStats: []}
575
618
  | Some(firstSlot) =>
576
619
  try {
577
620
  let minSlot = ref(firstSlot)
@@ -586,14 +629,19 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
586
629
  }
587
630
  requested->Utils.Set.add(slot)->ignore
588
631
  })
589
- let blockDatas = await queryBlockDataRange(
632
+ let (blockDatas, requestStats) = await queryBlockDataRange(
590
633
  ~fromSlot=minSlot.contents,
591
634
  ~toSlot=maxSlot.contents,
592
635
  )
593
636
  // Keep one entry per requested slot; drop duplicates and unrelated slots.
594
- Ok(blockDatas->Array.filter(data => requested->Utils.Set.delete(data.blockNumber)))
637
+ {
638
+ Source.result: Ok(
639
+ blockDatas->Array.filter(data => requested->Utils.Set.delete(data.blockNumber)),
640
+ ),
641
+ requestStats,
642
+ }
595
643
  } catch {
596
- | exn => Error(exn)
644
+ | exn => {Source.result: Error(exn), requestStats: []}
597
645
  }
598
646
  }
599
647
 
@@ -606,16 +654,9 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
606
654
  getBlockHashes,
607
655
  getHeightOrThrow: async () => {
608
656
  let timer = Performance.now()
609
- let h = await client.getHeight()
657
+ let height = await client.getHeight()
610
658
  let seconds = timer->Performance.secondsSince
611
- Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getHeight")
612
- Prometheus.SourceRequestCount.addSeconds(
613
- ~sourceName=name,
614
- ~chainId,
615
- ~method="getHeight",
616
- ~seconds,
617
- )
618
- h
659
+ {height, requestStats: [{method: "getHeight", seconds}]}
619
660
  },
620
661
  getItemsOrThrow,
621
662
  }
@@ -1,7 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Source from "./Source.res.mjs";
4
- import * as Prometheus from "../Prometheus.res.mjs";
5
4
  import * as EventRouter from "./EventRouter.res.mjs";
6
5
  import * as Performance from "../bindings/Performance.res.mjs";
7
6
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
@@ -166,7 +165,7 @@ function parseDecoded(d) {
166
165
  };
167
166
  }
168
167
 
169
- function toSvmInstruction(instr, programName, instructionName, logs, block) {
168
+ function toSvmInstruction(instr, programName, instructionName, logs) {
170
169
  return {
171
170
  programName: programName,
172
171
  instructionName: instructionName,
@@ -180,8 +179,7 @@ function toSvmInstruction(instr, programName, instructionName, logs, block) {
180
179
  d4: instr.d4,
181
180
  d8: instr.d8,
182
181
  params: Stdlib_Option.map(instr.decoded, parseDecoded),
183
- logs: logs,
184
- block: block
182
+ logs: logs
185
183
  };
186
184
  }
187
185
 
@@ -248,13 +246,26 @@ function toQueryTxField(field) {
248
246
  }
249
247
  }
250
248
 
249
+ function toQueryBlockField(field) {
250
+ switch (field) {
251
+ case "height" :
252
+ return "block_height";
253
+ case "parentSlot" :
254
+ return "parent_slot";
255
+ case "parentHash" :
256
+ return "parent_blockhash";
257
+ default:
258
+ return;
259
+ }
260
+ }
261
+
251
262
  function make(param) {
252
- let eventConfigs = param.eventConfigs;
263
+ let onEventRegistrations = param.onEventRegistrations;
253
264
  let chain = param.chain;
254
- let name = "SvmHyperSync";
265
+ let eventConfigs = onEventRegistrations.map(reg => reg.eventConfig);
255
266
  let programSchemas = buildProgramSchemas(eventConfigs);
256
267
  let client = SvmHyperSyncClient.make(param.endpointUrl, param.apiToken, param.clientTimeoutMillis, undefined, undefined, undefined, programSchemas.length !== 0 ? programSchemas : undefined);
257
- let match = EventRouter.fromSvmEventConfigsOrThrow(eventConfigs, chain);
268
+ let match = EventRouter.fromSvmEventConfigsOrThrow(onEventRegistrations, chain);
258
269
  let eventRouter = match[0];
259
270
  let orderingByProgram = {};
260
271
  match[1].forEach(o => {
@@ -288,15 +299,29 @@ function make(param) {
288
299
  return true;
289
300
  }
290
301
  });
302
+ let fields$1 = [
303
+ "slot",
304
+ "blockhash",
305
+ "block_time"
306
+ ];
307
+ let selected = new Set();
308
+ eventConfigs.forEach(cfg => {
309
+ cfg.selectedBlockFields.forEach(field => {
310
+ selected.add(field);
311
+ });
312
+ });
313
+ selected.forEach(field => {
314
+ let queryField = toQueryBlockField(field);
315
+ if (queryField !== undefined) {
316
+ fields$1.push(queryField);
317
+ return;
318
+ }
319
+ });
291
320
  let getItemsOrThrow = async (fromBlock, toBlock, param, contractNameByAddress, knownHeight, param$1, param$2, itemsTarget, retry, logger) => {
292
321
  let totalTimeRef = Performance.now();
293
322
  let pageFetchRef = Performance.now();
294
323
  let instructionSelections = buildInstructionSelections(eventConfigs);
295
- let fields_block = [
296
- "slot",
297
- "blockhash",
298
- "block_time"
299
- ];
324
+ let fields_block = fields$1;
300
325
  let fields_transaction = needsTransactions ? fields : undefined;
301
326
  let fields_log = needsLogs ? [
302
327
  "slot",
@@ -314,7 +339,7 @@ function make(param) {
314
339
  "pre_amount",
315
340
  "post_amount"
316
341
  ] : undefined;
317
- let fields$1 = {
342
+ let fields$2 = {
318
343
  block: fields_block,
319
344
  transaction: fields_transaction,
320
345
  log: fields_log,
@@ -322,7 +347,7 @@ function make(param) {
322
347
  };
323
348
  let query_toSlot = Stdlib_Option.map(toBlock, toBlock => toBlock + 1 | 0);
324
349
  let query_instructions = instructionSelections;
325
- let query_fields = fields$1;
350
+ let query_fields = fields$2;
326
351
  let query_maxNumInstructions = itemsTarget;
327
352
  let query = {
328
353
  fromSlot: fromBlock,
@@ -331,7 +356,6 @@ function make(param) {
331
356
  fields: query_fields,
332
357
  maxNumInstructions: query_maxNumInstructions
333
358
  };
334
- Prometheus.SourceRequestCount.increment(name, chain, "getInstructions");
335
359
  let match;
336
360
  try {
337
361
  match = await client.get(query);
@@ -354,6 +378,10 @@ function make(param) {
354
378
  }
355
379
  let resp = match[0];
356
380
  let pageFetchTime = Performance.secondsSince(pageFetchRef);
381
+ let requestStats = [{
382
+ method: "getInstructions",
383
+ seconds: pageFetchTime
384
+ }];
357
385
  let parsingRef = Performance.now();
358
386
  let blockTimeBySlot = {};
359
387
  resp.data.blocks.forEach(b => {
@@ -388,25 +416,18 @@ function make(param) {
388
416
  let contractAddress = instr.programId;
389
417
  let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, contractNameByAddress);
390
418
  if (maybeConfig !== undefined) {
419
+ let eventConfig = maybeConfig.eventConfig;
391
420
  let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress);
392
421
  let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({
393
422
  kind: Stdlib_Option.getOr(log.kind, ""),
394
423
  message: Stdlib_Option.getOr(log.message, "")
395
424
  })));
396
- let slotKey = instr.slot.toString();
397
- let blockTime = blockTimeBySlot[slotKey];
398
- let payload = toSvmInstruction(instr, maybeConfig.contractName, maybeConfig.name, maybeConfig.includeLogs ? maybeLogs : undefined, {
399
- slot: instr.slot,
400
- time: Stdlib_Option.getOr(blockTime, 0),
401
- hash: ""
402
- });
425
+ let payload = toSvmInstruction(instr, eventConfig.contractName, eventConfig.name, eventConfig.includeLogs ? maybeLogs : undefined);
403
426
  parsedQueueItems.push({
404
427
  kind: 0,
405
- eventConfig: maybeConfig,
406
- timestamp: Stdlib_Option.getOr(blockTime, 0),
428
+ onEventRegistration: maybeConfig,
407
429
  chain: chain,
408
430
  blockNumber: instr.slot,
409
- blockHash: "",
410
431
  logIndex: synthLogIndex(instr),
411
432
  transactionIndex: instr.transactionIndex,
412
433
  payload: payload
@@ -426,6 +447,7 @@ function make(param) {
426
447
  blockHashes: blockHashes,
427
448
  parsedQueueItems: parsedQueueItems,
428
449
  transactionStore: Primitive_option.some(match[1]),
450
+ blockStore: Primitive_option.some(match[2]),
429
451
  fromBlockQueried: fromBlock,
430
452
  latestFetchedBlockNumber: highestSlot,
431
453
  latestFetchedBlockTimestamp: latestBlockTime,
@@ -433,11 +455,13 @@ function make(param) {
433
455
  "total time elapsed (s)": totalTimeElapsed,
434
456
  "parsing time (s)": parsingTimeElapsed,
435
457
  "page fetch time (s)": pageFetchTime
436
- }
458
+ },
459
+ requestStats: requestStats
437
460
  };
438
461
  };
439
462
  let queryBlockDataRange = async (fromSlot, toSlot) => {
440
463
  let blockDatas = [];
464
+ let requestStats = [];
441
465
  let fromRef = fromSlot;
442
466
  let keepGoing = true;
443
467
  while (keepGoing) {
@@ -458,9 +482,13 @@ function make(param) {
458
482
  fields: query_fields,
459
483
  maxNumBlocks: query_maxNumBlocks
460
484
  };
461
- Prometheus.SourceRequestCount.increment(name, chain, "getBlockHashes");
485
+ let timerRef = Performance.now();
462
486
  let match = await client.get(query);
463
487
  let resp = match[0];
488
+ requestStats.push({
489
+ method: "getBlockHashes",
490
+ seconds: Performance.secondsSince(timerRef)
491
+ });
464
492
  resp.data.blocks.forEach(b => {
465
493
  blockDatas.push({
466
494
  blockHash: b.blockhash,
@@ -474,14 +502,20 @@ function make(param) {
474
502
  fromRef = resp.nextSlot;
475
503
  }
476
504
  };
477
- return blockDatas;
505
+ return [
506
+ blockDatas,
507
+ requestStats
508
+ ];
478
509
  };
479
510
  let getBlockHashes = async (blockNumbers, param) => {
480
511
  let firstSlot = blockNumbers[0];
481
512
  if (firstSlot === undefined) {
482
513
  return {
483
- TAG: "Ok",
484
- _0: []
514
+ result: {
515
+ TAG: "Ok",
516
+ _0: []
517
+ },
518
+ requestStats: []
485
519
  };
486
520
  }
487
521
  try {
@@ -501,21 +535,27 @@ function make(param) {
501
535
  }
502
536
  requested.add(slot);
503
537
  });
504
- let blockDatas = await queryBlockDataRange(minSlot.contents, maxSlot.contents);
538
+ let match = await queryBlockDataRange(minSlot.contents, maxSlot.contents);
505
539
  return {
506
- TAG: "Ok",
507
- _0: blockDatas.filter(data => requested.delete(data.blockNumber))
540
+ result: {
541
+ TAG: "Ok",
542
+ _0: match[0].filter(data => requested.delete(data.blockNumber))
543
+ },
544
+ requestStats: match[1]
508
545
  };
509
546
  } catch (raw_exn) {
510
547
  let exn = Primitive_exceptions.internalToException(raw_exn);
511
548
  return {
512
- TAG: "Error",
513
- _0: exn
549
+ result: {
550
+ TAG: "Error",
551
+ _0: exn
552
+ },
553
+ requestStats: []
514
554
  };
515
555
  }
516
556
  };
517
557
  return {
518
- name: name,
558
+ name: "SvmHyperSync",
519
559
  sourceFor: "Sync",
520
560
  chain: chain,
521
561
  poweredByHyperSync: true,
@@ -523,11 +563,15 @@ function make(param) {
523
563
  getBlockHashes: getBlockHashes,
524
564
  getHeightOrThrow: async () => {
525
565
  let timer = Performance.now();
526
- let h = await client.getHeight();
566
+ let height = await client.getHeight();
527
567
  let seconds = Performance.secondsSince(timer);
528
- Prometheus.SourceRequestCount.increment(name, chain, "getHeight");
529
- Prometheus.SourceRequestCount.addSeconds(name, chain, "getHeight", seconds);
530
- return h;
568
+ return {
569
+ height: height,
570
+ requestStats: [{
571
+ method: "getHeight",
572
+ seconds: seconds
573
+ }]
574
+ };
531
575
  },
532
576
  getItemsOrThrow: getItemsOrThrow
533
577
  };
@@ -542,6 +586,7 @@ export {
542
586
  toSvmInstruction,
543
587
  probeRouter,
544
588
  toQueryTxField,
589
+ toQueryBlockField,
545
590
  make,
546
591
  }
547
- /* Prometheus Not a pure module */
592
+ /* EventRouter Not a pure module */
@@ -21,42 +21,12 @@ let make = (~ecosystem: Ecosystem.name, ~shouldChecksum: bool): t => {
21
21
  }
22
22
  }
23
23
 
24
- // Field-name bit-index map from an ordered field-name array. The index is the
25
- // field code shared with the Rust store (`EvmTxField`/`SvmTxField`).
26
- let fieldCodes = (fields: array<string>): dict<int> => {
27
- let codes = Dict.make()
28
- fields->Array.forEachWithIndex((name, i) => codes->Dict.set(name, i))
29
- codes
30
- }
31
-
32
- let pow2: int => float = %raw(`c => Math.pow(2, c)`)
33
-
34
- // One event's selected transaction fields as a bitmask float (bit `code` set ⇔
35
- // selected). Each field appears once, so summing `pow2(code)` sets distinct bits
36
- // with no overlap; the result stays exact in f64 (codes span 0..31, so a mask is
37
- // at most 2^32-1).
38
- let maskFromFields = (selectedTransactionFields: Utils.Set.t<string>, ~codes: dict<int>): float => {
39
- let mask = ref(0.)
40
- selectedTransactionFields->Utils.Set.forEach(name =>
41
- switch codes->Utils.Dict.dangerouslyGetNonOption(name) {
42
- | Some(code) => mask := mask.contents +. pow2(code)
43
- | None => ()
44
- }
45
- )
46
- mask.contents
47
- }
48
-
49
- // Build an ecosystem's per-event mask function from its ordered field-name
50
- // array. The field codes are derived once and closed over.
51
- let makeMaskFn = (fields: array<string>): (Utils.Set.t<string> => float) => {
52
- let codes = fieldCodes(fields)
53
- selectedTransactionFields => selectedTransactionFields->maskFromFields(~codes)
54
- }
55
-
56
- // Bitwise OR of two per-event masks. Masks fit in 32 bits (≤32 transaction
57
- // fields), so `>>> 0` recovers the unsigned value that a plain `|` renders
58
- // negative once bit 31 is set.
59
- let orMask: (float, float) => float = %raw(`(a, b) => (a | b) >>> 0`)
24
+ // One event's selected transaction fields store selection bitmask, built from
25
+ // the ecosystem's ordered field-name array (the bit index is the field code
26
+ // shared with the Rust store, `EvmTxField`/`SvmTxField`).
27
+ let makeMaskFn = FieldMask.makeMaskFn
28
+ let orMask = FieldMask.orMask
29
+ let fieldCodes = FieldMask.fieldCodes
60
30
 
61
31
  // Drain another store (a fetch-response page) into this one.
62
32
  @send external merge: (t, t) => unit = "merge"
@@ -77,74 +47,3 @@ external materialize: (
77
47
 
78
48
  // Drop transactions for blocks above the given block (rolled back).
79
49
  @send external rollback: (t, int) => unit = "rollback"
80
-
81
- // Materialise each store-backed item's selected transaction fields and write the
82
- // resulting transaction onto its payload. Every event's mask comes from its own
83
- // `eventConfig.transactionFieldMask`, so a transaction decodes only the fields
84
- // the events on it selected — a large field (e.g. `input`) never materialises
85
- // for events that didn't ask for it. Items that already carry an inline
86
- // transaction (RPC/simulate/Fuel) are skipped. Store-backed items always get a
87
- // transaction object — the selected fields, or `{}` when nothing was selected —
88
- // so `event.transaction` is never `undefined` (matching the inline sources).
89
- // Deduped per (blockNumber, transactionIndex); each row's mask is the OR of the
90
- // masks of the events sharing that transaction.
91
- let materializeItems = async (store: t, ~items: array<Internal.item>) => {
92
- // Store-backed items arrive in (block, logIndex) order, and a transaction's
93
- // logs are contiguous within a block, so events sharing a (blockNumber,
94
- // transactionIndex) are adjacent regardless of event. Group them by extending
95
- // the current run rather than hashing a string key per item. A key recurring
96
- // non-adjacently just splits into two groups (one redundant decode) — never
97
- // incorrect.
98
- let blockNumbers = []
99
- let transactionIndices = []
100
- let masks = []
101
- let payloadGroups = []
102
- let anySelected = ref(false)
103
-
104
- items->Array.forEach(item =>
105
- switch item {
106
- | Internal.Event(_) =>
107
- let eventItem = item->Internal.castUnsafeEventItem
108
- switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
109
- | Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
110
- | None =>
111
- let {blockNumber, transactionIndex} = eventItem
112
- let mask = eventItem.eventConfig.transactionFieldMask
113
- if mask != 0. {
114
- anySelected := true
115
- }
116
- let last = payloadGroups->Array.length - 1
117
- if (
118
- last >= 0 &&
119
- blockNumbers->Array.getUnsafe(last) == blockNumber &&
120
- transactionIndices->Array.getUnsafe(last) == transactionIndex
121
- ) {
122
- payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
123
- masks->Array.setUnsafe(last, orMask(masks->Array.getUnsafe(last), mask))
124
- } else {
125
- blockNumbers->Array.push(blockNumber)
126
- transactionIndices->Array.push(transactionIndex)
127
- masks->Array.push(mask)
128
- payloadGroups->Array.push([eventItem.payload])
129
- }
130
- }
131
- | Internal.Block(_) => ()
132
- }
133
- )
134
-
135
- if payloadGroups->Utils.Array.notEmpty {
136
- if anySelected.contents {
137
- let txs = await store->materialize(~blockNumbers, ~transactionIndices, ~masks)
138
- payloadGroups->Array.forEachWithIndex((payloads, i) => {
139
- let tx = txs->Array.getUnsafe(i)
140
- payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
141
- })
142
- } else {
143
- // No event selected any field: stamp an empty transaction object so
144
- // `event.transaction` is never undefined, without a materialize call.
145
- payloadGroups->Array.forEach(payloads =>
146
- payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(%raw(`{}`)))
147
- )
148
- }
149
- }
150
- }