envio 3.3.0-alpha.4 → 3.3.0-alpha.6

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 (55) hide show
  1. package/evm.schema.json +10 -0
  2. package/package.json +7 -7
  3. package/src/Batch.res.mjs +1 -1
  4. package/src/BatchProcessing.res +5 -0
  5. package/src/BatchProcessing.res.mjs +6 -0
  6. package/src/ChainState.res +41 -7
  7. package/src/ChainState.res.mjs +22 -18
  8. package/src/ChainState.resi +3 -1
  9. package/src/Config.res +3 -0
  10. package/src/Config.res.mjs +3 -1
  11. package/src/Core.res +0 -3
  12. package/src/ExitOnCaughtUp.res +10 -2
  13. package/src/ExitOnCaughtUp.res.mjs +10 -1
  14. package/src/FetchState.res +63 -133
  15. package/src/FetchState.res.mjs +107 -166
  16. package/src/IndexerState.res +4 -0
  17. package/src/IndexerState.res.mjs +8 -1
  18. package/src/IndexerState.resi +1 -0
  19. package/src/IndexingAddresses.res +105 -0
  20. package/src/IndexingAddresses.res.mjs +100 -0
  21. package/src/IndexingAddresses.resi +32 -0
  22. package/src/Main.res +4 -15
  23. package/src/Main.res.mjs +6 -14
  24. package/src/Metrics.res +33 -0
  25. package/src/Metrics.res.mjs +39 -0
  26. package/src/Prometheus.res +2 -20
  27. package/src/Prometheus.res.mjs +83 -95
  28. package/src/SimulateDeadInputTracker.res +85 -0
  29. package/src/SimulateDeadInputTracker.res.mjs +73 -0
  30. package/src/SimulateDeadInputTracker.resi +12 -0
  31. package/src/SimulateItems.res +23 -42
  32. package/src/SimulateItems.res.mjs +12 -30
  33. package/src/TestIndexer.res +3 -27
  34. package/src/TestIndexer.res.mjs +2 -9
  35. package/src/bindings/Viem.res +0 -41
  36. package/src/bindings/Viem.res.mjs +1 -43
  37. package/src/sources/Evm.res +7 -36
  38. package/src/sources/Evm.res.mjs +4 -36
  39. package/src/sources/EvmChain.res +4 -2
  40. package/src/sources/EvmChain.res.mjs +3 -2
  41. package/src/sources/EvmRpcClient.res +36 -5
  42. package/src/sources/EvmRpcClient.res.mjs +7 -4
  43. package/src/sources/HyperSyncClient.res +0 -35
  44. package/src/sources/HyperSyncClient.res.mjs +1 -8
  45. package/src/sources/Rpc.res +15 -47
  46. package/src/sources/Rpc.res.mjs +25 -56
  47. package/src/sources/RpcSource.res +289 -204
  48. package/src/sources/RpcSource.res.mjs +293 -303
  49. package/src/sources/SimulateSource.res +1 -0
  50. package/src/sources/SimulateSource.res.mjs +2 -1
  51. package/src/sources/Source.res +4 -0
  52. package/src/sources/Svm.res +7 -15
  53. package/src/sources/Svm.res.mjs +4 -15
  54. package/src/sources/TransactionStore.res +14 -2
  55. package/src/sources/TransactionStore.res.mjs +10 -2
