envio 3.8.0 → 3.9.0-resolvers.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 (109) hide show
  1. package/README.md +2 -2
  2. package/evm.schema.json +7 -0
  3. package/fuel.schema.json +7 -0
  4. package/index.d.ts +433 -109
  5. package/index.js +10 -0
  6. package/package.json +6 -7
  7. package/src/AddressRows.res +121 -0
  8. package/src/AddressRows.res.mjs +143 -0
  9. package/src/Batch.res +11 -0
  10. package/src/Batch.res.mjs +2 -1
  11. package/src/Bin.res +47 -0
  12. package/src/Bin.res.mjs +34 -1
  13. package/src/ChainState.res +102 -100
  14. package/src/ChainState.res.mjs +81 -67
  15. package/src/ChainState.resi +28 -15
  16. package/src/Config.res +43 -122
  17. package/src/Config.res.mjs +33 -75
  18. package/src/ContractMapping.res +66 -0
  19. package/src/ContractMapping.res.mjs +73 -0
  20. package/src/Core.res +24 -0
  21. package/src/Core.res.mjs +4 -0
  22. package/src/Ecosystem.res +7 -2
  23. package/src/Env.res +128 -1
  24. package/src/Env.res.mjs +123 -1
  25. package/src/EventConfigBuilder.res +169 -28
  26. package/src/EventConfigBuilder.res.mjs +124 -17
  27. package/src/FetchState.res +59 -67
  28. package/src/FetchState.res.mjs +34 -44
  29. package/src/HandlerRegister.res +6 -4
  30. package/src/HandlerRegister.res.mjs +8 -5
  31. package/src/InMemoryStore.res +22 -32
  32. package/src/InMemoryStore.res.mjs +12 -24
  33. package/src/IndexerState.res +40 -3
  34. package/src/IndexerState.res.mjs +30 -4
  35. package/src/IndexerState.resi +4 -1
  36. package/src/Internal.res +16 -17
  37. package/src/Main.res +25 -15
  38. package/src/Main.res.mjs +19 -14
  39. package/src/Metrics.res +15 -1
  40. package/src/Metrics.res.mjs +5 -1
  41. package/src/Persistence.res +42 -19
  42. package/src/Persistence.res.mjs +8 -5
  43. package/src/PgStorage.res +232 -78
  44. package/src/PgStorage.res.mjs +114 -72
  45. package/src/PruneStaleHistory.res +1 -1
  46. package/src/PruneStaleHistory.res.mjs +1 -1
  47. package/src/Rollback.res +62 -41
  48. package/src/Rollback.res.mjs +61 -19
  49. package/src/RollbackCommit.res +7 -9
  50. package/src/RollbackCommit.res.mjs +5 -6
  51. package/src/SimulateItems.res +5 -12
  52. package/src/SimulateItems.res.mjs +6 -10
  53. package/src/Sink.res +35 -26
  54. package/src/Sink.res.mjs +20 -16
  55. package/src/TestIndexer.res +95 -113
  56. package/src/TestIndexer.res.mjs +63 -90
  57. package/src/Utils.res +38 -0
  58. package/src/Utils.res.mjs +39 -2
  59. package/src/Writing.res +4 -2
  60. package/src/Writing.res.mjs +3 -2
  61. package/src/bindings/ClickHouse.res +311 -683
  62. package/src/bindings/ClickHouse.res.mjs +294 -416
  63. package/src/bindings/ClickHouseSink.res +303 -0
  64. package/src/bindings/ClickHouseSink.res.mjs +216 -0
  65. package/src/bindings/NodeJs.res +9 -0
  66. package/src/bindings/NodeJs.res.mjs +8 -1
  67. package/src/bindings/Postgres.res +9 -0
  68. package/src/bindings/Postgres.res.mjs +3 -0
  69. package/src/db/EntityHistory.res +29 -24
  70. package/src/db/EntityHistory.res.mjs +11 -19
  71. package/src/db/InternalTable.res +247 -83
  72. package/src/db/InternalTable.res.mjs +216 -85
  73. package/src/db/RollbackScope.res +29 -0
  74. package/src/db/RollbackScope.res.mjs +31 -0
  75. package/src/db/Table.res +28 -1
  76. package/src/db/Table.res.mjs +26 -2
  77. package/src/resolvers/ResolverProcess.res +809 -0
  78. package/src/resolvers/ResolverProcess.res.mjs +667 -0
  79. package/src/resolvers/ResolverQuery.res +317 -0
  80. package/src/resolvers/ResolverQuery.res.mjs +220 -0
  81. package/src/resolvers/collisions.js +108 -0
  82. package/src/resolvers/db.js +363 -0
  83. package/src/resolvers/dispatch.js +165 -0
  84. package/src/resolvers/errors.js +30 -0
  85. package/src/resolvers/graphqlSelection.js +292 -0
  86. package/src/resolvers/hasuraAction.js +84 -0
  87. package/src/resolvers/hasuraApply.js +323 -0
  88. package/src/resolvers/hasuraMetadata.js +119 -0
  89. package/src/resolvers/index.js +95 -0
  90. package/src/resolvers/manifest.js +370 -0
  91. package/src/resolvers/server.js +270 -0
  92. package/src/sources/AddressSet.res +0 -22
  93. package/src/sources/AddressStore.res +48 -88
  94. package/src/sources/AddressStore.res.mjs +11 -22
  95. package/src/sources/Evm.res +2 -0
  96. package/src/sources/Evm.res.mjs +2 -0
  97. package/src/sources/Fuel.res +2 -0
  98. package/src/sources/Fuel.res.mjs +2 -0
  99. package/src/sources/Svm.res +2 -0
  100. package/src/sources/Svm.res.mjs +2 -0
  101. package/src/sources/SvmHyperSyncClient.res +13 -107
  102. package/src/sources/SvmHyperSyncClient.res.mjs +2 -5
  103. package/src/sources/SvmHyperSyncSource.res +5 -10
  104. package/src/sources/SvmHyperSyncSource.res.mjs +5 -13
  105. package/src/tui/Tui.res +1 -1
  106. package/src/tui/Tui.res.mjs +1 -1
  107. package/svm.schema.json +17 -78
  108. package/src/MemoryStorage.res +0 -756
  109. package/src/MemoryStorage.res.mjs +0 -623
