envio 3.3.0-alpha.8 → 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 (107) 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 +205 -59
  13. package/src/ChainState.res.mjs +130 -27
  14. package/src/ChainState.resi +16 -1
  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 +147 -39
  20. package/src/CrossChainState.res.mjs +42 -10
  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/EnvioGlobal.res +53 -0
  27. package/src/EnvioGlobal.res.mjs +31 -0
  28. package/src/EventConfigBuilder.res +30 -56
  29. package/src/EventConfigBuilder.res.mjs +24 -30
  30. package/src/EventProcessing.res +8 -5
  31. package/src/EventProcessing.res.mjs +2 -1
  32. package/src/FetchState.res +842 -474
  33. package/src/FetchState.res.mjs +545 -356
  34. package/src/HandlerLoader.res +1 -1
  35. package/src/HandlerLoader.res.mjs +1 -1
  36. package/src/HandlerRegister.res +478 -305
  37. package/src/HandlerRegister.res.mjs +277 -209
  38. package/src/HandlerRegister.resi +11 -6
  39. package/src/InMemoryStore.res +14 -26
  40. package/src/InMemoryStore.res.mjs +6 -19
  41. package/src/IndexerState.res +15 -39
  42. package/src/IndexerState.res.mjs +18 -29
  43. package/src/IndexerState.resi +2 -10
  44. package/src/Internal.res +121 -18
  45. package/src/Internal.res.mjs +96 -2
  46. package/src/LoadLayer.res +44 -24
  47. package/src/LoadLayer.res.mjs +43 -20
  48. package/src/LoadLayer.resi +1 -0
  49. package/src/LogSelection.res +92 -217
  50. package/src/LogSelection.res.mjs +95 -184
  51. package/src/Main.res +29 -143
  52. package/src/Main.res.mjs +25 -88
  53. package/src/Metrics.res +1 -1
  54. package/src/Metrics.res.mjs +1 -1
  55. package/src/Persistence.res +12 -3
  56. package/src/PgStorage.res +155 -82
  57. package/src/PgStorage.res.mjs +130 -77
  58. package/src/Prometheus.res +20 -10
  59. package/src/Prometheus.res.mjs +22 -10
  60. package/src/PruneStaleHistory.res +146 -35
  61. package/src/PruneStaleHistory.res.mjs +114 -20
  62. package/src/RawEvent.res +2 -2
  63. package/src/Rollback.res +32 -13
  64. package/src/Rollback.res.mjs +24 -11
  65. package/src/RollbackCommit.res +4 -1
  66. package/src/RollbackCommit.res.mjs +3 -2
  67. package/src/SimulateDeadInputTracker.res +1 -1
  68. package/src/SimulateItems.res +39 -7
  69. package/src/SimulateItems.res.mjs +28 -9
  70. package/src/TestIndexer.res +2 -2
  71. package/src/TestIndexer.res.mjs +2 -2
  72. package/src/TopicFilter.res +1 -25
  73. package/src/TopicFilter.res.mjs +0 -74
  74. package/src/UserContext.res +62 -23
  75. package/src/UserContext.res.mjs +26 -6
  76. package/src/Writing.res +60 -21
  77. package/src/Writing.res.mjs +27 -9
  78. package/src/bindings/NodeJs.res +5 -0
  79. package/src/bindings/Viem.res +0 -5
  80. package/src/sources/EventRouter.res +0 -21
  81. package/src/sources/EventRouter.res.mjs +0 -12
  82. package/src/sources/Evm.res +4 -1
  83. package/src/sources/Evm.res.mjs +1 -1
  84. package/src/sources/EvmChain.res +2 -27
  85. package/src/sources/EvmChain.res.mjs +2 -23
  86. package/src/sources/EvmRpcClient.res +58 -23
  87. package/src/sources/EvmRpcClient.res.mjs +11 -5
  88. package/src/sources/Fuel.res +3 -1
  89. package/src/sources/Fuel.res.mjs +1 -1
  90. package/src/sources/HyperSync.res +9 -38
  91. package/src/sources/HyperSync.res.mjs +16 -28
  92. package/src/sources/HyperSync.resi +2 -2
  93. package/src/sources/HyperSyncClient.res +88 -11
  94. package/src/sources/HyperSyncClient.res.mjs +39 -6
  95. package/src/sources/HyperSyncSource.res +18 -210
  96. package/src/sources/HyperSyncSource.res.mjs +9 -137
  97. package/src/sources/Rpc.res +0 -32
  98. package/src/sources/Rpc.res.mjs +1 -46
  99. package/src/sources/RpcSource.res +129 -533
  100. package/src/sources/RpcSource.res.mjs +161 -379
  101. package/src/sources/SimulateSource.res +37 -19
  102. package/src/sources/SimulateSource.res.mjs +27 -10
  103. package/src/sources/SourceManager.res +4 -14
  104. package/src/sources/SourceManager.res.mjs +3 -9
  105. package/src/sources/SourceManager.resi +0 -2
  106. package/src/sources/SvmHyperSyncSource.res +13 -3
  107. 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 = (
@@ -136,25 +161,6 @@ let makeInternal = (
136
161
  }
137
162
  })