@@ -0,0 +1,105 @@
1
+ type indexingAddress = Internal.indexingContract
2
+
3
+ type contractConfig = {startBlock: option<int>}
4
+
5
+ type t = dict<indexingAddress>
6
+
7
+ let deriveEffectiveStartBlock = (~registrationBlock: int, ~contractStartBlock: option<int>) => {
8
+ Pervasives.max(Pervasives.max(registrationBlock, 0), contractStartBlock->Option.getOr(0))
9
+ }
10
+
11
+ let makeContractConfigs = (~eventConfigs: array<Internal.eventConfig>): dict<contractConfig> => {
12
+ let contractConfigs: dict<contractConfig> = Dict.make()
13
+ eventConfigs->Array.forEach(ec => {
14
+ switch contractConfigs->Utils.Dict.dangerouslyGetNonOption(ec.contractName) {
15
+ | Some({startBlock}) =>
16
+ contractConfigs->Dict.set(
17
+ ec.contractName,
18
+ {
19
+ startBlock: switch (startBlock, ec.startBlock) {
20
+ | (Some(a), Some(b)) => Some(Pervasives.min(a, b))
21
+ | (Some(_) as s, None) | (None, Some(_) as s) => s
22
+ | (None, None) => None
23
+ },
24
+ },
25
+ )
26
+ | None =>
27
+ contractConfigs->Dict.set(
28
+ ec.contractName,
29
+ {
30
+ startBlock: ec.startBlock,
31
+ },
32
+ )
33
+ }
34
+ })
35
+ contractConfigs
36
+ }
37
+
38
+ let makeIndexingAddress = (
39
+ ~contract: Internal.indexingAddress,
40
+ ~contractConfigs: dict<contractConfig>,
41
+ ): indexingAddress => {
42
+ let contractStartBlock = switch contractConfigs->Utils.Dict.dangerouslyGetNonOption(
43
+ contract.contractName,
44
+ ) {
45
+ | Some({startBlock}) => startBlock
46
+ | None => None
47
+ }
48
+ {
49
+ address: contract.address,
50
+ contractName: contract.contractName,
51
+ registrationBlock: contract.registrationBlock,
52
+ effectiveStartBlock: deriveEffectiveStartBlock(
53
+ ~registrationBlock=contract.registrationBlock,
54
+ ~contractStartBlock,
55
+ ),
56
+ }
57
+ }
58
+
59
+ let make = (
60
+ ~contractConfigs: dict<contractConfig>,
61
+ ~addresses: array<Internal.indexingAddress>,
62
+ ): t => {
63
+ let indexingAddresses = Dict.make()
64
+ addresses->Array.forEach(contract => {
65
+ indexingAddresses->Dict.set(
66
+ contract.address->Address.toString,
67
+ makeIndexingAddress(~contract, ~contractConfigs),
68
+ )
69
+ })
70
+ indexingAddresses
71
+ }
72
+
73
+ let get = (indexingAddresses: t, address) =>
74
+ indexingAddresses->Utils.Dict.dangerouslyGetNonOption(address)
75
+
76
+ let size = (indexingAddresses: t) => indexingAddresses->Utils.Dict.size
77
+
78
+ let getContractAddresses = (indexingAddresses: t, ~contractName): array<Address.t> => {
79
+ let addresses = []
80
+ indexingAddresses->Utils.Dict.forEach(ia => {
81
+ if ia.contractName === contractName {
82
+ addresses->Array.push(ia.address)
83
+ }
84
+ })
85
+ addresses
86
+ }
87
+
88
+ // Underlying dict for the precompiled `clientAddressFilter` only — it does raw
89
+ // `indexingAddresses[srcAddress]` access in generated JS and can't take the opaque
90
+ // type. Don't reach for this elsewhere; use the domain accessors above.
91
+ let rawForFilter = (indexingAddresses: t): dict<indexingAddress> => indexingAddresses
92
+
93
+ let register = (indexingAddresses: t, additions: dict<indexingAddress>) => {
94
+ let _ = Utils.Dict.mergeInPlace(indexingAddresses, additions)
95
+ }
96
+
97
+ let rollbackInPlace = (indexingAddresses: t, ~targetBlockNumber: int): unit => {
98
+ // forEachWithKey is a `for..in`, so deleting the key currently being visited is
99
+ // safe — it doesn't affect enumeration of the remaining keys.
100
+ indexingAddresses->Utils.Dict.forEachWithKey((indexingContract, address) => {
101
+ if indexingContract.registrationBlock > targetBlockNumber {
102
+ indexingAddresses->Utils.Dict.deleteInPlace(address)
103
+ }
104
+ })
105
+ }
@@ -0,0 +1,100 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Utils from "./Utils.res.mjs";
4
+ import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+
7
+ function deriveEffectiveStartBlock(registrationBlock, contractStartBlock) {
8
+ return Primitive_int.max(Primitive_int.max(registrationBlock, 0), Stdlib_Option.getOr(contractStartBlock, 0));
9
+ }
10
+
11
+ function makeContractConfigs(eventConfigs) {
12
+ let contractConfigs = {};
13
+ eventConfigs.forEach(ec => {
14
+ let match = contractConfigs[ec.contractName];
15
+ if (match !== undefined) {
16
+ let startBlock = match.startBlock;
17
+ let match$1 = ec.startBlock;
18
+ contractConfigs[ec.contractName] = {
19
+ startBlock: startBlock !== undefined ? (
20
+ match$1 !== undefined ? Primitive_int.min(startBlock, match$1) : startBlock
21
+ ) : (
22
+ match$1 !== undefined ? match$1 : undefined
23
+ )
24
+ };
25
+ return;
26
+ }
27
+ contractConfigs[ec.contractName] = {
28
+ startBlock: ec.startBlock
29
+ };
30
+ });
31
+ return contractConfigs;
32
+ }
33
+
34
+ function makeIndexingAddress(contract, contractConfigs) {
35
+ let match = contractConfigs[contract.contractName];
36
+ let contractStartBlock = match !== undefined ? match.startBlock : undefined;
37
+ return {
38
+ address: contract.address,
39
+ contractName: contract.contractName,
40
+ registrationBlock: contract.registrationBlock,
41
+ effectiveStartBlock: deriveEffectiveStartBlock(contract.registrationBlock, contractStartBlock)
42
+ };
43
+ }
44
+
45
+ function make(contractConfigs, addresses) {
46
+ let indexingAddresses = {};
47
+ addresses.forEach(contract => {
48
+ indexingAddresses[contract.address] = makeIndexingAddress(contract, contractConfigs);
49
+ });
50
+ return indexingAddresses;
51
+ }
52
+
53
+ function get(indexingAddresses, address) {
54
+ return indexingAddresses[address];
55
+ }
56
+
57
+ function size(indexingAddresses) {
58
+ return Utils.Dict.size(indexingAddresses);
59
+ }
60
+
61
+ function getContractAddresses(indexingAddresses, contractName) {
62
+ let addresses = [];
63
+ Utils.Dict.forEach(indexingAddresses, ia => {
64
+ if (ia.contractName === contractName) {
65
+ addresses.push(ia.address);
66
+ return;
67
+ }
68
+ });
69
+ return addresses;
70
+ }
71
+
72
+ function rawForFilter(indexingAddresses) {
73
+ return indexingAddresses;
74
+ }
75
+
76
+ function register(indexingAddresses, additions) {
77
+ Object.assign(indexingAddresses, additions);
78
+ }
79
+
80
+ function rollbackInPlace(indexingAddresses, targetBlockNumber) {
81
+ Utils.Dict.forEachWithKey(indexingAddresses, (indexingContract, address) => {
82
+ if (indexingContract.registrationBlock > targetBlockNumber) {
83
+ return Utils.Dict.deleteInPlace(indexingAddresses, address);
84
+ }
85
+ });
86
+ }
87
+
88
+ export {
89
+ deriveEffectiveStartBlock,
90
+ makeContractConfigs,
91
+ makeIndexingAddress,
92
+ make,
93
+ get,
94
+ size,
95
+ getContractAddresses,
96
+ rawForFilter,
97
+ register,
98
+ rollbackInPlace,
99
+ }
100
+ /* Utils Not a pure module */
@@ -0,0 +1,32 @@
1
+ type indexingAddress = Internal.indexingContract
2
+
3
+ type contractConfig = {startBlock: option<int>}
4
+
5
+ type t
6
+
7
+ let deriveEffectiveStartBlock: (~registrationBlock: int, ~contractStartBlock: option<int>) => int
8
+
9
+ let makeContractConfigs: (~eventConfigs: array<Internal.eventConfig>) => dict<contractConfig>
10
+
11
+ let makeIndexingAddress: (
12
+ ~contract: Internal.indexingAddress,
13
+ ~contractConfigs: dict<contractConfig>,
14
+ ) => indexingAddress
15
+
16
+ let make: (~contractConfigs: dict<contractConfig>, ~addresses: array<Internal.indexingAddress>) => t
17
+
18
+ let get: (t, string) => option<indexingAddress>
19
+
20
+ let size: t => int
21
+
22
+ let getContractAddresses: (t, ~contractName: string) => array<Address.t>
23
+
24
+ // Underlying dict for the precompiled `clientAddressFilter` only, which does raw
25
+ // `indexingAddresses[srcAddress]` access in generated JS. Don't use elsewhere —
26
+ // prefer the domain accessors so the opaque type stays enforced.
27
+ let rawForFilter: t => dict<indexingAddress>
28
+
29
+ let register: (t, dict<indexingAddress>) => unit
30
+
31
+ // Deletes addresses registered after `targetBlockNumber` from the index in place.
32
+ let rollbackInPlace: (t, ~targetBlockNumber: int) => unit
package/src/Main.res CHANGED
@@ -168,18 +168,7 @@ let buildChainsObject = (~config: Config.t) => {
168
168
  | Some(state) => {
169
169
  let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)
170
170
  let chainState = state->IndexerState.getChainState(~chain)
171
- let indexingAddresses = chainState->ChainState.indexingAddresses
172
-
173
- // Collect all addresses for this contract name from indexingAddresses
174
- let addresses = []
175
- let values = indexingAddresses->Dict.valuesToArray
176
- for idx in 0 to values->Array.length - 1 {
177
- let indexingContract = values->Array.getUnsafe(idx)
178
- if indexingContract.contractName === contract.name {
179
- addresses->Array.push(indexingContract.address)->ignore
180
- }
181
- }
182
- addresses
171
+ chainState->ChainState.contractAddresses(~contractName=contract.name)
183
172
  }
