envio 3.3.0-alpha.2 → 3.3.0-alpha.4

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 (71) hide show
  1. package/index.d.ts +23 -21
  2. package/package.json +6 -6
  3. package/src/Bin.res +3 -0
  4. package/src/Bin.res.mjs +4 -0
  5. package/src/ChainFetching.res +26 -3
  6. package/src/ChainFetching.res.mjs +22 -3
  7. package/src/ChainState.res +3 -12
  8. package/src/ChainState.res.mjs +6 -8
  9. package/src/ChainState.resi +0 -2
  10. package/src/CrossChainState.res +15 -0
  11. package/src/CrossChainState.res.mjs +12 -0
  12. package/src/Ecosystem.res +0 -6
  13. package/src/EventConfigBuilder.res +45 -18
  14. package/src/EventConfigBuilder.res.mjs +28 -10
  15. package/src/EventProcessing.res +53 -37
  16. package/src/EventProcessing.res.mjs +41 -36
  17. package/src/FetchState.res +35 -42
  18. package/src/FetchState.res.mjs +37 -63
  19. package/src/HandlerLoader.res +10 -1
  20. package/src/HandlerLoader.res.mjs +15 -8
  21. package/src/Internal.res +8 -3
  22. package/src/Internal.res.mjs +1 -1
  23. package/src/LoadLayer.res +5 -5
  24. package/src/LoadLayer.res.mjs +6 -6
  25. package/src/Main.res +17 -9
  26. package/src/Main.res.mjs +15 -6
  27. package/src/PgStorage.res +4 -4
  28. package/src/PgStorage.res.mjs +5 -5
  29. package/src/Prometheus.res +10 -10
  30. package/src/Prometheus.res.mjs +9 -9
  31. package/src/PruneStaleHistory.res +2 -2
  32. package/src/PruneStaleHistory.res.mjs +3 -3
  33. package/src/Rollback.res +2 -2
  34. package/src/Rollback.res.mjs +3 -3
  35. package/src/SimulateItems.res +81 -15
  36. package/src/SimulateItems.res.mjs +63 -20
  37. package/src/TestIndexer.res +77 -39
  38. package/src/TestIndexer.res.mjs +45 -31
  39. package/src/bindings/Performance.res +7 -0
  40. package/src/bindings/Performance.res.mjs +21 -0
  41. package/src/bindings/Performance.resi +7 -0
  42. package/src/sources/EventRouter.res +17 -21
  43. package/src/sources/EventRouter.res.mjs +6 -9
  44. package/src/sources/Evm.res +10 -37
  45. package/src/sources/Evm.res.mjs +5 -40
  46. package/src/sources/Fuel.res +0 -4
  47. package/src/sources/Fuel.res.mjs +0 -7
  48. package/src/sources/HyperFuelSource.res +10 -11
  49. package/src/sources/HyperFuelSource.res.mjs +11 -11
  50. package/src/sources/HyperSync.res +3 -0
  51. package/src/sources/HyperSync.res.mjs +1 -0
  52. package/src/sources/HyperSync.resi +1 -0
  53. package/src/sources/HyperSyncClient.res +5 -1
  54. package/src/sources/HyperSyncSource.res +42 -24
  55. package/src/sources/HyperSyncSource.res.mjs +30 -26
  56. package/src/sources/RpcSource.res +7 -8
  57. package/src/sources/RpcSource.res.mjs +8 -8
  58. package/src/sources/SimulateSource.res +1 -1
  59. package/src/sources/Source.res +1 -1
  60. package/src/sources/SourceManager.res +17 -16
  61. package/src/sources/SourceManager.res.mjs +10 -15
  62. package/src/sources/Svm.res +6 -11
  63. package/src/sources/Svm.res.mjs +6 -14
  64. package/src/sources/SvmHyperSyncSource.res +24 -18
  65. package/src/sources/SvmHyperSyncSource.res.mjs +22 -15
  66. package/src/sources/TransactionStore.res +62 -35
  67. package/src/sources/TransactionStore.res.mjs +44 -18
  68. package/svm.schema.json +30 -37
  69. package/src/bindings/Hrtime.res +0 -58
  70. package/src/bindings/Hrtime.res.mjs +0 -90
  71. package/src/bindings/Hrtime.resi +0 -30
