envio 3.15.0-subgraph → 3.16.0-subgraph

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 (114) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +3 -3
  3. package/fuel.schema.json +2 -2
  4. package/index.d.ts +3 -3
  5. package/licenses/README.md +1 -1
  6. package/package.json +8 -8
  7. package/src/Batch.res.mjs +5 -5
  8. package/src/BatchProcessing.res +1 -0
  9. package/src/BatchProcessing.res.mjs +1 -0
  10. package/src/ChainFetching.res +1 -6
  11. package/src/ChainFetching.res.mjs +1 -1
  12. package/src/ChainState.res +48 -7
  13. package/src/ChainState.res.mjs +21 -8
  14. package/src/Clock.res +57 -0
  15. package/src/Clock.res.mjs +72 -0
  16. package/src/Config.res +16 -4
  17. package/src/Config.res.mjs +5 -3
  18. package/src/Core.res +4 -40
  19. package/src/CrossChainState.res +10 -6
  20. package/src/CrossChainState.res.mjs +1 -1
  21. package/src/EffectState.res +10 -2
  22. package/src/EffectState.res.mjs +8 -4
  23. package/src/EffectState.resi +3 -1
  24. package/src/EntityId.res +2 -7
  25. package/src/EntityId.res.mjs +1 -8
  26. package/src/EntityTables.res +4 -1
  27. package/src/EntityTables.res.mjs +1 -1
  28. package/src/Env.res +4 -0
  29. package/src/Env.res.mjs +5 -0
  30. package/src/FetchState.res +157 -69
  31. package/src/FetchState.res.mjs +112 -47
  32. package/src/FinalizeBackfill.res +2 -4
  33. package/src/FinalizeBackfill.res.mjs +3 -4
  34. package/src/InMemoryTable.res +17 -16
  35. package/src/InMemoryTable.res.mjs +14 -15
  36. package/src/IndexerState.res +28 -4
  37. package/src/IndexerState.res.mjs +19 -6
  38. package/src/IndexerState.resi +4 -0
  39. package/src/Internal.res +0 -49
  40. package/src/Internal.res.mjs +1 -80
  41. package/src/LoadLayer.res +17 -10
  42. package/src/LoadLayer.res.mjs +11 -8
  43. package/src/Main.res.mjs +1 -1
  44. package/src/Persistence.res +3 -23
  45. package/src/Persistence.res.mjs +3 -3
  46. package/src/PreloadLookahead.res +42 -24
  47. package/src/PreloadLookahead.res.mjs +51 -30
  48. package/src/Rollback.res +0 -1
  49. package/src/Rollback.res.mjs +1 -1
  50. package/src/SafeCheckpointTracking.res +4 -4
  51. package/src/SafeCheckpointTracking.res.mjs +1 -1
  52. package/src/StagedRows.res +106 -0
  53. package/src/StagedRows.res.mjs +102 -0
  54. package/src/Staging.res +32 -14
  55. package/src/Staging.res.mjs +17 -20
  56. package/src/Storage.res +278 -973
  57. package/src/Storage.res.mjs +218 -720
  58. package/src/Supervisor.res +75 -56
  59. package/src/Supervisor.res.mjs +55 -26
  60. package/src/SyncWrites.res +24 -18
  61. package/src/SyncWrites.res.mjs +11 -11
  62. package/src/TestIndexer.res +22 -27
  63. package/src/TestIndexer.res.mjs +20 -17
  64. package/src/Throttler.res +15 -8
  65. package/src/Throttler.res.mjs +9 -6
  66. package/src/Throttler.resi +1 -1
  67. package/src/UserContext.res +13 -4
  68. package/src/UserContext.res.mjs +5 -5
  69. package/src/Utils.res +0 -6
  70. package/src/Utils.res.mjs +0 -5
  71. package/src/Writing.res +0 -1
  72. package/src/Writing.res.mjs +1 -1
  73. package/src/bindings/ClickHouse.res +0 -322
  74. package/src/bindings/ClickHouse.res.mjs +0 -338
  75. package/src/bindings/StorageClient.res +107 -174
  76. package/src/bindings/StorageClient.res.mjs +19 -8
  77. package/src/bindings/Vitest.res +0 -26
  78. package/src/bindings/Vitest.res.mjs +0 -3
  79. package/src/db/Table.res +24 -150
  80. package/src/db/Table.res.mjs +38 -272
  81. package/src/sources/ChainSources.res +2 -0
  82. package/src/sources/ChainSources.res.mjs +2 -2
  83. package/src/sources/EvmChain.res +3 -0
  84. package/src/sources/EvmChain.res.mjs +4 -2
  85. package/src/sources/EvmHyperSyncSource.res +2 -2
  86. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  87. package/src/sources/EvmRpcClient.res +13 -3
  88. package/src/sources/EvmRpcClient.res.mjs +4 -3
  89. package/src/sources/EvmRpcWs.res +4 -3
  90. package/src/sources/EvmRpcWs.res.mjs +2 -2
  91. package/src/sources/HeightFeed.res +31 -10
  92. package/src/sources/HeightFeed.res.mjs +10 -6
  93. package/src/sources/HeightStream.res +32 -17
  94. package/src/sources/HeightStream.res.mjs +10 -10
  95. package/src/sources/HyperSyncSSE.res +4 -6
  96. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  97. package/src/sources/RpcSource.res +10 -1
  98. package/src/sources/RpcSource.res.mjs +4 -3
  99. package/src/sources/Source.res +3 -0
  100. package/src/sources/SourceManager.res +85 -104
  101. package/src/sources/SourceManager.res.mjs +18 -37
  102. package/src/sources/SourceManager.resi +3 -5
  103. package/src/sources/StartBlockResolver.res +1 -0
  104. package/src/sources/StartBlockResolver.res.mjs +4 -3
  105. package/src/sources/SvmHyperSyncSource.res +2 -2
  106. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  107. package/src/subgraph/graph-ts.ts +87 -28
  108. package/src/subgraph/runtime.ts +0 -1
  109. package/src/subgraph/scope.ts +0 -1
  110. package/svm.schema.json +3 -3
  111. package/src/PgWriting.res +0 -36
  112. package/src/PgWriting.res.mjs +0 -34
  113. package/src/db/IndexDefinition.res +0 -47
  114. package/src/db/IndexDefinition.res.mjs +0 -60
