envio 3.3.0-alpha.1 → 3.3.0-alpha.3

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 (84) hide show
  1. package/index.d.ts +23 -21
  2. package/package.json +6 -6
  3. package/src/Batch.res +0 -6
  4. package/src/Batch.res.mjs +0 -11
  5. package/src/BatchProcessing.res +2 -12
  6. package/src/BatchProcessing.res.mjs +2 -7
  7. package/src/ChainFetching.res +42 -6
  8. package/src/ChainFetching.res.mjs +30 -11
  9. package/src/ChainMetadata.res +1 -11
  10. package/src/ChainMetadata.res.mjs +1 -10
  11. package/src/ChainState.res +150 -1
  12. package/src/ChainState.res.mjs +138 -9
  13. package/src/ChainState.resi +51 -1
  14. package/src/Config.res +3 -6
  15. package/src/Config.res.mjs +2 -3
  16. package/src/Core.res +7 -0
  17. package/src/CrossChainState.res +24 -26
  18. package/src/CrossChainState.res.mjs +19 -16
  19. package/src/Ecosystem.res +6 -3
  20. package/src/Envio.res +3 -2
  21. package/src/EventConfigBuilder.res +61 -23
  22. package/src/EventConfigBuilder.res.mjs +27 -13
  23. package/src/EventProcessing.res +63 -21
  24. package/src/EventProcessing.res.mjs +47 -22
  25. package/src/FetchState.res +53 -43
  26. package/src/FetchState.res.mjs +71 -54
  27. package/src/HandlerLoader.res +10 -1
  28. package/src/HandlerLoader.res.mjs +15 -8
  29. package/src/Internal.res +46 -7
  30. package/src/Internal.res.mjs +19 -1
  31. package/src/LoadLayer.res +5 -5
  32. package/src/LoadLayer.res.mjs +6 -6
  33. package/src/Main.res +3 -50
  34. package/src/Main.res.mjs +2 -23
  35. package/src/PgStorage.res +4 -4
  36. package/src/PgStorage.res.mjs +5 -5
  37. package/src/Prometheus.res +10 -10
  38. package/src/Prometheus.res.mjs +9 -9
  39. package/src/PruneStaleHistory.res +2 -2
  40. package/src/PruneStaleHistory.res.mjs +3 -3
  41. package/src/Rollback.res +3 -3
  42. package/src/Rollback.res.mjs +4 -4
  43. package/src/SimulateItems.res +80 -15
  44. package/src/SimulateItems.res.mjs +65 -21
  45. package/src/TestIndexer.res +48 -36
  46. package/src/TestIndexer.res.mjs +35 -28
  47. package/src/bindings/Performance.res +7 -0
  48. package/src/bindings/Performance.res.mjs +21 -0
  49. package/src/bindings/Performance.resi +7 -0
  50. package/src/sources/EventRouter.res +17 -21
  51. package/src/sources/EventRouter.res.mjs +6 -9
  52. package/src/sources/Evm.res +47 -67
  53. package/src/sources/Evm.res.mjs +42 -65
  54. package/src/sources/Fuel.res +3 -3
  55. package/src/sources/Fuel.res.mjs +1 -6
  56. package/src/sources/HyperFuelSource.res +15 -11
  57. package/src/sources/HyperFuelSource.res.mjs +13 -11
  58. package/src/sources/HyperSync.res +7 -1
  59. package/src/sources/HyperSync.res.mjs +6 -3
  60. package/src/sources/HyperSync.resi +2 -0
  61. package/src/sources/HyperSyncClient.res +14 -79
  62. package/src/sources/HyperSyncClient.res.mjs +1 -22
  63. package/src/sources/HyperSyncSource.res +44 -27
  64. package/src/sources/HyperSyncSource.res.mjs +32 -27
  65. package/src/sources/RpcSource.res +14 -10
  66. package/src/sources/RpcSource.res.mjs +11 -9
  67. package/src/sources/SimulateSource.res +4 -2
  68. package/src/sources/SimulateSource.res.mjs +1 -0
  69. package/src/sources/Source.res +5 -1
  70. package/src/sources/SourceManager.res +17 -16
  71. package/src/sources/SourceManager.res.mjs +10 -15
  72. package/src/sources/Svm.res +23 -6
  73. package/src/sources/Svm.res.mjs +24 -6
  74. package/src/sources/SvmHyperSyncClient.res +3 -29
  75. package/src/sources/SvmHyperSyncSource.res +78 -96
  76. package/src/sources/SvmHyperSyncSource.res.mjs +79 -91
  77. package/src/sources/TransactionStore.res +128 -0
  78. package/src/sources/TransactionStore.res.mjs +97 -0
  79. package/src/tui/Tui.res +13 -16
  80. package/src/tui/Tui.res.mjs +12 -14
  81. package/svm.schema.json +30 -37
  82. package/src/bindings/Hrtime.res +0 -58
  83. package/src/bindings/Hrtime.res.mjs +0 -90
  84. package/src/bindings/Hrtime.resi +0 -30