138
163
 
139
- // TODO: Move it to the HandlerRegister module
140
- // so the error is thrown with better stack trace
141
- onBlockRegistrations->Array.forEach(onBlockRegistration => {
142
- if onBlockRegistration.startBlock->Option.getOr(startBlock) < startBlock {
143
- JsError.throwWithMessage(
144
- `The start block for onBlock handler "${onBlockRegistration.name}" is less than the chain start block (${startBlock->Int.toString}). This is not supported yet.`,
145
- )
146
- }
147
- switch endBlock {
148
- | Some(chainEndBlock) =>
149
- if onBlockRegistration.endBlock->Option.getOr(chainEndBlock) > chainEndBlock {
150
- JsError.throwWithMessage(
151
- `The end block for onBlock handler "${onBlockRegistration.name}" is greater than the chain end block (${chainEndBlock->Int.toString}). This is not supported yet.`,
152
- )
153
- }
154
- | None => ()
155
- }
156
- })
157
-
158
164
  let contractConfigs = IndexingAddresses.makeContractConfigs(~onEventRegistrations)
159
165
  let indexingAddressIndex = IndexingAddresses.make(~contractConfigs, ~addresses=indexingAddresses)
160
166
 
@@ -251,6 +257,7 @@ let makeInternal = (
251
257
  make(
252
258
  ~chainConfig,
253
259
  ~fetchState,
260
+ ~onEventRegistrations,
254
261
  ~indexingAddresses=indexingAddressIndex,
255
262
  ~sourceManager=SourceManager.make(~sources, ~isRealtime, ~reducedPollingInterval?),
256
263
  ~reorgDetection=ReorgDetection.make(
@@ -385,7 +392,8 @@ let isReadyToEnterReorgThreshold = (cs: t) => cs.fetchState->FetchState.isReadyT
385
392
  let startFetchingQueries = (cs: t, ~queries: array<FetchState.query>) => {
386
393
  cs.fetchState->FetchState.startFetchingQueries(~queries)
387
394
  cs.pendingBudget =
388
- cs.pendingBudget +. queries->Array.reduce(0., (acc, query) => acc +. query.itemsTarget)
395
+ cs.pendingBudget +.
396
+ queries->Array.reduce(0., (acc, query) => acc +. query.itemsEst->Int.toFloat)
389
397
  }
390
398
 
391
399
  // Drop every in-flight query and release their reservations together, keeping
@@ -395,24 +403,150 @@ let resetPendingQueries = (cs: t) => {
395
403
  cs.pendingBudget = 0.
396
404
  }
397
405
 
398
- // Turn this chain's share of the indexer-wide buffer budget into a soft
399
- // target block (via chain density, or knownHeight when density isn't known
400
- // yet), then propose queries sized against it. Called by CrossChainState's
401
- // waterfall, furthest-behind chain first, with chainTargetItems set to
402
- // whatever budget remains at that point in the waterfall.
403
- let getNextQuery = (cs: t, ~chainTargetItems: float) => {
406
+ let isReady = (cs: t) => cs.timestampCaughtUpToHeadOrEndblock !== None
407
+
408
+ // Block span over which a batch fully replaces the chain density estimate;
409
+ // smaller batches blend in proportionally, so a few sparse/dense blocks only
410
+ // nudge it.
411
+ let densityBlendWindow = 100.
412
+
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.
418
+ let fetchCeiling = (cs: t) => {
419
+ let fetchState = cs.fetchState
420
+ let head = Pervasives.max(0, fetchState.knownHeight - fetchState.blockLag)
421
+ switch fetchState.endBlock {
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
437
+ }
438
+ }
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
+
455
+ // This chain's share of the indexer-wide buffer budget turned into a soft
456
+ // target block: via density, or frontier + coldTargetRange when there's no
457
+ // density signal yet.
458
+ let targetBlock = (cs: t, ~chainTargetItems: float) => {
404
459
  let fetchState = cs.fetchState
405
- let knownHeight = fetchState.knownHeight
460
+ let fetchCeiling = cs->fetchCeiling
406
461
  let bufferBlockNumber = fetchState->FetchState.bufferBlockNumber
407
- let chainTargetBlock = switch cs.chainDensity {
462
+ switch cs->effectiveDensity {
408
463
  | Some(density) if density > 0. =>
409
464
  Pervasives.min(
410
- knownHeight,
465
+ fetchCeiling,
411
466
  bufferBlockNumber + Math.ceil(chainTargetItems /. density)->Float.toInt,
412
467
  )
413
- | _ => knownHeight
468
+ | _ => Pervasives.min(bufferBlockNumber + coldTargetRange, fetchCeiling)
414
469
  }
415
- fetchState->FetchState.getNextQuery(~chainTargetBlock, ~chainTargetItems)
470
+ }
471
+
472
+ // Block range that cross-chain progress alignment maps fractions over: from
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.
478
+ let progressRange = (cs: t) => {
479
+ let fetchState = cs.fetchState
480
+ (fetchState.startBlock, cs->fetchCeiling)
481
+ }
482
+
483
+ // A degenerate range (chain already at or past its last block) maps to 1 so it
484
+ // never constrains the other chains. Clamped at 0 for the initial -1 fetch
485
+ // frontier — the only possible blockNumber below the range's lower bound.
486
+ let progressAtBlock = (cs: t, ~blockNumber) => {
487
+ let (lower, upper) = cs->progressRange
488
+ upper <= lower
489
+ ? 1.
490
+ : Pervasives.max(
491
+ 0.,
492
+ Pervasives.min(1., (blockNumber - lower)->Int.toFloat /. (upper - lower)->Int.toFloat),
493
+ )
494
+ }
495
+
496
+ let blockAtProgress = (cs: t, ~progress) => {
497
+ let (lower, upper) = cs->progressRange
498
+ lower + Math.ceil(progress *. (upper - lower)->Int.toFloat)->Float.toInt
499
+ }
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
+
507
+ // Propose queries sized against this chain's target block. Called by
508
+ // CrossChainState's waterfall, furthest-behind chain first, with
509
+ // chainTargetItems set to whatever budget remains at that point and
510
+ // maxTargetBlock set to the most-behind chain's progress mapped onto this
511
+ // chain, so a chain with budget can't run further ahead than the chain the
512
+ // whole pool is prioritizing.
513
+ let getNextQuery = (
514
+ cs: t,
515
+ ~chainTargetItems: float,
516
+ ~chunkItemsMultiplier=1.,
517
+ ~itemsTargetFloor=0,
518
+ ~maxTargetBlock=?,
519
+ ) => {
520
+ let chainTargetBlock = cs->targetBlock(~chainTargetItems)
521
+ let chainTargetBlock = switch maxTargetBlock {
522
+ | Some(maxTargetBlock) => Pervasives.min(chainTargetBlock, maxTargetBlock)
523
+ | None => chainTargetBlock
524
+ }
525
+ // When the target block is clamped (head/endBlock/cross-chain alignment) a
526
+ // known-density chain can't use the whole handed budget — cap the fresh part
527
+ // at what the clamped range actually costs (in-flight reservations stay on
528
+ // top: they're already accounted and shouldn't crowd out new partitions), so
529
+ // the waterfall's leftover flows to the next chain in the same tick instead
530
+ // of being held by an oversized probe.
531
+ let chainTargetItems = switch cs->effectiveDensity {
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.
537
+ let rangeCost =
538
+ density *. (chainTargetBlock - cs.fetchState->FetchState.bufferBlockNumber)->Int.toFloat
539
+ Pervasives.min(chainTargetItems, Math.ceil(rangeCost) +. 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
543
+ }
544
+ cs.fetchState->FetchState.getNextQuery(
545
+ ~chainTargetBlock,
546
+ ~chainTargetItems,
547
+ ~chunkItemsMultiplier,
548
+ ~itemsTargetFloor,
549
+ )
416
550
  }
417
551
 
418
552
  // Run a fetch tick for this chain against its sources, feeding the owned fetch
@@ -451,8 +585,6 @@ let getHighestBlockBelowThreshold = (cs: t): int => {
451
585
 
452
586
  let isActivelyIndexing = (cs: t) => cs.fetchState->FetchState.isActivelyIndexing
453
587
 
454
- let isReady = (cs: t) => cs.timestampCaughtUpToHeadOrEndblock !== None
455
-
456
588
  // True once the fetch frontier has reached the head/endBlock for this chain.
457
589
  let isFetchingAtHead = (cs: t) => cs.fetchState->FetchState.isFetchingAtHead
458
590
 
@@ -648,6 +780,9 @@ let materializePageItems = async (
648
780
  ))
649
781
  }
650
782
 
783
+ let filterByClientAddress = (cs: t, items: array<Internal.item>): array<Internal.item> =>
784
+ items->FetchState.filterByClientAddress(~indexingAddresses=cs.indexingAddresses)
785
+
651
786
  let handleQueryResult = (
652
787
  cs: t,
653
788
  ~query: FetchState.query,
@@ -680,16 +815,11 @@ let handleQueryResult = (
680
815
 
681
816
  cs.fetchState =
682
817
  fs
683
- ->FetchState.handleQueryResult(
684
- ~indexingAddresses=cs.indexingAddresses,
685
- ~query,
686
- ~latestFetchedBlock,
687
- ~newItems,
688
- )
818
+ ->FetchState.handleQueryResult(~query, ~latestFetchedBlock, ~newItems)
689
819
  ->FetchState.updateKnownHeight(~knownHeight)
690
820
 
691
821
  // The query is no longer in flight, so release its reservation.
692
- cs.pendingBudget = Pervasives.max(0., cs.pendingBudget -. query.itemsTarget)
822
+ cs.pendingBudget = Pervasives.max(0., cs.pendingBudget -. query.itemsEst->Int.toFloat)
693
823
  }
694
824
 
695
825
  // Run reorg detection against a fetch response and commit the updated guard.
@@ -790,6 +920,10 @@ let advanceAfterBatch = (cs: t, ~batch: Batch.t, ~enteringReorgThreshold) =>
790
920
  cs.fetchState = enteringReorgThreshold
791
921
  ? chainAfterBatch.fetchState->FetchState.updateInternal(~blockLag=cs.chainConfig.blockLag)
792
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
793
927
  | None => ()
794
928
  }
795
929
 
@@ -852,8 +986,9 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t, ~blockTimestampName: string) =
852
986
  }
853
987
 
854
988
  // Chain-wide density update: seed with the batch's own events/block on
855
- // the first update, then smooth incrementally so a run of a few
856
- // sparse/dense blocks doesn't swing the target block estimate.
989
+ // the first update, then blend weighted by the batch's block span — a
990
+ // few sparse/dense blocks barely nudge the estimate, while a
991
+ // window-sized batch replaces it.
857
992
  let deltaBlocks = chainAfterBatch.progressBlockNumber - cs.committedProgressBlockNumber
858
993
  if deltaBlocks > 0 {
859
994
  let deltaEvents = chainAfterBatch.totalEventsProcessed -. cs.numEventsProcessed
@@ -866,13 +1001,22 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t, ~blockTimestampName: string) =
866
1001
  cs.chainDensity = Some(
867
1002
  switch cs.chainDensity {
868
1003
  | None => batchDensity
869
- | Some(oldDensity) => (oldDensity +. batchDensity) /. 2.
1004
+ | Some(oldDensity) =>
1005
+ let alpha = Pervasives.min(1., deltaBlocks->Int.toFloat /. densityBlendWindow)
1006
+ oldDensity *. (1. -. alpha) +. batchDensity *. alpha
870
1007
  },
871
1008
  )
872
1009
  }
873
1010
  }
874
1011
 
875
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
+ )
876
1020
  cs.numEventsProcessed = chainAfterBatch.totalEventsProcessed
