envio 3.3.0-alpha.9 → 3.3.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 (94) hide show
  1. package/index.d.ts +19 -0
  2. package/licenses/CLA.md +35 -0
  3. package/licenses/EULA.md +67 -0
  4. package/licenses/LICENSE.md +45 -0
  5. package/licenses/README.md +35 -0
  6. package/package.json +9 -8
  7. package/src/Batch.res.mjs +1 -1
  8. package/src/BatchProcessing.res +0 -7
  9. package/src/BatchProcessing.res.mjs +1 -8
  10. package/src/ChainFetching.res +30 -18
  11. package/src/ChainFetching.res.mjs +23 -13
  12. package/src/ChainState.res +134 -55
  13. package/src/ChainState.res.mjs +78 -36
  14. package/src/ChainState.resi +14 -2
  15. package/src/Config.res +9 -5
  16. package/src/Config.res.mjs +2 -2
  17. package/src/Core.res +20 -0
  18. package/src/Core.res.mjs +12 -0
  19. package/src/CrossChainState.res +115 -34
  20. package/src/CrossChainState.res.mjs +37 -14
  21. package/src/EffectState.res +100 -0
  22. package/src/EffectState.res.mjs +74 -0
  23. package/src/EffectState.resi +32 -0
  24. package/src/Envio.res +25 -7
  25. package/src/Envio.res.mjs +15 -19
  26. package/src/EventConfigBuilder.res +21 -46
  27. package/src/EventConfigBuilder.res.mjs +18 -25
  28. package/src/EventProcessing.res +8 -5
  29. package/src/EventProcessing.res.mjs +2 -1
  30. package/src/FetchState.res +814 -467
  31. package/src/FetchState.res.mjs +541 -369
  32. package/src/HandlerRegister.res +3 -1
  33. package/src/HandlerRegister.res.mjs +3 -2
  34. package/src/InMemoryStore.res +14 -26
  35. package/src/InMemoryStore.res.mjs +6 -19
  36. package/src/IndexerState.res +15 -39
  37. package/src/IndexerState.res.mjs +18 -29
  38. package/src/IndexerState.resi +2 -10
  39. package/src/Internal.res +98 -14
  40. package/src/Internal.res.mjs +96 -2
  41. package/src/LoadLayer.res +44 -24
  42. package/src/LoadLayer.res.mjs +43 -20
  43. package/src/LoadLayer.resi +1 -0
  44. package/src/LogSelection.res +0 -62
  45. package/src/LogSelection.res.mjs +0 -74
  46. package/src/Metrics.res +1 -1
  47. package/src/Metrics.res.mjs +1 -1
  48. package/src/Persistence.res +12 -3
  49. package/src/PgStorage.res +155 -82
  50. package/src/PgStorage.res.mjs +130 -77
  51. package/src/Prometheus.res +20 -10
  52. package/src/Prometheus.res.mjs +22 -10
  53. package/src/PruneStaleHistory.res +146 -35
  54. package/src/PruneStaleHistory.res.mjs +114 -20
  55. package/src/RawEvent.res +2 -2
  56. package/src/Rollback.res +32 -13
  57. package/src/Rollback.res.mjs +24 -11
  58. package/src/SimulateDeadInputTracker.res +1 -1
  59. package/src/SimulateItems.res +38 -6
  60. package/src/SimulateItems.res.mjs +28 -9
  61. package/src/TestIndexer.res +2 -2
  62. package/src/TestIndexer.res.mjs +2 -2
  63. package/src/TopicFilter.res +1 -25
  64. package/src/TopicFilter.res.mjs +0 -74
  65. package/src/UserContext.res +62 -23
  66. package/src/UserContext.res.mjs +26 -6
  67. package/src/Writing.res +60 -21
  68. package/src/Writing.res.mjs +27 -9
  69. package/src/bindings/NodeJs.res +5 -0
  70. package/src/bindings/Viem.res +0 -5
  71. package/src/sources/EventRouter.res +0 -21
  72. package/src/sources/EventRouter.res.mjs +0 -12
  73. package/src/sources/EvmChain.res +2 -27
  74. package/src/sources/EvmChain.res.mjs +2 -23
  75. package/src/sources/EvmRpcClient.res +58 -23
  76. package/src/sources/EvmRpcClient.res.mjs +11 -5
  77. package/src/sources/HyperSync.res +9 -38
  78. package/src/sources/HyperSync.res.mjs +16 -28
  79. package/src/sources/HyperSync.resi +2 -2
  80. package/src/sources/HyperSyncClient.res +88 -11
  81. package/src/sources/HyperSyncClient.res.mjs +39 -6
  82. package/src/sources/HyperSyncSource.res +18 -199
  83. package/src/sources/HyperSyncSource.res.mjs +9 -128
  84. package/src/sources/Rpc.res +0 -32
  85. package/src/sources/Rpc.res.mjs +1 -46
  86. package/src/sources/RpcSource.res +129 -522
  87. package/src/sources/RpcSource.res.mjs +161 -366
  88. package/src/sources/SimulateSource.res +37 -19
  89. package/src/sources/SimulateSource.res.mjs +27 -10
  90. package/src/sources/SourceManager.res +3 -7
  91. package/src/sources/SourceManager.res.mjs +2 -7
  92. package/src/sources/SourceManager.resi +0 -2
  93. package/src/sources/SvmHyperSyncSource.res +13 -3
  94. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
