envio 3.3.0-alpha.2 → 3.3.0-alpha.4

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 (71) hide show
  1. package/index.d.ts +23 -21
  2. package/package.json +6 -6
  3. package/src/Bin.res +3 -0
  4. package/src/Bin.res.mjs +4 -0
  5. package/src/ChainFetching.res +26 -3
  6. package/src/ChainFetching.res.mjs +22 -3
  7. package/src/ChainState.res +3 -12
  8. package/src/ChainState.res.mjs +6 -8
  9. package/src/ChainState.resi +0 -2
  10. package/src/CrossChainState.res +15 -0
  11. package/src/CrossChainState.res.mjs +12 -0
  12. package/src/Ecosystem.res +0 -6
  13. package/src/EventConfigBuilder.res +45 -18
  14. package/src/EventConfigBuilder.res.mjs +28 -10
  15. package/src/EventProcessing.res +53 -37
  16. package/src/EventProcessing.res.mjs +41 -36
  17. package/src/FetchState.res +35 -42
  18. package/src/FetchState.res.mjs +37 -63
  19. package/src/HandlerLoader.res +10 -1
  20. package/src/HandlerLoader.res.mjs +15 -8
  21. package/src/Internal.res +8 -3
  22. package/src/Internal.res.mjs +1 -1
  23. package/src/LoadLayer.res +5 -5
  24. package/src/LoadLayer.res.mjs +6 -6
  25. package/src/Main.res +17 -9
  26. package/src/Main.res.mjs +15 -6
  27. package/src/PgStorage.res +4 -4
  28. package/src/PgStorage.res.mjs +5 -5
  29. package/src/Prometheus.res +10 -10
  30. package/src/Prometheus.res.mjs +9 -9
  31. package/src/PruneStaleHistory.res +2 -2
  32. package/src/PruneStaleHistory.res.mjs +3 -3
  33. package/src/Rollback.res +2 -2
  34. package/src/Rollback.res.mjs +3 -3
  35. package/src/SimulateItems.res +81 -15
  36. package/src/SimulateItems.res.mjs +63 -20
  37. package/src/TestIndexer.res +77 -39
  38. package/src/TestIndexer.res.mjs +45 -31
  39. package/src/bindings/Performance.res +7 -0
  40. package/src/bindings/Performance.res.mjs +21 -0
  41. package/src/bindings/Performance.resi +7 -0
  42. package/src/sources/EventRouter.res +17 -21
  43. package/src/sources/EventRouter.res.mjs +6 -9
  44. package/src/sources/Evm.res +10 -37
  45. package/src/sources/Evm.res.mjs +5 -40
  46. package/src/sources/Fuel.res +0 -4
  47. package/src/sources/Fuel.res.mjs +0 -7
  48. package/src/sources/HyperFuelSource.res +10 -11
  49. package/src/sources/HyperFuelSource.res.mjs +11 -11
  50. package/src/sources/HyperSync.res +3 -0
  51. package/src/sources/HyperSync.res.mjs +1 -0
  52. package/src/sources/HyperSync.resi +1 -0
  53. package/src/sources/HyperSyncClient.res +5 -1
  54. package/src/sources/HyperSyncSource.res +42 -24
  55. package/src/sources/HyperSyncSource.res.mjs +30 -26
  56. package/src/sources/RpcSource.res +7 -8
  57. package/src/sources/RpcSource.res.mjs +8 -8
  58. package/src/sources/SimulateSource.res +1 -1
  59. package/src/sources/Source.res +1 -1
  60. package/src/sources/SourceManager.res +17 -16
  61. package/src/sources/SourceManager.res.mjs +10 -15
  62. package/src/sources/Svm.res +6 -11
  63. package/src/sources/Svm.res.mjs +6 -14
  64. package/src/sources/SvmHyperSyncSource.res +24 -18
  65. package/src/sources/SvmHyperSyncSource.res.mjs +22 -15
  66. package/src/sources/TransactionStore.res +62 -35
  67. package/src/sources/TransactionStore.res.mjs +44 -18
  68. package/svm.schema.json +30 -37
  69. package/src/bindings/Hrtime.res +0 -58
  70. package/src/bindings/Hrtime.res.mjs +0 -90
  71. package/src/bindings/Hrtime.resi +0 -30
@@ -1,11 +1,11 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Utils from "../Utils.res.mjs";
4
- import * as Hrtime from "../bindings/Hrtime.res.mjs";
5
4
  import * as Source from "./Source.res.mjs";
6
5
  import * as Logging from "../Logging.res.mjs";
7
6
  import * as FetchState from "../FetchState.res.mjs";