184
173
  // Before the global state is available (eg during handler
185
174
  // module load after resume), combine static addresses from config
@@ -627,9 +616,9 @@ let startServer = (~getState, ~persistence: Persistence.t, ~isDevelopmentMode: b
627
616
  app->get("/metrics", (_req, res) => {
628
617
  res->set("Content-Type", PromClient.defaultRegister->PromClient.getContentType)
629
618
  let _ =
630
- PromClient.defaultRegister
631
- ->PromClient.metrics
632
- ->Promise.thenResolve(metrics => res->endWithData(metrics))
619
+ Metrics.collect(~state=indexerStateRef.contents)->Promise.thenResolve(metrics =>
620
+ res->endWithData(metrics)
621
+ )
633
622
  })
634
623
 
635
624
  app->get("/metrics/runtime", (_req, res) => {
package/src/Main.res.mjs CHANGED
@@ -6,6 +6,7 @@ import * as Envio from "./Envio.res.mjs";
6
6
  import * as Utils from "./Utils.res.mjs";
7
7
  import * as Config from "./Config.res.mjs";
8
8
  import * as Logging from "./Logging.res.mjs";
9
+ import * as Metrics from "./Metrics.res.mjs";
9
10
  import Express from "express";
10
11
  import * as Process from "process";
11
12
  import * as ChainMap from "./ChainMap.res.mjs";
@@ -165,29 +166,20 @@ function buildChainsObject(config) {
165
166
  if (state !== undefined) {
166
167
  let chain = ChainMap.Chain.makeUnsafe(chainConfig.id);
167
168
  let chainState = IndexerState.getChainState(Primitive_option.valFromOption(state), chain);
168
- let indexingAddresses = ChainState.indexingAddresses(chainState);
169
- let addresses = [];
170
- let values = Object.values(indexingAddresses);
171
- for (let idx = 0, idx_finish = values.length; idx < idx_finish; ++idx) {
172
- let indexingContract = values[idx];
173
- if (indexingContract.contractName === contract.name) {
174
- addresses.push(indexingContract.address);
175
- }
176
- }
177
- return addresses;
169
+ return ChainState.contractAddresses(chainState, contract.name);
178
170
  }
179
171
  let chainState$1 = getInitialChainState(chainConfig.id);
180
172
  if (chainState$1 === undefined) {
181
173
  return contract.addresses;
182
174
  }
183
- let addresses$1 = contract.addresses.slice();
175
+ let addresses = contract.addresses.slice();
184
176
  chainState$1.indexingAddresses.forEach(dc => {
185
177
  if (dc.contractName === contract.name) {
186
- addresses$1.push(dc.address);
178
+ addresses.push(dc.address);
187
179
  return;
188
180
  }
189
181
  });
190
- return addresses$1;
182
+ return addresses;
191
183
  }
192
184
  });
193
185
  Object.defineProperty(chainObj, contract.name, {
@@ -486,7 +478,7 @@ function startServer(getState, persistence, isDevelopmentMode) {
486
478
  });
487
479
  app.get("/metrics", (_req, res) => {
488
480
  res.set("Content-Type", PromClient.register.contentType);
489
- PromClient.register.metrics().then(metrics => res.end(metrics));
481
+ Metrics.collect(indexerStateRef.contents).then(metrics => res.end(metrics));
490
482
  });
491
483
  app.get("/metrics/runtime", (_req, res) => {
492
484
  res.set("Content-Type", runtimeRegistry.contentType);
@@ -0,0 +1,33 @@
1
+ // Pull-based metrics computed from live indexer state at scrape time, merged with
2
+ // the imperatively-updated prom-client default registry. Used because the address
3
+ // index is mutated in place (no single update site to fire a gauge from).
4
+
5
+ let indexingAddressesName = "envio_indexing_addresses"
6
+ let indexingAddressesHelp = "The number of addresses indexed on chain. Includes both static and dynamic addresses."
7
+
8
+ // Render a gauge straight from the per-chain dict, one sample per key, without
9
+ // materialising an intermediate samples array. Accumulate into one string rather
10
+ // than building a lines array to join: `++` compiles to JS `+=`, which V8 grows
11
+ // as a ConsString instead of recopying.
12
+ let renderGauge = (~name, ~help, ~chains: dict<'a>, ~value: 'a => int) => {
13
+ let out = ref(`# HELP ${name} ${help}\n# TYPE ${name} gauge`)
14
+ let prefix = `\n${name}{chainId="`
15
+ chains->Utils.Dict.forEachWithKey((chain, chainId) => {
16
+ out := out.contents ++ prefix ++ chainId ++ `"} ` ++ value(chain)->Int.toString
17
+ })
18
+ out.contents
19
+ }
20
+
21
+ let collect = async (~state: option<IndexerState.t>) => {
22
+ let base = await PromClient.defaultRegister->PromClient.metrics
23
+ switch state {
24
+ | None => base
25
+ | Some(state) =>
26
+ `${base}${renderGauge(
27
+ ~name=indexingAddressesName,
28
+ ~help=indexingAddressesHelp,
29
+ ~chains=state->IndexerState.chainStates,
30
+ ~value=cs => (cs->ChainState.toChainData).numAddresses,
31
+ )}\n`
32
+ }
33
+ }
@@ -0,0 +1,39 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Utils from "./Utils.res.mjs";
4
+ import * as ChainState from "./ChainState.res.mjs";
5
+ import * as PromClient from "prom-client";
6
+ import * as IndexerState from "./IndexerState.res.mjs";
7
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
8
+
9
+ let indexingAddressesName = "envio_indexing_addresses";
10
+
11
+ let indexingAddressesHelp = "The number of addresses indexed on chain. Includes both static and dynamic addresses.";
12
+
13
+ function renderGauge(name, help, chains, value) {
14
+ let out = {
15
+ contents: `# HELP ` + name + ` ` + help + `\n# TYPE ` + name + ` gauge`
16
+ };
17
+ let prefix = `\n` + name + `{chainId="`;
18
+ Utils.Dict.forEachWithKey(chains, (chain, chainId) => {
19
+ out.contents = out.contents + prefix + chainId + `"} ` + value(chain).toString();
20
+ });
21
+ return out.contents;
22
+ }
23
+
24
+ async function collect(state) {
25
+ let base = await PromClient.register.metrics();
26
+ if (state !== undefined) {
27
+ return base + renderGauge(indexingAddressesName, indexingAddressesHelp, IndexerState.chainStates(Primitive_option.valFromOption(state)), cs => ChainState.toChainData(cs).numAddresses) + `\n`;
28
+ } else {
29
+ return base;
30
+ }
31
+ }
32
+
33
+ export {
34
+ indexingAddressesName,
35
+ indexingAddressesHelp,
36
+ renderGauge,
37
+ collect,
38
+ }
39
+ /* Utils Not a pure module */
@@ -246,10 +246,7 @@ module PreloadHandler = {
246
246
  )
247
247
  operations->Utils.Dict.deleteInPlace(key)
248
248
  }
249
- sumTimeCounter->SafeCounter.handleFloat(
250
- ~labels,
251
- ~value=timerRef->Performance.secondsSince,
252
- )
249
+ sumTimeCounter->SafeCounter.handleFloat(~labels, ~value=timerRef->Performance.secondsSince)
253
250
  count->SafeCounter.increment(~labels)
254
251
  }
255
252
  }
@@ -339,18 +336,6 @@ module ProcessStartTimeSeconds = {
339
336
  }
340
337
  }