877
1021
  // Processed blocks' transactions and blocks are no longer needed.
878
1022
  cs.transactionStore->TransactionStore.prune(chainAfterBatch.progressBlockNumber)
@@ -957,6 +1101,7 @@ let rollback = (
957
1101
  cs.transactionStore->TransactionStore.rollback(newProgressBlockNumber)
958
1102
  cs.blockStore->BlockStore.rollback(newProgressBlockNumber)
959
1103
  cs.committedProgressBlockNumber = newProgressBlockNumber
1104
+ cs.processingBlockNumber = newProgressBlockNumber
960
1105
  cs.numEventsProcessed = newTotalEventsProcessed
961
1106
  | None =>
962
1107
  if isReorgChain {
@@ -975,6 +1120,7 @@ let rollback = (
975
1120
  cs.committedProgressBlockNumber,
976
1121
  rollbackTargetBlockNumber,
977
1122
  )
1123
+ cs.processingBlockNumber = Pervasives.min(cs.processingBlockNumber, rollbackTargetBlockNumber)
978
1124
  }
979
1125
  }
980
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,
@@ -74,7 +86,6 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
74
86
  onEventRegistrations: [],
75
87
  onBlockRegistrations: []
76
88
  });
77
- let onBlockRegistrations = match.onBlockRegistrations;
78
89
  let onEventRegistrations = match.onEventRegistrations;
