envio 3.3.0-alpha.9 → 3.3.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/index.d.ts +19 -0
  2. package/licenses/CLA.md +35 -0
  3. package/licenses/EULA.md +67 -0
  4. package/licenses/LICENSE.md +45 -0
  5. package/licenses/README.md +35 -0
  6. package/package.json +9 -8
  7. package/src/Batch.res.mjs +1 -1
  8. package/src/BatchProcessing.res +0 -7
  9. package/src/BatchProcessing.res.mjs +1 -8
  10. package/src/ChainFetching.res +30 -18
  11. package/src/ChainFetching.res.mjs +23 -13
  12. package/src/ChainState.res +134 -55
  13. package/src/ChainState.res.mjs +78 -36
  14. package/src/ChainState.resi +14 -2
  15. package/src/Config.res +9 -5
  16. package/src/Config.res.mjs +2 -2
  17. package/src/Core.res +20 -0
  18. package/src/Core.res.mjs +12 -0
  19. package/src/CrossChainState.res +115 -34
  20. package/src/CrossChainState.res.mjs +37 -14
  21. package/src/EffectState.res +100 -0
  22. package/src/EffectState.res.mjs +74 -0
  23. package/src/EffectState.resi +32 -0
  24. package/src/Envio.res +25 -7
  25. package/src/Envio.res.mjs +15 -19
  26. package/src/EventConfigBuilder.res +21 -46
  27. package/src/EventConfigBuilder.res.mjs +18 -25
  28. package/src/EventProcessing.res +8 -5
  29. package/src/EventProcessing.res.mjs +2 -1
  30. package/src/FetchState.res +814 -467
  31. package/src/FetchState.res.mjs +541 -369
  32. package/src/HandlerRegister.res +3 -1
  33. package/src/HandlerRegister.res.mjs +3 -2
  34. package/src/InMemoryStore.res +14 -26
  35. package/src/InMemoryStore.res.mjs +6 -19
  36. package/src/IndexerState.res +15 -39
  37. package/src/IndexerState.res.mjs +18 -29
  38. package/src/IndexerState.resi +2 -10
  39. package/src/Internal.res +98 -14
  40. package/src/Internal.res.mjs +96 -2
  41. package/src/LoadLayer.res +44 -24
  42. package/src/LoadLayer.res.mjs +43 -20
  43. package/src/LoadLayer.resi +1 -0
  44. package/src/LogSelection.res +0 -62
  45. package/src/LogSelection.res.mjs +0 -74
  46. package/src/Metrics.res +1 -1
  47. package/src/Metrics.res.mjs +1 -1
  48. package/src/Persistence.res +12 -3
  49. package/src/PgStorage.res +155 -82
  50. package/src/PgStorage.res.mjs +130 -77
  51. package/src/Prometheus.res +20 -10
  52. package/src/Prometheus.res.mjs +22 -10
  53. package/src/PruneStaleHistory.res +146 -35
  54. package/src/PruneStaleHistory.res.mjs +114 -20
  55. package/src/RawEvent.res +2 -2
  56. package/src/Rollback.res +32 -13
  57. package/src/Rollback.res.mjs +24 -11
  58. package/src/SimulateDeadInputTracker.res +1 -1
  59. package/src/SimulateItems.res +38 -6
  60. package/src/SimulateItems.res.mjs +28 -9
  61. package/src/TestIndexer.res +2 -2
  62. package/src/TestIndexer.res.mjs +2 -2
  63. package/src/TopicFilter.res +1 -25
  64. package/src/TopicFilter.res.mjs +0 -74
  65. package/src/UserContext.res +62 -23
  66. package/src/UserContext.res.mjs +26 -6
  67. package/src/Writing.res +60 -21
  68. package/src/Writing.res.mjs +27 -9
  69. package/src/bindings/NodeJs.res +5 -0
  70. package/src/bindings/Viem.res +0 -5
  71. package/src/sources/EventRouter.res +0 -21
  72. package/src/sources/EventRouter.res.mjs +0 -12
  73. package/src/sources/EvmChain.res +2 -27
  74. package/src/sources/EvmChain.res.mjs +2 -23
  75. package/src/sources/EvmRpcClient.res +58 -23
  76. package/src/sources/EvmRpcClient.res.mjs +11 -5
  77. package/src/sources/HyperSync.res +9 -38
  78. package/src/sources/HyperSync.res.mjs +16 -28
  79. package/src/sources/HyperSync.resi +2 -2
  80. package/src/sources/HyperSyncClient.res +88 -11
  81. package/src/sources/HyperSyncClient.res.mjs +39 -6
  82. package/src/sources/HyperSyncSource.res +18 -199
  83. package/src/sources/HyperSyncSource.res.mjs +9 -128
  84. package/src/sources/Rpc.res +0 -32
  85. package/src/sources/Rpc.res.mjs +1 -46
  86. package/src/sources/RpcSource.res +129 -522
  87. package/src/sources/RpcSource.res.mjs +161 -366
  88. package/src/sources/SimulateSource.res +37 -19
  89. package/src/sources/SimulateSource.res.mjs +27 -10
  90. package/src/sources/SourceManager.res +3 -7
  91. package/src/sources/SourceManager.res.mjs +2 -7
  92. package/src/sources/SourceManager.resi +0 -2
  93. package/src/sources/SvmHyperSyncSource.res +13 -3
  94. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
@@ -4,6 +4,8 @@
4
4
 
