envio 3.15.0-subgraph → 3.16.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 (186) 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 +9 -10
  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/Bin.res +0 -7
  11. package/src/Bin.res.mjs +0 -8
  12. package/src/ChainFetching.res +17 -456
  13. package/src/ChainFetching.res.mjs +33 -404
  14. package/src/ChainState.res +48 -83
  15. package/src/ChainState.res.mjs +83 -115
  16. package/src/ChainState.resi +0 -26
  17. package/src/Clock.res +57 -0
  18. package/src/Clock.res.mjs +72 -0
  19. package/src/Config.res +7 -16
  20. package/src/Config.res.mjs +4 -11
  21. package/src/ContractRegisterContext.res +6 -3
  22. package/src/ContractRegisterContext.res.mjs +2 -4
  23. package/src/Core.res +5 -136
  24. package/src/Core.res.mjs +1 -90
  25. package/src/CrossChainState.res +2 -8
  26. package/src/CrossChainState.res.mjs +3 -3
  27. package/src/Ecosystem.res +4 -7
  28. package/src/Ecosystem.res.mjs +8 -8
  29. package/src/EffectState.res +10 -2
  30. package/src/EffectState.res.mjs +8 -4
  31. package/src/EffectState.resi +3 -1
  32. package/src/EntityId.res +2 -7
  33. package/src/EntityId.res.mjs +1 -8
  34. package/src/EntityTables.res +4 -1
  35. package/src/EntityTables.res.mjs +1 -1
  36. package/src/Env.res +0 -16
  37. package/src/Env.res.mjs +0 -11
  38. package/src/EventProcessing.res +1 -38
  39. package/src/EventProcessing.res.mjs +1 -28
  40. package/src/FetchState.res +160 -319
  41. package/src/FetchState.res.mjs +182 -292
  42. package/src/FinalizeBackfill.res +2 -4
  43. package/src/FinalizeBackfill.res.mjs +3 -4
  44. package/src/HandlerLoader.res +15 -39
  45. package/src/HandlerLoader.res.mjs +8 -27
  46. package/src/HandlerRegister.res +0 -4
  47. package/src/HandlerRegister.res.mjs +2 -5
  48. package/src/HandlerRegister.resi +0 -2
  49. package/src/InMemoryTable.res +17 -16
  50. package/src/InMemoryTable.res.mjs +14 -15
  51. package/src/IndexerState.res +28 -4
  52. package/src/IndexerState.res.mjs +19 -6
  53. package/src/IndexerState.resi +4 -0
  54. package/src/Internal.res +0 -110
  55. package/src/Internal.res.mjs +1 -115
  56. package/src/LoadLayer.res +21 -47
  57. package/src/LoadLayer.res.mjs +47 -72
  58. package/src/LoadLayer.resi +0 -26
  59. package/src/Main.res +1 -14
  60. package/src/Main.res.mjs +3 -4
  61. package/src/Persistence.res +3 -23
  62. package/src/Persistence.res.mjs +3 -3
  63. package/src/Rollback.res +0 -1
  64. package/src/Rollback.res.mjs +1 -1
  65. package/src/RunScope.res +0 -3
  66. package/src/SafeCheckpointTracking.res +4 -4
  67. package/src/SafeCheckpointTracking.res.mjs +1 -1
  68. package/src/SimulateItems.res +26 -42
  69. package/src/SimulateItems.res.mjs +16 -40
  70. package/src/StagedRows.res +106 -0
  71. package/src/StagedRows.res.mjs +102 -0
  72. package/src/Staging.res +32 -14
  73. package/src/Staging.res.mjs +17 -20
  74. package/src/Storage.res +277 -998
  75. package/src/Storage.res.mjs +216 -731
  76. package/src/Supervisor.res +75 -56
  77. package/src/Supervisor.res.mjs +55 -26
  78. package/src/TestIndexer.res +22 -27
  79. package/src/TestIndexer.res.mjs +20 -17
  80. package/src/Throttler.res +15 -8
  81. package/src/Throttler.res.mjs +9 -6
  82. package/src/Throttler.resi +1 -1
  83. package/src/UserContext.res +46 -410
  84. package/src/UserContext.res.mjs +30 -318
  85. package/src/Utils.res +0 -23
  86. package/src/Utils.res.mjs +1 -17
  87. package/src/Writing.res +0 -1
  88. package/src/Writing.res.mjs +1 -1
  89. package/src/bindings/ClickHouse.res +0 -322
  90. package/src/bindings/ClickHouse.res.mjs +0 -338
  91. package/src/bindings/StorageClient.res +107 -175
  92. package/src/bindings/StorageClient.res.mjs +19 -8
  93. package/src/bindings/Vitest.res +0 -26
  94. package/src/bindings/Vitest.res.mjs +0 -3
  95. package/src/db/Table.res +24 -150
  96. package/src/db/Table.res.mjs +38 -272
  97. package/src/sources/AddressSet.res +0 -2
  98. package/src/sources/AddressStore.res +0 -3
  99. package/src/sources/ChainSources.res +2 -0
  100. package/src/sources/ChainSources.res.mjs +2 -2
  101. package/src/sources/EvmChain.res +4 -7
  102. package/src/sources/EvmChain.res.mjs +5 -4
  103. package/src/sources/EvmHyperSyncSource.res +2 -2
  104. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  105. package/src/sources/EvmRpcClient.res +13 -3
  106. package/src/sources/EvmRpcClient.res.mjs +4 -3
  107. package/src/sources/EvmRpcWs.res +4 -3
  108. package/src/sources/EvmRpcWs.res.mjs +2 -2
  109. package/src/sources/HeightFeed.res +31 -10
  110. package/src/sources/HeightFeed.res.mjs +10 -6
  111. package/src/sources/HeightStream.res +32 -17
  112. package/src/sources/HeightStream.res.mjs +10 -10
  113. package/src/sources/HyperSync.res +1 -2
  114. package/src/sources/HyperSync.res.mjs +1 -2
  115. package/src/sources/HyperSyncSSE.res +4 -6
  116. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  117. package/src/sources/RpcSource.res +10 -1
  118. package/src/sources/RpcSource.res.mjs +4 -3
  119. package/src/sources/Source.res +3 -0
  120. package/src/sources/SourceManager.res +85 -104
  121. package/src/sources/SourceManager.res.mjs +18 -37
  122. package/src/sources/SourceManager.resi +3 -5
  123. package/src/sources/StartBlockResolver.res +1 -0
  124. package/src/sources/StartBlockResolver.res.mjs +4 -3
  125. package/src/sources/SvmHyperSyncSource.res +2 -2
  126. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  127. package/svm.schema.json +3 -3
  128. package/src/EffectCacheLookup.res +0 -98
  129. package/src/EffectCacheLookup.res.mjs +0 -116
  130. package/src/PgWriting.res +0 -36
  131. package/src/PgWriting.res.mjs +0 -34
  132. package/src/PreloadLookahead.res +0 -184
  133. package/src/PreloadLookahead.res.mjs +0 -197
  134. package/src/SubgraphLog.res +0 -19
  135. package/src/SubgraphLog.res.mjs +0 -26
  136. package/src/SyncWrites.res +0 -101
  137. package/src/SyncWrites.res.mjs +0 -98
  138. package/src/db/IndexDefinition.res +0 -47
  139. package/src/db/IndexDefinition.res.mjs +0 -60
  140. package/src/sources/CallSource.res +0 -274
  141. package/src/sources/CallSource.res.mjs +0 -196
  142. package/src/subgraph/abi-types.ts +0 -84
  143. package/src/subgraph/aggregations.ts +0 -745
  144. package/src/subgraph/as-package.ts +0 -143
  145. package/src/subgraph/assemblyscript-regex.ts +0 -67
  146. package/src/subgraph/assemblyscript.ts +0 -731
  147. package/src/subgraph/blocks.ts +0 -398
  148. package/src/subgraph/calls-source.ts +0 -702
  149. package/src/subgraph/calls.ts +0 -258
  150. package/src/subgraph/conformance.ts +0 -106
  151. package/src/subgraph/errors.ts +0 -85
  152. package/src/subgraph/graft.ts +0 -73
  153. package/src/subgraph/graph-cli-worker.mjs +0 -35
  154. package/src/subgraph/graph-cli.ts +0 -900
  155. package/src/subgraph/graph-node-decimal.ts +0 -199
  156. package/src/subgraph/graph-ts-enums.ts +0 -28
  157. package/src/subgraph/graph-ts-types/VERSION +0 -2
  158. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +0 -70
  159. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +0 -327
  160. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +0 -233
  161. package/src/subgraph/graph-ts-types/chain/near.d.ts +0 -253
  162. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +0 -32
  163. package/src/subgraph/graph-ts-types/common/collections.d.ts +0 -136
  164. package/src/subgraph/graph-ts-types/common/conversion.d.ts +0 -11
  165. package/src/subgraph/graph-ts-types/common/datasource.d.ts +0 -30
  166. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  167. package/src/subgraph/graph-ts-types/common/json.d.ts +0 -17
  168. package/src/subgraph/graph-ts-types/common/numbers.d.ts +0 -120
  169. package/src/subgraph/graph-ts-types/common/value.d.ts +0 -120
  170. package/src/subgraph/graph-ts-types/common/yaml.d.ts +0 -90
  171. package/src/subgraph/graph-ts-types/global/global.d.ts +0 -194
  172. package/src/subgraph/graph-ts-types/helper-functions.d.ts +0 -22
  173. package/src/subgraph/graph-ts-types/index.d.ts +0 -102
  174. package/src/subgraph/graph-ts.ts +0 -4280
  175. package/src/subgraph/hosts.ts +0 -554
  176. package/src/subgraph/log.ts +0 -28
  177. package/src/subgraph/receipts.ts +0 -184
  178. package/src/subgraph/register-slots.ts +0 -93
  179. package/src/subgraph/rollup.ts +0 -109
  180. package/src/subgraph/rpc.ts +0 -337
  181. package/src/subgraph/runtime.ts +0 -2060
  182. package/src/subgraph/scope.ts +0 -177
  183. package/src/subgraph/stats.ts +0 -42
  184. package/src/subgraph/template-instances.ts +0 -66
  185. package/src/subgraph/toolchain.ts +0 -327
  186. package/src/subgraph/transient.ts +0 -176