@@ -17,6 +17,30 @@ type t = {
17
17
  mutable pendingBudget: float,
18
18
  mutable reorgDetection: ReorgDetection.t,
19
19
  mutable safeCheckpointTracking: option<SafeCheckpointTracking.t>,
20
+ // Holds this chain's transactions (kept in Rust) keyed by (blockNumber,
21
+ // transactionIndex). Fetch responses merge their page in; entries are pruned
22
+ // as the chain progresses and dropped above the target on rollback.
23
+ transactionStore: TransactionStore.t,
24
+ // Bitmask of the transaction fields the store materialises at batch prep.
25
+ transactionFieldMask: float,
26
+ }
27
+
28
+ // Per-chain shape returned by the status API.
29
+ type chainData = {
30
+ chainId: float,
31
+ poweredByHyperSync: bool,
32
+ firstEventBlockNumber: option<int>,
33
+ latestProcessedBlock: option<int>,
34
+ timestampCaughtUpToHeadOrEndblock: option<Date.t>,
35
+ numEventsProcessed: float,
36
+ latestFetchedBlockNumber: int,
37
+ // Need this for API backwards compatibility
38
+ @as("currentBlockHeight")
39
+ knownHeight: int,
40
+ numBatchesFetched: int,
41
+ startBlock: int,
42
+ endBlock: option<int>,
43
+ numAddresses: int,
20
44
  }
21
45
 
