envio 3.8.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 (64) hide show
  1. package/README.md +2 -2
  2. package/index.d.ts +158 -104
  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 +76 -70
  9. package/src/ChainState.res.mjs +76 -65
  10. package/src/ChainState.resi +14 -9
  11. package/src/Config.res +23 -82
  12. package/src/Config.res.mjs +19 -63
  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/EventConfigBuilder.res +0 -2
  18. package/src/EventConfigBuilder.res.mjs +0 -2
  19. package/src/FetchState.res +59 -67
  20. package/src/FetchState.res.mjs +34 -44
  21. package/src/InMemoryStore.res +17 -30
  22. package/src/InMemoryStore.res.mjs +11 -23
  23. package/src/IndexerState.res +21 -1
  24. package/src/IndexerState.res.mjs +9 -3
  25. package/src/IndexerState.resi +1 -0
  26. package/src/Main.res +25 -15
  27. package/src/Main.res.mjs +19 -14
  28. package/src/MemoryStorage.res +39 -66
  29. package/src/MemoryStorage.res.mjs +28 -65
  30. package/src/Metrics.res +15 -1
  31. package/src/Metrics.res.mjs +5 -1
  32. package/src/Persistence.res +30 -16
  33. package/src/Persistence.res.mjs +8 -5
  34. package/src/PgStorage.res +183 -65
  35. package/src/PgStorage.res.mjs +92 -58
  36. package/src/Rollback.res +14 -10
  37. package/src/Rollback.res.mjs +4 -2
  38. package/src/SimulateItems.res +5 -12
  39. package/src/SimulateItems.res.mjs +6 -10
  40. package/src/TestIndexer.res +93 -111
  41. package/src/TestIndexer.res.mjs +61 -88
  42. package/src/Utils.res +7 -0
  43. package/src/Utils.res.mjs +9 -2
  44. package/src/Writing.res +2 -0
  45. package/src/Writing.res.mjs +2 -1
  46. package/src/bindings/ClickHouse.res +6 -0
  47. package/src/bindings/ClickHouse.res.mjs +4 -0
  48. package/src/bindings/NodeJs.res +9 -0
  49. package/src/bindings/NodeJs.res.mjs +8 -1
  50. package/src/bindings/Postgres.res +9 -0
  51. package/src/bindings/Postgres.res.mjs +3 -0
  52. package/src/db/EntityHistory.res +12 -18
  53. package/src/db/EntityHistory.res.mjs +3 -14
  54. package/src/db/InternalTable.res +171 -65
  55. package/src/db/InternalTable.res.mjs +176 -73
  56. package/src/db/Table.res +24 -0
  57. package/src/db/Table.res.mjs +20 -1
  58. package/src/sources/AddressSet.res +0 -22
  59. package/src/sources/AddressStore.res +48 -88
  60. package/src/sources/AddressStore.res.mjs +11 -22
  61. package/src/sources/SvmHyperSyncClient.res +17 -21
  62. package/src/sources/SvmHyperSyncSource.res +4 -9
  63. package/src/sources/SvmHyperSyncSource.res.mjs +5 -13
  64. package/svm.schema.json +10 -4
@@ -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,8 +106,8 @@ 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, rollbackTargetCheckpointId, rollbackDiffCheckpointId, progressBlockNumberByChainId, rolledBackAddresses) {
110
+ IndexerState.beginRollbackDiff(state, rollbackTargetCheckpointId, rollbackDiffCheckpointId, progressBlockNumberByChainId, rolledBackAddresses);
112
111
  let persistence = IndexerState.persistence(state);
113
112
  let committedCheckpointId = IndexerState.committedCheckpointId(state);
114
113
  let deletedEntities = {};
@@ -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,6 +789,7 @@ let drainBatchRun = (state: t): Batch.t => {
786
789
  checkpointBlockNumbers,
787
790
  checkpointBlockHashes,
788
791
  checkpointEventsProcessed,
792
+ registeredAddresses,
789
793
  }
790
794
  }
791
795
 
