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
@@ -1,554 +0,0 @@
1
- /**
2
- * The graph-node host ops that reach outside the chain data envio already has:
3
- * IPFS, Arweave, ENS, `eth_getBalance`/`eth_getCode`, and a block handler's
4
- * timestamp. Each is an envio effect, so preload batches and dedupes them and
5
- * the sync bridge suspends the mapping until the answer lands.
6
- */
7
-
8
- import { gate, rpcClient } from "./rpc.ts";
9
- import * as Sury from "rescript-schema";
10
- import { createEffect } from "../Envio.res.mjs";
11
- import { configureBlockTimestamps, requestBlockHeader, requestBlockTimestamp } from "./blocks.ts";
12
- import { configureReceiptLogs, requestReceiptLogs } from "./receipts.ts";
13
- import { missingRpcMessage } from "./errors.ts";
14
- import { HttpStatusError, isTransient, untilAnswered, withDeadline } from "./transient.ts";
15
- import { warn } from "./log.ts";
16
-
17
- /**
18
- * Public IPFS gateways, in the order they are asked. The Graph's own node holds
19
- * what subgraphs pin and doesn't rate-limit a backfill the way ipfs.io does
20
- * (which answers 429 to a box that has made a handful of requests); the others
21
- * take over when it is down, rate-limited or doesn't have the file.
22
- */
23
- const IPFS_GATEWAYS = ["https://ipfs.thegraph.com/ipfs/", "https://ipfs.io/ipfs/", "https://gateway.pinata.cloud/ipfs/"];
24
- const IPFS_GATEWAY_ENV = "ENVIO_SUBGRAPH_IPFS_URL";
25
- const ARWEAVE_GATEWAYS = ["https://arweave.net/"];
26
- const ARWEAVE_GATEWAY_ENV = "ENVIO_SUBGRAPH_ARWEAVE_URL";
27
- /** Fetches of a file data source's file in flight at once. */
28
- const FILE_IN_FLIGHT = 16;
29
-
30
- function gatewayList(env: string, fallback: string[], suffix: string): string[] {
31
- const configured = (process.env[env] ?? "")
32
- .split(/[\s,]+/)
33
- .map((url) => url.trim())
34
- .filter((url) => url !== "");
35
- if (configured.length === 0) return fallback;
36
- return configured.map((url) => {
37
- const base = url.replace(/\/+$/, "");
38
- return suffix && !base.endsWith(suffix) ? `${base}/${suffix}/` : `${base}/`;
39
- });
40
- }
41
-
42
- /**
43
- * The gateways a CID is appended to, read per fetch. `ENVIO_SUBGRAPH_IPFS_URL`
44
- * names one or several (comma separated), each with or without its `/ipfs/`
45
- * path.
46
- */
47
- export function ipfsGatewayUrls(): string[] {
48
- return gatewayList(IPFS_GATEWAY_ENV, IPFS_GATEWAYS, "ipfs");
49
- }
50
-
51
- export function arweaveGatewayUrls(): string[] {
52
- return gatewayList(ARWEAVE_GATEWAY_ENV, ARWEAVE_GATEWAYS, "");
53
- }
54
-
55
- /**
56
- * graph-node's `ContentPath::new` (graph/src/ipfs/content_path.rs): a CID and
57
- * an optional path under it, as `<CID>[/<path>]`, `/ipfs/<CID>…`,
58
- * `ipfs://<CID>…`, or an http(s) URL with `/ipfs/<CID>…` in its path or the CID
59
- * in its `arg` query parameter. `null` for anything it refuses to parse, which
60
- * `ipfs.cat` answers with null and a file data source is ignored for.
61
- */
62
- export function normalizeIpfsPath(raw: string): string | null {
63
- const input = raw.trim();
64
- if (input === "") return null;
65
- if (/^https?:\/\//.test(input)) {
66
- let url: URL;
67
- try {
68
- url = new URL(input);
69
- } catch {
70
- return null;
71
- }
72
- const arg = url.searchParams.get("arg");
73
- if (arg !== null) return cidAndPath(arg);
74
- const at = url.pathname.indexOf("/ipfs/");
75
- return cidAndPath(at >= 0 ? url.pathname.slice(at + "/ipfs/".length) : url.pathname);
76
- }
77
- return cidAndPath(input);
78
- }
79
-
80
- function cidAndPath(raw: string): string | null {
81
- let input = raw.replace(/^\/+|\/+$/g, "");
82
- for (const prefix of ["ipfs/", "ipfs://"]) {
83
- if (input.startsWith(prefix)) input = input.slice(prefix.length);
84
- }
85
- const slash = input.indexOf("/");
86
- const root = slash < 0 ? input : input.slice(0, slash);
87
- if (slash >= 0 && input.includes("?", slash)) return null;
88
- // CIDv0 is 46 base58 characters; CIDv1 is multibase-prefixed, most often
89
- // base32 (`b`) or base36 (`k`).
90
- const v0 = /^Qm[1-9A-HJ-NP-Za-km-z]{44}$/;
91
- const v1 = /^(b[a-z2-7]{20,}|k[a-z0-9]{20,}|z[1-9A-HJ-NP-Za-km-z]{20,}|f[0-9a-f]{20,})$/;
92
- return v0.test(root) || v1.test(root) ? input.replace(/\/+$/, "") : null;
93
- }
94
- /** graph-node heals a hash from its rainbow table; this is the public one. */
95
- const ENS_RAINBOW = "https://api.ensrainbow.io/v1/heal/";
96
- const ENS_RAINBOW_ENV = "ENVIO_SUBGRAPH_ENS_RAINBOW_URL";
97
- /** Lookups in flight at once: a registry backfill asks a million of them. */
98
- const ENS_IN_FLIGHT = 32;
99
- /** Attempts before a failing ENSRainbow is taken for down. */
100
- const ENS_TRIES = 5;
101
- /** How long a down ENSRainbow is answered for without being asked. */
102
- const ENS_DOWN_MS = 5 * 60_000;
103
-
104
- /** The heal endpoint a hash is appended to; `null` when lookups are turned off. */
105
- function ensRainbowUrl(): string | null {
106
- const configured = process.env[ENS_RAINBOW_ENV]?.trim();
107
- if (configured === undefined) return ENS_RAINBOW;
108
- if (configured === "" || configured === "off") return null;
109
- return configured.endsWith("/") ? configured : `${configured}/`;
110
- }
111
-
112
- /** ENSRainbow's own "not in my table", as opposed to a host answering 404 for an app it no longer runs. */
113
- async function isLabelNotFound(response: Response): Promise<boolean> {
114
- try {
115
- const body = (await response.json()) as { errorCode?: unknown; error?: unknown };
116
- return body.errorCode === 404 || typeof body.error === "string";
117
- } catch {
118
- return false;
119
- }
120
- }
121
-
122
- class RainbowDown extends Error {}
123
-
124
- /**
125
- * A failure no retry can mend: the host's certificate isn't its own (Railway
126
- * serves api.ensrainbow.io's name with its own certificate), or it doesn't
127
- * resolve at all.
128
- */
129
- const PERMANENT_FETCH_CODES = new Set([
130
- "ERR_TLS_CERT_ALTNAME_INVALID",
131
- "CERT_HAS_EXPIRED",
132
- "DEPTH_ZERO_SELF_SIGNED_CERT",
133
- "SELF_SIGNED_CERT_IN_CHAIN",
134
- "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
135
- "ENOTFOUND",
136
- ]);
137
-
138
- async function fetchRainbow(url: string, signal: AbortSignal): Promise<Response> {
139
- try {
140
- return await fetch(url, { signal });
141
- } catch (error) {
142
- const code = (error as { cause?: { code?: unknown } })?.cause?.code;
143
- if (typeof code === "string" && PERMANENT_FETCH_CODES.has(code)) {
144
- const detail = (error as { cause?: { message?: unknown } }).cause?.message;
145
- throw new RainbowDown(`${code}: ${String(detail ?? "")}`.trim());
146
- }
147
- throw error;
148
- }
149
- }
150
-
151
- const nullableString = Sury.union([Sury.string, null]);
152
-
153
- /**
154
- * One try at one URL: the bytes, `null` for the gateway's 404, a throw for
155
- * anything else (a timeout, a 5xx, a rate limit).
156
- */
157
- async function fetchOnce(url: string, signal: AbortSignal): Promise<string | null> {
158
- const response = await fetch(url, { signal });
159
- if (response.status === 404) return null;
160
- if (!response.ok) throw new HttpStatusError(url, response.status, response.statusText);
161
- return Buffer.from(new Uint8Array(await response.arrayBuffer())).toString("base64");
162
- }
163
-
164
- /**
165
- * The file at `path` under each gateway in turn: the first to have it answers.
166
- * Only "none of them has it" is an answer, and a cached one; a timeout, a 5xx or
167
- * a rate limit at any gateway that didn't have it is asked again rather than
168
- * cached as a null for good. With `budgetMs`, the last failure is thrown once
169
- * asking again would end past that long after the first round.
170
- */
171
- function fetchBase64(gateways: string[], path: string, budgetMs = Infinity): Promise<string | null> {
172
- return untilAnswered(`the fetch of ${path}`, () => firstGatewayAnswer(gateways, path), {
173
- deadline: false,
174
- budgetMs,
175
- });
176
- }
177
-
178
- /** How long a gateway has to answer before the next one is asked as well. */
179
- const HEDGE_MS = 2_000;
180
-
181
- /**
182
- * The gateways in order, each asked once the one before it has failed, has
183
- * no such file, or has kept silent for `HEDGE_MS`: a gateway that hangs to
184
- * its deadline holds the file up by `HEDGE_MS` rather than by the deadline.
185
- * The first to hand the file over answers and the others are cancelled; a
186
- * file is missing only if every gateway says so.
187
- */
188
- function firstGatewayAnswer(gateways: string[], path: string): Promise<string | null> {
189
- if (gateways.length === 0) return Promise.resolve(null);
190
- return new Promise((resolve, reject) => {
191
- const cancels: AbortController[] = [];
192
- let hedge: ReturnType<typeof setTimeout> | undefined;
193
- let started = 0;
194
- let finished = 0;
195
- let failure: unknown;
196
- let settled = false;
197
- const settle = (finish: () => void) => {
198
- if (settled) return;
199
- settled = true;
200
- clearTimeout(hedge);
201
- for (const cancel of cancels) cancel.abort();
202
- finish();
203
- };
204
- const next = () => {
205
- clearTimeout(hedge);
206
- if (settled || started >= gateways.length) return;
207
- const url = gateways[started++] + path;
208
- const cancel = new AbortController();
209
- cancels.push(cancel);
210
- withDeadline(`the fetch of ${url}`, (signal) => fetchOnce(url, AbortSignal.any([signal, cancel.signal])))
211
- .then(
212
- (content) => {
213
- if (content !== null) settle(() => resolve(content));
214
- },
215
- (error) => {
216
- failure = error;
217
- },
218
- )
219
- .finally(() => {
220
- finished++;
221
- if (finished === gateways.length) {
222
- settle(() => (failure === undefined ? resolve(null) : reject(failure)));
223
- } else if (finished === started) {
224
- next();
225
- }
226
- });
227
- if (started < gateways.length) hedge = setTimeout(next, HEDGE_MS);
228
- };
229
- next();
230
- });
231
- }
232
-
233
- /** graph-node's `GRAPH_IPFS_TIMEOUT`, 60 s by default (graph/src/env/mappings.rs). */
234
- function ipfsTimeoutMs(): number {
235
- const value = Number(process.env.ENVIO_SUBGRAPH_IPFS_TIMEOUT_MS);
236
- return Number.isFinite(value) && value > 0 ? value : 60_000;
237
- }
238
-
239
- /** At most `limit` of `task`s run at once; the rest queue. */
240
- function limiter(limit: number) {
241
- let running = 0;
242
- const waiting: (() => void)[] = [];
243
- return async <T>(task: () => Promise<T>): Promise<T> => {
244
- if (running < limit) running++;
245
- else await new Promise<void>((resolve) => waiting.push(resolve));
246
- try {
247
- return await task();
248
- } finally {
249
- const next = waiting.shift();
250
- if (next) next();
251
- else running--;
252
- }
253
- };
254
- }
255
-
256
- /** Tries before a file data source's file is given up on. */
257
- function fileAttempts(): number {
258
- const value = Number(process.env.ENVIO_SUBGRAPH_FILE_ATTEMPTS);
259
- return Number.isInteger(value) && value > 0 ? value : 4;
260
- }
261
-
262
- function fileRetryMs(): number {
263
- const value = Number(process.env.ENVIO_SUBGRAPH_FILE_RETRY_MS);
264
- return Number.isFinite(value) && value >= 0 ? value : 2_000;
265
- }
266
-
267
- export type HostEffects = ReturnType<typeof makeHostEffects>;
268
-
269
- export function makeHostEffects(
270
- rpcUrls: string[],
271
- headerLag: Record<number, number> = {},
272
- rpcOnlyChains: number[] = [],
273
- ) {
274
- configureBlockTimestamps(rpcUrls, headerLag, rpcOnlyChains);
275
- configureReceiptLogs(rpcUrls, rpcOnlyChains);
276
-
277
- const clientOrThrow = (callSite: string) => {
278
- if (rpcUrls.length === 0) {
279
- throw new Error(missingRpcMessage(callSite));
280
- }
281
- return rpcClient(rpcUrls);
282
- };
283
-
284
- // graph-node's `ipfs.cat` returns null for a file no gateway handed over
285
- // within its timeout ("Failed ipfs.cat, returning `null`"); that null isn't
286
- // cached, so a later ask fetches anew.
287
- const ipfsCat = createEffect(
288
- {
289
- name: "envio_subgraph_ipfs_cat",
290
- input: Sury.string,
291
- output: nullableString,
292
- rateLimit: false,
293
- cache: true,
294
- },
295
- async ({ input, context }: { input: string; context: { cache: boolean } }) => {
296
- const budget = ipfsTimeoutMs();
297
- try {
298
- return await fetchBase64(ipfsGatewayUrls(), input, budget);
299
- } catch (error) {
300
- if (!isTransient(error)) throw error;
301
- context.cache = false;
302
- const reason = error instanceof Error ? error.message.split("\n")[0] : String(error);
303
- warn(
304
- `Envio Subgraph: ipfs.cat(${input}) got no answer within ${budget}ms (${reason}), so it returns null, ` +
305
- "as graph-node's does after GRAPH_IPFS_TIMEOUT. The null isn't cached; ENVIO_SUBGRAPH_IPFS_TIMEOUT_MS " +
306
- "sets the wait, ENVIO_SUBGRAPH_IPFS_URL the gateways.",
307
- );
308
- return null;
309
- }
310
- },
311
- );
312
-
313
- // `ipfs.map` fails its handler where `ipfs.cat` would answer null, and
314
- // graph-node retries a block that failed that way, so it waits for the file.
315
- const ipfsMapFile = createEffect(
316
- {
317
- name: "envio_subgraph_ipfs_map",
318
- input: Sury.string,
319
- output: nullableString,
320
- rateLimit: false,
321
- cache: true,
322
- },
323
- async ({ input }: { input: string }) => fetchBase64(ipfsGatewayUrls(), input),
324
- );
325
-
326
- const arweaveData = createEffect(
327
- {
328
- name: "envio_subgraph_arweave",
329
- input: Sury.string,
330
- output: nullableString,
331
- rateLimit: false,
332
- cache: true,
333
- },
334
- async ({ input }: { input: string }) => fetchBase64(arweaveGatewayUrls(), input),
335
- );
336
-
337
- // A file data source's file: `ipfs:<path>` or `arweave:<id>`. graph-node polls
338
- // for it until it exists, backing off after a miss and after an error alike
339
- // (core/src/polling_monitor/mod.rs). Here a file every gateway says isn't
340
- // there is asked for `fileAttempts()` times and then reads null, cached like
341
- // any answer, and the handler is skipped. A failure asking again can mend (a
342
- // 429, a 5xx, a timeout) is no such answer: it is asked again, with backoff,
343
- // for the `ipfs.cat` budget, and a file still out of reach after that reads
344
- // null uncached, so a later run (or the same file created again) asks anew.
345
- const fileGate = limiter(FILE_IN_FLIGHT);
346
- const fileData = createEffect(
347
- {
348
- name: "envio_subgraph_file",
349
- input: Sury.string,
350
- output: nullableString,
351
- rateLimit: false,
352
- cache: true,
353
- },
354
- async ({ input, context }: { input: string; context: { cache: boolean } }) => {
355
- const split = input.indexOf(":");
356
- const kind = input.slice(0, split);
357
- const id = input.slice(split + 1);
358
- const gateways = kind === "arweave" ? arweaveGatewayUrls() : ipfsGatewayUrls();
359
- const url = gateways[0] + id;
360
- const attempts = fileAttempts();
361
- const budget = ipfsTimeoutMs();
362
- const started = Date.now();
363
- let unreachable = false;
364
- for (let attempt = 1; attempt <= attempts; attempt++) {
365
- try {
366
- const content = await untilAnswered(`the fetch of ${url}`, () => fileGate(() => firstGatewayAnswer(gateways, id)), {
367
- deadline: false,
368
- budgetMs: Math.max(0, budget - (Date.now() - started)),
369
- });
370
- if (content !== null) return content;
371
- unreachable = false;
372
- } catch (error) {
373
- const reason = error instanceof Error ? error.message.split("\n")[0] : String(error);
374
- if (isTransient(error)) {
375
- warn(
376
- `Envio Subgraph: the file data source's file ${url} couldn't be fetched within ${budget}ms (${reason}); ` +
377
- "its handler is skipped. The miss isn't cached; ENVIO_SUBGRAPH_IPFS_TIMEOUT_MS sets the wait.",
378
- );
379
- context.cache = false;
380
- return null;
381
- }
382
- unreachable = true;
383
- warn(`Envio Subgraph: file ${url} failed (${reason}).`);
384
- }
385
- if (attempt < attempts) await new Promise((resolve) => setTimeout(resolve, fileRetryMs() * 2 ** (attempt - 1)));
386
- }
387
- warn(
388
- `Envio Subgraph: the file data source's file ${url} wasn't there after ${attempts} attempts; ` +
389
- "its handler is skipped. (graph-node keeps polling for it; set ENVIO_SUBGRAPH_FILE_ATTEMPTS to wait longer.)",
390
- );
391
- if (unreachable) context.cache = false;
392
- return null;
393
- },
394
- );
395
-
396
- // graph-node returns null when its rainbow table doesn't hold the hash;
397
- // ENSRainbow answers that as a 404, and a hash that isn't one as a 400.
398
- // With no table loaded, graph-node fails the subgraph ("Missing ENS data")
399
- // and retries forever. A service that keeps failing — no hosted ENSRainbow
400
- // has stayed up — answers null instead, said once and not cached, so the run
401
- // goes on with the names a table would have healed left null.
402
- const ensGate = gate(ENS_IN_FLIGHT);
403
- // Keyed by endpoint: the env var is read per lookup.
404
- const ensDownUntil = new Map<string, number>();
405
- const ensWarned = new Set<string>();
406
- const ensDown = (url: string, reason: string) => {
407
- ensDownUntil.set(url, Date.now() + ENS_DOWN_MS);
408
- if (ensWarned.has(url)) return;
409
- ensWarned.add(url);
410
- warn(
411
- `Envio Subgraph: ENSRainbow at ${url} isn't answering (${reason}), so ens.nameByHash() ` +
412
- "returns null (graph-node without a rainbow table stops at this lookup instead), and asks again in " +
413
- `${ENS_DOWN_MS / 60_000} minutes. Those nulls aren't cached. Point ${ENS_RAINBOW_ENV} ` +
414
- "at a working ENSRainbow heal endpoint (…/v1/heal/), or set it to \"off\" to skip lookups.",
415
- );
416
- };
417
- const ensName = createEffect(
418
- {
419
- name: "envio_subgraph_ens_name",
420
- input: Sury.string,
421
- output: nullableString,
422
- rateLimit: false,
423
- cache: true,
424
- },
425
- async ({ input, context }: { input: string; context: { cache: boolean } }) => {
426
- const base = ensRainbowUrl();
427
- if (base === null || Date.now() < (ensDownUntil.get(base) ?? 0)) {
428
- context.cache = false;
429
- return null;
430
- }
431
- const url = base + input;
432
- try {
433
- return await untilAnswered(
434
- `the lookup of ${url}`,
435
- (signal) =>
436
- ensGate(async () => {
437
- // Lookups already backing off when another found it down stop here.
438
- if (Date.now() < (ensDownUntil.get(base) ?? 0)) throw new RainbowDown("already found down");
439
- const response = await fetchRainbow(url, signal);
440
- if (response.status === 400) return null;
441
- if (response.status === 404) {
442
- if (await isLabelNotFound(response)) return null;
443
- throw new RainbowDown(`${url} answered 404 without ENSRainbow's "Label not found"`);
444
- }
445
- if (!response.ok) {
446
- throw new HttpStatusError(url, response.status, response.statusText);
447
- }
448
- const body = (await response.json()) as { label?: string };
449
- return body.label ?? null;
450
- }),
451
- { maxTries: ENS_TRIES, deadline: false },
452
- );
453
- } catch (error) {
454
- const reason = error instanceof Error ? error.message.split("\n")[0] : String(error);
455
- ensDown(base, reason);
456
- context.cache = false;
457
- return null;
458
- }
459
- },
460
- );
461
-
462
- const getBalance = createEffect(
463
- {
464
- name: "envio_subgraph_eth_balance",
465
- input: Sury.string,
466
- output: Sury.string,
467
- rateLimit: false,
468
- cache: true,
469
- crossChain: false,
470
- },
471
- async ({ input }: { input: string }) => {
472
- const { address, blockNumber } = JSON.parse(input);
473
- const client = clientOrThrow("ethereum.getBalance");
474
- const balance = await untilAnswered(
475
- `the balance of ${address} at block ${blockNumber}`,
476
- () => client.getBalance({ address, blockNumber }),
477
- { deadline: false },
478
- );
479
- return balance.toString();
480
- },
481
- );
482
-
483
- const hasCode = createEffect(
484
- {
485
- name: "envio_subgraph_has_code",
486
- input: Sury.string,
487
- output: Sury.boolean,
488
- rateLimit: false,
489
- cache: true,
490
- crossChain: false,
491
- },
492
- async ({ input }: { input: string }) => {
493
- const { address, blockNumber } = JSON.parse(input);
494
- const client = clientOrThrow("ethereum.hasCode");
495
- const code = await untilAnswered(
496
- `the code of ${address} at block ${blockNumber}`,
497
- () => client.getCode({ address, blockNumber }),
498
- { deadline: false },
499
- );
500
- return code !== "0x";
501
- },
502
- );
503
-
504
- // Uncached on purpose: a block's timestamp is read exactly once, by that
505
- // block's own handler invocation, so a persisted row per indexed block would
506
- // be bloat with no reuse. The in-memory memo still covers replay rounds and
507
- // the preload -> execute transition.
508
- const blockTimestamp = createEffect(
509
- {
510
- name: "envio_subgraph_block_timestamp",
511
- input: Sury.number,
512
- output: Sury.string,
513
- rateLimit: false,
514
- cache: false,
515
- crossChain: false,
516
- },
517
- async ({ input, context }: { input: number; context: { chain: { id: number } } }) => {
518
- const timestamp = await requestBlockTimestamp(context.chain.id, input);
519
- return timestamp.toString();
520
- },
521
- );
522
-
523
- // A block handler's `block.hash` and the rest of the header, as one JSON
524
- // object. Uncached for the same reason as the timestamp.
525
- const blockHeader = createEffect(
526
- {
527
- name: "envio_subgraph_block_header",
528
- input: Sury.number,
529
- output: Sury.string,
530
- rateLimit: false,
531
- cache: false,
532
- crossChain: false,
533
- },
534
- async ({ input, context }: { input: number; context: { chain: { id: number } } }) =>
535
- JSON.stringify(await requestBlockHeader(context.chain.id, input)),
536
- );
537
-
538
- // `event.receipt.logs`: the logs of one transaction, as a JSON array. Input
539
- // is `{ blockNumber, transactionIndex, transactionHash }`.
540
- const receiptLogs = createEffect(
541
- {
542
- name: "envio_subgraph_receipt_logs",
543
- input: Sury.string,
544
- output: Sury.string,
545
- rateLimit: false,
546
- cache: false,
547
- crossChain: false,
548
- },
549
- async ({ input, context }: { input: string; context: { chain: { id: number } } }) =>
550
- JSON.stringify(await requestReceiptLogs({ ...JSON.parse(input), chainId: context.chain.id })),
551
- );
552
-
553
- return { ipfsCat, ipfsMapFile, arweaveData, fileData, ensName, getBalance, hasCode, blockTimestamp, blockHeader, receiptLogs };
554
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * The logger the subgraph runtime writes its own lines through: the one the
3
- * indexer hands it at registration. A line logged before that (codegen run
4
- * ahead of a start) goes to stderr.
5
- */
6
-
7
- import { info as loggerInfo, warn as loggerWarn } from "../Logger.res.mjs";
8
- import { notify } from "../SubgraphLog.res.mjs";
9
-
10
- export { notify };
11
-
12
- let logger: unknown;
13
-
14
- export function setLogger(next: unknown): void {
15
- logger = next;
16
- }
17
-
18
- export function info(message: string): void {
19
- notify("info", message);
20
- if (logger) loggerInfo(logger, message, undefined);
21
- else process.stderr.write(`${message}\n`);
22
- }
23
-
24
- export function warn(message: string): void {
25
- notify("warn", message);
26
- if (logger) loggerWarn(logger, message, undefined);
27
- else process.stderr.write(`${message}\n`);
28
- }