79
90
  chainConfig.contracts.forEach(contract => {
80
91
  let startBlock = contract.startBlock;
@@ -82,17 +93,9 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
82
93
  return Stdlib_JsError.throwWithMessage(`The start block for contract "` + contract.name + `" is less than the chain start block. This is not supported yet.`);
83
94
  }
84
95
  });
85
- onBlockRegistrations.forEach(onBlockRegistration => {
86
- if (Stdlib_Option.getOr(onBlockRegistration.startBlock, startBlock) < startBlock) {
87
- Stdlib_JsError.throwWithMessage(`The start block for onBlock handler "` + onBlockRegistration.name + `" is less than the chain start block (` + startBlock.toString() + `). This is not supported yet.`);
88
- }
89
- if (endBlock !== undefined && Stdlib_Option.getOr(onBlockRegistration.endBlock, endBlock) > endBlock) {
90
- return Stdlib_JsError.throwWithMessage(`The end block for onBlock handler "` + onBlockRegistration.name + `" is greater than the chain end block (` + endBlock.toString() + `). This is not supported yet.`);
91
- }
92
- });
93
96
  let contractConfigs = IndexingAddresses.makeContractConfigs(onEventRegistrations);
94
97
  let indexingAddressIndex = IndexingAddresses.make(contractConfigs, indexingAddresses);