22
46
  let configAddresses = (chainConfig: Config.chain): array<Internal.indexingAddress> => {
@@ -43,6 +67,7 @@ let make = (
43
67
  ~numEventsProcessed=0.,
44
68
  ~timestampCaughtUpToHeadOrEndblock=None,
45
69
  ~isProgressAtHead=false,
70
+ ~transactionFieldMask=0.,
46
71
  ~logger: Pino.t,
47
72
  ): t => {
48
73
  logger,
@@ -56,6 +81,8 @@ let make = (
56
81
  pendingBudget: 0.,
57
82
  reorgDetection,
58
83
  safeCheckpointTracking,
84
+ transactionStore: TransactionStore.make(),
85
+ transactionFieldMask,
59
86
  }
60
87
 
61
88
  let makeInternal = (
@@ -301,6 +328,7 @@ let makeInternal = (
301
328
  ~committedProgressBlockNumber=progressBlockNumber,
302
329
  ~timestampCaughtUpToHeadOrEndblock,
303
330
  ~numEventsProcessed,
331
+ ~transactionFieldMask=config.ecosystem.transactionFieldMask(eventConfigs),
304
332
  ~logger,
305
333
  )
306
334
  }
@@ -377,7 +405,6 @@ let makeFromDbState = (
377
405
  // --- Read accessors. ---
378
406
 
379
407
  let logger = (cs: t) => cs.logger
380
- let fetchState = (cs: t) => cs.fetchState
381
408
  let sourceManager = (cs: t) => cs.sourceManager
382
409
  let chainConfig = (cs: t) => cs.chainConfig
383
410
  let reorgDetection = (cs: t) => cs.reorgDetection
@@ -388,6 +415,19 @@ let numEventsProcessed = (cs: t) => cs.numEventsProcessed
388
415
  let pendingBudget = (cs: t) => cs.pendingBudget
389
416
  let timestampCaughtUpToHeadOrEndblock = (cs: t) => cs.timestampCaughtUpToHeadOrEndblock
390
417
 
418
+ // Fetch-frontier reads. The FetchState is owned here; callers go through these
419
+ // rather than reaching into it.
420
+ let knownHeight = (cs: t) => cs.fetchState.knownHeight
421
+ let indexingAddresses = (cs: t) => cs.fetchState.indexingAddresses
422
+ let bufferSize = (cs: t) => cs.fetchState->FetchState.bufferSize
423
+ let bufferReadyCount = (cs: t) => cs.fetchState->FetchState.bufferReadyCount
424
+ let getProgressPercentage = (cs: t) => cs.fetchState->FetchState.getProgressPercentage
425
+ let getProgressPercentageAt = (cs: t, ~blockNumber) =>
426
+ cs.fetchState->FetchState.getProgressPercentageAt(~blockNumber)
427
+ let hasReadyItem = (cs: t) =>
428
+ cs.fetchState->FetchState.isActivelyIndexing && cs.fetchState->FetchState.hasReadyItem
429
+ let isReadyToEnterReorgThreshold = (cs: t) => cs.fetchState->FetchState.isReadyToEnterReorgThreshold
430
+
391
431
  // Mark queries as in flight and reserve their estimated size against the shared
392
432
  // buffer budget in one step, so the counter stays in sync with the pending
393
433
  // queries it tracks.
@@ -404,6 +444,30 @@ let resetPendingQueries = (cs: t) => {
404
444
  cs.pendingBudget = 0.
405
445
  }
406
446
 
447
+ // Propose the chain's candidate queries against the shared buffer budget,
448
+ // accounting for this chain's own in-flight reservations.
449
+ let getNextQuery = (cs: t, ~budget) =>
450
+ cs.fetchState->FetchState.getNextQuery(~budget, ~chainPendingBudget=cs.pendingBudget)
451
+
452
+ // Run a fetch tick for this chain against its sources, feeding the owned fetch
453
+ // frontier to the source manager.
454
+ let dispatch = (
455
+ cs: t,
456
+ ~executeQuery,
457
+ ~waitForNewBlock,
458
+ ~onNewBlock,
459
+ ~action: FetchState.nextQuery,
460
+ ~stateId,
461
+ ) =>
462
+ cs.sourceManager->SourceManager.dispatch(
463
+ ~fetchState=cs.fetchState,
464
+ ~executeQuery,
465
+ ~waitForNewBlock,
466
+ ~onNewBlock,
467
+ ~action,
468
+ ~stateId,
469
+ )
470
+
407
471
  // --- Derived (pure). ---
408
472
 
409
473
  let hasProcessedToEndblock = (cs: t) => {
@@ -426,11 +490,34 @@ let isReady = (cs: t) => cs.timestampCaughtUpToHeadOrEndblock !== None
426
490
  // True once the fetch frontier has reached the head/endBlock for this chain.
427
491
  let isFetchingAtHead = (cs: t) => cs.fetchState->FetchState.isFetchingAtHead
428
492
 
493
+ // Reached head on a chain with no configured endBlock — used by auto-exit to
494
+ // detect that no events were found in the start..head range.
495
+ let isAtHeadWithoutEndBlock = (cs: t) =>
496
+ cs.isProgressAtHead && cs.fetchState.endBlock->Option.isNone
497
+
429
498
  // --- State transitions. The chain state is mutated only through these; each
430
499
  // owns a cohesive update so callers don't juggle individual fields. ---
431
500
 
432
501
  // Apply a fetch response: register any new dynamic contracts, append the items
433
502
  // to the buffer and advance the known head.
503
+ // Materialise the chain store's selected transaction fields onto a batch's
504
+ // items at batch prep (the persistent-store path).
505
+ let materializeBatchItems = (cs: t, ~items: array<Internal.item>) =>
506
+ cs.transactionStore->TransactionStore.materializeItems(~items, ~mask=cs.transactionFieldMask)
507
+
508
+ // Materialise a fetch-response page's transactions onto its items before
509
+ // contract-register handlers read them. `None` pages (RPC/Fuel/Simulate keep the
510
+ // transaction inline) are a no-op.
511
+ let materializePageItems = (
512
+ cs: t,
513
+ ~items: array<Internal.item>,
514
+ ~page: option<TransactionStore.t>,
515
+ ) =>
516
+ switch page {
517
+ | Some(store) => store->TransactionStore.materializeItems(~items, ~mask=cs.transactionFieldMask)
518
+ | None => Promise.resolve()
519
+ }
520
+
434
521
  let handleQueryResult = (
435
522
  cs: t,
436
523
  ~query: FetchState.query,
@@ -438,7 +525,15 @@ let handleQueryResult = (
438
525
  ~newItemsWithDcs,
439
526
  ~latestFetchedBlock,
440
527
  ~knownHeight,
528
+ ~transactionStore as page: option<TransactionStore.t>,
441
529
  ) => {
530
+ // Merge this response's page into the chain store in lockstep with appending
531
+ // its items to the buffer. Inline-transaction sources contribute no page.
532
+ switch page {
533
+ | Some(page) => cs.transactionStore->TransactionStore.merge(page)
534
+ | None => ()
535
+ }
536
+
442
537
  let fs = switch newItemsWithDcs {
443
538
  | [] => cs.fetchState
444
539
  | _ => cs.fetchState->FetchState.registerDynamicContracts(newItemsWithDcs)
@@ -491,6 +586,56 @@ let setEndBlockToFirstEvent = (cs: t, ~blockNumber) =>
491
586
  let enterReorgThreshold = (cs: t) =>
492
587
  cs.fetchState = cs.fetchState->FetchState.updateInternal(~blockLag=cs.chainConfig.blockLag)
493
588
 
589
+ // Snapshot the chain's metadata fields for staging into the chains table.
590
+ let toChainMetadata = (cs: t): InternalTable.Chains.metaFields => {
591
+ firstEventBlockNumber: cs.fetchState.firstEventBlock->Null.fromOption,
592
+ isHyperSync: (cs.sourceManager->SourceManager.getActiveSource).poweredByHyperSync,
593
+ latestFetchedBlockNumber: cs.fetchState->FetchState.bufferBlockNumber,
594
+ timestampCaughtUpToHeadOrEndblock: cs.timestampCaughtUpToHeadOrEndblock->Null.fromOption,
595
+ }
596
+
597
+ // Snapshot the chain's view for the status API.
598
+ let toChainData = (cs: t): chainData => {
599
+ chainId: cs.chainConfig.id->Int.toFloat,
600
+ poweredByHyperSync: (cs.sourceManager->SourceManager.getActiveSource).poweredByHyperSync,
601
+ firstEventBlockNumber: cs.fetchState.firstEventBlock,
602
+ latestProcessedBlock: cs.committedProgressBlockNumber === -1
603
+ ? None
604
+ : Some(cs.committedProgressBlockNumber),
605
+ timestampCaughtUpToHeadOrEndblock: cs.timestampCaughtUpToHeadOrEndblock,
606
+ numEventsProcessed: cs.numEventsProcessed,
607
+ latestFetchedBlockNumber: Pervasives.max(cs.fetchState->FetchState.bufferBlockNumber, 0),
608
+ knownHeight: cs->hasProcessedToEndblock
609
+ ? cs.fetchState.endBlock->Option.getOr(cs.fetchState.knownHeight)
610
+ : cs.fetchState.knownHeight,
611
+ numBatchesFetched: 0,
612
+ startBlock: cs.fetchState.startBlock,
613
+ endBlock: cs.fetchState.endBlock,
614
+ numAddresses: cs.fetchState->FetchState.numAddresses,
615
+ }
616
+
617
+ // Snapshot the inputs a batch build needs from this chain.
618
+ let toChainBeforeBatch = (cs: t): Batch.chainBeforeBatch => {
619
+ fetchState: cs.fetchState,
620
+ progressBlockNumber: cs.committedProgressBlockNumber,
621
+ totalEventsProcessed: cs.numEventsProcessed,
622
+ sourceBlockNumber: cs.fetchState.knownHeight,
623
+ reorgDetection: cs.reorgDetection,
624
+ chainConfig: cs.chainConfig,
625
+ }
626
+
627
+ // Whether the chain's post-batch fetch frontier is ready to cross into the reorg
628
+ // threshold, using the batch's progressed frontier when this chain advanced.
629
+ let isReadyToEnterReorgThresholdAfterBatch = (cs: t, ~batch: Batch.t) => {
630
+ let fetchState = switch batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(
631
+ cs.fetchState.chainId,
632
+ ) {
633
+ | Some(chainAfterBatch) => chainAfterBatch.fetchState
634
+ | None => cs.fetchState
635
+ }
636
+ fetchState->FetchState.isReadyToEnterReorgThreshold
637
+ }
638
+
494
639
  // Commit the post-batch fetch frontier for a chain that progressed in the batch,
495
640
  // applying blockLag when this batch also crosses into the reorg threshold.
496
641
  let advanceAfterBatch = (cs: t, ~batch: Batch.t, ~enteringReorgThreshold) =>
@@ -551,6 +696,8 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t) => {
551
696
 
552
697
  cs.committedProgressBlockNumber = chainAfterBatch.progressBlockNumber
553
698
  cs.numEventsProcessed = chainAfterBatch.totalEventsProcessed
699
+ // Processed blocks' transactions are no longer needed.
700
+ cs.transactionStore->TransactionStore.prune(chainAfterBatch.progressBlockNumber)
554
701
  cs.isProgressAtHead = cs.isProgressAtHead || chainAfterBatch.isProgressAtHeadWhenBatchCreated
555
702
  switch cs.safeCheckpointTracking {
556
703
  | Some(safeCheckpointTracking) =>
@@ -624,6 +771,7 @@ let rollback = (
624
771
  | None => ()
625
772
  }
626
773
  cs.fetchState = cs.fetchState->FetchState.rollback(~targetBlockNumber=newProgressBlockNumber)
774
+ cs.transactionStore->TransactionStore.rollback(newProgressBlockNumber)
627
775
  cs.committedProgressBlockNumber = newProgressBlockNumber
628
776
  cs.numEventsProcessed = newTotalEventsProcessed
629
777
  | None =>
@@ -634,6 +782,7 @@ let rollback = (
634
782
  )
635
783
  cs.fetchState =
636
784
  cs.fetchState->FetchState.rollback(~targetBlockNumber=rollbackTargetBlockNumber)
785
+ cs.transactionStore->TransactionStore.rollback(rollbackTargetBlockNumber)
637
786
  cs.committedProgressBlockNumber = Pervasives.min(
638
787
  cs.committedProgressBlockNumber,
639
788
  rollbackTargetBlockNumber,
@@ -10,6 +10,7 @@ import * as EvmChain from "./sources/EvmChain.res.mjs";
10
10
  import * as FetchState from "./FetchState.res.mjs";
11
11
  import * as Prometheus from "./Prometheus.res.mjs";
12
12
  import * as EventRouter from "./sources/EventRouter.res.mjs";
13
+ import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
13
14
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
14
15
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
15
16
  import * as SourceManager from "./sources/SourceManager.res.mjs";
@@ -19,6 +20,7 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
19
20
  import * as HyperFuelSource from "./sources/HyperFuelSource.res.mjs";
20
21
  import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
21
22
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
23
+ import * as TransactionStore from "./sources/TransactionStore.res.mjs";
22
24
  import * as SvmHyperSyncSource from "./sources/SvmHyperSyncSource.res.mjs";
23
25
  import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
24
26
 
@@ -36,11 +38,12 @@ function configAddresses(chainConfig) {
36
38
  return addresses;
37
39
  }
38
40
 
39
- function make(chainConfig, fetchState, sourceManager, reorgDetection, committedProgressBlockNumber, safeCheckpointTrackingOpt, numEventsProcessedOpt, timestampCaughtUpToHeadOrEndblockOpt, isProgressAtHeadOpt, logger) {
41
+ function make(chainConfig, fetchState, sourceManager, reorgDetection, committedProgressBlockNumber, safeCheckpointTrackingOpt, numEventsProcessedOpt, timestampCaughtUpToHeadOrEndblockOpt, isProgressAtHeadOpt, transactionFieldMaskOpt, logger) {
40
42
  let safeCheckpointTracking = safeCheckpointTrackingOpt !== undefined ? Primitive_option.valFromOption(safeCheckpointTrackingOpt) : undefined;
41
43
  let numEventsProcessed = numEventsProcessedOpt !== undefined ? numEventsProcessedOpt : 0;
42
44
  let timestampCaughtUpToHeadOrEndblock = timestampCaughtUpToHeadOrEndblockOpt !== undefined ? Primitive_option.valFromOption(timestampCaughtUpToHeadOrEndblockOpt) : undefined;
43
45
  let isProgressAtHead = isProgressAtHeadOpt !== undefined ? isProgressAtHeadOpt : false;
46
+ let transactionFieldMask = transactionFieldMaskOpt !== undefined ? transactionFieldMaskOpt : 0;
44
47
  return {
45
48
  logger: logger,
46
49
  fetchState: fetchState,
@@ -52,7 +55,9 @@ function make(chainConfig, fetchState, sourceManager, reorgDetection, committedP
52
55
  numEventsProcessed: numEventsProcessed,
53
56
  pendingBudget: 0,
54
57
  reorgDetection: reorgDetection,
55
- safeCheckpointTracking: safeCheckpointTracking
58
+ safeCheckpointTracking: safeCheckpointTracking,
59
+ transactionStore: TransactionStore.make(),
60
+ transactionFieldMask: transactionFieldMask
56
61
  };
57
62
  }
58
63
 
@@ -171,7 +176,7 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
171
176
  sources$1 = sources._0;
172
177
  break;
173
178
  }
174
- return make(chainConfig, fetchState, SourceManager.make(sources$1, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), ReorgDetection.make(chainReorgCheckpoints, maxReorgDepth, config.shouldRollbackOnReorg), progressBlockNumber, Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), numEventsProcessed, Primitive_option.some(timestampCaughtUpToHeadOrEndblock), undefined, logger);
179
+ return make(chainConfig, fetchState, SourceManager.make(sources$1, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), ReorgDetection.make(chainReorgCheckpoints, maxReorgDepth, config.shouldRollbackOnReorg), progressBlockNumber, Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), numEventsProcessed, Primitive_option.some(timestampCaughtUpToHeadOrEndblock), undefined, config.ecosystem.transactionFieldMask(eventConfigs), logger);
175
180
  }
176
181
 
177
182
  function makeFromConfig(chainConfig, config, registrations, knownHeight) {
@@ -195,10 +200,6 @@ function logger(cs) {
195
200
  return cs.logger;
196
201
  }
197
202
 
198
- function fetchState(cs) {
199
- return cs.fetchState;
200
- }
201
-
202
203
  function sourceManager(cs) {
203
204
  return cs.sourceManager;
204
205
  }
@@ -235,11 +236,55 @@ function timestampCaughtUpToHeadOrEndblock(cs) {
235
236
  return cs.timestampCaughtUpToHeadOrEndblock;
236
237
  }
237
238
 
239
+ function knownHeight(cs) {
240
+ return cs.fetchState.knownHeight;
241
+ }
242
+
243
+ function indexingAddresses(cs) {
244
+ return cs.fetchState.indexingAddresses;
245
+ }
246
+
247
+ function bufferSize(cs) {
248
+ return FetchState.bufferSize(cs.fetchState);
249
+ }
250
+
251
+ function bufferReadyCount(cs) {
252
+ return FetchState.bufferReadyCount(cs.fetchState);
253
+ }
254
+
255
+ function getProgressPercentage(cs) {
256
+ return FetchState.getProgressPercentage(cs.fetchState);
257
+ }
258
+
259
+ function getProgressPercentageAt(cs, blockNumber) {
260
+ return FetchState.getProgressPercentageAt(cs.fetchState, blockNumber);
261
+ }
262
+
263
+ function hasReadyItem(cs) {
264
+ if (FetchState.isActivelyIndexing(cs.fetchState)) {
265
+ return FetchState.hasReadyItem(cs.fetchState);
266
+ } else {
267
+ return false;
268
+ }
269
+ }
270
+
271
+ function isReadyToEnterReorgThreshold(cs) {
272
+ return FetchState.isReadyToEnterReorgThreshold(cs.fetchState);
273
+ }
274
+
238
275
  function startFetchingQueries(cs, queries) {
239
276
  FetchState.startFetchingQueries(cs.fetchState, queries);
240
277
  cs.pendingBudget = cs.pendingBudget + Stdlib_Array.reduce(queries, 0, (acc, query) => acc + query.estResponseSize);
241
278
  }
242
279
 
280
+ function getNextQuery(cs, budget) {
281
+ return FetchState.getNextQuery(cs.fetchState, budget, cs.pendingBudget);
282
+ }
283
+
284
+ function dispatch(cs, executeQuery, waitForNewBlock, onNewBlock, action, stateId) {
285
+ return SourceManager.dispatch(cs.sourceManager, cs.fetchState, executeQuery, waitForNewBlock, onNewBlock, action, stateId);
286
+ }
287
+
243
288
  function hasProcessedToEndblock(cs) {
244
289
  let fetchState = cs.fetchState;
245
290
  let committedProgressBlockNumber = cs.committedProgressBlockNumber;
@@ -272,7 +317,30 @@ function isFetchingAtHead(cs) {
272
317
  return FetchState.isFetchingAtHead(cs.fetchState);
273
318
  }
274
319
 
275
- function handleQueryResult(cs, query, newItems, newItemsWithDcs, latestFetchedBlock, knownHeight) {
320
+ function isAtHeadWithoutEndBlock(cs) {
321
+ if (cs.isProgressAtHead) {
322
+ return Stdlib_Option.isNone(cs.fetchState.endBlock);
323
+ } else {
324
+ return false;
325
+ }
326
+ }
327
+
328
+ function materializeBatchItems(cs, items) {
329
+ return TransactionStore.materializeItems(cs.transactionStore, items, cs.transactionFieldMask);
330
+ }
331
+
332
+ function materializePageItems(cs, items, page) {
333
+ if (page !== undefined) {
334
+ return TransactionStore.materializeItems(Primitive_option.valFromOption(page), items, cs.transactionFieldMask);
335
+ } else {
336
+ return Promise.resolve();
337
+ }
338
+ }
339
+
340
+ function handleQueryResult(cs, query, newItems, newItemsWithDcs, latestFetchedBlock, knownHeight, page) {
341
+ if (page !== undefined) {
342
+ cs.transactionStore.merge(Primitive_option.valFromOption(page));
343
+ }
276
344
  let fs = newItemsWithDcs.length !== 0 ? FetchState.registerDynamicContracts(cs.fetchState, newItemsWithDcs) : cs.fetchState;
277
345
  cs.fetchState = FetchState.updateKnownHeight(FetchState.handleQueryResult(fs, query, latestFetchedBlock, newItems), knownHeight);
278
346
  cs.pendingBudget = Primitive_float.max(0, cs.pendingBudget - query.estResponseSize);
@@ -344,6 +412,48 @@ function enterReorgThreshold(cs) {
344
412
  cs.fetchState = FetchState.updateInternal(cs.fetchState, undefined, undefined, undefined, cs.chainConfig.blockLag, undefined);
345
413
  }
346
414
 
415
+ function toChainMetadata(cs) {
416
+ return {
417
+ first_event_block: Stdlib_Null.fromOption(cs.fetchState.firstEventBlock),
418
+ buffer_block: FetchState.bufferBlockNumber(cs.fetchState),
419
+ ready_at: Stdlib_Null.fromOption(cs.timestampCaughtUpToHeadOrEndblock),
420
+ _is_hyper_sync: SourceManager.getActiveSource(cs.sourceManager).poweredByHyperSync
421
+ };
422
+ }
423
+
424
+ function toChainData(cs) {
425
+ return {
426
+ chainId: cs.chainConfig.id,
427
+ poweredByHyperSync: SourceManager.getActiveSource(cs.sourceManager).poweredByHyperSync,
428
+ firstEventBlockNumber: cs.fetchState.firstEventBlock,
429
+ latestProcessedBlock: cs.committedProgressBlockNumber === -1 ? undefined : cs.committedProgressBlockNumber,
430
+ timestampCaughtUpToHeadOrEndblock: cs.timestampCaughtUpToHeadOrEndblock,
431
+ numEventsProcessed: cs.numEventsProcessed,
432
+ latestFetchedBlockNumber: Primitive_int.max(FetchState.bufferBlockNumber(cs.fetchState), 0),
433
+ currentBlockHeight: hasProcessedToEndblock(cs) ? Stdlib_Option.getOr(cs.fetchState.endBlock, cs.fetchState.knownHeight) : cs.fetchState.knownHeight,
434
+ numBatchesFetched: 0,
435
+ startBlock: cs.fetchState.startBlock,
436
+ endBlock: cs.fetchState.endBlock,
437
+ numAddresses: FetchState.numAddresses(cs.fetchState)
438
+ };
439
+ }
440
+
441
+ function toChainBeforeBatch(cs) {
442
+ return {
443
+ fetchState: cs.fetchState,
444
+ reorgDetection: cs.reorgDetection,
445
+ progressBlockNumber: cs.committedProgressBlockNumber,
446
+ sourceBlockNumber: cs.fetchState.knownHeight,
447
+ totalEventsProcessed: cs.numEventsProcessed,
448
+ chainConfig: cs.chainConfig
449
+ };
450
+ }
451
+
452
+ function isReadyToEnterReorgThresholdAfterBatch(cs, batch) {
453
+ let chainAfterBatch = batch.progressedChainsById[cs.fetchState.chainId];
454
+ return FetchState.isReadyToEnterReorgThreshold(chainAfterBatch !== undefined ? chainAfterBatch.fetchState : cs.fetchState);
455
+ }
456
+
347
457
  function advanceAfterBatch(cs, batch, enteringReorgThreshold) {
348
458
  let chainAfterBatch = batch.progressedChainsById[cs.fetchState.chainId];
349
459
  if (chainAfterBatch !== undefined) {
@@ -394,6 +504,7 @@ function applyBatchProgress(cs, batch) {
394
504
  }
395
505
  cs.committedProgressBlockNumber = chainAfterBatch.progressBlockNumber;
396
506
  cs.numEventsProcessed = chainAfterBatch.totalEventsProcessed;
507
+ cs.transactionStore.prune(chainAfterBatch.progressBlockNumber);
397
508
  cs.isProgressAtHead = cs.isProgressAtHead || chainAfterBatch.isProgressAtHeadWhenBatchCreated;
398
509
  let safeCheckpointTracking = cs.safeCheckpointTracking;
399
510
  if (safeCheckpointTracking !== undefined) {
@@ -415,6 +526,7 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
415
526
  if (isReorgChain) {
416
527
  cs.reorgDetection = ReorgDetection.rollbackToValidBlockNumber(cs.reorgDetection, rollbackTargetBlockNumber);
417
528
  cs.fetchState = FetchState.rollback(cs.fetchState, rollbackTargetBlockNumber);
529
+ cs.transactionStore.rollback(rollbackTargetBlockNumber);
418
530
  cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, rollbackTargetBlockNumber);
419
531
  return;
420
532
  } else {
@@ -436,6 +548,7 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
436
548
  cs.safeCheckpointTracking = SafeCheckpointTracking.rollback(safeCheckpointTracking, newProgressBlockNumber);
437
549
  }
438
550
  cs.fetchState = FetchState.rollback(cs.fetchState, newProgressBlockNumber);
551
+ cs.transactionStore.rollback(newProgressBlockNumber);
439
552
  cs.committedProgressBlockNumber = newProgressBlockNumber;
440
553
  cs.numEventsProcessed = newTotalEventsProcessed;
441
554
  }
@@ -446,7 +559,6 @@ export {
446
559
  makeFromConfig,
447
560
  makeFromDbState,
448
561
  logger,
449
- fetchState,
450
562
  sourceManager,
451
563
  chainConfig,
452
564
  reorgDetection,
@@ -457,12 +569,29 @@ export {
457
569
  pendingBudget,
458
570
  startFetchingQueries,
459
571
  timestampCaughtUpToHeadOrEndblock,
572
+ knownHeight,
573
+ indexingAddresses,
574
+ bufferSize,
575
+ bufferReadyCount,
576
+ getProgressPercentage,
577
+ getProgressPercentageAt,
578
+ hasReadyItem,
579
+ isReadyToEnterReorgThreshold,
580
+ getNextQuery,
581
+ dispatch,
582
+ toChainData,
583
+ toChainMetadata,
584
+ toChainBeforeBatch,
585
+ isReadyToEnterReorgThresholdAfterBatch,
460
586
  hasProcessedToEndblock,
461
587
  getHighestBlockBelowThreshold,
462
588
  isActivelyIndexing,
463
589
  isReady,
464
590
  isFetchingAtHead,
591
+ isAtHeadWithoutEndBlock,
465
592
  handleQueryResult,
593
+ materializeBatchItems,
594
+ materializePageItems,
466
595
  registerReorgGuard,
467
596
  prepareReorg,
468
597
  updateKnownHeight,
@@ -15,6 +15,7 @@ let make: (
15
15
  ~numEventsProcessed: float=?,
16
16
  ~timestampCaughtUpToHeadOrEndblock: option<Date.t>=?,
17
17
  ~isProgressAtHead: bool=?,
18
+ ~transactionFieldMask: float=?,
18
19
  ~logger: Pino.t,
19
20
  ) => t
20
21
 
@@ -38,7 +39,6 @@ let makeFromDbState: (
38
39
 
39
40
  // Accessors.
40
41
  let logger: t => Pino.t
41
- let fetchState: t => FetchState.t
42
42
  let sourceManager: t => SourceManager.t
43
43
  let chainConfig: t => Config.chain
44
44
  let reorgDetection: t => ReorgDetection.t
@@ -50,12 +50,55 @@ let pendingBudget: t => float
50
50
  let startFetchingQueries: (t, ~queries: array<FetchState.query>) => unit
51
51
  let timestampCaughtUpToHeadOrEndblock: t => option<Date.t>
52
52
 
53
+ // Fetch-frontier reads.
54
+ let knownHeight: t => int
55
+ let indexingAddresses: t => dict<FetchState.indexingAddress>
56
+ let bufferSize: t => int
57
+ let bufferReadyCount: t => int
58
+ let getProgressPercentage: t => float
59
+ let getProgressPercentageAt: (t, ~blockNumber: int) => float
60
+ let hasReadyItem: t => bool
61
+ let isReadyToEnterReorgThreshold: t => bool
62
+
63
+ // Fetch control.
64
+ let getNextQuery: (t, ~budget: int) => FetchState.nextQuery
65
+ let dispatch: (
66
+ t,
67
+ ~executeQuery: FetchState.query => promise<unit>,
68
+ ~waitForNewBlock: (~knownHeight: int) => promise<int>,
69
+ ~onNewBlock: (~knownHeight: int) => unit,
70
+ ~action: FetchState.nextQuery,
71
+ ~stateId: int,
72
+ ) => promise<unit>
73
+
74
+ // Views.
75
+ type chainData = {
76
+ chainId: float,
77
+ poweredByHyperSync: bool,
78
+ firstEventBlockNumber: option<int>,
79
+ latestProcessedBlock: option<int>,
80
+ timestampCaughtUpToHeadOrEndblock: option<Date.t>,
81
+ numEventsProcessed: float,
82
+ latestFetchedBlockNumber: int,
83
+ @as("currentBlockHeight")
84
+ knownHeight: int,
85
+ numBatchesFetched: int,
86
+ startBlock: int,
87
+ endBlock: option<int>,
88
+ numAddresses: int,
89
+ }
90
+ let toChainData: t => chainData
91
+ let toChainMetadata: t => InternalTable.Chains.metaFields
92
+ let toChainBeforeBatch: t => Batch.chainBeforeBatch
93
+ let isReadyToEnterReorgThresholdAfterBatch: (t, ~batch: Batch.t) => bool
94
+
53
95
  // Derived (pure).
54
96
  let hasProcessedToEndblock: t => bool
55
97
  let getHighestBlockBelowThreshold: t => int
56
98
  let isActivelyIndexing: t => bool
57
99
  let isReady: t => bool
58
100
  let isFetchingAtHead: t => bool
101
+ let isAtHeadWithoutEndBlock: t => bool
59
102
 
60
103
  // State transitions. The chain state is mutated only through these.
61
104
  let handleQueryResult: (
@@ -65,7 +108,14 @@ let handleQueryResult: (
65
108
  ~newItemsWithDcs: array<Internal.item>,
66
109
  ~latestFetchedBlock: FetchState.blockNumberAndTimestamp,
67
110
  ~knownHeight: int,
111
+ ~transactionStore: option<TransactionStore.t>,
68
112
  ) => unit
113
+ let materializeBatchItems: (t, ~items: array<Internal.item>) => promise<unit>
114
+ let materializePageItems: (
115
+ t,
116
+ ~items: array<Internal.item>,
117
+ ~page: option<TransactionStore.t>,
118
+ ) => promise<unit>
69
119
  let registerReorgGuard: (
70
120
  t,
71
121
  ~blockHashes: array<ReorgDetection.blockData>,
package/src/Config.res CHANGED
@@ -195,9 +195,8 @@ let svmEventDescriptorSchema = S.schema(s =>
195
195
  {
196
196
  "discriminator": s.matches(S.option(S.string)),
197
197
  "discriminatorByteLen": s.matches(S.int),
198
- "includeTransaction": s.matches(S.bool),
198
+ "transactionFields": s.matches(S.array(Internal.svmTransactionFieldSchema)),
199
199
  "includeLogs": s.matches(S.bool),
200
- "includeTokenBalances": s.matches(S.bool),
201
200
  "accountFilters": s.matches(
202
201
  S.option(
203
202
  S.array(
@@ -704,9 +703,8 @@ let fromPublic = (publicConfigJson: JSON.t) => {
704
703
  "svm": option<{
705
704
  "discriminator": option<string>,
706
705
  "discriminatorByteLen": int,
707
- "includeTransaction": bool,
706
+ "transactionFields": array<Internal.svmTransactionField>,
708
707
  "includeLogs": bool,
709
- "includeTokenBalances": bool,
710
708
  "accountFilters": option<
711
709
  array<array<{"position": int, "values": array<string>}>>,
712
710
  >,
@@ -740,9 +738,8 @@ let fromPublic = (publicConfigJson: JSON.t) => {
740
738
  ~programId,
741
739
  ~discriminator=svm["discriminator"],
742
740
  ~discriminatorByteLen=svm["discriminatorByteLen"],
743
- ~includeTransaction=svm["includeTransaction"],
741
+ ~transactionFields=svm["transactionFields"],
744
742
  ~includeLogs=svm["includeLogs"],
745
- ~includeTokenBalances=svm["includeTokenBalances"],
746
743
  ~accountFilters,
747
744
  ~isInner=svm["isInner"],
748
745
  ~isWildcard=false,
@@ -111,9 +111,8 @@ let publicConfigChainSchema = S$RescriptSchema.schema(s => ({
111
111
  let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
112
112
  discriminator: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
113
113
  discriminatorByteLen: s.m(S$RescriptSchema.int),
114
- includeTransaction: s.m(S$RescriptSchema.bool),
114
+ transactionFields: s.m(S$RescriptSchema.array(Internal.svmTransactionFieldSchema)),
115
115
  includeLogs: s.m(S$RescriptSchema.bool),
116
- includeTokenBalances: s.m(S$RescriptSchema.bool),
117
116
  accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
118
117
  position: s.m(S$RescriptSchema.int),
119
118
  values: s.m(S$RescriptSchema.array(S$RescriptSchema.string))
@@ -518,7 +517,7 @@ function fromPublic(publicConfigJson) {
518
517
  position: af.position,
519
518
  values: af.values
520
519
  })));
521
- return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeTransaction, svm.includeLogs, svm.includeTokenBalances, accountFilters, svm.isInner, false, undefined, undefined, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes, startBlock);
520
+ return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeLogs, svm.transactionFields, accountFilters, svm.isInner, false, undefined, undefined, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes, startBlock);
522
521
  }
523
522
  });
524
523
  } else {
package/src/Core.res CHANGED
@@ -9,6 +9,7 @@ type evmRpcClientCtor
9
9
  type evmDecoderCtor
10
10
  type svmHypersyncClientCtor
11
11
  type hyperfuelClientCtor
12
+ type transactionStoreCtor
12
13
 
13
14
  type addon = {
14
15
  getConfigJson: (~configPath: Null.t<string>, ~directory: Null.t<string>) => string,
@@ -23,6 +24,12 @@ type addon = {
23
24
  svmHypersyncClient: svmHypersyncClientCtor,
24
25
  @as("HyperfuelClient")
25
26
  hyperfuelClient: hyperfuelClientCtor,
27
+ @as("TransactionStore")
28
+ transactionStore: transactionStoreCtor,
29
+ // Ordered transaction-field names exposed for the field-code contract test
30
+ // (the ReScript `transactionFields` arrays must match the Rust ordinals).
31
+ evmTransactionFieldNames: unit => array<string>,
32
+ svmTransactionFieldNames: unit => array<string>,
26
33
  }
27
34
 
28
35
  @module("node:module") external createRequire: string => {..} = "createRequire"