envio 3.3.0-alpha.6 → 3.3.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/index.d.ts +23 -5
  2. package/package.json +6 -6
  3. package/src/Address.res +5 -2
  4. package/src/Address.res.mjs +3 -1
  5. package/src/Api.res +1 -1
  6. package/src/Api.res.mjs +1 -1
  7. package/src/ChainFetching.res +34 -34
  8. package/src/ChainFetching.res.mjs +29 -29
  9. package/src/ChainState.res +328 -165
  10. package/src/ChainState.res.mjs +205 -100
  11. package/src/ChainState.resi +16 -7
  12. package/src/Config.res +55 -27
  13. package/src/Config.res.mjs +46 -7
  14. package/src/ContractRegisterContext.res +2 -12
  15. package/src/ContractRegisterContext.res.mjs +3 -5
  16. package/src/Core.res +7 -0
  17. package/src/CrossChainState.res +54 -79
  18. package/src/CrossChainState.res.mjs +45 -60
  19. package/src/CrossChainState.resi +1 -1
  20. package/src/Ecosystem.res +3 -3
  21. package/src/Ecosystem.res.mjs +3 -3
  22. package/src/Envio.res +14 -9
  23. package/src/EventConfigBuilder.res +105 -56
  24. package/src/EventConfigBuilder.res.mjs +69 -33
  25. package/src/EventProcessing.res +19 -15
  26. package/src/EventProcessing.res.mjs +13 -12
  27. package/src/FetchState.res +383 -182
  28. package/src/FetchState.res.mjs +260 -264
  29. package/src/HandlerLoader.res +7 -112
  30. package/src/HandlerLoader.res.mjs +1 -87
  31. package/src/HandlerRegister.res +209 -6
  32. package/src/HandlerRegister.res.mjs +90 -5
  33. package/src/HandlerRegister.resi +13 -2
  34. package/src/IndexerState.res +6 -3
  35. package/src/IndexerState.res.mjs +3 -3
  36. package/src/IndexerState.resi +1 -1
  37. package/src/IndexingAddresses.res +10 -7
  38. package/src/IndexingAddresses.res.mjs +8 -7
  39. package/src/IndexingAddresses.resi +3 -1
  40. package/src/Internal.res +82 -36
  41. package/src/Internal.res.mjs +11 -1
  42. package/src/Main.res +22 -23
  43. package/src/Main.res.mjs +15 -17
  44. package/src/Metrics.res +43 -2
  45. package/src/Metrics.res.mjs +39 -3
  46. package/src/Prometheus.res +0 -35
  47. package/src/Prometheus.res.mjs +33 -68
  48. package/src/RawEvent.res +7 -2
  49. package/src/RawEvent.res.mjs +4 -4
  50. package/src/SimulateItems.res +26 -8
  51. package/src/SimulateItems.res.mjs +11 -14
  52. package/src/TestIndexer.res +1 -1
  53. package/src/TestIndexer.res.mjs +1 -1
  54. package/src/sources/BlockStore.res +46 -0
  55. package/src/sources/BlockStore.res.mjs +24 -0
  56. package/src/sources/EventRouter.res +19 -12
  57. package/src/sources/EventRouter.res.mjs +7 -5
  58. package/src/sources/Evm.res +54 -4
  59. package/src/sources/Evm.res.mjs +43 -4
  60. package/src/sources/EvmChain.res +14 -18
  61. package/src/sources/EvmChain.res.mjs +12 -13
  62. package/src/sources/FieldMask.res +39 -0
  63. package/src/sources/FieldMask.res.mjs +42 -0
  64. package/src/sources/Fuel.res +5 -2
  65. package/src/sources/Fuel.res.mjs +4 -3
  66. package/src/sources/HyperFuelSource.res +32 -40
  67. package/src/sources/HyperFuelSource.res.mjs +53 -45
  68. package/src/sources/HyperSync.res +35 -9
  69. package/src/sources/HyperSync.res.mjs +52 -35
  70. package/src/sources/HyperSync.resi +11 -3
  71. package/src/sources/HyperSyncClient.res +15 -4
  72. package/src/sources/HyperSyncHeightStream.res +1 -8
  73. package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
  74. package/src/sources/HyperSyncSource.res +39 -58
  75. package/src/sources/HyperSyncSource.res.mjs +44 -44
  76. package/src/sources/RpcSource.res +114 -92
  77. package/src/sources/RpcSource.res.mjs +77 -46
  78. package/src/sources/RpcWebSocketHeightStream.res +0 -5
  79. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
  80. package/src/sources/SimulateSource.res +6 -3
  81. package/src/sources/SimulateSource.res.mjs +13 -5
  82. package/src/sources/Source.res +25 -5
  83. package/src/sources/SourceManager.res +66 -3
  84. package/src/sources/SourceManager.res.mjs +55 -7
  85. package/src/sources/SourceManager.resi +10 -0
  86. package/src/sources/Svm.res +15 -10
  87. package/src/sources/Svm.res.mjs +24 -7
  88. package/src/sources/SvmHyperSyncClient.res +5 -6
  89. package/src/sources/SvmHyperSyncSource.res +84 -41
  90. package/src/sources/SvmHyperSyncSource.res.mjs +90 -43
  91. package/src/sources/TransactionStore.res +6 -107
  92. package/src/sources/TransactionStore.res.mjs +5 -86
  93. package/svm.schema.json +19 -0
@@ -7,15 +7,19 @@ type blockNumberAndTimestamp = {
7
7
 
8
8
  type blockNumberAndLogIndex = {blockNumber: int, logIndex: int}
9
9
 
10
- type selection = {eventConfigs: array<Internal.eventConfig>, dependsOnAddresses: bool}
10
+ type selection = {
11
+ onEventRegistrations: array<Internal.onEventRegistration>,
12
+ dependsOnAddresses: bool,
13
+ }
11
14
 
12
15
  type pendingQuery = {
13
16
  fromBlock: int,
14
17
  toBlock: option<int>,
15
18
  isChunk: bool,
16
- // Estimated items this in-flight query will add, carried from the query so the
17
- // shared buffer budget can account for what's already being fetched.
18
- estResponseSize: float,
19
+ // Items this in-flight query is targeting (server maxNumLogs-style cap), carried
20
+ // from the query so the shared buffer budget can account for what's already
21
+ // being fetched.
22
+ itemsTarget: float,
19
23
  // Stores latestFetchedBlock when query completes. Only needed to persist
20
24
  // timestamp while earlier queries are still pending before updating
21
25
  // the partition's latestFetchedBlock.
@@ -59,15 +63,12 @@ type query = {
59
63
  fromBlock: int,
60
64
  toBlock: option<int>,
61
65
  isChunk: bool,
62
- // Estimated number of items the query will return, from the partition's
63
- // density and the query's block range. Used to admit queries against the
64
- // shared buffer budget.
65
- estResponseSize: float,
66
- // Owning chain and the chain progress % at the query's fromBlock. Set by the
67
- // cross-chain scheduler so candidate queries can be pooled and ordered
68
- // (furthest-behind first) without allocating a side tuple per query.
69
- mutable chainId: int,
70
- mutable progress: float,
66
+ // Items this query targets: the server-side maxNumLogs-style cap, and the
67
+ // unit the chain's per-tick budget is reserved/consumed in. For a partition
68
+ // with known density this is density × the query's block range; for a
69
+ // partition with no signal yet it's whatever budget share the query was
70
+ // sized against.
71
+ itemsTarget: float,
71
72
  selection: selection,
72
73
  addressesByContractName: dict<array<Address.t>>,
73
74
  }
@@ -90,24 +91,28 @@ let deriveContractNameByAddress: dict<array<Address.t>> => dict<
90
91
  result
91
92
  })
