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/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
- )
138
- }
139
-
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,
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)
146
133
  }
147
134
 
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,23 +184,37 @@ 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<
233
198
  Internal.entity,
234
199
  > => {
235
- // Loads for non-entity tables (e.g. effect caches `envio_effect_<name>`) reach
236
- // here too. Nothing persists those, so there's nothing to return an empty
237
- // result makes the effect recompute instead of crashing on a missing config.
200
+ // Effect caches (`envio_effect_<name>`) are loaded through the same call, and
201
+ // they have no entity config they're served from the cache `writeBatch`
202
+ // filled, so a resumed indexer reuses cached outputs instead of recomputing
203
+ // them the way Postgres would.
238
204
  switch state.entityConfigs->Dict.get(tableName) {
239
- | None => []
205
+ | None =>
206
+ switch state.effectCache->Dict.get(tableName) {
207
+ | None => []
208
+ | Some(cacheDict) =>
209
+ cacheDict
210
+ ->Dict.valuesToArray
211
+ ->Array.filter(item =>
212
+ filter->EntityFilter.matches(
213
+ ~entity=item->(Utils.magic: Internal.effectCacheItem => dict<EntityFilter.FieldValue.t>),
214
+ )
215
+ )
216
+ ->(Utils.magic: array<Internal.effectCacheItem> => array<Internal.entity>)
217
+ }
240
218
  | Some(entityConfig) =>
241
219
  let entityDict = state.entities->Dict.get(entityConfig.name)->Option.getOr(Dict.make())
242
220
  let matched =
@@ -292,8 +270,10 @@ let backfillHistory = (
292
270
  }
293
271
  }
294
272
 
295
- let applyRollback = (state: t, ~targetCheckpointId) => {
273
+ let applyRollback = (state: t, ~targetCheckpointId, ~rolledBackAddresses) => {
296
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))
297
277
  state.history
298
278
  ->Dict.toArray
299
279
  ->Array.forEach(((name, rows)) =>
@@ -308,6 +288,7 @@ let writeBatch = (
308
288
  ~isInReorgThreshold,
309
289
  ~config: Config.t,
310
290
  ~updatedEntities: array<Persistence.updatedEntity>,
291
+ ~registeredAddresses: array<AddressRows.staged>,
311
292
  ~updatedEffectsCache: array<Persistence.updatedEffectCache>,
312
293
  ~chainMetaData: option<dict<InternalTable.Chains.metaFields>>,
313
294
  ) => {
@@ -316,10 +297,24 @@ let writeBatch = (
316
297
  // Rollback first, exactly like the Postgres transaction: the batch being
317
298
  // written is the reprocessed one, so its rows must land on the reverted state.
318
299
  switch rollback {
319
- | Some({targetCheckpointId}) => state->applyRollback(~targetCheckpointId)
300
+ | Some({targetCheckpointId, rolledBackAddresses}) =>
301
+ state->applyRollback(~targetCheckpointId, ~rolledBackAddresses)
320
302
  | None => ()
321
303
  }
322
304
 
305
+ registeredAddresses->Array.forEach(({row}) => state.addresses->AddressRows.Table.insert(row))
306
+
307
+ // The rollback diff restates what the reverted state already is, so it is not
308
+ // a change history should record — and an id it touches needs no backfill
309
+ // either, because the diff already carries its reverted value. Postgres draws
310
+ // the same line, keyed on the diff's checkpoint id.
311
+ let diffCheckpointId = rollback->Option.map(({diffCheckpointId}) => diffCheckpointId)
312
+ let isDiff = (change: Change.t<Internal.entity>) =>
313
+ switch diffCheckpointId {
314
+ | Some(diffCheckpointId) => change->Change.getCheckpointId === diffCheckpointId
315
+ | None => false
316
+ }
317
+
323
318
  updatedEntities->Array.forEach(({entityConfig, scope, changes}: Persistence.updatedEntity) => {
324
319
  let entityDict = state->getEntityDict(~name=entityConfig.name)
325
320
  let historyRows = state->getHistory(~name=entityConfig.name)
@@ -327,9 +322,17 @@ let writeBatch = (
327
322
  // onto the stored entity the same way the Postgres write path does.
328
323
  let chainIdField = entityConfig.table->Table.getChainIdField
329
324
 
325
+ let idsWithDiff = Utils.Set.make()
326
+ changes->Array.forEach(change =>
327
+ if isDiff(change) {
328
+ idsWithDiff->Utils.Set.add(change->Change.getEntityId->EntityId.toKey)->ignore
329
+ }
330
+ )
331
+
330
332
  changes->Array.forEach(change => {
331
333
  let entityId = change->Change.getEntityId
332
- if shouldSaveHistory {
334
+ let shouldSaveChangeHistory = shouldSaveHistory && !isDiff(change)
335
+ if shouldSaveHistory && !(idsWithDiff->Utils.Set.has(entityId->EntityId.toKey)) {
333
336
  state->backfillHistory(~entityConfig, ~scope, ~entityId, ~rows=historyRows)
334
337
  }
335
338
  switch change {
@@ -340,7 +343,7 @@ let writeBatch = (
340
343
  | _ => entity
341
344
  }
342
345
  entityDict->Dict.set(rowKey(~scope, ~entityId), storedEntity)
343
- if shouldSaveHistory {
346
+ if shouldSaveChangeHistory {
344
347
  historyRows
345
348
  ->Array.push({
346
349
  entityId,
@@ -353,7 +356,7 @@ let writeBatch = (
353
356
  }
354
357
  | Delete({checkpointId}) =>
355
358
  entityDict->Utils.Dict.deleteInPlace(rowKey(~scope, ~entityId))
356
- if shouldSaveHistory {
359
+ if shouldSaveChangeHistory {
357
360
  historyRows
358
361
  ->Array.push({
359
362
  entityId,
@@ -471,8 +474,7 @@ let getRollbackData = (
471
474
  ->ignore
472
475
  | Some({action: DELETE, entityId, scope}) =>
473
476
  removals->Array.push({Persistence.entityId, scope})->ignore
474
- | Some({action: SET, entity: Some(entity)}) =>
475
- restored->Array.push(entity->(Utils.magic: Internal.entity => unknown))->ignore
477
+ | Some({action: SET, entity: Some(entity)}) => restored->Array.push(entity)->ignore
476
478
  | Some({action: SET, entity: None}) => ()
477
479
  }
478
480
  })
@@ -521,9 +523,9 @@ let getRollbackProgressDiff = (state: t, ~rollbackTargetCheckpointId) => {
521
523
  let toStorage = (state: t, ~config: Config.t): Persistence.storage => {
522
524
  name: "memory",
523
525
  isInitialized: async () => state.isInitialized,
524
- initialize: async (~chainConfigs=[], ~entities=[], ~enums as _=[], ~envioInfo) => {
526
+ initialize: async (~chainConfigs=[], ~entities=[], ~enums as _=[], ~contractMapping, ~envioInfo) => {
525
527
  state->registerEntities(~entities)
526
- state->seedIndexingAddresses(~chainConfigs)
528
+ state->seedConfigAddresses(~chainConfigs, ~contractMapping, ~ecosystem=config.ecosystem.name)
527
529
  chainConfigs->Array.forEach(chainConfig =>
528
530
  state.chains->Dict.set(
529
531
  chainConfig.id->ChainId.toString,
@@ -543,9 +545,10 @@ let toStorage = (state: t, ~config: Config.t): Persistence.storage => {
543
545
  )
544
546
  state.envioInfo = Some(envioInfo)
545
547
  state.isInitialized = true
546
- state->toInitialState(~cleanRun=true)
548
+ state->toInitialState(~cleanRun=true, ~contractMapping)
547
549
  },
548
- resumeInitialState: async () => state->toInitialState(~cleanRun=false),
550
+ resumeInitialState: async () =>
551
+ state->toInitialState(~cleanRun=false, ~contractMapping=config.contractMapping),
549
552
  loadOrThrow: async (~filter, ~table: Table.table) =>
550
553
  state
551
554
  ->handleLoad(~tableName=table.tableName, ~filter)
@@ -562,14 +565,14 @@ let toStorage = (state: t, ~config: Config.t): Persistence.storage => {
562
565
  ),
563
566
  dumpEffectCache: async () => (),
564
567
  reset: async () => {
565
- let clear = dict =>
566
- dict->Dict.keysToArray->Array.forEach(key => dict->Utils.Dict.deleteInPlace(key))
568
+ let clear = Utils.Dict.clearInPlace
567
569
  state.entities->clear
568
570
  state.history->clear
569
571
  state.chains->clear
570
572
  state.effectCache->clear
571
573
  state.cache->clear
572
574
  state.checkpoints = []
575
+ state.addresses->AddressRows.Table.clear
573
576
  state.envioInfo = None
574
577
  state.isInitialized = false
575
578
  },
@@ -648,6 +651,7 @@ let toStorage = (state: t, ~config: Config.t): Persistence.storage => {
648
651
  ~allEntities as _,
649
652
  ~updatedEffectsCache,
650
653
  ~updatedEntities,
654
+ ~registeredAddresses,
651
655
  ~chainMetaData,
652
656
  ~onWrite as _,
653
657
  ) =>
@@ -657,6 +661,7 @@ let toStorage = (state: t, ~config: Config.t): Persistence.storage => {
657
661
  ~isInReorgThreshold,
658
662
  ~config,
659
663
  ~updatedEntities,
664
+ ~registeredAddresses,
660
665
  ~updatedEffectsCache,
661
666
  ~chainMetaData,
662
667
  ),
@@ -6,6 +6,8 @@ import * as Config from "./Config.res.mjs";
6
6
  import * as ChainId from "./ChainId.res.mjs";
7
7
  import * as EntityId from "./EntityId.res.mjs";
8
8
  import * as Internal from "./Internal.res.mjs";
9
+ import * as ChainState from "./ChainState.res.mjs";
10
+ import * as AddressRows from "./AddressRows.res.mjs";
9
11
  import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
10
12
  import * as EntityFilter from "./db/EntityFilter.res.mjs";
11
13
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
@@ -26,6 +28,7 @@ function make() {
26
28
  cache: {},
27
29
  effectCache: {},
28
30
  envioInfo: undefined,
31
+ addresses: AddressRows.Table.make(),
29
32
  isInitialized: false
30
33
  };
31
34
  }
@@ -76,58 +79,17 @@ function registerEntities(state, entities) {
76
79
  });
77
80
  }
78
81
 
79
- function seedIndexingAddresses(state, chainConfigs) {
80
- let dict = getEntityDict(state, Config.EnvioAddresses.name);
81
- chainConfigs.forEach(chainConfig => {
82
- chainConfig.contracts.forEach(contract => {
83
- contract.addresses.forEach(address => {
84
- let entity_id = Config.EnvioAddresses.makeId(chainConfig.id, address);
85
- let entity_chain_id = chainConfig.id;
86
- let entity_contract_name = contract.name;
87
- let entity = {
88
- id: entity_id,
89
- chain_id: entity_chain_id,
90
- registration_block: -1,
91
- registration_log_index: -1,
92
- contract_name: entity_contract_name
93
- };
94
- dict[entity_id] = entity;
95
- });
96
- });
97
- });
82
+ function seedConfigAddresses(state, chainConfigs, contractMapping, ecosystem) {
83
+ AddressRows.Table.clear(state.addresses);
84
+ ChainState.seedConfigAddresses(state.addresses, chainConfigs, ecosystem, contractMapping);
98
85
  }
99
86
 
100
- function toIndexingAddress(dc) {
101
- return {
102
- address: Config.EnvioAddresses.getAddress(dc),
103
- contractName: dc.contract_name,
104
- registrationBlock: dc.registration_block
105
- };
106
- }
107
-
108
- function getIndexingAddressesByChain(state) {
109
- let byChain = {};
110
- let dcDict = state.entities[Config.EnvioAddresses.name];
111
- if (dcDict !== undefined) {
112
- Object.values(dcDict).forEach(entity => {
113
- let chainIdStr = ChainId.toString(entity.chain_id);
114
- let arr = byChain[chainIdStr];
115
- let contracts;
116
- if (arr !== undefined) {
117
- contracts = arr;
118
- } else {
119
- let arr$1 = [];
120
- byChain[chainIdStr] = arr$1;
121
- contracts = arr$1;
122
- }
123
- contracts.push(toIndexingAddress(entity));
124
- });
125
- }
126
- return byChain;
87
+ function addressRowsByChain(state) {
88
+ return AddressRows.Table.groupByChain(state.addresses);
127
89
  }
128
90
 
129
91
  function toInitialChainStates(state) {
130
- let addressesByChain = getIndexingAddressesByChain(state);
92
+ let addressesByChain = AddressRows.Table.groupByChain(state.addresses);
131
93
  return Object.values(state.chains).map(chain => ({
132
94
  id: chain.id,
133
95
  startBlock: chain.startBlock,
@@ -137,7 +99,7 @@ function toInitialChainStates(state) {
137
99
  numEventsProcessed: chain.numEventsProcessed,
138
100
  firstEventBlockNumber: chain.firstEventBlockNumber,
139
101
  timestampCaughtUpToHeadOrEndblock: chain.timestampCaughtUpToHeadOrEndblock,
140
- indexingAddresses: Stdlib_Option.getOr(addressesByChain[ChainId.toString(chain.id)], []),
102
+ addressRows: Stdlib_Option.getOr(addressesByChain[ChainId.toString(chain.id)], AddressRows.emptySeedRows()),
141
103
  sourceBlockNumber: chain.sourceBlockNumber
142
104
  }));
143
105
  }
@@ -174,33 +136,39 @@ function reorgCheckpoints(state) {
174
136
  });
175
137
  }
176
138
 
177
- function toInitialState(state, cleanRun) {
139
+ function toInitialState(state, cleanRun, contractMapping) {
178
140
  return {
179
141
  cleanRun: cleanRun,
142
+ contractMapping: contractMapping,
143
+ envioInfo: state.envioInfo,
180
144
  cache: state.cache,
181
145
  chains: toInitialChainStates(state),
182
146
  checkpointId: committedCheckpointId(state),
183
- reorgCheckpoints: reorgCheckpoints(state),
184
- envioInfo: state.envioInfo
147
+ reorgCheckpoints: reorgCheckpoints(state)
185
148
  };
186
149
  }
187
150
 
188
151
  function handleLoad(state, tableName, filter) {
189
152
  let entityConfig = state.entityConfigs[tableName];
190
- if (entityConfig === undefined) {
191
- return [];
153
+ if (entityConfig !== undefined) {
154
+ let entityDict = Stdlib_Option.getOr(state.entities[entityConfig.name], {});
155
+ let matched = Object.values(entityDict).filter(entity => EntityFilter.matches(filter, entity));
156
+ let field = Table.getChainIdField(entityConfig.table);
157
+ if (field !== undefined) {
158
+ return matched.map(entity => {
159
+ let copy = Utils.Dict.shallowCopy(entity);
160
+ Utils.Dict.deleteInPlace(copy, field.fieldName);
161
+ return copy;
162
+ });
163
+ } else {
164
+ return matched;
165
+ }
192
166
  }
193
- let entityDict = Stdlib_Option.getOr(state.entities[entityConfig.name], {});
194
- let matched = Object.values(entityDict).filter(entity => EntityFilter.matches(filter, entity));
195
- let field = Table.getChainIdField(entityConfig.table);
196
- if (field !== undefined) {
197
- return matched.map(entity => {
198
- let copy = Utils.Dict.shallowCopy(entity);
199
- Utils.Dict.deleteInPlace(copy, field.fieldName);
200
- return copy;
201
- });
167
+ let cacheDict = state.effectCache[tableName];
168
+ if (cacheDict !== undefined) {
169
+ return Object.values(cacheDict).filter(item => EntityFilter.matches(filter, item));
202
170
  } else {
203
- return matched;
171
+ return [];
204
172
  }
205
173
  }
206
174
 
@@ -222,27 +190,46 @@ function backfillHistory(state, entityConfig, scope, entityId, rows) {
222
190
  }
223
191
  }
224
192
 
225
- function applyRollback(state, targetCheckpointId) {
193
+ function applyRollback(state, targetCheckpointId, rolledBackAddresses) {
226
194
  state.checkpoints = state.checkpoints.filter(cp => cp.id <= targetCheckpointId);
195
+ rolledBackAddresses.forEach(key => AddressRows.Table.$$delete(state.addresses, key));
227
196
  Object.entries(state.history).forEach(param => {
228
197
  state.history[param[0]] = param[1].filter(row => row.checkpointId <= targetCheckpointId);
229
198
  });
230
199
  }
231
200
 
232
- function writeBatch(state, batch, rollback, isInReorgThreshold, config, updatedEntities, updatedEffectsCache, chainMetaData) {
201
+ function writeBatch(state, batch, rollback, isInReorgThreshold, config, updatedEntities, registeredAddresses, updatedEffectsCache, chainMetaData) {
233
202
  let shouldSaveHistory = Config.shouldSaveHistory(config, isInReorgThreshold);
234
203
  if (rollback !== undefined) {
235
- applyRollback(state, rollback.targetCheckpointId);
204
+ applyRollback(state, rollback.targetCheckpointId, rollback.rolledBackAddresses);
236
205
  }
206
+ registeredAddresses.forEach(param => AddressRows.Table.insert(state.addresses, param.row));
207
+ let diffCheckpointId = Stdlib_Option.map(rollback, param => param.diffCheckpointId);
208
+ let isDiff = change => {
209
+ if (diffCheckpointId !== undefined) {
210
+ return change.checkpointId === diffCheckpointId;
211
+ } else {
212
+ return false;
213
+ }
214
+ };
237
215
  updatedEntities.forEach(param => {
216
+ let changes = param.changes;
238
217
  let scope = param.scope;
239
218
  let entityConfig = param.entityConfig;
240
219
  let entityDict = getEntityDict(state, entityConfig.name);
241
220
  let historyRows = getHistory(state, entityConfig.name);
242
221
  let chainIdField = Table.getChainIdField(entityConfig.table);
243
- param.changes.forEach(change => {
222
+ let idsWithDiff = new Set();
223
+ changes.forEach(change => {
224
+ if (isDiff(change)) {
225
+ idsWithDiff.add(EntityId.toKey(change.entityId));
226
+ return;
227
+ }
228
+ });
229
+ changes.forEach(change => {
244
230
  let entityId = change.entityId;
245
- if (shouldSaveHistory) {
231
+ let shouldSaveChangeHistory = shouldSaveHistory && !isDiff(change);
232
+ if (shouldSaveHistory && !idsWithDiff.has(EntityId.toKey(entityId))) {
246
233
  backfillHistory(state, entityConfig, scope, entityId, historyRows);
247
234
  }
248
235
  if (change.type === "SET") {
@@ -250,7 +237,7 @@ function writeBatch(state, batch, rollback, isInReorgThreshold, config, updatedE
250
237
  let match = Internal.chainScopeChainId(scope);
251
238
  let storedEntity = chainIdField !== undefined && match !== undefined ? Internal.stampChainId(entity, chainIdField.fieldName, Primitive_option.valFromOption(match)) : entity;
252
239
  entityDict[rowKey(scope, entityId)] = storedEntity;
253
- if (shouldSaveHistory) {
240
+ if (shouldSaveChangeHistory) {
254
241
  historyRows.push({
255
242
  entityId: entityId,
256
243
  scope: scope,
@@ -264,7 +251,7 @@ function writeBatch(state, batch, rollback, isInReorgThreshold, config, updatedE
264
251
  }
265
252
  }
266
253
  Utils.Dict.deleteInPlace(entityDict, rowKey(scope, entityId));
267
- if (shouldSaveHistory) {
254
+ if (shouldSaveChangeHistory) {
268
255
  historyRows.push({
269
256
  entityId: entityId,
270
257
  scope: scope,
@@ -409,12 +396,12 @@ function toStorage(state, config) {
409
396
  return {
410
397
  name: "memory",
411
398
  isInitialized: async () => state.isInitialized,
412
- initialize: async (chainConfigsOpt, entitiesOpt, $staropt$star, envioInfo) => {
399
+ initialize: async (chainConfigsOpt, entitiesOpt, $staropt$star, contractMapping, envioInfo) => {
413
400
  let chainConfigs = chainConfigsOpt !== undefined ? chainConfigsOpt : [];
414
401
  let entities = entitiesOpt !== undefined ? entitiesOpt : [];
415
402
  $staropt$star !== undefined;
416
403
  registerEntities(state, entities);
417
- seedIndexingAddresses(state, chainConfigs);
404
+ seedConfigAddresses(state, chainConfigs, contractMapping, config.ecosystem.name);
418
405
  chainConfigs.forEach(chainConfig => {
419
406
  state.chains[ChainId.toString(chainConfig.id)] = {
420
407
  id: chainConfig.id,
@@ -431,9 +418,9 @@ function toStorage(state, config) {
431
418
  });
432
419
  state.envioInfo = envioInfo;
433
420
  state.isInitialized = true;
434
- return toInitialState(state, true);
421
+ return toInitialState(state, true, contractMapping);
435
422
  },
436
- resumeInitialState: async () => toInitialState(state, false),
423
+ resumeInitialState: async () => toInitialState(state, false, config.contractMapping),
437
424
  loadOrThrow: async (filter, table) => handleLoad(state, table.tableName, filter),
438
425
  ensureQueryIndexes: async (param, param$1) => {},
439
426
  ensureSchemaIndexes: async param => {},
@@ -448,15 +435,14 @@ function toStorage(state, config) {
448
435
  },
449
436
  dumpEffectCache: async () => {},
450
437
  reset: async () => {
451
- let clear = dict => {
452
- Object.keys(dict).forEach(key => Utils.Dict.deleteInPlace(dict, key));
453
- };
438
+ let clear = Utils.Dict.clearInPlace;
454
439
  clear(state.entities);
455
440
  clear(state.history);
456
441
  clear(state.chains);
457
442
  clear(state.effectCache);
458
443
  clear(state.cache);
459
444
  state.checkpoints = [];
445
+ AddressRows.Table.clear(state.addresses);
460
446
  state.envioInfo = undefined;
461
447
  state.isInitialized = false;
462
448
  },
@@ -517,7 +503,7 @@ function toStorage(state, config) {
517
503
  }),
518
504
  getRollbackProgressDiff: async rollbackTargetCheckpointId => getRollbackProgressDiff(state, rollbackTargetCheckpointId),
519
505
  getRollbackData: async (entityConfig, rollbackTargetCheckpointId) => getRollbackData(state, entityConfig, rollbackTargetCheckpointId),
520
- writeBatch: async (batch, rollback, isInReorgThreshold, param, param$1, updatedEffectsCache, updatedEntities, chainMetaData, param$2) => writeBatch(state, batch, rollback, isInReorgThreshold, config, updatedEntities, updatedEffectsCache, chainMetaData),
506
+ writeBatch: async (batch, rollback, isInReorgThreshold, param, param$1, updatedEffectsCache, updatedEntities, registeredAddresses, chainMetaData, param$2) => writeBatch(state, batch, rollback, isInReorgThreshold, config, updatedEntities, registeredAddresses, updatedEffectsCache, chainMetaData),
521
507
  close: async () => {}
522
508
  };
523
509
  }
@@ -579,9 +565,8 @@ export {
579
565
  getEntityDict,
580
566
  getHistory,
581
567
  registerEntities,
582
- seedIndexingAddresses,
583
- toIndexingAddress,
584
- getIndexingAddressesByChain,
568
+ seedConfigAddresses,
569
+ addressRowsByChain,
585
570
  toInitialChainStates,
586
571
  committedCheckpointId,
587
572
  reorgCheckpoints,