envio 3.3.0-alpha.4 → 3.3.0-alpha.6

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 (55) hide show
  1. package/evm.schema.json +10 -0
  2. package/package.json +7 -7
  3. package/src/Batch.res.mjs +1 -1
  4. package/src/BatchProcessing.res +5 -0
  5. package/src/BatchProcessing.res.mjs +6 -0
  6. package/src/ChainState.res +41 -7
  7. package/src/ChainState.res.mjs +22 -18
  8. package/src/ChainState.resi +3 -1
  9. package/src/Config.res +3 -0
  10. package/src/Config.res.mjs +3 -1
  11. package/src/Core.res +0 -3
  12. package/src/ExitOnCaughtUp.res +10 -2
  13. package/src/ExitOnCaughtUp.res.mjs +10 -1
  14. package/src/FetchState.res +63 -133
  15. package/src/FetchState.res.mjs +107 -166
  16. package/src/IndexerState.res +4 -0
  17. package/src/IndexerState.res.mjs +8 -1
  18. package/src/IndexerState.resi +1 -0
  19. package/src/IndexingAddresses.res +105 -0
  20. package/src/IndexingAddresses.res.mjs +100 -0
  21. package/src/IndexingAddresses.resi +32 -0
  22. package/src/Main.res +4 -15
  23. package/src/Main.res.mjs +6 -14
  24. package/src/Metrics.res +33 -0
  25. package/src/Metrics.res.mjs +39 -0
  26. package/src/Prometheus.res +2 -20
  27. package/src/Prometheus.res.mjs +83 -95
  28. package/src/SimulateDeadInputTracker.res +85 -0
  29. package/src/SimulateDeadInputTracker.res.mjs +73 -0
  30. package/src/SimulateDeadInputTracker.resi +12 -0
  31. package/src/SimulateItems.res +23 -42
  32. package/src/SimulateItems.res.mjs +12 -30
  33. package/src/TestIndexer.res +3 -27
  34. package/src/TestIndexer.res.mjs +2 -9
  35. package/src/bindings/Viem.res +0 -41
  36. package/src/bindings/Viem.res.mjs +1 -43
  37. package/src/sources/Evm.res +7 -36
  38. package/src/sources/Evm.res.mjs +4 -36
  39. package/src/sources/EvmChain.res +4 -2
  40. package/src/sources/EvmChain.res.mjs +3 -2
  41. package/src/sources/EvmRpcClient.res +36 -5
  42. package/src/sources/EvmRpcClient.res.mjs +7 -4
  43. package/src/sources/HyperSyncClient.res +0 -35
  44. package/src/sources/HyperSyncClient.res.mjs +1 -8
  45. package/src/sources/Rpc.res +15 -47
  46. package/src/sources/Rpc.res.mjs +25 -56
  47. package/src/sources/RpcSource.res +289 -204
  48. package/src/sources/RpcSource.res.mjs +293 -303
  49. package/src/sources/SimulateSource.res +1 -0
  50. package/src/sources/SimulateSource.res.mjs +2 -1
  51. package/src/sources/Source.res +4 -0
  52. package/src/sources/Svm.res +7 -15
  53. package/src/sources/Svm.res.mjs +4 -15
  54. package/src/sources/TransactionStore.res +14 -2
  55. package/src/sources/TransactionStore.res.mjs +10 -2
@@ -389,39 +389,28 @@ let ProcessStartTimeSeconds = {
389
389
  set: set$3
390
390
  };
391
391
 
392
- let gauge$4 = makeOrThrow$1("envio_indexing_addresses", "The number of addresses indexed on chain. Includes both static and dynamic addresses.", chainIdLabelsSchema);
392
+ let gauge$4 = makeOrThrow$1("envio_indexing_concurrency", "The number of executing concurrent queries to the chain data-source.", chainIdLabelsSchema);
393
393
 
