envio 3.10.0-subgraph → 3.11.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 (76) hide show
  1. package/evm.schema.json +21 -4
  2. package/fuel.schema.json +21 -4
  3. package/index.d.ts +1 -1
  4. package/package.json +6 -6
  5. package/src/ChainState.res +9 -73
  6. package/src/ChainState.res.mjs +6 -58
  7. package/src/Config.res +73 -27
  8. package/src/Config.res.mjs +71 -21
  9. package/src/Core.res +0 -15
  10. package/src/Core.res.mjs +0 -11
  11. package/src/EventConfigBuilder.res +14 -10
  12. package/src/EventConfigBuilder.res.mjs +6 -4
  13. package/src/EventProcessing.res +6 -6
  14. package/src/EventProcessing.res.mjs +6 -8
  15. package/src/HandlerLoader.res +8 -20
  16. package/src/HandlerLoader.res.mjs +2 -11
  17. package/src/HandlerRegister.res +12 -11
  18. package/src/HandlerRegister.res.mjs +6 -5
  19. package/src/Internal.res +20 -4
  20. package/src/Internal.res.mjs +9 -0
  21. package/src/Main.res +8 -1
  22. package/src/Main.res.mjs +3 -3
  23. package/src/Persistence.res +11 -1
  24. package/src/Persistence.res.mjs +6 -2
  25. package/src/PgStorage.res +1 -1
  26. package/src/PgStorage.res.mjs +1 -1
  27. package/src/SimulateItems.res +13 -6
  28. package/src/SimulateItems.res.mjs +7 -6
  29. package/src/TestIndexer.res +6 -5
  30. package/src/TestIndexer.res.mjs +5 -4
  31. package/src/UserContext.res +51 -358
  32. package/src/UserContext.res.mjs +35 -271
  33. package/src/db/InternalTable.res +1 -1
  34. package/src/db/InternalTable.res.mjs +2 -1
  35. package/src/sources/ChainSources.res +72 -0
  36. package/src/sources/ChainSources.res.mjs +55 -0
  37. package/src/sources/EvmHyperSyncSource.res +10 -22
  38. package/src/sources/EvmHyperSyncSource.res.mjs +9 -41
  39. package/src/sources/FuelHyperSyncSource.res +10 -18
  40. package/src/sources/FuelHyperSyncSource.res.mjs +8 -38
  41. package/src/sources/HyperSync.res +31 -0
  42. package/src/sources/HyperSync.res.mjs +31 -0
  43. package/src/sources/HyperSync.resi +16 -0
  44. package/src/sources/StartBlockResolver.res +173 -0
  45. package/src/sources/StartBlockResolver.res.mjs +134 -0
  46. package/src/sources/Svm.res +0 -51
  47. package/src/sources/Svm.res.mjs +0 -46
  48. package/src/sources/SvmHyperSyncSource.res +20 -9
  49. package/src/sources/SvmHyperSyncSource.res.mjs +34 -12
  50. package/svm.schema.json +78 -63
  51. package/src/subgraph/blocks.ts +0 -176
  52. package/src/subgraph/calls.ts +0 -217
  53. package/src/subgraph/conformance.ts +0 -102
  54. package/src/subgraph/division.ts +0 -133
  55. package/src/subgraph/errors.ts +0 -90
  56. package/src/subgraph/graph-ts-types/VERSION +0 -2
  57. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +0 -70
  58. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +0 -327
  59. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +0 -233
  60. package/src/subgraph/graph-ts-types/chain/near.d.ts +0 -253
  61. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +0 -32
  62. package/src/subgraph/graph-ts-types/common/collections.d.ts +0 -136
  63. package/src/subgraph/graph-ts-types/common/conversion.d.ts +0 -11
  64. package/src/subgraph/graph-ts-types/common/datasource.d.ts +0 -30
  65. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  66. package/src/subgraph/graph-ts-types/common/json.d.ts +0 -17
  67. package/src/subgraph/graph-ts-types/common/numbers.d.ts +0 -120
  68. package/src/subgraph/graph-ts-types/common/value.d.ts +0 -120
  69. package/src/subgraph/graph-ts-types/common/yaml.d.ts +0 -90
  70. package/src/subgraph/graph-ts-types/global/global.d.ts +0 -194
  71. package/src/subgraph/graph-ts-types/helper-functions.d.ts +0 -22
  72. package/src/subgraph/graph-ts-types/index.d.ts +0 -102
  73. package/src/subgraph/graph-ts.ts +0 -1948
  74. package/src/subgraph/hosts.ts +0 -148
  75. package/src/subgraph/runtime.ts +0 -863
  76. package/src/subgraph/scope.ts +0 -66