@@ -102,7 +102,7 @@ function buildOnEventRegistrationWith(config, chainId, eventConfig, isWildcard,
102
102
  case "fuel" :
103
103
  return EventConfigBuilder.buildFuelOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, startBlock);
104
104
  case "svm" :
105
- return EventConfigBuilder.buildSvmOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, fieldSelection, startBlock);
105
+ return EventConfigBuilder.buildSvmOnEventRegistration(eventConfig, isWildcard, handler, contractRegister, where, fieldSelection, startBlock);
106
106
  }
107
107
  }
108
108
 
@@ -209,7 +209,7 @@ function addOnEventRegistration(registration, contractName, eventName, handler,
209
209
  fieldSelection = EventConfigBuilder.resolveInlineFieldSelection(fields$1, contractName, eventName, registration.config.enableRawEvents);
210
210
  break;
211
211
  case "fuel" :
212
- fieldSelection = Stdlib_JsError.throwWithMessage(`The fields option of the "` + eventName + `" event registration on contract "` + contractName + `" is only supported on EVM. Select the fields in your config instead.`);
212
+ fieldSelection = Stdlib_JsError.throwWithMessage(`The fields option of the "` + eventName + `" event registration on contract "` + contractName + `" is not supported on Fuel. Select the fields in your config instead.`);
213
213
  break;
214
214
  case "svm" :
215
215
  fieldSelection = EventConfigBuilder.resolveSvmInlineFieldSelection(fields$1, contractName, eventName);
@@ -243,13 +243,16 @@ function addOnEventRegistration(registration, contractName, eventName, handler,
243
243
  if (Utils.$$Array.isEmpty(names)) {
244
244
  return empty;
245
245
  } else {
246
- return names.join(", ");
246
+ return Utils.$$Array.quotedJoin(names);
247
247
  }
248
248
  };
249
+ let match$2 = registration.config.ecosystem;
250
+ let eventNoun = match$2.eventNoun;
251
+ let contractNoun = match$2.contractNoun;
249
252
  if (Utils.$$Array.isEmpty(eventNames)) {
250
- return Stdlib_JsError.throwWithMessage(`Contract "` + contractName + `" is not configured on any chain, so its handler for "` + eventName + `" would never run. Add it to your config, or remove the registration. Configured contracts: ` + listOr(match$1[0], "none") + `.`);
253
+ return Stdlib_JsError.throwWithMessage(Utils.$$String.capitalize(contractNoun) + ` "` + contractName + `" is not configured on any chain, so its handler for "` + eventName + `" would never run. Add it to your config, or remove the registration. Configured ` + contractNoun + `s: ` + listOr(match$1[0], "none") + `.`);
251
254
  } else {
252
- return Stdlib_JsError.throwWithMessage(`Event "` + eventName + `" is not configured on contract "` + contractName + `", so its handler would never run. Add it to your config, or remove the registration. Configured events on "` + contractName + `": ` + listOr(eventNames, "none") + `.`);
255
+ return Stdlib_JsError.throwWithMessage(Utils.$$String.capitalize(eventNoun) + ` "` + eventName + `" is not configured on ` + contractNoun + ` "` + contractName + `", so its handler would never run. Add it to your config, or remove the registration. Configured ` + eventNoun + `s on "` + contractName + `": ` + listOr(eventNames, "none") + `.`);
253
256
  }
254
257
  }
255
258
 
