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
@@ -2,15 +2,16 @@
2
2
 
3
3
  import * as Env from "./Env.res.mjs";
4
4
  import * as Svm from "./sources/Svm.res.mjs";
5
+ import * as Core from "./Core.res.mjs";
5
6
  import * as Batch from "./Batch.res.mjs";
6
7
  import * as Utils from "./Utils.res.mjs";
7
8
  import * as ChainId from "./ChainId.res.mjs";
8
9
  import * as Logging from "./Logging.res.mjs";
9
- import * as ChainMap from "./ChainMap.res.mjs";
10
10
  import * as EvmChain from "./sources/EvmChain.res.mjs";
11
11
  import * as FieldMask from "./sources/FieldMask.res.mjs";
12
12
  import * as BlockStore from "./sources/BlockStore.res.mjs";
13
13
  import * as FetchState from "./FetchState.res.mjs";
14
+ import * as AddressRows from "./AddressRows.res.mjs";
14
15
  import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
15
16
  import * as AddressStore from "./sources/AddressStore.res.mjs";
16
17
  import * as EntityTables from "./EntityTables.res.mjs";
@@ -20,6 +21,7 @@ import * as SourceManager from "./sources/SourceManager.res.mjs";
20
21
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
21
22
  import * as SimulateSource from "./sources/SimulateSource.res.mjs";
22
23
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
24
+ import * as ContractMapping from "./ContractMapping.res.mjs";
23
25
  import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
24
26
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
25
27
  import * as TransactionStore from "./sources/TransactionStore.res.mjs";
@@ -27,18 +29,26 @@ import * as SvmHyperSyncSource from "./sources/SvmHyperSyncSource.res.mjs";
27
29
  import * as FuelHyperSyncSource from "./sources/FuelHyperSyncSource.res.mjs";
28
30
  import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
29
31
 
30
- function configAddresses(chainConfig) {
32
+ function configStorageRows(chainConfig, ecosystem, contractMapping) {
31
33
  let addresses = [];
34
+ let contractIds = [];
32
35
  chainConfig.contracts.forEach(contract => {
36
+ let contractId = ContractMapping.idOfOrThrow(contractMapping, contract.name, undefined);
33
37
  contract.addresses.forEach(address => {
34
- addresses.push({
35
- address: address,
36
- contractName: contract.name,
37
- registrationBlock: -1
38
- });
38
+ addresses.push(address);
39
+ contractIds.push(contractId);
39
40
  });
40
41
  });
41
- return addresses;
42
+ return Core.getAddon().encodeAddresses(ecosystem, addresses).map((address, idx) => ({
43
+ chainId: chainConfig.id,
44
+ address: address,
45
+ contractId: contractIds[idx],
46
+ registrationBlock: AddressRows.configRegistrationBlock
47
+ }));
48
+ }
49
+
50
+ function seedConfigAddresses(table, chainConfigs, ecosystem, contractMapping) {
51
+ chainConfigs.forEach(chainConfig => AddressRows.Table.insertMany(table, configStorageRows(chainConfig, ecosystem, contractMapping)));
42
52
  }
43
53
 
44
54
  function validateOnEventRegistrations(chainId, registrations) {
@@ -94,17 +104,21 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
94
104
  };
95
105
  }
96
106
 