92
93
 
93
- // Default estimate for a query whose partition hasn't responded yet, so the
94
- // shared budget still accounts for unknown queries instead of treating them as
95
- // free.
96
- let defaultEstResponseSize = 10_000.
97
-
98
- // Estimated items a query will return, from the partition's event density
99
- // (items/block derived from its last response) and the query's block range.
100
- // toBlock None is the open-ended tail, capped at maxQueryBlockNumber. A partition
101
- // that responded with no items has density 0, so its queries cost 0 — correct,
102
- // they don't fill the buffer. Only a partition that has never responded
103
- // (prevQueryRange 0) has no signal, so it falls back to defaultEstResponseSize.
104
- let calculateEstResponseSize = (p: partition, ~fromBlock, ~toBlock, ~maxQueryBlockNumber) =>
105
- if p.prevQueryRange > 0 {
106
- let density = p.prevRangeSize->Int.toFloat /. p.prevQueryRange->Int.toFloat
107
- (toBlock->Option.getOr(maxQueryBlockNumber) - fromBlock + 1)->Int.toFloat *. density
108
- } else {
109
- defaultEstResponseSize
110
- }
94
+ // Floor for a query's itemsTarget. Also used by SourceManager as the floor for
95
+ // its own maxNumLogs-style safety net.
96
+ let minItemsTarget = 2_000.
97
+
98
+ // Ceiling for an unknown-density probe query's itemsTarget. A partition with no
99
+ // trusted density yet gets one open-ended probe; without a cap it would claim its
100
+ // full even share of the chain's (possibly whole-pool) budget and starve sibling
101
+ // chains needing their own first probe in the same cross-chain tick.
102
+ let maxItemsTarget = 10_000.
103
+
104
+ // itemsTarget for a query over [fromBlock, toBlock], from the partition's event
105
+ // density (items/block derived from its last response). toBlock None is the
106
+ // open-ended tail, capped at chainTargetBlock — the soft per-tick horizon the
107
+ // owning chain wants to reach (see getNextQuery). A partition that responded
108
+ // with no items has density 0, so its queries cost 0 — correct, they don't
109
+ // fill the buffer. Only called for partitions with a known density
110
+ // (prevQueryRange > 0); partitions with no signal yet are sized instead
111
+ // against their even split of the chain's range budget.
112
+ let densityItemsTarget = (p: partition, ~fromBlock, ~toBlock, ~chainTargetBlock) => {
113
+ let density = p.prevRangeSize->Int.toFloat /. p.prevQueryRange->Int.toFloat
114
+ (toBlock->Option.getOr(chainTargetBlock) - fromBlock + 1)->Int.toFloat *. density
115
+ }
111
116
 
112
117
  // Calculate the chunk range from history using min-of-last-3-ranges heuristic