@@ -212,7 +212,7 @@ module PreloadHandler = {
212
212
 
213
213
  type operationRef = {
214
214
  mutable pendingCount: int,
215
- timerRef: Hrtime.timeRef,
215
+ timerRef: Performance.timeRef,
216
216
  }
217
217
  let operations: dict<operationRef> = Dict.make()
218
218
 
@@ -227,11 +227,11 @@ module PreloadHandler = {
227
227
  key,
228
228
  {
229
229
  pendingCount: 1,
230
- timerRef: Hrtime.makeTimer(),
230
+ timerRef: Performance.now(),
231
231
  },
232
232
  )
233
233
  }
234
- Hrtime.makeTimer()
234
+ Performance.now()
235
235
  }
236
236
 
237
237
  let endOperation = (timerRef, ~contract, ~event) => {
@@ -242,13 +242,13 @@ module PreloadHandler = {
242
242
  if operationRef.pendingCount === 0 {
243
243
  timeCounter->SafeCounter.handleFloat(
244
244
  ~labels,
245
- ~value=operationRef.timerRef->Hrtime.timeSince->Hrtime.toSecondsFloat,
245
+ ~value=operationRef.timerRef->Performance.secondsSince,
246
246
  )
247
247
  operations->Utils.Dict.deleteInPlace(key)
248
248
  }
249
249
  sumTimeCounter->SafeCounter.handleFloat(
250
250
  ~labels,
251
- ~value=timerRef->Hrtime.timeSince->Hrtime.toSecondsFloat,
251
+ ~value=timerRef->Performance.secondsSince,
252
252
  )
253
253
  count->SafeCounter.increment(~labels)
254
254
  }
@@ -764,7 +764,7 @@ module StorageLoad = {
764
764
 
765
765
  type operationRef = {
766
766
  mutable pendingCount: int,
767
- timerRef: Hrtime.timeRef,
767
+ timerRef: Performance.timeRef,
768
768
  }
769
769
  let operations = Dict.make()
770
770
 
@@ -780,12 +780,12 @@ module StorageLoad = {
780
780
  (
781
781
  {
782
782
  pendingCount: 1,
783
- timerRef: Hrtime.makeTimer(),
783
+ timerRef: Performance.now(),
784
784
  }: operationRef
785
785
  ),
786
786
  )
787
787
  }
788
- Hrtime.makeTimer()
788
+ Performance.now()
789
789
  }
790
790
 