95
- let fetchState = FetchState.make(startBlock, endBlock, onEventRegistrations, contractConfigs, indexingAddresses, config.maxAddrInPartition, chainConfig.id, (config.batchSize << 1), knownHeight, progressBlockNumber, onBlockRegistrations, Primitive_int.max(!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth, chainConfig.blockLag), Primitive_option.some(firstEventBlock));
98
+ let fetchState = FetchState.make(startBlock, endBlock, onEventRegistrations, contractConfigs, indexingAddresses, config.maxAddrInPartition, chainConfig.id, (config.batchSize << 1), knownHeight, progressBlockNumber, match.onBlockRegistrations, Primitive_int.max(!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth, chainConfig.blockLag), Primitive_option.some(firstEventBlock));
96
99
  let chainReorgCheckpoints = Stdlib_Array.filterMap(reorgCheckpoints, reorgCheckpoint => {
97
100
  if (reorgCheckpoint.chain_id === chainConfig.id) {
98
101
  return reorgCheckpoint;
@@ -144,7 +147,7 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
144
147
  }
145
148
  let firstEventBlock$1 = fetchState.firstEventBlock;
146
149
  let chainDensity = firstEventBlock$1 !== undefined && progressBlockNumber > firstEventBlock$1 && numEventsProcessed > 0 ? numEventsProcessed / (progressBlockNumber - firstEventBlock$1 | 0) : undefined;
147
- 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);
148
151
  }
149
152
 