@@ -1,33 +1,26 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Source from "./Source.res.mjs";
4
- import * as Logging from "../Logging.res.mjs";
5
4
  import * as HyperSync from "./HyperSync.res.mjs";
6
5
  import * as Performance from "../bindings/Performance.res.mjs";
7
6
  import * as HyperSyncSSE from "./HyperSyncSSE.res.mjs";
8
- import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
9
7
  import * as ErrorHandling from "../ErrorHandling.res.mjs";
10
8
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
11
- import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
12
9
  import * as HyperSyncClient from "./HyperSyncClient.res.mjs";
13
10
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
14
11
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
15
12
 
16
- function isUnauthorizedError(message) {
17
- return message.includes("401 Unauthorized");
18
- }
19
-
20
13
  function make(param) {
21
- let apiToken = param.apiToken;
22
14
  let onEventRegistrations = param.onEventRegistrations;
23
15
  let endpointUrl = param.endpointUrl;
24
16
  let chainId = param.chainId;
25
- let apiToken$1 = apiToken !== undefined ? apiToken : Stdlib_JsError.throwWithMessage(`An Envio API token is required for using HyperSync as a data-source.
26
- Set the ENVIO_API_TOKEN environment variable in your .env file.
27
- Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
17
+ let unauthorizedWarned = {
18
+ contents: false
19
+ };
20
+ let apiToken = HyperSync.requireApiToken(param.apiToken);
28
21
  let client;
29
22
  try {
30
- client = HyperSyncClient.make(endpointUrl, apiToken$1, param.clientTimeoutMillis, HyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations), !param.lowercaseAddresses, param.serializationFormat, param.enableQueryCaching, undefined, undefined, undefined, param.logLevel, param.addressStore);
23
+ client = HyperSyncClient.make(endpointUrl, apiToken, param.clientTimeoutMillis, HyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations), !param.lowercaseAddresses, param.serializationFormat, param.enableQueryCaching, undefined, undefined, undefined, param.logLevel, param.addressStore);
31
24
  } catch (raw_exn) {
32
25
  let exn = Primitive_exceptions.internalToException(raw_exn);
33
26
  client = ErrorHandling.mkLogAndRaise(undefined, "Failed to instantiate the hypersync client, please double check your ABI", exn);
@@ -150,33 +143,9 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
150
143
  let height;
151
144
  try {
152
145
  height = await client.getHeight();
153
- } catch (raw_e) {
154
- let e = Primitive_exceptions.internalToException(raw_e);
155
- if (e.RE_EXN_ID === "JsExn") {
156
- let e$1 = e._1;
157
- let message = Stdlib_JsExn.message(e$1);
158
- if (message !== undefined) {
159
- if (message.includes("401 Unauthorized")) {
160
- Logging.error(`Your ENVIO_API_TOKEN was rejected by HyperSync (401 Unauthorized). The indexer will not be able to fetch events. Update the token and try again using 'envio start' or 'envio dev'. For more info: https://docs.envio.dev/docs/HyperSync/api-tokens`);
161
- await new Promise((param, param$1) => {});
162
- height = 0;
163
- } else {
164
- throw {
165
- RE_EXN_ID: "JsExn",
166
- _1: e$1,
167
- Error: new Error()
168
- };
169
- }
170
- } else {
171
- throw {
172
- RE_EXN_ID: "JsExn",
173
- _1: e$1,
174
- Error: new Error()
175
- };
176
- }
177
- } else {
178
- throw e;
179
- }
146
+ } catch (raw_exn) {
147
+ let exn = Primitive_exceptions.internalToException(raw_exn);
148
+ height = HyperSync.rethrowLoggingUnauthorized(exn, unauthorizedWarned, "HyperSync");
180
149
  }
181
150
  let seconds = Performance.secondsSince(timerRef);
182
151
  return {
@@ -188,12 +157,11 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
188
157
  };
189
158
  },
190
159
  getItemsOrThrow: getItemsOrThrow,
191
- createHeightSubscription: (onHeight, onStatus) => HyperSyncSSE.subscribe(endpointUrl, apiToken$1, onHeight, onStatus)
160
+ createHeightSubscription: (onHeight, onStatus) => HyperSyncSSE.subscribe(endpointUrl, apiToken, onHeight, onStatus)
192
161
  };
193
162
  }
194
163
 