5
5
  type t = {
6
6
  logger: Pino.t,
7
+ // The registrations used to build this chain's sources and route native items.
8
+ onEventRegistrations: array<Internal.onEventRegistration>,
7
9
  mutable fetchState: FetchState.t,
8
10
  // The chain-wide address index. Not `mutable`: the dict is mutated in place by
9
11
  // register/rollback, so the reference is stable across fetchState versions.
@@ -13,8 +15,13 @@ type t = {
13
15
  mutable isProgressAtHead: bool,
14
16
  mutable timestampCaughtUpToHeadOrEndblock: option<Date.t>,
15
17
  mutable committedProgressBlockNumber: int,
18
+ // Progress of the batch currently being processed. The buffer is consumed at
19
+ // batch creation (see advanceAfterBatch), so this runs ahead of
20
+ // committedProgressBlockNumber until the batch commits — it's the true lower
21
+ // boundary of the remaining buffer's block span.
22
+ mutable processingBlockNumber: int,
16
23
  mutable numEventsProcessed: float,
17
- // Running sum of in-flight queries' itemsTarget, kept here so the
24
+ // Running sum of in-flight queries' itemsEst, kept here so the
18
25
  // scheduler doesn't re-sum pending queries on every tick. Incremented when
19
26
  // queries are dispatched, decremented as their responses land.
20
27
  mutable pendingBudget: float,
@@ -66,9 +73,22 @@ let configAddresses = (chainConfig: Config.chain): array<Internal.indexingAddres
66
73
  addresses
67
74
  }
68
75
 
76
+ let validateOnEventRegistrations = (
77
+ ~chainId: int,
78
+ registrations: array<Internal.onEventRegistration>,
79
+ ) =>
80
+ registrations->Array.forEachWithIndex((registration, expectedIndex) => {
81
+ if registration.index !== expectedIndex {
82
+ JsError.throwWithMessage(
83
+ `Invalid onEvent registration index for chain ${chainId->Int.toString}: ${registration.eventConfig.contractName}.${registration.eventConfig.name} has index ${registration.index->Int.toString}, but its ChainState position is ${expectedIndex->Int.toString}.`,
84
+ )
85
+ }
86
+ })
87
+
69
88
  let make = (
70
89
  ~chainConfig: Config.chain,
71
90
  ~fetchState: FetchState.t,
91
+ ~onEventRegistrations=[],
72
92
  ~indexingAddresses: IndexingAddresses.t,
73
93
  ~sourceManager: SourceManager.t,
74
94
  ~reorgDetection: ReorgDetection.t,
@@ -82,21 +102,26 @@ let make = (
82
102
  ~blockStore=BlockStore.make(~ecosystem=Ecosystem.Evm, ~shouldChecksum=false),
83
103
  ~logger: Pino.t,
84
104
  ): t => {
85
- logger,
86
- fetchState,
87
- indexingAddresses,
88
- sourceManager,
89
- chainConfig,
90
- isProgressAtHead,
91
- timestampCaughtUpToHeadOrEndblock,
92
- committedProgressBlockNumber,
93
- numEventsProcessed,
94
- pendingBudget: 0.,
95
- chainDensity,
96
- reorgDetection,
97
- safeCheckpointTracking,
98
- transactionStore,
99
- blockStore,
105
+ validateOnEventRegistrations(~chainId=chainConfig.id, onEventRegistrations)
106
+ {
107
+ logger,
108
+ onEventRegistrations,
109
+ fetchState,
110
+ indexingAddresses,
111
+ sourceManager,
112
+ chainConfig,
113
+ isProgressAtHead,
114
+ timestampCaughtUpToHeadOrEndblock,
115
+ committedProgressBlockNumber,
116
+ processingBlockNumber: committedProgressBlockNumber,
117
+ numEventsProcessed,
118
+ pendingBudget: 0.,
119
+ chainDensity,
120
+ reorgDetection,
121
+ safeCheckpointTracking,
122
+ transactionStore,
123
+ blockStore,
124
+ }
100
125
  }
101
126
 
102
127
  let makeInternal = (
@@ -232,6 +257,7 @@ let makeInternal = (
232
257
  make(
233
258
  ~chainConfig,
234
259
  ~fetchState,
260
+ ~onEventRegistrations,
235
261
  ~indexingAddresses=indexingAddressIndex,
236
262
  ~sourceManager=SourceManager.make(~sources, ~isRealtime, ~reducedPollingInterval?),
237
263
  ~reorgDetection=ReorgDetection.make(
@@ -367,7 +393,7 @@ let startFetchingQueries = (cs: t, ~queries: array<FetchState.query>) => {
367
393
  cs.fetchState->FetchState.startFetchingQueries(~queries)
368
394
  cs.pendingBudget =
369
395
  cs.pendingBudget +.
370
- queries->Array.reduce(0., (acc, query) => acc +. query.itemsTarget->Int.toFloat)
396
+ queries->Array.reduce(0., (acc, query) => acc +. query.itemsEst->Int.toFloat)
371
397
  }
372
398
 
373
399
  // Drop every in-flight query and release their reservations together, keeping
@@ -384,44 +410,74 @@ let isReady = (cs: t) => cs.timestampCaughtUpToHeadOrEndblock !== None
384
410
  // nudge it.
385
411
  let densityBlendWindow = 100.
386
412
 
387
- // The last block this chain can fetch right now: the head, or endBlock when
388
- // it's below the head.
413
+ // The last block this chain can fetch right now: the lagged head
414
+ // (knownHeight - blockLag, the frontier when a reorg-threshold blockLag holds
415
+ // back the tip), or endBlock when it's below that head. Matching isFetchingAtHead's
416
+ // definition of the head is what lets a chain parked at its lagged head read
417
+ // 100% progress instead of looking behind against blocks it can't fetch.
389
418
  let fetchCeiling = (cs: t) => {
390
419
  let fetchState = cs.fetchState
420
+ let head = Pervasives.max(0, fetchState.knownHeight - fetchState.blockLag)
391
421
  switch fetchState.endBlock {
392
- | Some(endBlock) => Pervasives.min(endBlock, fetchState.knownHeight)
393
- | None => fetchState.knownHeight
422
+ | Some(endBlock) => Pervasives.min(endBlock, head)
423
+ | None => head
424
+ }
425
+ }
426
+
427
+ // Events/block over the ready part of the buffer — a live signal that reacts
428
+ // to what fetching just found, unlike the processing EMA which only moves as
429
+ // batches commit.
430
+ let readyBufferDensity = (cs: t) => {
431
+ let readyCount = cs.fetchState->FetchState.bufferReadyCount
432
+ let span = cs.fetchState->FetchState.bufferBlockNumber - cs.processingBlockNumber
433
+ if readyCount > 0 && span > 0 {
434
+ Some(readyCount->Int.toFloat /. span->Int.toFloat)
435
+ } else {
436
+ None
394
437
  }
395
438
  }
396
439
 
440
+ // Density used for query sizing: the higher of the processing EMA and the
441
+ // ready-buffer density, so a stale-low EMA can't undersize queries while the
442
+ // buffer proves the range is dense. None means the chain is cold — no density
443
+ // signal at all.
444
+ let effectiveDensity = (cs: t) =>
445
+ switch (cs.chainDensity, cs->readyBufferDensity) {
446
+ | (Some(ema), Some(buffer)) => Some(Pervasives.max(ema, buffer))
447
+ | (Some(_) as density, None) | (None, Some(_) as density) => density
448
+ | (None, None) => None
449
+ }
450
+
451
+ // How far past the frontier a chain with no density signal targets while it
452
+ // takes its first measurements.
453
+ let coldTargetRange = 20_000
454
+
397
455
  // This chain's share of the indexer-wide buffer budget turned into a soft
398
- // target block: via chain density, or the fetch ceiling when density isn't
399
- // known yet.
456
+ // target block: via density, or frontier + coldTargetRange when there's no
457
+ // density signal yet.
400
458
  let targetBlock = (cs: t, ~chainTargetItems: float) => {
401
459
  let fetchState = cs.fetchState
402
460
  let fetchCeiling = cs->fetchCeiling
403
461
  let bufferBlockNumber = fetchState->FetchState.bufferBlockNumber
404
- switch cs.chainDensity {
462
+ switch cs->effectiveDensity {
405
463
  | Some(density) if density > 0. =>
406
464
  Pervasives.min(
407
465
  fetchCeiling,
408
466
  bufferBlockNumber + Math.ceil(chainTargetItems /. density)->Float.toInt,
409
467
  )
410
- | _ => fetchCeiling
468
+ | _ => Pervasives.min(bufferBlockNumber + coldTargetRange, fetchCeiling)
411
469
  }
412
470
  }
413
471
 
414
472
  // Block range that cross-chain progress alignment maps fractions over: from
415
- // the first block that can hold this chain's events to the last block it will
416
- // fetch.
473
+ // the chain's startBlock to the last block it can fetch right now. The lower
474
+ // bound is deliberately static — anchoring it at firstEventBlock would make
475
+ // two chains sitting at the same block read different progress fractions
476
+ // depending on whether each has discovered its first event yet, so the
477
+ // anchor's line could map below a follower's own frontier and stall it.
417
478
  let progressRange = (cs: t) => {
418
479
  let fetchState = cs.fetchState
419
- let lower = fetchState.firstEventBlock->Option.getOr(fetchState.startBlock)
420
- let upper = switch fetchState.endBlock {
421
- | Some(endBlock) => endBlock
422
- | None => fetchState.knownHeight
423
- }
424
- (lower, upper)
480
+ (fetchState.startBlock, cs->fetchCeiling)
425
481
  }
426
482
 
427
483
  // A degenerate range (chain already at or past its last block) maps to 1 so it
@@ -442,13 +498,25 @@ let blockAtProgress = (cs: t, ~progress) => {
442
498
  lower + Math.ceil(progress *. (upper - lower)->Int.toFloat)->Float.toInt
443
499
  }
444
500
 
501
+ // The fetch frontier as a fraction of the alignable range — what the
502
+ // cross-chain waterfall aligns other chains against. Based on blocks actually
503
+ // fetched, so it holds up even while this chain's queries are in flight.
504
+ let frontierProgress = (cs: t) =>
505
+ cs->progressAtBlock(~blockNumber=cs.fetchState->FetchState.bufferBlockNumber)
506
+
445
507
  // Propose queries sized against this chain's target block. Called by
446
508
  // CrossChainState's waterfall, furthest-behind chain first, with
447
509
  // chainTargetItems set to whatever budget remains at that point and
448
510
  // maxTargetBlock set to the most-behind chain's progress mapped onto this
449
511
  // chain, so a chain with budget can't run further ahead than the chain the
450
512
  // whole pool is prioritizing.
451
- let getNextQuery = (cs: t, ~chainTargetItems: float, ~maxTargetBlock=?) => {
513
+ let getNextQuery = (
514
+ cs: t,
515
+ ~chainTargetItems: float,
516
+ ~chunkItemsMultiplier=1.,
517
+ ~itemsTargetFloor=0,
518
+ ~maxTargetBlock=?,
519
+ ) => {
452
520
  let chainTargetBlock = cs->targetBlock(~chainTargetItems)
453
521
  let chainTargetBlock = switch maxTargetBlock {
454
522
  | Some(maxTargetBlock) => Pervasives.min(chainTargetBlock, maxTargetBlock)
@@ -460,25 +528,25 @@ let getNextQuery = (cs: t, ~chainTargetItems: float, ~maxTargetBlock=?) => {
460
528
  // top: they're already accounted and shouldn't crowd out new partitions), so
461
529
  // the waterfall's leftover flows to the next chain in the same tick instead
462
530
  // of being held by an oversized probe.
463
- let chainTargetItems = switch cs.chainDensity {
531
+ let chainTargetItems = switch cs->effectiveDensity {
464
532
  | Some(density) if density > 0. =>
533
+ // No extra headroom here: the budget is reserved in honest itemsEst units,
534
+ // and truncation safety lives in the itemsTarget server cap (sized with
535
+ // chunkItemsMultiplier at query creation) — multiplying the budget cap too
536
+ // would compound the two and hold budget away from other chains.
465
537
  let rangeCost =
466
538
  density *. (chainTargetBlock - cs.fetchState->FetchState.bufferBlockNumber)->Int.toFloat
467
- // 3x headroom only for a chain already caught up once and polling the
468
- // head: there a single query covers the whole remaining range, and a
469
- // slightly denser-than-expected range would otherwise truncate at the
470
- // server cap and force an immediate catch-up query for the last blocks.
471
- // During backfill the range never fits one query anyway, so headroom
472
- // would just hold budget away from other chains.
473
- let rangeCost =
474
- chainTargetBlock >= cs->fetchCeiling && cs->isReady ? rangeCost *. 3. : rangeCost
475
539
  Pervasives.min(chainTargetItems, Math.ceil(rangeCost) +. cs.pendingBudget)
476
- | _ =>
477
- // No density signal yet: bound the probe so one unknown chain doesn't
478
- // hold the whole pool while it takes its first measurements.
479
- Pervasives.min(chainTargetItems, 5_000. +. cs.pendingBudget)
540
+ // No density signal: the cross-chain waterfall already clamped the handed
541
+ // budget to the cold-chain cap, so it's used as-is.
542
+ | _ => chainTargetItems
480
543
  }
481
- cs.fetchState->FetchState.getNextQuery(~chainTargetBlock, ~chainTargetItems)
544
+ cs.fetchState->FetchState.getNextQuery(
545
+ ~chainTargetBlock,
546
+ ~chainTargetItems,
547
+ ~chunkItemsMultiplier,
548
+ ~itemsTargetFloor,
549
+ )
482
550
  }
483
551
 
484
552
  // Run a fetch tick for this chain against its sources, feeding the owned fetch
@@ -712,6 +780,9 @@ let materializePageItems = async (
712
780
  ))
713
781
  }
714
782
 
783
+ let filterByClientAddress = (cs: t, items: array<Internal.item>): array<Internal.item> =>
784
+ items->FetchState.filterByClientAddress(~indexingAddresses=cs.indexingAddresses)
785
+
715
786
  let handleQueryResult = (
716
787
  cs: t,
717
788
  ~query: FetchState.query,
@@ -744,16 +815,11 @@ let handleQueryResult = (
744
815
 
745
816
  cs.fetchState =
746
817
  fs
747
- ->FetchState.handleQueryResult(
748
- ~indexingAddresses=cs.indexingAddresses,
749
- ~query,
750
- ~latestFetchedBlock,
751
- ~newItems,
752
- )
818
+ ->FetchState.handleQueryResult(~query, ~latestFetchedBlock, ~newItems)
753
819
  ->FetchState.updateKnownHeight(~knownHeight)
754
820
 
755
821
  // The query is no longer in flight, so release its reservation.
756
- cs.pendingBudget = Pervasives.max(0., cs.pendingBudget -. query.itemsTarget->Int.toFloat)
822
+ cs.pendingBudget = Pervasives.max(0., cs.pendingBudget -. query.itemsEst->Int.toFloat)
757
823
  }
758
824
 
759
825
  // Run reorg detection against a fetch response and commit the updated guard.
@@ -854,6 +920,10 @@ let advanceAfterBatch = (cs: t, ~batch: Batch.t, ~enteringReorgThreshold) =>
854
920
  cs.fetchState = enteringReorgThreshold
855
921
  ? chainAfterBatch.fetchState->FetchState.updateInternal(~blockLag=cs.chainConfig.blockLag)
856
922
  : chainAfterBatch.fetchState
923
+
924
+ // The batch's items just left the buffer, so the remaining buffer's span
925
+ // starts at the batch's progress.
926
+ cs.processingBlockNumber = chainAfterBatch.progressBlockNumber
857
927
  | None => ()
858
928
  }
859
929
 
@@ -940,6 +1010,13 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t, ~blockTimestampName: string) =
940
1010
  }
941
1011
 
942
1012
  cs.committedProgressBlockNumber = chainAfterBatch.progressBlockNumber
1013
+
1014
+ // Normally already set by advanceAfterBatch at batch creation; catch up
1015
+ // here for paths that commit progress without it.
1016
+ cs.processingBlockNumber = Pervasives.max(
1017
+ cs.processingBlockNumber,
1018
+ chainAfterBatch.progressBlockNumber,
1019
+ )
943
1020
  cs.numEventsProcessed = chainAfterBatch.totalEventsProcessed
944
1021
  // Processed blocks' transactions and blocks are no longer needed.
945
1022
  cs.transactionStore->TransactionStore.prune(chainAfterBatch.progressBlockNumber)
@@ -1024,6 +1101,7 @@ let rollback = (
1024
1101
  cs.transactionStore->TransactionStore.rollback(newProgressBlockNumber)
1025
1102
  cs.blockStore->BlockStore.rollback(newProgressBlockNumber)
1026
1103
  cs.committedProgressBlockNumber = newProgressBlockNumber
1104
+ cs.processingBlockNumber = newProgressBlockNumber
1027
1105
  cs.numEventsProcessed = newTotalEventsProcessed
1028
1106
  | None =>
1029
1107
  if isReorgChain {
@@ -1042,6 +1120,7 @@ let rollback = (
1042
1120
  cs.committedProgressBlockNumber,
1043
1121
  rollbackTargetBlockNumber,
1044
1122
  )
1123
+ cs.processingBlockNumber = Pervasives.min(cs.processingBlockNumber, rollbackTargetBlockNumber)
1045
1124
  }
1046
1125
  }
1047
1126
  }
@@ -40,7 +40,16 @@ function configAddresses(chainConfig) {
40
40
  return addresses;
41
41
  }
42
42
 
43
- function make(chainConfig, fetchState, indexingAddresses, sourceManager, reorgDetection, committedProgressBlockNumber, safeCheckpointTrackingOpt, numEventsProcessedOpt, timestampCaughtUpToHeadOrEndblockOpt, isProgressAtHeadOpt, transactionStoreOpt, chainDensityOpt, blockStoreOpt, logger) {
43
+ function validateOnEventRegistrations(chainId, registrations) {
44
+ registrations.forEach((registration, expectedIndex) => {
45
+ if (registration.index !== expectedIndex) {
46
+ return Stdlib_JsError.throwWithMessage(`Invalid onEvent registration index for chain ` + chainId.toString() + `: ` + registration.eventConfig.contractName + `.` + registration.eventConfig.name + ` has index ` + registration.index.toString() + `, but its ChainState position is ` + expectedIndex.toString() + `.`);
47
+ }
48
+ });
49
+ }
50
+
51
+ function make(chainConfig, fetchState, onEventRegistrationsOpt, indexingAddresses, sourceManager, reorgDetection, committedProgressBlockNumber, safeCheckpointTrackingOpt, numEventsProcessedOpt, timestampCaughtUpToHeadOrEndblockOpt, isProgressAtHeadOpt, transactionStoreOpt, chainDensityOpt, blockStoreOpt, logger) {
52
+ let onEventRegistrations = onEventRegistrationsOpt !== undefined ? onEventRegistrationsOpt : [];
44
53
  let safeCheckpointTracking = safeCheckpointTrackingOpt !== undefined ? Primitive_option.valFromOption(safeCheckpointTrackingOpt) : undefined;
45
54
  let numEventsProcessed = numEventsProcessedOpt !== undefined ? numEventsProcessedOpt : 0;
46
55
  let timestampCaughtUpToHeadOrEndblock = timestampCaughtUpToHeadOrEndblockOpt !== undefined ? Primitive_option.valFromOption(timestampCaughtUpToHeadOrEndblockOpt) : undefined;
@@ -48,8 +57,10 @@ function make(chainConfig, fetchState, indexingAddresses, sourceManager, reorgDe
48
57
  let transactionStore = transactionStoreOpt !== undefined ? Primitive_option.valFromOption(transactionStoreOpt) : TransactionStore.make("evm", false);
49
58
  let chainDensity = chainDensityOpt !== undefined ? Primitive_option.valFromOption(chainDensityOpt) : undefined;
50
59
  let blockStore = blockStoreOpt !== undefined ? Primitive_option.valFromOption(blockStoreOpt) : BlockStore.make("evm", false);
60
+ validateOnEventRegistrations(chainConfig.id, onEventRegistrations);
51
61
  return {
52
62
  logger: logger,
63
+ onEventRegistrations: onEventRegistrations,
53
64
  fetchState: fetchState,
54
65
  indexingAddresses: indexingAddresses,
55
66
  sourceManager: sourceManager,
@@ -57,6 +68,7 @@ function make(chainConfig, fetchState, indexingAddresses, sourceManager, reorgDe
57
68
  isProgressAtHead: isProgressAtHead,
58
69
  timestampCaughtUpToHeadOrEndblock: timestampCaughtUpToHeadOrEndblock,
59
70
  committedProgressBlockNumber: committedProgressBlockNumber,
71
+ processingBlockNumber: committedProgressBlockNumber,
60
72
  numEventsProcessed: numEventsProcessed,
61
73
  pendingBudget: 0,
62
74
  chainDensity: chainDensity,
@@ -135,7 +147,7 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
135
147
  }
136
148
  let firstEventBlock$1 = fetchState.firstEventBlock;
137
149
  let chainDensity = firstEventBlock$1 !== undefined && progressBlockNumber > firstEventBlock$1 && numEventsProcessed > 0 ? numEventsProcessed / (progressBlockNumber - firstEventBlock$1 | 0) : undefined;
138
- return make(chainConfig, fetchState, indexingAddressIndex, 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, Primitive_option.some(TransactionStore.make(config.ecosystem.name, !lowercaseAddresses)), Primitive_option.some(chainDensity), Primitive_option.some(BlockStore.make(config.ecosystem.name, !lowercaseAddresses)), logger);
150
+ return make(chainConfig, fetchState, onEventRegistrations, indexingAddressIndex, 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, Primitive_option.some(TransactionStore.make(config.ecosystem.name, !lowercaseAddresses)), Primitive_option.some(chainDensity), Primitive_option.some(BlockStore.make(config.ecosystem.name, !lowercaseAddresses)), logger);
139
151
  }
140
152
 
141
153
  function makeFromConfig(chainConfig, config, registrationsByChainId, knownHeight) {
@@ -233,7 +245,7 @@ function isReadyToEnterReorgThreshold(cs) {
233
245
 
234
246
  function startFetchingQueries(cs, queries) {
235
247
  FetchState.startFetchingQueries(cs.fetchState, queries);
236
- cs.pendingBudget = cs.pendingBudget + Stdlib_Array.reduce(queries, 0, (acc, query) => acc + query.itemsTarget);
248
+ cs.pendingBudget = cs.pendingBudget + Stdlib_Array.reduce(queries, 0, (acc, query) => acc + query.itemsEst);
237
249
  }
238
250
 
239
251
  function isReady(cs) {
@@ -242,11 +254,36 @@ function isReady(cs) {
242
254
 
243
255
  function fetchCeiling(cs) {
244
256
  let fetchState = cs.fetchState;
257
+ let head = Primitive_int.max(0, fetchState.knownHeight - fetchState.blockLag | 0);
245
258
  let endBlock = fetchState.endBlock;
246
259
  if (endBlock !== undefined) {
247
- return Primitive_int.min(endBlock, fetchState.knownHeight);
260
+ return Primitive_int.min(endBlock, head);
261
+ } else {
262
+ return head;
263
+ }
264
+ }
265
+
266
+ function readyBufferDensity(cs) {
267
+ let readyCount = FetchState.bufferReadyCount(cs.fetchState);
268
+ let span = FetchState.bufferBlockNumber(cs.fetchState) - cs.processingBlockNumber | 0;
269
+ if (readyCount > 0 && span > 0) {
270
+ return readyCount / span;
271
+ }
272
+ }
273
+
274
+ function effectiveDensity(cs) {
275
+ let match = cs.chainDensity;
276
+ let match$1 = readyBufferDensity(cs);
277
+ if (match !== undefined) {
278
+ if (match$1 !== undefined) {
279
+ return Primitive_float.max(match, match$1);
280
+ } else {
281
+ return match;
282
+ }
283
+ } else if (match$1 !== undefined) {
284
+ return match$1;
248
285
  } else {
249
- return fetchState.knownHeight;
286
+ return;
250
287
  }
251
288
  }
252
289
 
@@ -254,26 +291,30 @@ function targetBlock(cs, chainTargetItems) {
254
291
  let fetchState = cs.fetchState;
255
292
  let fetchCeiling$1 = fetchCeiling(cs);
256
293
  let bufferBlockNumber = FetchState.bufferBlockNumber(fetchState);
257
- let density = cs.chainDensity;
294
+ let density = effectiveDensity(cs);
258
295
  if (density !== undefined && density > 0) {
259
296
  return Primitive_int.min(fetchCeiling$1, bufferBlockNumber + (Math.ceil(chainTargetItems / density) | 0) | 0);
260
297
  } else {
261
- return fetchCeiling$1;
298
+ return Primitive_int.min(bufferBlockNumber + 20000 | 0, fetchCeiling$1);
262
299
  }
263
300
  }
264
301
 
265
302
  function progressRange(cs) {
266
303
  let fetchState = cs.fetchState;
267
- let lower = Stdlib_Option.getOr(fetchState.firstEventBlock, fetchState.startBlock);
268
- let endBlock = fetchState.endBlock;
269
- let upper = endBlock !== undefined ? endBlock : fetchState.knownHeight;
270
304
  return [
271
- lower,
272
- upper
305
+ fetchState.startBlock,
306
+ fetchCeiling(cs)
273
307
  ];
274
308
  }
275
309
 
276
- function progressAtBlock(cs, blockNumber) {
310
+ function blockAtProgress(cs, progress) {
311
+ let match = progressRange(cs);
312
+ let lower = match[0];
313
+ return lower + (Math.ceil(progress * (match[1] - lower | 0)) | 0) | 0;
314
+ }
315
+
316
+ function frontierProgress(cs) {
317
+ let blockNumber = FetchState.bufferBlockNumber(cs.fetchState);
277
318
  let match = progressRange(cs);
278
319
  let upper = match[1];
279
320
  let lower = match[0];
@@ -284,29 +325,20 @@ function progressAtBlock(cs, blockNumber) {
284
325
  }
285
326
  }
286
327
 
287
- function blockAtProgress(cs, progress) {
288
- let match = progressRange(cs);
289
- let lower = match[0];
290
- return lower + (Math.ceil(progress * (match[1] - lower | 0)) | 0) | 0;
291
- }
292
-
293
- function getNextQuery(cs, chainTargetItems, maxTargetBlock) {
328
+ function getNextQuery(cs, chainTargetItems, chunkItemsMultiplierOpt, itemsTargetFloorOpt, maxTargetBlock) {
329
+ let chunkItemsMultiplier = chunkItemsMultiplierOpt !== undefined ? chunkItemsMultiplierOpt : 1;
330
+ let itemsTargetFloor = itemsTargetFloorOpt !== undefined ? itemsTargetFloorOpt : 0;
294
331
  let chainTargetBlock = targetBlock(cs, chainTargetItems);
295
332
  let chainTargetBlock$1 = maxTargetBlock !== undefined ? Primitive_int.min(chainTargetBlock, maxTargetBlock) : chainTargetBlock;
296
- let density = cs.chainDensity;
333
+ let density = effectiveDensity(cs);
297
334
  let chainTargetItems$1;
298
- if (density !== undefined) {
299
- if (density > 0) {
300
- let rangeCost = density * (chainTargetBlock$1 - FetchState.bufferBlockNumber(cs.fetchState) | 0);
301
- let rangeCost$1 = chainTargetBlock$1 >= fetchCeiling(cs) && cs.timestampCaughtUpToHeadOrEndblock !== undefined ? rangeCost * 3 : rangeCost;
302
- chainTargetItems$1 = Primitive_float.min(chainTargetItems, Math.ceil(rangeCost$1) + cs.pendingBudget);
303
- } else {
304
- chainTargetItems$1 = Primitive_float.min(chainTargetItems, 5000 + cs.pendingBudget);
305
- }
335
+ if (density !== undefined && density > 0) {
336
+ let rangeCost = density * (chainTargetBlock$1 - FetchState.bufferBlockNumber(cs.fetchState) | 0);
337
+ chainTargetItems$1 = Primitive_float.min(chainTargetItems, Math.ceil(rangeCost) + cs.pendingBudget);
306
338
  } else {
307
- chainTargetItems$1 = Primitive_float.min(chainTargetItems, 5000 + cs.pendingBudget);
339
+ chainTargetItems$1 = chainTargetItems;
308
340
  }
309
- return FetchState.getNextQuery(cs.fetchState, chainTargetBlock$1, chainTargetItems$1);
341
+ return FetchState.getNextQuery(cs.fetchState, chainTargetBlock$1, chainTargetItems$1, chunkItemsMultiplier, itemsTargetFloor);
310
342
  }
311
343
 
312
344
  function dispatch(cs, executeQuery, waitForNewBlock, onNewBlock, action, stateId) {
@@ -477,6 +509,10 @@ async function materializePageItems(items, transactionStore, blockStore, ecosyst
477
509
  ]);
478
510
  }
479
511
 
512
+ function filterByClientAddress(cs, items) {
513
+ return FetchState.filterByClientAddress(items, cs.indexingAddresses);
514
+ }
515
+
480
516
  function handleQueryResult(cs, query, newItems, newItemsWithDcs, latestFetchedBlock, knownHeight, txPage, blockPage) {
481
517
  if (txPage !== undefined) {
482
518
  cs.transactionStore.merge(Primitive_option.valFromOption(txPage));
@@ -485,8 +521,8 @@ function handleQueryResult(cs, query, newItems, newItemsWithDcs, latestFetchedBl
485
521
  cs.blockStore.merge(Primitive_option.valFromOption(blockPage));
486
522
  }
487
523
  let fs = newItemsWithDcs.length !== 0 ? FetchState.registerDynamicContracts(cs.fetchState, cs.indexingAddresses, newItemsWithDcs) : cs.fetchState;
488
- cs.fetchState = FetchState.updateKnownHeight(FetchState.handleQueryResult(fs, cs.indexingAddresses, query, latestFetchedBlock, newItems), knownHeight);
489
- cs.pendingBudget = Primitive_float.max(0, cs.pendingBudget - query.itemsTarget);
524
+ cs.fetchState = FetchState.updateKnownHeight(FetchState.handleQueryResult(fs, query, latestFetchedBlock, newItems), knownHeight);
525
+ cs.pendingBudget = Primitive_float.max(0, cs.pendingBudget - query.itemsEst);
490
526
  }
491
527
 
492
528
  function registerReorgGuard(cs, blockHashes, knownHeight) {
@@ -550,7 +586,7 @@ function setEndBlockToFirstEvent(cs, blockNumber) {
550
586
  }
551
587
 
552
588
  function enterReorgThreshold(cs) {
553
- cs.fetchState = FetchState.updateInternal(cs.fetchState, undefined, undefined, cs.chainConfig.blockLag, undefined);
589
+ cs.fetchState = FetchState.updateInternal(cs.fetchState, undefined, undefined, undefined, cs.chainConfig.blockLag, undefined);
554
590
  }
555
591
 
556
592
  function toChainMetadata(cs) {
@@ -598,7 +634,8 @@ function isReadyToEnterReorgThresholdAfterBatch(cs, batch) {
598
634
  function advanceAfterBatch(cs, batch, enteringReorgThreshold) {
599
635
  let chainAfterBatch = batch.progressedChainsById[cs.fetchState.chainId];
600
636
  if (chainAfterBatch !== undefined) {
601
- cs.fetchState = enteringReorgThreshold ? FetchState.updateInternal(chainAfterBatch.fetchState, undefined, undefined, cs.chainConfig.blockLag, undefined) : chainAfterBatch.fetchState;
637
+ cs.fetchState = enteringReorgThreshold ? FetchState.updateInternal(chainAfterBatch.fetchState, undefined, undefined, undefined, cs.chainConfig.blockLag, undefined) : chainAfterBatch.fetchState;
638
+ cs.processingBlockNumber = chainAfterBatch.progressBlockNumber;
602
639
  return;
603
640
  }
604
641
  }
@@ -665,6 +702,7 @@ function applyBatchProgress(cs, batch, blockTimestampName) {
665
702
  }
666
703
  }
667
704
  cs.committedProgressBlockNumber = chainAfterBatch.progressBlockNumber;
705
+ cs.processingBlockNumber = Primitive_int.max(cs.processingBlockNumber, chainAfterBatch.progressBlockNumber);
668
706
  cs.numEventsProcessed = chainAfterBatch.totalEventsProcessed;
669
707
  cs.transactionStore.prune(chainAfterBatch.progressBlockNumber);
670
708
  cs.blockStore.prune(chainAfterBatch.progressBlockNumber);
@@ -692,6 +730,7 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
692
730
  cs.transactionStore.rollback(rollbackTargetBlockNumber);
693
731
  cs.blockStore.rollback(rollbackTargetBlockNumber);
694
732
  cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, rollbackTargetBlockNumber);
733
+ cs.processingBlockNumber = Primitive_int.min(cs.processingBlockNumber, rollbackTargetBlockNumber);
695
734
  return;
696
735
  } else {
697
736
  return;
@@ -715,6 +754,7 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
715
754
  cs.transactionStore.rollback(newProgressBlockNumber);
716
755
  cs.blockStore.rollback(newProgressBlockNumber);
717
756
  cs.committedProgressBlockNumber = newProgressBlockNumber;
757
+ cs.processingBlockNumber = newProgressBlockNumber;
718
758
  cs.numEventsProcessed = newTotalEventsProcessed;
719
759
  }
720
760
 
@@ -740,11 +780,12 @@ export {
740
780
  bufferReadyCount,
741
781
  getProgressPercentage,
742
782
  chainDensity,
783
+ effectiveDensity,
743
784
  hasReadyItem,
744
785
  isReadyToEnterReorgThreshold,
745
786
  targetBlock,
746
- progressAtBlock,
747
787
  blockAtProgress,
788
+ frontierProgress,
748
789
  getNextQuery,
749
790
  dispatch,
750
791
  toChainData,
@@ -757,6 +798,7 @@ export {
757
798
  isReady,
758
799
  isFetchingAtHead,
759
800
  isAtHeadWithoutEndBlock,
801
+ filterByClientAddress,
760
802
  handleQueryResult,
761
803
  materializeBatchItems,
762
804
  materializePageItems,
@@ -8,6 +8,7 @@ let configAddresses: Config.chain => array<Internal.indexingAddress>
8
8
  let make: (
9
9
  ~chainConfig: Config.chain,
10
10
  ~fetchState: FetchState.t,
11
+ ~onEventRegistrations: array<Internal.onEventRegistration>=?,
11
12
  ~indexingAddresses: IndexingAddresses.t,
12
13
  ~sourceManager: SourceManager.t,
13
14
  ~reorgDetection: ReorgDetection.t,
@@ -60,14 +61,21 @@ let bufferSize: t => int
60
61
  let bufferReadyCount: t => int
61
62
  let getProgressPercentage: t => float
62
63
  let chainDensity: t => option<float>
64
+ let effectiveDensity: t => option<float>
63
65
  let hasReadyItem: t => bool
64
66
  let isReadyToEnterReorgThreshold: t => bool
65
67
 
66
68
  // Fetch control.
67
69
  let targetBlock: (t, ~chainTargetItems: float) => int
68
- let progressAtBlock: (t, ~blockNumber: int) => float
69
70
  let blockAtProgress: (t, ~progress: float) => int
70
- let getNextQuery: (t, ~chainTargetItems: float, ~maxTargetBlock: int=?) => FetchState.nextQuery
71
+ let frontierProgress: t => float
72
+ let getNextQuery: (
73
+ t,
74
+ ~chainTargetItems: float,
75
+ ~chunkItemsMultiplier: float=?,
76
+ ~itemsTargetFloor: int=?,
77
+ ~maxTargetBlock: int=?,
78
+ ) => FetchState.nextQuery
71
79
  let dispatch: (
72
80
  t,
73
81
  ~executeQuery: FetchState.query => promise<unit>,
@@ -106,6 +114,10 @@ let isReady: t => bool
106
114
  let isFetchingAtHead: t => bool
107
115
  let isAtHeadWithoutEndBlock: t => bool
108
116
 
117
+ // Drop over-fetched events an address-param filter rejects, before contract
118
+ // registration runs on them. Uses the chain's current indexing addresses.
119
+ let filterByClientAddress: (t, array<Internal.item>) => array<Internal.item>
120
+
109
121
  // State transitions. The chain state is mutated only through these.
110
122
  let handleQueryResult: (
111
123
  t,
package/src/Config.res CHANGED
@@ -200,14 +200,18 @@ let svmEventDescriptorSchema = S.schema(s =>
200
200
  "transactionFields": s.matches(S.array(Internal.svmTransactionFieldSchema)),
201
201
  "blockFields": s.matches(S.option(S.array(Internal.svmBlockFieldSchema))),
202
202
  "includeLogs": s.matches(S.bool),
203
+ // An array of AND-groups OR-ed together: the CLI normalizes both the flat
204
+ // and `any_of` YAML shapes to `Vec<Vec<SvmAccountFilterJson>>`.
203
205
  "accountFilters": s.matches(
204
206
  S.option(
205
207
  S.array(
206
- S.schema(s =>
207
- {
208
- "position": s.matches(S.int),
209
- "values": s.matches(S.array(S.string)),
210
- }
208
+ S.array(
209
+ S.schema(s =>
210
+ {
211
+ "position": s.matches(S.int),
212
+ "values": s.matches(S.array(S.string)),
213
+ }
214
+ ),
211
215
  ),
212
216
  ),
213
217
  ),
@@ -115,10 +115,10 @@ let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
115
115
  transactionFields: s.m(S$RescriptSchema.array(Internal.svmTransactionFieldSchema)),
116
116
  blockFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.svmBlockFieldSchema))),
117
117
  includeLogs: s.m(S$RescriptSchema.bool),
118
- accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
118
+ accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
119
119
  position: s.m(S$RescriptSchema.int),
120
120
  values: s.m(S$RescriptSchema.array(S$RescriptSchema.string))
121
- }))))),
121
+ })))))),
122
122
  isInner: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
123
123
  accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.string))),
124
124
  args: s.m(S$RescriptSchema.option(S$RescriptSchema.json(false)))