791
791
  let endOperation = (timerRef, ~storage, ~operation, ~whereSize, ~size) => {
@@ -796,13 +796,13 @@ module StorageLoad = {
796
796
  if operationRef.pendingCount === 0 {
797
797
  timeCounter->SafeCounter.handleFloat(
798
798
  ~labels,
799
- ~value=operationRef.timerRef->Hrtime.timeSince->Hrtime.toSecondsFloat,
799
+ ~value=operationRef.timerRef->Performance.secondsSince,
800
800
  )
801
801
  operations->Utils.Dict.deleteInPlace(key)
802
802
  }
803
803
  sumTimeCounter->SafeCounter.handleFloat(
804
804
  ~labels,
805
- ~value=timerRef->Hrtime.timeSince->Hrtime.toSecondsFloat,
805
+ ~value=timerRef->Performance.secondsSince,
806
806
  )
807
807
  counter->SafeCounter.increment(~labels)
808
808
  whereSizeCounter->SafeCounter.handleInt(~labels, ~value=whereSize)
@@ -1,8 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Utils from "./Utils.res.mjs";
4
- import * as Hrtime from "./bindings/Hrtime.res.mjs";
5
4
  import * as PromClient from "./bindings/PromClient.res.mjs";
5
+ import * as Performance from "./bindings/Performance.res.mjs";
6
6
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
7
7
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
8
8
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -290,10 +290,10 @@ function startOperation(contract, event) {
290
290
  } else {
291
291
  operations[key] = {
292
292
  pendingCount: 1,
293
- timerRef: Hrtime.makeTimer()
293
+ timerRef: Performance.now()
294
294
  };
295
295
  }
296
- return Hrtime.makeTimer();
296
+ return Performance.now();
297
297
  }
298
298
 
299
299
  function endOperation(timerRef, contract, event) {
@@ -305,10 +305,10 @@ function endOperation(timerRef, contract, event) {
305
305
  let operationRef = operations[key];
306
306
  operationRef.pendingCount = operationRef.pendingCount - 1 | 0;
307
307
  if (operationRef.pendingCount === 0) {
308
- handleFloat(timeCounter$1, labels, Hrtime.toSecondsFloat(Hrtime.timeSince(operationRef.timerRef)));
308
+ handleFloat(timeCounter$1, labels, Performance.secondsSince(operationRef.timerRef));
309
309
  Utils.Dict.deleteInPlace(operations, key);
310
310
  }
311
- handleFloat(sumTimeCounter, labels, Hrtime.toSecondsFloat(Hrtime.timeSince(timerRef)));
311
+ handleFloat(sumTimeCounter, labels, Performance.secondsSince(timerRef));
312
312
  increment(count$1, labels);
313
313
  }
314
314
 
@@ -775,10 +775,10 @@ function startOperation$1(storage, operation) {
775
775
  } else {
776
776
  operations$1[key] = {
777
777
  pendingCount: 1,
778
- timerRef: Hrtime.makeTimer()
778
+ timerRef: Performance.now()
779
779
  };
780
780
  }
781
- return Hrtime.makeTimer();
781
+ return Performance.now();
782
782
  }
783
783
 
784
784
  function endOperation$1(timerRef, storage, operation, whereSize, size) {
@@ -790,10 +790,10 @@ function endOperation$1(timerRef, storage, operation, whereSize, size) {
790
790
  let operationRef = operations$1[key];
791
791
  operationRef.pendingCount = operationRef.pendingCount - 1 | 0;
792
792
  if (operationRef.pendingCount === 0) {
793
- handleFloat(timeCounter$7, labels, Hrtime.toSecondsFloat(Hrtime.timeSince(operationRef.timerRef)));
793
+ handleFloat(timeCounter$7, labels, Performance.secondsSince(operationRef.timerRef));
794
794
  Utils.Dict.deleteInPlace(operations$1, key);
795
795
  }
796
- handleFloat(sumTimeCounter$3, labels, Hrtime.toSecondsFloat(Hrtime.timeSince(timerRef)));
796
+ handleFloat(sumTimeCounter$3, labels, Performance.secondsSince(timerRef));
797
797
  increment(counter$8, labels);
798
798
  handleInt(whereSizeCounter, labels, whereSize);
799
799
  handleInt(sizeCounter$1, labels, size);
@@ -14,7 +14,7 @@ let runPrune = async (state: IndexerState.t) => {
14
14
  await Utils.delay(1000)
15
15
  }
16
16
  let entityConfig = persistence.allEntities->Array.getUnsafe(idx)
17
- let timeRef = Hrtime.makeTimer()
17
+ let timeRef = Performance.now()
18
18
  try {
19
19
  let () = await persistence.storage.pruneStaleEntityHistory(
20
20
  ~entityName=entityConfig.name,
@@ -34,7 +34,7 @@ let runPrune = async (state: IndexerState.t) => {
34
34
  )
35
35
  }
36
36
  Prometheus.RollbackHistoryPrune.increment(
37
- ~timeSeconds=Hrtime.timeSince(timeRef)->Hrtime.toSecondsFloat,
37
+ ~timeSeconds=Performance.secondsSince(timeRef),
38
38
  ~entityName=entityConfig.name,
39
39
  )
40
40
  }
@@ -1,10 +1,10 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Utils from "./Utils.res.mjs";
4
- import * as Hrtime from "./bindings/Hrtime.res.mjs";
5
4
  import * as Logging from "./Logging.res.mjs";
6
5
  import * as Throttler from "./Throttler.res.mjs";
7
6
  import * as Prometheus from "./Prometheus.res.mjs";
7
+ import * as Performance from "./bindings/Performance.res.mjs";
8
8
  import * as IndexerState from "./IndexerState.res.mjs";
9
9
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
10
10
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
@@ -21,7 +21,7 @@ async function runPrune(state) {
21
21
  await Utils.delay(1000);
22
22
  }
23
23
  let entityConfig = persistence.allEntities[idx];
24
- let timeRef = Hrtime.makeTimer();
24
+ let timeRef = Performance.now();
25
25
  try {
26
26
  await persistence.storage.pruneStaleEntityHistory(entityConfig.name, entityConfig.index, safeCheckpointId);
27
27
  } catch (raw_exn) {
@@ -31,7 +31,7 @@ async function runPrune(state) {
31
31
  safeCheckpointId: safeCheckpointId
32
32
  }), `Failed to prune stale entity history`, exn);
33
33
  }
34
- Prometheus.RollbackHistoryPrune.increment(Hrtime.toSecondsFloat(Hrtime.timeSince(timeRef)), entityConfig.name);
34
+ Prometheus.RollbackHistoryPrune.increment(Performance.secondsSince(timeRef), entityConfig.name);
35
35
  }