@@ -869,6 +869,17 @@ module OptimizedPartitions = {
869
869
  // triggers, 2 for one created by those, and so on).
870
870
  type creationBlock = {block: int, round: int}
871
871
 
872
+ // When a contract's addresses leave the server-side filter. A switched contract
873
+ // is fetched by the single address-free partition and gated against the address
874
+ // store on our side. The switch is sticky for the run.
875
+ type clientFilterPolicy = {
876
+ // Past this many registered addresses. None never switches on count alone.
877
+ addressThreshold: option<int>,
878
+ // Every dynamic contract, whatever its address count. Keeps a chain at one
879
+ // query per block, of one shape, however many contracts it registers.
880
+ allDynamicContracts: bool,
881
+ }
882
+
872
883
  type t = {
873
884
  optimizedPartitions: OptimizedPartitions.t,
874
885
  startBlock: int,
@@ -884,6 +895,7 @@ type t = {
884
895
  // How much blocks behind the head we should query
885
896
  // Needed to query before entering reorg threshold
886
897
  blockLag: int,
898
+ blockThrottle: int,
887
899
  // Buffer of items ordered from earliest to latest
888
900
  buffer: array<Internal.item>,
889
901
  // Caps how far ahead onBlock items are pre-generated (set to 2x the batch
@@ -895,10 +907,7 @@ type t = {
895
907
  onBlockRegistrations: array<Internal.onBlockRegistration>,
896
908
  knownHeight: int,
897
909
  firstEventBlock: option<int>,
898
- // Per-contract registered-address count past which a dynamic contract is
899
- // switched to client-side filtering. None disables the switch, leaving every
900
- // contract filtered server-side.
901
- clientFilterAddressThreshold: option<int>,
910
+ clientFilterPolicy: clientFilterPolicy,
902
911
  // Subgraph mode only: the block each dynamically registered address was
903
912
  // registered in, keyed by contract and lowercase address, until fetching
904
913
  // passes it. Its events in that block run after the rest of the block, as
@@ -1293,6 +1302,7 @@ let updateInternal = (
1293
1302
  optimizedPartitions,
1294
1303
  latestOnBlockBlockNumber,
1295
1304
  blockLag,
1305
+ blockThrottle: fetchState.blockThrottle,
1296
1306
  knownHeight,
1297
1307
  // Single merge point: fold any onBlock items into the sorted base buffer.
1298
1308
  buffer: switch blockItems {
@@ -1300,7 +1310,7 @@ let updateInternal = (
1300
1310
  | blockItems => base->mergeIntoBuffer(blockItems)
1301
1311
  },
1302
1312
  firstEventBlock: fetchState.firstEventBlock,
1303
- clientFilterAddressThreshold: fetchState.clientFilterAddressThreshold,
1313
+ clientFilterPolicy: fetchState.clientFilterPolicy,
1304
1314
  creationBlocks: ?fetchState.creationBlocks,
1305
1315
  blockRegisterThrough: ?fetchState.blockRegisterThrough,
1306
1316
  }
@@ -1330,22 +1340,40 @@ type notice =
1330
1340
  requestedFromBlock: int,
1331
1341
  })
1332
1342
 
1333
- // Move a contract to client-side address filtering, recording why.
1334
- let addClientFilteredContract = (
1343
+ // The client-filtered set once `policy` has judged `contractNames`. A copy, so
1344
+ // older fetch state snapshots keep theirs: Utils.Set.add mutates in place.
1345
+ let switchToClientSideFiltering = (
1335
1346
  clientFilteredContracts: Utils.Set.t<string>,
1336
- ~contractName,
1347
+ ~contractNames: array<string>,
1348
+ ~policy: clientFilterPolicy,
1349
+ ~dynamicContracts: Utils.Set.t<string>,
1350
+ ~addressStore: AddressStore.t,
1337
1351
  ~onNotice,
1338
- ~addressCount,
1339
- ~threshold,
1340
1352
  // A resumed fetch state re-derives the switch from the persisted addresses,
1341
- // but it already happened - and was reported - in the run that crossed the
1342
- // threshold.
1353
+ // but it already happened - and was reported - in an earlier run.
1343
1354
  ~isAlreadyReported=false,
1344
1355
  ) => {
1345
- clientFilteredContracts->Utils.Set.add(contractName)->ignore
1346
- if !isAlreadyReported {
1347
- onNotice(SwitchedToClientSideFiltering({contractName, addressCount, threshold}))
1348
- }
1356
+ let switched = clientFilteredContracts->Utils.Set.toArray->Utils.Set.fromArray
1357
+ contractNames->Array.forEach(contractName =>
1358
+ if !(switched->Utils.Set.has(contractName)) {
1359
+ if policy.allDynamicContracts && dynamicContracts->Utils.Set.has(contractName) {
1360
+ switched->Utils.Set.add(contractName)->ignore
1361
+ } else {
1362
+ switch policy.addressThreshold {
1363
+ | Some(threshold) =>
1364
+ let addressCount = addressStore->AddressStore.contractCount(contractName)
1365
+ if addressCount > threshold {
1366
+ switched->Utils.Set.add(contractName)->ignore
1367
+ if !isAlreadyReported {
1368
+ onNotice(SwitchedToClientSideFiltering({contractName, addressCount, threshold}))
1369
+ }
1370
+ }
1371
+ | None => ()
1372
+ }
1373
+ }
1374
+ }
1375
+ )
1376
+ switched
1349
1377
  }
1350
1378
 
1351
1379
  // The block a partition will have fetched to once everything on its pending
@@ -2019,34 +2047,16 @@ let registerDynamicContracts = (
2019
2047
  }
2020
2048
  }
2021
2049
 
2022
- // Switch any dynamic contract that has just crossed the server-side
2023
- // address threshold to client-side filtering. Sticky: the set only grows, and
2024
- // collapse in createPartitions folds the contract's
2025
- // partitions into the single address-free partition.
2026
- // Clone the sticky set before mutating so this update owns its copy and
2027
- // older fetchState snapshots keep theirs (Utils.Set.add mutates in place).
2028
- let clientFilteredContracts = switch fetchState.clientFilterAddressThreshold {
2029
- | Some(threshold) =>
2030
- let clientFilteredContracts =
2031
- fetchState.optimizedPartitions.clientFilteredContracts
2032
- ->Utils.Set.toArray
2033
- ->Utils.Set.fromArray
2034
- dynamicContractsRef.contents
2035
- ->Utils.Set.toArray
2036
- ->Array.forEach(contractName => {
2037
- let addressCount = addressStore->AddressStore.contractCount(contractName)
2038
- if !(clientFilteredContracts->Utils.Set.has(contractName)) && addressCount > threshold {
2039
- clientFilteredContracts->addClientFilteredContract(
2040
- ~contractName,
2041
- ~onNotice,
2042
- ~addressCount,
2043
- ~threshold,
2044
- )
2045
- }
2046
- })
2047
- clientFilteredContracts
2048
- | None => fetchState.optimizedPartitions.clientFilteredContracts
2049
- }
2050
+ // Collapse in createPartitions folds a switched contract's partitions
2051
+ // into the single address-free partition.
2052
+ let clientFilteredContracts =
2053
+ fetchState.optimizedPartitions.clientFilteredContracts->switchToClientSideFiltering(
2054
+ ~contractNames=dynamicContractsRef.contents->Utils.Set.toArray,
2055
+ ~policy=fetchState.clientFilterPolicy,
2056
+ ~dynamicContracts=dynamicContractsRef.contents,
2057
+ ~addressStore,
2058
+ ~onNotice,
2059
+ )
2050
2060
 
2051
2061
  // Only this batch's additions need partitions; everything already
2052
2062
  // registered has one.
@@ -2077,6 +2087,48 @@ let registerDynamicContracts = (
2077
2087
  }
2078
2088
  }
2079
2089
 
2090
+ // Every dynamic contract, the ones registered from now on included, is fetched
2091
+ // address-free and filtered on our side. Its partitions fold into the single
2092
+ // address-free partition through the same collapse the address threshold uses.
2093
+ let switchDynamicContractsToClientSideFiltering = (fetchState: t, ~addressStore: AddressStore.t) =>
2094
+ if fetchState.clientFilterPolicy.allDynamicContracts {
2095
+ fetchState
2096
+ } else {
2097
+ let fetchState = {
2098
+ ...fetchState,
2099
+ clientFilterPolicy: {...fetchState.clientFilterPolicy, allDynamicContracts: true},
2100
+ }
2101
+ let {
2102
+ dynamicContracts,
2103
+ clientFilteredContracts,
2104
+ maxAddrInPartition,
2105
+ nextPartitionIndex,
2106
+ } = fetchState.optimizedPartitions
2107
+ let switched =
2108
+ clientFilteredContracts
2109
+ ->Utils.Set.toArray
2110
+ ->Array.concat(dynamicContracts->Utils.Set.toArray)
2111
+ ->Utils.Set.fromArray
2112
+ if switched->Utils.Set.size === clientFilteredContracts->Utils.Set.size {
2113
+ fetchState
2114
+ } else {
2115
+ fetchState->updateInternal(
2116
+ ~optimizedPartitions=createPartitions(
2117
+ ~registeringSetsByContract=Dict.make(),
2118
+ ~addressStore,
2119
+ ~dynamicContracts,
2120
+ ~clientFilteredContracts=switched,
2121
+ ~normalSelection=fetchState.normalSelection,
2122
+ ~maxAddrInPartition,
2123
+ ~nextPartitionIndex,
2124
+ ~existingPartitions=fetchState.optimizedPartitions.entities->Dict.valuesToArray,
2125
+ ~progressBlockNumber=0,
2126
+ ~knownHeight=fetchState.knownHeight,
2127
+ ),
2128
+ )
2129
+ }
2130
+ }
2131
+
2080
2132
  /*
2081
2133
  Updates fetchState with a response for a given query.
2082
2134
  Throws if the partition with given query cannot be found (unexpected)
@@ -2612,6 +2664,20 @@ let acceptCandidates = (
2612
2664
  }
2613
2665
  }
2614
2666
 
2667
+ // A throttled chain lets `blockThrottle - 1` new blocks build up before it
2668
+ // fetches them, so a frontier that far behind the lagged head is waiting, not
2669
+ // behind. An end block the head has reached is fetched without waiting.
2670
+ let blocksBehindHeadAllowedByThrottle = ({blockThrottle, endBlock, knownHeight, blockLag}: t) =>
2671
+ switch endBlock {
2672
+ | Some(endBlock) if knownHeight - blockLag >= endBlock => 0
2673
+ | _ => Pervasives.max(0, blockThrottle - 1)
2674
+ }
2675
+
2676
+ // The highest block the chain fetches up to right now: the lagged head, less the
2677
+ // blocks a throttled chain lets build up first.
2678
+ let headFetchTarget = ({knownHeight, blockLag} as fetchState: t) =>
2679
+ knownHeight - blockLag - fetchState->blocksBehindHeadAllowedByThrottle
2680
+
2615
2681
  // Candidate queries are sized against chainTargetBlock, the soft querying
2616
2682
  // horizon the owning chain wants to reach this tick — derived by ChainState
2617
2683
  // from its share of the indexer-wide buffer budget and its chain-level event
@@ -2640,7 +2706,7 @@ let acceptCandidates = (
2640
2706
  // rangeTargetDensity × (chainTargetBlock − fromBlock + 1) / inRangeCount — so
2641
2707
  // unknown-density partitions probe in parallel within one budget.
2642
2708
  let getNextQuery = (
2643
- {optimizedPartitions, blockLag, latestOnBlockBlockNumber, knownHeight, endBlock}: t,
2709
+ {optimizedPartitions, blockLag, latestOnBlockBlockNumber, knownHeight, endBlock} as fetchState: t,
2644
2710
  ~chainTargetBlock: int,
2645
2711
  ~chainTargetItems: float,
2646
2712
  ) => {
@@ -2648,7 +2714,8 @@ let getNextQuery = (
2648
2714
  if headBlockNumber <= 0 {
2649
2715
  WaitingForNewBlock
2650
2716
  } else {
2651
- let isOnBlockBehindTheHead = latestOnBlockBlockNumber < headBlockNumber
2717
+ let caughtUpBlockNumber = headBlockNumber - fetchState->blocksBehindHeadAllowedByThrottle
2718
+ let isOnBlockBehindTheHead = latestOnBlockBlockNumber < caughtUpBlockNumber
2652
2719
  let shouldWaitForNewBlock = ref(
2653
2720
  switch endBlock {
2654
2721
  | Some(endBlock) => headBlockNumber < endBlock
@@ -2693,7 +2760,7 @@ let getNextQuery = (
2693
2760
  }
2694
2761
  }
2695
2762
  inFlightCounts->Array.setUnsafe(idx, inFlightCount.contents)
2696
- if p.mutPendingQueries->Array.length > 0 || p.latestFetchedBlock < headBlockNumber {
2763
+ if p.mutPendingQueries->Array.length > 0 || p.latestFetchedBlock < caughtUpBlockNumber {
2697
2764
  // Even if there are some partitions waiting for the new block
2698
2765
  // We still want to wait for all partitions reaching the head
2699
2766
  // because they might update knownHeight in their response
@@ -2872,6 +2939,7 @@ let make = (
2872
2939
  ~progressBlockNumber=startBlock - 1,
2873
2940
  ~onBlockRegistrations=[],
2874
2941
  ~blockLag=0,
2942
+ ~blockThrottle=0,
2875
2943
  ~firstEventBlock=None,
2876
2944
  ~clientFilterAddressThreshold=None,
2877
2945
  ~isResumed=false,
@@ -2955,7 +3023,6 @@ let make = (
2955
3023
  })
2956
3024
 
2957
3025
  let dynamicContracts = Utils.Set.make()
2958
- let clientFilteredContracts = Utils.Set.make()
2959
3026
  let registeringSetsByContract = Dict.make()
2960
3027
 
2961
3028
  // What each contract needs a partition for is read back off the store rather
@@ -2979,26 +3046,24 @@ let make = (
2979
3046
  }
2980
3047
  })
2981
3048
 
2982
- // Switch any contract already over the server-side address threshold to
2983
- // client-side filtering at creation — a config contract with a large static
2984
- // address list, or a dynamic contract restored from a large persisted set.
2985
- switch clientFilterAddressThreshold {
2986
- | Some(threshold) =>
2987
- registeringSetsByContract->Utils.Dict.forEachWithKey((set, contractName) => {
2988
- let addressCount = set->AddressSet.size
2989
- if addressCount > threshold {
2990
- clientFilteredContracts->addClientFilteredContract(
2991
- ~contractName,
2992
- ~onNotice,
2993
- ~addressCount,
2994
- ~threshold,
2995
- ~isAlreadyReported=isResumed,
2996
- )
2997
- }
2998
- })
2999
- | None => ()
3049
+ let clientFilterPolicy = {
3050
+ addressThreshold: clientFilterAddressThreshold,
3051
+ allDynamicContracts: false,
3000
3052
  }
3001
3053
 
3054
+ // Switch at creation whatever the threshold already covers: a config
3055
+ // contract with a large static address list, or a dynamic contract restored
3056
+ // from a large persisted set.
3057
+ let clientFilteredContracts =
3058
+ Utils.Set.make()->switchToClientSideFiltering(
3059
+ ~contractNames=registeringSetsByContract->Dict.keysToArray,
3060
+ ~policy=clientFilterPolicy,
3061
+ ~dynamicContracts,
3062
+ ~addressStore,
3063
+ ~onNotice,
3064
+ ~isAlreadyReported=isResumed,
3065
+ )
3066
+
3002
3067
  let optimizedPartitions = createPartitions(
3003
3068
  ~registeringSetsByContract,
3004
3069
  ~addressStore,
@@ -3074,12 +3139,13 @@ let make = (
3074
3139
  latestOnBlockBlockNumber,
3075
3140
  normalSelection,
3076
3141
  blockLag,
3142
+ blockThrottle,
3077
3143
  onBlockRegistrations,
3078
3144
  maxOnBlockBufferSize,
3079
3145
  knownHeight,
3080
3146
  buffer,
3081
3147
  firstEventBlock,
3082
- clientFilterAddressThreshold,
3148
+ clientFilterPolicy,
3083
3149
  creationBlocks: ?(deferCreationBlockEvents ? Some(Dict.make()) : None),
3084
3150
  ?blockRegisterThrough,
3085
3151
  }
@@ -3301,15 +3367,37 @@ let isActivelyIndexing = ({endBlock} as fetchState: t) => {
3301
3367
  // True once the fetch frontier has reached the (lagged) head or endBlock,
3302
3368
  // regardless of whether the buffer has been consumed yet. Unlike
3303
3369
  // isReadyToEnterReorgThreshold, fetched-but-unprocessed items still count.
3304
- let isFetchingAtHead = ({endBlock, blockLag, knownHeight} as fetchState: t) => {
3370
+ let isFetchingAtHead = ({endBlock, knownHeight} as fetchState: t) => {
3305
3371
  let bufferBlockNumber = fetchState->bufferBlockNumber
3306
3372
  knownHeight !== 0 &&
3307
3373
  switch endBlock {
3308
3374
  | Some(endBlock) if bufferBlockNumber >= endBlock => true
3309
- | _ => bufferBlockNumber >= knownHeight - blockLag
3375
+ | _ => bufferBlockNumber >= fetchState->headFetchTarget
3310
3376
  }
3311
3377
  }
3312
3378
 
3379
+ // Counted in blocks whatever the chain's block time: the chain still fetches
3380
+ // block by block near the head, and an address-free query must never reach back
3381
+ // into a range a partition is still backfilling.
3382
+ let headClientFilteringDistance = 200
3383
+
3384
+ // Every partition is within `headClientFilteringDistance` of the fetch target. A
3385
+ // chain with no head to follow never is: no known height yet, or an end block it
3386
+ // stops at before the head.
3387
+ let isWithinHeadClientFilteringDistance = (
3388
+ {endBlock, blockLag, knownHeight, optimizedPartitions} as fetchState: t,
3389
+ ) =>
3390
+ knownHeight !== 0 &&
3391
+ switch endBlock {
3392
+ | Some(endBlock) => endBlock > knownHeight - blockLag
3393
+ | None => true
3394
+ } &&
3395
+ switch optimizedPartitions->OptimizedPartitions.getLatestFullyFetchedBlock {
3396
+ | Some(latestFetchedBlock) =>
3397
+ fetchState->headFetchTarget - latestFetchedBlock <= headClientFilteringDistance
3398
+ | None => true
3399
+ }
3400
+
3313
3401
  // Frontier at (or within `tolerance` of) the lagged head with no processable
3314
3402
  // items left — the moment the chain can cross into the reorg threshold.
3315
3403
  // `tolerance` absorbs the head advancing between a chain catching up and this
@@ -1036,28 +1036,49 @@ function updateInternal(fetchState, optimizedPartitionsOpt, mutItems, mutItemsSo
1036
1036
  chainId: fetchState.chainId,
1037
1037
  latestOnBlockBlockNumber: latestOnBlockBlockNumber,
1038
1038
  blockLag: blockLag,
1039
+ blockThrottle: fetchState.blockThrottle,
1039
1040
  buffer: blockItems.length !== 0 ? mergeIntoBuffer(base, blockItems) : base,
1040
1041
  maxOnBlockBufferSize: fetchState.maxOnBlockBufferSize,
1041
1042
  onBlockRegistrations: fetchState.onBlockRegistrations,
1042
1043
  knownHeight: knownHeight,
1043
1044
  firstEventBlock: fetchState.firstEventBlock,
1044
- clientFilterAddressThreshold: fetchState.clientFilterAddressThreshold,
1045
+ clientFilterPolicy: fetchState.clientFilterPolicy,
1045
1046
  creationBlocks: fetchState.creationBlocks,
1046
1047
  blockRegisterThrough: fetchState.blockRegisterThrough
1047
1048
  };
1048
1049
  }
1049
1050
 
1050
- function addClientFilteredContract(clientFilteredContracts, contractName, onNotice, addressCount, threshold, isAlreadyReportedOpt) {
1051
+ function switchToClientSideFiltering(clientFilteredContracts, contractNames, policy, dynamicContracts, addressStore, onNotice, isAlreadyReportedOpt) {
1051
1052
  let isAlreadyReported = isAlreadyReportedOpt !== undefined ? isAlreadyReportedOpt : false;
1052
- clientFilteredContracts.add(contractName);
1053
- if (!isAlreadyReported) {
1054
- return onNotice({
1055
- TAG: "SwitchedToClientSideFiltering",
1056
- contractName: contractName,
1057
- addressCount: addressCount,
1058
- threshold: threshold
1059
- });
1060
- }
1053
+ let switched = new Set(Array.from(clientFilteredContracts));
1054
+ contractNames.forEach(contractName => {
1055
+ if (switched.has(contractName)) {
1056
+ return;
1057
+ }
1058
+ if (policy.allDynamicContracts && dynamicContracts.has(contractName)) {
1059
+ switched.add(contractName);
1060
+ return;
1061
+ }
1062
+ let threshold = policy.addressThreshold;
1063
+ if (threshold === undefined) {
1064
+ return;
1065
+ }
1066
+ let addressCount = addressStore.contractCount(contractName);
1067
+ if (addressCount > threshold) {
1068
+ switched.add(contractName);
1069
+ if (!isAlreadyReported) {
1070
+ return onNotice({
1071
+ TAG: "SwitchedToClientSideFiltering",
1072
+ contractName: contractName,
1073
+ addressCount: addressCount,
1074
+ threshold: threshold
1075
+ });
1076
+ } else {
1077
+ return;
1078
+ }
1079
+ }
1080
+ });
1081
+ return switched;
1061
1082
  }
1062
1083
 
1063
1084
  function claimedFetchedBlock(p, knownHeight) {
@@ -1505,20 +1526,7 @@ function registerDynamicContracts(fetchState, addressStore, onNotice, claimCeili
1505
1526
  }
1506
1527
  }
1507
1528
  }
1508
- let threshold = fetchState.clientFilterAddressThreshold;
1509
- let clientFilteredContracts;
1510
- if (threshold !== undefined) {
1511
- let clientFilteredContracts$1 = new Set(Array.from(fetchState.optimizedPartitions.clientFilteredContracts));
1512
- Array.from(dynamicContractsRef).forEach(contractName => {
1513
- let addressCount = addressStore.contractCount(contractName);
1514
- if (!clientFilteredContracts$1.has(contractName) && addressCount > threshold) {
1515
- return addClientFilteredContract(clientFilteredContracts$1, contractName, onNotice, addressCount, threshold, undefined);
1516
- }
1517
- });
1518
- clientFilteredContracts = clientFilteredContracts$1;
1519
- } else {
1520
- clientFilteredContracts = fetchState.optimizedPartitions.clientFilteredContracts;
1521
- }
1529
+ let clientFilteredContracts = switchToClientSideFiltering(fetchState.optimizedPartitions.clientFilteredContracts, Array.from(dynamicContractsRef), fetchState.clientFilterPolicy, dynamicContractsRef, addressStore, onNotice, undefined);
1522
1530
  let registeringSetsByContract = {};
1523
1531
  registeringContractNames.forEach(contractName => {
1524
1532
  registeringSetsByContract[contractName] = AddressStore.makeSet(addressStore, contractName, {
@@ -1529,6 +1537,27 @@ function registerDynamicContracts(fetchState, addressStore, onNotice, claimCeili
1529
1537
  return updateInternal(fetchState, optimizedPartitions, undefined, undefined, undefined, undefined);
1530
1538
  }
1531
1539
 
1540
+ function switchDynamicContractsToClientSideFiltering(fetchState, addressStore) {
1541
+ if (fetchState.clientFilterPolicy.allDynamicContracts) {
1542
+ return fetchState;
1543
+ }
1544
+ let newrecord = {...fetchState};
1545
+ let init = fetchState.clientFilterPolicy;
1546
+ newrecord.clientFilterPolicy = {
1547
+ addressThreshold: init.addressThreshold,
1548
+ allDynamicContracts: true
1549
+ };
1550
+ let match = newrecord.optimizedPartitions;
1551
+ let clientFilteredContracts = match.clientFilteredContracts;
1552
+ let dynamicContracts = match.dynamicContracts;
1553
+ let switched = new Set(Array.from(clientFilteredContracts).concat(Array.from(dynamicContracts)));
1554
+ if (switched.size === clientFilteredContracts.size) {
1555
+ return newrecord;
1556
+ } else {
1557
+ return updateInternal(newrecord, createPartitions({}, addressStore, dynamicContracts, switched, newrecord.normalSelection, match.maxAddrInPartition, match.nextPartitionIndex, Object.values(newrecord.optimizedPartitions.entities), 0, newrecord.knownHeight), undefined, undefined, undefined, undefined);
1558
+ }
1559
+ }
1560
+
1532
1561
  function handleQueryResult(fetchState, query, latestFetchedBlock, newItems) {
1533
1562
  let tmp;
1534
1563
  if (newItems.length !== 0) {
@@ -1838,15 +1867,30 @@ function acceptCandidates(candidates, reservations, chainTargetItems, partitionI
1838
1867
  };
1839
1868
  }
1840
1869
 
1841
- function getNextQuery(param, chainTargetBlock, chainTargetItems) {
1842
- let blockLag = param.blockLag;
1870
+ function blocksBehindHeadAllowedByThrottle(param) {
1871
+ let blockThrottle = param.blockThrottle;
1843
1872
  let endBlock = param.endBlock;
1844
- let optimizedPartitions = param.optimizedPartitions;
1845
- let headBlockNumber = param.knownHeight - blockLag | 0;
1873
+ if (endBlock !== undefined && (param.knownHeight - param.blockLag | 0) >= endBlock) {
1874
+ return 0;
1875
+ } else {
1876
+ return Primitive_int.max(0, blockThrottle - 1 | 0);
1877
+ }
1878
+ }
1879
+
1880
+ function headFetchTarget(fetchState) {
1881
+ return (fetchState.knownHeight - fetchState.blockLag | 0) - blocksBehindHeadAllowedByThrottle(fetchState) | 0;
1882
+ }
1883
+
1884
+ function getNextQuery(fetchState, chainTargetBlock, chainTargetItems) {
1885
+ let blockLag = fetchState.blockLag;
1886
+ let endBlock = fetchState.endBlock;
1887
+ let optimizedPartitions = fetchState.optimizedPartitions;
1888
+ let headBlockNumber = fetchState.knownHeight - blockLag | 0;
1846
1889
  if (headBlockNumber <= 0) {
1847
1890
  return "WaitingForNewBlock";
1848
1891
  }
1849
- let isOnBlockBehindTheHead = param.latestOnBlockBlockNumber < headBlockNumber;
1892
+ let caughtUpBlockNumber = headBlockNumber - blocksBehindHeadAllowedByThrottle(fetchState) | 0;
1893
+ let isOnBlockBehindTheHead = fetchState.latestOnBlockBlockNumber < caughtUpBlockNumber;
1850
1894
  let shouldWaitForNewBlock = (
1851
1895
  endBlock !== undefined ? headBlockNumber < endBlock : true
1852
1896
  ) && !isOnBlockBehindTheHead;
@@ -1872,7 +1916,7 @@ function getNextQuery(param, chainTargetBlock, chainTargetItems) {
1872
1916
  }
1873
1917
  }
1874
1918
  inFlightCounts[idx] = inFlightCount;
1875
- if (p.mutPendingQueries.length !== 0 || p.latestFetchedBlock < headBlockNumber) {
1919
+ if (p.mutPendingQueries.length !== 0 || p.latestFetchedBlock < caughtUpBlockNumber) {
1876
1920
  shouldWaitForNewBlock = false;
1877
1921
  }
1878
1922
  }
@@ -1963,10 +2007,11 @@ function getReadyItemsCount(fetchState, targetSize, fromItem) {
1963
2007
  return acc;
1964
2008
  }
1965
2009
 
1966
- function make$1(startBlock, endBlock, onEventRegistrations, addressStore, addressRows, maxAddrInPartition, chainId, onNotice, maxOnBlockBufferSize, knownHeight, progressBlockNumberOpt, onBlockRegistrationsOpt, blockLagOpt, firstEventBlockOpt, clientFilterAddressThresholdOpt, isResumedOpt, deferCreationBlockEventsOpt) {
2010
+ function make$1(startBlock, endBlock, onEventRegistrations, addressStore, addressRows, maxAddrInPartition, chainId, onNotice, maxOnBlockBufferSize, knownHeight, progressBlockNumberOpt, onBlockRegistrationsOpt, blockLagOpt, blockThrottleOpt, firstEventBlockOpt, clientFilterAddressThresholdOpt, isResumedOpt, deferCreationBlockEventsOpt) {
1967
2011
  let progressBlockNumber = progressBlockNumberOpt !== undefined ? progressBlockNumberOpt : startBlock - 1 | 0;
1968
2012
  let onBlockRegistrations = onBlockRegistrationsOpt !== undefined ? onBlockRegistrationsOpt : [];
1969
2013
  let blockLag = blockLagOpt !== undefined ? blockLagOpt : 0;
2014
+ let blockThrottle = blockThrottleOpt !== undefined ? blockThrottleOpt : 0;
1970
2015
  let firstEventBlock = firstEventBlockOpt !== undefined ? Primitive_option.valFromOption(firstEventBlockOpt) : undefined;
1971
2016
  let clientFilterAddressThreshold = clientFilterAddressThresholdOpt !== undefined ? Primitive_option.valFromOption(clientFilterAddressThresholdOpt) : undefined;
1972
2017
  let isResumed = isResumedOpt !== undefined ? isResumedOpt : false;
@@ -2016,7 +2061,6 @@ function make$1(startBlock, endBlock, onEventRegistrations, addressStore, addres
2016
2061
  requestedFromBlock: rejected.effectiveStartBlock
2017
2062
  }));
2018
2063
  let dynamicContracts = new Set();
2019
- let clientFilteredContracts = new Set();
2020
2064
  let registeringSetsByContract = {};
2021
2065
  Array.from(contractNamesWithNormalEvents).forEach(contractName => {
2022
2066
  if (addressStore.contractCount(contractName) > 0) {
@@ -2030,14 +2074,11 @@ function make$1(startBlock, endBlock, onEventRegistrations, addressStore, addres
2030
2074
  return;
2031
2075
  }
2032
2076
  });
2033
- if (clientFilterAddressThreshold !== undefined) {
2034
- Utils.Dict.forEachWithKey(registeringSetsByContract, (set, contractName) => {
2035
- let addressCount = set.size();
2036
- if (addressCount > clientFilterAddressThreshold) {
2037
- return addClientFilteredContract(clientFilteredContracts, contractName, onNotice, addressCount, clientFilterAddressThreshold, isResumed);
2038
- }
2039
- });
2040
- }
2077
+ let clientFilterPolicy = {
2078
+ addressThreshold: clientFilterAddressThreshold,
2079
+ allDynamicContracts: false
2080
+ };
2081
+ let clientFilteredContracts = switchToClientSideFiltering(new Set(), Object.keys(registeringSetsByContract), clientFilterPolicy, dynamicContracts, addressStore, onNotice, isResumed);
2041
2082
  let optimizedPartitions = createPartitions(registeringSetsByContract, addressStore, dynamicContracts, clientFilteredContracts, normalSelection, maxAddrInPartition, partitions.length, partitions, progressBlockNumber, knownHeight);
2042
2083
  if (optimizedPartitions.idsInAscOrder.length === 0 && Utils.$$Array.isEmpty(onBlockRegistrations$1)) {
2043
2084
  Stdlib_JsError.throwWithMessage(`Invalid configuration: Nothing to fetch on chain ` + ChainId.toString(chainId) + `. ` + (`addresses=` + addressRows.addresses.length.toString() + `, `) + (`onEventRegistrations=` + onEventRegistrations.length.toString() + `, `) + (`normalRegistrations=` + normalRegistrations.length.toString() + `. `) + `Make sure that you provided at least one contract address to index, or have events with Wildcard mode enabled, or have onBlock handlers.`);
@@ -2070,12 +2111,13 @@ function make$1(startBlock, endBlock, onEventRegistrations, addressStore, addres
2070
2111
  chainId: chainId,
2071
2112
  latestOnBlockBlockNumber: latestOnBlockBlockNumber,
2072
2113
  blockLag: blockLag,
2114
+ blockThrottle: blockThrottle,
2073
2115
  buffer: buffer,
2074
2116
  maxOnBlockBufferSize: maxOnBlockBufferSize,
2075
2117
  onBlockRegistrations: onBlockRegistrations$1,
2076
2118
  knownHeight: knownHeight,
2077
2119
  firstEventBlock: firstEventBlock,
2078
- clientFilterAddressThreshold: clientFilterAddressThreshold,
2120
+ clientFilterPolicy: clientFilterPolicy,
2079
2121
  creationBlocks: deferCreationBlockEvents ? ({}) : undefined,
2080
2122
  blockRegisterThrough: blockRegisterThrough
2081
2123
  };
@@ -2238,21 +2280,37 @@ function isActivelyIndexing(fetchState) {
2238
2280
  }
2239
2281
 
2240
2282
  function isFetchingAtHead(fetchState) {
2241
- let knownHeight = fetchState.knownHeight;
2242
- let blockLag = fetchState.blockLag;
2243
2283
  let endBlock = fetchState.endBlock;
2244
2284
  let bufferBlockNumber$1 = bufferBlockNumber(fetchState);
2245
- if (knownHeight !== 0) {
2285
+ if (fetchState.knownHeight !== 0) {
2246
2286
  if (endBlock !== undefined && bufferBlockNumber$1 >= endBlock) {
2247
2287
  return true;
2248
2288
  } else {
2249
- return bufferBlockNumber$1 >= (knownHeight - blockLag | 0);
2289
+ return bufferBlockNumber$1 >= headFetchTarget(fetchState);
2250
2290
  }
2251
2291
  } else {
2252
2292
  return false;
2253
2293
  }
2254
2294
  }
2255
2295
 
2296
+ function isWithinHeadClientFilteringDistance(fetchState) {
2297
+ let knownHeight = fetchState.knownHeight;
2298
+ let endBlock = fetchState.endBlock;
2299
+ let optimizedPartitions = fetchState.optimizedPartitions;
2300
+ if (!(knownHeight !== 0 && (
2301
+ endBlock !== undefined ? endBlock > (knownHeight - fetchState.blockLag | 0) : true
2302
+ ))) {
2303
+ return false;
2304
+ }
2305
+ let id = optimizedPartitions.idsInAscOrder[0];
2306
+ let latestFetchedBlock = id !== undefined ? optimizedPartitions.entities[id].latestFetchedBlock : undefined;
2307
+ if (latestFetchedBlock !== undefined) {
2308
+ return (headFetchTarget(fetchState) - latestFetchedBlock | 0) <= 200;
2309
+ } else {
2310
+ return true;
2311
+ }
2312
+ }
2313
+
2256
2314
  function isReadyToEnterReorgThreshold(tolerance, fetchState) {
2257
2315
  let knownHeight = fetchState.knownHeight;
2258
2316
  let endBlock = fetchState.endBlock;
@@ -2339,6 +2397,8 @@ let maxChainConcurrency = Env.maxChainConcurrency;
2339
2397
 
2340
2398
  let chunkRangeGrowthFactor = 1.8;
2341
2399
 
2400
+ let headClientFilteringDistance = 200;
2401
+
2342
2402
  export {
2343
2403
  deriveSelectionStartBlock,
2344
2404
  makeSelection,
@@ -2364,7 +2424,7 @@ export {
2364
2424
  mergeIntoBuffer,
2365
2425
  appendOnBlockItems,
2366
2426
  updateInternal,
2367
- addClientFilteredContract,
2427
+ switchToClientSideFiltering,
2368
2428
  claimedFetchedBlock,
2369
2429
  collapseClientFilteredContracts,
2370
2430
  reportRejectedRegistration,
@@ -2373,6 +2433,7 @@ export {
2373
2433
  markCreationBlockEvents,
2374
2434
  creationRoundOfRegistration,
2375
2435
  registerDynamicContracts,
2436
+ switchDynamicContractsToClientSideFiltering,
2376
2437
  handleQueryResult,
2377
2438
  capAtEndBlock,
2378
2439
  canAdvanceBlockRegisters,
@@ -2389,6 +2450,8 @@ export {
2389
2450
  walkPartitionPending,
2390
2451
  pushForwardCandidates,
2391
2452
  acceptCandidates,
2453
+ blocksBehindHeadAllowedByThrottle,
2454
+ headFetchTarget,
2392
2455
  getNextQuery,
2393
2456
  hasReadyItem,
2394
2457
  getReadyItemsCount,
@@ -2400,6 +2463,8 @@ export {
2400
2463
  resetPendingQueries,
2401
2464
  isActivelyIndexing,
2402
2465
  isFetchingAtHead,
2466
+ headClientFilteringDistance,
2467
+ isWithinHeadClientFilteringDistance,
2403
2468
  isReadyToEnterReorgThreshold,
2404
2469
  getProgressPercentage,
2405
2470
  sortForBatch,
@@ -40,7 +40,7 @@ let runOnce = async (state: IndexerState.t) => {
40
40
  let storage = persistence->Persistence.getInitializedStorageOrThrow
41
41
  let readyAt = Date.make()
42
42
 
43
- await storage.finalizeBackfill(~entities=persistence.allEntities, ~chainIds, ~readyAt)
43
+ await storage.finalizeBackfill(~chainIds, ~readyAt)
44
44
 
45
45
  // Only after the commit: in-memory readiness must never run ahead of the
46
46
  // `ready_at` a restart would read back.
@@ -68,10 +68,8 @@ let run = (state: IndexerState.t) =>
68
68
  // Best-effort and not awaited by the loop: indexing is already live and correct
69
69
  // without the index, just slower.
70
70
  let repairSchemaIndexes = (state: IndexerState.t) => {
71
- let persistence = state->IndexerState.persistence
72
- let storage = persistence->Persistence.getInitializedStorageOrThrow
71
+ let storage = state->IndexerState.persistence->Persistence.getInitializedStorageOrThrow
73
72
  storage.ensureSchemaIndexes(
74
- ~entities=persistence.allEntities,
75
73
  ~chainIds=state->IndexerState.crossChainState->CrossChainState.chainIds,
76
74
  )
77
75
  }