@@ -606,29 +606,39 @@ let effectLabelsSchema = S.object(s => {
606
606
  s.field("effect", S.string)
607
607
  })
608
608
 
609
+ // For metrics whose backing state lives per scope ("crossChain" or a chain
610
+ // id) — without the label, scopes of the same effect would clobber each
611
+ // other's gauge value.
612
+ let effectScopeLabelsSchema = S.schema(s =>
613
+ {
614
+ "effect": s.matches(S.string),
615
+ "scope": s.matches(S.string),
616
+ }
617
+ )
618
+
609
619
  module EffectCalls = {
610
620
  let timeCounter = SafeCounter.makeOrThrow(
611
621
  ~name="envio_effect_call_seconds",
612
622
  ~help="Processing time taken to call the Effect function.",
613
- ~labelSchema=effectLabelsSchema,
623
+ ~labelSchema=effectScopeLabelsSchema,
614
624
  )
615
625
 
616
626
  let sumTimeCounter = SafeCounter.makeOrThrow(
617
627
  ~name="envio_effect_call_seconds_total",
618
628
  ~help="Cumulative time spent calling the Effect function during the indexing process.",
619
- ~labelSchema=effectLabelsSchema,
629
+ ~labelSchema=effectScopeLabelsSchema,
620
630
  )
621
631
 
622
632
  let totalCallsCount = SafeCounter.makeOrThrow(
623
633
  ~name="envio_effect_call_total",
624
634
  ~help="Cumulative number of resolved Effect function calls during the indexing process.",
625
- ~labelSchema=effectLabelsSchema,
635
+ ~labelSchema=effectScopeLabelsSchema,
626
636
  )
627
637
 
628
638
  let activeCallsCount = SafeGauge.makeOrThrow(
629
639
  ~name="envio_effect_active_calls",
630
640
  ~help="The number of Effect function calls that are currently running.",
631
- ~labelSchema=effectLabelsSchema,
641
+ ~labelSchema=effectScopeLabelsSchema,
632
642
  )
633
643
  }
634
644
 