36
36
  }
37
37
 
package/src/Rollback.res CHANGED
@@ -94,7 +94,7 @@ and executeRollback = async (
94
94
  ~scheduleFetch,
95
95
  ~scheduleProcessing,
96
96
  ) => {
97
- let startTime = Hrtime.makeTimer()
97
+ let startTime = Performance.now()
98
98
 
99
99
  let chainState = state->IndexerState.getChainState(~chain=reorgChain)
100
100
 
@@ -194,7 +194,7 @@ and executeRollback = async (
194
194
  "targetCheckpointId": rollbackTargetCheckpointId,
195
195
  })
196
196
  Prometheus.RollbackSuccess.increment(
197
- ~timeSeconds=Hrtime.timeSince(startTime)->Hrtime.toSecondsFloat,
197
+ ~timeSeconds=Performance.secondsSince(startTime),
198
198
  ~rollbackedProcessedEvents=rollbackedProcessedEvents.contents,
199
199
  )
200
200
 
@@ -1,11 +1,11 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Utils from "./Utils.res.mjs";
4
- import * as Hrtime from "./bindings/Hrtime.res.mjs";
5
4
  import * as Logging from "./Logging.res.mjs";
6
5
  import * as Writing from "./Writing.res.mjs";
7
6
  import * as ChainState from "./ChainState.res.mjs";
8
7
  import * as Prometheus from "./Prometheus.res.mjs";
8
+ import * as Performance from "./bindings/Performance.res.mjs";
9
9
  import * as IndexerState from "./IndexerState.res.mjs";
10
10
  import * as Stdlib_Float from "@rescript/runtime/lib/es6/Stdlib_Float.js";
11
11
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
@@ -32,7 +32,7 @@ async function getLastKnownValidBlock(chainState, reorgBlockNumber, isRealtime)
32
32
  }
33
33
 