394
- function set$4(addressesCount, chainId) {
395
- handleInt$1(gauge$4, chainId, addressesCount);
394
+ function set$4(concurrency, chainId) {
395
+ handleInt$1(gauge$4, chainId, concurrency);
396
396
  }
397
397
 
398
- let IndexingAddresses = {
398
+ let IndexingConcurrency = {
399
399
  gauge: gauge$4,
400
400
  set: set$4
401
401
  };
402
402
 
403
- let gauge$5 = makeOrThrow$1("envio_indexing_concurrency", "The number of executing concurrent queries to the chain data-source.", chainIdLabelsSchema);
403
+ let gauge$5 = makeOrThrow$1("envio_indexing_partitions", "The number of partitions used to split fetching logic by addresses and block ranges.", chainIdLabelsSchema);
404
404
 
405
- function set$5(concurrency, chainId) {
406
- handleInt$1(gauge$5, chainId, concurrency);
405
+ function set$5(partitionsCount, chainId) {
406
+ handleInt$1(gauge$5, chainId, partitionsCount);
407
407
  }
408
408
 
409
- let IndexingConcurrency = {
409
+ let IndexingPartitions = {
410
410
  gauge: gauge$5,
411
411
  set: set$5
412
412
  };
413
413
 
414
- let gauge$6 = makeOrThrow$1("envio_indexing_partitions", "The number of partitions used to split fetching logic by addresses and block ranges.", chainIdLabelsSchema);
415
-
416
- function set$6(partitionsCount, chainId) {
417
- handleInt$1(gauge$6, chainId, partitionsCount);
418
- }
419
-
420
- let IndexingPartitions = {
421
- gauge: gauge$6,
422
- set: set$6
423
- };
424
-
425
414
  let counter = makeOrThrow("envio_indexing_idle_seconds", "The time the indexer source syncing has been idle. A high value may indicate the source sync is a bottleneck.", chainIdLabelsSchema);
426
415
 
427
416
  let IndexingIdleTime = {
@@ -440,51 +429,51 @@ let IndexingQueryTime = {
440
429
  counter: counter$2
441
430
  };
442
431
 
443
- let gauge$7 = makeOrThrow$1("envio_indexing_buffer_size", "The current number of items in the indexing buffer.", chainIdLabelsSchema);
432
+ let gauge$6 = makeOrThrow$1("envio_indexing_buffer_size", "The current number of items in the indexing buffer.", chainIdLabelsSchema);
444
433
 
445
- function set$7(bufferSize, chainId) {
446
- handleInt$1(gauge$7, chainId, bufferSize);
434
+ function set$6(bufferSize, chainId) {
435
+ handleInt$1(gauge$6, chainId, bufferSize);
447
436
  }
448
437
 
449
438
  let IndexingBufferSize = {
450
- gauge: gauge$7,
451
- set: set$7
439
+ gauge: gauge$6,
440
+ set: set$6
452
441
  };
453
442
 
454
- let gauge$8 = PromClient.Gauge.makeGauge({
443
+ let gauge$7 = PromClient.Gauge.makeGauge({
455
444
  name: "envio_indexing_target_buffer_size",
456
445
  help: "The indexer-wide target buffer size shared across all chains. The actual number of items in the queue may exceed this value, but the indexer always tries to keep the buffer filled up to this target."
457
446
  });
458
447
 
459
- function set$8(targetBufferSize) {
460
- gauge$8.set(targetBufferSize);
448
+ function set$7(targetBufferSize) {
449
+ gauge$7.set(targetBufferSize);
461
450
  }
462
451
 
463
452
  let IndexingTargetBufferSize = {
464
- gauge: gauge$8,
465
- set: set$8
453
+ gauge: gauge$7,
454
+ set: set$7
466
455
  };
467
456
 
468
- let gauge$9 = makeOrThrow$1("envio_indexing_buffer_block", "The highest block number that has been fully fetched by the indexer.", chainIdLabelsSchema);
457
+ let gauge$8 = makeOrThrow$1("envio_indexing_buffer_block", "The highest block number that has been fully fetched by the indexer.", chainIdLabelsSchema);
469
458
 
470
- function set$9(blockNumber, chainId) {
471
- handleInt$1(gauge$9, chainId, blockNumber);
459
+ function set$8(blockNumber, chainId) {
460
+ handleInt$1(gauge$8, chainId, blockNumber);
472
461
  }
473
462
 
474
463
  let IndexingBufferBlockNumber = {
475
- gauge: gauge$9,
476
- set: set$9
464
+ gauge: gauge$8,
465
+ set: set$8
477
466
  };
478
467
 
479
- let gauge$10 = makeOrThrow$1("envio_indexing_end_block", "The block number to stop indexing at. (inclusive)", chainIdLabelsSchema);
468
+ let gauge$9 = makeOrThrow$1("envio_indexing_end_block", "The block number to stop indexing at. (inclusive)", chainIdLabelsSchema);
480
469
 
481
- function set$10(endBlock, chainId) {
482
- handleInt$1(gauge$10, chainId, endBlock);
470
+ function set$9(endBlock, chainId) {
471
+ handleInt$1(gauge$9, chainId, endBlock);
483
472
  }
484
473
 
485
474
  let IndexingEndBlock = {
486
- gauge: gauge$10,
487
- set: set$10
475
+ gauge: gauge$9,
476
+ set: set$9
488
477
  };
489
478
 
490
479
  let sourceLabelsSchema = S$RescriptSchema.schema(s => ({
@@ -525,18 +514,18 @@ let SourceRequestCount = {
525
514
  addSeconds: addSeconds
526
515
  };
527
516
 
528
- let gauge$11 = makeOrThrow$1("envio_source_known_height", "The latest known block number reported by the source. This value may lag behind the actual chain height, as it is updated only when queried.", sourceLabelsSchema);
517
+ let gauge$10 = makeOrThrow$1("envio_source_known_height", "The latest known block number reported by the source. This value may lag behind the actual chain height, as it is updated only when queried.", sourceLabelsSchema);
529
518
 
530
- function set$11(sourceName, chainId, blockNumber) {
531
- handleInt$1(gauge$11, {
519
+ function set$10(sourceName, chainId, blockNumber) {
520
+ handleInt$1(gauge$10, {
532
521
  source: sourceName,
533
522
  chainId: chainId
534
523
  }, blockNumber);
535
524
  }
536
525
 
537
526
  let SourceHeight = {
538
- gauge: gauge$11,
539
- set: set$11
527
+ gauge: gauge$10,
528
+ set: set$10
540
529
  };
541
530
 
542
531
  let counter$4 = makeOrThrow("envio_reorg_detected_total", "Total number of reorgs detected", chainIdLabelsSchema);
@@ -550,43 +539,43 @@ let ReorgCount = {
550
539
  increment: increment$5
551
540
  };
552
541
 
553
- let gauge$12 = makeOrThrow$1("envio_reorg_detected_block", "The block number where reorg was detected the last time. This doesn't mean that the block was reorged, this is simply where we found block hash to be different.", chainIdLabelsSchema);
542
+ let gauge$11 = makeOrThrow$1("envio_reorg_detected_block", "The block number where reorg was detected the last time. This doesn't mean that the block was reorged, this is simply where we found block hash to be different.", chainIdLabelsSchema);
554
543
 
555
- function set$12(blockNumber, chain) {
556
- handleInt$1(gauge$12, chain, blockNumber);
544
+ function set$11(blockNumber, chain) {
545
+ handleInt$1(gauge$11, chain, blockNumber);
557
546
  }
558
547
 
559
548
  let ReorgDetectionBlockNumber = {
560
- gauge: gauge$12,
561
- set: set$12
549
+ gauge: gauge$11,
550
+ set: set$11
562
551
  };
563
552
 
564
- let gauge$13 = PromClient.Gauge.makeGauge({
553
+ let gauge$12 = PromClient.Gauge.makeGauge({
565
554
  name: "envio_reorg_threshold",
566
555
  help: "Whether indexing is currently within the reorg threshold"
567
556
  });
568
557
 
569
- function set$13(isInReorgThreshold) {
570
- gauge$13.set(isInReorgThreshold ? 1 : 0);
558
+ function set$12(isInReorgThreshold) {
559
+ gauge$12.set(isInReorgThreshold ? 1 : 0);
571
560
  }
572
561
 
573
562
  let ReorgThreshold = {
574
- gauge: gauge$13,
575
- set: set$13
563
+ gauge: gauge$12,
564
+ set: set$12
576
565
  };
577
566
 
578
- let gauge$14 = PromClient.Gauge.makeGauge({
567
+ let gauge$13 = PromClient.Gauge.makeGauge({
579
568
  name: "envio_rollback_enabled",
580
569
  help: "Whether rollback on reorg is enabled"
581
570
  });
582
571
 
583
- function set$14(enabled) {
584
- gauge$14.set(enabled ? 1 : 0);
572
+ function set$13(enabled) {
573
+ gauge$13.set(enabled ? 1 : 0);
585
574
  }
586
575
 
587
576
  let RollbackEnabled = {
588
- gauge: gauge$14,
589
- set: set$14
577
+ gauge: gauge$13,
578
+ set: set$13
590
579
  };
591
580
 
592
581
  let timeCounter$3 = PromClient.Counter.makeCounter({
@@ -635,62 +624,62 @@ let RollbackHistoryPrune = {
635
624
  increment: increment$7
636
625
  };
637
626
 
638
- let gauge$15 = makeOrThrow$1("envio_rollback_target_block", "The block number reorg was rollbacked to the last time.", chainIdLabelsSchema);
627
+ let gauge$14 = makeOrThrow$1("envio_rollback_target_block", "The block number reorg was rollbacked to the last time.", chainIdLabelsSchema);
639
628
 
640
- function set$15(blockNumber, chain) {
641
- handleInt$1(gauge$15, chain, blockNumber);
629
+ function set$14(blockNumber, chain) {
630
+ handleInt$1(gauge$14, chain, blockNumber);
642
631
  }
643
632
 
644
633
  let RollbackTargetBlockNumber = {
645
- gauge: gauge$15,
646
- set: set$15
634
+ gauge: gauge$14,
635
+ set: set$14
647
636
  };
648
637
 
649
- let gauge$16 = PromClient.Gauge.makeGauge({
638
+ let gauge$15 = PromClient.Gauge.makeGauge({
650
639
  name: "envio_processing_max_batch_size",
651
640
  help: "The maximum number of items to process in a single batch."
652
641
  });
653
642
 
654
- function set$16(maxBatchSize) {
655
- gauge$16.set(maxBatchSize);
643
+ function set$15(maxBatchSize) {
644
+ gauge$15.set(maxBatchSize);
656
645
  }
657
646
 
658
647
  let ProcessingMaxBatchSize = {
659
- gauge: gauge$16,
660
- set: set$16
648
+ gauge: gauge$15,
649
+ set: set$15
661
650
  };
662
651
 
663
- let gauge$17 = makeOrThrow$1("envio_progress_block", "The block number of the latest block processed and stored in the database.", chainIdLabelsSchema);
652
+ let gauge$16 = makeOrThrow$1("envio_progress_block", "The block number of the latest block processed and stored in the database.", chainIdLabelsSchema);
664
653
 
665
- function set$17(blockNumber, chainId) {
666
- handleInt$1(gauge$17, chainId, blockNumber);
654
+ function set$16(blockNumber, chainId) {
655
+ handleInt$1(gauge$16, chainId, blockNumber);
667
656
  }
668
657
 
669
658
  let ProgressBlockNumber = {
670
- gauge: gauge$17,
671
- set: set$17
659
+ gauge: gauge$16,
660
+ set: set$16
672
661
  };
673
662
 
674
- let gauge$18 = makeOrThrow$1("envio_progress_events", "The number of events processed and reflected in the database.", chainIdLabelsSchema);
663
+ let gauge$17 = makeOrThrow$1("envio_progress_events", "The number of events processed and reflected in the database.", chainIdLabelsSchema);
675
664
 
676
- function set$18(processedCount, chainId) {
677
- handleFloat$1(gauge$18, chainId, processedCount);
665
+ function set$17(processedCount, chainId) {
666
+ handleFloat$1(gauge$17, chainId, processedCount);
678
667
  }
679
668
 
680
669
  let ProgressEventsCount = {
681
- gauge: gauge$18,
682
- set: set$18
670
+ gauge: gauge$17,
671
+ set: set$17
683
672
  };
684
673
 
685
- let gauge$19 = makeOrThrow$1("envio_progress_latency", "The latency in milliseconds between the latest processed event creation and the time it was written to storage.", chainIdLabelsSchema);
674
+ let gauge$18 = makeOrThrow$1("envio_progress_latency", "The latency in milliseconds between the latest processed event creation and the time it was written to storage.", chainIdLabelsSchema);
686
675
 
687
- function set$19(latencyMs, chainId) {
688
- handleInt$1(gauge$19, chainId, latencyMs);
676
+ function set$18(latencyMs, chainId) {
677
+ handleInt$1(gauge$18, chainId, latencyMs);
689
678
  }
690
679
 
691
680
  let ProgressLatency = {
692
- gauge: gauge$19,
693
- set: set$19
681
+ gauge: gauge$18,
682
+ set: set$18
694
683
  };
695
684
 
696
685
  let effectLabelsSchema = S$RescriptSchema.object(s => s.f("effect", S$RescriptSchema.string));
@@ -710,15 +699,15 @@ let EffectCalls = {
710
699
  activeCallsCount: activeCallsCount
711
700
  };
712
701
 
713
- let gauge$20 = makeOrThrow$1("envio_effect_cache", "The number of items in the effect cache.", effectLabelsSchema);
702
+ let gauge$19 = makeOrThrow$1("envio_effect_cache", "The number of items in the effect cache.", effectLabelsSchema);
714
703
 
715
- function set$20(count, effectName) {
716
- handleInt$1(gauge$20, effectName, count);
704
+ function set$19(count, effectName) {
705
+ handleInt$1(gauge$19, effectName, count);
717
706
  }
718
707
 
719
708
  let EffectCacheCount = {
720
- gauge: gauge$20,
721
- set: set$20
709
+ gauge: gauge$19,
710
+ set: set$19
722
711
  };
723
712
 
724
713
  let counter$7 = makeOrThrow("envio_effect_cache_invalidations", "The number of effect cache invalidations.", effectLabelsSchema);
@@ -732,18 +721,18 @@ let EffectCacheInvalidationsCount = {
732
721
  increment: increment$8
733
722
  };
734
723
 
735
- let gauge$21 = makeOrThrow$1("envio_effect_queue", "The number of effect calls waiting in the rate limit queue.", effectLabelsSchema);
724
+ let gauge$20 = makeOrThrow$1("envio_effect_queue", "The number of effect calls waiting in the rate limit queue.", effectLabelsSchema);
736
725
 
737
726
  let timeCounter$6 = makeOrThrow("envio_effect_queue_wait_seconds", "The time spent waiting in the rate limit queue.", effectLabelsSchema);
738
727
 
739
- function set$21(count, effectName) {
740
- handleInt$1(gauge$21, effectName, count);
728
+ function set$20(count, effectName) {
729
+ handleInt$1(gauge$20, effectName, count);
741
730
  }
742
731
 
743
732
  let EffectQueueCount = {
744
- gauge: gauge$21,
733
+ gauge: gauge$20,
745
734
  timeCounter: timeCounter$6,
746
- set: set$21
735
+ set: set$20
747
736
  };
748
737
 
749
738
  let loadLabelsSchema = S$RescriptSchema.schema(s => ({
@@ -846,7 +835,6 @@ export {
846
835
  IndexingKnownHeight,
847
836
  Info,
848
837
  ProcessStartTimeSeconds,
849
- IndexingAddresses,
850
838
  IndexingConcurrency,
851
839
  IndexingPartitions,
852
840
  IndexingIdleTime,
@@ -0,0 +1,85 @@
1
+ // Lives on IndexerState and is fed each processed batch, so ChainState and the
2
+ // fetch loop carry no simulate-specific state.
3
+
4
+ // Match a provided item to a processed one by its (chain, block, logIndex)
5
+ // coordinate rather than object identity, so matching survives any copy or
6
+ // transform of the item between the source and the batch.
7
+ let itemKey = (item: Internal.item): string =>
8
+ switch item {
9
+ | Internal.Event({chain, blockNumber, logIndex}) =>
10
+ `${chain
11
+ ->ChainMap.Chain.toChainId
12
+ ->Int.toString}:${blockNumber->Int.toString}:${logIndex->Int.toString}`
13
+ | _ => "" // non-event items are never a provided simulate input
14
+ }
15
+
16
+ // `index` is the item's position in its chain's `simulate` array, reported back
17
+ // so a user finds it without echoing its fields.
18
+ type entry = {chainId: int, index: int, key: string}
19
+
20
+ type t = {mutable unprocessed: array<entry>}
21
+
22
+ let makeFromConfig = (config: Config.t): option<t> => {
23
+ let entries =
24
+ config.chainMap
25
+ ->ChainMap.values
26
+ ->Array.flatMap(chainConfig =>
27
+ switch chainConfig.sourceConfig {
28
+ | Config.CustomSources(sources) =>
29
+ sources->Array.flatMap(source =>
30
+ source.simulateItems
31
+ ->Option.getOr([])
32
+ ->Array.mapWithIndex(
33
+ (item, index) => {
34
+ chainId: chainConfig.id,
35
+ index,
36
+ key: itemKey(item),
37
+ },
38
+ )
39
+ )
40
+ | _ => []
41
+ }
42
+ )
43
+ switch entries {
44
+ | [] => None
45
+ | _ => Some({unprocessed: entries})
46
+ }
47
+ }
48
+
49
+ let recordProcessed = (t: t, ~batch: Batch.t) => {
50
+ let processedKeys = batch.items->Array.map(itemKey)->Utils.Set.fromArray
51
+ t.unprocessed = t.unprocessed->Array.filter(entry => !(processedKeys->Utils.Set.has(entry.key)))
52
+ }
53
+
54
+ // Unrouted item indices grouped by chain, in the order chains were first seen.
55
+ let unroutedByChain = (t: t): array<(int, array<int>)> => {
56
+ let indicesByChain = Dict.make()
57
+ let chainOrder = []
58
+ t.unprocessed->Array.forEach(entry => {
59
+ let key = entry.chainId->Int.toString
60
+ if indicesByChain->Dict.get(key)->Option.isNone {
61
+ chainOrder->Array.push(entry.chainId)->ignore
62
+ }
63
+ indicesByChain->Utils.Dict.push(key, entry.index)
64
+ })
65
+ chainOrder->Array.map(chainId => (chainId, indicesByChain->Dict.getUnsafe(chainId->Int.toString)))
66
+ }
67
+
68
+ let failureMessage = (t: t): option<string> =>
69
+ switch t->unroutedByChain {
70
+ | [] => None
71
+ | byChain =>
72
+ let count = byChain->Array.reduce(0, (acc, (_chainId, indices)) => acc + indices->Array.length)
73
+ let itemWord = count === 1 ? "item" : "items"
74
+ let lines =
75
+ byChain
76
+ ->Array.map(((chainId, indices)) =>
77
+ ` - chain ${chainId->Int.toString}: ${indices
78
+ ->Array.map(index => index->Int.toString)
79
+ ->Array.join(", ")}`
80
+ )
81
+ ->Array.join("\n")
82
+ Some(
83
+ `simulate: ${count->Int.toString} ${itemWord} you passed to simulate never reached a handler, so nothing ran for them. Each was filtered out before the handler — usually a non-wildcard srcAddress that isn't indexed for the contract, or a where/block filter that excluded the event. Unrouted items, by index in each chain's simulate array:\n${lines}`,
84
+ )
85
+ }
@@ -0,0 +1,73 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Utils from "./Utils.res.mjs";
4
+ import * as ChainMap from "./ChainMap.res.mjs";
5
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
+
8
+ function itemKey(item) {
9
+ if (item.kind === 0) {
10
+ return item.chain.toString() + `:` + item.blockNumber.toString() + `:` + item.logIndex.toString();
11
+ } else {
12
+ return "";
13
+ }
14
+ }
15
+
16
+ function makeFromConfig(config) {
17
+ let entries = ChainMap.values(config.chainMap).flatMap(chainConfig => {
18
+ let sources = chainConfig.sourceConfig;
19
+ if (sources.TAG === "CustomSources") {
20
+ return sources._0.flatMap(source => Stdlib_Option.getOr(source.simulateItems, []).map((item, index) => ({
21
+ chainId: chainConfig.id,
22
+ index: index,
23
+ key: itemKey(item)
24
+ })));
25
+ } else {
26
+ return [];
27
+ }
28
+ });
29
+ if (entries.length !== 0) {
30
+ return {
31
+ unprocessed: entries
32
+ };
33
+ }
34
+ }
35
+
36
+ function recordProcessed(t, batch) {
37
+ let processedKeys = new Set(batch.items.map(itemKey));
38
+ t.unprocessed = t.unprocessed.filter(entry => !processedKeys.has(entry.key));
39
+ }
40
+
41
+ function unroutedByChain(t) {
42
+ let indicesByChain = {};
43
+ let chainOrder = [];
44
+ t.unprocessed.forEach(entry => {
45
+ let key = entry.chainId.toString();
46
+ if (Stdlib_Option.isNone(indicesByChain[key])) {
47
+ chainOrder.push(entry.chainId);
48
+ }
49
+ Utils.Dict.push(indicesByChain, key, entry.index);
50
+ });
51
+ return chainOrder.map(chainId => [
52
+ chainId,
53
+ indicesByChain[chainId.toString()]
54
+ ]);
55
+ }
56
+
57
+ function failureMessage(t) {
58
+ let byChain = unroutedByChain(t);
59
+ if (byChain.length === 0) {
60
+ return;
61
+ }
62
+ let count = Stdlib_Array.reduce(byChain, 0, (acc, param) => acc + param[1].length | 0);
63
+ let itemWord = count === 1 ? "item" : "items";
64
+ let lines = byChain.map(param => ` - chain ` + param[0].toString() + `: ` + param[1].map(index => index.toString()).join(", ")).join("\n");
65
+ return `simulate: ` + count.toString() + ` ` + itemWord + ` you passed to simulate never reached a handler, so nothing ran for them. Each was filtered out before the handler — usually a non-wildcard srcAddress that isn't indexed for the contract, or a where/block filter that excluded the event. Unrouted items, by index in each chain's simulate array:\n` + lines;
66
+ }
67
+
68
+ export {
69
+ makeFromConfig,
70
+ recordProcessed,
71
+ failureMessage,
72
+ }
73
+ /* Utils Not a pure module */
@@ -0,0 +1,12 @@
1
+ type t
2
+
3
+ // Builds a tracker from the run's config, seeded with every item each simulate
4
+ // source provided. None when no chain has a simulate source.
5
+ let makeFromConfig: Config.t => option<t>
6
+
7
+ // Drop the batch's items from the not-yet-routed set, matched by event coordinate.
8
+ let recordProcessed: (t, ~batch: Batch.t) => unit
9
+
10
+ // The error to fail the run with when items never reached a handler, listing them
11
+ // by chain and index within that chain's `simulate` array. None when all routed.
12
+ let failureMessage: t => option<string>
@@ -231,46 +231,6 @@ let deriveSrcAddress = (
231
231
  }
232
232
  }
233
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
-
274
234
  let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Config.chain): array<
275
235
  Internal.item,
276
236
  > => {
@@ -281,8 +241,11 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
281
241
  let currentLogIndex = ref(0)
282
242
 
283
243
  let items = []
244
+ // Coordinate "block:logIndex" -> the index of the first item that claimed it,
245
+ // used to reject two items resolving to the same (block, logIndex).
246
+ let seenCoordinates = Dict.make()
284
247
 
285
- simulateItems->Array.forEach(rawJson => {
248
+ simulateItems->Array.forEachWithIndex((rawJson, itemIndex) => {
286
249
  let raw = rawJson->(Utils.magic: JSON.t => rawSimulateItem)
287
250
 
288
251
  switch (raw->getContract, raw->getEvent) {
@@ -306,8 +269,14 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
306
269
  }
307
270
  let params = paramsJson->S.convertOrThrow(eventConfig.simulateParamsSchema)
308
271
 
272
+ // An explicit logIndex advances the auto-increment counter past itself, so a
273
+ // later item that omits logIndex picks up after it instead of colliding.
309
274
  let logIndex = switch item.logIndex {
310
- | Some(li) => li
275
+ | Some(li) =>
276
+ if li >= currentLogIndex.contents {
277
+ currentLogIndex := li + 1
278
+ }
279
+ li
311
280
  | None =>
312
281
  let li = currentLogIndex.contents
313
282
  currentLogIndex := li + 1
@@ -345,6 +314,18 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
345
314
  // Update currentBlock for subsequent items
346
315
  currentBlock := blockNumber
347
316
 
317
+ // A simulate item must land on a distinct (block, logIndex): event ordering
318
+ // and the dead-input tracker both key on it. Catch explicit duplicates and
319
+ // explicit-vs-auto-increment collisions here, naming both offending indices.
320
+ let coordinate = `${blockNumber->Int.toString}:${logIndex->Int.toString}`
321
+ switch seenCoordinates->Dict.get(coordinate) {
322
+ | Some(firstIndex) =>
323
+ JsError.throwWithMessage(
324
+ `simulate: items at index ${firstIndex->Int.toString} and ${itemIndex->Int.toString} on chain ${chainId->Int.toString} both resolve to block ${blockNumber->Int.toString}, logIndex ${logIndex->Int.toString}. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`,
325
+ )
326
+ | None => seenCoordinates->Dict.set(coordinate, itemIndex)
327
+ }
328
+
348
329
  items
349
330
  ->Array.push(
350
331
  Internal.Event({