195
164
  export {
196
- isUnauthorizedError,
197
165
  make,
198
166
  }
199
167
  /* Source Not a pure module */
@@ -1,7 +1,5 @@
1
1
  open Source
2
2
 
3
- let isUnauthorizedError = (message: string) => message->String.includes("401 Unauthorized")
4
-
5
3
  type options = {
6
4
  chainId: ChainId.t,
7
5
  endpointUrl: string,
@@ -15,13 +13,11 @@ type options = {
15
13
  let make = ({chainId, endpointUrl, apiToken, onEventRegistrations, addressStore}: options): t => {
16
14
  let name = "HyperFuel"
17
15
 
18
- let apiToken = switch apiToken {
19
- | Some(token) => token
20
- | None =>
21
- JsError.throwWithMessage(`An Envio API token is required for using HyperFuel as a data-source.
22
- Set the ENVIO_API_TOKEN environment variable in your .env file.
23
- Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
24
- }
16
+ // Per source, so one rejected token is reported once rather than on every
17
+ // height retry for the life of the process.
18
+ let unauthorizedWarned = ref(false)
19
+
20
+ let apiToken = apiToken->HyperSync.requireApiToken
25
21
 
26
22
  let client = switch FuelHyperSyncClient.make(
27
23
  {url: endpointUrl, apiToken},
@@ -213,15 +209,11 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
213
209
  getHeightOrThrow: async () => {
214
210
  let timerRef = Performance.now()
215
211
  let height = try await client->FuelHyperSyncClient.getHeight catch {
216
- | JsExn(e) =>
217
- switch e->JsExn.message {
218
- | Some(message) if message->isUnauthorizedError =>
219
- Logging.error(`Your ENVIO_API_TOKEN was rejected by HyperFuel (401 Unauthorized). The indexer will not be able to fetch events. Update the token and try again using 'envio start' or 'envio dev'. For more info: https://docs.envio.dev/docs/HyperSync/api-tokens`)
220
- // Retrying an unauthorized request can never succeed, so block forever
221
- let _ = await Promise.make((_, _) => ())
222
- 0
223
- | _ => throw(JsExn(e))
224
- }
212
+ | exn =>
213
+ exn->HyperSync.rethrowLoggingUnauthorized(
214
+ ~warned=unauthorizedWarned,
215
+ ~product="HyperFuel",
216
+ )
225
217
  }
226
218
  let seconds = timerRef->Performance.secondsSince
227
219
  {height, requestStats: [{method: "getHeight", seconds}]}
@@ -4,7 +4,6 @@ import * as Source from "./Source.res.mjs";
4
4
  import * as Logging from "../Logging.res.mjs";
5
5
  import * as HyperSync from "./HyperSync.res.mjs";
6
6
  import * as Performance from "../bindings/Performance.res.mjs";
7
- import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
8
7
  import * as ErrorHandling from "../ErrorHandling.res.mjs";
9
8
  import * as FuelHyperSync from "./FuelHyperSync.res.mjs";
10
9
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
@@ -12,22 +11,18 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
12
11
  import * as FuelHyperSyncClient from "./FuelHyperSyncClient.res.mjs";
13
12
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
14
13
 
15
- function isUnauthorizedError(message) {
16
- return message.includes("401 Unauthorized");
17
- }
18
-
19
14
  function make(param) {
20
15
  let onEventRegistrations = param.onEventRegistrations;
21
- let apiToken = param.apiToken;
22
16
  let chainId = param.chainId;
23
- let apiToken$1 = apiToken !== undefined ? apiToken : Stdlib_JsError.throwWithMessage(`An Envio API token is required for using HyperFuel as a data-source.
24
- Set the ENVIO_API_TOKEN environment variable in your .env file.
25
- Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
17
+ let unauthorizedWarned = {
18
+ contents: false
19
+ };
20
+ let apiToken = HyperSync.requireApiToken(param.apiToken);
26
21
  let client;
27
22
  try {
28
23
  client = FuelHyperSyncClient.make({
29
24
  url: param.endpointUrl,
30
- apiToken: apiToken$1
25
+ apiToken: apiToken
31
26
  }, FuelHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations), param.addressStore);
32
27
  } catch (raw_exn) {
33
28
  let exn = Primitive_exceptions.internalToException(raw_exn);
@@ -188,33 +183,9 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
188
183
  let height;
189
184
  try {
190
185
  height = await client.getHeight();
191
- } catch (raw_e) {
192
- let e = Primitive_exceptions.internalToException(raw_e);
193
- if (e.RE_EXN_ID === "JsExn") {
194
- let e$1 = e._1;
195
- let message = Stdlib_JsExn.message(e$1);
196
- if (message !== undefined) {
197
- if (message.includes("401 Unauthorized")) {
198
- Logging.error(`Your ENVIO_API_TOKEN was rejected by HyperFuel (401 Unauthorized). The indexer will not be able to fetch events. Update the token and try again using 'envio start' or 'envio dev'. For more info: https://docs.envio.dev/docs/HyperSync/api-tokens`);
199
- await new Promise((param, param$1) => {});
200
- height = 0;
201
- } else {
202
- throw {
203
- RE_EXN_ID: "JsExn",
204
- _1: e$1,
205
- Error: new Error()
206
- };
207
- }
208
- } else {
209
- throw {
210
- RE_EXN_ID: "JsExn",
211
- _1: e$1,
212
- Error: new Error()
213
- };
214
- }
215
- } else {
216
- throw e;
217
- }
186
+ } catch (raw_exn) {
187
+ let exn = Primitive_exceptions.internalToException(raw_exn);
188
+ height = HyperSync.rethrowLoggingUnauthorized(exn, unauthorizedWarned, "HyperFuel");
218
189
  }
219
190
  let seconds = Performance.secondsSince(timerRef);
220
191
  return {
@@ -230,7 +201,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
230
201
  }
231
202
 
232
203
  export {
233
- isUnauthorizedError,
234
204
  make,
235
205
  }
236
206
  /* Source Not a pure module */
@@ -9,6 +9,37 @@ let pollingInterval = 400
9
9
  let rateLimitedPrefix = "RATE_LIMITED:"
10
10
  let behindHeadPrefix = "SOURCE_BEHIND_HEAD:"
11
11
 
12
+ let requireApiToken = apiToken =>
13
+ switch apiToken {
14
+ | Some(token) => token
15
+ | None =>
16
+ JsError.throwWithMessage(`An Envio API token is required for using HyperSync as a data-source.
17
+ Set the ENVIO_API_TOKEN environment variable in your .env file.
18
+ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
19
+ }
20
+
21
+ let isUnauthorizedError = (message: string) => message->String.includes("401 Unauthorized")
22
+
23
+ // Never swallows the failure: the caller's retry ramp has to keep asking,
24
+ // because a token can be replaced without restarting the indexer. All this adds
25
+ // is one loud line the first time a source sees a 401 - saying it on every retry
26
+ // would bury everything else in the log.
27
+ let rethrowLoggingUnauthorized = (exn: exn, ~warned: ref<bool>, ~product: string): 'a => {
28
+ switch exn {
29
+ | JsExn(jsExn) =>
30
+ switch jsExn->JsExn.message {
31
+ | Some(message) if message->isUnauthorizedError =>
32
+ if !warned.contents {
33
+ warned := true
34
+ Logging.error(`Your ENVIO_API_TOKEN was rejected by ${product} (401 Unauthorized). The indexer will not be able to fetch events. Update the token and try again using 'envio start' or 'envio dev'. For more info: https://docs.envio.dev/docs/HyperSync/api-tokens`)
35
+ }
36
+ | _ => ()
37
+ }
38
+ | _ => ()
39
+ }
40
+ throw(exn)
41
+ }
42
+
12
43
  let markerValue = (msg, ~prefix) =>