@@ -864,10 +864,16 @@ module OptimizedPartitions = {
864
864
  }
865
865
  }
866
866
 
867
- // Subgraph mode: where a dynamically registered address was registered and in
868
- // which round of its block (1 for a data source created by the block's own
869
- // triggers, 2 for one created by those, and so on).
870
- type creationBlock = {block: int, round: int}
867
+ // When a contract's addresses leave the server-side filter. A switched contract
868
+ // is fetched by the single address-free partition and gated against the address
869
+ // store on our side. The switch is sticky for the run.
870
+ type clientFilterPolicy = {
871
+ // Past this many registered addresses. None never switches on count alone.
872
+ addressThreshold: option<int>,
873
+ // Every dynamic contract, whatever its address count. Keeps a chain at one
874
+ // query per block, of one shape, however many contracts it registers.
875
+ allDynamicContracts: bool,
876
+ }
871
877
 
872
878
  type t = {
873
879
  optimizedPartitions: OptimizedPartitions.t,
@@ -884,6 +890,7 @@ type t = {
884
890
  // How much blocks behind the head we should query
885
891
  // Needed to query before entering reorg threshold
886
892
  blockLag: int,
893
+ blockThrottle: int,
887
894
  // Buffer of items ordered from earliest to latest
888
895
  buffer: array<Internal.item>,
889
896
  // Caps how far ahead onBlock items are pre-generated (set to 2x the batch
@@ -895,20 +902,7 @@ type t = {
895
902
  onBlockRegistrations: array<Internal.onBlockRegistration>,
896
903
  knownHeight: int,
897
904
  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>,
902
- // Subgraph mode only: the block each dynamically registered address was
903
- // registered in, keyed by contract and lowercase address, until fetching
904
- // passes it. Its events in that block run after the rest of the block, as
905
- // graph-node runs a new data source's creation-block triggers.
906
- creationBlocks?: dict<creationBlock>,
907
- // Set when an onBlock registration has a contractRegister: the last block
908
- // whose block-handler register passes have run and whose registrations are
909
- // applied. No block item past it is generated, so none is processed before
910
- // the data sources its block created are fetched.
911
- blockRegisterThrough?: int,
905
+ clientFilterPolicy: clientFilterPolicy,
912
906
  }
913
907
 
914
908
  // The latest block whose items are all in the buffer: the lowest partition
@@ -998,65 +992,6 @@ let compareTiebreak = (a: Internal.item, b: Internal.item): int => {
998
992
  }
999
993
  }
1000
994
 
1001
- // Within a block, graph-node runs a subgraph's triggers in this order: logs and
1002
- // calls by transaction — a transaction's logs, then its calls in trace order —
1003
- // then the block triggers of `filter: call` handlers, one per call, then the
1004
- // block handlers. A call item is told apart by its rank (see
1005
- // `Internal.getItemCallRank`), and only a run that has one pays for this
1006
- // comparison. Creation-block items (rank 4) keep their place after everything
1007
- // else, ordered among themselves by the same transaction rule. A `once`
1008
- // handler's item (rank -1) comes before all of them.
1009
- let blockOrderRank = (item: Internal.item) =>
1010
- switch item->Internal.getItemCreationRound {
1011
- | Some(round) => 3 + round
1012
- | None =>
1013
- if item->Internal.getItemKind !== 0 {
1014
- item->Internal.itemRunsFirst ? -1 : 3
1015
- } else if item->Internal.getItemCallRank === Some(2) {
1016
- 2
1017
- } else {
1018
- 0
1019
- }
1020
- }
1021
-
1022
- let compareWithCalls = (a: Internal.item, b: Internal.item): int => {
1023
- let ra = blockOrderRank(a)
1024
- let rb = blockOrderRank(b)
1025
- if ra !== rb {
1026
- ra < rb ? -1 : 1
1027
- } else if ra === 3 || ra === -1 {
1028
- // Block handlers: as before.
1029
- let la = a->Internal.getItemLogIndex
1030
- let lb = b->Internal.getItemLogIndex
1031
- if la != lb {
1032
- la < lb ? -1 : 1
1033
- } else {
1034
- compareTiebreak(a, b)
1035
- }
1036
- } else {
1037
- let ta = a->Internal.getItemTransactionIndex
1038
- let tb = b->Internal.getItemTransactionIndex
1039
- if ta != tb {
1040
- ta < tb ? -1 : 1
1041
- } else {
1042
- // A transaction's logs precede its calls.
1043
- let ca = a->Internal.getItemCallRank->Option.isSome ? 1 : 0
1044
- let cb = b->Internal.getItemCallRank->Option.isSome ? 1 : 0
1045
- if ca !== cb {
1046
- ca < cb ? -1 : 1
1047
- } else {
1048
- let la = a->Internal.getItemLogIndex
1049
- let lb = b->Internal.getItemLogIndex
1050
- if la != lb {
1051
- la < lb ? -1 : 1
1052
- } else {
1053
- compareTiebreak(a, b)
1054
- }
1055
- }
1056
- }
1057
- }
1058
- }
1059
-
1060
995
  // Total order on buffer items: block, then item kind, then the ecosystem's
1061
996
  // within-block order, then registration index. Returns a plain int (-1/0/1)
1062
997
  // with explicit field comparisons so it can be called directly from the
@@ -1068,32 +1003,15 @@ let compareWithCalls = (a: Internal.item, b: Internal.item): int => {
1068
1003
  // block's handlers by construction. A sentinel log index for block items
1069
1004
  // would put the same guarantee at the mercy of how large an ecosystem's key
1070
1005
  // grows — which is how SVM, keyed by transaction index, came to run slot
1071
- // handlers ahead of most of a slot's instructions. A subgraph's `once` handler
1072
- // is the exception: it runs ahead of its block's events.
1006
+ // handlers ahead of most of a slot's instructions.
1073
1007
  let compareBufferItem = (a: Internal.item, b: Internal.item): int => {
1074
1008
  let ba = a->Internal.getItemBlockNumber
1075
1009
  let bb = b->Internal.getItemBlockNumber
1076
1010
  if ba != bb {
1077
1011
  ba < bb ? -1 : 1
1078
- } else if Internal.callOrderingActive.contents {
1079
- compareWithCalls(a, b)
1080
1012
  } else {
1081
- let ka = switch a->Internal.getItemCreationRound {
1082
- | Some(round) => 1 + round
1083
- | None =>
1084
- switch a->Internal.getItemKind {
1085
- | 0 => 0
1086
- | kind => a->Internal.itemRunsFirst ? -1 : kind
1087
- }
1088
- }
1089
- let kb = switch b->Internal.getItemCreationRound {
1090
- | Some(round) => 1 + round
1091
- | None =>
1092
- switch b->Internal.getItemKind {
1093
- | 0 => 0
1094
- | kind => b->Internal.itemRunsFirst ? -1 : kind
1095
- }
1096
- }
1013
+ let ka = a->Internal.getItemKind
1014
+ let kb = b->Internal.getItemKind
1097
1015
  if ka !== kb {
1098
1016
  ka < kb ? -1 : 1
1099
1017
  } else {
@@ -1115,7 +1033,6 @@ let compareBufferItem = (a: Internal.item, b: Internal.item): int => {
1115
1033
  let isSameLog = (a: Internal.item, b: Internal.item): bool =>
1116
1034
  a->Internal.getItemKind === 0 &&
1117
1035
  b->Internal.getItemKind === 0 &&
1118
- a->Internal.getItemCallRank === b->Internal.getItemCallRank &&
1119
1036
  a->Internal.getItemBlockNumber === b->Internal.getItemBlockNumber &&
1120
1037
  a->Internal.getItemLogIndex === b->Internal.getItemLogIndex &&
1121
1038
  switch (a->Internal.getItemOrderPath, b->Internal.getItemOrderPath) {
@@ -1269,10 +1186,6 @@ let updateInternal = (
1269
1186
  },
1270
1187
  knownHeight,
1271
1188
  )
1272
- let maxBlockNumber = switch fetchState.blockRegisterThrough {
1273
- | Some(through) => Pervasives.min(maxBlockNumber, through)
1274
- | None => maxBlockNumber
1275
- }
1276
1189
  appendOnBlockItems(
1277
1190
  ~mutItems=blockItems,
1278
1191
  ~onBlockRegistrations,
@@ -1293,6 +1206,7 @@ let updateInternal = (
1293
1206
  optimizedPartitions,
1294
1207
  latestOnBlockBlockNumber,
1295
1208
  blockLag,
1209
+ blockThrottle: fetchState.blockThrottle,
1296
1210
  knownHeight,
1297
1211
  // Single merge point: fold any onBlock items into the sorted base buffer.
1298
1212
  buffer: switch blockItems {
@@ -1300,9 +1214,7 @@ let updateInternal = (
1300
1214
  | blockItems => base->mergeIntoBuffer(blockItems)
1301
1215
  },
1302
1216
  firstEventBlock: fetchState.firstEventBlock,
1303
- clientFilterAddressThreshold: fetchState.clientFilterAddressThreshold,
1304
- creationBlocks: ?fetchState.creationBlocks,
1305
- blockRegisterThrough: ?fetchState.blockRegisterThrough,
1217
+ clientFilterPolicy: fetchState.clientFilterPolicy,
1306
1218
  }
1307
1219
 
1308
1220
  updatedFetchState
@@ -1330,22 +1242,40 @@ type notice =
1330
1242
  requestedFromBlock: int,
1331
1243
  })
1332
1244
 
1333
- // Move a contract to client-side address filtering, recording why.
1334
- let addClientFilteredContract = (
1245
+ // The client-filtered set once `policy` has judged `contractNames`. A copy, so
1246
+ // older fetch state snapshots keep theirs: Utils.Set.add mutates in place.
1247
+ let switchToClientSideFiltering = (
1335
1248
  clientFilteredContracts: Utils.Set.t<string>,
1336
- ~contractName,
1249
+ ~contractNames: array<string>,
1250
+ ~policy: clientFilterPolicy,
1251
+ ~dynamicContracts: Utils.Set.t<string>,
1252
+ ~addressStore: AddressStore.t,
1337
1253
  ~onNotice,
1338
- ~addressCount,
1339
- ~threshold,
1340
1254
  // 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.
1255
+ // but it already happened - and was reported - in an earlier run.
1343
1256
  ~isAlreadyReported=false,
1344
1257
  ) => {
1345
- clientFilteredContracts->Utils.Set.add(contractName)->ignore
1346
- if !isAlreadyReported {
1347
- onNotice(SwitchedToClientSideFiltering({contractName, addressCount, threshold}))
1348
- }
1258
+ let switched = clientFilteredContracts->Utils.Set.toArray->Utils.Set.fromArray
1259
+ contractNames->Array.forEach(contractName =>
1260
+ if !(switched->Utils.Set.has(contractName)) {
1261
+ if policy.allDynamicContracts && dynamicContracts->Utils.Set.has(contractName) {
1262
+ switched->Utils.Set.add(contractName)->ignore
1263
+ } else {
1264
+ switch policy.addressThreshold {
1265
+ | Some(threshold) =>
1266
+ let addressCount = addressStore->AddressStore.contractCount(contractName)
1267
+ if addressCount > threshold {
1268
+ switched->Utils.Set.add(contractName)->ignore
1269
+ if !isAlreadyReported {
1270
+ onNotice(SwitchedToClientSideFiltering({contractName, addressCount, threshold}))
1271
+ }
1272
+ }
1273
+ | None => ()
1274
+ }
1275
+ }
1276
+ }
1277
+ )
1278
+ switched
1349
1279
  }
1350
1280
 
1351
1281
  // The block a partition will have fetched to once everything on its pending
@@ -1821,54 +1751,6 @@ OptimizedPartitions.t => {
1821
1751
  )
1822
1752
  }
1823
1753
 
1824
- let creationBlockKey = (~contractName, ~address: Address.t) =>
1825
- `${contractName}:${address->Address.toString->String.toLowerCase}`
1826
-
1827
- // Marks the response's events that were logged in the block their address was
1828
- // registered in, once the registrations fetching has already passed are
1829
- // dropped: those can't receive another item.
1830
- let markCreationBlockEvents = (creationBlocks: dict<creationBlock>, ~newItems, ~fetchedThrough) => {
1831
- creationBlocks
1832
- ->Dict.toArray
1833
- ->Array.forEach(((key, {block})) =>
1834
- if block < fetchedThrough {
1835
- creationBlocks->Utils.Dict.deleteInPlace(key)
1836
- }
1837
- )
1838
- newItems->Array.forEach(item =>
1839
- if item->Internal.getItemKind === 0 {
1840
- let eventItem = item->Internal.castUnsafeEventItem
1841
- let key = creationBlockKey(
1842
- ~contractName=eventItem.onEventRegistration.eventConfig.contractName,
1843
- ~address=eventItem.payload->Internal.getPayloadSrcAddress,
1844
- )
1845
- switch creationBlocks->Utils.Dict.dangerouslyGetNonOption(key) {
1846
- | Some({block, round}) if block === eventItem.blockNumber =>
1847
- item->Internal.markItemCreationRound(round)
1848
- | _ => ()
1849
- }
1850
- }
1851
- )
1852
- }
1853
-
1854
- // The round a registration made by `item`'s handler belongs to: one past the
1855
- // round `item` itself runs in, which is its address's registration round when
1856
- // the event was logged in the block that address was registered in.
1857
- let creationRoundOfRegistration = (fetchState: t, ~item: Internal.item) =>
1858
- switch fetchState.creationBlocks {
1859
- | None => 1
1860
- | Some(creationBlocks) =>
1861
- let eventItem = item->Internal.castUnsafeEventItem
1862
- let key = creationBlockKey(
1863
- ~contractName=eventItem.onEventRegistration.eventConfig.contractName,
1864
- ~address=eventItem.payload->Internal.getPayloadSrcAddress,
1865
- )
1866
- switch creationBlocks->Utils.Dict.dangerouslyGetNonOption(key) {
1867
- | Some({block, round}) if block === eventItem.blockNumber => round + 1
1868
- | _ => 1
1869
- }
1870
- }
1871
-
1872
1754
  let registerDynamicContracts = (
1873
1755
  fetchState: t,
1874
1756
  ~addressStore: AddressStore.t,
@@ -1907,17 +1789,6 @@ let registerDynamicContracts = (
1907
1789
  let verdict = verdicts->Array.getUnsafe(idx)
1908
1790
  switch verdict {
1909
1791
  | Added({fetchable: true}) =>
1910
- switch fetchState.creationBlocks {
1911
- | Some(creationBlocks) =>
1912
- creationBlocks->Dict.set(
1913
- creationBlockKey(~contractName=registration.contractName, ~address=registration.address),
1914
- {
1915
- block: registration.registrationBlock,
1916
- round: registration.creationRound->Option.getOr(1),
1917
- },
1918
- )
1919
- | None => ()
1920
- }
1921
1792
  if !(registeringContractNames->Array.includes(registration.contractName)) {
1922
1793
  registeringContractNames->Array.push(registration.contractName)->ignore
1923
1794
  }
@@ -2019,34 +1890,16 @@ let registerDynamicContracts = (
2019
1890
  }
2020
1891
  }
2021
1892
 
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
- }
1893
+ // Collapse in createPartitions folds a switched contract's partitions
1894
+ // into the single address-free partition.
1895
+ let clientFilteredContracts =
1896
+ fetchState.optimizedPartitions.clientFilteredContracts->switchToClientSideFiltering(
1897
+ ~contractNames=dynamicContractsRef.contents->Utils.Set.toArray,
1898
+ ~policy=fetchState.clientFilterPolicy,
1899
+ ~dynamicContracts=dynamicContractsRef.contents,
1900
+ ~addressStore,
1901
+ ~onNotice,
1902
+ )
2050
1903
 
2051
1904
  // Only this batch's additions need partitions; everything already
2052
1905
  // registered has one.
@@ -2077,6 +1930,48 @@ let registerDynamicContracts = (
2077
1930
  }
2078
1931
  }
2079
1932
 
1933
+ // Every dynamic contract, the ones registered from now on included, is fetched
1934
+ // address-free and filtered on our side. Its partitions fold into the single
1935
+ // address-free partition through the same collapse the address threshold uses.
1936
+ let switchDynamicContractsToClientSideFiltering = (fetchState: t, ~addressStore: AddressStore.t) =>
1937
+ if fetchState.clientFilterPolicy.allDynamicContracts {
1938
+ fetchState
1939
+ } else {
1940
+ let fetchState = {
1941
+ ...fetchState,
1942
+ clientFilterPolicy: {...fetchState.clientFilterPolicy, allDynamicContracts: true},
1943
+ }
1944
+ let {
1945
+ dynamicContracts,
1946
+ clientFilteredContracts,
1947
+ maxAddrInPartition,
1948
+ nextPartitionIndex,
1949
+ } = fetchState.optimizedPartitions
1950
+ let switched =
1951
+ clientFilteredContracts
1952
+ ->Utils.Set.toArray
1953
+ ->Array.concat(dynamicContracts->Utils.Set.toArray)
1954
+ ->Utils.Set.fromArray
1955
+ if switched->Utils.Set.size === clientFilteredContracts->Utils.Set.size {
1956
+ fetchState
1957
+ } else {
1958
+ fetchState->updateInternal(
1959
+ ~optimizedPartitions=createPartitions(
1960
+ ~registeringSetsByContract=Dict.make(),
1961
+ ~addressStore,
1962
+ ~dynamicContracts,
1963
+ ~clientFilteredContracts=switched,
1964
+ ~normalSelection=fetchState.normalSelection,
1965
+ ~maxAddrInPartition,
1966
+ ~nextPartitionIndex,
1967
+ ~existingPartitions=fetchState.optimizedPartitions.entities->Dict.valuesToArray,
1968
+ ~progressBlockNumber=0,
1969
+ ~knownHeight=fetchState.knownHeight,
1970
+ ),
1971
+ )
1972
+ }
1973
+ }
1974
+
2080
1975
  /*
2081
1976
  Updates fetchState with a response for a given query.
2082
1977
  Throws if the partition with given query cannot be found (unexpected)
@@ -2098,80 +1993,12 @@ let handleQueryResult = (fetchState: t, ~query: query, ~latestFetchedBlock: int,
2098
1993
  ~mutItems=?{
2099
1994
  switch newItems {
2100
1995
  | [] => None
2101
- | _ =>
2102
- switch fetchState.creationBlocks {
2103
- | Some(creationBlocks) =>
2104
- creationBlocks->markCreationBlockEvents(
2105
- ~newItems,
2106
- ~fetchedThrough=fetchState->bufferBlockNumber,
2107
- )
2108
- | None => ()
2109
- }
2110
- Some(fetchState.buffer->mergeIntoBuffer(newItems))
1996
+ | _ => Some(fetchState.buffer->mergeIntoBuffer(newItems))
2111
1997
  }
2112
1998
  },
2113
1999
  )
2114
2000
  }
2115
2001
 
2116
- // The items of the block handlers' register passes for the blocks after
2117
- // `after`, through `through`, at most about `limit` of them in whole blocks;
2118
- // and the last block they cover. `nextBlockRegisterItems` starts after the
2119
- // last block passed.
2120
- let capAtEndBlock = (fetchState: t, block) =>
2121
- switch fetchState.endBlock {
2122
- | Some(endBlock) => Pervasives.min(block, endBlock)
2123
- | None => block
2124
- }
2125
-
2126
- let canAdvanceBlockRegisters = (fetchState: t, ~through) =>
2127
- switch fetchState.blockRegisterThrough {
2128
- | Some(after) => fetchState->capAtEndBlock(through) > after
2129
- | None => false
2130
- }
2131
-
2132
- let blockRegisterItemsAfter = (fetchState: t, ~after, ~through, ~limit) =>
2133
- switch fetchState.blockRegisterThrough {
2134
- | Some(_) if fetchState->capAtEndBlock(through) > after =>
2135
- let through = fetchState->capAtEndBlock(through)
2136
- let items = []
2137
- let covered = appendOnBlockItems(
2138
- ~mutItems=items,
2139
- ~onBlockRegistrations=fetchState.onBlockRegistrations->Array.filter((
2140
- r: Internal.onBlockRegistration,
2141
- ) => r.contractRegister !== None),
2142
- ~indexerStartBlock=fetchState.startBlock,
2143
- ~fromBlock=after,
2144
- ~maxBlockNumber=through,
2145
- ~maxOnBlockBufferSize=limit,
2146
- )
2147
- Some((items, covered))
2148
- | _ => None
2149
- }
2150
-
2151
- let nextBlockRegisterItems = (fetchState: t, ~through, ~limit) =>
2152
- switch fetchState.blockRegisterThrough {
2153
- | Some(after) => fetchState->blockRegisterItemsAfter(~after, ~through, ~limit)
2154
- | None => None
2155
- }
2156
-
2157
- let setBlockRegisterThrough = (fetchState: t, ~through) =>
2158
- switch fetchState.blockRegisterThrough {
2159
- | Some(current) if through > current =>
2160
- {...fetchState, blockRegisterThrough: through}->updateInternal
2161
- | _ => fetchState
2162
- }
2163
-
2164
- // How far block-handler register passes can run with no response to run them
2165
- // with: through the block every partition has fetched, or the head when there
2166
- // is no partition, and never past the chain's end block.
2167
- let blockRegisterFrontier = (fetchState: t) =>
2168
- fetchState->capAtEndBlock(
2169
- switch fetchState.optimizedPartitions->OptimizedPartitions.getLatestFullyFetchedBlock {
2170
- | Some(block) => Pervasives.min(block, fetchState.knownHeight)
2171
- | None => fetchState.knownHeight
2172
- },
2173
- )
2174
-
2175
2002
  type nextQuery =
2176
2003
  | WaitingForNewBlock
2177
2004
  | NothingToQuery
@@ -2612,6 +2439,20 @@ let acceptCandidates = (
2612
2439
  }
2613
2440
  }
2614
2441
 
2442
+ // A throttled chain lets `blockThrottle - 1` new blocks build up before it
2443
+ // fetches them, so a frontier that far behind the lagged head is waiting, not
2444
+ // behind. An end block the head has reached is fetched without waiting.
2445
+ let blocksBehindHeadAllowedByThrottle = ({blockThrottle, endBlock, knownHeight, blockLag}: t) =>
2446
+ switch endBlock {
2447
+ | Some(endBlock) if knownHeight - blockLag >= endBlock => 0
2448
+ | _ => Pervasives.max(0, blockThrottle - 1)
2449
+ }
2450
+
2451
+ // The highest block the chain fetches up to right now: the lagged head, less the
2452
+ // blocks a throttled chain lets build up first.
2453
+ let headFetchTarget = ({knownHeight, blockLag} as fetchState: t) =>
2454
+ knownHeight - blockLag - fetchState->blocksBehindHeadAllowedByThrottle
2455
+
2615
2456
  // Candidate queries are sized against chainTargetBlock, the soft querying
2616
2457
  // horizon the owning chain wants to reach this tick — derived by ChainState
2617
2458
  // from its share of the indexer-wide buffer budget and its chain-level event
@@ -2640,7 +2481,7 @@ let acceptCandidates = (
2640
2481
  // rangeTargetDensity × (chainTargetBlock − fromBlock + 1) / inRangeCount — so
2641
2482
  // unknown-density partitions probe in parallel within one budget.
2642
2483
  let getNextQuery = (
2643
- {optimizedPartitions, blockLag, latestOnBlockBlockNumber, knownHeight, endBlock}: t,
2484
+ {optimizedPartitions, blockLag, latestOnBlockBlockNumber, knownHeight, endBlock} as fetchState: t,
2644
2485
  ~chainTargetBlock: int,
2645
2486
  ~chainTargetItems: float,
2646
2487
  ) => {
@@ -2648,7 +2489,8 @@ let getNextQuery = (
2648
2489
  if headBlockNumber <= 0 {
2649
2490
  WaitingForNewBlock
2650
2491
  } else {
2651
- let isOnBlockBehindTheHead = latestOnBlockBlockNumber < headBlockNumber
2492
+ let caughtUpBlockNumber = headBlockNumber - fetchState->blocksBehindHeadAllowedByThrottle
2493
+ let isOnBlockBehindTheHead = latestOnBlockBlockNumber < caughtUpBlockNumber
2652
2494
  let shouldWaitForNewBlock = ref(
2653
2495
  switch endBlock {
2654
2496
  | Some(endBlock) => headBlockNumber < endBlock
@@ -2693,7 +2535,7 @@ let getNextQuery = (
2693
2535
  }
2694
2536
  }
2695
2537
  inFlightCounts->Array.setUnsafe(idx, inFlightCount.contents)
2696
- if p.mutPendingQueries->Array.length > 0 || p.latestFetchedBlock < headBlockNumber {
2538
+ if p.mutPendingQueries->Array.length > 0 || p.latestFetchedBlock < caughtUpBlockNumber {
2697
2539
  // Even if there are some partitions waiting for the new block
2698
2540
  // We still want to wait for all partitions reaching the head
2699
2541
  // because they might update knownHeight in their response
@@ -2872,10 +2714,10 @@ let make = (
2872
2714
  ~progressBlockNumber=startBlock - 1,
2873
2715
  ~onBlockRegistrations=[],
2874
2716
  ~blockLag=0,
2717
+ ~blockThrottle=0,
2875
2718
  ~firstEventBlock=None,
2876
2719
  ~clientFilterAddressThreshold=None,
2877
2720
  ~isResumed=false,
2878
- ~deferCreationBlockEvents=false,
2879
2721
  ): t => {
2880
2722
  let latestFetchedBlock = progressBlockNumber
2881
2723
 
@@ -2955,7 +2797,6 @@ let make = (
2955
2797
  })
2956
2798
 
2957
2799
  let dynamicContracts = Utils.Set.make()
2958
- let clientFilteredContracts = Utils.Set.make()
2959
2800
  let registeringSetsByContract = Dict.make()
2960
2801
 
2961
2802
  // What each contract needs a partition for is read back off the store rather
@@ -2979,26 +2820,24 @@ let make = (
2979
2820
  }
2980
2821
  })
2981
2822
 
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 => ()
2823
+ let clientFilterPolicy = {
2824
+ addressThreshold: clientFilterAddressThreshold,
2825
+ allDynamicContracts: false,
3000
2826
  }
3001
2827
 
2828
+ // Switch at creation whatever the threshold already covers: a config
2829
+ // contract with a large static address list, or a dynamic contract restored
2830
+ // from a large persisted set.
2831
+ let clientFilteredContracts =
2832
+ Utils.Set.make()->switchToClientSideFiltering(
2833
+ ~contractNames=registeringSetsByContract->Dict.keysToArray,
2834
+ ~policy=clientFilterPolicy,
2835
+ ~dynamicContracts,
2836
+ ~addressStore,
2837
+ ~onNotice,
2838
+ ~isAlreadyReported=isResumed,
2839
+ )
2840
+
3002
2841
  let optimizedPartitions = createPartitions(
3003
2842
  ~registeringSetsByContract,
3004
2843
  ~addressStore,
@@ -3031,14 +2870,6 @@ let make = (
3031
2870
  // fetching, so without seeding the buffer here getNextQuery would return
3032
2871
  // NothingToQuery and the indexer would get stuck.
3033
2872
  let buffer = []
3034
- // Every block up to the progress block was processed, so its register passes
3035
- // ran and what they registered is in the seeded rows.
3036
- let blockRegisterThrough =
3037
- onBlockRegistrations->Array.some((r: Internal.onBlockRegistration) =>
3038
- r.contractRegister !== None
3039
- )
3040
- ? Some(progressBlockNumber)
3041
- : None
3042
2873
  let latestOnBlockBlockNumber = switch onBlockRegistrations {
3043
2874
  // As updateInternal keeps it: with nothing to generate per block the pointer
3044
2875
  // is the head cap on the buffer frontier, and left at the progress block it
@@ -3051,10 +2882,6 @@ let make = (
3051
2882
  | None => knownHeight
3052
2883
  | Some(latestFullyFetchedBlock) => Pervasives.min(latestFullyFetchedBlock, knownHeight)
3053
2884
  }
3054
- let maxBlockNumber = switch blockRegisterThrough {
3055
- | Some(through) => Pervasives.min(maxBlockNumber, through)
3056
- | None => maxBlockNumber
3057
- }
3058
2885
  appendOnBlockItems(
3059
2886
  ~mutItems=buffer,
3060
2887
  ~onBlockRegistrations,
@@ -3074,14 +2901,13 @@ let make = (
3074
2901
  latestOnBlockBlockNumber,
3075
2902
  normalSelection,
3076
2903
  blockLag,
2904
+ blockThrottle,
3077
2905
  onBlockRegistrations,
3078
2906
  maxOnBlockBufferSize,
3079
2907
  knownHeight,
3080
2908
  buffer,
3081
2909
  firstEventBlock,
3082
- clientFilterAddressThreshold,
3083
- creationBlocks: ?(deferCreationBlockEvents ? Some(Dict.make()) : None),
3084
- ?blockRegisterThrough,
2910
+ clientFilterPolicy,
3085
2911
  }
3086
2912
 
3087
2913
  fetchState
@@ -3233,13 +3059,6 @@ let rollback = (
3233
3059
  fetchState.latestOnBlockBlockNumber,
3234
3060
  targetBlockNumber,
3235
3061
  ),
3236
- // The passes past the target run again on the refetched blocks: what they
3237
- // registered was pruned from the store above.
3238
- blockRegisterThrough: ?(
3239
- fetchState.blockRegisterThrough->Option.map(through =>
3240
- Pervasives.min(through, targetBlockNumber)
3241
- )
3242
- ),
3243
3062
  }->updateInternal(
3244
3063
  ~optimizedPartitions,
3245
3064
  // Filtering the sorted buffer keeps it sorted and deduped.
@@ -3301,15 +3120,37 @@ let isActivelyIndexing = ({endBlock} as fetchState: t) => {
3301
3120
  // True once the fetch frontier has reached the (lagged) head or endBlock,
3302
3121
  // regardless of whether the buffer has been consumed yet. Unlike
3303
3122
  // isReadyToEnterReorgThreshold, fetched-but-unprocessed items still count.
3304
- let isFetchingAtHead = ({endBlock, blockLag, knownHeight} as fetchState: t) => {
3123
+ let isFetchingAtHead = ({endBlock, knownHeight} as fetchState: t) => {
3305
3124
  let bufferBlockNumber = fetchState->bufferBlockNumber
3306
3125
  knownHeight !== 0 &&
3307
3126
  switch endBlock {
3308
3127
  | Some(endBlock) if bufferBlockNumber >= endBlock => true
3309
- | _ => bufferBlockNumber >= knownHeight - blockLag
3128
+ | _ => bufferBlockNumber >= fetchState->headFetchTarget
3310
3129
  }
3311
3130
  }
3312
3131
 
3132
+ // Counted in blocks whatever the chain's block time: the chain still fetches
3133
+ // block by block near the head, and an address-free query must never reach back
3134
+ // into a range a partition is still backfilling.
3135
+ let headClientFilteringDistance = 200
3136
+
3137
+ // Every partition is within `headClientFilteringDistance` of the fetch target. A
3138
+ // chain with no head to follow never is: no known height yet, or an end block it
3139
+ // stops at before the head.
3140
+ let isWithinHeadClientFilteringDistance = (
3141
+ {endBlock, blockLag, knownHeight, optimizedPartitions} as fetchState: t,
3142
+ ) =>
3143
+ knownHeight !== 0 &&
3144
+ switch endBlock {
3145
+ | Some(endBlock) => endBlock > knownHeight - blockLag
3146
+ | None => true
3147
+ } &&
3148
+ switch optimizedPartitions->OptimizedPartitions.getLatestFullyFetchedBlock {
3149
+ | Some(latestFetchedBlock) =>
3150
+ fetchState->headFetchTarget - latestFetchedBlock <= headClientFilteringDistance
3151
+ | None => true
3152
+ }
3153
+
3313
3154
  // Frontier at (or within `tolerance` of) the lagged head with no processable
3314
3155
  // items left — the moment the chain can cross into the reorg threshold.
3315
3156
  // `tolerance` absorbs the head advancing between a chain catching up and this