8
7
  import * as Prometheus from "../Prometheus.res.mjs";
8
+ import * as Performance from "../bindings/Performance.res.mjs";
9
9
  import * as ErrorHandling from "../ErrorHandling.res.mjs";
10
10
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
11
11
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
@@ -139,7 +139,7 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
139
139
  disabled: false,
140
140
  lastFailedAt: undefined
141
141
  })),
142
- statusStart: Hrtime.makeTimer(),
142
+ statusStart: Performance.now(),
143
143
  status: "Idle",
144
144
  newBlockStallTimeout: newBlockStallTimeout,
145
145
  newBlockStallTimeoutRealtime: newBlockStallTimeoutRealtime,
@@ -148,6 +148,7 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
148
148
  getHeightRetryInterval: getHeightRetryInterval,
149
149
  activeSource: initialActiveSource,
150
150
  waitingForNewBlockStateId: undefined,
151
+ waitingLogged: false,
151
152
  fetchingPartitionsCount: 0,
152
153
  recoveryTimeout: recoveryTimeout,
153
154
  hasRealtime: hasRealtime,
@@ -172,8 +173,8 @@ function trackNewStatus(sourceManager, newStatus) {
172
173
  promCounter = Prometheus.IndexingQueryTime.counter;
173
174
  break;
174
175
  }
175
- Prometheus.SafeCounter.handleFloat(promCounter, sourceManager.activeSource.chain, Hrtime.toSecondsFloat(Hrtime.timeSince(sourceManager.statusStart)));
176
- sourceManager.statusStart = Hrtime.makeTimer();
176
+ Prometheus.SafeCounter.handleFloat(promCounter, sourceManager.activeSource.chain, Performance.secondsSince(sourceManager.statusStart));
177
+ sourceManager.statusStart = Performance.now();
177
178
  sourceManager.status = newStatus;
178
179
  }
179
180
 
@@ -408,10 +409,9 @@ async function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPo
408
409
  chainId: sourceManager.activeSource.chain,
409
410
  knownHeight: knownHeight
410
411
  });
411
- if (reducedPolling) {
412
- Logging.childTrace(logger, `Waiting for new blocks with reduced polling (` + (sourceManager.reducedPollingInterval / 1000 | 0).toString() + `s). Chain is caught up, waiting for other chains to backfill.`);
413
- } else {
414
- Logging.childTrace(logger, "Initiating check for new blocks.");
412
+ if (!sourceManager.waitingLogged) {
413
+ Logging.childTrace(logger, reducedPolling ? `Waiting for new blocks with reduced polling (` + (sourceManager.reducedPollingInterval / 1000 | 0).toString() + `s). Chain is caught up, waiting for other chains to backfill.` : "Initiating check for new blocks.");
414
+ sourceManager.waitingLogged = true;
415
415
  }
416
416
  let mainSources = getNextSources(sourceManager, isRealtime, undefined);
417
417
  let status = {
@@ -453,6 +453,7 @@ async function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPo
453
453
  source: source.name,
454
454
  newBlockHeight: newBlockHeight
455
455
  });
456
+ sourceManager.waitingLogged = false;
456
457
  status.contents = "Done";
457
458
  return newBlockHeight;
458
459
  }
@@ -499,13 +500,7 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
499
500
  retry: retry
500
501
  });
501
502
  try {
502
- let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, query.indexingAddresses, knownHeight, query.partitionId, query.selection, retry, logger$2);
503
- Logging.childTrace(logger$2, {
504
- msg: "Fetched block range from server",
505
- toBlock: response.latestFetchedBlockNumber,
506
- numEvents: response.parsedQueueItems.length,
507
- stats: response.stats
508
- });
503
+ let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, FetchState.deriveContractNameByAddress(query.addressesByContractName), knownHeight, query.partitionId, query.selection, retry, logger$2);
509
504
  sourceState.lastFailedAt = undefined;
510
505
  responseRef = response;