113
118
  let getMinHistoryRange = (p: partition) => {
@@ -545,10 +550,10 @@ type t = {
545
550
  // Buffer of items ordered from earliest to latest
546
551
  buffer: array<Internal.item>,
547
552
  // Caps how far ahead onBlock items are pre-generated (set to 2x the batch
548
- // size). Fetch depth is bounded separately by getNextQuery's itemBudget, the
549
- // chain's per-tick slice of the indexer-wide pool.
553
+ // size). Event fetch depth is bounded separately, by CrossChainState's
554
+ // cross-chain admission against the indexer-wide buffer pool.
550
555
  maxOnBlockBufferSize: int,
551
- onBlockConfigs: array<Internal.onBlockConfig>,
556
+ onBlockRegistrations: array<Internal.onBlockRegistration>,
552
557
  knownHeight: int,
553
558
  firstEventBlock: option<int>,
554
559
  }
@@ -624,7 +629,7 @@ let blockItemLogIndex = 16777216
624
629
  // are generated at once to prevent OOM.
625
630
  let appendOnBlockItems = (
626
631
  ~mutItems: array<Internal.item>,
627
- ~onBlockConfigs: array<Internal.onBlockConfig>,
632
+ ~onBlockRegistrations: array<Internal.onBlockRegistration>,
628
633
  ~indexerStartBlock,
629
634
  ~fromBlock,
630
635
  ~maxBlockNumber,
@@ -644,27 +649,27 @@ let appendOnBlockItems = (
644
649
  let blockNumber = latestOnBlockBlockNumber.contents + 1
645
650
  latestOnBlockBlockNumber := blockNumber
646
651
 
647
- for configIdx in 0 to onBlockConfigs->Array.length - 1 {
648
- let onBlockConfig = onBlockConfigs->Array.getUnsafe(configIdx)
652
+ for configIdx in 0 to onBlockRegistrations->Array.length - 1 {
653
+ let onBlockRegistration = onBlockRegistrations->Array.getUnsafe(configIdx)
649
654
 
650
- let handlerStartBlock = switch onBlockConfig.startBlock {
655
+ let handlerStartBlock = switch onBlockRegistration.startBlock {
651
656
  | Some(startBlock) => startBlock
652
657
  | None => indexerStartBlock
653
658
  }
654
659
 
655
660
  if (
656
661
  blockNumber >= handlerStartBlock &&
657
- switch onBlockConfig.endBlock {
662
+ switch onBlockRegistration.endBlock {
658
663
  | Some(endBlock) => blockNumber <= endBlock
659
664
  | None => true
660
665
  } &&
661
- (blockNumber - handlerStartBlock)->Pervasives.mod(onBlockConfig.interval) === 0
666
+ (blockNumber - handlerStartBlock)->Pervasives.mod(onBlockRegistration.interval) === 0
662
667
  ) {
663
668
  mutItems->Array.push(
664
669
  Block({
665
- onBlockConfig,
670
+ onBlockRegistration,
666
671
  blockNumber,
667
- logIndex: blockItemLogIndex + onBlockConfig.index,
672
+ logIndex: blockItemLogIndex + onBlockRegistration.index,
668
673
  }),
669
674
  )
670
675
  newItemsCounter := newItemsCounter.contents + 1
@@ -688,9 +693,9 @@ let updateInternal = (
688
693
  ): t => {
689
694
  let mutItemsRef = ref(mutItems)
690
695
 
691
- let latestOnBlockBlockNumber = switch fetchState.onBlockConfigs {
696
+ let latestOnBlockBlockNumber = switch fetchState.onBlockRegistrations {
692
697
  | [] => knownHeight
693
- | onBlockConfigs => {
698
+ | onBlockRegistrations => {
694
699
  // Calculate the max block number we are going to create items for
695
700
  // Use maxOnBlockBufferSize to get the last target item in the buffer
696
701
  //
@@ -718,7 +723,7 @@ let updateInternal = (
718
723
 
719
724
  appendOnBlockItems(
720
725
  ~mutItems,
721
- ~onBlockConfigs,
726
+ ~onBlockRegistrations,
722
727
  ~indexerStartBlock=fetchState.startBlock,
723
728
  ~fromBlock=fetchState.latestOnBlockBlockNumber,
724
729
  ~maxBlockNumber,
@@ -733,7 +738,7 @@ let updateInternal = (
733
738
  contractConfigs: fetchState.contractConfigs,
734
739
  normalSelection: fetchState.normalSelection,
735
740
  chainId: fetchState.chainId,
736
- onBlockConfigs: fetchState.onBlockConfigs,
741
+ onBlockRegistrations: fetchState.onBlockRegistrations,
737
742
  maxOnBlockBufferSize: fetchState.maxOnBlockBufferSize,
738
743
  optimizedPartitions,
739
744
  latestOnBlockBlockNumber,
@@ -999,7 +1004,7 @@ let registerDynamicContracts = (
999
1004
  // Might contain duplicates which we should filter out
1000
1005
  items: array<Internal.item>,
1001
1006
  ) => {
1002
- if fetchState.normalSelection.eventConfigs->Utils.Array.isEmpty {
1007
+ if fetchState.normalSelection.onEventRegistrations->Utils.Array.isEmpty {
1003
1008
  // Can the normalSelection be empty?
1004
1009
  JsError.throwWithMessage(
1005
1010
  "Invalid configuration. No events to fetch for the dynamic contract registration.",
@@ -1283,8 +1288,8 @@ let handleQueryResult = (
1283
1288
  // param-level analogue of EventRouter's srcAddress effectiveStartBlock check.
1284
1289
  let newItems = newItems->Array.filter(item =>
1285
1290
  switch item {
1286
- | Internal.Event({eventConfig, payload, blockNumber}) =>
1287
- switch eventConfig.clientAddressFilter {
1291
+ | Internal.Event({onEventRegistration, payload, blockNumber}) =>
1292
+ switch onEventRegistration.clientAddressFilter {
1288
1293
  | Some(filter) =>
1289
1294
  filter(payload, blockNumber, indexingAddresses->IndexingAddresses.rawForFilter)
1290
1295
  | None => true
@@ -1322,7 +1327,7 @@ let startFetchingQueries = ({optimizedPartitions}: t, ~queries: array<query>) =>
1322
1327
  fromBlock: q.fromBlock,
1323
1328
  toBlock: q.toBlock,
1324
1329
  isChunk: q.isChunk,
1325
- estResponseSize: q.estResponseSize,
1330
+ itemsTarget: q.itemsTarget,
1326
1331
  fetchedBlock: None,
1327
1332
  }
1328
1333
 
@@ -1346,45 +1351,50 @@ let startFetchingQueries = ({optimizedPartitions}: t, ~queries: array<query>) =>
1346
1351
  // Most parallel in-flight chunk queries a single partition may have at once.
1347
1352
  let maxPendingChunksPerPartition = 10
1348
1353
 
1349
- @inline
1350
- let pushQueriesForRange = (
1354
+ // Fills a gap range (a hole left between completed/pending chunks, e.g. from an
1355
+ // out-of-order partial response) unconditionally against the partition's real
1356
+ // density — gaps are already-committed range, not subject to this tick's
1357
+ // water-fill budget. Returns the created queries' total itemsTarget.
1358
+ let pushGapFillQueries = (
1351
1359
  queries: array<query>,
1352
1360
  ~partitionId: string,
1353
1361
  ~rangeFromBlock: int,
1354
1362
  ~rangeEndBlock: option<int>,
1355
- ~maxQueryBlockNumber: int,
1363
+ ~knownHeight: int,
1364
+ ~chainTargetBlock: int,
1356
1365
  ~maybeChunkRange: option<int>,
1357
1366
  ~maxChunks: int,
1358
1367
  ~partition: partition,
1359
1368
  ~selection: selection,
1360
1369
  ~addressesByContractName: dict<array<Address.t>>,
1361
1370
  ) => {
1362
- if rangeFromBlock <= maxQueryBlockNumber && maxChunks > 0 {
1371
+ let cost = ref(0.)
1372
+ if rangeFromBlock <= knownHeight && maxChunks > 0 {
1363
1373
  switch rangeEndBlock {
1364
1374
  | Some(endBlock) if rangeFromBlock > endBlock => ()
1365
1375
  | _ =>
1366
1376
  switch maybeChunkRange {
1367
1377
  | None =>
1378
+ let itemsTarget = densityItemsTarget(
1379
+ partition,
1380
+ ~fromBlock=rangeFromBlock,
1381
+ ~toBlock=rangeEndBlock,
1382
+ ~chainTargetBlock,
1383
+ )
1368
1384
  queries->Array.push({
1369
1385
  partitionId,
1370
1386
  fromBlock: rangeFromBlock,
1371
1387
  toBlock: rangeEndBlock,
1372
1388
  selection,
1373
1389
  isChunk: false,
1374
- estResponseSize: calculateEstResponseSize(
1375
- partition,
1376
- ~fromBlock=rangeFromBlock,
1377
- ~toBlock=rangeEndBlock,
1378
- ~maxQueryBlockNumber,
1379
- ),
1380
- chainId: 0,
1381
- progress: 0.,
1390
+ itemsTarget,
1382
1391
  addressesByContractName,
1383
1392
  })
1393
+ cost := cost.contents +. itemsTarget
1384
1394
  | Some(chunkRange) =>
1385
1395
  let maxBlock = switch rangeEndBlock {
1386
1396
  | Some(eb) => eb
1387
- | None => maxQueryBlockNumber
1397
+ | None => chainTargetBlock
1388
1398
  }
1389
1399
  let chunkSize = Js.Math.ceil_int(chunkRange->Int.toFloat *. 1.8)
1390
1400
  if rangeFromBlock + chunkSize * 2 - 1 <= maxBlock {
@@ -1394,102 +1404,119 @@ let pushQueriesForRange = (
1394
1404
  chunkIdx.contents < maxChunks && chunkFromBlock.contents + chunkSize - 1 <= maxBlock
1395
1405
  ) {
1396
1406
  let chunkToBlock = chunkFromBlock.contents + chunkSize - 1
1407
+ let itemsTarget = densityItemsTarget(
1408
+ partition,
1409
+ ~fromBlock=chunkFromBlock.contents,
1410
+ ~toBlock=Some(chunkToBlock),
1411
+ ~chainTargetBlock,
1412
+ )
1397
1413
  queries->Array.push({
1398
1414
  partitionId,
1399
1415
  fromBlock: chunkFromBlock.contents,
1400
1416
  toBlock: Some(chunkToBlock),
1401
1417
  isChunk: true,
1402
1418
  selection,
1403
- estResponseSize: calculateEstResponseSize(
1404
- partition,
1405
- ~fromBlock=chunkFromBlock.contents,
1406
- ~toBlock=Some(chunkToBlock),
1407
- ~maxQueryBlockNumber,
1408
- ),
1409
- chainId: 0,
1410
- progress: 0.,
1419
+ itemsTarget,
1411
1420
  addressesByContractName,
1412
1421
  })
1422
+ cost := cost.contents +. itemsTarget
1413
1423
  chunkFromBlock := chunkToBlock + 1
1414
1424
  chunkIdx := chunkIdx.contents + 1
1415
1425
  }
1416
1426
  } else {
1417
1427
  // Not enough room for 2 chunks, fall back to a single query
1428
+ let itemsTarget = densityItemsTarget(
1429
+ partition,
1430
+ ~fromBlock=rangeFromBlock,
1431
+ ~toBlock=rangeEndBlock,
1432
+ ~chainTargetBlock,
1433
+ )
1418
1434
  queries->Array.push({
1419
1435
  partitionId,
1420
1436
  fromBlock: rangeFromBlock,
1421
1437
  toBlock: rangeEndBlock,
1422
1438
  selection,
1423
1439
  isChunk: rangeEndBlock !== None,
1424
- estResponseSize: calculateEstResponseSize(
1425
- partition,
1426
- ~fromBlock=rangeFromBlock,
1427
- ~toBlock=rangeEndBlock,
1428
- ~maxQueryBlockNumber,
1429
- ),
1430
- chainId: 0,
1431
- progress: 0.,
1440
+ itemsTarget,
1432
1441
  addressesByContractName,
1433
1442
  })
1443
+ cost := cost.contents +. itemsTarget
1434
1444
  }
1435
1445
  }
1436
1446
  }
1437
1447
  }
1448
+ cost.contents
1438
1449
  }
1439
1450
 
1451
+ // Per-partition mutable state threaded through the water-fill rounds below.
1452
+ type waterFillState = {
1453
+ partitionId: string,
1454
+ p: partition,
1455
+ mutable cursor: int,
1456
+ mutable chunksUsedThisCall: int,
1457
+ // Existing mutPendingQueries count before this call — fixed for the call,
1458
+ // used with chunksUsedThisCall against maxPendingChunksPerPartition.
1459
+ pendingCount: int,
1460
+ queryEndBlock: option<int>,
1461
+ maybeChunkRange: option<int>,
1462
+ // This partition's own slot in queriesByPartitionIndex — gap-fill and every
1463
+ // water-fill round push here directly, so query order falls out of
1464
+ // idsInAscOrder for free (see getNextQuery) instead of a final sort pass.
1465
+ bucket: array<query>,
1466
+ }
1467
+
1468
+ // Candidate queries are sized against chainTargetBlock, the soft querying
1469
+ // horizon the owning chain wants to reach this tick — derived by ChainState
1470
+ // from its share of the indexer-wide buffer budget and its chain-level event
1471
+ // density. chainTargetBlock is never used as a hard query end, only to (a)
1472
+ // select which partitions are "in range" this tick and (b) size an open-ended
1473
+ // query with no other ceiling: the true hard bounds stay
1474
+ // endBlock/mergeBlock/the lagged head.
1475
+ //
1476
+ // In-range partitions evenly split chainTargetItems (this chain's target
1477
+ // total footprint — in-flight plus new). A partition already holding more
1478
+ // than its even share (from earlier ticks' in-flight queries) is skipped this
1479
+ // round so its share flows to the others; the split is recomputed every round
1480
+ // against the shrinking set of partitions still with range left, so leftover
1481
+ // keeps redistributing until the budget or the range runs out.
1482
+ //
1483
+ // A partition with a trusted density (two or more responses — see
1484
+ // getMinHistoryRange) always emits at least one full-size chunk/query once
1485
+ // included in a round, sized by its real density — uncapped, may overshoot
1486
+ // that round's share (the server also enforces itemsTarget via a
1487
+ // maxNumLogs-style cap, so an overshoot truncates the response rather than
1488
+ // the buffer). A partition with no trusted density yet (zero or one
1489
+ // response — a single sample is too noisy to size by) emits one query sized
1490
+ // exactly to its round's share instead.
1440
1491
  let getNextQuery = (
1441
- {buffer, optimizedPartitions, blockLag, latestOnBlockBlockNumber, knownHeight} as fetchState: t,
1442
- ~budget,
1443
- ~chainPendingBudget,
1492
+ {optimizedPartitions, blockLag, latestOnBlockBlockNumber, knownHeight, endBlock}: t,
1493
+ ~chainTargetBlock: int,
1494
+ ~chainTargetItems: float,
1444
1495
  ) => {
1445
1496
  let headBlockNumber = knownHeight - blockLag
1446
1497
  if headBlockNumber <= 0 {
1447
1498
  WaitingForNewBlock
1448
- } else if budget <= 0 {
1449
- // No room left in the shared buffer pool for this chain; wait for processing
1450
- // to drain before fetching more.
1451
- NothingToQuery
1452
1499
  } else {
1453
1500
  let isOnBlockBehindTheHead = latestOnBlockBlockNumber < headBlockNumber
1454
1501
  let shouldWaitForNewBlock = ref(
1455
- switch fetchState.endBlock {
1502
+ switch endBlock {
1456
1503
  | Some(endBlock) => headBlockNumber < endBlock
1457
1504
  | None => true
1458
1505
  } &&
1459
1506
  !isOnBlockBehindTheHead,
1460
1507
  )
1461
1508
 
1462
- // Fetch at most `budget` items past the ready frontier (plus what's already
1463
- // in flight) so processing always has buffer without ballooning memory.
1464
- // budget already excludes items at/below the frontier (they're in the shared
1465
- // totalReadyCount), so offset the index by bufferReadyCount — otherwise the
1466
- // ready prefix is subtracted twice and the buffer caps at a fraction of its
1467
- // target. A partition that fetched further is skipped until the buffer drains.
1468
- let maxQueryBlockNumber = {
1469
- switch buffer->Array.get(
1470
- fetchState->bufferReadyCount + budget + chainPendingBudget->Float.toInt - 1,
1471
- ) {
1472
- | Some(item) =>
1473
- // Just in case check that we don't query beyond the current block
1474
- Pervasives.min(item->Internal.getItemBlockNumber, knownHeight)
1475
- | None => knownHeight
1476
- }
1477
- }
1478
-
1479
- let queries = []
1480
-
1481
1509
  let partitionsCount = optimizedPartitions.idsInAscOrder->Array.length
1482
- let idxRef = ref(0)
1483
- while idxRef.contents < partitionsCount {
1484
- let idx = idxRef.contents
1510
+
1511
+ // Every partition is visited once here regardless of whether it gets a
1512
+ // query pushed below — waiting-for-new-block bookkeeping shouldn't depend
1513
+ // on this tick's budget.
1514
+ for idx in 0 to partitionsCount - 1 {
1485
1515
  let partitionId = optimizedPartitions.idsInAscOrder->Array.getUnsafe(idx)
1486
1516
  let p = optimizedPartitions.entities->Dict.getUnsafe(partitionId)
1487
-
1488
- let pendingCount = p.mutPendingQueries->Array.length
1489
- let isBehindTheHead = p.latestFetchedBlock.blockNumber < headBlockNumber
1490
- let hasPendingQueries = pendingCount > 0
1491
-
1492
- if hasPendingQueries || isBehindTheHead {
1517
+ if (
1518
+ p.mutPendingQueries->Array.length > 0 || p.latestFetchedBlock.blockNumber < headBlockNumber
1519
+ ) {
1493
1520
  // Even if there are some partitions waiting for the new block
1494
1521
  // We still want to wait for all partitions reaching the head
1495
1522
  // because they might update knownHeight in their response
@@ -1497,54 +1524,84 @@ let getNextQuery = (
1497
1524
  // and we don't want to poll the head for a few small partitions
1498
1525
  shouldWaitForNewBlock := false
1499
1526
  }
1527
+ }
1500
1528
 
1501
- let partitionQueriesStart = queries->Array.length
1502
-
1503
- // Compute queryEndBlock for this partition
1504
- let queryEndBlock = Utils.Math.minOptInt(fetchState.endBlock, p.mergeBlock)
1505
- let queryEndBlock = switch blockLag {
1529
+ // One bucket per partition, in idsInAscOrder order — gap-fill and
1530
+ // water-fill both push into a partition's own bucket, so flattening at
1531
+ // the end (see below) reproduces idsInAscOrder without a sort.
1532
+ let queriesByPartitionIndex: array<
1533
+ array<query>,
1534
+ > = Array.fromInitializer(~length=partitionsCount, _ => [])
1535
+
1536
+ // Compute queryEndBlock for this partition
1537
+ let computeQueryEndBlock = (p: partition) => {
1538
+ let queryEndBlock = Utils.Math.minOptInt(endBlock, p.mergeBlock)
1539
+ switch blockLag {
1506
1540
  | 0 => queryEndBlock
1507
1541
  | _ =>
1508
1542
  // Force head block as an endBlock when blockLag is set
1509
1543
  // because otherwise HyperSync might return bigger range
1510
1544
  Utils.Math.minOptInt(Some(headBlockNumber), queryEndBlock)
1511
1545
  }
1512
- // Enforce the response range up until target block
1513
- // Otherwise for indexers with 100+ partitions
1514
- // we might blow up the buffer size to more than 600k events
1515
- // simply because of HyperSync returning extra blocks
1516
- let queryEndBlock = switch (queryEndBlock, maxQueryBlockNumber < knownHeight) {
1517
- | (Some(endBlock), true) => Some(Pervasives.min(maxQueryBlockNumber, endBlock))
1518
- | (None, true) => Some(maxQueryBlockNumber)
1519
- | (_, false) => queryEndBlock
1546
+ }
1547
+
1548
+ // Each partition's existing in-flight itemsTarget, summed once and reused
1549
+ // both for chainReserved (the call-wide fresh-budget ceiling below) and to
1550
+ // seed each partition's water-fill footprint so a partition already
1551
+ // holding in-flight queries is counted toward its even share and doesn't
1552
+ // get topped up past the others.
1553
+ let existingReservedByPartition = Dict.make()
1554
+ let chainReserved = ref(0.)
1555
+ for idx in 0 to partitionsCount - 1 {
1556
+ let partitionId = optimizedPartitions.idsInAscOrder->Array.getUnsafe(idx)
1557
+ let p = optimizedPartitions.entities->Dict.getUnsafe(partitionId)
1558
+ let cost = ref(0.)
1559
+ for pqIdx in 0 to p.mutPendingQueries->Array.length - 1 {
1560
+ cost := cost.contents +. (p.mutPendingQueries->Array.getUnsafe(pqIdx)).itemsTarget
1520
1561
  }
1562
+ existingReservedByPartition->Dict.set(partitionId, cost.contents)
1563
+ chainReserved := chainReserved.contents +. cost.contents
1564
+ }
1521
1565
 
1566
+ // Phase A: gap-fill. Walk each partition's pending queries once,
1567
+ // unconditionally filling any hole (e.g. from an out-of-order partial
1568
+ // chunk response) — uncapped, same as before this tick's water-fill was
1569
+ // introduced. This also determines each partition's post-gap cursor and
1570
+ // whether it's blocked on an unresolved single-shot query.
1571
+ let fillStates = []
1572
+ let gapFillCost = ref(0.)
1573
+ for idx in 0 to partitionsCount - 1 {
1574
+ let partitionId = optimizedPartitions.idsInAscOrder->Array.getUnsafe(idx)
1575
+ let p = optimizedPartitions.entities->Dict.getUnsafe(partitionId)
1576
+ let bucket = queriesByPartitionIndex->Array.getUnsafe(idx)
1577
+ let pendingCount = p.mutPendingQueries->Array.length
1578
+ let queryEndBlock = computeQueryEndBlock(p)
1522
1579
  let maybeChunkRange = getMinHistoryRange(p)
1523
1580
 
1524
- // Walk pending queries to find open ranges and create queries for each
1525
1581
  let cursor = ref(p.latestFetchedBlock.blockNumber + 1)
1526
1582
  let canContinue = ref(true)
1583
+ let chunksUsedThisCall = ref(0)
1527
1584
  let pqIdx = ref(0)
1528
- while pqIdx.contents < p.mutPendingQueries->Array.length && canContinue.contents {
1585
+ while pqIdx.contents < pendingCount && canContinue.contents {
1529
1586
  let pq = p.mutPendingQueries->Array.getUnsafe(pqIdx.contents)
1530
1587
 
1531
- // Gap before this pending query → create queries for the gap range
1532
1588
  if pq.fromBlock > cursor.contents {
1533
- pushQueriesForRange(
1534
- queries,
1589
+ let beforeLen = bucket->Array.length
1590
+ let cost = pushGapFillQueries(
1591
+ bucket,
1535
1592
  ~partitionId,
1536
1593
  ~rangeFromBlock=cursor.contents,
1537
1594
  ~rangeEndBlock=Utils.Math.minOptInt(Some(pq.fromBlock - 1), queryEndBlock),
1538
- ~maxQueryBlockNumber,
1595
+ ~knownHeight,
1596
+ ~chainTargetBlock,
1539
1597
  ~maybeChunkRange,
1540
- ~maxChunks=maxPendingChunksPerPartition -
1541
- pendingCount -
1542
- (queries->Array.length -
1543
- partitionQueriesStart),
1598
+ ~maxChunks=maxPendingChunksPerPartition - pendingCount - chunksUsedThisCall.contents,
1544
1599
  ~partition=p,
1545
1600
  ~selection=p.selection,
1546
1601
  ~addressesByContractName=p.addressesByContractName,
1547
1602
  )
1603
+ chunksUsedThisCall := chunksUsedThisCall.contents + (bucket->Array.length - beforeLen)
1604
+ gapFillCost := gapFillCost.contents +. cost
1548
1605
  }
1549
1606
  switch pq {
1550
1607
  | {isChunk: true, toBlock: Some(toBlock), fetchedBlock: Some({blockNumber})}
@@ -1556,28 +1613,180 @@ let getNextQuery = (
1556
1613
  pqIdx := pqIdx.contents + 1
1557
1614
  }
1558
1615
 
1559
- // Tail range after all pending queries
1560
1616
  if canContinue.contents {
1561
- pushQueriesForRange(
1562
- queries,
1563
- ~partitionId,
1564
- ~rangeFromBlock=cursor.contents,
1565
- ~rangeEndBlock=queryEndBlock,
1566
- ~maxQueryBlockNumber,
1567
- ~maybeChunkRange,
1568
- ~maxChunks=maxPendingChunksPerPartition -
1569
- pendingCount -
1570
- (queries->Array.length -
1571
- partitionQueriesStart),
1572
- ~partition=p,
1573
- ~selection=p.selection,
1574
- ~addressesByContractName=p.addressesByContractName,
1575
- )
1617
+ fillStates
1618
+ ->Array.push({
1619
+ partitionId,
1620
+ p,
1621
+ cursor: cursor.contents,
1622
+ chunksUsedThisCall: chunksUsedThisCall.contents,
1623
+ pendingCount,
1624
+ queryEndBlock,
1625
+ maybeChunkRange,
1626
+ bucket,
1627
+ })
1628
+ ->ignore
1576
1629
  }
1630
+ }
1577
1631
 
1578
- idxRef := idxRef.contents + 1
1632
+ // Fresh budget for this call — what's left of chainTargetItems after
1633
+ // existing in-flight queries and this tick's gap-fill. The water-fill
1634
+ // rounds below hand it out; a partition already holding a large share
1635
+ // (seeded below) gets proportionally less so totals stay even.
1636
+ let rangeItemsTarget = Pervasives.max(
1637
+ 0.,
1638
+ chainTargetItems -. chainReserved.contents -. gapFillCost.contents,
1639
+ )
1640
+
1641
+ // Each in-range partition's running footprint: existing in-flight plus any
1642
+ // gap-fill just pushed into its bucket. The water-fill levels every
1643
+ // partition up toward a shared line, so this seed is what a partition
1644
+ // starts the fill already holding.
1645
+ let reservedByPartition = Dict.make()
1646
+ fillStates->Array.forEach(fs => {
1647
+ let cost = ref(existingReservedByPartition->Dict.getUnsafe(fs.partitionId))
1648
+ for i in 0 to fs.bucket->Array.length - 1 {
1649
+ cost := cost.contents +. (fs.bucket->Array.getUnsafe(i)).itemsTarget
1650
+ }
1651
+ reservedByPartition->Dict.set(fs.partitionId, cost.contents)
1652
+ })
1653
+
1654
+ // Emits this round's queries for one partition, given its share of the
1655
+ // range budget. Mutates fs.cursor/chunksUsedThisCall and returns the
1656
+ // itemsTarget consumed.
1657
+ //
1658
+ // Density is trusted only once the chunking heuristic is active
1659
+ // (maybeChunkRange requires prevQueryRange AND prevPrevQueryRange, i.e.
1660
+ // two responses — see getMinHistoryRange) — a single response is noisy
1661
+ // (a first query that happens to land on an unusually dense/sparse block
1662
+ // would otherwise mis-size the very next one), so it's treated the same
1663
+ // as a partition with no signal at all: sized exactly to this round's
1664
+ // share instead of an uncapped, one-sample density estimate.
1665
+ let emitQueries = (fs: waterFillState, ~budget: float) => {
1666
+ let p = fs.p
1667
+ let maxBlock = switch fs.queryEndBlock {
1668
+ | Some(eb) => eb
1669
+ | None => chainTargetBlock
1670
+ }
1671
+ switch fs.maybeChunkRange {
1672
+ | Some(minHistoryRange) =>
1673
+ // Chunking active: strict chunks with a hard endBlock, uncapped real
1674
+ // density — at least one full chunk this round even if budget falls
1675
+ // short.
1676
+ let density = p.prevRangeSize->Int.toFloat /. p.prevQueryRange->Int.toFloat
1677
+ let chunkSize = Js.Math.ceil_int(minHistoryRange->Int.toFloat *. 1.8)
1678
+ let chunkCost = density *. chunkSize->Int.toFloat
1679
+ let maxChunksRemaining =
1680
+ maxPendingChunksPerPartition - fs.pendingCount - fs.chunksUsedThisCall
1681
+ let affordable = if chunkCost > 0. {
1682
+ Math.floor(budget /. chunkCost)->Float.toInt
1683
+ } else {
1684
+ maxChunksRemaining
1685
+ }
1686
+ let numChunks = Pervasives.max(1, Pervasives.min(affordable, maxChunksRemaining))
1687
+ let consumed = ref(0.)
1688
+ let created = ref(0)
1689
+ let chunkFromBlock = ref(fs.cursor)
1690
+ while created.contents < numChunks && chunkFromBlock.contents <= maxBlock {
1691
+ let chunkToBlock = Pervasives.min(chunkFromBlock.contents + chunkSize - 1, maxBlock)
1692
+ let itemsTarget = density *. (chunkToBlock - chunkFromBlock.contents + 1)->Int.toFloat
1693
+ fs.bucket->Array.push({
1694
+ partitionId: fs.partitionId,
1695
+ fromBlock: chunkFromBlock.contents,
1696
+ toBlock: Some(chunkToBlock),
1697
+ isChunk: true,
1698
+ selection: p.selection,
1699
+ itemsTarget,
1700
+ addressesByContractName: p.addressesByContractName,
1701
+ })
1702
+ consumed := consumed.contents +. itemsTarget
1703
+ chunkFromBlock := chunkToBlock + 1
1704
+ created := created.contents + 1
1705
+ }
1706
+ fs.cursor = chunkFromBlock.contents
1707
+ fs.chunksUsedThisCall = fs.chunksUsedThisCall + created.contents
1708
+ consumed.contents
1709
+ | None =>
1710
+ // No trusted density yet (zero or one response): one open query sized
1711
+ // to this round's share, capped so a single probe can't swallow the
1712
+ // whole (possibly cross-chain-wide) budget and starve other partitions
1713
+ // or chains before they get their own first probe.
1714
+ let itemsTarget = Pervasives.min(Math.round(budget), maxItemsTarget)
1715
+ fs.bucket->Array.push({
1716
+ partitionId: fs.partitionId,
1717
+ fromBlock: fs.cursor,
1718
+ toBlock: fs.queryEndBlock,
1719
+ isChunk: false,
1720
+ selection: p.selection,
1721
+ itemsTarget,
1722
+ addressesByContractName: p.addressesByContractName,
1723
+ })
1724
+ fs.cursor = maxBlock + 1
1725
+ fs.chunksUsedThisCall = fs.chunksUsedThisCall + 1
1726
+ itemsTarget
1727
+ }
1579
1728
  }
1580
1729
 
1730
+ let isInRange = (fs: waterFillState) =>
1731
+ fs.cursor <= chainTargetBlock &&
1732
+ switch fs.queryEndBlock {
1733
+ | Some(eb) => fs.cursor <= eb
1734
+ | None => true
1735
+ } &&
1736
+ fs.pendingCount + fs.chunksUsedThisCall < maxPendingChunksPerPartition
1737
+
1738
+ // Water-fill. Range membership is fixed by chainTargetBlock/queryEndBlock,
1739
+ // so the in-range partitions are filtered once up front; each round levels
1740
+ // every still-not-filled partition up toward a shared line and keeps only
1741
+ // those still in range for the next round.
1742
+ //
1743
+ // The line is (remaining fresh budget + those partitions' current
1744
+ // footprint) / count: a partition already holding more than the line gets
1745
+ // nothing and drops out (its head start is its whole share), while the
1746
+ // rest are topped up to the line — so leftover from an under-using or
1747
+ // filled partition redistributes to whoever can still use it, and totals
1748
+ // stay even regardless of processing order (the line is fixed before the
1749
+ // round's emits).
1750
+ //
1751
+ // No explicit round cap: a partition survives a round only by advancing
1752
+ // chunksUsedThisCall (capped at maxPendingChunksPerPartition) or by
1753
+ // consuming fresh budget, so the not-filled set drains on its own and the
1754
+ // loop also stops once the whole budget is reserved.
1755
+ let notFilledPartitions = ref(fillStates->Array.filter(isInRange))
1756
+ let reservedFromRange = ref(0.)
1757
+ while (
1758
+ notFilledPartitions.contents->Array.length > 0 &&
1759
+ reservedFromRange.contents < rangeItemsTarget
1760
+ ) {
1761
+ let n = notFilledPartitions.contents->Array.length
1762
+ let footprintSum = ref(0.)
1763
+ notFilledPartitions.contents->Array.forEach(fs =>
1764
+ footprintSum := footprintSum.contents +. reservedByPartition->Dict.getUnsafe(fs.partitionId)
1765
+ )
1766
+ let line =
1767
+ (rangeItemsTarget -. reservedFromRange.contents +. footprintSum.contents) /. n->Int.toFloat
1768
+ let next = []
1769
+ notFilledPartitions.contents->Array.forEach(fs => {
1770
+ let reserved = reservedByPartition->Dict.getUnsafe(fs.partitionId)
1771
+ let budget = line -. reserved
1772
+ if budget > 0. {
1773
+ let consumed = emitQueries(fs, ~budget)
1774
+ reservedByPartition->Dict.set(fs.partitionId, reserved +. consumed)
1775
+ reservedFromRange := reservedFromRange.contents +. consumed
1776
+ if fs->isInRange {
1777
+ next->Array.push(fs)->ignore
1778
+ }
1779
+ }
1780
+ })
1781
+ notFilledPartitions := next
1782
+ }
1783
+
1784
+ // Each partition pushed only into its own bucket (indexed by its
1785
+ // idsInAscOrder position), so flattening reproduces idsInAscOrder order
1786
+ // directly — no sort needed even though water-fill rounds interleave
1787
+ // across partitions.
1788
+ let queries = queriesByPartitionIndex->Array.flat
1789
+
1581
1790
  if queries->Utils.Array.isEmpty {
1582
1791
  if shouldWaitForNewBlock.contents {
1583
1792
  WaitingForNewBlock
@@ -1626,7 +1835,7 @@ Instantiates a fetch state with partitions for initial addresses
1626
1835
  let make = (
1627
1836
  ~startBlock,
1628
1837
  ~endBlock,
1629
- ~eventConfigs: array<Internal.eventConfig>,
1838
+ ~onEventRegistrations: array<Internal.onEventRegistration>,
1630
1839
  ~contractConfigs: dict<IndexingAddresses.contractConfig>,
1631
1840
  ~addresses: array<Internal.indexingAddress>,
1632
1841
  ~maxAddrInPartition,
@@ -1634,7 +1843,7 @@ let make = (
1634
1843
  ~maxOnBlockBufferSize,
1635
1844
  ~knownHeight,
1636
1845
  ~progressBlockNumber=startBlock - 1,
1637
- ~onBlockConfigs=[],
1846
+ ~onBlockRegistrations=[],
1638
1847
  ~blockLag=0,
1639
1848
  ~firstEventBlock=None,
1640
1849
  ): t => {
@@ -1644,15 +1853,15 @@ let make = (
1644
1853
  }
1645
1854
 
1646
1855
  let notDependingOnAddresses = []
1647
- let normalEventConfigs = []
1856
+ let normalRegistrations = []
1648
1857
  let contractNamesWithNormalEvents = Utils.Set.make()
1649
1858
 
1650
- eventConfigs->Array.forEach(ec => {
1651
- if ec.dependsOnAddresses {
1652
- normalEventConfigs->Array.push(ec)
1653
- contractNamesWithNormalEvents->Utils.Set.add(ec.contractName)->ignore
1859
+ onEventRegistrations->Array.forEach(reg => {
1860
+ if reg.dependsOnAddresses {
1861
+ normalRegistrations->Array.push(reg)
1862
+ contractNamesWithNormalEvents->Utils.Set.add(reg.eventConfig.contractName)->ignore
1654
1863
  } else {
1655
- notDependingOnAddresses->Array.push(ec)
1864
+ notDependingOnAddresses->Array.push(reg)
1656
1865
  }
1657
1866
  })
1658
1867
 
@@ -1664,7 +1873,7 @@ let make = (
1664
1873
  latestFetchedBlock,
1665
1874
  selection: {
1666
1875
  dependsOnAddresses: false,
1667
- eventConfigs: notDependingOnAddresses,
1876
+ onEventRegistrations: notDependingOnAddresses,
1668
1877
  },
1669
1878
  addressesByContractName: Dict.make(),
1670
1879
  mergeBlock: None,
@@ -1679,7 +1888,7 @@ let make = (
1679
1888
 
1680
1889
  let normalSelection = {
1681
1890
  dependsOnAddresses: true,
1682
- eventConfigs: normalEventConfigs,
1891
+ onEventRegistrations: normalRegistrations,
1683
1892
  }
1684
1893
 
1685
1894
  let registeringContractsByContract: dict<dict<indexingAddress>> = Dict.make()
@@ -1715,12 +1924,15 @@ let make = (
1715
1924
  ~progressBlockNumber,
1716
1925
  )
1717
1926
 
1718
- if optimizedPartitions->OptimizedPartitions.count === 0 && onBlockConfigs->Utils.Array.isEmpty {
1927
+ if (
1928
+ optimizedPartitions->OptimizedPartitions.count === 0 &&
1929
+ onBlockRegistrations->Utils.Array.isEmpty
1930
+ ) {
1719
1931
  JsError.throwWithMessage(
1720
1932
  `Invalid configuration: Nothing to fetch on chain ${chainId->Int.toString}. ` ++
1721
1933
  `addresses=${addresses->Array.length->Int.toString}, ` ++
1722
- `eventConfigs=${eventConfigs->Array.length->Int.toString}, ` ++
1723
- `normalEventConfigs=${normalEventConfigs
1934
+ `onEventRegistrations=${onEventRegistrations->Array.length->Int.toString}, ` ++
1935
+ `normalRegistrations=${normalRegistrations
1724
1936
  ->Array.length
1725
1937
  ->Int.toString}. ` ++ `Make sure that you provided at least one contract address to index, or have events with Wildcard mode enabled, or have onBlock handlers.`,
1726
1938
  )
@@ -1731,14 +1943,14 @@ let make = (
1731
1943
  // fetching, so without seeding the buffer here getNextQuery would return
1732
1944
  // NothingToQuery and the indexer would get stuck.
1733
1945
  let buffer = []
1734
- let latestOnBlockBlockNumber = if knownHeight > 0 && onBlockConfigs->Utils.Array.notEmpty {
1946
+ let latestOnBlockBlockNumber = if knownHeight > 0 && onBlockRegistrations->Utils.Array.notEmpty {
1735
1947
  let maxBlockNumber = switch optimizedPartitions->OptimizedPartitions.getLatestFullyFetchedBlock {
1736
1948
  | None => knownHeight
1737
1949
  | Some(latestFullyFetchedBlock) => latestFullyFetchedBlock.blockNumber
1738
1950
  }
1739
1951
  appendOnBlockItems(
1740
1952
  ~mutItems=buffer,
1741
- ~onBlockConfigs,
1953
+ ~onBlockRegistrations,
1742
1954
  ~indexerStartBlock=startBlock,
1743
1955
  ~fromBlock=progressBlockNumber,
1744
1956
  ~maxBlockNumber,
@@ -1757,7 +1969,7 @@ let make = (
1757
1969
  latestOnBlockBlockNumber,
1758
1970
  normalSelection,
1759
1971
  blockLag,
1760
- onBlockConfigs,
1972
+ onBlockRegistrations,
1761
1973
  maxOnBlockBufferSize,
1762
1974
  knownHeight,
1763
1975
  buffer,
@@ -2007,17 +2219,6 @@ let getProgressPercentage = (fetchState: t) => {
2007
2219
  }
2008
2220
  }
2009
2221
 
2010
- // Progress a specific block sits at along the chain, used to order queries from
2011
- // different chains: a query starting further back (lower %) is fetched first.
2012
- let getProgressPercentageAt = (fetchState: t, ~blockNumber) => {
2013
- switch fetchState.firstEventBlock {
2014
- | None => 0.
2015
- | Some(firstEventBlock) =>
2016
- let totalRange = fetchState.knownHeight - firstEventBlock
2017
- totalRange <= 0 ? 0. : (blockNumber - firstEventBlock)->Int.toFloat /. totalRange->Int.toFloat
2018
- }
2019
- }
2020
-
2021
2222
  let sortForBatch = {
2022
2223
  let hasFullBatch = ({buffer} as fetchState: t, ~batchSizeTarget) => {
2023
2224
  switch buffer->Array.get(batchSizeTarget - 1) {