envio 3.15.0 → 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 (186) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +11 -2
  3. package/fuel.schema.json +10 -1
  4. package/index.d.ts +6 -0
  5. package/licenses/README.md +1 -1
  6. package/package.json +10 -9
  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 +7 -0
  11. package/src/Bin.res.mjs +8 -0
  12. package/src/ChainFetching.res +450 -16
  13. package/src/ChainFetching.res.mjs +403 -32
  14. package/src/ChainState.res +124 -7
  15. package/src/ChainState.res.mjs +128 -67
  16. package/src/ChainState.resi +26 -0
  17. package/src/Clock.res +57 -0
  18. package/src/Clock.res.mjs +72 -0
  19. package/src/Config.res +31 -3
  20. package/src/Config.res.mjs +15 -4
  21. package/src/ContractRegisterContext.res +3 -6
  22. package/src/ContractRegisterContext.res.mjs +4 -2
  23. package/src/Core.res +95 -36
  24. package/src/Core.res.mjs +90 -1
  25. package/src/CrossChainState.res +12 -2
  26. package/src/CrossChainState.res.mjs +3 -3
  27. package/src/Ecosystem.res +7 -4
  28. package/src/Ecosystem.res.mjs +8 -8
  29. package/src/EffectCacheLookup.res +98 -0
  30. package/src/EffectCacheLookup.res.mjs +116 -0
  31. package/src/EffectState.res +10 -2
  32. package/src/EffectState.res.mjs +8 -4
  33. package/src/EffectState.resi +3 -1
  34. package/src/EntityId.res +2 -7
  35. package/src/EntityId.res.mjs +1 -8
  36. package/src/EntityTables.res +4 -1
  37. package/src/EntityTables.res.mjs +1 -1
  38. package/src/Env.res +20 -0
  39. package/src/Env.res.mjs +16 -0
  40. package/src/EventProcessing.res +38 -1
  41. package/src/EventProcessing.res.mjs +28 -1
  42. package/src/FetchState.res +408 -73
  43. package/src/FetchState.res.mjs +361 -91
  44. package/src/FinalizeBackfill.res +2 -4
  45. package/src/FinalizeBackfill.res.mjs +3 -4
  46. package/src/HandlerLoader.res +39 -15
  47. package/src/HandlerLoader.res.mjs +27 -8
  48. package/src/HandlerRegister.res +4 -0
  49. package/src/HandlerRegister.res.mjs +5 -2
  50. package/src/HandlerRegister.resi +2 -0
  51. package/src/InMemoryTable.res +17 -16
  52. package/src/InMemoryTable.res.mjs +14 -15
  53. package/src/IndexerState.res +28 -4
  54. package/src/IndexerState.res.mjs +19 -6
  55. package/src/IndexerState.resi +4 -0
  56. package/src/Internal.res +61 -49
  57. package/src/Internal.res.mjs +36 -80
  58. package/src/LoadLayer.res +54 -15
  59. package/src/LoadLayer.res.mjs +75 -45
  60. package/src/LoadLayer.resi +26 -0
  61. package/src/Main.res +14 -1
  62. package/src/Main.res.mjs +4 -3
  63. package/src/Persistence.res +3 -23
  64. package/src/Persistence.res.mjs +3 -3
  65. package/src/PreloadLookahead.res +202 -0
  66. package/src/PreloadLookahead.res.mjs +218 -0
  67. package/src/Rollback.res +0 -1
  68. package/src/Rollback.res.mjs +1 -1
  69. package/src/RunScope.res +3 -0
  70. package/src/SafeCheckpointTracking.res +4 -4
  71. package/src/SafeCheckpointTracking.res.mjs +1 -1
  72. package/src/SimulateItems.res +42 -26
  73. package/src/SimulateItems.res.mjs +40 -16
  74. package/src/StagedRows.res +106 -0
  75. package/src/StagedRows.res.mjs +102 -0
  76. package/src/Staging.res +32 -14
  77. package/src/Staging.res.mjs +17 -20
  78. package/src/Storage.res +303 -948
  79. package/src/Storage.res.mjs +229 -708
  80. package/src/SubgraphLog.res +19 -0
  81. package/src/SubgraphLog.res.mjs +26 -0
  82. package/src/Supervisor.res +75 -56
  83. package/src/Supervisor.res.mjs +55 -26
  84. package/src/SyncWrites.res +107 -0
  85. package/src/SyncWrites.res.mjs +98 -0
  86. package/src/TestIndexer.res +22 -27
  87. package/src/TestIndexer.res.mjs +20 -17
  88. package/src/Throttler.res +15 -8
  89. package/src/Throttler.res.mjs +9 -6
  90. package/src/Throttler.resi +1 -1
  91. package/src/UserContext.res +419 -46
  92. package/src/UserContext.res.mjs +318 -30
  93. package/src/Utils.res +17 -6
  94. package/src/Utils.res.mjs +12 -6
  95. package/src/Writing.res +0 -1
  96. package/src/Writing.res.mjs +1 -1
  97. package/src/bindings/ClickHouse.res +0 -322
  98. package/src/bindings/ClickHouse.res.mjs +0 -338
  99. package/src/bindings/StorageClient.res +108 -174
  100. package/src/bindings/StorageClient.res.mjs +19 -8
  101. package/src/bindings/Vitest.res +0 -26
  102. package/src/bindings/Vitest.res.mjs +0 -3
  103. package/src/db/Table.res +24 -150
  104. package/src/db/Table.res.mjs +38 -272
  105. package/src/sources/AddressSet.res +2 -0
  106. package/src/sources/AddressStore.res +3 -0
  107. package/src/sources/CallSource.res +274 -0
  108. package/src/sources/CallSource.res.mjs +196 -0
  109. package/src/sources/ChainSources.res +2 -0
  110. package/src/sources/ChainSources.res.mjs +2 -2
  111. package/src/sources/EvmChain.res +10 -1
  112. package/src/sources/EvmChain.res.mjs +6 -3
  113. package/src/sources/EvmHyperSyncSource.res +2 -2
  114. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  115. package/src/sources/EvmRpcClient.res +13 -3
  116. package/src/sources/EvmRpcClient.res.mjs +4 -3
  117. package/src/sources/EvmRpcWs.res +4 -3
  118. package/src/sources/EvmRpcWs.res.mjs +2 -2
  119. package/src/sources/HeightFeed.res +31 -10
  120. package/src/sources/HeightFeed.res.mjs +10 -6
  121. package/src/sources/HeightStream.res +32 -17
  122. package/src/sources/HeightStream.res.mjs +10 -10
  123. package/src/sources/HyperSync.res +2 -1
  124. package/src/sources/HyperSync.res.mjs +2 -1
  125. package/src/sources/HyperSyncSSE.res +4 -6
  126. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  127. package/src/sources/RpcSource.res +10 -1
  128. package/src/sources/RpcSource.res.mjs +4 -3
  129. package/src/sources/Source.res +3 -0
  130. package/src/sources/SourceManager.res +84 -77
  131. package/src/sources/SourceManager.res.mjs +17 -20
  132. package/src/sources/SourceManager.resi +3 -4
  133. package/src/sources/StartBlockResolver.res +1 -0
  134. package/src/sources/StartBlockResolver.res.mjs +3 -2
  135. package/src/sources/SvmHyperSyncSource.res +2 -2
  136. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  137. package/src/subgraph/abi-types.ts +84 -0
  138. package/src/subgraph/aggregations.ts +745 -0
  139. package/src/subgraph/as-package.ts +143 -0
  140. package/src/subgraph/assemblyscript-regex.ts +67 -0
  141. package/src/subgraph/assemblyscript.ts +731 -0
  142. package/src/subgraph/blocks.ts +398 -0
  143. package/src/subgraph/calls-source.ts +702 -0
  144. package/src/subgraph/calls.ts +258 -0
  145. package/src/subgraph/conformance.ts +106 -0
  146. package/src/subgraph/errors.ts +85 -0
  147. package/src/subgraph/graft.ts +73 -0
  148. package/src/subgraph/graph-cli-worker.mjs +35 -0
  149. package/src/subgraph/graph-cli.ts +900 -0
  150. package/src/subgraph/graph-node-decimal.ts +199 -0
  151. package/src/subgraph/graph-ts-enums.ts +28 -0
  152. package/src/subgraph/graph-ts-types/VERSION +2 -0
  153. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  154. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  155. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  156. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  157. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  158. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  159. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  160. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  161. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  162. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  163. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  164. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  165. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  166. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  167. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  168. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  169. package/src/subgraph/graph-ts.ts +4339 -0
  170. package/src/subgraph/hosts.ts +554 -0
  171. package/src/subgraph/log.ts +28 -0
  172. package/src/subgraph/receipts.ts +184 -0
  173. package/src/subgraph/register-slots.ts +93 -0
  174. package/src/subgraph/rollup.ts +109 -0
  175. package/src/subgraph/rpc.ts +337 -0
  176. package/src/subgraph/runtime.ts +2059 -0
  177. package/src/subgraph/scope.ts +176 -0
  178. package/src/subgraph/stats.ts +42 -0
  179. package/src/subgraph/template-instances.ts +66 -0
  180. package/src/subgraph/toolchain.ts +327 -0
  181. package/src/subgraph/transient.ts +176 -0
  182. package/svm.schema.json +11 -2
  183. package/src/PgWriting.res +0 -36
  184. package/src/PgWriting.res.mjs +0 -34
  185. package/src/db/IndexDefinition.res +0 -47
  186. package/src/db/IndexDefinition.res.mjs +0 -67
