envio 3.7.0 → 3.9.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 (79) hide show
  1. package/README.md +2 -2
  2. package/index.d.ts +399 -156
  3. package/package.json +6 -6
  4. package/src/AddressRows.res +121 -0
  5. package/src/AddressRows.res.mjs +143 -0
  6. package/src/Batch.res +2 -0
  7. package/src/Batch.res.mjs +2 -1
  8. package/src/ChainState.res +82 -68
  9. package/src/ChainState.res.mjs +81 -65
  10. package/src/ChainState.resi +14 -9
  11. package/src/Config.res +41 -91
  12. package/src/Config.res.mjs +34 -68
  13. package/src/ContractMapping.res +66 -0
  14. package/src/ContractMapping.res.mjs +73 -0
  15. package/src/Core.res +20 -0
  16. package/src/Core.res.mjs +4 -0
  17. package/src/Envio.res +54 -76
  18. package/src/EventConfigBuilder.res +157 -30
  19. package/src/EventConfigBuilder.res.mjs +118 -24
  20. package/src/FetchState.res +59 -67
  21. package/src/FetchState.res.mjs +34 -44
  22. package/src/HandlerRegister.res +20 -11
  23. package/src/HandlerRegister.res.mjs +22 -5
  24. package/src/Hasura.res +30 -22
  25. package/src/Hasura.res.mjs +11 -5
  26. package/src/InMemoryStore.res +18 -36
  27. package/src/InMemoryStore.res.mjs +12 -26
  28. package/src/IndexerState.res +21 -1
  29. package/src/IndexerState.res.mjs +9 -3
  30. package/src/IndexerState.resi +1 -0
  31. package/src/Internal.res +33 -13
  32. package/src/Internal.res.mjs +12 -16
  33. package/src/Main.res +27 -20
  34. package/src/Main.res.mjs +19 -14
  35. package/src/MemoryStorage.res +78 -73
  36. package/src/MemoryStorage.res.mjs +66 -81
  37. package/src/Metrics.res +15 -1
  38. package/src/Metrics.res.mjs +5 -1
  39. package/src/Persistence.res +34 -18
  40. package/src/Persistence.res.mjs +8 -5
  41. package/src/PgStorage.res +189 -66
  42. package/src/PgStorage.res.mjs +93 -59
  43. package/src/Rollback.res +14 -10
  44. package/src/Rollback.res.mjs +4 -2
  45. package/src/SimulateItems.res +254 -9
  46. package/src/SimulateItems.res.mjs +214 -47
  47. package/src/TestIndexer.res +119 -121
  48. package/src/TestIndexer.res.mjs +95 -100
  49. package/src/Utils.res +7 -0
  50. package/src/Utils.res.mjs +9 -2
  51. package/src/Writing.res +2 -0
  52. package/src/Writing.res.mjs +2 -1
  53. package/src/bindings/ClickHouse.res +39 -6
  54. package/src/bindings/ClickHouse.res.mjs +29 -4
  55. package/src/bindings/NodeJs.res +9 -0
  56. package/src/bindings/NodeJs.res.mjs +8 -1
  57. package/src/bindings/Postgres.res +9 -0
  58. package/src/bindings/Postgres.res.mjs +3 -0
  59. package/src/db/EntityHistory.res +12 -18
  60. package/src/db/EntityHistory.res.mjs +3 -14
  61. package/src/db/InternalTable.res +182 -65
  62. package/src/db/InternalTable.res.mjs +183 -73
  63. package/src/db/Table.res +24 -0
  64. package/src/db/Table.res.mjs +20 -1
  65. package/src/sources/AddressSet.res +0 -22
  66. package/src/sources/AddressStore.res +48 -88
  67. package/src/sources/AddressStore.res.mjs +11 -22
  68. package/src/sources/EvmHyperSyncSource.res +3 -2
  69. package/src/sources/SimulateSource.res +8 -4
  70. package/src/sources/SimulateSource.res.mjs +7 -3
  71. package/src/sources/Svm.res +34 -7
  72. package/src/sources/Svm.res.mjs +32 -4
  73. package/src/sources/SvmHyperSyncClient.res +24 -30
  74. package/src/sources/SvmHyperSyncClient.res.mjs +3 -2
  75. package/src/sources/SvmHyperSyncSource.res +88 -36
  76. package/src/sources/SvmHyperSyncSource.res.mjs +71 -39
  77. package/src/sources/TransactionStore.res +38 -0
  78. package/src/sources/TransactionStore.res.mjs +5 -0
  79. package/svm.schema.json +37 -82