34
34
  async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, scheduleFetch, scheduleProcessing) {
35
- let startTime = Hrtime.makeTimer();
35
+ let startTime = Performance.now();
36
36
  let chainState = IndexerState.getChainState(state, reorgChain);
37
37
  let logger = Logging.createChildFrom(ChainState.logger(chainState), {
38
38
  action: "Rollback",
@@ -70,7 +70,7 @@ async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, sch
70
70
  beforeCheckpointId: IndexerState.committedCheckpointId(state),
71
71
  targetCheckpointId: rollbackTargetCheckpointId
72
72
  });
73
- Prometheus.RollbackSuccess.increment(Hrtime.toSecondsFloat(Hrtime.timeSince(startTime)), rollbackedProcessedEvents);
73
+ Prometheus.RollbackSuccess.increment(Performance.secondsSince(startTime), rollbackedProcessedEvents);
74
74
  IndexerState.completeRollback(state, eventsProcessedDiffByChain);
75
75
  scheduleFetch();
76
76
  return scheduleProcessing();
@@ -195,6 +195,82 @@ let findEventConfig = (~config: Config.t, ~contractName: string, ~eventName: str
195
195
  found.contents
196
196
  }
197
197
 
198
+ let dummySrcAddress = Address.unsafeFromString("0x0000000000000000000000000000000000000000")
199
+
200
+ // First address configured for `contractName` on the simulated chain. Only the
201
+ // simulated chain is consulted — a contract's address on another chain has no
202
+ // meaning here.
203
+ let firstContractAddress = (~chainConfig: Config.chain, ~contractName: string): option<
204
+ Address.t,
205
+ > => {
206
+ let found = ref(None)
207
+ chainConfig.contracts->Array.forEach(contract => {
208
+ if found.contents->Option.isNone && contract.name === contractName {
209
+ found := contract.addresses->Array.get(0)
210
+ }
211
+ })
212
+ found.contents
213
+ }
214
+
215
+ let deriveSrcAddress = (
216
+ ~providedSrcAddress: option<Address.t>,
217
+ ~eventConfig: Internal.eventConfig,
218
+ ~chainConfig: Config.chain,
219
+ ): Address.t => {
220
+ switch providedSrcAddress {
221
+ | Some(addr) => addr
222
+ | None =>
223
+ if eventConfig.isWildcard {
224
+ dummySrcAddress
225
+ } else {
226
+ switch firstContractAddress(~chainConfig, ~contractName=eventConfig.contractName) {
227
+ | Some(addr) => addr
228
+ | None => dummySrcAddress
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ // A non-wildcard event is gated by `clientAddressFilter` on srcAddress ownership,
235
+ // so a simulate item whose srcAddress isn't indexed on this chain would be
236
+ // silently dropped (handler never runs). Fail loudly instead.
237
+ //
238
+ // `config` must already have handler registrations applied, otherwise an event
239
+ // made wildcard purely through `indexer.onEvent({ wildcard: true })` reads back
240
+ // as non-wildcard and gets wrongly rejected.
241
+ let validateSrcAddresses = (
242
+ ~simulateItems: array<JSON.t>,
243
+ ~config: Config.t,
244
+ ~chainConfig: Config.chain,
245
+ ~indexingAddresses: array<Internal.indexingAddress>,
246
+ ): unit => {
247
+ let known = Utils.Set.make()
248
+ indexingAddresses->Array.forEach(ia => known->Utils.Set.add(ia.address->Address.toString)->ignore)
249
+ simulateItems->Array.forEach(rawJson => {
250
+ let raw = rawJson->(Utils.magic: JSON.t => rawSimulateItem)
251
+ switch (raw->getContract, raw->getEvent) {
252
+ | (Some(contractName), Some(eventName)) =>
253
+ switch findEventConfig(~config, ~contractName, ~eventName) {
254
+ | Some(eventConfig) if !eventConfig.isWildcard =>
255
+ let item = rawJson->(Utils.magic: JSON.t => Envio.evmSimulateItem)
256
+ let srcAddress = deriveSrcAddress(
257
+ ~providedSrcAddress=item.srcAddress,
258
+ ~eventConfig,
259
+ ~chainConfig,
260
+ )
261
+ if !(known->Utils.Set.has(srcAddress->Address.toString)) {
262
+ JsError.throwWithMessage(
263
+ `simulate: ${contractName}.${eventName} resolved to address ${srcAddress->Address.toString}, which isn't indexed on chain ${chainConfig.id->Int.toString}. ` ++
264
+ `Provide a "srcAddress" configured or registered for ${contractName} on this chain, or use a wildcard event.`,
265
+ )
266
+ }
267
+ | _ => ()
268
+ }
269
+ | _ => ()
270
+ }
271
+ })
272
+ }
273
+
198
274
  let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Config.chain): array<
199
275
  Internal.item,
200
276
  > => {
@@ -238,21 +314,11 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
238
314
  li
239
315
  }
240
316
 
241
- let srcAddress = switch item.srcAddress {
242
- | Some(addr) => addr
243
- | None =>
244
- // Use first address from contract config
245
- let addr = ref(Address.unsafeFromString("0x0000000000000000000000000000000000000000"))
246
- chainConfig.contracts->Array.forEach(contract => {
247
- if contract.name === contractName {
248
- switch contract.addresses->Array.get(0) {
249
- | Some(a) => addr := a
250
- | None => ()
251
- }
252
- }
253
- })
254
- addr.contents
255
- }
317
+ let srcAddress = deriveSrcAddress(
318
+ ~providedSrcAddress=item.srcAddress,
319
+ ~eventConfig,
320
+ ~chainConfig,
321
+ )
256
322
 
257
323
  let rawItem = rawJson->(Utils.magic: JSON.t => {..})
258
324
  let blockJson: option<JSON.t> =
@@ -135,6 +135,64 @@ function findEventConfig(config, contractName, eventName) {
135
135
  return found.contents;
136
136
  }
137
137
 
138
+ let dummySrcAddress = "0x0000000000000000000000000000000000000000";
139
+
140
+ function firstContractAddress(chainConfig, contractName) {
141
+ let found = {
142
+ contents: undefined
143
+ };
144
+ chainConfig.contracts.forEach(contract => {
145
+ if (Stdlib_Option.isNone(found.contents) && contract.name === contractName) {
146
+ found.contents = contract.addresses[0];
147
+ return;
148
+ }
149
+ });
150
+ return found.contents;
151
+ }
152
+
153
+ function deriveSrcAddress(providedSrcAddress, eventConfig, chainConfig) {
154
+ if (providedSrcAddress !== undefined) {
155
+ return Primitive_option.valFromOption(providedSrcAddress);
156
+ }
157
+ if (eventConfig.isWildcard) {
158
+ return dummySrcAddress;
159
+ }
160
+ let addr = firstContractAddress(chainConfig, eventConfig.contractName);
161
+ if (addr !== undefined) {
162
+ return Primitive_option.valFromOption(addr);
163
+ } else {
164
+ return dummySrcAddress;
165
+ }
166
+ }
167
+
168
+ function validateSrcAddresses(simulateItems, config, chainConfig, indexingAddresses) {
169
+ let known = new Set();
170
+ indexingAddresses.forEach(ia => {
171
+ known.add(ia.address);
172
+ });
173
+ simulateItems.forEach(rawJson => {
174
+ let match = rawJson.contract;
175
+ let match$1 = rawJson.event;
176
+ if (match === undefined) {
177
+ return;
178
+ }
179
+ if (match$1 === undefined) {
180
+ return;
181
+ }
182
+ let eventConfig = findEventConfig(config, match, match$1);
183
+ if (eventConfig === undefined) {
184
+ return;
185
+ }
186
+ if (eventConfig.isWildcard) {
187
+ return;
188
+ }
189
+ let srcAddress = deriveSrcAddress(rawJson.srcAddress, eventConfig, chainConfig);
190
+ if (!known.has(srcAddress)) {
191
+ return Stdlib_JsError.throwWithMessage(`simulate: ` + match + `.` + match$1 + ` resolved to address ` + srcAddress + `, which isn't indexed on chain ` + chainConfig.id.toString() + `. ` + (`Provide a "srcAddress" configured or registered for ` + match + ` on this chain, or use a wildcard event.`));
192
+ }
193
+ });
194
+ }
195
+
138
196
  function parse(simulateItems, config, chainConfig) {
139
197
  let chain = ChainMap.Chain.makeUnsafe(chainConfig.id);
140
198
  let chainId = chainConfig.id;
@@ -169,26 +227,7 @@ function parse(simulateItems, config, chainConfig) {
169
227
  currentLogIndex.contents = li$1 + 1 | 0;
170
228
  logIndex = li$1;
171
229
  }
172
- let addr = rawJson.srcAddress;
173
- let srcAddress;
174
- if (addr !== undefined) {
175
- srcAddress = Primitive_option.valFromOption(addr);
176
- } else {
177
- let addr$1 = {
178
- contents: "0x0000000000000000000000000000000000000000"
179
- };
180
- chainConfig.contracts.forEach(contract => {
181
- if (contract.name !== match) {
182
- return;
183
- }
184
- let a = contract.addresses[0];
185
- if (a !== undefined) {
186
- addr$1.contents = Primitive_option.valFromOption(a);
187
- return;
188
- }
189
- });
190
- srcAddress = addr$1.contents;
191
- }
230
+ let srcAddress = deriveSrcAddress(rawJson.srcAddress, eventConfig, chainConfig);
192
231
  let blockJson = rawJson.block;
193
232
  let blockJson$1 = (blockJson == null) ? undefined : Primitive_option.some(blockJson);
194
233
  let transactionJson = rawJson.transaction;
@@ -318,6 +357,10 @@ export {
318
357
  fuelSimulateTransactionSchema,
319
358
  parseFuelSimulateTransaction,
320
359
  findEventConfig,
360
+ dummySrcAddress,
361
+ firstContractAddress,
362
+ deriveSrcAddress,
363
+ validateSrcAddresses,
321
364
  parse,
322
365
  patchConfig,
323
366
  }
@@ -43,6 +43,33 @@ let toIndexingAddress = (dc: InternalTable.EnvioAddresses.t): Internal.indexingA
43
43
  registrationBlock: dc.registrationBlock,
44
44
  }
45
45
 
46
+ // All indexing addresses (config-seeded + dynamically registered) grouped by
47
+ // chain id string, derived from the envio_addresses entities.
48
+ let getIndexingAddressesByChain = (state: testIndexerState): dict<
49
+ array<Internal.indexingAddress>,
50
+ > => {
51
+ let byChain = Dict.make()
52
+ switch state.entities->Dict.get(InternalTable.EnvioAddresses.name) {
53
+ | Some(dcDict) =>
54
+ dcDict
55
+ ->Dict.valuesToArray
56
+ ->Array.forEach(entity => {
57
+ let dc = entity->castToEnvioAddresses
58
+ let chainIdStr = dc.chainId->Int.toString
59
+ let contracts = switch byChain->Dict.get(chainIdStr) {
60
+ | Some(arr) => arr
61
+ | None =>
62
+ let arr = []
63
+ byChain->Dict.set(chainIdStr, arr)
64
+ arr
65
+ }
66
+ contracts->Array.push(dc->toIndexingAddress)->ignore
67
+ })
68
+ | None => ()
69
+ }
70
+ byChain
71
+ }
72
+
46
73
  let handleLoad = (state: testIndexerState, ~tableName: string, ~filter: EntityFilter.t): JSON.t => {
47
74
  // Loads for non-entity tables (e.g. effect caches `envio_effect_<name>`) reach
48
75
  // here too. TestIndexer never persists those, so there's nothing to return —
@@ -566,22 +593,10 @@ let makeCreateTestIndexer = (~config: Config.t, ~workerPath: string): (
566
593
  `Cannot access ${contract.name}.addresses while indexer.process() is running. ` ++ "Wait for process() to complete before reading contract addresses.",
567
594
  )
568
595
  }
569
- let addresses = []
570
- switch state.entities->Dict.get(InternalTable.EnvioAddresses.name) {
571
- | Some(dcDict) =>
572
- dcDict
573
- ->Dict.valuesToArray
574
- ->Array.forEach(
575
- entity => {
576
- let dc = entity->castToEnvioAddresses
577
- if dc.contractName === contract.name && dc.chainId === chainConfig.id {
578
- addresses->Array.push(dc->Config.EnvioAddresses.getAddress)->ignore
579
- }
580
- },
581
- )
582
- | None => ()
583
- }
584
- addresses
596
+ getIndexingAddressesByChain(state)
597
+ ->Dict.get(chainConfig.id->Int.toString)
598
+ ->Option.getOr([])
599
+ ->Array.filterMap(ia => ia.contractName === contract.name ? Some(ia.address) : None)
585
600
  },
586
601
  },
587
602
  )
@@ -652,7 +667,11 @@ let makeCreateTestIndexer = (~config: Config.t, ~workerPath: string): (
652
667
  Int.compare(aId, bId)
653
668
  })