@@ -636,11 +646,11 @@ module EffectCacheCount = {
636
646
  let gauge = SafeGauge.makeOrThrow(
637
647
  ~name="envio_effect_cache",
638
648
  ~help="The number of items in the effect cache.",
639
- ~labelSchema=effectLabelsSchema,
649
+ ~labelSchema=effectScopeLabelsSchema,
640
650
  )
641
651
 
642
- let set = (~count, ~effectName) => {
643
- gauge->SafeGauge.handleInt(~labels=effectName, ~value=count)
652
+ let set = (~count, ~effectName, ~scope) => {
653
+ gauge->SafeGauge.handleInt(~labels={"effect": effectName, "scope": scope}, ~value=count)
644
654
  }
645
655
  }
646
656
 
@@ -660,7 +670,7 @@ module EffectQueueCount = {
660
670
  let gauge = SafeGauge.makeOrThrow(
661
671
  ~name="envio_effect_queue",
662
672
  ~help="The number of effect calls waiting in the rate limit queue.",
663
- ~labelSchema=effectLabelsSchema,
673
+ ~labelSchema=effectScopeLabelsSchema,
664
674
  )
665
675
 
666
676
  let timeCounter = SafeCounter.makeOrThrow(
@@ -669,8 +679,8 @@ module EffectQueueCount = {
669
679
  ~labelSchema=effectLabelsSchema,
670
680
  )
671
681
 
672
- let set = (~count, ~effectName) => {
673
- gauge->SafeGauge.handleInt(~labels=effectName, ~value=count)
682
+ let set = (~count, ~effectName, ~scope) => {
683
+ gauge->SafeGauge.handleInt(~labels={"effect": effectName, "scope": scope}, ~value=count)
674
684
  }
675
685
  }
676
686
 
@@ -651,13 +651,18 @@ let ProgressLatency = {
651
651
 
652
652
  let effectLabelsSchema = S$RescriptSchema.object(s => s.f("effect", S$RescriptSchema.string));
653
653
 
654
- let timeCounter$5 = makeOrThrow("envio_effect_call_seconds", "Processing time taken to call the Effect function.", effectLabelsSchema);
654
+ let effectScopeLabelsSchema = S$RescriptSchema.schema(s => ({
655
+ effect: s.m(S$RescriptSchema.string),
656
+ scope: s.m(S$RescriptSchema.string)
657
+ }));
658
+
659
+ let timeCounter$5 = makeOrThrow("envio_effect_call_seconds", "Processing time taken to call the Effect function.", effectScopeLabelsSchema);
655
660
 
656
- let sumTimeCounter$1 = makeOrThrow("envio_effect_call_seconds_total", "Cumulative time spent calling the Effect function during the indexing process.", effectLabelsSchema);
661
+ let sumTimeCounter$1 = makeOrThrow("envio_effect_call_seconds_total", "Cumulative time spent calling the Effect function during the indexing process.", effectScopeLabelsSchema);
657
662
 
658
- let totalCallsCount = makeOrThrow("envio_effect_call_total", "Cumulative number of resolved Effect function calls during the indexing process.", effectLabelsSchema);
663
+ let totalCallsCount = makeOrThrow("envio_effect_call_total", "Cumulative number of resolved Effect function calls during the indexing process.", effectScopeLabelsSchema);
659
664
 
660
- let activeCallsCount = makeOrThrow$1("envio_effect_active_calls", "The number of Effect function calls that are currently running.", effectLabelsSchema);
665
+ let activeCallsCount = makeOrThrow$1("envio_effect_active_calls", "The number of Effect function calls that are currently running.", effectScopeLabelsSchema);
661
666
 
662
667
  let EffectCalls = {
663
668
  timeCounter: timeCounter$5,
@@ -666,10 +671,13 @@ let EffectCalls = {
666
671
  activeCallsCount: activeCallsCount
667
672
  };
668
673
 
669
- let gauge$19 = makeOrThrow$1("envio_effect_cache", "The number of items in the effect cache.", effectLabelsSchema);
674
+ let gauge$19 = makeOrThrow$1("envio_effect_cache", "The number of items in the effect cache.", effectScopeLabelsSchema);
670
675
 
671
- function set$19(count, effectName) {
672
- handleInt$1(gauge$19, effectName, count);
676
+ function set$19(count, effectName, scope) {
677
+ handleInt$1(gauge$19, {
678
+ effect: effectName,
679
+ scope: scope
680
+ }, count);
673
681
  }
674
682
 
675
683
  let EffectCacheCount = {
@@ -688,12 +696,15 @@ let EffectCacheInvalidationsCount = {
688
696
  increment: increment$7
689
697
  };
690
698
 
691
- let gauge$20 = makeOrThrow$1("envio_effect_queue", "The number of effect calls waiting in the rate limit queue.", effectLabelsSchema);
699
+ let gauge$20 = makeOrThrow$1("envio_effect_queue", "The number of effect calls waiting in the rate limit queue.", effectScopeLabelsSchema);
692
700
 
693
701
  let timeCounter$6 = makeOrThrow("envio_effect_queue_wait_seconds", "The time spent waiting in the rate limit queue.", effectLabelsSchema);
694
702
 
695
- function set$20(count, effectName) {
696
- handleInt$1(gauge$20, effectName, count);
703
+ function set$20(count, effectName, scope) {
704
+ handleInt$1(gauge$20, {
705
+ effect: effectName,
706
+ scope: scope
707
+ }, count);
697
708
  }
698
709
 
699
710
  let EffectQueueCount = {
@@ -825,6 +836,7 @@ export {
825
836
  ProgressEventsCount,
826
837
  ProgressLatency,
827
838
  effectLabelsSchema,
839
+ effectScopeLabelsSchema,
828
840
  EffectCalls,
829
841
  EffectCacheCount,
830
842
  EffectCacheInvalidationsCount,
@@ -1,45 +1,156 @@
1
- // Throttled prune of stale entity-history rows below the safe checkpoint.
2
-
3
- let runPrune = async (state: IndexerState.t) => {
4
- switch state->IndexerState.getSafeCheckpointId {
5
- | None => ()
6
- | Some(safeCheckpointId) =>
7
- let persistence = state->IndexerState.persistence
8
- await persistence.storage.pruneStaleCheckpoints(~safeCheckpointId)
9
-
10
- for idx in 0 to persistence.allEntities->Array.length - 1 {
11
- if idx !== 0 {
12
- // Add some delay between entities
13
- // To unblock the pg client if it's needed for something else
14
- await Utils.delay(1000)
1
+ // Prune of stale entity-history rows below the safe checkpoint.
2
+ //
3
+ // Pruning must never run concurrently with a write to the same entity's history
4
+ // table: the prune's anchor deletion relies on "no history after the safe
5
+ // checkpoint", which a concurrently committing batch can falsify, losing the
6
+ // anchor and breaking a later rollback. The write loop enforces the safety by
7
+ // running the concurrent group only for entities absent from the batch being
8
+ // written (and awaiting it before the next write starts), and the forced group
9
+ // alone after the write.
10
+
11
+ let maxEntitiesPerWrite = 5
12
+ let forcedIntervalMultiplier = 5.
13
+
14
+ type targets = {
15
+ safeCheckpointId: Internal.checkpointId,
16
+ concurrent: array<Internal.entityConfig>,
17
+ forced: array<Internal.entityConfig>,
18
+ }
19
+
20
+ let selectFrom = (
21
+ ~allEntities: array<Internal.entityConfig>,
22
+ ~lastPrunedAtMillis: dict<float>,
23
+ ~writtenEntityNames: Utils.Set.t<string>,
24
+ ~isRollback,
25
+ ~nowMillis,
26
+ ~intervalMillis,
27
+ ~safeCheckpointId,
28
+ ) => {
29
+ let byOldestPrune = ((a, _), (b, _)) => a -. b
30
+ let toEntities = candidates => candidates->Array.map(((_, entityConfig)) => entityConfig)
31
+
32
+ let concurrentCandidates = []
33
+ let forcedCandidates = []
34
+ allEntities->Array.forEach(entityConfig => {
35
+ if entityConfig.storage.postgres {
36
+ let lastPrunedAt =
37
+ lastPrunedAtMillis
38
+ ->Utils.Dict.dangerouslyGetNonOption(entityConfig.name)
39
+ ->Option.getOr(0.)
40
+ if (
41
+ !isRollback &&
42
+ !(writtenEntityNames->Utils.Set.has(entityConfig.name)) &&
43
+ nowMillis -. lastPrunedAt >= intervalMillis
44
+ ) {
45
+ concurrentCandidates->Array.push((lastPrunedAt, entityConfig))
46
+ } else if nowMillis -. lastPrunedAt >= intervalMillis *. forcedIntervalMultiplier {
47
+ forcedCandidates->Array.push((lastPrunedAt, entityConfig))
15
48
  }
16
- let entityConfig = persistence.allEntities->Array.getUnsafe(idx)
17
- let timeRef = Performance.now()
18
- try {
19
- let () = await persistence.storage.pruneStaleEntityHistory(
20
- ~entityName=entityConfig.name,
21
- ~entityIndex=entityConfig.index,
49
+ }
50
+ })
51
+
52
+ let sortedConcurrent = concurrentCandidates->Array.toSorted(byOldestPrune)
53
+ // Concurrent candidates beyond the cap are not selected, so the starved
54
+ // ones among them still qualify for the forced group.
55
+ for idx in maxEntitiesPerWrite to sortedConcurrent->Array.length - 1 {
56
+ let (lastPrunedAt, _) = sortedConcurrent->Array.getUnsafe(idx)
57
+ if nowMillis -. lastPrunedAt >= intervalMillis *. forcedIntervalMultiplier {
58
+ forcedCandidates->Array.push(sortedConcurrent->Array.getUnsafe(idx))
59
+ }
60
+ }
61
+
62
+ {
63
+ safeCheckpointId,
64
+ concurrent: sortedConcurrent->Array.slice(~start=0, ~end=maxEntitiesPerWrite)->toEntities,
65
+ forced: forcedCandidates
66
+ ->Array.toSorted(byOldestPrune)
67
+ ->Array.slice(~start=0, ~end=maxEntitiesPerWrite)
68
+ ->toEntities,
69
+ }
70
+ }
71
+
72
+ let select = (state: IndexerState.t, ~writtenEntityNames, ~isRollback) => {
73
+ let config = state->IndexerState.config
74
+ if config->Config.shouldPruneHistory(~isInReorgThreshold=state->IndexerState.isInReorgThreshold) {
75
+ switch state->IndexerState.getSafeCheckpointId {
76
+ | None => None
77
+ | Some(safeCheckpointId) =>
78
+ Some(
79
+ selectFrom(
80
+ ~allEntities=(state->IndexerState.persistence).allEntities,
81
+ ~lastPrunedAtMillis=state->IndexerState.lastPrunedAtMillis,
82
+ ~writtenEntityNames,
83
+ ~isRollback,
84
+ ~nowMillis=Date.now(),
85
+ ~intervalMillis=Env.ThrottleWrites.pruneStaleDataIntervalMillis->Int.toFloat,
22
86
  ~safeCheckpointId,
23
- )
24
- } catch {
25
- | exn =>
26
- exn->ErrorHandling.mkLogAndRaise(
27
- ~msg=`Failed to prune stale entity history`,
28
- ~logger=Logging.createChild(
29
- ~params={
30
- "entityName": entityConfig.name,
31
- "safeCheckpointId": safeCheckpointId,
32
- },
33
- ),
34
- )
35
- }
87
+ ),
88
+ )
89
+ }
90
+ } else {
91
+ None
92
+ }
93
+ }
94
+
95
+ let pruneEntities = async (state: IndexerState.t, ~entities, ~safeCheckpointId) => {
96
+ let persistence = state->IndexerState.persistence
97
+ for idx in 0 to entities->Array.length - 1 {
98
+ let entityConfig: Internal.entityConfig = entities->Array.getUnsafe(idx)
99
+ let timeRef = Performance.now()
100
+ // Recorded for failures too, so a failing prune retries on the same
101
+ // interval instead of on every write.
102
+ state->IndexerState.lastPrunedAtMillis->Dict.set(entityConfig.name, Date.now())
103
+ switch await persistence.storage.pruneStaleEntityHistory(
104
+ ~entityName=entityConfig.name,
105
+ ~entityIndex=entityConfig.index,
106
+ ~safeCheckpointId,
107
+ ) {
108
+ | () =>
36
109
  Prometheus.RollbackHistoryPrune.increment(
37
110
  ~timeSeconds=Performance.secondsSince(timeRef),
38
111
  ~entityName=entityConfig.name,
39
112
  )
113
+ | exception exn =>
114
+ // Pruning is cleanup; a failure must not fail the write loop.
115
+ Logging.createChild(
116
+ ~params={
117
+ "entityName": entityConfig.name,
118
+ "safeCheckpointId": safeCheckpointId,
119
+ },
120
+ )->Logging.childErrorWithExn(exn->Utils.prettifyExn, `Failed to prune stale entity history`)
40
121
  }
41
122
  }
42
123
  }
43
124
 
44
- let schedule = (state: IndexerState.t) =>
45
- state->IndexerState.pruneStaleEntityHistoryThrottler->Throttler.schedule(() => runPrune(state))
125
+ let pruneCheckpoints = async (state: IndexerState.t, ~safeCheckpointId) => {
126
+ switch await (state->IndexerState.persistence).storage.pruneStaleCheckpoints(~safeCheckpointId) {
127
+ | () => ()
128
+ | exception exn =>
129
+ Logging.createChild(~params={"safeCheckpointId": safeCheckpointId})->Logging.childErrorWithExn(
130
+ exn->Utils.prettifyExn,
131
+ `Failed to prune stale checkpoints`,
132
+ )
133
+ }
134
+ }
135
+
136
+ let runConcurrent = async (state: IndexerState.t, ~targets) => {
137
+ switch targets {
138
+ | Some({safeCheckpointId, concurrent}) if concurrent->Utils.Array.notEmpty =>
139
+ await pruneCheckpoints(state, ~safeCheckpointId)
140
+ await pruneEntities(state, ~entities=concurrent, ~safeCheckpointId)
141
+ | Some(_) | None => ()
142
+ }
143
+ }
144
+
145
+ let runForced = async (state: IndexerState.t, ~targets) => {
146
+ switch targets {
147
+ | Some({safeCheckpointId, concurrent, forced}) if forced->Utils.Array.notEmpty =>
148
+ // When nothing ran concurrently (eg a rollback write), checkpoint pruning
149
+ // lands here, after the write, so it never overlaps a rollback transaction.
150
+ if concurrent->Utils.Array.isEmpty {
151
+ await pruneCheckpoints(state, ~safeCheckpointId)
152
+ }
153
+ await pruneEntities(state, ~entities=forced, ~safeCheckpointId)
154
+ | Some(_) | None => ()
155
+ }
156
+ }
@@ -1,46 +1,140 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Env from "./Env.res.mjs";
3
4
  import * as Utils from "./Utils.res.mjs";
5
+ import * as Config from "./Config.res.mjs";
4
6
  import * as Logging from "./Logging.res.mjs";
5
- import * as Throttler from "./Throttler.res.mjs";
6
7
  import * as Prometheus from "./Prometheus.res.mjs";
7
8
  import * as Performance from "./bindings/Performance.res.mjs";
8
9
  import * as IndexerState from "./IndexerState.res.mjs";
9
- import * as ErrorHandling from "./ErrorHandling.res.mjs";
10
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
10
11
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
11
12
 
12
- async function runPrune(state) {
13
- let safeCheckpointId = IndexerState.getSafeCheckpointId(state);
14
- if (safeCheckpointId === undefined) {
13
+ function selectFrom(allEntities, lastPrunedAtMillis, writtenEntityNames, isRollback, nowMillis, intervalMillis, safeCheckpointId) {
14
+ let byOldestPrune = (param, param$1) => param[0] - param$1[0];
15
+ let toEntities = candidates => candidates.map(param => param[1]);
16
+ let concurrentCandidates = [];
17
+ let forcedCandidates = [];
18
+ allEntities.forEach(entityConfig => {
19
+ if (!entityConfig.storage.postgres) {
20
+ return;
21
+ }
22
+ let lastPrunedAt = Stdlib_Option.getOr(lastPrunedAtMillis[entityConfig.name], 0);
23
+ if (!isRollback && !writtenEntityNames.has(entityConfig.name) && nowMillis - lastPrunedAt >= intervalMillis) {
24
+ concurrentCandidates.push([
25
+ lastPrunedAt,
26
+ entityConfig
27
+ ]);
28
+ return;
29
+ } else if (nowMillis - lastPrunedAt >= intervalMillis * 5) {
30
+ forcedCandidates.push([
31
+ lastPrunedAt,
32
+ entityConfig
33
+ ]);
34
+ return;
35
+ } else {
36
+ return;
37
+ }
38
+ });
39
+ let sortedConcurrent = concurrentCandidates.toSorted(byOldestPrune);
40
+ for (let idx = 5, idx_finish = sortedConcurrent.length; idx < idx_finish; ++idx) {
41
+ let match = sortedConcurrent[idx];
42
+ if (nowMillis - match[0] >= intervalMillis * 5) {
43
+ forcedCandidates.push(sortedConcurrent[idx]);
44
+ }
45
+ }
46
+ return {
47
+ safeCheckpointId: safeCheckpointId,
48
+ concurrent: toEntities(sortedConcurrent.slice(0, 5)),
49
+ forced: toEntities(forcedCandidates.toSorted(byOldestPrune).slice(0, 5))
50
+ };
51
+ }
52
+
53
+ function select(state, writtenEntityNames, isRollback) {
54
+ let config = IndexerState.config(state);
55
+ if (!Config.shouldPruneHistory(config, IndexerState.isInReorgThreshold(state))) {
15
56
  return;
16
57
  }
58
+ let safeCheckpointId = IndexerState.getSafeCheckpointId(state);
59
+ if (safeCheckpointId !== undefined) {
60
+ return selectFrom(IndexerState.persistence(state).allEntities, IndexerState.lastPrunedAtMillis(state), writtenEntityNames, isRollback, Date.now(), Env.ThrottleWrites.pruneStaleDataIntervalMillis, safeCheckpointId);
61
+ }
62
+ }
63
+
64
+ async function pruneEntities(state, entities, safeCheckpointId) {
17
65
  let persistence = IndexerState.persistence(state);
18
- await persistence.storage.pruneStaleCheckpoints(safeCheckpointId);
19
- for (let idx = 0, idx_finish = persistence.allEntities.length; idx < idx_finish; ++idx) {
20
- if (idx !== 0) {
21
- await Utils.delay(1000);
22
- }
23
- let entityConfig = persistence.allEntities[idx];
66
+ for (let idx = 0, idx_finish = entities.length; idx < idx_finish; ++idx) {
67
+ let entityConfig = entities[idx];
24
68
  let timeRef = Performance.now();
69
+ IndexerState.lastPrunedAtMillis(state)[entityConfig.name] = Date.now();
70
+ let exit = 0;
71
+ let val;
25
72
  try {
26
- await persistence.storage.pruneStaleEntityHistory(entityConfig.name, entityConfig.index, safeCheckpointId);
73
+ val = await persistence.storage.pruneStaleEntityHistory(entityConfig.name, entityConfig.index, safeCheckpointId);
74
+ exit = 1;
27
75
  } catch (raw_exn) {
28
76
  let exn = Primitive_exceptions.internalToException(raw_exn);
29
- ErrorHandling.mkLogAndRaise(Logging.createChild({
77
+ Logging.childErrorWithExn(Logging.createChild({
30
78
  entityName: entityConfig.name,
31
79
  safeCheckpointId: safeCheckpointId
32
- }), `Failed to prune stale entity history`, exn);
80
+ }), Utils.prettifyExn(exn), `Failed to prune stale entity history`);
81
+ }
82
+ if (exit === 1) {
83
+ Prometheus.RollbackHistoryPrune.increment(Performance.secondsSince(timeRef), entityConfig.name);
33
84
  }
34
- Prometheus.RollbackHistoryPrune.increment(Performance.secondsSince(timeRef), entityConfig.name);
35
85
  }
36
86
  }
37
87
 
38
- function schedule(state) {
39
- Throttler.schedule(IndexerState.pruneStaleEntityHistoryThrottler(state), () => runPrune(state));
88
+ async function pruneCheckpoints(state, safeCheckpointId) {
89
+ try {
90
+ await IndexerState.persistence(state).storage.pruneStaleCheckpoints(safeCheckpointId);
91
+ return;
92
+ } catch (raw_exn) {
93
+ let exn = Primitive_exceptions.internalToException(raw_exn);
94
+ return Logging.childErrorWithExn(Logging.createChild({
95
+ safeCheckpointId: safeCheckpointId
96
+ }), Utils.prettifyExn(exn), `Failed to prune stale checkpoints`);
97
+ }
98
+ }
99
+
100
+ async function runConcurrent(state, targets) {
101
+ if (targets === undefined) {
102
+ return;
103
+ }
104
+ let concurrent = targets.concurrent;
105
+ let safeCheckpointId = targets.safeCheckpointId;
106
+ if (Utils.$$Array.notEmpty(concurrent)) {
107
+ await pruneCheckpoints(state, safeCheckpointId);
108
+ return await pruneEntities(state, concurrent, safeCheckpointId);
109
+ }
110
+ }
111
+
112
+ async function runForced(state, targets) {
113
+ if (targets === undefined) {
114
+ return;
115
+ }
116
+ let forced = targets.forced;
117
+ let safeCheckpointId = targets.safeCheckpointId;
118
+ if (Utils.$$Array.notEmpty(forced)) {
119
+ if (Utils.$$Array.isEmpty(targets.concurrent)) {
120
+ await pruneCheckpoints(state, safeCheckpointId);
121
+ }
122
+ return await pruneEntities(state, forced, safeCheckpointId);
123
+ }
40
124
  }
41
125
 
126
+ let maxEntitiesPerWrite = 5;
127
+
128
+ let forcedIntervalMultiplier = 5;
129
+
42
130
  export {
43
- runPrune,
44
- schedule,
131
+ maxEntitiesPerWrite,
132
+ forcedIntervalMultiplier,
133
+ selectFrom,
134
+ select,
135
+ pruneEntities,
136
+ pruneCheckpoints,
137
+ runConcurrent,
138
+ runForced,
45
139
  }
46
- /* Utils Not a pure module */
140
+ /* Env Not a pure module */
package/src/RawEvent.res CHANGED
@@ -32,8 +32,8 @@ let make = (
32
32
  ~blockTimestamp: int,
33
33
  ~cleanUpRawEventFieldsInPlace: JSON.t => unit,
34
34
  ): Internal.rawEvent => {
35
- let {onEventRegistration, chain, blockNumber, logIndex} = eventItem
36
- let eventConfig = onEventRegistration.eventConfig
35
+ let {chain, blockNumber, logIndex} = eventItem
36
+ let eventConfig = eventItem.onEventRegistration.eventConfig
37
37
  let chainId = chain->ChainMap.Chain.toChainId
38
38
  let eventId = EventUtils.packEventIndex(~logIndex, ~blockNumber)
39
39
  let blockFields =
package/src/Rollback.res CHANGED
@@ -72,7 +72,7 @@ let rec rollback = async (
72
72
  // found yet. Wait for the ReorgDetected branch above to find it and re-kick.
73
73
  | FindingReorgDepth => ()
74
74
  | FoundReorgDepth(_) if state->IndexerState.isProcessing =>
75
- Logging.info("Waiting for batch to finish processing before executing rollback")
75
+ Logging.trace("Waiting for batch to finish processing before executing rollback")
76
76
  | FoundReorgDepth({chain: reorgChain, rollbackTargetBlockNumber}) =>
77
77
  await executeRollback(
78
78
  state,
@@ -96,10 +96,10 @@ and executeRollback = async (
96
96
  ) => {
97
97
  let startTime = Performance.now()
98
98
 
99
- let chainState = state->IndexerState.getChainState(~chain=reorgChain)
100
-
101
- let logger = Logging.createChildFrom(
102
- ~logger=chainState->ChainState.logger,
99
+ // Not derived from the reorg chain's logger: that would bind its chainId onto
100
+ // every line, colliding with the per-chain chainId on the "Rollbacked" logs.
101
+ // The reorg chain is identified by the reorgChain param instead.
102
+ let logger = Logging.createChild(
103
103
  ~params={
104
104
  "action": "Rollback",
105
105
  "reorgChain": reorgChain,
@@ -161,10 +161,12 @@ and executeRollback = async (
161
161
  }
162
162
  }
163
163
 
164
+ let rolledBackChains = []
164
165
  state
165
166
  ->IndexerState.chainStates
166
167
  ->Utils.Dict.forEach(cs => {
167
168
  let chainId = (cs->ChainState.chainConfig).id
169
+ let fromBlock = cs->ChainState.committedProgressBlockNumber
168
170
  cs->ChainState.rollback(
169
171
  ~newProgressBlockNumber=newProgressBlockNumberPerChain->Utils.Dict.dangerouslyGetByIntNonOption(
170
172
  chainId,
@@ -175,6 +177,19 @@ and executeRollback = async (
175
177
  ~rollbackTargetBlockNumber,
176
178
  ~isReorgChain=chainId === reorgChainId,
177
179
  )
180
+ let toBlock = cs->ChainState.committedProgressBlockNumber
181
+ if fromBlock !== toBlock {
182
+ rolledBackChains
183
+ ->Array.push({
184
+ "chainId": chainId,
185
+ "fromBlock": fromBlock,
186
+ "toBlock": toBlock,
187
+ "rollbackedEvents": eventsProcessedDiffByChain
188
+ ->Utils.Dict.dangerouslyGetByIntNonOption(chainId)
189
+ ->Option.getOr(0.),
190
+ })
191
+ ->ignore
192
+ }
178
193
  })
179
194
 
180
195
  let diff = await state->InMemoryStore.prepareRollbackDiff(
@@ -183,15 +198,19 @@ and executeRollback = async (
183
198
  ~progressBlockNumberByChainId=newProgressBlockNumberPerChain,
184
199
  )
185
200
 
201
+ rolledBackChains->Array.forEach(chain => {
202
+ logger->Logging.childInfo({
203
+ "msg": "Rollbacked",
204
+ "chainId": chain["chainId"],
205
+ "fromBlock": chain["fromBlock"],
206
+ "toBlock": chain["toBlock"],
207
+ "rollbackedEvents": chain["rollbackedEvents"],
208
+ })
209
+ })
186
210
  logger->Logging.childTrace({
187
- "msg": "Finished rollback on reorg",
188
- "entityChanges": {
189
- "deleted": diff["deletedEntities"],
190
- "upserted": diff["setEntities"],
191
- },
192
- "rollbackedEvents": rollbackedProcessedEvents.contents,
193
- "beforeCheckpointId": state->IndexerState.committedCheckpointId,
194
- "targetCheckpointId": rollbackTargetCheckpointId,
211
+ "msg": "Rollback entity changes",
212
+ "deleted": diff["deletedEntities"],
213
+ "upserted": diff["setEntities"],
195
214
  })
196
215
  Prometheus.RollbackSuccess.increment(
197
216
  ~timeSeconds=Performance.secondsSince(startTime),
@@ -33,8 +33,7 @@ async function getLastKnownValidBlock(chainState, reorgBlockNumber, isRealtime)
33
33
 
34
34
  async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, scheduleFetch, scheduleProcessing) {
35
35
  let startTime = Performance.now();
36
- let chainState = IndexerState.getChainState(state, reorgChain);
37
- let logger = Logging.createChildFrom(ChainState.logger(chainState), {
36
+ let logger = Logging.createChild({
38
37
  action: "Rollback",
39
38
  reorgChain: reorgChain,
40
39
  targetBlockNumber: rollbackTargetBlockNumber
@@ -55,20 +54,34 @@ async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, sch
55
54
  eventsProcessedDiffByChain[diff.chain_id] = eventsProcessedDiff;
56
55
  newProgressBlockNumberPerChain[diff.chain_id] = rollbackTargetCheckpointId === 0n && diff.chain_id === reorgChain ? Primitive_int.min(diff.new_progress_block_number, rollbackTargetBlockNumber) : diff.new_progress_block_number;
57
56
  }
57
+ let rolledBackChains = [];
58
58
  Utils.Dict.forEach(IndexerState.chainStates(state), cs => {
59
59
  let chainId = ChainState.chainConfig(cs).id;
60
+ let fromBlock = ChainState.committedProgressBlockNumber(cs);
60
61
  ChainState.rollback(cs, newProgressBlockNumberPerChain[chainId], eventsProcessedDiffByChain[chainId], rollbackTargetBlockNumber, chainId === reorgChain);
62
+ let toBlock = ChainState.committedProgressBlockNumber(cs);
63
+ if (fromBlock !== toBlock) {
64
+ rolledBackChains.push({
65
+ chainId: chainId,
66
+ fromBlock: fromBlock,
67
+ toBlock: toBlock,
68
+ rollbackedEvents: Stdlib_Option.getOr(eventsProcessedDiffByChain[chainId], 0)
69
+ });
70
+ return;
71
+ }
61
72
  });
62
73
  let diff$1 = await InMemoryStore.prepareRollbackDiff(state, rollbackTargetCheckpointId, IndexerState.committedCheckpointId(state) + 1n, newProgressBlockNumberPerChain);
74
+ rolledBackChains.forEach(chain => Logging.childInfo(logger, {
75
+ msg: "Rollbacked",
76
+ chainId: chain.chainId,
77
+ fromBlock: chain.fromBlock,
78
+ toBlock: chain.toBlock,
79
+ rollbackedEvents: chain.rollbackedEvents
80
+ }));
63
81
  Logging.childTrace(logger, {
64
- msg: "Finished rollback on reorg",
65
- entityChanges: {
66
- deleted: diff$1.deletedEntities,
67
- upserted: diff$1.setEntities
68
- },
69
- rollbackedEvents: rollbackedProcessedEvents,
70
- beforeCheckpointId: IndexerState.committedCheckpointId(state),
71
- targetCheckpointId: rollbackTargetCheckpointId
82
+ msg: "Rollback entity changes",
83
+ deleted: diff$1.deletedEntities,
84
+ upserted: diff$1.setEntities
72
85
  });
73
86
  Prometheus.RollbackSuccess.increment(Performance.secondsSince(startTime), rollbackedProcessedEvents);
74
87
  IndexerState.completeRollback(state, eventsProcessedDiffByChain);
@@ -97,7 +110,7 @@ async function rollback(state, scheduleFetch, scheduleProcessing, scheduleRollba
97
110
  return scheduleRollback();
98
111
  case "FoundReorgDepth" :
99
112
  if (IndexerState.isProcessing(state)) {
100
- return Logging.info("Waiting for batch to finish processing before executing rollback");
113
+ return Logging.trace("Waiting for batch to finish processing before executing rollback");
101
114
  } else {
102
115
  return await executeRollback(state, match.chain, match.rollbackTargetBlockNumber, scheduleFetch, scheduleProcessing);
103
116
  }
@@ -10,7 +10,7 @@ let itemKey = (item: Internal.item): string =>
10
10
  `${chain
11
11
  ->ChainMap.Chain.toChainId
12
12
  ->Int.toString}:${blockNumber->Int.toString}:${logIndex->Int.toString}`
13
- | _ => "" // non-event items are never a provided simulate input
13
+ | Internal.Block(_) => ""
14
14
  }
15
15
 
16
16
  // `index` is the item's position in its chain's `simulate` array, reported back