@@ -797,7 +801,13 @@ let takeRollback = (state: t): option<Persistence.rollback> => {
797
801
  }
798
802
 
799
803
  // Advance the committed (durably persisted) frontier after a successful write.
800
- let markCommitted = (state: t, ~upToCheckpointId) => state.committedCheckpointId = upToCheckpointId
804
+ // Written rows leave the buffer only once the transaction that holds them has
805
+ // committed. A failed write keeps them, and re-inserting a row the database
806
+ // already has is a no-op. Rows staged while the write was in flight belong to
807
+ // later checkpoints — ids only ever grow — so this can't drop one unwritten.
808
+ let markCommitted = (state: t, ~upToCheckpointId) => {
809
+ state.committedCheckpointId = upToCheckpointId
810
+ }
801
811
 
802
812
  // Reset the in-memory tables and arm the rollback diff that the next write commits.
803
813
  let beginRollbackDiff = (
@@ -805,15 +815,25 @@ let beginRollbackDiff = (
805
815
  ~targetCheckpointId,
806
816
  ~diffCheckpointId,
807
817
  ~progressBlockNumberByChainId,
818
+ ~rolledBackAddresses,
808
819
  ) => {
809
820
  let perChainEntities = state.allEntities->EntityTables.perChain
810
821
  state.entities = EntityTables.make(state.allEntities->EntityTables.crossChain)
811
822
  state->chainStates->Utils.Dict.forEach(cs => cs->ChainState.resetEntities(~perChainEntities))
812
823
  state.effectState->EffectState.resetForRollback
824
+ // The address store reports only what each rollback killed, and never
825
+ // re-reports a registration it already tombstoned. A rollback that lands
826
+ // before the previous one has been written must therefore carry the earlier
827
+ // keys too, or their rows outlive every rollback that could delete them.
828
+ let rolledBackAddresses = switch state.rollback {
829
+ | Some({rolledBackAddresses: pending}) => pending->Array.concat(rolledBackAddresses)
830
+ | None => rolledBackAddresses
831
+ }
813
832
  state.rollback = Some({
814
833
  targetCheckpointId,
815
834
  diffCheckpointId,
816
835
  progressBlockNumberByChainId,
836
+ rolledBackAddresses,
817
837
  })
818
838
  }
819
839
 
@@ -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,7 +653,8 @@ 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
 
@@ -665,14 +668,17 @@ function markCommitted(state, upToCheckpointId) {
665
668
  state.committedCheckpointId = upToCheckpointId;
666
669
  }
667
670
 
668
- function beginRollbackDiff(state, targetCheckpointId, diffCheckpointId, progressBlockNumberByChainId) {
671
+ function beginRollbackDiff(state, targetCheckpointId, diffCheckpointId, progressBlockNumberByChainId, rolledBackAddresses) {
669
672
  let perChainEntities = EntityTables.perChain(state.allEntities);
670
673
  state.entities = EntityTables.make(EntityTables.crossChain(state.allEntities));
671
674
  Utils.Dict.forEach(CrossChainState.chainStates(state.crossChainState), cs => ChainState.resetEntities(cs, perChainEntities));
672
675
  EffectState.resetForRollback(state.effectState);
676
+ let match = state.rollback;
677
+ let rolledBackAddresses$1 = match !== undefined ? match.rolledBackAddresses.concat(rolledBackAddresses) : rolledBackAddresses;
673
678
  state.rollback = {
674
679
  targetCheckpointId: targetCheckpointId,
675
680
  diffCheckpointId: diffCheckpointId,
681
+ rolledBackAddresses: rolledBackAddresses$1,
676
682
  progressBlockNumberByChainId: progressBlockNumberByChainId
677
683
  };
678
684
  }
@@ -143,6 +143,7 @@ let beginRollbackDiff: (
143
143
  ~targetCheckpointId: Internal.checkpointId,
144
144
  ~diffCheckpointId: Internal.checkpointId,
145
145
  ~progressBlockNumberByChainId: dict<int>,
146
+ ~rolledBackAddresses: array<AddressRows.key>,
146
147
  ) => unit
147
148
  let recordWriteFailure: (t, exn) => unit
148
149
  let beginWriteFiber: (t, promise<unit>) => unit
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,
@@ -49,6 +49,7 @@ type t = {
49
49
  cache: dict<Persistence.effectCacheRecord>,
50
50
  effectCache: dict<dict<Internal.effectCacheItem>>,
51
51
  mutable envioInfo: option<JSON.t>,
52
+ addresses: AddressRows.Table.t,
52
53
  mutable isInitialized: bool,
53
54
  }
54
55
 
@@ -61,6 +62,7 @@ let make = (): t => {
61
62
  cache: Dict.make(),
62
63
  effectCache: Dict.make(),
63
64
  envioInfo: None,
65
+ addresses: AddressRows.Table.make(),
64
66
  isInitialized: false,
65
67
  }
66
68
 
@@ -117,61 +119,23 @@ let registerEntities = (state: t, ~entities: array<Internal.entityConfig>) =>
117
119
 
118
120
  // Seeds the config's contract addresses, mirroring what PgStorage.initialize
119
121
  // writes into `envio_addresses`.
120
- let seedIndexingAddresses = (state: t, ~chainConfigs: array<Config.chain>) => {
121
- let dict = state->getEntityDict(~name=InternalTable.EnvioAddresses.name)
122
- chainConfigs->Array.forEach(chainConfig =>
123
- chainConfig.contracts->Array.forEach(contract =>
124
- contract.addresses->Array.forEach(
125
- address => {
126
- let entity: InternalTable.EnvioAddresses.t = {
127
- id: Config.EnvioAddresses.makeId(~chainId=chainConfig.id, ~address),
128
- chainId: chainConfig.id,
129
- contractName: contract.name,
130
- registrationBlock: -1,
131
- registrationLogIndex: -1,
132
- }
133
- dict->Dict.set(entity.id, entity->Config.EnvioAddresses.castToInternal)
134
- },
135
- )
136
- )
137
- )
122
+ let seedConfigAddresses = (
123
+ state: t,
124
+ ~chainConfigs: array<Config.chain>,
125
+ ~contractMapping,
126
+ ~ecosystem,
127
+ ) => {
128
+ // Initialize starts from an empty schema — the Postgres DDL drops and
129
+ // recreates one — so a re-initialize must not stack a second copy of the
130
+ // config's addresses on what a previous one left behind.
131
+ state.addresses->AddressRows.Table.clear
132
+ state.addresses->ChainState.seedConfigAddresses(~chainConfigs, ~ecosystem, ~contractMapping)
138
133
  }
139
134
 
140
- external castToEnvioAddresses: Internal.entity => InternalTable.EnvioAddresses.t = "%identity"
141
-
142
- let toIndexingAddress = (dc: InternalTable.EnvioAddresses.t): Internal.indexingAddress => {
143
- address: dc->Config.EnvioAddresses.getAddress,
144
- contractName: dc.contractName,
145
- registrationBlock: dc.registrationBlock,
146
- }
147
-
148
- // All indexing addresses (config-seeded + dynamically registered) grouped by
149
- // chain id string, derived from the envio_addresses entities.
150
- let getIndexingAddressesByChain = (state: t): dict<array<Internal.indexingAddress>> => {
151
- let byChain = Dict.make()
152
- switch state.entities->Dict.get(InternalTable.EnvioAddresses.name) {
153
- | Some(dcDict) =>
154
- dcDict
155
- ->Dict.valuesToArray
156
- ->Array.forEach(entity => {
157
- let dc = entity->castToEnvioAddresses
158
- let chainIdStr = dc.chainId->ChainId.toString
159
- let contracts = switch byChain->Dict.get(chainIdStr) {
160
- | Some(arr) => arr
161
- | None =>
162
- let arr = []
163
- byChain->Dict.set(chainIdStr, arr)
164
- arr
165
- }
166
- contracts->Array.push(dc->toIndexingAddress)->ignore
167
- })
168
- | None => ()
169
- }
170
- byChain
171
- }
135
+ let addressRowsByChain = (state: t) => state.addresses->AddressRows.Table.groupByChain
172
136
 
173
137
  let toInitialChainStates = (state: t): array<Persistence.initialChainState> => {
174
- let addressesByChain = state->getIndexingAddressesByChain
138
+ let addressesByChain = state->addressRowsByChain
175
139
  state.chains
176
140
  ->Dict.valuesToArray
177
141
  ->Array.map((chain): Persistence.initialChainState => {
@@ -183,9 +147,9 @@ let toInitialChainStates = (state: t): array<Persistence.initialChainState> => {
183
147
  numEventsProcessed: chain.numEventsProcessed,
184
148
  firstEventBlockNumber: chain.firstEventBlockNumber,
185
149
  timestampCaughtUpToHeadOrEndblock: chain.timestampCaughtUpToHeadOrEndblock,
186
- indexingAddresses: addressesByChain
187
- ->Dict.get(chain.id->ChainId.toString)
188
- ->Option.getOr([]),
150
+ addressRows: addressesByChain
151
+ ->Utils.Dict.dangerouslyGetNonOption(chain.id->ChainId.toString)
152
+ ->Option.getOr(AddressRows.emptySeedRows()),
189
153
  sourceBlockNumber: chain.sourceBlockNumber,
190
154
  })
191
155
  }
@@ -220,13 +184,14 @@ let reorgCheckpoints = (state: t): array<Internal.reorgCheckpoint> =>
220
184
  }
221
185
  )
222
186
 
223
- let toInitialState = (state: t, ~cleanRun): Persistence.initialState => {
187
+ let toInitialState = (state: t, ~cleanRun, ~contractMapping): Persistence.initialState => {
224
188
  cleanRun,
189
+ contractMapping,
190
+ envioInfo: state.envioInfo,
225
191
  cache: state.cache,
226
192
  chains: state->toInitialChainStates,
227
193
  checkpointId: state->committedCheckpointId,
228
194
  reorgCheckpoints: state->reorgCheckpoints,
229
- envioInfo: state.envioInfo,
230
195
  }
231
196
 
232
197
  let handleLoad = (state: t, ~tableName: string, ~filter: EntityFilter.t): array<
@@ -305,8 +270,10 @@ let backfillHistory = (
305
270
  }
306
271
  }
307
272
 
308
- let applyRollback = (state: t, ~targetCheckpointId) => {
273
+ let applyRollback = (state: t, ~targetCheckpointId, ~rolledBackAddresses) => {
309
274
  state.checkpoints = state.checkpoints->Array.filter(cp => cp.id <= targetCheckpointId)
275
+ // Addresses are removed by primary key, exactly like the Postgres delete.
276
+ rolledBackAddresses->Array.forEach(key => state.addresses->AddressRows.Table.delete(key))
310
277
  state.history
311
278
  ->Dict.toArray
312
279
  ->Array.forEach(((name, rows)) =>
@@ -321,6 +288,7 @@ let writeBatch = (
321
288
  ~isInReorgThreshold,
322
289
  ~config: Config.t,
323
290
  ~updatedEntities: array<Persistence.updatedEntity>,
291
+ ~registeredAddresses: array<AddressRows.staged>,
324
292
  ~updatedEffectsCache: array<Persistence.updatedEffectCache>,
325
293
  ~chainMetaData: option<dict<InternalTable.Chains.metaFields>>,
326
294
  ) => {
@@ -329,10 +297,13 @@ let writeBatch = (
329
297
  // Rollback first, exactly like the Postgres transaction: the batch being
330
298
  // written is the reprocessed one, so its rows must land on the reverted state.
331
299
  switch rollback {
332
- | Some({targetCheckpointId}) => state->applyRollback(~targetCheckpointId)
300
+ | Some({targetCheckpointId, rolledBackAddresses}) =>
301
+ state->applyRollback(~targetCheckpointId, ~rolledBackAddresses)
333
302
  | None => ()
334
303
  }
335
304
 
305
+ registeredAddresses->Array.forEach(({row}) => state.addresses->AddressRows.Table.insert(row))
306
+
336
307
  // The rollback diff restates what the reverted state already is, so it is not
337
308
  // a change history should record — and an id it touches needs no backfill
338
309
  // either, because the diff already carries its reverted value. Postgres draws
@@ -503,8 +474,7 @@ let getRollbackData = (
503
474
  ->ignore
504
475
  | Some({action: DELETE, entityId, scope}) =>
505
476
  removals->Array.push({Persistence.entityId, scope})->ignore
506
- | Some({action: SET, entity: Some(entity)}) =>
507
- restored->Array.push(entity)->ignore
477
+ | Some({action: SET, entity: Some(entity)}) => restored->Array.push(entity)->ignore
508
478
  | Some({action: SET, entity: None}) => ()
509
479
  }
510
480
  })
@@ -553,9 +523,9 @@ let getRollbackProgressDiff = (state: t, ~rollbackTargetCheckpointId) => {
553
523
  let toStorage = (state: t, ~config: Config.t): Persistence.storage => {
554
524
  name: "memory",
555
525
  isInitialized: async () => state.isInitialized,
556
- initialize: async (~chainConfigs=[], ~entities=[], ~enums as _=[], ~envioInfo) => {
526
+ initialize: async (~chainConfigs=[], ~entities=[], ~enums as _=[], ~contractMapping, ~envioInfo) => {
557
527
  state->registerEntities(~entities)
558
- state->seedIndexingAddresses(~chainConfigs)
528
+ state->seedConfigAddresses(~chainConfigs, ~contractMapping, ~ecosystem=config.ecosystem.name)
559
529
  chainConfigs->Array.forEach(chainConfig =>
560
530
  state.chains->Dict.set(
561
531
  chainConfig.id->ChainId.toString,
@@ -575,9 +545,10 @@ let toStorage = (state: t, ~config: Config.t): Persistence.storage => {
575
545
  )
576
546
  state.envioInfo = Some(envioInfo)
577
547
  state.isInitialized = true
578
- state->toInitialState(~cleanRun=true)
548
+ state->toInitialState(~cleanRun=true, ~contractMapping)
579
549
  },
580
- resumeInitialState: async () => state->toInitialState(~cleanRun=false),
550
+ resumeInitialState: async () =>
551
+ state->toInitialState(~cleanRun=false, ~contractMapping=config.contractMapping),
581
552
  loadOrThrow: async (~filter, ~table: Table.table) =>
582
553
  state
583
554
  ->handleLoad(~tableName=table.tableName, ~filter)
@@ -594,14 +565,14 @@ let toStorage = (state: t, ~config: Config.t): Persistence.storage => {
594
565
  ),
595
566
  dumpEffectCache: async () => (),
596
567
  reset: async () => {
597
- let clear = dict =>
598
- dict->Dict.keysToArray->Array.forEach(key => dict->Utils.Dict.deleteInPlace(key))
568
+ let clear = Utils.Dict.clearInPlace
599
569
  state.entities->clear
600
570
  state.history->clear
601
571
  state.chains->clear
602
572
  state.effectCache->clear
603
573
  state.cache->clear
604
574
  state.checkpoints = []
575
+ state.addresses->AddressRows.Table.clear
605
576
  state.envioInfo = None
606
577
  state.isInitialized = false
607
578
  },
@@ -680,6 +651,7 @@ let toStorage = (state: t, ~config: Config.t): Persistence.storage => {
680
651
  ~allEntities as _,
681
652
  ~updatedEffectsCache,
682
653
  ~updatedEntities,
654
+ ~registeredAddresses,
683
655
  ~chainMetaData,
684
656
  ~onWrite as _,
685
657
  ) =>
@@ -689,6 +661,7 @@ let toStorage = (state: t, ~config: Config.t): Persistence.storage => {
689
661
  ~isInReorgThreshold,
690
662
  ~config,
691
663
  ~updatedEntities,
664
+ ~registeredAddresses,
692
665
  ~updatedEffectsCache,
693
666
  ~chainMetaData,
694
667
  ),