654
669
 
655
- // Parse and validate all chain configs upfront before starting any workers
670
+ // Parse and validate the block ranges upfront before starting any
671
+ // workers. srcAddress validation can't run here: it depends on which
672
+ // events are wildcard, and `config` reflects config.yaml only —
673
+ // handler-level `wildcard: true` takes effect only once registrations
674
+ // are applied.
656
675
  let chainEntries = sortedChainKeys->Array.map(chainIdStr => {
657
676
  let rawChainConfig = rawChains->Dict.getUnsafe(chainIdStr)
658
677
  let chainId = switch chainIdStr->Int.fromString {
@@ -684,26 +703,9 @@ let makeCreateTestIndexer = (~config: Config.t, ~workerPath: string): (
684
703
  let chains: dict<chainConfig> = Dict.make()
685
704
  chains->Dict.set(chainIdStr, processChainConfig)
686
705
 
687
- // Extract dynamic contracts from state.entities for each chain
688
- let indexingAddressesByChain: dict<array<Internal.indexingAddress>> = Dict.make()
689
- switch state.entities->Dict.get(InternalTable.EnvioAddresses.name) {
690
- | Some(dcDict) =>
691
- dcDict
692
- ->Dict.valuesToArray
693
- ->Array.forEach(entity => {
694
- let dc = entity->castToEnvioAddresses
695
- let dcChainIdStr = dc.chainId->Int.toString
696
- let contracts = switch indexingAddressesByChain->Dict.get(dcChainIdStr) {
697
- | Some(arr) => arr
698
- | None =>
699
- let arr = []
700
- indexingAddressesByChain->Dict.set(dcChainIdStr, arr)
701
- arr
702
- }
703
- contracts->Array.push(dc->toIndexingAddress)->ignore
704
- })
705
- | None => ()
706
- }
706
+ // Rebuilt per chain so workers see contracts registered by earlier
707
+ // chains in the same process() call.
708
+ let indexingAddressesByChain = getIndexingAddressesByChain(state)
707
709
 
708
710
  let initialState = makeInitialState(
709
711
  ~config,
@@ -865,7 +867,27 @@ let initTestWorker = () => {
865
867
  | Some(_) => ()
866
868
  }
867
869
 
868
- let patchConfig = (config, _registrations) => {
870
+ let patchConfig = (config: Config.t, _registrations) => {
871
+ // `config` here has handler registrations applied, so `isWildcard`
872
+ // reflects `indexer.onEvent({ wildcard: true })` — which the main thread
873
+ // can't see. Validate srcAddresses against it before patching in the
874
+ // simulate source.
875
+ switch simulate {
876
+ | Some(simulateItems) =>
877
+ let chainConfig = config.chainMap->ChainMap.get(ChainMap.Chain.makeUnsafe(~chainId))
878
+ let indexingAddresses =
879
+ initialState.chains
880
+ ->Array.find(c => c.id == chainId)
881
+ ->Option.mapOr([], c => c.indexingAddresses)
882
+ SimulateItems.validateSrcAddresses(
883
+ ~simulateItems,
884
+ ~config,
885
+ ~chainConfig,
886
+ ~indexingAddresses,
887
+ )
888
+ | None => ()
889
+ }
890
+
869
891
  let config = SimulateItems.patchConfig(~config, ~processConfig)
870
892
 
871
893
  // In auto-exit mode, set batchSize=1 to process one block checkpoint at a time
@@ -875,7 +897,23 @@ let initTestWorker = () => {
875
897
  config
876
898
  }
877
899
  }
878
- Main.start(~persistence, ~isTest=true, ~patchConfig, ~exitAfterFirstEventBlock)->ignore
900
+ Main.start(~persistence, ~isTest=true, ~patchConfig, ~exitAfterFirstEventBlock)
901
+ ->Promise.catch(exn => {
902
+ // `Main.start` rejects on any fatal error: a runtime failure arrives wrapped
903
+ // in `Main.FatalError` (already logged), a setup throw (e.g. patchConfig's
904
+ // srcAddress validation) arrives raw. The parent only learns of failures
905
+ // through the worker `error` event, which fires on an *uncaught* exception.
906
+ // Throwing synchronously in this catch would just reject the catch's own
907
+ // promise (swallowed by `ignore`); `setImmediate` re-throws outside the
908
+ // promise chain so it becomes uncaught and reaches the parent.
909
+ let toThrow = switch exn {
910
+ | Main.FatalError(inner) => inner
911
+ | _ => exn->Utils.prettifyExn
912
+ }
913
+ NodeJs.setImmediate(() => throw(toThrow))
914
+ Promise.resolve()
915
+ })
916
+ ->ignore
879
917
  | None =>
880
918
  Logging.error("TestIndexerWorker: No worker data provided")
881
919
  NodeJs.process->NodeJs.exitWithCode(Failure)