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.
- package/index.d.ts +19 -0
- package/licenses/CLA.md +35 -0
- package/licenses/EULA.md +67 -0
- package/licenses/LICENSE.md +45 -0
- package/licenses/README.md +35 -0
- package/package.json +9 -8
- package/src/Batch.res.mjs +1 -1
- package/src/BatchProcessing.res +0 -7
- package/src/BatchProcessing.res.mjs +1 -8
- package/src/ChainFetching.res +30 -18
- package/src/ChainFetching.res.mjs +23 -13
- package/src/ChainState.res +134 -55
- package/src/ChainState.res.mjs +78 -36
- package/src/ChainState.resi +14 -2
- package/src/Config.res +9 -5
- package/src/Config.res.mjs +2 -2
- package/src/Core.res +20 -0
- package/src/Core.res.mjs +12 -0
- package/src/CrossChainState.res +115 -34
- package/src/CrossChainState.res.mjs +37 -14
- package/src/EffectState.res +100 -0
- package/src/EffectState.res.mjs +74 -0
- package/src/EffectState.resi +32 -0
- package/src/Envio.res +25 -7
- package/src/Envio.res.mjs +15 -19
- package/src/EventConfigBuilder.res +21 -46
- package/src/EventConfigBuilder.res.mjs +18 -25
- package/src/EventProcessing.res +8 -5
- package/src/EventProcessing.res.mjs +2 -1
- package/src/FetchState.res +814 -467
- package/src/FetchState.res.mjs +541 -369
- package/src/HandlerRegister.res +3 -1
- package/src/HandlerRegister.res.mjs +3 -2
- package/src/InMemoryStore.res +14 -26
- package/src/InMemoryStore.res.mjs +6 -19
- package/src/IndexerState.res +15 -39
- package/src/IndexerState.res.mjs +18 -29
- package/src/IndexerState.resi +2 -10
- package/src/Internal.res +98 -14
- package/src/Internal.res.mjs +96 -2
- package/src/LoadLayer.res +44 -24
- package/src/LoadLayer.res.mjs +43 -20
- package/src/LoadLayer.resi +1 -0
- package/src/LogSelection.res +0 -62
- package/src/LogSelection.res.mjs +0 -74
- package/src/Metrics.res +1 -1
- package/src/Metrics.res.mjs +1 -1
- package/src/Persistence.res +12 -3
- package/src/PgStorage.res +155 -82
- package/src/PgStorage.res.mjs +130 -77
- package/src/Prometheus.res +20 -10
- package/src/Prometheus.res.mjs +22 -10
- package/src/PruneStaleHistory.res +146 -35
- package/src/PruneStaleHistory.res.mjs +114 -20
- package/src/RawEvent.res +2 -2
- package/src/Rollback.res +32 -13
- package/src/Rollback.res.mjs +24 -11
- package/src/SimulateDeadInputTracker.res +1 -1
- package/src/SimulateItems.res +38 -6
- package/src/SimulateItems.res.mjs +28 -9
- package/src/TestIndexer.res +2 -2
- package/src/TestIndexer.res.mjs +2 -2
- package/src/TopicFilter.res +1 -25
- package/src/TopicFilter.res.mjs +0 -74
- package/src/UserContext.res +62 -23
- package/src/UserContext.res.mjs +26 -6
- package/src/Writing.res +60 -21
- package/src/Writing.res.mjs +27 -9
- package/src/bindings/NodeJs.res +5 -0
- package/src/bindings/Viem.res +0 -5
- package/src/sources/EventRouter.res +0 -21
- package/src/sources/EventRouter.res.mjs +0 -12
- package/src/sources/EvmChain.res +2 -27
- package/src/sources/EvmChain.res.mjs +2 -23
- package/src/sources/EvmRpcClient.res +58 -23
- package/src/sources/EvmRpcClient.res.mjs +11 -5
- package/src/sources/HyperSync.res +9 -38
- package/src/sources/HyperSync.res.mjs +16 -28
- package/src/sources/HyperSync.resi +2 -2
- package/src/sources/HyperSyncClient.res +88 -11
- package/src/sources/HyperSyncClient.res.mjs +39 -6
- package/src/sources/HyperSyncSource.res +18 -199
- package/src/sources/HyperSyncSource.res.mjs +9 -128
- package/src/sources/Rpc.res +0 -32
- package/src/sources/Rpc.res.mjs +1 -46
- package/src/sources/RpcSource.res +129 -522
- package/src/sources/RpcSource.res.mjs +161 -366
- package/src/sources/SimulateSource.res +37 -19
- package/src/sources/SimulateSource.res.mjs +27 -10
- package/src/sources/SourceManager.res +3 -7
- package/src/sources/SourceManager.res.mjs +2 -7
- package/src/sources/SourceManager.resi +0 -2
- package/src/sources/SvmHyperSyncSource.res +13 -3
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
package/src/Prometheus.res
CHANGED
|
@@ -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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
|
package/src/Prometheus.res.mjs
CHANGED
|
@@ -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
|
|
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.",
|
|
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.",
|
|
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.",
|
|
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.",
|
|
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,
|
|
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.",
|
|
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,
|
|
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
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
45
|
-
state->IndexerState.
|
|
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
|
|
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
|
-
|
|
13
|
-
let
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
77
|
+
Logging.childErrorWithExn(Logging.createChild({
|
|
30
78
|
entityName: entityConfig.name,
|
|
31
79
|
safeCheckpointId: safeCheckpointId
|
|
32
|
-
}), `Failed to prune stale entity history
|
|
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
|
|
39
|
-
|
|
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
|
-
|
|
44
|
-
|
|
131
|
+
maxEntitiesPerWrite,
|
|
132
|
+
forcedIntervalMultiplier,
|
|
133
|
+
selectFrom,
|
|
134
|
+
select,
|
|
135
|
+
pruneEntities,
|
|
136
|
+
pruneCheckpoints,
|
|
137
|
+
runConcurrent,
|
|
138
|
+
runForced,
|
|
45
139
|
}
|
|
46
|
-
/*
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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": "
|
|
188
|
-
"
|
|
189
|
-
|
|
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),
|
package/src/Rollback.res.mjs
CHANGED
|
@@ -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
|
|
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: "
|
|
65
|
-
|
|
66
|
-
|
|
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.
|
|
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
|
-
| _ => ""
|
|
13
|
+
| Internal.Block(_) => ""
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
// `index` is the item's position in its chain's `simulate` array, reported back
|