150
153
  function makeFromConfig(chainConfig, config, registrationsByChainId, knownHeight) {
@@ -242,16 +245,100 @@ function isReadyToEnterReorgThreshold(cs) {
242
245
 
243
246
  function startFetchingQueries(cs, queries) {
244
247
  FetchState.startFetchingQueries(cs.fetchState, queries);
245
- 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);
246
249
  }
247
250
 
248
- function getNextQuery(cs, chainTargetItems) {
251
+ function isReady(cs) {
252
+ return cs.timestampCaughtUpToHeadOrEndblock !== undefined;
253
+ }
254
+
255
+ function fetchCeiling(cs) {
249
256
  let fetchState = cs.fetchState;
250
- let knownHeight = fetchState.knownHeight;
257
+ let head = Primitive_int.max(0, fetchState.knownHeight - fetchState.blockLag | 0);
258
+ let endBlock = fetchState.endBlock;
259
+ if (endBlock !== undefined) {
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;
285
+ } else {
286
+ return;
287
+ }
288
+ }
289
+
290
+ function targetBlock(cs, chainTargetItems) {
291
+ let fetchState = cs.fetchState;
292
+ let fetchCeiling$1 = fetchCeiling(cs);
251
293
  let bufferBlockNumber = FetchState.bufferBlockNumber(fetchState);
252
- let density = cs.chainDensity;
253
- let chainTargetBlock = density !== undefined && density > 0 ? Primitive_int.min(knownHeight, bufferBlockNumber + (Math.ceil(chainTargetItems / density) | 0) | 0) : knownHeight;
254
- return FetchState.getNextQuery(fetchState, chainTargetBlock, chainTargetItems);
294
+ let density = effectiveDensity(cs);
295
+ if (density !== undefined && density > 0) {
296
+ return Primitive_int.min(fetchCeiling$1, bufferBlockNumber + (Math.ceil(chainTargetItems / density) | 0) | 0);
297
+ } else {
298
+ return Primitive_int.min(bufferBlockNumber + 20000 | 0, fetchCeiling$1);
299
+ }
300
+ }
301
+
302
+ function progressRange(cs) {
303
+ let fetchState = cs.fetchState;
304
+ return [
305
+ fetchState.startBlock,
306
+ fetchCeiling(cs)
307
+ ];
308
+ }
309
+
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);
318
+ let match = progressRange(cs);
319
+ let upper = match[1];
320
+ let lower = match[0];
321
+ if (upper <= lower) {
322
+ return 1;
323
+ } else {
324
+ return Primitive_float.max(0, Primitive_float.min(1, (blockNumber - lower | 0) / (upper - lower | 0)));
325
+ }
326
+ }
327
+
328
+ function getNextQuery(cs, chainTargetItems, chunkItemsMultiplierOpt, itemsTargetFloorOpt, maxTargetBlock) {
329
+ let chunkItemsMultiplier = chunkItemsMultiplierOpt !== undefined ? chunkItemsMultiplierOpt : 1;
330
+ let itemsTargetFloor = itemsTargetFloorOpt !== undefined ? itemsTargetFloorOpt : 0;
331
+ let chainTargetBlock = targetBlock(cs, chainTargetItems);
332
+ let chainTargetBlock$1 = maxTargetBlock !== undefined ? Primitive_int.min(chainTargetBlock, maxTargetBlock) : chainTargetBlock;
333
+ let density = effectiveDensity(cs);
334
+ let chainTargetItems$1;
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);
338
+ } else {
339
+ chainTargetItems$1 = chainTargetItems;
340
+ }
341
+ return FetchState.getNextQuery(cs.fetchState, chainTargetBlock$1, chainTargetItems$1, chunkItemsMultiplier, itemsTargetFloor);
255
342
  }
256
343
 
257
344
  function dispatch(cs, executeQuery, waitForNewBlock, onNewBlock, action, stateId) {
@@ -282,10 +369,6 @@ function isActivelyIndexing(cs) {
282
369
  return FetchState.isActivelyIndexing(cs.fetchState);
283
370
  }
284
371
 
285
- function isReady(cs) {
286
- return cs.timestampCaughtUpToHeadOrEndblock !== undefined;
287
- }
288
-
289
372
  function isFetchingAtHead(cs) {
290
373
  return FetchState.isFetchingAtHead(cs.fetchState);
291
374
  }
@@ -426,6 +509,10 @@ async function materializePageItems(items, transactionStore, blockStore, ecosyst
426
509
  ]);
427
510
  }
428
511
 