package/src/Metrics.res CHANGED
@@ -12,6 +12,8 @@ type chainMetrics = {
12
12
  startBlock: int,
13
13
  endBlock: option<int>,
14
14
  numAddresses: int,
15
+ // Per-contract registration counts, in the chain's contract-id order.
16
+ addressesByContract: array<(string, int)>,
15
17
  isReady: bool,
16
18
  // Raw source height, unlike knownHeight which is clamped to endBlock.
17
19
  sourceBlockNumber: int,
@@ -694,11 +696,23 @@ let renderMetrics = (b: builder, metrics: t) => {
694
696
  )
695
697
  b->series(
696
698
  ~name="envio_indexing_addresses",
697
- ~help="The number of addresses indexed on chain. Includes both static and dynamic addresses.",
699
+ ~help="The number of address registrations on chain, static and dynamic. An address shared by N contracts counts N times.",
698
700
  ~kind="gauge",
699
701
  ~entries=chains,
700
702
  ~value=m => m.numAddresses->Int.toFloat,
701
703
  )
704
+ b->series(
705
+ ~name="envio_indexing_contract_addresses",
706
+ ~help="The number of address registrations per contract on chain, static and dynamic. An address shared by N contracts counts N times.",
707
+ ~kind="gauge",
708
+ ~entries=metrics.chains->Array.flatMap(m =>
709
+ m.addressesByContract->Array.map(((contract, count)) => (
710
+ `{chainId="${m.chainId->ChainId.toString}",contract="${contract->escapeLabelValue}"}`,
711
+ count,
712
+ ))
713
+ ),
714
+ ~value=count => count->Int.toFloat,
715
+ )
702
716
  }
703
717
 
704
718
  let contentType = "text/plain; version=0.0.4; charset=utf-8"
@@ -229,7 +229,11 @@ function renderMetrics(b, metrics) {
229
229
  series(b, "envio_storage_load_size", "Cumulative number of records loaded from storage during the indexing process.", "counter", storageLoads, s => s.size);
230
230
  series(b, "envio_storage_write_seconds", "Cumulative time spent writing batch data to storage.", "counter", storageWrites, s => s.seconds);
231
231
  series(b, "envio_storage_write_total", "Cumulative number of successful storage write operations during the indexing process.", "counter", storageWrites, s => s.count);
232
- series(b, "envio_indexing_addresses", "The number of addresses indexed on chain. Includes both static and dynamic addresses.", "gauge", chains, m => m.numAddresses);
232
+ series(b, "envio_indexing_addresses", "The number of address registrations on chain, static and dynamic. An address shared by N contracts counts N times.", "gauge", chains, m => m.numAddresses);
233
+ series(b, "envio_indexing_contract_addresses", "The number of address registrations per contract on chain, static and dynamic. An address shared by N contracts counts N times.", "gauge", metrics.chains.flatMap(m => m.addressesByContract.map(param => [
234
+ `{chainId="` + ChainId.toString(m.chainId) + `",contract="` + escapeLabelValue(param[0]) + `"}`,
235
+ param[1]
236
+ ])), count => count);
233
237
  }
234
238
 
