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
@@ -0,0 +1,398 @@
1
+ /**
2
+ * A block handler's `block.timestamp` and header fields, and the batching they
3
+ * share.
4
+ *
5
+ * envio hands a block handler only the block number, so the timestamp has to be
6
+ * fetched. Every handler invocation in a batch asks within the same microtask,
7
+ * so the requests are collected and answered by a HyperSync range query per
8
+ * cluster of nearby blocks rather than one round trip per block. An RPC endpoint, if one is configured,
9
+ * is the fallback for whatever HyperSync couldn't answer.
10
+ *
11
+ * `block.hash` and the other header fields ride the same path, as a second
12
+ * batcher so a handler that only reads the timestamp keeps its narrow
13
+ * queries. Fetched after the batch, a header is outside the reorg guard that
14
+ * validates an event's block, so it is only ever served from below the
15
+ * chain's head by its reorg depth: the translator holds the chain back by
16
+ * that much, and HyperSync's answers are checked against it here too.
17
+ */
18
+
19
+ import { rpcClient } from "./rpc.ts";
20
+ import { HttpStatusError, untilAnswered, withDeadline } from "./transient.ts";
21
+
22
+ const API_TOKEN_ENV_VAR = "ENVIO_API_TOKEN";
23
+
24
+ const hypersyncUrl = (chainId: number) => `https://${chainId}.hypersync.xyz/query`;
25
+
26
+ type Waiter<T> = {
27
+ resolve: (value: T) => void;
28
+ reject: (error: unknown) => void;
29
+ };
30
+
31
+ let rpcUrls: string[] = [];
32
+ /** Per chain, how far below the head a header must sit before it is served. */
33
+ let headerLag: Record<number, number> = {};
34
+ /** Chains HyperSync doesn't serve, which RPC answers alone. */
35
+ let rpcOnly = new Set<number>();
36
+
37
+ export function configureBlockTimestamps(
38
+ urls: string[],
39
+ lag: Record<number, number> = {},
40
+ rpcOnlyChains: number[] = [],
41
+ ) {
42
+ rpcUrls = urls;
43
+ headerLag = lag;
44
+ rpcOnly = new Set(rpcOnlyChains);
45
+ timestamps.reset();
46
+ headers.reset();
47
+ }
48
+
49
+ /** A block's header as envio names its fields, every value a hex or decimal string. */
50
+ export type BlockHeader = Record<string, string | null>;
51
+
52
+ type Fetched<T> = { blocks: Map<number, T>; archiveHeight?: number };
53
+
54
+ type Batcher<T> = {
55
+ request: (chainId: number, blockNumber: number) => Promise<T>;
56
+ reset: () => void;
57
+ };
58
+
59
+ /**
60
+ * Collects the blocks asked for within one microtask and answers them with
61
+ * HyperSync range queries, falling back to RPC for what it didn't return.
62
+ */
63
+ function makeBatcher<T>(
64
+ what: string,
65
+ fromHyperSync: (chainId: number, from: number, to: number) => Promise<Fetched<T>>,
66
+ fromRpc: (chainId: number, blockNumber: number) => Promise<T>,
67
+ ): Batcher<T> {
68
+ /** Blocks asked for since the last flush, by chain then block number. */
69
+ let pending = new Map<number, Map<number, Waiter<T>[]>>();
70
+ let flushScheduled = false;
71
+
72
+ async function answer(chainId: number, blocks: Map<number, Waiter<T>[]>) {
73
+ let answers = new Map<number, T>();
74
+
75
+ // HyperSync isn't asked for a chain it doesn't serve: where a firewall
76
+ // drops the connection rather than refusing it, asking held the batch for
77
+ // the request deadline.
78
+ const askHyperSync = !(rpcOnly.has(chainId) && rpcUrls.length > 0);
79
+ try {
80
+ if (askHyperSync) {
81
+ const fetched = await bounded(
82
+ clusters([...blocks.keys()]).map(([from, to]) => () => fromHyperSync(chainId, from, to)),
83
+ MAX_QUERIES_IN_FLIGHT,
84
+ );
85
+ answers = new Map(fetched.flatMap((cluster) => [...cluster.blocks]));
86
+ }
87
+ } catch (error) {
88
+ if (rpcUrls.length === 0) {
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ for (const waiters of blocks.values()) {
91
+ for (const waiter of waiters) {
92
+ waiter.reject(
93
+ new Error(
94
+ `Envio Subgraph couldn't read a block ${what} from HyperSync: ${message}\n` +
95
+ `Set ${API_TOKEN_ENV_VAR} in .env or the environment — create one at\n` +
96
+ `https://envio.dev/app/api-tokens — or set ENVIO_SUBGRAPH_RPC to fall back to RPC.`,
97
+ ),
98
+ );
99
+ }
100
+ }
101
+ return;
102
+ }
103
+ }
104
+
105
+ // What HyperSync didn't answer is asked of RPC all at once: the client
106
+ // bounds the requests in flight, and one at a time a chain HyperSync
107
+ // doesn't serve paid the endpoint's latency once per block.
108
+ await Promise.all(
109
+ [...blocks].map(async ([blockNumber, waiters]) => {
110
+ const known = answers.get(blockNumber);
111
+ if (known !== undefined) {
112
+ for (const waiter of waiters) waiter.resolve(known);
113
+ return;
114
+ }
115
+ try {
116
+ const value = await fromRpc(chainId, blockNumber);
117
+ for (const waiter of waiters) waiter.resolve(value);
118
+ } catch (error) {
119
+ for (const waiter of waiters) waiter.reject(error);
120
+ }
121
+ }),
122
+ );
123
+ }
124
+
125
+ return {
126
+ request(chainId, blockNumber) {
127
+ return new Promise<T>((resolve, reject) => {
128
+ let blocks = pending.get(chainId);
129
+ if (!blocks) {
130
+ blocks = new Map();
131
+ pending.set(chainId, blocks);
132
+ }
133
+ const waiters = blocks.get(blockNumber);
134
+ if (waiters) {
135
+ waiters.push({ resolve, reject });
136
+ } else {
137
+ blocks.set(blockNumber, [{ resolve, reject }]);
138
+ }
139
+
140
+ if (!flushScheduled) {
141
+ flushScheduled = true;
142
+ queueMicrotask(() => {
143
+ flushScheduled = false;
144
+ const collected = pending;
145
+ pending = new Map();
146
+ for (const [chain, blocksForChain] of collected) {
147
+ void answer(chain, blocksForChain);
148
+ }
149
+ });
150
+ }
151
+ });
152
+ },
153
+ reset() {
154
+ pending = new Map();
155
+ flushScheduled = false;
156
+ },
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Gaps narrower than one response's worth of headers are fetched through
162
+ * rather than split: a polling handler every few blocks is one query, one
163
+ * every 100,000 is one tiny query per block.
164
+ */
165
+ const CLUSTER_GAP = 10_000;
166
+ /**
167
+ * A range is paged one response at a time, so merging without bound turned a
168
+ * polling handler every 1,600 blocks into one scan over the whole batch —
169
+ * Notional's 3.2M blocks, 38 s per timestamp. Capped, the pieces run side by
170
+ * side.
171
+ */
172
+ const MAX_CLUSTER_SPAN = 20_000;
173
+ const MAX_QUERIES_IN_FLIGHT = 8;
174
+
175
+ /** The requested blocks as `[from, to]` ranges, in order. */
176
+ function clusters(blockNumbers: number[]): [number, number][] {
177
+ const sorted = [...blockNumbers].sort((a, b) => a - b);
178
+ const ranges: [number, number][] = [];
179
+ for (const blockNumber of sorted) {
180
+ const last = ranges[ranges.length - 1];
181
+ if (last && blockNumber - last[1] <= CLUSTER_GAP && blockNumber - last[0] < MAX_CLUSTER_SPAN) {
182
+ last[1] = blockNumber;
183
+ } else ranges.push([blockNumber, blockNumber]);
184
+ }
185
+ return ranges;
186
+ }
187
+
188
+ /** Runs `tasks` with at most `limit` of them at once, keeping their order. */
189
+ async function bounded<T>(tasks: (() => Promise<T>)[], limit: number): Promise<T[]> {
190
+ const results: T[] = new Array(tasks.length);
191
+ let next = 0;
192
+ const worker = async () => {
193
+ while (next < tasks.length) {
194
+ const index = next++;
195
+ results[index] = await tasks[index]();
196
+ }
197
+ };
198
+ await Promise.all(Array.from({ length: Math.min(limit, tasks.length) }, worker));
199
+ return results;
200
+ }
201
+
202
+
203
+ type HyperSyncResponse = {
204
+ data: { blocks?: Record<string, string>[] }[];
205
+ next_block: number;
206
+ archive_height?: number | null;
207
+ };
208
+
209
+ /**
210
+ * One HyperSync range of blocks, `fields` selected, each block turned into a
211
+ * `T` by `parse`. A wide range takes more than one round trip: HyperSync answers
212
+ * as much of it as one response holds and points at where to resume.
213
+ */
214
+ async function hyperSyncBlocks<T>(
215
+ chainId: number,
216
+ fromBlock: number,
217
+ toBlock: number,
218
+ fields: string[],
219
+ parse: (block: Record<string, string>) => T,
220
+ what: string,
221
+ ): Promise<Fetched<T>> {
222
+ const token = process.env[API_TOKEN_ENV_VAR];
223
+ const blocks = new Map<number, T>();
224
+ let archiveHeight: number | undefined;
225
+
226
+ let cursor = fromBlock;
227
+ while (cursor <= toBlock) {
228
+ const url = hypersyncUrl(chainId);
229
+ const from = cursor;
230
+ // With an RPC to fall back to, one try: a failing HyperSync hands the
231
+ // blocks to RPC rather than holding them until it recovers.
232
+ const ask = rpcUrls.length > 0 ? withDeadline : untilAnswered;
233
+ const body = await ask(
234
+ `HyperSync's ${what} of blocks ${from}-${toBlock}`,
235
+ async (signal): Promise<HyperSyncResponse> => {
236
+ const response = await fetch(url, {
237
+ method: "POST",
238
+ signal,
239
+ headers: {
240
+ "content-type": "application/json",
241
+ ...(token ? { authorization: `Bearer ${token}` } : {}),
242
+ },
243
+ body: JSON.stringify({
244
+ from_block: from,
245
+ to_block: toBlock + 1,
246
+ include_all_blocks: true,
247
+ field_selection: { block: fields },
248
+ }),
249
+ });
250
+ if (!response.ok) {
251
+ throw new HttpStatusError(url, response.status, await response.text());
252
+ }
253
+ return (await response.json()) as HyperSyncResponse;
254
+ },
255
+ );
256
+ for (const page of body.data ?? []) {
257
+ for (const block of page.blocks ?? []) {
258
+ blocks.set(Number(block.number), parse(block));
259
+ }
260
+ }
261
+ if (typeof body.archive_height === "number") archiveHeight = body.archive_height;
262
+
263
+ if (!body.next_block || body.next_block <= cursor) {
264
+ break;
265
+ }
266
+ cursor = body.next_block;
267
+ }
268
+
269
+ return { blocks, archiveHeight };
270
+ }
271
+
272
+ const timestamps = makeBatcher<bigint>(
273
+ "timestamp",
274
+ async (chainId, from, to) =>
275
+ hyperSyncBlocks(
276
+ chainId,
277
+ from,
278
+ to,
279
+ ["number", "timestamp"],
280
+ (block) => BigInt(block.timestamp),
281
+ "timestamps",
282
+ ),
283
+ async (_chainId, blockNumber) => {
284
+ if (rpcUrls.length === 0) {
285
+ throw new Error(
286
+ `Envio Subgraph couldn't read the timestamp of block ${blockNumber}: HyperSync didn't\n` +
287
+ `return it, and no RPC fallback is configured. Set ENVIO_SUBGRAPH_RPC in .env or the\n` +
288
+ `environment.`,
289
+ );
290
+ }
291
+ const client = rpcClient(rpcUrls);
292
+ const block = await untilAnswered(
293
+ `block ${blockNumber} from RPC`,
294
+ () => client.getBlock({ blockNumber }),
295
+ { deadline: false },
296
+ );
297
+ return BigInt(block.timestamp);
298
+ },
299
+ );
300
+
301
+ export function requestBlockTimestamp(chainId: number, blockNumber: number): Promise<bigint> {
302
+ return timestamps.request(chainId, blockNumber);
303
+ }
304
+
305
+ /** HyperSync's block columns, under the names envio decodes a block's fields as. */
306
+ const HEADER_COLUMNS: Record<string, string> = {
307
+ number: "number",
308
+ hash: "hash",
309
+ parent_hash: "parentHash",
310
+ sha3_uncles: "sha3Uncles",
311
+ miner: "miner",
312
+ state_root: "stateRoot",
313
+ transactions_root: "transactionsRoot",
314
+ receipts_root: "receiptsRoot",
315
+ gas_used: "gasUsed",
316
+ gas_limit: "gasLimit",
317
+ difficulty: "difficulty",
318
+ total_difficulty: "totalDifficulty",
319
+ size: "size",
320
+ base_fee_per_gas: "baseFeePerGas",
321
+ timestamp: "timestamp",
322
+ };
323
+
324
+ function headerFromHyperSync(block: Record<string, string>): BlockHeader {
325
+ const header: BlockHeader = {};
326
+ for (const [column, name] of Object.entries(HEADER_COLUMNS)) {
327
+ header[name] = block[column] ?? null;
328
+ }
329
+ return header;
330
+ }
331
+
332
+ function headerFromRpc(block: Record<string, unknown>): BlockHeader {
333
+ const header: BlockHeader = {};
334
+ for (const name of Object.values(HEADER_COLUMNS)) {
335
+ const value = block[name];
336
+ header[name] = value === undefined || value === null ? null : String(value);
337
+ }
338
+ return header;
339
+ }
340
+
341
+ const HEADER_WAIT_MS = 5_000;
342
+ const HEADER_WAIT_TRIES = 120;
343
+ const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
344
+
345
+ /**
346
+ * A header read from inside the chain's reorg window could be of a block that
347
+ * is later replaced, with nothing to correct what the handler stored. The
348
+ * translator holds the chain back by the reorg depth, so this is a backstop:
349
+ * if HyperSync's head says the block is still too close, wait for it.
350
+ */
351
+ function belowReorgWindow(chainId: number, blockNumber: number, archiveHeight?: number) {
352
+ const lag = headerLag[chainId] ?? 0;
353
+ return lag === 0 || archiveHeight === undefined || blockNumber <= archiveHeight - lag;
354
+ }
355
+
356
+ const headers = makeBatcher<BlockHeader>(
357
+ "header",
358
+ async (chainId, from, to) => {
359
+ for (let attempt = 0; ; attempt++) {
360
+ const fetched = await hyperSyncBlocks(
361
+ chainId,
362
+ from,
363
+ to,
364
+ Object.keys(HEADER_COLUMNS),
365
+ headerFromHyperSync,
366
+ "headers",
367
+ );
368
+ if (belowReorgWindow(chainId, to, fetched.archiveHeight)) return fetched;
369
+ if (attempt >= HEADER_WAIT_TRIES) {
370
+ throw new Error(
371
+ `Envio Subgraph won't serve the header of block ${to} on chain ${chainId} yet: it is within ` +
372
+ `${headerLag[chainId]} blocks of the head (${fetched.archiveHeight}), where a reorg could replace it.`,
373
+ );
374
+ }
375
+ await sleep(HEADER_WAIT_MS);
376
+ }
377
+ },
378
+ async (_chainId, blockNumber) => {
379
+ if (rpcUrls.length === 0) {
380
+ throw new Error(
381
+ `Envio Subgraph couldn't read the header of block ${blockNumber}: HyperSync didn't\n` +
382
+ `return it, and no RPC fallback is configured. Set ENVIO_SUBGRAPH_RPC in .env or the\n` +
383
+ `environment.`,
384
+ );
385
+ }
386
+ const client = rpcClient(rpcUrls);
387
+ const block = await untilAnswered(
388
+ `block ${blockNumber} from RPC`,
389
+ () => client.getBlock({ blockNumber }),
390
+ { deadline: false },
391
+ );
392
+ return headerFromRpc(block);
393
+ },
394
+ );
395
+
396
+ export function requestBlockHeader(chainId: number, blockNumber: number): Promise<BlockHeader> {
397
+ return headers.request(chainId, blockNumber);
398
+ }