511
506
  } catch (raw_error) {
@@ -20,16 +20,12 @@ let transactionFields = [
20
20
  "tokenBalances",
21
21
  ]
22
22
 
23
- // Field name bit index (the code shared with the Rust store), built once.
24
- let transactionFieldCodes = TransactionStore.fieldCodes(transactionFields)
25
-
26
- let transactionFieldMask = (eventConfigs: array<Internal.eventConfig>): float =>
27
- eventConfigs->TransactionStore.mask(~codes=transactionFieldCodes)
23
+ // One instruction's selected transaction fields store selection bitmask.
24
+ // Computed per event at config build and cached on the event config.
25
+ let eventTransactionFieldMask = TransactionStore.makeMaskFn(transactionFields)
28
26
 
29
27
  let make = (~logger: Pino.t): Ecosystem.t => {
30
28
  name: Svm,
31
- blockFields: ["slot"],
32
- transactionFields,
33
29
  blockNumberName: "height",
34
30
  blockTimestampName: "time",
35
31
  blockHashName: "hash",
@@ -42,7 +38,6 @@ let make = (~logger: Pino.t): Ecosystem.t => {
42
38
  // parse. The schema is a no-op object that always surfaces `None`.
43
39
  onEventBlockFilterSchema: S.object(_ => None),
44
40
  logger,
45
- transactionFieldMask,
46
41
  toEvent: eventItem => eventItem.payload->(Utils.magic: Internal.eventPayload => Internal.event),
47
42
  toEventLogger: eventItem => {
48
43
  let instruction =
@@ -94,9 +89,9 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S
94
89
  getBlockHashes: (~blockNumbers as _, ~logger as _) =>
95
90
  JsError.throwWithMessage("Svm does not support getting block hashes"),
96
91
  getHeightOrThrow: async () => {
97
- let timerRef = Hrtime.makeTimer()
92
+ let timerRef = Performance.now()
98
93
  let height = await GetFinalizedSlot.route->Rest.fetch((), ~client)
99
- let seconds = timerRef->Hrtime.timeSince->Hrtime.toSecondsFloat
94
+ let seconds = timerRef->Performance.secondsSince
100
95
  Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getSlot")
101
96
  Prometheus.SourceRequestCount.addSeconds(
102
97
  ~sourceName=name,
@@ -110,7 +105,7 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S
110
105
  ~fromBlock as _,
111
106
  ~toBlock as _,
112
107
  ~addressesByContractName as _,
113
- ~indexingAddresses as _,
108
+ ~contractNameByAddress as _,
114
109
  ~knownHeight as _,
115
110
  ~partitionId as _,
116
111
  ~selection as _,
@@ -3,9 +3,9 @@
3
3
  import * as Rpc from "./Rpc.res.mjs";
4
4
  import * as Rest from "../vendored/Rest.res.mjs";
5
5
  import * as Utils from "../Utils.res.mjs";
6
- import * as Hrtime from "../bindings/Hrtime.res.mjs";
7
6
  import * as Logging from "../Logging.res.mjs";
8
7
  import * as Prometheus from "../Prometheus.res.mjs";
8
+ import * as Performance from "../bindings/Performance.res.mjs";
9
9
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
10
10
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
11
11
  import * as TransactionStore from "./TransactionStore.res.mjs";
@@ -30,17 +30,11 @@ let transactionFields = [
30
30
  "tokenBalances"
31
31
  ];
32
32
 
33
- let transactionFieldCodes = TransactionStore.fieldCodes(transactionFields);
34
-
35
- function transactionFieldMask(eventConfigs) {
36
- return TransactionStore.mask(eventConfigs, transactionFieldCodes);
37
- }
33
+ let eventTransactionFieldMask = TransactionStore.makeMaskFn(transactionFields);
38
34
 
39
35
  function make(logger) {
40
36
  return {
41
37
  name: "svm",
42
- blockFields: ["slot"],
43
- transactionFields: transactionFields,
44
38
  blockNumberName: "height",
45
39
  blockTimestampName: "time",
46
40
  blockHashName: "hash",
@@ -50,7 +44,6 @@ function make(logger) {
50
44
  onEventBlockFilterSchema: S$RescriptSchema.object(param => {}),
51
45
  logger: logger,
52
46
  toEvent: eventItem => eventItem.payload,
53
- transactionFieldMask: transactionFieldMask,
54
47
  toEventLogger: eventItem => {
55
48
  let instruction = eventItem.payload;
56
49
  return Logging.createChildFrom(logger, {
@@ -89,9 +82,9 @@ function makeRPCSource(chain, rpc, sourceForOpt) {
89
82
  pollingInterval: 10000,
90
83
  getBlockHashes: (param, param$1) => Stdlib_JsError.throwWithMessage("Svm does not support getting block hashes"),
91
84
  getHeightOrThrow: async () => {
92
- let timerRef = Hrtime.makeTimer();
85
+ let timerRef = Performance.now();
93
86
  let height = await Rest.fetch(route, undefined, client);
94
- let seconds = Hrtime.toSecondsFloat(Hrtime.timeSince(timerRef));
87
+ let seconds = Performance.secondsSince(timerRef);
95
88
  Prometheus.SourceRequestCount.increment(name, chain, "getSlot");
96
89
  Prometheus.SourceRequestCount.addSeconds(name, chain, "getSlot", seconds);
97
90
  return height;
@@ -103,10 +96,9 @@ function makeRPCSource(chain, rpc, sourceForOpt) {
103
96
  export {
104
97
  cleanUpRawEventFieldsInPlace,
105
98
  transactionFields,
106
- transactionFieldCodes,
107
- transactionFieldMask,
99
+ eventTransactionFieldMask,
108
100
  make,
109
101
  GetFinalizedSlot,
110
102
  makeRPCSource,
111
103
  }
112
- /* transactionFieldCodes Not a pure module */
104
+ /* eventTransactionFieldMask Not a pure module */
@@ -216,11 +216,11 @@ let probeRouter = (
216
216
  instr: SvmHyperSyncClient.ResponseTypes.instruction,
217
217
  byteLengthsDesc: array<int>,
218
218
  ~contractAddress,
219
- ~indexingAddresses,
219
+ ~contractNameByAddress,
220
220
  ) => {
221
221
  let probe = (dN: option<string>) => {
222
222
  let tag = EventRouter.getSvmEventId(~programId, ~discriminator=dN)
223
- router->EventRouter.get(~tag, ~contractAddress, ~blockNumber=instr.slot, ~indexingAddresses)
223
+ router->EventRouter.get(~tag, ~contractAddress, ~contractNameByAddress)
224
224
  }
225
225
 
226
226
  let result = byteLengthsDesc->Array.reduce(None, (acc, len) =>
@@ -319,26 +319,32 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
319
319
  )
320
320
  fields
321
321
  }
322
- // Every selected field except `tokenBalances` (its own table) is read off a
323
- // stored transaction record, so the transaction table must be fetched to
324
- // populate the store even when only `transactionIndex` (a key column) is
325
- // selected.
326
- let txFieldCount = selectedTxFields->Utils.Set.size
327
- let needsTransactions = txFieldCount > 1 || (txFieldCount === 1 && !needsTokenBalances)
322
+ // The transaction table is fetched only when a selected field is actually read
323
+ // off a stored transaction record. `transactionIndex` materialises from the
324
+ // store key and `tokenBalances` lives in its own table, so neither requires it.
325
+ let needsTransactions =
326
+ selectedTxFields
327
+ ->Utils.Set.toArray
328
+ ->Array.some(field =>
329
+ switch field {
330
+ | Internal.TransactionIndex | Internal.TokenBalances => false
331
+ | _ => true
332
+ }
333
+ )
328
334
 
329
335
  let getItemsOrThrow = async (
330
336
  ~fromBlock,
331
337
  ~toBlock,
332
338
  ~addressesByContractName as _,
333
- ~indexingAddresses,
339
+ ~contractNameByAddress,
334
340
  ~knownHeight,
335
341
  ~partitionId as _,
336
342
  ~selection as _,
337
343
  ~retry,
338
344
  ~logger,
339
345
  ) => {
340
- let totalTimeRef = Hrtime.makeTimer()
341
- let pageFetchRef = Hrtime.makeTimer()
346
+ let totalTimeRef = Performance.now()
347
+ let pageFetchRef = Performance.now()
342
348
 
343
349
  let instructionSelections = buildInstructionSelections(eventConfigs)
344
350
  // Under the server's default merge mode, requesting a table's columns is
@@ -384,9 +390,9 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
384
390
  ),
385
391
  )
386
392
  }
387
- let pageFetchTime = pageFetchRef->Hrtime.timeSince->Hrtime.toSecondsFloat
393
+ let pageFetchTime = pageFetchRef->Performance.secondsSince
388
394
 
389
- let parsingRef = Hrtime.makeTimer()
395
+ let parsingRef = Performance.now()
390
396
 
391
397
  // Per-slot unix timestamp lookup from the response's `blocks` table. Slots
392
398
  // without a block row (rare; usually skipped slots) fall back to `None`.
@@ -434,7 +440,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
434
440
  instr,
435
441
  byteLengths,
436
442
  ~contractAddress,
437
- ~indexingAddresses,
443
+ ~contractNameByAddress,
438
444
  )
439
445
 
440
446
  switch maybeConfig {
@@ -492,7 +498,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
492
498
  let _ = logger
493
499
  })
494
500
 
495
- let parsingTimeElapsed = parsingRef->Hrtime.timeSince->Hrtime.toSecondsFloat
501
+ let parsingTimeElapsed = parsingRef->Performance.secondsSince
496
502
  let highestSlot = resp.nextSlot - 1
497
503
  let latestBlockTime =
498
504
  blockTimeBySlot
@@ -507,7 +513,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
507
513
  blockHash: b.blockhash,
508
514
  })
509
515
 
510
- let totalTimeElapsed = totalTimeRef->Hrtime.timeSince->Hrtime.toSecondsFloat
516
+ let totalTimeElapsed = totalTimeRef->Performance.secondsSince
511
517
 
512
518
  {
513
519
  latestFetchedBlockTimestamp: latestBlockTime,
@@ -597,9 +603,9 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
597
603
  poweredByHyperSync: true,
598
604
  getBlockHashes,
599
605
  getHeightOrThrow: async () => {
600
- let timer = Hrtime.makeTimer()
606
+ let timer = Performance.now()
601
607
  let h = await client.getHeight()
602
- let seconds = timer->Hrtime.timeSince->Hrtime.toSecondsFloat
608
+ let seconds = timer->Performance.secondsSince
603
609
  Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getHeight")
604
610
  Prometheus.SourceRequestCount.addSeconds(
605
611
  ~sourceName=name,
@@ -1,9 +1,9 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Hrtime from "../bindings/Hrtime.res.mjs";
4
3
  import * as Source from "./Source.res.mjs";
5
4
  import * as Prometheus from "../Prometheus.res.mjs";
6
5
  import * as EventRouter from "./EventRouter.res.mjs";
6
+ import * as Performance from "../bindings/Performance.res.mjs";
7
7
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
8
8
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
9
9
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
@@ -185,10 +185,10 @@ function toSvmInstruction(instr, programName, instructionName, logs, block) {
185
185
  };
186
186
  }
187
187
 
188
- function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress, indexingAddresses) {
188
+ function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress, contractNameByAddress) {
189
189
  let probe = dN => {
190
190
  let tag = EventRouter.getSvmEventId(programId, dN);
191
- return EventRouter.get(router, tag, contractAddress, instr.slot, indexingAddresses);
191
+ return EventRouter.get(router, tag, contractAddress, contractNameByAddress);
192
192
  };
193
193
  let result = Stdlib_Array.reduce(byteLengthsDesc, undefined, (acc, len) => {
194
194
  if (acc !== undefined) {
@@ -279,11 +279,18 @@ function make(param) {
279
279
  return;
280
280
  }
281
281
  });
282
- let txFieldCount = selectedTxFields.size;
283
- let needsTransactions = txFieldCount > 1 || txFieldCount === 1 && !needsTokenBalances;
284
- let getItemsOrThrow = async (fromBlock, toBlock, param, indexingAddresses, knownHeight, param$1, param$2, retry, logger) => {
285
- let totalTimeRef = Hrtime.makeTimer();
286
- let pageFetchRef = Hrtime.makeTimer();
282
+ let needsTransactions = Array.from(selectedTxFields).some(field => {
283
+ switch (field) {
284
+ case "transactionIndex" :
285
+ case "tokenBalances" :
286
+ return false;
287
+ default:
288
+ return true;
289
+ }
290
+ });
291
+ let getItemsOrThrow = async (fromBlock, toBlock, param, contractNameByAddress, knownHeight, param$1, param$2, retry, logger) => {
292
+ let totalTimeRef = Performance.now();
293
+ let pageFetchRef = Performance.now();
287
294
  let instructionSelections = buildInstructionSelections(eventConfigs);
288
295
  let fields_block = [
289
296
  "slot",
@@ -344,8 +351,8 @@ function make(param) {
344
351
  };
345
352
  }
346
353
  let resp = match[0];
347
- let pageFetchTime = Hrtime.toSecondsFloat(Hrtime.timeSince(pageFetchRef));
348
- let parsingRef = Hrtime.makeTimer();
354
+ let pageFetchTime = Performance.secondsSince(pageFetchRef);
355
+ let parsingRef = Performance.now();
349
356
  let blockTimeBySlot = {};
350
357
  resp.data.blocks.forEach(b => {
351
358
  let t = b.blockTime;
@@ -377,7 +384,7 @@ function make(param) {
377
384
  let programId = instr.programId;
378
385
  let byteLengths = Stdlib_Option.getOr(orderingByProgram[instr.programId], []);
379
386
  let contractAddress = instr.programId;
380
- let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, indexingAddresses);
387
+ let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, contractNameByAddress);
381
388
  if (maybeConfig !== undefined) {
382
389
  let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress);
383
390
  let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({
@@ -404,14 +411,14 @@ function make(param) {
404
411
  });
405
412
  }
406
413
  });
407
- let parsingTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(parsingRef));
414
+ let parsingTimeElapsed = Performance.secondsSince(parsingRef);
408
415
  let highestSlot = resp.nextSlot - 1 | 0;
409
416
  let latestBlockTime = Stdlib_Option.getOr(blockTimeBySlot[highestSlot.toString()], 0);
410
417
  let blockHashes = resp.data.blocks.map(b => ({
411
418
  blockHash: b.blockhash,
412
419
  blockNumber: b.slot
413
420
  }));
414
- let totalTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(totalTimeRef));
421
+ let totalTimeElapsed = Performance.secondsSince(totalTimeRef);
415
422
  return {
416
423
  knownHeight: knownHeight,
417
424
  blockHashes: blockHashes,
@@ -513,9 +520,9 @@ function make(param) {
513
520
  pollingInterval: 1000,
514
521
  getBlockHashes: getBlockHashes,
515
522
  getHeightOrThrow: async () => {
516
- let timer = Hrtime.makeTimer();
523
+ let timer = Performance.now();
517
524
  let h = await client.getHeight();
518
- let seconds = Hrtime.toSecondsFloat(Hrtime.timeSince(timer));
525
+ let seconds = Performance.secondsSince(timer);
519
526
  Prometheus.SourceRequestCount.increment(name, chain, "getHeight");
520
527
  Prometheus.SourceRequestCount.addSeconds(name, chain, "getHeight", seconds);
521
528
  return h;
@@ -19,32 +19,45 @@ let fieldCodes = (fields: array<string>): dict<int> => {
19
19
 
20
20
  let pow2: int => float = %raw(`c => Math.pow(2, c)`)
21
21
 
22
- // Union of an ecosystem's selected transaction fields as a bitmask float (bit
23
- // `code` set ⇔ selected). Built arithmetically to dodge 32-bit JS bitwise ops.
24
- let mask = (eventConfigs: array<Internal.eventConfig>, ~codes: dict<int>): float => {
25
- let selected = Utils.Set.make()
26
- eventConfigs->Array.forEach(eventConfig =>
27
- eventConfig.selectedTransactionFields->Utils.Set.forEach(name =>
28
- switch codes->Utils.Dict.dangerouslyGetNonOption(name) {
29
- | Some(code) => selected->Utils.Set.add(code)->ignore
30
- | None => ()
31
- }
32
- )
22
+ // One event's selected transaction fields as a bitmask float (bit `code` set ⇔
23
+ // selected). Each field appears once, so summing `pow2(code)` sets distinct bits
24
+ // with no overlap; the result stays exact in f64 (codes span 0..31, so a mask is
25
+ // at most 2^32-1).
26
+ let maskFromFields = (selectedTransactionFields: Utils.Set.t<string>, ~codes: dict<int>): float => {
27
+ let mask = ref(0.)
28
+ selectedTransactionFields->Utils.Set.forEach(name =>
29
+ switch codes->Utils.Dict.dangerouslyGetNonOption(name) {
30
+ | Some(code) => mask := mask.contents +. pow2(code)
31
+ | None => ()
32
+ }
33
33
  )
34
- selected->Utils.Set.toArray->Array.reduce(0., (mask, code) => mask +. pow2(code))
34
+ mask.contents
35
+ }
36
+
37
+ // Build an ecosystem's per-event mask function from its ordered field-name
38
+ // array. The field codes are derived once and closed over.
39
+ let makeMaskFn = (fields: array<string>): (Utils.Set.t<string> => float) => {
40
+ let codes = fieldCodes(fields)
41
+ selectedTransactionFields => selectedTransactionFields->maskFromFields(~codes)
35
42
  }
36
43
 
44
+ // Bitwise OR of two per-event masks. Masks fit in 32 bits (≤32 transaction
45
+ // fields), so `>>> 0` recovers the unsigned value that a plain `|` renders
46
+ // negative once bit 31 is set.
47
+ let orMask: (float, float) => float = %raw(`(a, b) => (a | b) >>> 0`)
48
+
37
49
  // Drain another store (a fetch-response page) into this one.
38
50
  @send external merge: (t, t) => unit = "merge"
39
51
 
40
- // Bulk-materialise the fields selected by `mask` (one bit per field code) for
41
- // the given transactions, off the JS thread. Result is aligned with the input.
52
+ // Bulk-materialise transactions off the JS thread, one row per
53
+ // (blockNumbers[i], transactionIndices[i]) key, decoding only the fields set in
54
+ // that row's own masks[i]. Result is aligned with the input.
42
55
  @send
43
56
  external materialize: (
44
57
  t,
45
58
  ~blockNumbers: array<int>,
46
59
  ~transactionIndices: array<int>,
47
- ~mask: float,
60
+ ~masks: array<float>,
48
61
  ) => promise<array<Internal.eventTransaction>> = "materialize"
49
62
 
50
63
  // Drop transactions for blocks at or below the given block (already processed).
@@ -53,21 +66,28 @@ external materialize: (
53
66
  // Drop transactions for blocks above the given block (rolled back).
54
67
  @send external rollback: (t, int) => unit = "rollback"
55
68
 
56
- // Materialise the mask-selected fields for the store-backed items and write the
57
- // resulting transaction onto each item's payload. Items that already carry an
58
- // inline transaction (RPC/simulate/Fuel) are skipped. Store-backed items always
59
- // get a transaction objectthe selected fields, or `{}` when the chain
60
- // selected none so `event.transaction` is never `undefined` (matching the
61
- // inline sources). Deduped per (blockNumber, transactionIndex).
62
- let materializeItems = async (store: t, ~items: array<Internal.item>, ~mask: float) => {
69
+ // Materialise each store-backed item's selected transaction fields and write the
70
+ // resulting transaction onto its payload. Every event's mask comes from its own
71
+ // `eventConfig.transactionFieldMask`, so a transaction decodes only the fields
72
+ // the events on it selected a large field (e.g. `input`) never materialises
73
+ // for events that didn't ask for it. Items that already carry an inline
74
+ // transaction (RPC/simulate/Fuel) are skipped. Store-backed items always get a
75
+ // transaction object the selected fields, or `{}` when nothing was selected —
76
+ // so `event.transaction` is never `undefined` (matching the inline sources).
77
+ // Deduped per (blockNumber, transactionIndex); each row's mask is the OR of the
78
+ // masks of the events sharing that transaction.
79
+ let materializeItems = async (store: t, ~items: array<Internal.item>) => {
63
80
  // Store-backed items arrive in (block, logIndex) order, and a transaction's
64
81
  // logs are contiguous within a block, so events sharing a (blockNumber,
65
- // transactionIndex) are adjacent. Group them by extending the current run
66
- // rather than hashing a string key per item. A key recurring non-adjacently
67
- // just splits into two groups (one redundant decode) — never incorrect.
82
+ // transactionIndex) are adjacent regardless of event. Group them by extending
83
+ // the current run rather than hashing a string key per item. A key recurring
84
+ // non-adjacently just splits into two groups (one redundant decode) — never
85
+ // incorrect.
68
86
  let blockNumbers = []
69
87
  let transactionIndices = []
88
+ let masks = []
70
89
  let payloadGroups = []
90
+ let anySelected = ref(false)
71
91
 
72
92
  items->Array.forEach(item =>
73
93
  switch item {
@@ -77,6 +97,10 @@ let materializeItems = async (store: t, ~items: array<Internal.item>, ~mask: flo
77
97
  | Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
78
98
  | None =>
79
99
  let {blockNumber, transactionIndex} = eventItem
100
+ let mask = eventItem.eventConfig.transactionFieldMask
101
+ if mask != 0. {
102
+ anySelected := true
103
+ }
80
104
  let last = payloadGroups->Array.length - 1
81
105
  if (
82
106
  last >= 0 &&
@@ -84,9 +108,11 @@ let materializeItems = async (store: t, ~items: array<Internal.item>, ~mask: flo
84
108
  transactionIndices->Array.getUnsafe(last) == transactionIndex
85
109
  ) {
86
110
  payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
111
+ masks->Array.setUnsafe(last, orMask(masks->Array.getUnsafe(last), mask))
87
112
  } else {
88
113
  blockNumbers->Array.push(blockNumber)
89
114
  transactionIndices->Array.push(transactionIndex)
115
+ masks->Array.push(mask)
90
116
  payloadGroups->Array.push([eventItem.payload])
91
117
  }
92
118
  }
@@ -95,17 +121,18 @@ let materializeItems = async (store: t, ~items: array<Internal.item>, ~mask: flo
95
121
  )
96
122
 
97
123
  if payloadGroups->Utils.Array.notEmpty {
98
- // A zero mask selects no fields, so there's nothing to decode — each
99
- // store-backed item still gets an empty transaction object, so
100
- // `event.transaction` is never undefined (matching the inline sources).
101
- let txs = if mask == 0. {
102
- payloadGroups->Array.map((_): Internal.eventTransaction => %raw(`{}`))
124
+ if anySelected.contents {
125
+ let txs = await store->materialize(~blockNumbers, ~transactionIndices, ~masks)
126
+ payloadGroups->Array.forEachWithIndex((payloads, i) => {
127
+ let tx = txs->Array.getUnsafe(i)
128
+ payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
129
+ })
103
130
  } else {
104
- await store->materialize(~blockNumbers, ~transactionIndices, ~mask)
131
+ // No event selected any field: stamp an empty transaction object so
132
+ // `event.transaction` is never undefined, without a materialize call.
133
+ payloadGroups->Array.forEach(payloads =>
134
+ payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(%raw(`{}`)))
135
+ )
105
136
  }
106
- payloadGroups->Array.forEachWithIndex((payloads, i) => {
107
- let tx = txs->Array.getUnsafe(i)
108
- payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
109
- })
110
137
  }
111
138
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  import * as Core from "../Core.res.mjs";
4
4
  import * as Utils from "../Utils.res.mjs";
5
- import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
5
 
7
6
  function make() {
8
7
  return Core.getAddon().TransactionStore.new();
@@ -18,24 +17,35 @@ function fieldCodes(fields) {
18
17
 
19
18
  let pow2 = (c => Math.pow(2, c));
20
19
 
21
- function mask(eventConfigs, codes) {
22
- let selected = new Set();
23
- eventConfigs.forEach(eventConfig => {
24
- eventConfig.selectedTransactionFields.forEach(name => {
25
- let code = codes[name];
26
- if (code !== undefined) {
27
- selected.add(code);
28
- return;
29
- }
30
- });
20
+ function maskFromFields(selectedTransactionFields, codes) {
21
+ let mask = {
22
+ contents: 0
23
+ };
24
+ selectedTransactionFields.forEach(name => {
25
+ let code = codes[name];
26
+ if (code !== undefined) {
27
+ mask.contents = mask.contents + pow2(code);
28
+ return;
29
+ }
31
30
  });
32
- return Stdlib_Array.reduce(Array.from(selected), 0, (mask, code) => mask + pow2(code));
31
+ return mask.contents;
32
+ }
33
+
34
+ function makeMaskFn(fields) {
35
+ let codes = fieldCodes(fields);
36
+ return selectedTransactionFields => maskFromFields(selectedTransactionFields, codes);
33
37
  }
34
38
 
35
- async function materializeItems(store, items, mask) {
39
+ let orMask = ((a, b) => (a | b) >>> 0);
40
+
41
+ async function materializeItems(store, items) {
36
42
  let blockNumbers = [];
37
43
  let transactionIndices = [];
44
+ let masks = [];
38
45
  let payloadGroups = [];
46
+ let anySelected = {
47
+ contents: false
48
+ };
39
49
  items.forEach(item => {
40
50
  if (item.kind !== 0) {
41
51
  return;
@@ -46,23 +56,37 @@ async function materializeItems(store, items, mask) {
46
56
  }
47
57
  let transactionIndex = item.transactionIndex;
48
58
  let blockNumber = item.blockNumber;
59
+ let mask = item.eventConfig.transactionFieldMask;
60
+ if (mask !== 0) {
61
+ anySelected.contents = true;
62
+ }
49
63
  let last = payloadGroups.length - 1 | 0;
50
64
  if (last >= 0 && blockNumbers[last] === blockNumber && transactionIndices[last] === transactionIndex) {
51
65
  payloadGroups[last].push(item.payload);
66
+ masks[last] = orMask(masks[last], mask);
52
67
  } else {
53
68
  blockNumbers.push(blockNumber);
54
69
  transactionIndices.push(transactionIndex);
70
+ masks.push(mask);
55
71
  payloadGroups.push([item.payload]);
56
72
  }
57
73
  });
58
74
  if (!Utils.$$Array.notEmpty(payloadGroups)) {
59
75
  return;
60
76
  }
61
- let txs = mask === 0 ? payloadGroups.map(param => ({})) : await store.materialize(blockNumbers, transactionIndices, mask);
62
- payloadGroups.forEach((payloads, i) => {
63
- let tx = txs[i];
77
+ if (anySelected.contents) {
78
+ let txs = await store.materialize(blockNumbers, transactionIndices, masks);
79
+ payloadGroups.forEach((payloads, i) => {
80
+ let tx = txs[i];
81
+ payloads.forEach(payload => {
82
+ payload.transaction = tx;
83
+ });
84
+ });
85
+ return;
86
+ }
87
+ payloadGroups.forEach(payloads => {
64
88
  payloads.forEach(payload => {
65
- payload.transaction = tx;
89
+ payload.transaction = {};
66
90
  });
67
91
  });
68
92
  }
@@ -71,7 +95,9 @@ export {
71
95
  make,
72
96
  fieldCodes,
73
97
  pow2,
74
- mask,
98
+ maskFromFields,
99
+ makeMaskFn,
100
+ orMask,
75
101
  materializeItems,
76
102
  }
77
103
  /* Core Not a pure module */