97
- function configContractNames(config) {
98
- let names = new Set();
99
- ChainMap.values(config.chainMap).forEach(chain => {
100
- chain.contracts.forEach(contract => {
101
- names.add(contract.name);
102
- });
107
+ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInReorgThreshold, isRealtime, config, contractMapping, registrationsByChainId, reducedPollingInterval) {
108
+ let chainId = chainConfig.id;
109
+ let logger = Logging.createChild({
110
+ chainId: chainId
103
111
  });
104
- return Array.from(names);
105
- }
106
-
107
- function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firstEventBlockOpt, progressBlockNumber, config, registrationsByChainId, logger, timestampCaughtUpToHeadOrEndblock, numEventsProcessed, isInReorgThreshold, isRealtime, reorgCheckpoints, maxReorgDepth, knownHeightOpt, isResumedOpt, reducedPollingInterval) {
112
+ let progressBlockNumber = resumedChainState.progressBlockNumber >= 0 ? resumedChainState.progressBlockNumber : resumedChainState.startBlock - 1 | 0;
113
+ let addressRows = resumedChainState.addressRows;
114
+ let startBlock = resumedChainState.startBlock;
115
+ let endBlock = resumedChainState.endBlock;
116
+ let firstEventBlockOpt = Primitive_option.some(resumedChainState.firstEventBlockNumber);
117
+ let timestampCaughtUpToHeadOrEndblock = resumedChainState.timestampCaughtUpToHeadOrEndblock;
118
+ let numEventsProcessed = resumedChainState.numEventsProcessed;
119
+ let maxReorgDepth = resumedChainState.maxReorgDepth;
120
+ let knownHeightOpt = resumedChainState.sourceBlockNumber;
121
+ let isResumedOpt = true;
108
122
  let firstEventBlock = firstEventBlockOpt !== undefined ? Primitive_option.valFromOption(firstEventBlockOpt) : undefined;
109
123
  let knownHeight = knownHeightOpt !== undefined ? knownHeightOpt : 0;
110
124
  let isResumed = isResumedOpt !== undefined ? isResumedOpt : false;
@@ -120,7 +134,7 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
120
134
  }
121
135
  });
122
136
  let lowercaseAddresses = config.lowercaseAddresses;
123
- let addressStore = AddressStore.make(config.ecosystem.name, !lowercaseAddresses, AddressStore.contractsOf(onEventRegistrations, configContractNames(config)));
137
+ let addressStore = AddressStore.make(config.ecosystem.name, !lowercaseAddresses, AddressStore.contractsOf(onEventRegistrations, contractMapping));
124
138
  let match$1 = config.ecosystem.name;
125
139
  let tmp;
126
140
  switch (match$1) {
@@ -132,13 +146,13 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
132
146
  tmp = undefined;
133
147
  break;
134
148
  }
135
- let fetchState = FetchState.make(startBlock, endBlock, onEventRegistrations, addressStore, indexingAddresses, config.maxAddrInPartition, chainConfig.id, (config.batchSize << 1), knownHeight, progressBlockNumber, match.onBlockRegistrations, Primitive_int.max(!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : maxReorgDepth, chainConfig.blockLag), Primitive_option.some(firstEventBlock), Primitive_option.some(tmp), isResumed);
149
+ let fetchState = FetchState.make(startBlock, endBlock, onEventRegistrations, addressStore, addressRows, config.maxAddrInPartition, chainConfig.id, (config.batchSize << 1), knownHeight, progressBlockNumber, match.onBlockRegistrations, Primitive_int.max(!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : maxReorgDepth, chainConfig.blockLag), Primitive_option.some(firstEventBlock), Primitive_option.some(tmp), isResumed);
136
150
  let chainReorgCheckpoints = Stdlib_Array.filterMap(reorgCheckpoints, reorgCheckpoint => {
137
151
  if (reorgCheckpoint.chain_id === chainConfig.id) {
138
152
  return reorgCheckpoint;
139
153
  }
140
154
  });
141
- let chainId = chainConfig.id;
155
+ let chainId$1 = chainConfig.id;
142
156
  let sources = chainConfig.sourceConfig;
143
157
  let sources$1;
144
158
  switch (sources.TAG) {
@@ -155,11 +169,11 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
155
169
  headers: headers
156
170
  };
157
171
  });
158
- sources$1 = EvmChain.makeSources(chainId, onEventRegistrations, sources.hypersync, evmRpcs, lowercaseAddresses, addressStore);
172
+ sources$1 = EvmChain.makeSources(chainId$1, onEventRegistrations, sources.hypersync, evmRpcs, lowercaseAddresses, addressStore);
159
173
  break;
160
174
  case "FuelSourceConfig" :