@@ -127,14 +127,18 @@ let dropCommittedEffects = (
127
127
 
128
128
  let prepareRollbackDiff = async (
129
129
  state: IndexerState.t,
130
+ ~rollbackScope: RollbackScope.t,
130
131
  ~rollbackTargetCheckpointId,
131
132
  ~rollbackDiffCheckpointId,
132
- ~progressBlockNumberByChainId,
133
+ ~progressedChains,
134
+ ~rolledBackAddresses,
133
135
  ) => {
134
136
  state->IndexerState.beginRollbackDiff(
135
137
  ~targetCheckpointId=rollbackTargetCheckpointId,
136
138
  ~diffCheckpointId=rollbackDiffCheckpointId,
137
- ~progressBlockNumberByChainId,
139
+ ~scope=rollbackScope,
140
+ ~progressedChains,
141
+ ~rolledBackAddresses,
138
142
  )
139
143
  let persistence = state->IndexerState.persistence
140
144
  let committedCheckpointId = state->IndexerState.committedCheckpointId
@@ -150,6 +154,7 @@ let prepareRollbackDiff = async (
150
154
  ->Array.map(async entityConfig => {
151
155
  let (removals, restoredEntities) = await persistence.storage.getRollbackData(
152
156
  ~entityConfig,
157
+ ~scope=rollbackScope,
153
158
  ~rollbackTargetCheckpointId,
154
159
  )
155
160
 
@@ -194,12 +199,6 @@ let prepareRollbackDiff = async (
194
199
  // registered them: the store is where they already live, and it knows which
195
200
  // ones the database hasn't seen yet.
196
201
  let setBatchDcs = (state: IndexerState.t, ~batch: Batch.t) => {
197
- let inMemTable = state->getInMemTable(
198
- ~entityConfig=InternalTable.EnvioAddresses.entityConfig,
199
- ~scope=CrossChain,
200
- )
201
- let committedCheckpointId = state->IndexerState.committedCheckpointId
202
-
203
202
  batch.progressedChainsById->Utils.Dict.forEach(progressedChain => {
204
203
  let chainId = progressedChain.fetchState.chainId
205
204
  let chainState = state->IndexerState.getChainState(~chainId)
@@ -219,31 +218,22 @@ let setBatchDcs = (state: IndexerState.t, ~batch: Batch.t) => {
219
218
  }
220
219
  }
221
220
 
222
- chainState
223
- ->ChainState.drainAddressesForWrite(
224
- ~toBlockInclusive=progressedChain.progressBlockNumber,
225
- ~checkpointBlockNumbers,
226
- )
227
- ->Array.forEach(dc => {
228
- let entity: InternalTable.EnvioAddresses.t = {
229
- id: InternalTable.EnvioAddresses.makeId(~chainId, ~address=dc.address),
230
- chainId,
231
- contractName: dc.contractName,
232
- registrationBlock: dc.registrationBlock,
233
- // Only ever written, never read back. Kept on the table so the column
234
- // doesn't need a migration.
235
- registrationLogIndex: -1,
236
- }
237
-
238
- inMemTable->InMemoryTable.Entity.set(
239
- ~committedCheckpointId,
240
- Set({
241
- entityId: entity.id->EntityId.unsafeOfString,
242
- checkpointId: checkpointIds->Array.getUnsafe(dc.checkpointIdx),
243
- entity: entity->InternalTable.EnvioAddresses.castToInternal,
244
- }),
221
+ batch.registeredAddresses->Array.pushMany(
222
+ chainState
223
+ ->ChainState.drainAddressesForWrite(
224
+ ~toBlockInclusive=progressedChain.progressBlockNumber,
225
+ ~checkpointBlockNumbers,
245
226
  )
246
- })
227
+ ->Array.map((dc): AddressRows.staged => {
228
+ row: {
229
+ chainId,
230
+ address: dc.address,
231
+ contractId: dc.contractId,
232
+ registrationBlock: dc.registrationBlock,
233
+ },
234
+ checkpointId: checkpointIds->Array.getUnsafe(dc.checkpointIdx),
235
+ }),
236
+ )->ignore
247
237
  }
248
238
  })
249
239
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  import * as Table from "./db/Table.res.mjs";
4
4
  import * as Utils from "./Utils.res.mjs";
5
- import * as Config from "./Config.res.mjs";
6
5
  import * as Internal from "./Internal.res.mjs";
7
6
  import * as ChainState from "./ChainState.res.mjs";
8
7
  import * as EffectState from "./EffectState.res.mjs";
@@ -107,14 +106,14 @@ function dropCommittedEffects(inMemTable, committedCheckpointId, keepLoadedFromD
107
106
  inMemTable.changesCount = inMemTable.changesCount - keysToDelete.length;
108
107
  }
109
108
 
110
- async function prepareRollbackDiff(state, rollbackTargetCheckpointId, rollbackDiffCheckpointId, progressBlockNumberByChainId) {
111
- IndexerState.beginRollbackDiff(state, rollbackTargetCheckpointId, rollbackDiffCheckpointId, progressBlockNumberByChainId);
109
+ async function prepareRollbackDiff(state, rollbackScope, rollbackTargetCheckpointId, rollbackDiffCheckpointId, progressedChains, rolledBackAddresses) {
110
+ IndexerState.beginRollbackDiff(state, rollbackTargetCheckpointId, rollbackDiffCheckpointId, rollbackScope, progressedChains, rolledBackAddresses);
112
111
  let persistence = IndexerState.persistence(state);
113
112
  let committedCheckpointId = IndexerState.committedCheckpointId(state);
114
113
  let deletedEntities = {};
115
114
  let setEntities = {};
116
115
  await Promise.all(persistence.allEntities.filter(entityConfig => entityConfig.storage.postgres).map(async entityConfig => {
117
- let match = await persistence.storage.getRollbackData(entityConfig, rollbackTargetCheckpointId);
116
+ let match = await persistence.storage.getRollbackData(entityConfig, rollbackScope, rollbackTargetCheckpointId);
118
117
  match[0].forEach(param => {
119
118
  let entityId = param.entityId;
120
119
  Utils.Dict.push(deletedEntities, entityConfig.name, entityId);
@@ -142,8 +141,6 @@ async function prepareRollbackDiff(state, rollbackTargetCheckpointId, rollbackDi
142
141
  }
143
142
 
144
143
  function setBatchDcs(state, batch) {
145
- let inMemTable = getInMemTable(state, Config.EnvioAddresses.entityConfig, "crossChain");
146
- let committedCheckpointId = IndexerState.committedCheckpointId(state);
147
144
  Utils.Dict.forEach(batch.progressedChainsById, progressedChain => {
148
145
  let chainId = progressedChain.fetchState.chainId;
149
146
  let chainState = IndexerState.getChainState(state, chainId);
@@ -158,24 +155,15 @@ function setBatchDcs(state, batch) {
158
155
  checkpointBlockNumbers.push(batch.checkpointBlockNumbers[idx]);
159
156
  }
160
157
  }
161
- ChainState.drainAddressesForWrite(chainState, progressedChain.progressBlockNumber, checkpointBlockNumbers).forEach(dc => {
162
- let entity_id = Config.EnvioAddresses.makeId(chainId, dc.address);
163
- let entity_registration_block = dc.registrationBlock;
164
- let entity_contract_name = dc.contractName;
165
- let entity = {
166
- id: entity_id,
167
- chain_id: chainId,
168
- registration_block: entity_registration_block,
169
- registration_log_index: -1,
170
- contract_name: entity_contract_name
171
- };
172
- InMemoryTable.Entity.set(inMemTable, committedCheckpointId, {
173
- type: "SET",
174
- entityId: entity_id,
175
- entity: entity,
176
- checkpointId: checkpointIds[dc.checkpointIdx]
177
- });
178
- });
158
+ batch.registeredAddresses.push(...ChainState.drainAddressesForWrite(chainState, progressedChain.progressBlockNumber, checkpointBlockNumbers).map(dc => ({
159
+ row: {
160
+ chainId: chainId,
161
+ address: dc.address,
162
+ contractId: dc.contractId,
163
+ registrationBlock: dc.registrationBlock
164
+ },
165
+ checkpointId: checkpointIds[dc.checkpointIdx]
166
+ })));
179
167
  });
180
168
  }
181
169
 
@@ -267,6 +267,7 @@ let makeFromDbState = (
267
267
  ~isInReorgThreshold,
268
268
  ~isRealtime,
269
269
  ~config,
270
+ ~contractMapping=initialState.contractMapping,
270
271
  ~registrationsByChainId,
271
272
  ~reducedPollingInterval?,
272
273
  ),
@@ -757,6 +758,7 @@ let drainBatchRun = (state: t): Batch.t => {
757
758
  let checkpointBlockNumbers = []
758
759
  let checkpointBlockHashes = []
759
760
  let checkpointEventsProcessed = []
761
+ let registeredAddresses = []
760
762
  all->Array.forEach(batch => {
761
763
  // Once one batch lands in rest, all later ones follow it, preserving order.
762
764
  if rest->Utils.Array.isEmpty && batch.isInReorgThreshold == isInReorgThreshold {
@@ -770,6 +772,7 @@ let drainBatchRun = (state: t): Batch.t => {
770
772
  checkpointBlockNumbers->Array.pushMany(batch.checkpointBlockNumbers)
771
773
  checkpointBlockHashes->Array.pushMany(batch.checkpointBlockHashes)
772
774
  checkpointEventsProcessed->Array.pushMany(batch.checkpointEventsProcessed)
775
+ registeredAddresses->Array.pushMany(batch.registeredAddresses)
773
776
  } else {
774
777
  rest->Array.push(batch)
775
778
  }
@@ -786,9 +789,12 @@ let drainBatchRun = (state: t): Batch.t => {
786
789
  checkpointBlockNumbers,
787
790
  checkpointBlockHashes,
788
791
  checkpointEventsProcessed,
792
+ registeredAddresses,
789
793
  }
790
794
  }
791
795
 
796
+ let pendingRollback = (state: t): option<Persistence.rollback> => state.rollback
797
+
792
798
  // Take the pending rollback diff to write, clearing it from the store.
793
799
  let takeRollback = (state: t): option<Persistence.rollback> => {
794
800
  let rollback = state.rollback
@@ -797,23 +803,54 @@ let takeRollback = (state: t): option<Persistence.rollback> => {
797
803
  }
798
804
 
799
805
  // Advance the committed (durably persisted) frontier after a successful write.
800
- let markCommitted = (state: t, ~upToCheckpointId) => state.committedCheckpointId = upToCheckpointId
806
+ // Written rows leave the buffer only once the transaction that holds them has
807
+ // committed. A failed write keeps them, and re-inserting a row the database
808
+ // already has is a no-op. Rows staged while the write was in flight belong to
809
+ // later checkpoints — ids only ever grow — so this can't drop one unwritten.
810
+ let markCommitted = (state: t, ~upToCheckpointId) => {
811
+ state.committedCheckpointId = upToCheckpointId
812
+ }
801
813
 
802
814
  // Reset the in-memory tables and arm the rollback diff that the next write commits.
803
815
  let beginRollbackDiff = (
804
816
  state: t,
805
817
  ~targetCheckpointId,
806
818
  ~diffCheckpointId,
807
- ~progressBlockNumberByChainId,
819
+ ~scope,
820
+ ~progressedChains: array<InternalTable.Chains.progressedChain>,
821
+ ~rolledBackAddresses,
808
822
  ) => {
809
823
  let perChainEntities = state.allEntities->EntityTables.perChain
810
824
  state.entities = EntityTables.make(state.allEntities->EntityTables.crossChain)
811
825
  state->chainStates->Utils.Dict.forEach(cs => cs->ChainState.resetEntities(~perChainEntities))
812
826
  state.effectState->EffectState.resetForRollback
827
+ // The address store reports only what each rollback killed, and never
828
+ // re-reports a registration it already tombstoned. A rollback that lands
829
+ // before the previous one has been written must therefore carry the earlier
830
+ // keys too, or their rows outlive every rollback that could delete them.
831
+ let rolledBackAddresses = switch state.rollback {
832
+ | Some({rolledBackAddresses: pending}) => pending->Array.concat(rolledBackAddresses)
833
+ | None => rolledBackAddresses
834
+ }
835
+ // Same for the chains: this rollback recomputes progress from the checkpoints,
836
+ // so a chain the pending diff already moved can land on exactly the block it
837
+ // is now at and go unreported here. Its stored progress still needs
838
+ // correcting, so the pending rows carry over — this rollback's row for a
839
+ // chain wins, being the later reading of the same chain state.
840
+ let progressedChains = switch state.rollback {
841
+ | Some({progressedChains: pending}) =>
842
+ let byChainId = Dict.make()
843
+ pending->Array.forEach(chain => byChainId->ChainId.Dict.set(chain.chainId, chain))
844
+ progressedChains->Array.forEach(chain => byChainId->ChainId.Dict.set(chain.chainId, chain))
845
+ byChainId->Dict.valuesToArray
846
+ | None => progressedChains
847
+ }
813
848
  state.rollback = Some({
814
849
  targetCheckpointId,
815
850
  diffCheckpointId,
816
- progressBlockNumberByChainId,
851
+ scope,
852
+ progressedChains,
853
+ rolledBackAddresses,
817
854
  })
818
855
  }
819
856
 
@@ -100,7 +100,7 @@ function makeFromDbState(config, persistence, initialState, registrationsByChain
100
100
  initialState.chains.forEach(resumedChainState => {
101
101
  let chainId = Config.getChain(config, resumedChainState.id);
102
102
  let chainConfig = ChainMap.get(config.chainMap, chainId);
103
- chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, registrationsByChainId, reducedPollingInterval);
103
+ chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, initialState.contractMapping, registrationsByChainId, reducedPollingInterval);
104
104
  });
105
105
  let state = make(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSize, initialState.checkpointId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
106
106
  CrossChainState.markCaughtUpOnResume(state.crossChainState);
@@ -625,6 +625,7 @@ function drainBatchRun(state) {
625
625
  let checkpointBlockNumbers = [];
626
626
  let checkpointBlockHashes = [];
627
627
  let checkpointEventsProcessed = [];
628
+ let registeredAddresses = [];
628
629
  all.forEach(batch => {
629
630
  if (Utils.$$Array.isEmpty(rest) && batch.isInReorgThreshold === isInReorgThreshold) {
630
631
  Utils.Dict.forEachWithKey(batch.progressedChainsById, (chainAfterBatch, key) => {
@@ -637,6 +638,7 @@ function drainBatchRun(state) {
637
638
  checkpointBlockNumbers.push(...batch.checkpointBlockNumbers);
638
639
  checkpointBlockHashes.push(...batch.checkpointBlockHashes);
639
640
  checkpointEventsProcessed.push(...batch.checkpointEventsProcessed);
641
+ registeredAddresses.push(...batch.registeredAddresses);
640
642
  } else {
641
643
  rest.push(batch);
642
644
  }
@@ -651,10 +653,15 @@ function drainBatchRun(state) {
651
653
  checkpointChainIds: checkpointChainIds,
652
654
  checkpointBlockNumbers: checkpointBlockNumbers,
653
655
  checkpointBlockHashes: checkpointBlockHashes,
654
- checkpointEventsProcessed: checkpointEventsProcessed
656
+ checkpointEventsProcessed: checkpointEventsProcessed,
657
+ registeredAddresses: registeredAddresses
655
658
  };
656
659
  }
657
660
 
661
+ function pendingRollback(state) {
662
+ return state.rollback;
663
+ }
664
+
658
665
  function takeRollback(state) {
659
666
  let rollback = state.rollback;
660
667
  state.rollback = undefined;
@@ -665,15 +672,33 @@ function markCommitted(state, upToCheckpointId) {
665
672
  state.committedCheckpointId = upToCheckpointId;
666
673
  }
667
674
 
668
- function beginRollbackDiff(state, targetCheckpointId, diffCheckpointId, progressBlockNumberByChainId) {
675
+ function beginRollbackDiff(state, targetCheckpointId, diffCheckpointId, scope, progressedChains, rolledBackAddresses) {
669
676
  let perChainEntities = EntityTables.perChain(state.allEntities);
670
677
  state.entities = EntityTables.make(EntityTables.crossChain(state.allEntities));
671
678
  Utils.Dict.forEach(CrossChainState.chainStates(state.crossChainState), cs => ChainState.resetEntities(cs, perChainEntities));
672
679
  EffectState.resetForRollback(state.effectState);
680
+ let match = state.rollback;
681
+ let rolledBackAddresses$1 = match !== undefined ? match.rolledBackAddresses.concat(rolledBackAddresses) : rolledBackAddresses;
682
+ let match$1 = state.rollback;
683
+ let progressedChains$1;
684
+ if (match$1 !== undefined) {
685
+ let byChainId = {};
686
+ match$1.progressedChains.forEach(chain => {
687
+ byChainId[chain.chainId] = chain;
688
+ });
689
+ progressedChains.forEach(chain => {
690
+ byChainId[chain.chainId] = chain;
691
+ });
692
+ progressedChains$1 = Object.values(byChainId);
693
+ } else {
694
+ progressedChains$1 = progressedChains;
695
+ }
673
696
  state.rollback = {
674
697
  targetCheckpointId: targetCheckpointId,
675
698
  diffCheckpointId: diffCheckpointId,
676
- progressBlockNumberByChainId: progressBlockNumberByChainId
699
+ scope: scope,
700
+ rolledBackAddresses: rolledBackAddresses$1,
701
+ progressedChains: progressedChains$1
677
702
  };
678
703
  }
679
704
 
@@ -806,6 +831,7 @@ export {
806
831
  recordRollbackSuccess,
807
832
  queueProcessedBatch,
808
833
  drainBatchRun,
834
+ pendingRollback,
809
835
  takeRollback,
810
836
  markCommitted,
811
837
  beginRollbackDiff,
@@ -136,13 +136,16 @@ let recordRollbackSuccess: (t, ~timeSeconds: float, ~rollbackedProcessedEvents:
136
136
  // Store domain operations.
137
137
  let queueProcessedBatch: (t, ~batch: Batch.t) => unit
138
138
  let drainBatchRun: t => Batch.t
139
+ let pendingRollback: t => option<Persistence.rollback>
139
140
  let takeRollback: t => option<Persistence.rollback>
140
141
  let markCommitted: (t, ~upToCheckpointId: Internal.checkpointId) => unit
141
142
  let beginRollbackDiff: (
142
143
  t,
143
144
  ~targetCheckpointId: Internal.checkpointId,
144
145
  ~diffCheckpointId: Internal.checkpointId,
145
- ~progressBlockNumberByChainId: dict<int>,
146
+ ~scope: RollbackScope.t,
147
+ ~progressedChains: array<InternalTable.Chains.progressedChain>,
148
+ ~rolledBackAddresses: array<AddressRows.key>,
146
149
  ) => unit
147
150
  let recordWriteFailure: (t, exn) => unit
148
151
  let beginWriteFiber: (t, promise<unit>) => unit
package/src/Internal.res CHANGED
@@ -573,15 +573,6 @@ type svmInstructionEventConfig = {
573
573
  programId: SvmTypes.Pubkey.t,
574
574
  /** Hex-encoded discriminator. `None` matches every instruction in the program. */
575
575
  discriminator: option<string>,
576
- /** Length of the discriminator in bytes (0 / 1 / 2 / 4 / 8). Drives the
577
- `dN` selector at query time and the dispatch-key precomputation in the
578
- router. */
579
- discriminatorByteLen: int,
580
- /** Disjunctive normal form: outer array is OR of AND-groups, inner array is
581
- AND across positions. Empty outer array means "no account filter". */
582
- accountFilters: array<svmAccountFilterGroup>,
583
- /** `None` matches both outer and inner (CPI-invoked) instructions. */
584
- isInner: option<bool>,
585
576
  /** Positional account names from the Borsh schema, in declared order.
586
577
  `[]` means no schema is attached for this instruction. */
587
578
  accounts: array<string>,
@@ -597,10 +588,11 @@ type svmInstructionEventConfig = {
597
588
  // Per-(event, chain) registration produced when user handler code registers an
598
589
  // event (`onEvent`) or a dynamic contract registers. References its definition
599
590
  // by value as `.eventConfig` and adds the handler binding plus the
600
- // registration/`where`-derived fetch state. Not `private`: Fuel/SVM
601
- // registrations add no ecosystem-specific fields (so they're bare aliases that
602
- // must stay directly constructable), and the evm→base cast in sources is sound
603
- // by ecosystem homogeneity — an EVM chain only ever holds `evmOnEventRegistration`s.
591
+ // registration/`where`-derived fetch state. Not `private`: Fuel registrations
592
+ // add no ecosystem-specific fields (so the alias must stay directly
593
+ // constructable), and the ecosystem→base casts in sources are sound by
594
+ // ecosystem homogeneity — an EVM chain only ever holds
595
+ // `evmOnEventRegistration`s.
604
596
  type onEventRegistration = {
605
597
  // Chain-scoped sequential index — the registration's position in the
606
598
  // chain's onEventRegistrations array, assigned when registration finishes
@@ -639,11 +631,18 @@ type evmOnEventRegistration = {
639
631
  resolvedWhere: resolvedWhere,
640
632
  }
641
633
 
642
- // Fuel and SVM registrations add no ecosystem-specific fetch state (their
643
- // filters are config-derived and live on the definition), so they're bare
644
- // aliases of the base registration.
634
+ // Fuel registrations add no ecosystem-specific fetch state, so it's a bare
635
+ // alias of the base registration.
645
636
  type fuelOnEventRegistration = onEventRegistration
646
- type svmOnEventRegistration = onEventRegistration
637
+
638
+ type svmOnEventRegistration = {
639
+ ...onEventRegistration,
640
+ /** Disjunctive normal form: outer array is OR of AND-groups, inner array is
641
+ AND across positions. Empty outer array means "no account filter". */
642
+ accountFilters: array<svmAccountFilterGroup>,
643
+ /** `None` matches both outer and inner (CPI-invoked) instructions. */
644
+ isInner: option<bool>,
645
+ }
647
646
 
648
647
  type svmProgramConfig = {
649
648
  name: string,
package/src/Main.res CHANGED
@@ -88,17 +88,22 @@ let getGlobalPersistence = () =>
88
88
  let setGlobalPersistence = (persistence: Persistence.t) =>
89
89
  EnvioGlobal.value.persistence = Some(persistence->(Utils.magic: Persistence.t => unknown))
90
90
 
91
- let getInitialChainState = (~chainId: ChainId.t): option<Persistence.initialChainState> => {
91
+ let getInitialState = (): option<Persistence.initialState> => {
92
92
  switch getGlobalPersistence() {
93
93
  | Some(persistence) =>
94
94
  switch persistence.storageStatus {
95
- | Ready(initialState) => initialState.chains->Array.find(c => c.id === chainId)
95
+ | Ready(initialState) => Some(initialState)
96
96
  | _ => None
97
97
  }
98
98
  | None => None
99
99
  }
100
100
  }
101
101
 
102
+ let getInitialChainState = (~chainId: ChainId.t): option<Persistence.initialChainState> =>
103
+ getInitialState()->Option.flatMap(initialState =>
104
+ initialState.chains->Array.find(c => c.id === chainId)
105
+ )
106
+
102
107
  // Importing `generated` must not trigger `Config.load()`,
103
108
  // so the exported indexer calls this lazily on first `indexer.chains` access.
104
109
  let buildChainsObject = (~config: Config.t) => {
@@ -184,20 +189,23 @@ let buildChainsObject = (~config: Config.t) => {
184
189
  chainState->ChainState.contractAddresses(~contractName=contract.name)
185
190
  }
186
191
  // Before the global state is available (eg during handler
187
- // module load after resume), combine static addresses from config
188
- // with dynamic contracts persisted in the database.
192
+ // module load after resume), read them off what persistence
193
+ // restored — which already holds the config's own addresses
194
+ // alongside the dynamically registered ones.
189
195
  | None =>
190
- switch getInitialChainState(~chainId=chainConfig.id) {
191
- | Some(chainState) =>
192
- let addresses = contract.addresses->Array.copy
193
- chainState.indexingAddresses->Array.forEach(
194
- dc => {
195
- if dc.contractName === contract.name {
196
- addresses->Array.push(dc.address)->ignore
197
- }
198
- },
199
- )
200
- addresses
196
+ switch getInitialState() {
197
+ | Some(initialState) =>
198
+ switch initialState.chains->Array.find(c => c.id === chainConfig.id) {
199
+ | Some(chainState) =>
200
+ chainState.addressRows->AddressRows.renderOfContract(
201
+ ~ecosystem=(config.ecosystem.name :> string),
202
+ ~shouldChecksum=!config.lowercaseAddresses,
203
+ ~contractId=initialState.contractMapping->ContractMapping.idOfOrThrow(
204
+ contract.name,
205
+ ),
206
+ )
207
+ | None => contract.addresses
208
+ }
201
209
  | None => contract.addresses
202
210
  }
203
211
  }
@@ -577,6 +585,7 @@ let migrate = async (~reset) => {
577
585
  await persistence->Persistence.init(
578
586
  ~reset,
579
587
  ~chainConfigs=config.chainMap->ChainMap.values,
588
+ ~contractMapping=config.contractMapping,
580
589
  ~envioInfo=getEnvioInfo(),
581
590
  ~resetCommand="envio local db-migrate setup",
582
591
  ~runCommand=None,
@@ -628,6 +637,7 @@ let start = async (
628
637
  await persistence->Persistence.init(
629
638
  ~reset,
630
639
  ~chainConfigs=config.chainMap->ChainMap.values,
640
+ ~contractMapping=config.contractMapping,
631
641
  ~envioInfo=getEnvioInfo(),
632
642
  ~resetCommand=isDevelopmentMode ? "envio dev -r" : "envio start -r",
633
643
  ~runCommand=Some(isDevelopmentMode ? "envio dev" : "envio start"),
package/src/Main.res.mjs CHANGED
@@ -13,6 +13,7 @@ import * as Process from "process";
13
13
  import * as ChainMap from "./ChainMap.res.mjs";
14
14
  import * as PgStorage from "./PgStorage.res.mjs";
15
15
  import * as ChainState from "./ChainState.res.mjs";
16
+ import * as AddressRows from "./AddressRows.res.mjs";
16
17
  import * as EnvioGlobal from "./EnvioGlobal.res.mjs";
17
18
  import * as IndexerLoop from "./IndexerLoop.res.mjs";
18
19
  import * as Persistence from "./Persistence.res.mjs";
@@ -24,6 +25,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
24
25
  import * as Helpers from "yargs/helpers";
25
26
  import * as RollbackCommit from "./RollbackCommit.res.mjs";
26
27
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
28
+ import * as ContractMapping from "./ContractMapping.res.mjs";
27
29
  import * as HandlerRegister from "./HandlerRegister.res.mjs";
28
30
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
29
31
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -94,7 +96,7 @@ function setGlobalPersistence(persistence) {
94
96
  EnvioGlobal.value.persistence = Primitive_option.some(persistence);
95
97
  }
96
98
 
97
- function getInitialChainState(chainId) {
99
+ function getInitialState() {
98
100
  let persistence = EnvioGlobal.value.persistence;
99
101
  if (persistence === undefined) {
100
102
  return;
@@ -103,10 +105,14 @@ function getInitialChainState(chainId) {
103
105
  if (typeof initialState !== "object" || initialState.TAG === "Initializing") {
104
106
  return;
105
107
  } else {
106
- return initialState._0.chains.find(c => c.id === chainId);
108
+ return initialState._0;
107
109
  }
108
110
  }
109
111
 
112
+ function getInitialChainState(chainId) {
113
+ return Stdlib_Option.flatMap(getInitialState(), initialState => initialState.chains.find(c => c.id === chainId));
114
+ }
115
+
110
116
  function buildChainsObject(config) {
111
117
  let chainIds = [];
112
118
  let chains = Object.create(null);
@@ -178,18 +184,16 @@ function buildChainsObject(config) {
178
184
  let chainState = IndexerState.getChainState(Primitive_option.valFromOption(state), chainConfig.id);
179
185
  return ChainState.contractAddresses(chainState, contract.name);
180
186
  }
181
- let chainState$1 = getInitialChainState(chainConfig.id);
182
- if (chainState$1 === undefined) {
187
+ let initialState = getInitialState();
188
+ if (initialState === undefined) {
189
+ return contract.addresses;
190
+ }
191
+ let chainState$1 = initialState.chains.find(c => c.id === chainConfig.id);
192
+ if (chainState$1 !== undefined) {
193
+ return AddressRows.renderOfContract(chainState$1.addressRows, config.ecosystem.name, !config.lowercaseAddresses, ContractMapping.idOfOrThrow(initialState.contractMapping, contract.name, undefined));
194
+ } else {
183
195
  return contract.addresses;
184
196
  }
185
- let addresses = contract.addresses.slice();
186
- chainState$1.indexingAddresses.forEach(dc => {
187
- if (dc.contractName === contract.name) {
188
- addresses.push(dc.address);
189
- return;
190
- }
191
- });
192
- return addresses;
193
197
  }
194
198
  });
195
199
  Object.defineProperty(chainObj, contract.name, {
@@ -463,7 +467,7 @@ function getEnvioInfo() {
463
467
  async function migrate(reset) {
464
468
  let config = Config.load();
465
469
  let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
466
- await Persistence.init(persistence, ChainMap.values(config.chainMap), Config.stripSensitiveData(Config.getPublicConfigJson()), "envio local db-migrate setup", undefined, reset);
470
+ await Persistence.init(persistence, ChainMap.values(config.chainMap), config.contractMapping, Config.stripSensitiveData(Config.getPublicConfigJson()), "envio local db-migrate setup", undefined, reset);
467
471
  return await persistence.storage.close();
468
472
  }
469
473
 
@@ -490,7 +494,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
490
494
  let isDevelopmentMode = !isTest && config.isDev;
491
495
  let persistence$1 = persistence !== undefined ? persistence : PgStorage.makePersistenceFromConfig(config, undefined);
492
496
  EnvioGlobal.value.persistence = Primitive_option.some(persistence$1);
493
- await Persistence.init(persistence$1, ChainMap.values(config.chainMap), Config.stripSensitiveData(Config.getPublicConfigJson()), isDevelopmentMode ? "envio dev -r" : "envio start -r", isDevelopmentMode ? "envio dev" : "envio start", reset);
497
+ await Persistence.init(persistence$1, ChainMap.values(config.chainMap), config.contractMapping, Config.stripSensitiveData(Config.getPublicConfigJson()), isDevelopmentMode ? "envio dev -r" : "envio start -r", isDevelopmentMode ? "envio dev" : "envio start", reset);
494
498
  let registrationsByChainId = await HandlerLoader.registerAllHandlers(config);
495
499
  let config$1;
496
500
  if (isTest) {
@@ -552,6 +556,7 @@ export {
552
556
  setIndexerState,
553
557
  getGlobalPersistence,
554
558
  setGlobalPersistence,
559
+ getInitialState,
555
560
  getInitialChainState,
556
561
  buildChainsObject,
557
562
  getGlobalIndexer,
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"