13
44
  msg->String.slice(~start=prefix->String.length, ~end=msg->String.length)->Int.fromString
14
45
 
@@ -1,17 +1,45 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Source from "./Source.res.mjs";
4
+ import * as Logging from "../Logging.res.mjs";
4
5
  import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
5
6
  import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
6
7
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
7
8
  import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
8
9
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
10
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
11
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
9
12
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
10
13
 
11
14
  let rateLimitedPrefix = "RATE_LIMITED:";
12
15
 
13
16
  let behindHeadPrefix = "SOURCE_BEHIND_HEAD:";
14
17
 
18
+ function requireApiToken(apiToken) {
19
+ if (apiToken !== undefined) {
20
+ return Primitive_option.valFromOption(apiToken);
21
+ } else {
22
+ return Stdlib_JsError.throwWithMessage(`An Envio API token is required for using HyperSync as a data-source.
23
+ Set the ENVIO_API_TOKEN environment variable in your .env file.
24
+ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
25
+ }
26
+ }
27
+
28
+ function isUnauthorizedError(message) {
29
+ return message.includes("401 Unauthorized");
30
+ }
31
+
32
+ function rethrowLoggingUnauthorized(exn, warned, product) {
33
+ if (exn.RE_EXN_ID === "JsExn") {
34
+ let message = Stdlib_JsExn.message(exn._1);
35
+ if (message !== undefined && message.includes("401 Unauthorized") && !warned.contents) {
36
+ warned.contents = true;
37
+ Logging.error(`Your ENVIO_API_TOKEN was rejected by ` + product + ` (401 Unauthorized). The indexer will not be able to fetch events. Update the token and try again using 'envio start' or 'envio dev'. For more info: https://docs.envio.dev/docs/HyperSync/api-tokens`);
38
+ }
39
+ }
40
+ throw exn;
41
+ }
42
+
15
43
  function markerValue(msg, prefix) {
16
44
  return Stdlib_Int.fromString(msg.slice(prefix.length, msg.length), undefined);
17
45
  }