161
175
  sources$1 = [FuelHyperSyncSource.make({
162
- chainId: chainId,
176
+ chainId: chainId$1,
163
177
  endpointUrl: sources.hypersync,
164
178
  apiToken: Env.envioApiToken,
165
179
  onEventRegistrations: onEventRegistrations,
@@ -170,18 +184,18 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
170
184
  let rpc = sources.rpc;
171
185
  let hypersync = sources.hypersync;
172
186
  sources$1 = hypersync !== undefined ? [SvmHyperSyncSource.make({
173
- chainId: chainId,
187
+ chainId: chainId$1,
174
188
  endpointUrl: hypersync,
175
189
  apiToken: Env.envioApiToken,
176
190
  onEventRegistrations: onEventRegistrations,
177
191
  clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
178
192
  addressStore: addressStore
179
193
  })] : (
180
- rpc !== undefined ? [Svm.makeRPCSource(chainId, rpc, undefined)] : Stdlib_JsError.throwWithMessage(`Chain ` + ChainId.toString(chainId) + ` has no SVM data source`)
194
+ rpc !== undefined ? [Svm.makeRPCSource(chainId$1, rpc, undefined)] : Stdlib_JsError.throwWithMessage(`Chain ` + ChainId.toString(chainId$1) + ` has no SVM data source`)
181
195
  );
182
196
  break;
183
197
  case "SimulateSourceConfig" :
184
- sources$1 = [SimulateSource.make(sources.items, sources.endBlock, chainId, addressStore, config.ecosystem.name)];
198
+ sources$1 = [SimulateSource.make(sources.items, sources.endBlock, chainId$1, addressStore, config.ecosystem.name, Primitive_option.some(sources.transactionStore), Primitive_option.some(sources.blockStore))];
185
199
  break;
186
200
  case "CustomSources" :
187
201
  sources$1 = sources._0;
@@ -197,23 +211,7 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
197
211
  }
198
212
  let firstEventBlock$1 = fetchState.firstEventBlock;
199
213
  let chainDensity = firstEventBlock$1 !== undefined && progressBlockNumber > firstEventBlock$1 && numEventsProcessed > 0 ? numEventsProcessed / (progressBlockNumber - firstEventBlock$1 | 0) : undefined;
200
- return make(chainConfig, fetchState, onEventRegistrations, addressStore, SourceManager.make(sources$1, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), progressBlockNumber, Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), config.shouldRollbackOnReorg, maxReorgDepth, numEventsProcessed, Primitive_option.some(timestampCaughtUpToHeadOrEndblock), undefined, Primitive_option.some(TransactionStore.make(config.ecosystem.name, !lowercaseAddresses)), Primitive_option.some(chainDensity), Primitive_option.some(blockStore), config.reorgThresholdReadyTolerance, EntityTables.perChain(config.allEntities), logger);
201
- }
202
-
203
- function makeFromConfig(chainConfig, config, registrationsByChainId, knownHeight) {
204
- let logger = Logging.createChild({
205
- chainId: chainConfig.id
206
- });
207
- return makeInternal(chainConfig, configAddresses(chainConfig), chainConfig.startBlock, chainConfig.endBlock, undefined, -1, config, registrationsByChainId, logger, undefined, 0, false, false, [], chainConfig.maxReorgDepth, knownHeight, undefined, undefined);
208
- }
209
-
210
- function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInReorgThreshold, isRealtime, config, registrationsByChainId, reducedPollingInterval) {
211
- let chainId = chainConfig.id;
212
- let logger = Logging.createChild({
213
- chainId: chainId
214
- });
215
- let progressBlockNumber = resumedChainState.progressBlockNumber >= 0 ? resumedChainState.progressBlockNumber : resumedChainState.startBlock - 1 | 0;
216
- return makeInternal(chainConfig, resumedChainState.indexingAddresses, resumedChainState.startBlock, resumedChainState.endBlock, Primitive_option.some(resumedChainState.firstEventBlockNumber), progressBlockNumber, config, registrationsByChainId, logger, resumedChainState.timestampCaughtUpToHeadOrEndblock, resumedChainState.numEventsProcessed, isInReorgThreshold, isRealtime, reorgCheckpoints, resumedChainState.maxReorgDepth, resumedChainState.sourceBlockNumber, true, reducedPollingInterval);
214
+ return make(chainConfig, fetchState, onEventRegistrations, addressStore, SourceManager.make(sources$1, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), progressBlockNumber, Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), config.shouldRollbackOnReorg, maxReorgDepth, numEventsProcessed, Primitive_option.some(timestampCaughtUpToHeadOrEndblock), undefined, Primitive_option.some(TransactionStore.make(config.ecosystem.name, !lowercaseAddresses)), Primitive_option.some(chainDensity), Primitive_option.some(blockStore), config.reorgThresholdReadyTolerance, EntityTables.perChain(config.userEntities), logger);
217
215
  }