512
+ function filterByClientAddress(cs, items) {
513
+ return FetchState.filterByClientAddress(items, cs.indexingAddresses);
514
+ }
515
+
429
516
  function handleQueryResult(cs, query, newItems, newItemsWithDcs, latestFetchedBlock, knownHeight, txPage, blockPage) {
430
517
  if (txPage !== undefined) {
431
518
  cs.transactionStore.merge(Primitive_option.valFromOption(txPage));
@@ -434,8 +521,8 @@ function handleQueryResult(cs, query, newItems, newItemsWithDcs, latestFetchedBl
434
521
  cs.blockStore.merge(Primitive_option.valFromOption(blockPage));
435
522
  }
436
523
  let fs = newItemsWithDcs.length !== 0 ? FetchState.registerDynamicContracts(cs.fetchState, cs.indexingAddresses, newItemsWithDcs) : cs.fetchState;
437
- cs.fetchState = FetchState.updateKnownHeight(FetchState.handleQueryResult(fs, cs.indexingAddresses, query, latestFetchedBlock, newItems), knownHeight);
438
- 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);
439
526
  }
440
527
 
441
528
  function registerReorgGuard(cs, blockHashes, knownHeight) {
@@ -499,7 +586,7 @@ function setEndBlockToFirstEvent(cs, blockNumber) {
499
586
  }
500
587
 
501
588
  function enterReorgThreshold(cs) {
502
- 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);
503
590
  }
504
591
 
505
592
  function toChainMetadata(cs) {
@@ -547,7 +634,8 @@ function isReadyToEnterReorgThresholdAfterBatch(cs, batch) {
547
634
  function advanceAfterBatch(cs, batch, enteringReorgThreshold) {
548
635
  let chainAfterBatch = batch.progressedChainsById[cs.fetchState.chainId];
549
636
  if (chainAfterBatch !== undefined) {
550
- 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;
551
639
  return;
552
640
  }
553
641
  }
@@ -603,10 +691,18 @@ function applyBatchProgress(cs, batch, blockTimestampName) {
603
691
  if (exit === 1) {
604
692
  let batchDensity = deltaEvents / deltaBlocks;
605
693
  let oldDensity = cs.chainDensity;
606
- cs.chainDensity = oldDensity !== undefined ? (oldDensity + batchDensity) / 2 : batchDensity;
694
+ let tmp;
695
+ if (oldDensity !== undefined) {
696
+ let alpha = Primitive_float.min(1, deltaBlocks / 100);
697
+ tmp = oldDensity * (1 - alpha) + batchDensity * alpha;
698
+ } else {
699
+ tmp = batchDensity;
700
+ }
701
+ cs.chainDensity = tmp;
607
702
  }
608
703
  }
609
704
  cs.committedProgressBlockNumber = chainAfterBatch.progressBlockNumber;
705
+ cs.processingBlockNumber = Primitive_int.max(cs.processingBlockNumber, chainAfterBatch.progressBlockNumber);
610
706
  cs.numEventsProcessed = chainAfterBatch.totalEventsProcessed;
611
707
  cs.transactionStore.prune(chainAfterBatch.progressBlockNumber);
612
708
  cs.blockStore.prune(chainAfterBatch.progressBlockNumber);
@@ -634,6 +730,7 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
634
730
  cs.transactionStore.rollback(rollbackTargetBlockNumber);
635
731
  cs.blockStore.rollback(rollbackTargetBlockNumber);
636
732
  cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, rollbackTargetBlockNumber);
733
+ cs.processingBlockNumber = Primitive_int.min(cs.processingBlockNumber, rollbackTargetBlockNumber);
637
734
  return;
638
735
  } else {
639
736
  return;
@@ -657,6 +754,7 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
657
754
  cs.transactionStore.rollback(newProgressBlockNumber);
658
755
  cs.blockStore.rollback(newProgressBlockNumber);
659
756
  cs.committedProgressBlockNumber = newProgressBlockNumber;
757
+ cs.processingBlockNumber = newProgressBlockNumber;
660
758
  cs.numEventsProcessed = newTotalEventsProcessed;
661
759
  }
662
760
 
@@ -682,8 +780,12 @@ export {
682
780
  bufferReadyCount,
683
781
  getProgressPercentage,
684
782
  chainDensity,
783
+ effectiveDensity,
685
784
  hasReadyItem,
686
785
  isReadyToEnterReorgThreshold,
786
+ targetBlock,
787
+ blockAtProgress,
788
+ frontierProgress,
687
789
  getNextQuery,
688
790
  dispatch,
689
791
  toChainData,
@@ -696,6 +798,7 @@ export {
696
798
  isReady,
697
799
  isFetchingAtHead,
698
800
  isAtHeadWithoutEndBlock,
801
+ filterByClientAddress,
699
802
  handleQueryResult,
700
803
  materializeBatchItems,
701
804
  materializePageItems,