@@ -163,6 +191,9 @@ let pollingInterval = 400;
163
191
 
164
192
  export {
165
193
  pollingInterval,
194
+ requireApiToken,
195
+ isUnauthorizedError,
196
+ rethrowLoggingUnauthorized,
166
197
  mapNativeFailure,
167
198
  mapNativeFailureExn,
168
199
  reraiseIfRecoverable,
@@ -10,6 +10,22 @@ type logsQueryPage = {
10
10
  // one. Every ecosystem's client talks to the same service, so they wait alike.
11
11
  let pollingInterval: int
12
12
 
13
+ // Every HyperSync-backed source needs a token to reach the service, so a
14
+ // missing one fails at construction with the message that says how to get one,
15
+ // rather than as a 401 per request.
16
+ let requireApiToken: option<string> => string
17
+
18
+ // Surfaced by the native clients when the edge rejects the API token. The
19
+ // corrupted-token test feeds the real server error (from the query endpoint;
20
+ // the edge no longer 401s malformed tokens on /height) through this so it can't
21
+ // silently drift away from the message shape a 401 actually produces.
22
+ let isUnauthorizedError: string => bool
23
+
24
+ // Rethrows every failure unchanged, adding one loud line the first time a source
25
+ // sees a 401. Never swallows it: the caller's retry ramp has to keep asking,
26
+ // because a token can be replaced without restarting the indexer.
27
+ let rethrowLoggingUnauthorized: (exn, ~warned: ref<bool>, ~product: string) => 'a
28
+
13
29
  // Map a native client's `PREFIX:<int>` failure marker onto the exception
14
30
  // SourceManager retries on (`RateLimited` / `SourceBehindHead`), falling back to
15
31
  // the original cause.
@@ -0,0 +1,173 @@
1
+ // Resolves a chain's `start_block: latest` to a concrete block number, once,
2
+ // right before the indexer's first-ever persisted state is written (see
3
+ // `Persistence.init`). Never runs on a normal resume (crash recovery, a plain
4
+ // restarted process): `envio_chains.start_block` is written once and read
5
+ // back verbatim from then on, so a resolved "latest" naturally stays fixed
6
+ // across downtime instead of jumping to a new head - any gap gets backfilled
7
+ // rather than skipped. The CLI's `-r` (`--restart`) flag is the exception: it
8
+ // forces `reset=true` in `Persistence.init`, which wipes the DB and runs this
9
+ // again, the same as any other fresh deploy.
10
+
11
+ // Sources built only to read the chain's height. No registrations exist yet at
12
+ // this point in startup - handler files load after persistence initializes -
13
+ // and none are needed to ask a backend how far it has got. These are not the
14
+ // sources the chain goes on to index with; those are built later, once
15
+ // registrations exist.
16
+ let makeProbeSources = (chainConfig: Config.chain, ~lowercaseAddresses): array<Source.t> => {
17
+ let addressStore = AddressStore.make(
18
+ ~ecosystem=chainConfig.ecosystem,
19
+ ~shouldChecksum=!lowercaseAddresses,
20
+ ~contracts=[],
21
+ )
22
+ ChainSources.make(~chainConfig, ~onEventRegistrations=[], ~addressStore, ~lowercaseAddresses)
23
+ }
24
+
25
+ // How long to keep asking a chain that won't answer.
26
+ type retry =
27
+ // The indexer. A chain it can't reach is the operator's to fix, and the
28
+ // process staying up is what gives them the chance to - the same thing it
29
+ // does for a chain that goes unreachable while it runs.
30
+ | UntilItAnswers
31
+ // `envio local db-migrate up/setup`. A one-shot command has nobody waiting to
32
+ // watch it recover, so every source gets one attempt and then it reports what
33
+ // each of them said.
34
+ | Once
35
+
36
+ // Sources that can serve historical sync, primaries first - the same ordering a
37
+ // backfill would use. A realtime-only source is left out: it isn't what this
38
+ // chain reads its history from.
39
+ let candidateSources = (sources: array<Source.t>) => {
40
+ let hasRealtime = sources->Array.some(source => source.sourceFor === Realtime)
41
+ let roleOf = (source: Source.t) =>
42
+ SourceManager.getSourceRole(~sourceFor=source.sourceFor, ~isRealtime=false, ~hasRealtime)
43
+ sources
44
+ ->Array.filter(source => roleOf(source)->Option.isSome)
45
+ ->Array.toSorted((a, b) =>
46
+ switch (roleOf(a), roleOf(b)) {
47
+ | (Some(Primary), Some(Secondary)) => Ordering.less
48
+ | (Some(Secondary), Some(Primary)) => Ordering.greater
49
+ | _ => Ordering.equal
50
+ }
51
+ )
52
+ }
53
+
54
+ // One attempt per source. Trying the next source is not a retry - it's the
55
+ // failover the config asked for - but no source is asked twice.
56
+ let readHeadOnceOrThrow = async (chainConfig: Config.chain, ~sources): int => {
57
+ let candidates = candidateSources(sources)
58
+ if candidates->Utils.Array.isEmpty {
59
+ // The condition `SourceManager.make` rejects on the retrying path. Sharing
60
+ // its wording keeps one misconfiguration from having two explanations - and
61
+ // "no source answered" would be untrue here, since none was asked.
62
+ JsError.throwWithMessage("Invalid configuration, no data-source for historical sync provided")
63
+ }
64
+ let failures = []
65
+ let head = ref(None)
66
+ for i in 0 to candidates->Array.length - 1 {
67
+ if head.contents->Option.isNone {
68
+ let source = candidates->Array.getUnsafe(i)
69
+ switch await source.getHeightOrThrow() {
70
+ | {height} => head := Some(height)
71
+ | exception exn =>
72
+ failures
73
+ ->Array.push(`${source.name}: ${exn->Utils.exnMessage->Option.getOr("unknown error")}`)
74
+ ->ignore
75
+ }
76
+ }
77
+ }
78
+ switch head.contents {
79
+ | Some(head) => head
80
+ | None =>
81
+ JsError.throwWithMessage(
82
+ `Chain ${chainConfig.id->ChainId.toString}: couldn't resolve the "latest" start block - no source answered a height request.${failures
83
+ ->Array.map(failure => `\n ${failure}`)
84
+ ->Array.join("")}`,
85
+ )
86
+ }
87
+ }
88
+
89
+ // Waiting for a height above 0 is the same question as "what is the head", and
90
+ // it comes with the runtime's own answer to a source that won't say: retry with
91
+ // backoff, a bound on how long any one request is waited for, and failover to
92
+ // `for: fallback` sources once the primary has been quiet for a stall window.
93
+ //
94
+ // It never gives up, which is what makes it safe to use here. A deadline would
95
+ // need a race, and the losing side of that race is a registered waiter and a
96
+ // live poll loop that nothing can reach to stop.
97
+ let resolveHeadOrThrow = async (
98
+ chainConfig: Config.chain,
99
+ ~lowercaseAddresses,
100
+ ~retry,
101
+ ~getHeightRetryInterval=?,
102
+ ~newBlockStallTimeout=?,
103
+ ): int => {
104
+ let sources = chainConfig->makeProbeSources(~lowercaseAddresses)
105
+ switch retry {
106
+ | Once => await chainConfig->readHeadOnceOrThrow(~sources)
107
+ | UntilItAnswers =>
108
+ let sourceManager = SourceManager.make(
109
+ ~sources,
110
+ ~isRealtime=false,
111
+ ~getHeightRetryInterval?,
112
+ ~newBlockStallTimeout?,
113
+ )
114
+ await sourceManager->SourceManager.waitForNewBlock(
115
+ ~knownHeight=0,
116
+ ~isRealtime=false,
117
+ ~reducedPolling=false,
118
+ )
119
+ }
120
+ }
121
+
122
+ // Sequential rather than `Promise.all`: a chain that fails validation must not
123
+ // leave sibling chains' height polling running behind the rejection.
124
+ let resolveAllOrThrow = async (
125
+ chainConfigs: array<Config.chain>,
126
+ ~lowercaseAddresses,
127
+ ~retry=UntilItAnswers,
128
+ ~getHeightRetryInterval=?,
129
+ ~newBlockStallTimeout=?,
130
+ ): array<Config.chain> => {
131
+ let resolved = []
132
+ for i in 0 to chainConfigs->Array.length - 1 {
133
+ let chainConfig = chainConfigs->Array.getUnsafe(i)
134
+ let chainConfig = switch chainConfig.startBlock {
135
+ | Config.Block(_) => chainConfig
136
+ | Config.Latest =>
137
+ let head = await chainConfig->resolveHeadOrThrow(
138
+ ~lowercaseAddresses,
139
+ ~retry,
140
+ ~getHeightRetryInterval?,
141
+ ~newBlockStallTimeout?,
142
+ )
143
+ let chainId = chainConfig.id->ChainId.toString
144
+ switch chainConfig.endBlock {
145
+ | Some(endBlock) if head > endBlock =>
146
+ JsError.throwWithMessage(
147
+ `Chain ${chainId}: the "latest" start block resolved to ${head->Int.toString}, which is past the configured end_block (${endBlock->Int.toString}). There is nothing to index - remove end_block, raise it above the chain's current head, or pin start_block to a fixed value instead of "latest".`,
148
+ )
149
+ | _ => ()
150
+ }
151
+ // Checked here, before anything is persisted: the same guard in
152
+ // `ChainState.makeInternal` would only fire after the resolved head is
153
+ // written to envio_chains, and then again on every resume.
154
+ chainConfig.contracts->Array.forEach(contract =>
155
+ switch contract.startBlock {
156
+ | Some(contractStartBlock) if contractStartBlock < head =>
157
+ JsError.throwWithMessage(
158
+ `Chain ${chainId}: contract "${contract.name}" has start_block ${contractStartBlock->Int.toString}, but the chain's "latest" start block resolved to ${head->Int.toString}. A contract can't start before its chain does - remove the contract's start_block, or pin the chain's start_block to a fixed value instead of "latest".`,
159
+ )
160
+ | _ => ()
161
+ }
162
+ )
163
+ Logging.info({
164
+ "msg": `Resolved the "latest" start block for chain ${chainId} to block ${head->Int.toString}.`,
165
+ "chainId": chainConfig.id,
166
+ "startBlock": head,
167
+ })
168
+ {...chainConfig, startBlock: Config.Block(head)}
169
+ }
170
+ resolved->Array.push(chainConfig)->ignore
171
+ }
172
+ resolved
173
+ }
@@ -0,0 +1,134 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Utils from "../Utils.res.mjs";
4
+ import * as ChainId from "../ChainId.res.mjs";
5
+ import * as Logging from "../Logging.res.mjs";
6
+ import * as AddressStore from "./AddressStore.res.mjs";
7
+ import * as ChainSources from "./ChainSources.res.mjs";
8
+ import * as SourceManager from "./SourceManager.res.mjs";
9
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
10
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
11
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
12
+
13
+ function makeProbeSources(chainConfig, lowercaseAddresses) {
14
+ let addressStore = AddressStore.make(chainConfig.ecosystem, !lowercaseAddresses, []);
15
+ return ChainSources.make(chainConfig, [], addressStore, lowercaseAddresses);
16
+ }
17
+
18
+ function candidateSources(sources) {
19
+ let hasRealtime = sources.some(source => source.sourceFor === "Realtime");
20
+ let roleOf = source => SourceManager.getSourceRole(source.sourceFor, false, hasRealtime);
21
+ return sources.filter(source => Stdlib_Option.isSome(roleOf(source))).toSorted((a, b) => {
22
+ let match = roleOf(a);
23
+ let match$1 = roleOf(b);
24
+ if (match !== undefined) {
25
+ if (match === "Primary") {
26
+ if (match$1 !== undefined) {
27
+ if (match$1 === "Primary") {
28
+ return 0;
29
+ } else {
30
+ return -1;
31
+ }
32
+ } else {
33
+ return 0;
34
+ }
35
+ } else if (match$1 !== undefined) {
36
+ if (match$1 === "Primary") {
37
+ return 1;
38
+ } else {
39
+ return 0;
40
+ }
41
+ } else {
42
+ return 0;
43
+ }
44
+ } else {
45
+ return 0;
46
+ }
47
+ });
48
+ }
49
+
50
+ async function readHeadOnceOrThrow(chainConfig, sources) {
51
+ let candidates = candidateSources(sources);
52
+ if (Utils.$$Array.isEmpty(candidates)) {
53
+ Stdlib_JsError.throwWithMessage("Invalid configuration, no data-source for historical sync provided");
54
+ }
55
+ let failures = [];
56
+ let head;
57
+ for (let i = 0, i_finish = candidates.length; i < i_finish; ++i) {
58
+ if (Stdlib_Option.isNone(head)) {
59
+ let source = candidates[i];
60
+ let exit = 0;
61
+ let val;
62
+ try {
63
+ val = await source.getHeightOrThrow();
64
+ exit = 1;
65
+ } catch (raw_exn) {
66
+ let exn = Primitive_exceptions.internalToException(raw_exn);
67
+ failures.push(source.name + `: ` + Stdlib_Option.getOr(Utils.exnMessage(exn), "unknown error"));
68
+ }
69
+ if (exit === 1) {
70
+ head = val.height;
71
+ }
72
+ }
73
+ }
74
+ let head$1 = head;
75
+ if (head$1 !== undefined) {
76
+ return head$1;
77
+ } else {
78
+ return Stdlib_JsError.throwWithMessage(`Chain ` + ChainId.toString(chainConfig.id) + `: couldn't resolve the "latest" start block - no source answered a height request.` + failures.map(failure => `\n ` + failure).join(""));
79
+ }
80
+ }
81
+
82
+ async function resolveHeadOrThrow(chainConfig, lowercaseAddresses, retry, getHeightRetryInterval, newBlockStallTimeout) {
83
+ let sources = makeProbeSources(chainConfig, lowercaseAddresses);
84
+ if (retry !== "UntilItAnswers") {
85
+ return await readHeadOnceOrThrow(chainConfig, sources);
86
+ }
87
+ let sourceManager = SourceManager.make(sources, false, newBlockStallTimeout, undefined, undefined, undefined, undefined, getHeightRetryInterval);
88
+ return await SourceManager.waitForNewBlock(sourceManager, 0, false, false);
89
+ }
90
+
91
+ async function resolveAllOrThrow(chainConfigs, lowercaseAddresses, retryOpt, getHeightRetryInterval, newBlockStallTimeout) {
92
+ let retry = retryOpt !== undefined ? retryOpt : "UntilItAnswers";
93
+ let resolved = [];
94
+ for (let i = 0, i_finish = chainConfigs.length; i < i_finish; ++i) {
95
+ let chainConfig = chainConfigs[i];
96
+ let match = chainConfig.startBlock;
97
+ let chainConfig$1;
98
+ if (match === "latest") {
99
+ let head = await resolveHeadOrThrow(chainConfig, lowercaseAddresses, retry, getHeightRetryInterval, newBlockStallTimeout);
100
+ let chainId = ChainId.toString(chainConfig.id);
101
+ let endBlock = chainConfig.endBlock;
102
+ if (endBlock !== undefined && head > endBlock) {
103
+ Stdlib_JsError.throwWithMessage(`Chain ` + chainId + `: the "latest" start block resolved to ` + head.toString() + `, which is past the configured end_block (` + endBlock.toString() + `). There is nothing to index - remove end_block, raise it above the chain's current head, or pin start_block to a fixed value instead of "latest".`);
104
+ }
105
+ chainConfig.contracts.forEach(contract => {
106
+ let contractStartBlock = contract.startBlock;
107
+ if (contractStartBlock !== undefined && contractStartBlock < head) {
108
+ return Stdlib_JsError.throwWithMessage(`Chain ` + chainId + `: contract "` + contract.name + `" has start_block ` + contractStartBlock.toString() + `, but the chain's "latest" start block resolved to ` + head.toString() + `. A contract can't start before its chain does - remove the contract's start_block, or pin the chain's start_block to a fixed value instead of "latest".`);
109
+ }
110
+ });
111
+ Logging.info({
112
+ msg: `Resolved the "latest" start block for chain ` + chainId + ` to block ` + head.toString() + `.`,
113
+ chainId: chainConfig.id,
114
+ startBlock: head
115
+ });
116
+ let newrecord = {...chainConfig};
117
+ newrecord.startBlock = head;
118
+ chainConfig$1 = newrecord;
119
+ } else {
120
+ chainConfig$1 = chainConfig;
121
+ }
122
+ resolved.push(chainConfig$1);
123
+ }
124
+ return resolved;
125
+ }
126
+
127
+ export {
128
+ makeProbeSources,
129
+ candidateSources,
130
+ readHeadOnceOrThrow,
131
+ resolveHeadOrThrow,
132
+ resolveAllOrThrow,
133
+ }
134
+ /* Utils Not a pure module */