218
216
 
219
217
  function logger(cs) {
@@ -567,6 +565,11 @@ async function applyTransactionGroups(store, g) {
567
565
  payloads.forEach(payload => {
568
566
  payload.transaction = tx;
569
567
  });
568
+ let match = tx["accountActivities"];
569
+ if (match !== undefined) {
570
+ payloads.forEach(payload => Svm.attachAccountActivities(payload, tx));
571
+ return;
572
+ }
570
573
  });
571
574
  return;
572
575
  }
@@ -716,6 +719,10 @@ function toMetrics(cs) {
716
719
  startBlock: cs.fetchState.startBlock,
717
720
  endBlock: cs.fetchState.endBlock,
718
721
  numAddresses: cs.addressStore.size(),
722
+ addressesByContract: cs.addressStore.contractCounts().map(param => [
723
+ param.contractName,
724
+ param.count
725
+ ]),
719
726
  isReady: cs.timestampCaughtUpToHeadOrEndblock !== undefined,
720
727
  sourceBlockNumber: cs.fetchState.knownHeight,
721
728
  progressBlockNumber: cs.committedProgressBlockNumber,
@@ -851,35 +858,44 @@ function markReady(cs, readyAt) {
851
858
  }
852
859
 
853
860
  function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTargetBlockNumber, isReorgChain) {
854
- if (newProgressBlockNumber === undefined) {
855
- if (isReorgChain) {
856
- cs.fetchState = FetchState.rollback(cs.fetchState, cs.addressStore, rollbackTargetBlockNumber);
857
- cs.transactionStore.rollback(rollbackTargetBlockNumber);
858
- cs.blockStore.rollback(rollbackTargetBlockNumber);
859
- cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, rollbackTargetBlockNumber);
860
- cs.processingBlockNumber = Primitive_int.min(cs.processingBlockNumber, rollbackTargetBlockNumber);
861
- return;
862
- } else {
863
- return;
861
+ let rollbackTo = targetBlockNumber => {
862
+ let match = FetchState.rollback(cs.fetchState, cs.addressStore, targetBlockNumber);
863
+ cs.fetchState = match.fetchState;
864
+ return match.rolledBackAddresses.map(param => ({
865
+ chainId: cs.chainConfig.id,
866
+ address: param.address,
867
+ contractId: param.contractId
868
+ }));
869
+ };
870
+ if (newProgressBlockNumber !== undefined) {
871
+ let newTotalEventsProcessed = cs.numEventsProcessed - Stdlib_Option.getOrThrow(eventsProcessedDiff, "Missing events-processed diff for rolled-back chain");
872
+ let safeCheckpointTracking = cs.safeCheckpointTracking;
873
+ if (safeCheckpointTracking !== undefined) {
874
+ cs.safeCheckpointTracking = SafeCheckpointTracking.rollback(safeCheckpointTracking, newProgressBlockNumber);
864
875
  }
876
+ let rolledBackAddresses = rollbackTo(newProgressBlockNumber);
877
+ cs.transactionStore.rollback(newProgressBlockNumber);
878
+ cs.blockStore.rollback(newProgressBlockNumber);
879
+ cs.committedProgressBlockNumber = newProgressBlockNumber;
880
+ cs.processingBlockNumber = newProgressBlockNumber;
881
+ cs.numEventsProcessed = newTotalEventsProcessed;
882
+ return rolledBackAddresses;
865
883
  }
866
- let newTotalEventsProcessed = cs.numEventsProcessed - Stdlib_Option.getOrThrow(eventsProcessedDiff, "Missing events-processed diff for rolled-back chain");
867
- let safeCheckpointTracking = cs.safeCheckpointTracking;
868
- if (safeCheckpointTracking !== undefined) {
869
- cs.safeCheckpointTracking = SafeCheckpointTracking.rollback(safeCheckpointTracking, newProgressBlockNumber);
884
+ if (!isReorgChain) {
885
+ return [];
870
886
  }
871
- cs.fetchState = FetchState.rollback(cs.fetchState, cs.addressStore, newProgressBlockNumber);
872
- cs.transactionStore.rollback(newProgressBlockNumber);
873
- cs.blockStore.rollback(newProgressBlockNumber);
874
- cs.committedProgressBlockNumber = newProgressBlockNumber;
875
- cs.processingBlockNumber = newProgressBlockNumber;
876
- cs.numEventsProcessed = newTotalEventsProcessed;
887
+ let rolledBackAddresses$1 = rollbackTo(rollbackTargetBlockNumber);
888
+ cs.transactionStore.rollback(rollbackTargetBlockNumber);
889
+ cs.blockStore.rollback(rollbackTargetBlockNumber);
890
+ cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, rollbackTargetBlockNumber);
891
+ cs.processingBlockNumber = Primitive_int.min(cs.processingBlockNumber, rollbackTargetBlockNumber);
892
+ return rolledBackAddresses$1;
877
893
  }
878
894
 
879
895
  export {
880
- configAddresses,
896
+ configStorageRows,
897
+ seedConfigAddresses,
881
898
  make,
882
- makeFromConfig,
883
899
  makeFromDbState,
884
900
  logger,
885
901
  blockStore,
@@ -3,7 +3,18 @@
3
3
 
4
4
  type t
5
5
 
6
- let configAddresses: Config.chain => array<Internal.indexingAddress>
6
+ let configStorageRows: (
7
+ Config.chain,
8
+ ~ecosystem: Ecosystem.name,
9
+ ~contractMapping: ContractMapping.t,
10
+ ) => array<AddressRows.row>
11
+
12
+ let seedConfigAddresses: (
13
+ AddressRows.Table.t,
14
+ ~chainConfigs: array<Config.chain>,
15
+ ~ecosystem: Ecosystem.name,
16
+ ~contractMapping: ContractMapping.t,
17
+ ) => unit
7
18
 
8
19
  let make: (
9
20
  ~chainConfig: Config.chain,
@@ -26,13 +37,6 @@ let make: (
26
37
  ~logger: Pino.t,
27
38
  ) => t
28
39
 
29
- let makeFromConfig: (
30
- Config.chain,
31
- ~config: Config.t,
32
- ~registrationsByChainId: HandlerRegister.registrationsByChainId,
33
- ~knownHeight: int,
34
- ) => t
35
-
36
40
  let makeFromDbState: (
37
41
  Config.chain,
38
42
  ~resumedChainState: Persistence.initialChainState,
@@ -40,6 +44,7 @@ let makeFromDbState: (
40
44
  ~isInReorgThreshold: bool,
41
45
  ~isRealtime: bool,
42
46
  ~config: Config.t,
47
+ ~contractMapping: ContractMapping.t,
43
48
  ~registrationsByChainId: HandlerRegister.registrationsByChainId,
44
49
  ~reducedPollingInterval: int=?,
45
50
  ) => t
@@ -158,4 +163,4 @@ let rollback: (
158
163
  ~eventsProcessedDiff: option<float>,
159
164
  ~rollbackTargetBlockNumber: int,
160
165
  ~isReorgChain: bool,
161
- ) => unit
166
+ ) => array<AddressRows.key>
package/src/Config.res CHANGED
@@ -33,13 +33,19 @@ type sourceConfig =
33
33
  // A `simulate` run: the items the test fed in, parsed against the chain's
34
34
  // registrations. The source itself is built with the chain's address store,
35
35
  // like every other source, so it can apply the same gates.
36
- | SimulateSourceConfig({items: array<Internal.item>, endBlock: int})
36
+ | SimulateSourceConfig({
37
+ items: array<Internal.item>,
38
+ endBlock: int,
39
+ transactionStore?: TransactionStore.t,
40
+ blockStore?: BlockStore.t,
41
+ })
37
42
  // For tests: pass custom sources directly
38
43
  | CustomSources(array<Source.t>)
39
44
 
40
45
  type chain = {
41
46
  name: string,
42
47
  id: ChainId.t,
48
+ ecosystem: Ecosystem.name,
43
49
  startBlock: int,
44
50
  endBlock?: int,
45
51
  maxReorgDepth: int,
@@ -101,6 +107,9 @@ type t = {
101
107
  // they can express fits an INTEGER.
102
108
  chainIdMode: ChainId.mode,
103
109
  chainMap: ChainMap.t<chain>,
110
+ // Derived from every chain's contracts, so an id means the same contract on
111
+ // every chain and on every restart.
112
+ contractMapping: ContractMapping.t,
104
113
  defaultChain: option<chain>,
105
114
  ecosystem: Ecosystem.t,
106
115
  enableRawEvents: bool,
@@ -117,75 +126,9 @@ type t = {
117
126
  isDev: bool,
118
127
  userEntitiesByName: dict<Internal.entityConfig>,
119
128
  userEntities: array<Internal.entityConfig>,
120
- allEntities: array<Internal.entityConfig>,
121
129
  allEnums: array<Table.enumConfig<Table.enum>>,
122
130
  }
123
131
 
124
- module EnvioAddresses = {
125
- let name = "envio_addresses"
126
- let index = -1
127
-
128
- let makeId = (~chainId: ChainId.t, ~address) => {
129
- chainId->ChainId.toString ++ "-" ++ address->Address.toString
130
- }
131
-
132
- type t = {
133
- id: string,
134
- @as("chain_id") chainId: ChainId.t,
135
- @as("registration_block") registrationBlock: int,
136
- // Vestigial: always written as -1 and never read back. The column stays so
137
- // an existing schema doesn't need a migration.
138
- @as("registration_log_index") registrationLogIndex: int,
139
- @as("contract_name") contractName: string,
140
- }
141
-
142
- // Extract the raw contract address from the composite id ({chainId}-{address}).
143
- // Inverse of makeId. Keep in sync with makeId above and the SUBSTRING SQL in
144
- // InternalTable.Chains.makeGetInitialStateQuery.
145
- let getAddress = (entity: t): Address.t => {
146
- let sepIdx = entity.id->String.indexOf("-")
147
- entity.id
148
- ->String.slice(~start=sepIdx + 1, ~end=entity.id->String.length)
149
- ->Address.unsafeFromString
150
- }
151
-
152
- let schema = S.schema(s => {
153
- id: s.matches(S.string),
154
- chainId: s.matches(ChainId.schema),
155
- registrationBlock: s.matches(S.int),
156
- registrationLogIndex: s.matches(S.int),
157
- contractName: s.matches(S.string),
158
- })
159
-
160
- let table = Table.mkTable(
161
- name,
162
- ~fields=[
163
- Table.mkField("id", String, ~isPrimaryKey=true, ~fieldSchema=S.string),
164
- Table.mkField("chain_id", ChainId, ~fieldSchema=ChainId.schema),
165
- Table.mkField("registration_block", Int32, ~fieldSchema=S.int),
166
- // -1 sentinel when registered from a block handler (no log index)
167
- Table.mkField("registration_log_index", Int32, ~fieldSchema=S.int),
168
- Table.mkField("contract_name", String, ~fieldSchema=S.string),
169
- ],
170
- )
171
-
172
- external castToInternal: t => Internal.entity = "%identity"
173
-
174
- let entityConfig = {
175
- Internal.name,
176
- index,
177
- schema,
178
- table,
179
- // Internal address tracking is Postgres-only; the global config is
180
- // always required to have Postgres enabled (Storage::resolve forbids
181
- // a Postgres-disabled global), so this is safe regardless of mode.
182
- storage: {postgres: true, clickhouse: false},
183
- // The table already keys rows by chain through the composite `id`, so the
184
- // per-chain mode must not append a second chain-id column to it.
185
- crossChain: true,
186
- }->Internal.fromGenericEntityConfig
187
- }
188
-
189
132
  type rpcSourceFor = | @as("sync") Sync | @as("fallback") Fallback | @as("realtime") Realtime
190
133
 
191
134
  let rpcSourceForSchema = S.enum([Sync, Fallback, Realtime])
@@ -235,9 +178,6 @@ let svmEventDescriptorSchema = S.schema(s =>
235
178
  {
236
179
  "discriminator": s.matches(S.option(S.string)),
237
180
  "discriminatorByteLen": s.matches(S.int),
238
- "transactionFields": s.matches(S.array(Internal.svmTransactionFieldSchema)),
239
- "blockFields": s.matches(S.option(S.array(Internal.svmBlockFieldSchema))),
240
- "includeLogs": s.matches(S.bool),
241
181
  // An array of AND-groups OR-ed together: the CLI normalizes both the flat
242
182
  // and `any_of` YAML shapes to `Vec<Vec<SvmAccountFilterJson>>`.
243
183
  "accountFilters": s.matches(
@@ -348,10 +288,18 @@ let propertySchema = S.schema(s =>
348
288
  }
349
289
  )
350
290
 
291
+ let clickhouseSkippingIndexSchema: S.t<Internal.clickhouseSkippingIndex> = S.object(s => {
292
+ Internal.name: s.field("name", S.string),
293
+ expr: s.field("expr", S.string),
294
+ type_: s.field("type", S.string),
295
+ granularity: ?s.field("granularity", S.option(S.int)),
296
+ })
297
+
351
298
  let clickhouseTableOptionsSchema: S.t<Internal.clickhouseTableOptions> = S.object(s => {
352
299
  Internal.partitionBy: ?s.field("partitionBy", S.option(S.string)),
353
300
  orderBy: ?s.field("orderBy", S.option(S.array(S.string))),
354
301
  ttl: ?s.field("ttl", S.option(S.string)),
302
+ skippingIndexes: ?s.field("skippingIndexes", S.option(S.array(clickhouseSkippingIndexSchema))),
355
303
  })
356
304
 
357
305
  // The entity's `clickhouse` storage arg mirrors the @storage directive:
@@ -377,6 +325,7 @@ let entityJsonSchema = S.schema(s =>
377
325
  "name": s.matches(S.string),
378
326
  "crossChain": s.matches(S.option(S.bool)),
379
327
  "storage": s.matches(S.option(entityStorageSchema)),
328
+ "internal": s.matches(S.option(S.bool)),
380
329
  "properties": s.matches(S.array(propertySchema)),
381
330
  "derivedFields": s.matches(S.option(S.array(derivedFieldSchema))),
382
331
  "compositeIndices": s.matches(S.option(S.array(S.array(compositeIndexFieldSchema)))),
@@ -580,6 +529,7 @@ let parseEntitiesFromJson = (
580
529
  table,
581
530
  storage,
582
531
  crossChain,
532
+ internal: entityJson["internal"]->Option.getOr(false),
583
533
  }->Internal.fromGenericEntityConfig
584
534
  })
585
535
  }
@@ -616,6 +566,14 @@ let publicConfigSchema = S.schema(s =>
616
566
  }
617
567
  )
618
568
 
569
+ let contractMappingOf = (~chainConfigs: array<chain>): ContractMapping.t => {
570
+ let names = []
571
+ chainConfigs->Array.forEach(chain =>
572
+ chain.contracts->Array.forEach(contract => names->Array.push(contract.name)->ignore)
573
+ )
574
+ ContractMapping.make(~names)
575
+ }
576
+
619
577
  let fromPublic = (publicConfigJson: JSON.t) => {
620
578
  let maxAddrInPartition = Env.maxAddrInPartition
621
579
  // Parse public config
@@ -796,9 +754,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
796
754
  "svm": option<{
797
755
  "discriminator": option<string>,
798
756
  "discriminatorByteLen": int,
799
- "transactionFields": array<Internal.svmTransactionField>,
800
- "blockFields": option<array<Internal.svmBlockField>>,
801
- "includeLogs": bool,
802
757
  "accountFilters": option<
803
758
  array<array<{"position": int, "values": array<string>}>>,
804
759
  >,
@@ -832,9 +787,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
832
787
  ~programId,
833
788
  ~discriminator=svm["discriminator"],
834
789
  ~discriminatorByteLen=svm["discriminatorByteLen"],
835
- ~transactionFields=svm["transactionFields"],
836
- ~blockFields=?svm["blockFields"],
837
- ~includeLogs=svm["includeLogs"],
838
790
  ~accountFilters,
839
791
  ~isInner=svm["isInner"],
840
792
  ~accounts=svm["accounts"]->Option.getOr([]),
@@ -923,25 +875,24 @@ let fromPublic = (publicConfigJson: JSON.t) => {
923
875
  }
924
876
  })
925
877
 
926
- // The same address under two contract definitions (or twice under one)
927
- // would later violate the (chainId, address) primary key of
928
- // envio_addresses with an opaque Postgres error — fail fast with the
929
- // offending pair instead. parseAddress already canonicalizes casing
930
- // (checksum or lowercase), so an exact match catches case variants too.
931
- let contractNameByAddress = Dict.make()
878
+ // One address listed twice for the same contract would violate the
879
+ // (chainId, address, contract) primary key of envio_addresses with an
880
+ // opaque Postgres error — fail fast instead. Two contracts may share an
881
+ // address: each indexes it with its own events. parseAddress already
882
+ // canonicalizes casing (checksum or lowercase), so an exact match
883
+ // catches case variants too.
884
+ let seen = Utils.Set.make()
932
885
  contracts->Array.forEach(contract => {
933
886
  contract.addresses->Array.forEach(
934
887
  address => {
935
888
  let addressString = address->Address.toString
936
- switch contractNameByAddress->Dict.get(addressString) {
937
- | Some(existingContractName) =>
889
+ let key = contract.name ++ "-" ++ addressString
890
+ if seen->Utils.Set.has(key) {
938
891
  JsError.throwWithMessage(
939
- existingContractName === contract.name
940
- ? `Address ${addressString} is listed multiple times for the contract ${contract.name} on chain ${chainId->ChainId.toString}. Please remove the duplicate from your config.`
941
- : `Address ${addressString} on chain ${chainId->ChainId.toString} is configured for multiple contracts: ${existingContractName} and ${contract.name}. Indexing the same address with multiple contract definitions is not supported. Please define the events on a single contract definition instead.`,
892
+ `Address ${addressString} is listed multiple times for the contract ${contract.name} on chain ${chainId->ChainId.toString}. Please remove the duplicate from your config.`,
942
893
  )
943
- | None => contractNameByAddress->Dict.set(addressString, contract.name)
944
894
  }
895
+ seen->Utils.Set.add(key)->ignore
945
896
  },
946
897
  )
947
898
  })
@@ -1012,6 +963,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1012
963
  {
1013
964
  name: chainName,
1014
965
  id: chainId,
966
+ ecosystem: ecosystemName,
1015
967
  startBlock: publicChainConfig["startBlock"],
1016
968
  endBlock: ?publicChainConfig["endBlock"],
1017
969
  maxReorgDepth: switch ecosystemName {
@@ -1066,8 +1018,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1066
1018
  ->Option.getOr([])
1067
1019
  ->parseEntitiesFromJson(~enumConfigsByName, ~globalStorage, ~defaultCrossChain)
1068
1020
 
1069
- let allEntities = userEntities->Array.concat([EnvioAddresses.entityConfig])
1070
-
1071
1021
  // Keyed by the capitalized entity name to match the handler-context
1072
1022
  // accessor (`context.Pool_snapshots`) the generated types expose, while
1073
1023
  // entityConfig.name stays the original schema name used for the physical
@@ -1104,6 +1054,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1104
1054
  storage: globalStorage,
1105
1055
  chainIdMode: publicConfig["chainIdMode"]->Option.getOr(Int32),
1106
1056
  chainMap,
1057
+ contractMapping: contractMappingOf(~chainConfigs=chains),
1107
1058
  defaultChain: chains->Array.get(0),
1108
1059
  enableRawEvents: publicConfig["rawEvents"]->Option.getOr(false),
1109
1060
  ecosystem,
@@ -1115,7 +1066,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1115
1066
  isDev: publicConfig["isDev"]->Option.getOr(false),
1116
1067
  userEntitiesByName,
1117
1068
  userEntities,
1118
- allEntities,
1119
1069
  allEnums,
1120
1070
  }
1121
1071
  }