235
239
  function collect(metrics) {
@@ -27,22 +27,25 @@ type initialChainState = {
27
27
  numEventsProcessed: float,
28
28
  firstEventBlockNumber: option<int>,
29
29
  timestampCaughtUpToHeadOrEndblock: option<Date.t>,
30
- indexingAddresses: array<Internal.indexingAddress>,
30
+ // Every address the chain indexes, columnar — config-declared and dynamically
31
+ // registered alike. The chain's address store seeds straight from it.
32
+ addressRows: AddressRows.seedRows,
31
33
  sourceBlockNumber: int,
32
34
  }
33
35
 
34
36
  type initialState = {
35
37
  cleanRun: bool,
38
+ // On a resume this is what the database holds, not what the config would
39
+ // derive — the ids must never reshuffle under stored rows.
40
+ contractMapping: ContractMapping.t,
41
+ // Public config snapshot, restored with the address rows. None when
42
+ // envio_info or envio_contracts is missing.
43
+ envioInfo: option<JSON.t>,
36
44
  cache: dict<effectCacheRecord>,
37
45
  chains: array<initialChainState>,
38
46
  checkpointId: Internal.checkpointId,
39
47
  // Needed to keep reorg detection logic between restarts
40
48
  reorgCheckpoints: array<Internal.reorgCheckpoint>,
41
- // Public config snapshot read from envio_info, used by `Persistence.init`
42
- // to compat-check a resume against the running config. None when the
43
- // schema pre-dates envio_info or the row is missing — `init` treats that
44
- // as a version mismatch.
45
- envioInfo: option<JSON.t>,
46
49
  }
47
50
 
48
51
  // Carries the already-resolved cache address (`table`) rather than an effect +
@@ -58,6 +61,9 @@ type updatedEffectCache = {
58
61
  type rollback = {
59
62
  targetCheckpointId: Internal.checkpointId,
60
63
  diffCheckpointId: Internal.checkpointId,
64
+ // The address registrations the rollback dropped, as the chains' address
65
+ // stores resolved them. Deleted by primary key in the same transaction.
66
+ rolledBackAddresses: array<AddressRows.key>,
61
67
  // Last valid block per chain affected by the rollback. Read by
62
68
  // `RollbackCommit.fire` once the diff is durably written.
63
69
  progressBlockNumberByChainId: dict<int>,
@@ -92,6 +98,7 @@ type storage = {
92
98
  ~chainConfigs: array<Config.chain>=?,
93
99
  ~entities: array<Internal.entityConfig>=?,
94
100
  ~enums: array<Table.enumConfig<Table.enum>>=?,
101
+ ~contractMapping: ContractMapping.t,
95
102
  ~envioInfo: JSON.t,
96
103
  ) => promise<initialState>,
97
104
  resumeInitialState: unit => promise<initialState>,
@@ -147,11 +154,13 @@ type storage = {
147
154
  "new_progress_block_number": int,
148
155
  }>,
149
156
  >,
150
- // Get rollback data for entity
157
+ // Rollback data for an entity, as decoded entities rather than storage rows:
158
+ // only the storage knows how it encoded them, so each one decodes its own
159
+ // before handing them back.
151
160
  getRollbackData: (
152
161
  ~entityConfig: Internal.entityConfig,
153
162
  ~rollbackTargetCheckpointId: Internal.checkpointId,
154
- ) => promise<(array<rollbackRemoval>, array<unknown>)>,
163
+ ) => promise<(array<rollbackRemoval>, array<Internal.entity>)>,
155
164
  // Write batch to storage
156
165
  writeBatch: (
157
166
  ~batch: Batch.t,
@@ -161,6 +170,8 @@ type storage = {
161
170
  ~allEntities: array<Internal.entityConfig>,
162
171
  ~updatedEffectsCache: array<updatedEffectCache>,
163
172
  ~updatedEntities: array<updatedEntity>,
173
+ // Addresses this batch registered, with the checkpoint that covers them.
174
+ ~registeredAddresses: array<AddressRows.staged>,
164
175
  // Chain metadata stale since the last write, persisted in the same
165
176
  // transaction so it never races the batch write.
166
177
  ~chainMetaData: option<dict<InternalTable.Chains.metaFields>>,
@@ -194,7 +205,7 @@ let make = (
194
205
  ~allEnums,
195
206
  ~storage,
196
207
  ) => {
197
- let allEntities = userEntities->Array.concat([InternalTable.EnvioAddresses.entityConfig])
208
+ let allEntities = userEntities
198
209
  let allEnums =
199
210
  allEnums->Array.concat([EntityHistory.RowAction.config->Table.fromGenericEnumConfig])
200
211
  {
@@ -207,7 +218,15 @@ let make = (
207
218
  }
208
219
 
209
220
  let init = {
210
- async (persistence, ~chainConfigs, ~envioInfo, ~resetCommand, ~runCommand, ~reset=false) => {
221
+ async (
222
+ persistence,
223
+ ~chainConfigs,
224
+ ~contractMapping,
225
+ ~envioInfo,
226
+ ~resetCommand,
227
+ ~runCommand,
228
+ ~reset=false,
229
+ ) => {
211
230
  try {
212
231
  let shouldRun = switch persistence.storageStatus {
213
232
  | Unknown => true
@@ -229,6 +248,7 @@ let init = {
229
248
  ~entities=persistence.allEntities,
230
249
  ~enums=persistence.allEnums,
231
250
  ~chainConfigs,
251
+ ~contractMapping,
232
252
  ~envioInfo,
233
253
  )
234
254
  Logging.info(`The indexer storage is ready. Starting indexing!`)
@@ -243,17 +263,10 @@ let init = {
243
263
  ) {
244
264
  Logging.info(`Found existing indexer storage. Resuming indexing state...`)
245
265
  let initialState = await persistence.storage.resumeInitialState()
246
- // Compat-check the running config against what was stored on the
247
- // last successful initialize. None means the schema pre-dates
248
- // envio_info (or the row was wiped out-of-band) and we can't
249
- // compare — treat it as a version mismatch.
250
266
  let changedPaths = switch initialState.envioInfo {
251
- | None => ["envio info is missing — storage initialized by an older envio"]
267
+ | None => ["storage was initialized by an older envio version"]
252
268
  | Some(stored) => Config.diffPaths(~stored, ~current=envioInfo)
253
269
  }
254
- // `storage.clickhouse` is serialized as a plain bool by the
255
- // public config (see Rust `StorageConfig`), so probe for
256
- // `Boolean(true)`, not an object.
257
270
  let hasClickhouse = switch envioInfo {
258
271
  | Object(d) =>
259
272
  switch d->Dict.get("storage") {
@@ -267,6 +280,9 @@ let init = {
267
280
  | _ => false
268
281
  }
269
282
  Config.throwIfIncompatible(changedPaths, ~resetCommand, ~runCommand, ~hasClickhouse)
283
+ if !(initialState.contractMapping->ContractMapping.isEqual(contractMapping)) {
284
+ Config.throwIfIncompatible(["contracts"], ~resetCommand, ~runCommand, ~hasClickhouse)
285
+ }
270
286
  persistence.storageStatus = Ready(initialState)
271
287
  let progress = Dict.make()
272
288
  initialState.chains->Array.forEach(c => {
@@ -5,23 +5,23 @@ import * as Logging from "./Logging.res.mjs";
5
5
  import * as EntityHistory from "./db/EntityHistory.res.mjs";
6
6
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
7
7
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
8
+ import * as ContractMapping from "./ContractMapping.res.mjs";
8
9
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
9
10
 
10
11
  let StorageError = /* @__PURE__ */Primitive_exceptions.create("Persistence.StorageError");
11
12
 
12
13
  function make(userEntities, allEnums, storage) {
13
- let allEntities = userEntities.concat([Config.EnvioAddresses.entityConfig]);
14
14
  let allEnums$1 = allEnums.concat([EntityHistory.RowAction.config]);
15
15
  return {
16
16
  userEntities: userEntities,
17
- allEntities: allEntities,
17
+ allEntities: userEntities,
18
18
  allEnums: allEnums$1,
19
19
  storageStatus: "Unknown",
20
20
  storage: storage
21
21
  };
22
22
  }
23
23
 
24
- async function init(persistence, chainConfigs, envioInfo, resetCommand, runCommand, resetOpt) {
24
+ async function init(persistence, chainConfigs, contractMapping, envioInfo, resetCommand, runCommand, resetOpt) {
25
25
  let reset = resetOpt !== undefined ? resetOpt : false;
26
26
  try {
27
27
  let promise = persistence.storageStatus;
@@ -49,7 +49,7 @@ async function init(persistence, chainConfigs, envioInfo, resetCommand, runComma
49
49
  };
50
50
  if (reset || !await persistence.storage.isInitialized()) {
51
51
  Logging.info(`Initializing the indexer storage...`);
52
- let initialState = await persistence.storage.initialize(chainConfigs, persistence.allEntities, persistence.allEnums, envioInfo);
52
+ let initialState = await persistence.storage.initialize(chainConfigs, persistence.allEntities, persistence.allEnums, contractMapping, envioInfo);
53
53
  Logging.info(`The indexer storage is ready. Starting indexing!`);
54
54
  persistence.storageStatus = {
55
55
  TAG: "Ready",
@@ -63,7 +63,7 @@ async function init(persistence, chainConfigs, envioInfo, resetCommand, runComma
63
63
  Logging.info(`Found existing indexer storage. Resuming indexing state...`);
64
64
  let initialState$1 = await persistence.storage.resumeInitialState();
65
65
  let stored = initialState$1.envioInfo;
66
- let changedPaths = stored !== undefined ? Config.diffPaths(stored, envioInfo) : ["envio info is missing — storage initialized by an older envio"];
66
+ let changedPaths = stored !== undefined ? Config.diffPaths(stored, envioInfo) : ["storage was initialized by an older envio version"];
67
67
  let hasClickhouse;
68
68
  if (typeof envioInfo === "object" && envioInfo !== null && !Array.isArray(envioInfo)) {
69
69
  let match$1 = envioInfo["storage"];
@@ -77,6 +77,9 @@ async function init(persistence, chainConfigs, envioInfo, resetCommand, runComma
77
77
  hasClickhouse = false;
78
78
  }
79
79
  Config.throwIfIncompatible(changedPaths, resetCommand, runCommand, hasClickhouse);
80
+ if (!ContractMapping.isEqual(initialState$1.contractMapping, contractMapping)) {
81
+ Config.throwIfIncompatible(["contracts"], resetCommand, runCommand, hasClickhouse);
82
+ }
80
83
  persistence.storageStatus = {
81
84
  TAG: "Ready",
82
85
  _0: initialState$1