@@ -11,21 +11,42 @@ type partitionQueryResponse = {
11
11
  let hasContractRegister = (item: Internal.item) =>
12
12
  switch item {
13
13
  | Event({onEventRegistration: {contractRegister: Some(_)}}) => true
14
+ | Block({onBlockRegistration: {contractRegister: ?Some(_)}}) => true
14
15
  | Event(_) | Block(_) => false
15
16
  }
16
17
 
17
- let invokeContractRegister = (runtime: HandlerRuntime.t, item: Internal.item) => {
18
- let contractRegister = switch (item->Internal.castUnsafeEventItem).onEventRegistration {
19
- | {contractRegister: Some(contractRegister)} => contractRegister
20
- | {contractRegister: None, eventConfig: {name: eventName}} =>
21
- // Unexpected case, since we should pass only events with contract register to this function
22
- JsError.throwWithMessage("Contract register is not set for event " ++ eventName)
18
+ let invokeContractRegister = (runtime: HandlerRuntime.t, item: Internal.item) =>
19
+ switch item {
20
+ | Event({onEventRegistration}) =>
21
+ let contractRegister = switch onEventRegistration {
22
+ | {contractRegister: Some(contractRegister)} => contractRegister
23
+ | {contractRegister: None, eventConfig: {name: eventName}} =>
24
+ // Unexpected case, since we should pass only events with contract register to this function
25
+ JsError.throwWithMessage("Contract register is not set for event " ++ eventName)
26
+ }
27
+ contractRegister({
28
+ event: item->Ecosystem.getItemEvent(~ecosystem=runtime->HandlerRuntime.ecosystem),
29
+ context: runtime.contractRegisterContext,
30
+ })
31
+ | Block({blockNumber, onBlockRegistration, ?block}) =>
32
+ let contractRegister = switch onBlockRegistration {
33
+ | {contractRegister: ?Some(contractRegister)} => contractRegister
34
+ | {name} => JsError.throwWithMessage("Contract register is not set for block handler " ++ name)
35
+ }
36
+ let ecosystem = runtime->HandlerRuntime.ecosystem
37
+ let context = runtime.contractRegisterContext
38
+ // A block handler's register pass gets what its handler gets, the block (or
39
+ // slot), with the register context in place of the handler's.
40
+ contractRegister(
41
+ switch ecosystem.name {
42
+ | Svm => {slot: blockNumber, ?block, context}
43
+ | Evm | Fuel => {
44
+ block: block->Option.getOr(Ecosystem.makeKeyOnlyBlock(~blockNumber, ~ecosystem)),
45
+ context,
46
+ }
47
+ },
48
+ )
23
49
  }
24
- contractRegister({
25
- event: item->Ecosystem.getItemEvent(~ecosystem=runtime->HandlerRuntime.ecosystem),
26
- context: runtime.contractRegisterContext,
27
- })
28
- }
29
50
 
30
51
  let runContractRegistersOrThrow = async (
31
52
  ~itemsWithContractRegister: array<Internal.item>,
@@ -36,8 +57,11 @@ let runContractRegistersOrThrow = async (
36
57
  // materialise the selected fields onto the payloads before running them. All
37
58
  // items belong to the chain being fetched, and both stores are the chain's
38
59
  // own, which this response's pages have already been merged into.
60
+ // A subgraph's block register passes ride along; their blocks have no page.
39
61
  await ChainState.materializePageItems(
40
- ~items=itemsWithContractRegister,
62
+ ~items=Env.subgraphMode.contents
63
+ ? itemsWithContractRegister->Array.filter(item => item->Internal.getItemKind === 0)
64
+ : itemsWithContractRegister,
41
65
  ~transactionStore=chainState->ChainState.transactionStore,
42
66
  ~blockStore=chainState->ChainState.blockStore,
43
67
  ~shouldChecksum=chainState->ChainState.shouldChecksum,
@@ -47,6 +71,7 @@ let runContractRegistersOrThrow = async (
47
71
  let registrations: array<AddressStore.registration> = []
48
72
  let chainLogger = chainState->ChainState.logger
49
73
  let ecosystem = runtime->HandlerRuntime.ecosystem
74
+ let isSubgraph = Env.subgraphMode.contents
50
75
 
51
76
  let _ = await itemsWithContractRegister
52
77
  ->Array.map(async item => {
@@ -56,13 +81,32 @@ let runContractRegistersOrThrow = async (
56
81
  chainLogger,
57
82
  ecosystem,
58
83
  registrations,
84
+ creationRound: ?(
85
+ isSubgraph
86
+ ? Some(
87
+ switch item {
88
+ // A block handler is one of its block's own triggers.
89
+ | Block(_) => 1
90
+ | Event(_) =>
91
+ chainState->ChainState.fetchState->FetchState.creationRoundOfRegistration(~item)
92
+ },
93
+ )
94
+ : None
95
+ ),
59
96
  }
60
97
  let scope = RunScope.ContractRegister(run)
61
98
  switch await RunScope.enter(scope, invokeContractRegister, runtime, item) {
62
99
  | exception exn =>
63
100
  scope->RunScope.resolve
64
101
  exn->ErrorHandling.mkLogAndRaise(
65
- ~msg="Event contractRegister failed, please fix the error to keep the indexer running smoothly",
102
+ ~msg=switch item {
103
+ | Event(
104
+ _,
105
+ ) => "Event contractRegister failed, please fix the error to keep the indexer running smoothly"
106
+ | Block(
107
+ _,
108
+ ) => "Block handler contractRegister failed, please fix the error to keep the indexer running smoothly"
109
+ },
66
110
  ~logger=run->RunScope.contractRegisterLogger,
67
111
  )
68
112
  | () => scope->RunScope.resolve
@@ -73,6 +117,221 @@ let runContractRegistersOrThrow = async (
73
117
  registrations
74
118
  }
75
119
 
120
+ // A response's events and the block passes of the same blocks, in the order
121
+ // graph-node runs their triggers: a `once` handler first in its block, the
122
+ // other block handlers after the block's logs and calls
123
+ // (chain/ethereum/src/trigger.rs, `impl Ord for EthereumTrigger`).
124
+ let mergeRegisterItems = (~eventItems: array<Internal.item>, ~blockItems: array<Internal.item>) => {
125
+ let merged = []
126
+ let i = ref(0)
127
+ let j = ref(0)
128
+ while i.contents < eventItems->Array.length || j.contents < blockItems->Array.length {
129
+ let takeBlock = switch (eventItems->Array.get(i.contents), blockItems->Array.get(j.contents)) {
130
+ | (None, _) => true
131
+ | (_, None) => false
132
+ | (Some(event), Some(block)) =>
133
+ let eventBlock = event->Internal.getItemBlockNumber
134
+ let blockBlock = block->Internal.getItemBlockNumber
135
+ blockBlock < eventBlock || (blockBlock === eventBlock && block->Internal.itemRunsFirst)
136
+ }
137
+ if takeBlock {
138
+ merged->Array.push(blockItems->Array.getUnsafe(j.contents))
139
+ j := j.contents + 1
140
+ } else {
141
+ merged->Array.push(eventItems->Array.getUnsafe(i.contents))
142
+ i := i.contents + 1
143
+ }
144
+ }
145
+ merged
146
+ }
147
+
148
+ // An unfiltered handler has a pass for every block, so a run with block passes
149
+ // starts them a chunk at a time, in chain order: each chunk reads what the ones
150
+ // before it saved, and a pass is started before any later one of its chunk,
151
+ // which is the order the runtime hands out its slots in.
152
+ let blockRegisterChunk = 2048
153
+ // Block passes built at once: a response can span millions of blocks.
154
+ let blockRegisterWindow = 10_000
155
+ let maxResponseBlockPasses = 50_000
156
+
157
+ let runRegisterPassesOrThrow = async (
158
+ ~eventItems: array<Internal.item>,
159
+ ~blockItems: array<Internal.item>,
160
+ ~runtime: HandlerRuntime.t,
161
+ ~chainState: ChainState.t,
162
+ ) => {
163
+ let items = mergeRegisterItems(~eventItems, ~blockItems)
164
+ let registrations = []
165
+ let start = ref(0)
166
+ while start.contents < items->Array.length {
167
+ let chunk = items->Array.slice(~start=start.contents, ~end=start.contents + blockRegisterChunk)
168
+ registrations
169
+ ->Array.pushMany(
170
+ await runContractRegistersOrThrow(~itemsWithContractRegister=chunk, ~runtime, ~chainState),
171
+ )
172
+ ->ignore
173
+ start := start.contents + blockRegisterChunk
174
+ }
175
+ registrations
176
+ }
177
+
178
+ // Passes of a chunk finish in any order, and of two registrations of one
179
+ // address the store keeps the first: it has to be the earlier block's.
180
+ let sortRegistrations = (registrations: array<AddressStore.registration>) =>
181
+ registrations->Array.toSorted((a, b) =>
182
+ switch Int.compare(a.registrationBlock, b.registrationBlock) {
183
+ | 0. => Int.compare(a.creationRound->Option.getOr(1), b.creationRound->Option.getOr(1))
184
+ | order => order
185
+ }
186
+ )
187
+
188
+ // A response's event passes with the block passes of the blocks after the last
189
+ // one passed, through `through`, a window of block passes at a time. Returns
190
+ // the registrations and the last block passed, if the block passes advanced.
191
+ let runResponsePassesOrThrow = async (
192
+ ~eventItems: array<Internal.item>,
193
+ ~through,
194
+ ~runtime: HandlerRuntime.t,
195
+ ~chainState: ChainState.t,
196
+ ) => {
197
+ let registrations = []
198
+ let eventIdx = ref(0)
199
+ let passedThrough = ref(None)
200
+ let after = ref(chainState->ChainState.blockRegisterThrough->Option.getOr(through))
201
+ let isDone = ref(false)
202
+ while !isDone.contents {
203
+ let (blockItems, covered) = switch chainState->ChainState.blockRegisterItemsAfter(
204
+ ~after=after.contents,
205
+ ~through,
206
+ ~limit=blockRegisterWindow,
207
+ ) {
208
+ | Some((blockItems, covered)) => (blockItems, Some(covered))
209
+ | None => ([], None)
210
+ }
211
+ // The events up to the window's last block go with it, and the last
212
+ // window takes the rest.
213
+ let eventEnd = switch covered {
214
+ | Some(covered) if covered < through =>
215
+ let idx = ref(eventIdx.contents)
216
+ while (
217
+ idx.contents < eventItems->Array.length &&
218
+ eventItems->Array.getUnsafe(idx.contents)->Internal.getItemBlockNumber <= covered
219
+ ) {
220
+ idx := idx.contents + 1
221
+ }
222
+ idx.contents
223
+ | _ => eventItems->Array.length
224
+ }
225
+ let startedAt = Date.now()
226
+ let windowRegistrations = await runRegisterPassesOrThrow(
227
+ ~eventItems=eventItems->Array.slice(~start=eventIdx.contents, ~end=eventEnd),
228
+ ~blockItems,
229
+ ~runtime,
230
+ ~chainState,
231
+ )
232
+ chainState
233
+ ->ChainState.logger
234
+ ->Logger.trace(
235
+ "Ran register passes with block passes",
236
+ ~params={
237
+ "after": after.contents,
238
+ "through": covered,
239
+ "blockPasses": blockItems->Array.length,
240
+ "eventPasses": eventEnd - eventIdx.contents,
241
+ "registrations": windowRegistrations->Array.length,
242
+ "ms": Date.now() -. startedAt,
243
+ },
244
+ )
245
+ registrations->Array.pushMany(windowRegistrations)->ignore
246
+ eventIdx := eventEnd
247
+ switch covered {
248
+ | Some(covered) =>
249
+ passedThrough := Some(covered)
250
+ after := covered
251
+ isDone := covered >= through && eventIdx.contents >= eventItems->Array.length
252
+ | None => isDone := true
253
+ }
254
+ }
255
+ (registrations->sortRegistrations, passedThrough.contents)
256
+ }
257
+
258
+ // A subgraph's register pass for one event can cost a dozen contract calls, and
259
+ // a response is applied only when every pass of it is done: a 5,000-event
260
+ // response of PairCreated made PancakeSwap v2 wait for ~15,000 calls before it
261
+ // committed anything. A response with more register passes than the calls can
262
+ // answer promptly is cut after a block and the rest fetched again; HyperSync
263
+ // answers that far faster than the RPC does. The runtime reports the calls its
264
+ // finished passes made (`__envioSubgraphRegisterCostPerItem`); until it has
265
+ // seen enough passes the cost is assumed to be high, and a run whose passes
266
+ // make no calls is never cut.
267
+ let registerCallsPerItem: unit => option<float> = %raw(`() => {
268
+ const cost = globalThis.__envioSubgraphRegisterCostPerItem
269
+ const value = cost ? cost() : undefined
270
+ return value === undefined ? undefined : value
271
+ }`)
272
+
273
+ // A subgraph's register store keeps an entity's past versions for the passes
274
+ // still to read them, and none starts below the lowest partition frontier.
275
+ let reportRegisterFrontier: (int, int) => unit = %raw(`(chainId, block) => {
276
+ const report = globalThis.__envioSubgraphRegisterFrontier
277
+ if (report) report(chainId, block)
278
+ }`)
279
+
280
+ let registerCallsPerChunk = 3000.
281
+ let unknownCostChunk = 100
282
+ let minRegisterChunk = 100
283
+
284
+ let registerChunkLimit = () =>
285
+ if !Env.subgraphMode.contents {
286
+ None
287
+ } else {
288
+ switch Env.subgraphRegisterChunk {
289
+ | Some(limit) => limit > 0 ? Some(limit) : None
290
+ | None =>
291
+ switch registerCallsPerItem() {
292
+ | None => Some(unknownCostChunk)
293
+ | Some(cost) if cost < 0.5 => None
294
+ | Some(cost) =>
295
+ Some(Pervasives.max(minRegisterChunk, (registerCallsPerChunk /. cost)->Float.toInt))
296
+ }
297
+ }
298
+ }
299
+
300
+ // The items and last block of `response` up to the block holding the `limit`th
301
+ // item that has a register pass. Whole blocks only: a block's items are never
302
+ // split between two responses.
303
+ let cutAtRegisterLimit = (items: array<Internal.item>, ~latestFetchedBlock, ~limit) => {
304
+ let seen = ref(0)
305
+ let cutIdx = ref(-1)
306
+ let idx = ref(0)
307
+ while cutIdx.contents < 0 && idx.contents < items->Array.length {
308
+ if items->Array.getUnsafe(idx.contents)->hasContractRegister {
309
+ seen := seen.contents + 1
310
+ if seen.contents >= limit {
311
+ cutIdx := idx.contents
312
+ }
313
+ }
314
+ idx := idx.contents + 1
315
+ }
316
+ if cutIdx.contents < 0 {
317
+ (items, latestFetchedBlock)
318
+ } else {
319
+ let cutBlock = items->Array.getUnsafe(cutIdx.contents)->Internal.getItemBlockNumber
320
+ let last = ref(cutIdx.contents)
321
+ while (
322
+ last.contents + 1 < items->Array.length &&
323
+ items->Array.getUnsafe(last.contents + 1)->Internal.getItemBlockNumber <= cutBlock
324
+ ) {
325
+ last := last.contents + 1
326
+ }
327
+ if last.contents + 1 >= items->Array.length && cutBlock >= latestFetchedBlock {
328
+ (items, latestFetchedBlock)
329
+ } else {
330
+ (items->Array.slice(~start=0, ~end=last.contents + 1), cutBlock)
331
+ }
332
+ }
333
+ }
334
+
76
335
  let rec onQueryResponse = async (
77
336
  state: IndexerState.t,
78
337
  {chainId, response, query}: partitionQueryResponse,
@@ -94,6 +353,11 @@ let rec onQueryResponse = async (
94
353
  stats,
95
354
  knownHeight,
96
355
  } = response
356
+ let (parsedQueueItems, latestFetchedBlockNumber) = switch registerChunkLimit() {
357
+ | Some(limit) =>
358
+ parsedQueueItems->cutAtRegisterLimit(~latestFetchedBlock=latestFetchedBlockNumber, ~limit)
359
+ | None => (parsedQueueItems, latestFetchedBlockNumber)
360
+ }
97
361
 
98
362
  chainState->ChainState.recordBlockRangeFetch(
99
363
  ~totalTimeElapsed=stats.totalTimeElapsed,
@@ -216,7 +480,12 @@ let rec onQueryResponse = async (
216
480
 
217
481
  // Re-check staleness: contract registration is async, so the chain state
218
482
  // may have rolled back by the time we apply the fetched items.
219
- let proceed = (~newRegistrations) =>
483
+ let proceed = (
484
+ ~newRegistrations,
485
+ ~blockRegisterThrough=?,
486
+ ~newItems=newItems,
487
+ ~latestFetchedBlock=latestFetchedBlockNumber,
488
+ ) =>
220
489
  if !(state->IndexerState.isStale(~stateId)) {
221
490
  applyQueryResponse(
222
491
  state,
@@ -224,17 +493,93 @@ let rec onQueryResponse = async (
224
493
  ~newItems,
225
494
  ~newRegistrations,
226
495
  ~knownHeight,
227
- ~latestFetchedBlock=latestFetchedBlockNumber,
496
+ ~latestFetchedBlock,
228
497
  ~query,
229
498
  )
499
+ switch blockRegisterThrough {
500
+ | Some(through) => chainState->ChainState.markBlockRegistered(~through)
501
+ | None => ()
502
+ }
230
503
  ChainMetadata.stage(state)
231
504
  scheduleFetch()
232
505
  scheduleProcessing()
233
506
  }
234
507
 
508
+ // The block passes run with the response that first fetches their
509
+ // blocks, among its events in chain order, and after every earlier unit
510
+ // of the chain's register work.
511
+ let blockRegisterCeiling = Pervasives.min(
512
+ latestFetchedBlockNumber,
513
+ Pervasives.max(knownHeight, chainState->ChainState.knownHeight),
514
+ )
515
+ let withBlockPasses =
516
+ chainState->ChainState.hasBlockRegisters &&
517
+ (itemsWithContractRegister->Utils.Array.notEmpty ||
518
+ chainState->ChainState.canAdvanceBlockRegisters(~through=blockRegisterCeiling))
519
+
235
520
  switch itemsWithContractRegister {
521
+ | _ if withBlockPasses =>
522
+ let (previous, finish) = chainState->ChainState.queueBlockRegisterRun
523
+ let held =
524
+ chainState->ChainState.holdExtraBudget(
525
+ ~items=Pervasives.max(0, newItems->Array.length - query.itemsEst),
526
+ )
527
+ await previous
528
+ if state->IndexerState.isStale(~stateId) {
529
+ chainState->ChainState.releaseExtraBudget(held)
530
+ finish()
531
+ scheduleFetch()
532
+ } else {
533
+ // A response spanning millions of blocks of an unfiltered handler is
534
+ // applied only once all their passes ran, so it carries at most
535
+ // `maxResponseBlockPasses` past its first block and is cut after
536
+ // them; the partition fetches the rest again.
537
+ let cutAt = switch chainState->ChainState.blockRegisterItemsAfter(
538
+ ~after=Pervasives.max(
539
+ chainState->ChainState.blockRegisterThrough->Option.getOr(blockRegisterCeiling),
540
+ query.fromBlock - 1,
541
+ ),
542
+ ~through=blockRegisterCeiling,
543
+ ~limit=maxResponseBlockPasses,
544
+ ) {
545
+ | Some((_, covered)) if covered < blockRegisterCeiling => Some(covered)
546
+ | _ => None
547
+ }
548
+ let through = cutAt->Option.getOr(blockRegisterCeiling)
549
+ let (newItems, eventItems, latestFetchedBlock) = if cutAt !== None {
550
+ let isKept = item => item->Internal.getItemBlockNumber <= through
551
+ (
552
+ newItems->Array.filter(isKept),
553
+ itemsWithContractRegister->Array.filter(isKept),
554
+ through,
555
+ )
556
+ } else {
557
+ (newItems, itemsWithContractRegister, latestFetchedBlockNumber)
558
+ }
559
+ switch await runResponsePassesOrThrow(~eventItems, ~through, ~runtime, ~chainState) {
560
+ | exception exn =>
561
+ finish()
562
+ IndexerState.errorExit(
563
+ state,
564
+ exn->ErrorHandling.make(~logger=chainState->ChainState.logger),
565
+ )
566
+ | (newRegistrations, blockRegisterThrough) =>
567
+ chainState->ChainState.releaseExtraBudget(held)
568
+ proceed(~newRegistrations, ~blockRegisterThrough?, ~newItems, ~latestFetchedBlock)
569
+ finish()
570
+ }
571
+ }
236
572
  | [] => proceed(~newRegistrations=[])
237
573
  | _ =>
574
+ // A subgraph's register passes run in chunks, long enough for the pool
575
+ // to admit queries on top of a response it doesn't count yet.
576
+ let held = Env.subgraphMode.contents
577
+ ? Some(
578
+ chainState->ChainState.holdExtraBudget(
579
+ ~items=Pervasives.max(0, newItems->Array.length - query.itemsEst),
580
+ ),
581
+ )
582
+ : None
238
583
  switch await runContractRegistersOrThrow(
239
584
  ~itemsWithContractRegister,
240
585
  ~runtime,
@@ -245,7 +590,9 @@ let rec onQueryResponse = async (
245
590
  state,
246
591
  exn->ErrorHandling.make(~logger=chainState->ChainState.logger),
247
592
  )
248
- | newRegistrations => proceed(~newRegistrations)
593
+ | newRegistrations =>
594
+ held->Option.forEach(held => chainState->ChainState.releaseExtraBudget(held))
595
+ proceed(~newRegistrations)
249
596
  }
250
597
  }
251
598
  }
@@ -270,6 +617,21 @@ and applyQueryResponse = (
270
617
  ~knownHeight,
271
618
  )
272
619
 
620
+ if Env.subgraphMode.contents {
621
+ let fetchState = chainState->ChainState.fetchState
622
+ switch fetchState.optimizedPartitions->FetchState.OptimizedPartitions.getLatestFullyFetchedBlock {
623
+ | Some(frontier) =>
624
+ // A block pass still to run reads at its own block, which can be below
625
+ // every partition.
626
+ let frontier = switch fetchState.blockRegisterThrough {
627
+ | Some(through) => Pervasives.min(frontier, through)
628
+ | None => frontier
629
+ }
630
+ reportRegisterFrontier(chainId->ChainId.toInt, frontier)
631
+ | None => ()
632
+ }
633
+ }
634
+
273
635
  // In auto-exit mode, set endBlock to the first event's block when events arrive.
274
636
  if state->IndexerState.exitAfterFirstEventBlock {
275
637
  switch newItems->Array.find(item =>
@@ -305,6 +667,68 @@ let finishWaitingForNewBlock = (
305
667
  scheduleProcessing()
306
668
  }
307
669
 
670
+ // Block passes no response will run: the blocks every partition has already
671
+ // fetched (or, with no partition, the blocks up to the head), up to a limit per
672
+ // run. Owns its error boundary, as it isn't awaited.
673
+ let catchUpBlockRegisters = async (
674
+ state: IndexerState.t,
675
+ ~chainId,
676
+ ~runtime,
677
+ ~stateId,
678
+ ~scheduleFetch,
679
+ ~scheduleProcessing,
680
+ ) => {
681
+ let chainState = state->IndexerState.getChainState(~chainId)
682
+ if chainState->ChainState.isBlockRegisterIdle {
683
+ switch chainState->ChainState.nextBlockRegisterItems(
684
+ ~through=chainState->ChainState.blockRegisterFrontier,
685
+ ~limit=blockRegisterWindow,
686
+ ) {
687
+ | None => ()
688
+ | Some((blockItems, through)) =>
689
+ let (_, finish) = chainState->ChainState.queueBlockRegisterRun
690
+ let startedAt = Date.now()
691
+ switch await runRegisterPassesOrThrow(~eventItems=[], ~blockItems, ~runtime, ~chainState) {
692
+ | exception exn =>
693
+ finish()
694
+ IndexerState.errorExit(
695
+ state,
696
+ exn->ErrorHandling.make(~logger=chainState->ChainState.logger),
697
+ )
698
+ | registrations =>
699
+ let isStale = state->IndexerState.isStale(~stateId)
700
+ chainState
701
+ ->ChainState.logger
702
+ ->Logger.trace(
703
+ "Ran block passes",
704
+ ~params={
705
+ "through": through,
706
+ "blockPasses": blockItems->Array.length,
707
+ "registrations": registrations->Array.length,
708
+ "stale": isStale,
709
+ "ms": Date.now() -. startedAt,
710
+ },
711
+ )
712
+ if !isStale {
713
+ chainState->ChainState.applyBlockRegisters(
714
+ ~registrations=registrations->sortRegistrations,
715
+ ~through,
716
+ )
717
+ }
718
+ finish()
719
+
720
+ // A run dropped as stale is run again: the fetch tick that would have
721
+ // started it found this one in flight and passed.
722
+ if !isStale {
723
+ ChainMetadata.stage(state)
724
+ scheduleProcessing()
725
+ }
726
+ scheduleFetch()
727
+ }
728
+ }
729
+ }
730
+ }
731
+
308
732
  let fetchChain = async (
309
733
  state: IndexerState.t,
310
734
  chainId,
@@ -317,6 +741,16 @@ let fetchChain = async (
317
741
  ) => {
318
742
  let chainState = state->IndexerState.getChainState(~chainId)
319
743
  if !(state->IndexerState.isResolvingReorg) && !(state->IndexerState.isStopped) {
744
+ if chainState->ChainState.hasBlockRegisters {
745
+ catchUpBlockRegisters(
746
+ state,
747
+ ~chainId,
748
+ ~runtime,
749
+ ~stateId,
750
+ ~scheduleFetch,
751
+ ~scheduleProcessing,
752
+ )->Promise.ignore
753
+ }
320
754
  let isRealtime = state->IndexerState.isRealtime
321
755
  let sourceManager = chainState->ChainState.sourceManager
322
756