341
338
 
342
- module IndexingAddresses = {
343
- let gauge = SafeGauge.makeOrThrow(
344
- ~name="envio_indexing_addresses",
345
- ~help="The number of addresses indexed on chain. Includes both static and dynamic addresses.",
346
- ~labelSchema=chainIdLabelsSchema,
347
- )
348
-
349
- let set = (~addressesCount, ~chainId) => {
350
- gauge->SafeGauge.handleInt(~labels=chainId, ~value=addressesCount)
351
- }
352
- }
353
-
354
339
  module IndexingConcurrency = {
355
340
  let gauge = SafeGauge.makeOrThrow(
356
341
  ~name="envio_indexing_concurrency",
@@ -800,10 +785,7 @@ module StorageLoad = {
800
785
  )
801
786
  operations->Utils.Dict.deleteInPlace(key)
802
787
  }
803
- sumTimeCounter->SafeCounter.handleFloat(
804
- ~labels,
805
- ~value=timerRef->Performance.secondsSince,
806
- )
788
+ sumTimeCounter->SafeCounter.handleFloat(~labels, ~value=timerRef->Performance.secondsSince)
807
789
  counter->SafeCounter.increment(~labels)
808
790
  whereSizeCounter->SafeCounter.handleInt(~labels, ~value=whereSize)
809
791
  sizeCounter->SafeCounter.handleInt(~labels, ~value=size)