envio 3.12.1 → 3.13.0-alpha.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/package.json +6 -6
- package/src/BatchProcessing.res +9 -9
- package/src/BatchProcessing.res.mjs +5 -5
- package/src/Bin.res +24 -17
- package/src/Bin.res.mjs +5 -0
- package/src/ChainFetching.res +24 -10
- package/src/ChainFetching.res.mjs +8 -3
- package/src/ChainState.res +43 -0
- package/src/ChainState.res.mjs +43 -0
- package/src/ChainState.resi +2 -0
- package/src/Config.res +35 -0
- package/src/Config.res.mjs +39 -0
- package/src/Core.res +4 -0
- package/src/Core.res.mjs +4 -0
- package/src/CrossChainState.res +60 -3
- package/src/CrossChainState.res.mjs +38 -4
- package/src/CrossChainState.resi +10 -1
- package/src/Env.res +4 -0
- package/src/IndexerLoop.res +2 -0
- package/src/IndexerLoop.res.mjs +1 -0
- package/src/IndexerState.res +41 -1
- package/src/IndexerState.res.mjs +43 -4
- package/src/IndexerState.resi +10 -0
- package/src/Logging.res +38 -6
- package/src/Logging.res.mjs +32 -5
- package/src/Main.res +131 -268
- package/src/Main.res.mjs +28 -152
- package/src/Metrics.res +263 -102
- package/src/Metrics.res.mjs +227 -48
- package/src/Persistence.res +27 -2
- package/src/Persistence.res.mjs +9 -2
- package/src/PgStorage.res +17 -9
- package/src/PgStorage.res.mjs +11 -8
- package/src/Server.res +181 -0
- package/src/Server.res.mjs +143 -0
- package/src/Supervisor.res +415 -0
- package/src/Supervisor.res.mjs +325 -0
- package/src/TestIndexer.res.mjs +1 -1
- package/src/Worker.res +95 -0
- package/src/Worker.res.mjs +80 -0
- package/src/bindings/NodeJs.res +41 -0
- package/src/db/InternalTable.res +8 -1
- package/src/db/InternalTable.res.mjs +5 -1
- package/src/tui/Tui.res +24 -0
- package/src/tui/Tui.res.mjs +18 -0
- package/src/tui/components/SyncETA.res +12 -6
- package/src/tui/components/SyncETA.res.mjs +12 -8
package/src/CrossChainState.res
CHANGED
|
@@ -17,6 +17,11 @@ type t = {
|
|
|
17
17
|
mutable isCaughtUp: bool,
|
|
18
18
|
// Indexer-wide fetch buffer pool (item count), shared across all chains.
|
|
19
19
|
targetBufferSize: int,
|
|
20
|
+
// Set on a process driving part of a split run: the chains it drives may be
|
|
21
|
+
// at the head while chains in another process are still backfilling, and an
|
|
22
|
+
// indexer switches to realtime as a whole or not at all. Cleared by the
|
|
23
|
+
// supervisor once every chain in the run has arrived.
|
|
24
|
+
mutable holdRealtime: bool,
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
// The whole-indexer fetch buffer pool, independent of chain count.
|
|
@@ -26,16 +31,50 @@ let calculateTargetBufferSize = () =>
|
|
|
26
31
|
| None => 100_000
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
let make = (
|
|
34
|
+
let make = (
|
|
35
|
+
~chainStates,
|
|
36
|
+
~isRealtime,
|
|
37
|
+
~targetBufferSize=calculateTargetBufferSize(),
|
|
38
|
+
~holdRealtime=false,
|
|
39
|
+
): t => {
|
|
30
40
|
{
|
|
31
41
|
chainStates,
|
|
32
42
|
chainIds: chainStates->Dict.valuesToArray->Array.map(cs => (cs->ChainState.chainConfig).id),
|
|
33
43
|
isRealtime,
|
|
34
44
|
isCaughtUp: isRealtime,
|
|
35
45
|
targetBufferSize,
|
|
46
|
+
holdRealtime,
|
|
36
47
|
}
|
|
37
48
|
}
|
|
38
49
|
|
|
50
|
+
// The supervisor's go-ahead: every chain in the run has reached the head, so
|
|
51
|
+
// this process may make the transitions it has been holding back.
|
|
52
|
+
let releaseRealtime = (crossChainState: t) => crossChainState.holdRealtime = false
|
|
53
|
+
|
|
54
|
+
let isHoldingRealtime = (crossChainState: t) => crossChainState.holdRealtime
|
|
55
|
+
|
|
56
|
+
// Whether this process has got as far as it can without the run's leave. What a
|
|
57
|
+
// supervisor reads to decide that a split run may go realtime as one.
|
|
58
|
+
//
|
|
59
|
+
// Three ways to have arrived, because a chain can be as far along as it can get
|
|
60
|
+
// in three different states. Its chains have caught up; or it resumed already
|
|
61
|
+
// realtime; or every chain is waiting to enter the reorg threshold, which is as
|
|
62
|
+
// far as one can fetch while the pre-threshold lag holds it at the safe block —
|
|
63
|
+
// entering the threshold is what lifts that lag, so a run held until its chains
|
|
64
|
+
// reached the head would be holding back the transition that gets them there.
|
|
65
|
+
//
|
|
66
|
+
// The process's own conclusion rather than a reading a supervisor reassembles:
|
|
67
|
+
// a chain committed at what was the head and resumed once the head had moved on
|
|
68
|
+
// has arrived, and no live reading of it can say so — which is the same reason
|
|
69
|
+
// `markCaughtUpOnResume` decides before any source request.
|
|
70
|
+
let hasArrivedAtHead = (crossChainState: t) =>
|
|
71
|
+
crossChainState.isCaughtUp ||
|
|
72
|
+
crossChainState.isRealtime || {
|
|
73
|
+
let chainStates = crossChainState.chainStates->Dict.valuesToArray
|
|
74
|
+
chainStates->Utils.Array.notEmpty &&
|
|
75
|
+
chainStates->Array.every(ChainState.isReadyToEnterReorgThreshold)
|
|
76
|
+
}
|
|
77
|
+
|
|
39
78
|
// Resolve a chain's state by id. The id always comes from `chainIds`, which is
|
|
40
79
|
// derived from `chainStates`, so the entry is guaranteed present.
|
|
41
80
|
let getChainState = (crossChainState: t, chainId) =>
|
|
@@ -122,6 +161,16 @@ let createBatch = (
|
|
|
122
161
|
|
|
123
162
|
// Enter the reorg threshold: shrink each chain's buffer by its configured
|
|
124
163
|
// blockLag and flip the flag.
|
|
164
|
+
// Whether every chain this process drives has buffered close enough to the head
|
|
165
|
+
// to enter the threshold together — and, in a split run, whether the rest of the
|
|
166
|
+
// run has too. Chains enter it as one indexer, so one chain still backfilling
|
|
167
|
+
// holds the others back whatever process it runs in.
|
|
168
|
+
let isReadyToEnterReorgThreshold = (crossChainState: t, ~batch) =>
|
|
169
|
+
!crossChainState.holdRealtime &&
|
|
170
|
+
crossChainState.chainStates
|
|
171
|
+
->Dict.valuesToArray
|
|
172
|
+
->Array.every(cs => cs->ChainState.isReadyToEnterReorgThresholdAfterBatch(~batch))
|
|
173
|
+
|
|
125
174
|
let enterReorgThreshold = (crossChainState: t) => {
|
|
126
175
|
Logging.info("Reorg threshold reached")
|
|
127
176
|
|
|
@@ -150,7 +199,10 @@ let applyBatchProgress = (crossChainState: t, ~batch: Batch.t, ~blockTimestampNa
|
|
|
150
199
|
}
|
|
151
200
|
|
|
152
201
|
crossChainState.isCaughtUp =
|
|
153
|
-
crossChainState.isCaughtUp ||
|
|
202
|
+
crossChainState.isCaughtUp ||
|
|
203
|
+
(!crossChainState.holdRealtime &&
|
|
204
|
+
crossChainState->nextItemIsNone &&
|
|
205
|
+
everyChainCaughtUp.contents)
|
|
154
206
|
}
|
|
155
207
|
|
|
156
208
|
// Every chain has buffered up to its head (or endblock) with nothing
|
|
@@ -174,8 +226,13 @@ let isSettledAtHead = (crossChainState: t) => {
|
|
|
174
226
|
}
|
|
175
227
|
|
|
176
228
|
// Enter the FinalizingIndexes phase without a batch, for the resume above.
|
|
229
|
+
// Not while the run holds this process back: the hold keeps the pre-threshold
|
|
230
|
+
// lag in place, and a chain that has fetched to a lagged head it was never
|
|
231
|
+
// going to get past reads as settled without having indexed anything. What a
|
|
232
|
+
// held process may conclude about where it stands, it concludes from what was
|
|
233
|
+
// persisted — see `markCaughtUpOnResume`, which the hold leaves alone.
|
|
177
234
|
let markCaughtUpIfSettled = (crossChainState: t) =>
|
|
178
|
-
if crossChainState->isSettledAtHead {
|
|
235
|
+
if !crossChainState.holdRealtime && crossChainState->isSettledAtHead {
|
|
179
236
|
crossChainState.isCaughtUp = true
|
|
180
237
|
}
|
|
181
238
|
|
|
@@ -20,19 +20,41 @@ function calculateTargetBufferSize() {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function make(chainStates, isRealtime, targetBufferSizeOpt) {
|
|
23
|
+
function make(chainStates, isRealtime, targetBufferSizeOpt, holdRealtimeOpt) {
|
|
24
24
|
let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : (
|
|
25
25
|
Env.targetBufferSize !== undefined ? Env.targetBufferSize : 100000
|
|
26
26
|
);
|
|
27
|
+
let holdRealtime = holdRealtimeOpt !== undefined ? holdRealtimeOpt : false;
|
|
27
28
|
return {
|
|
28
29
|
chainStates: chainStates,
|
|
29
30
|
chainIds: Object.values(chainStates).map(cs => ChainState.chainConfig(cs).id),
|
|
30
31
|
isRealtime: isRealtime,
|
|
31
32
|
isCaughtUp: isRealtime,
|
|
32
|
-
targetBufferSize: targetBufferSize
|
|
33
|
+
targetBufferSize: targetBufferSize,
|
|
34
|
+
holdRealtime: holdRealtime
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
function releaseRealtime(crossChainState) {
|
|
39
|
+
crossChainState.holdRealtime = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isHoldingRealtime(crossChainState) {
|
|
43
|
+
return crossChainState.holdRealtime;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hasArrivedAtHead(crossChainState) {
|
|
47
|
+
if (crossChainState.isCaughtUp || crossChainState.isRealtime) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
let chainStates = Object.values(crossChainState.chainStates);
|
|
51
|
+
if (Utils.$$Array.notEmpty(chainStates)) {
|
|
52
|
+
return chainStates.every(ChainState.isReadyToEnterReorgThreshold);
|
|
53
|
+
} else {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
function chainStates(crossChainState) {
|
|
37
59
|
return crossChainState.chainStates;
|
|
38
60
|
}
|
|
@@ -94,6 +116,14 @@ function createBatch(crossChainState, config, frontier, batchSizeTarget) {
|
|
|
94
116
|
return Batch.make(config.checkpointSequence, frontier, Utils.Dict.mapValues(crossChainState.chainStates, none => ChainState.toChainBeforeBatch(none, crossChainState.isRealtime)), batchSizeTarget, HistoryPolicy.decide(config, shouldSaveHistory(crossChainState)));
|
|
95
117
|
}
|
|
96
118
|
|
|
119
|
+
function isReadyToEnterReorgThreshold(crossChainState, batch) {
|
|
120
|
+
if (crossChainState.holdRealtime) {
|
|
121
|
+
return false;
|
|
122
|
+
} else {
|
|
123
|
+
return Object.values(crossChainState.chainStates).every(cs => ChainState.isReadyToEnterReorgThresholdAfterBatch(cs, batch));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
97
127
|
function enterReorgThreshold(crossChainState) {
|
|
98
128
|
Logging.info("Reorg threshold reached");
|
|
99
129
|
for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
|
|
@@ -111,7 +141,7 @@ function applyBatchProgress(crossChainState, batch, blockTimestampName) {
|
|
|
111
141
|
everyChainCaughtUp = false;
|
|
112
142
|
}
|
|
113
143
|
}
|
|
114
|
-
crossChainState.isCaughtUp = crossChainState.isCaughtUp || nextItemIsNone(crossChainState) && everyChainCaughtUp;
|
|
144
|
+
crossChainState.isCaughtUp = crossChainState.isCaughtUp || !crossChainState.holdRealtime && nextItemIsNone(crossChainState) && everyChainCaughtUp;
|
|
115
145
|
}
|
|
116
146
|
|
|
117
147
|
function isSettledAtHead(crossChainState) {
|
|
@@ -125,7 +155,7 @@ function isSettledAtHead(crossChainState) {
|
|
|
125
155
|
}
|
|
126
156
|
|
|
127
157
|
function markCaughtUpIfSettled(crossChainState) {
|
|
128
|
-
if (isSettledAtHead(crossChainState)) {
|
|
158
|
+
if (!crossChainState.holdRealtime && isSettledAtHead(crossChainState)) {
|
|
129
159
|
crossChainState.isCaughtUp = true;
|
|
130
160
|
return;
|
|
131
161
|
}
|
|
@@ -256,6 +286,10 @@ export {
|
|
|
256
286
|
shouldSaveHistory,
|
|
257
287
|
nextItemIsNone,
|
|
258
288
|
getSafeCheckpointIdByChain,
|
|
289
|
+
releaseRealtime,
|
|
290
|
+
isHoldingRealtime,
|
|
291
|
+
hasArrivedAtHead,
|
|
292
|
+
isReadyToEnterReorgThreshold,
|
|
259
293
|
createBatch,
|
|
260
294
|
enterReorgThreshold,
|
|
261
295
|
applyBatchProgress,
|
package/src/CrossChainState.resi
CHANGED
|
@@ -5,7 +5,12 @@ type t
|
|
|
5
5
|
|
|
6
6
|
let calculateTargetBufferSize: unit => int
|
|
7
7
|
|
|
8
|
-
let make: (
|
|
8
|
+
let make: (
|
|
9
|
+
~chainStates: dict<ChainState.t>,
|
|
10
|
+
~isRealtime: bool,
|
|
11
|
+
~targetBufferSize: int=?,
|
|
12
|
+
~holdRealtime: bool=?,
|
|
13
|
+
) => t
|
|
9
14
|
|
|
10
15
|
// Accessors.
|
|
11
16
|
let chainStates: t => dict<ChainState.t>
|
|
@@ -25,6 +30,10 @@ let getSafeCheckpointIdByChain: (
|
|
|
25
30
|
) => array<(ChainId.t, option<Internal.checkpointId>)>
|
|
26
31
|
|
|
27
32
|
// Cross-chain transitions.
|
|
33
|
+
let releaseRealtime: t => unit
|
|
34
|
+
let isHoldingRealtime: t => bool
|
|
35
|
+
let hasArrivedAtHead: t => bool
|
|
36
|
+
let isReadyToEnterReorgThreshold: (t, ~batch: Batch.t) => bool
|
|
28
37
|
let createBatch: (t, ~config: Config.t, ~frontier: Frontier.t, ~batchSizeTarget: int) => Batch.t
|
|
29
38
|
let enterReorgThreshold: t => unit
|
|
30
39
|
let applyBatchProgress: (t, ~batch: Batch.t, ~blockTimestampName: string) => unit
|
package/src/Env.res
CHANGED
|
@@ -127,6 +127,10 @@ module Db = {
|
|
|
127
127
|
//the SSL modes should be provided as string otherwise as 'require' | 'allow' | 'prefer' | 'verify-full'
|
|
128
128
|
~devFallback=Bool(false),
|
|
129
129
|
)
|
|
130
|
+
// The budget for the whole run, not for one process: a run that splits across
|
|
131
|
+
// workers divides it among them, and each caps its own pool to its share.
|
|
132
|
+
// The default buys a single worker, so a run splits only once the operator
|
|
133
|
+
// raises the budget it may spend.
|
|
130
134
|
let maxConnections = envSafe->EnvSafe.get("ENVIO_PG_MAX_CONNECTIONS", S.int, ~fallback=2)
|
|
131
135
|
}
|
|
132
136
|
|
package/src/IndexerLoop.res
CHANGED
package/src/IndexerLoop.res.mjs
CHANGED
package/src/IndexerState.res
CHANGED
|
@@ -115,6 +115,10 @@ type t = {
|
|
|
115
115
|
// waitForNewBlock waiter is bound to the old, pre-realtime source). A fetch
|
|
116
116
|
// response or waiter carrying an older epoch than this is discarded.
|
|
117
117
|
mutable epoch: int,
|
|
118
|
+
// The loop's one door in from outside it: IndexerLoop owns scheduling and
|
|
119
|
+
// wires this when it starts, so an event the loop can't see for itself can
|
|
120
|
+
// still make it re-evaluate. A no-op before then.
|
|
121
|
+
mutable scheduleProcessing: unit => unit,
|
|
118
122
|
// None off the simulate path.
|
|
119
123
|
simulateDeadInputTracker: option<SimulateDeadInputTracker.t>,
|
|
120
124
|
// --- Metric counters, rendered by Metrics at scrape time. ---
|
|
@@ -141,6 +145,7 @@ let make = (
|
|
|
141
145
|
~chainStates: dict<ChainState.t>,
|
|
142
146
|
~isRealtime: bool,
|
|
143
147
|
~targetBufferSize=CrossChainState.calculateTargetBufferSize(),
|
|
148
|
+
~holdRealtime=false,
|
|
144
149
|
~committedFrontier=Frontier.empty(),
|
|
145
150
|
~isDevelopmentMode=false,
|
|
146
151
|
~shouldUseTui=false,
|
|
@@ -180,11 +185,17 @@ let make = (
|
|
|
180
185
|
chainMetaDirty: false,
|
|
181
186
|
chainMetaThrottler,
|
|
182
187
|
isProcessing: false,
|
|
183
|
-
crossChainState: CrossChainState.make(
|
|
188
|
+
crossChainState: CrossChainState.make(
|
|
189
|
+
~chainStates,
|
|
190
|
+
~isRealtime,
|
|
191
|
+
~targetBufferSize,
|
|
192
|
+
~holdRealtime,
|
|
193
|
+
),
|
|
184
194
|
indexerStartTime: Date.make(),
|
|
185
195
|
indexerStartTimeRef: Performance.now(),
|
|
186
196
|
rollbackState: NoRollback,
|
|
187
197
|
lastPrunedAtMillis: Dict.make(),
|
|
198
|
+
scheduleProcessing: () => (),
|
|
188
199
|
loadManager: LoadManager.make(),
|
|
189
200
|
keepProcessAlive: isDevelopmentMode || shouldUseTui,
|
|
190
201
|
exitAfterFirstEventBlock,
|
|
@@ -227,6 +238,9 @@ let makeFromDbState = (
|
|
|
227
238
|
~exitAfterFirstEventBlock=false,
|
|
228
239
|
~reducedPollingInterval=?,
|
|
229
240
|
~targetBufferSize=CrossChainState.calculateTargetBufferSize(),
|
|
241
|
+
// A process driving part of a split run waits for its supervisor before
|
|
242
|
+
// entering the reorg threshold or switching to realtime.
|
|
243
|
+
~holdRealtime=false,
|
|
230
244
|
~onError,
|
|
231
245
|
~onExit=?,
|
|
232
246
|
) => {
|
|
@@ -274,6 +288,7 @@ let makeFromDbState = (
|
|
|
274
288
|
~chainStates,
|
|
275
289
|
~isRealtime,
|
|
276
290
|
~targetBufferSize,
|
|
291
|
+
~holdRealtime,
|
|
277
292
|
~committedFrontier=initialState.checkpointFrontier,
|
|
278
293
|
~isDevelopmentMode,
|
|
279
294
|
~shouldUseTui,
|
|
@@ -500,9 +515,33 @@ let isFinalizingIndexes = (state: t) =>
|
|
|
500
515
|
state.crossChainState->CrossChainState.isCaughtUp &&
|
|
501
516
|
!(state.crossChainState->CrossChainState.isRealtime)
|
|
502
517
|
|
|
518
|
+
// The FinalizingIndexes phase is the transition a held process waits on: it
|
|
519
|
+
// ends with `ready_at` committed and the indexer realtime.
|
|
520
|
+
let shouldFinalizeIndexes = (state: t) =>
|
|
521
|
+
state->isFinalizingIndexes && !(state.crossChainState->CrossChainState.isHoldingRealtime)
|
|
522
|
+
|
|
503
523
|
let markCaughtUpIfSettled = (state: t) =>
|
|
504
524
|
state.crossChainState->CrossChainState.markCaughtUpIfSettled
|
|
505
525
|
|
|
526
|
+
let isReadyToEnterReorgThreshold = (state: t, ~batch) =>
|
|
527
|
+
state.crossChainState->CrossChainState.isReadyToEnterReorgThreshold(~batch)
|
|
528
|
+
|
|
529
|
+
let bindScheduleProcessing = (state: t, scheduleProcessing) =>
|
|
530
|
+
state.scheduleProcessing = scheduleProcessing
|
|
531
|
+
|
|
532
|
+
// A process still waiting on its supervisor owes the schema the indexes its
|
|
533
|
+
// chains deferred, so reaching every end block doesn't make it done.
|
|
534
|
+
let isHoldingRealtime = (state: t) => state.crossChainState->CrossChainState.isHoldingRealtime
|
|
535
|
+
|
|
536
|
+
let hasArrivedAtHead = (state: t) => state.crossChainState->CrossChainState.hasArrivedAtHead
|
|
537
|
+
|
|
538
|
+
let releaseRealtime = (state: t) => {
|
|
539
|
+
state.crossChainState->CrossChainState.releaseRealtime
|
|
540
|
+
// Every chain is parked at the head with no batch coming, so nothing would
|
|
541
|
+
// notice the hold is gone without a pass through processing.
|
|
542
|
+
state.scheduleProcessing()
|
|
543
|
+
}
|
|
544
|
+
|
|
506
545
|
let markReady = (state: t, ~readyAt) => state.crossChainState->CrossChainState.markReady(~readyAt)
|
|
507
546
|
|
|
508
547
|
let rollbackState = (state: t) => state.rollbackState
|
|
@@ -572,6 +611,7 @@ let toMetrics = (state: t): Metrics.t => {
|
|
|
572
611
|
elapsedSeconds: state.indexerStartTimeRef->Performance.secondsSince,
|
|
573
612
|
targetBufferSize: state.crossChainState->CrossChainState.targetBufferSize,
|
|
574
613
|
isInReorgThreshold: state.crossChainState->CrossChainState.isInReorgThreshold,
|
|
614
|
+
hasArrivedAtHead: state.crossChainState->CrossChainState.hasArrivedAtHead,
|
|
575
615
|
rollbackEnabled: state.config.shouldRollbackOnReorg,
|
|
576
616
|
maxBatchSize: state.config.batchSize,
|
|
577
617
|
preloadSeconds: state.preloadSeconds,
|
package/src/IndexerState.res.mjs
CHANGED
|
@@ -24,8 +24,9 @@ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js
|
|
|
24
24
|
import * as CheckpointSequence from "./db/CheckpointSequence.res.mjs";
|
|
25
25
|
import * as SimulateDeadInputTracker from "./SimulateDeadInputTracker.res.mjs";
|
|
26
26
|
|
|
27
|
-
function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt, committedFrontierOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
|
|
27
|
+
function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt, holdRealtimeOpt, committedFrontierOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
|
|
28
28
|
let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : CrossChainState.calculateTargetBufferSize();
|
|
29
|
+
let holdRealtime = holdRealtimeOpt !== undefined ? holdRealtimeOpt : false;
|
|
29
30
|
let committedFrontier = committedFrontierOpt !== undefined ? committedFrontierOpt : Frontier.empty();
|
|
30
31
|
let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
|
|
31
32
|
let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
|
|
@@ -54,7 +55,7 @@ function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt,
|
|
|
54
55
|
chainMetaDirty: false,
|
|
55
56
|
chainMetaThrottler: chainMetaThrottler,
|
|
56
57
|
isProcessing: false,
|
|
57
|
-
crossChainState: CrossChainState.make(chainStates, isRealtime, targetBufferSize),
|
|
58
|
+
crossChainState: CrossChainState.make(chainStates, isRealtime, targetBufferSize, holdRealtime),
|
|
58
59
|
rollbackState: "NoRollback",
|
|
59
60
|
indexerStartTime: new Date(),
|
|
60
61
|
indexerStartTimeRef: Performance.now(),
|
|
@@ -66,6 +67,7 @@ function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt,
|
|
|
66
67
|
onExit: onExit,
|
|
67
68
|
isStopped: false,
|
|
68
69
|
epoch: 0,
|
|
70
|
+
scheduleProcessing: () => {},
|
|
69
71
|
simulateDeadInputTracker: SimulateDeadInputTracker.makeFromConfig(config),
|
|
70
72
|
preloadSeconds: 0,
|
|
71
73
|
processingSeconds: 0,
|
|
@@ -82,11 +84,12 @@ function make(config, persistence, chainStates, isRealtime, targetBufferSizeOpt,
|
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
function makeFromDbState(config, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, reducedPollingInterval, targetBufferSizeOpt, onError, onExit) {
|
|
87
|
+
function makeFromDbState(config, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, reducedPollingInterval, targetBufferSizeOpt, holdRealtimeOpt, onError, onExit) {
|
|
86
88
|
let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
|
|
87
89
|
let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
|
|
88
90
|
let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
|
|
89
91
|
let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : CrossChainState.calculateTargetBufferSize();
|
|
92
|
+
let holdRealtime = holdRealtimeOpt !== undefined ? holdRealtimeOpt : false;
|
|
90
93
|
let isInReorgThreshold = initialState.cleanRun ? false : initialState.chains.some(resumedChainState => {
|
|
91
94
|
let progressBlockNumber = resumedChainState.progressBlockNumber;
|
|
92
95
|
let sourceBlockNumber = resumedChainState.sourceBlockNumber;
|
|
@@ -104,7 +107,7 @@ function makeFromDbState(config, persistence, initialState, registrationsByChain
|
|
|
104
107
|
let chainConfig = ChainMap.get(config.chainMap, chainId);
|
|
105
108
|
chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, initialState.contractMapping, registrationsByChainId, reducedPollingInterval);
|
|
106
109
|
});
|
|
107
|
-
let state = make(config, persistence, chainStates, isRealtime, targetBufferSize, initialState.checkpointFrontier, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
|
|
110
|
+
let state = make(config, persistence, chainStates, isRealtime, targetBufferSize, holdRealtime, initialState.checkpointFrontier, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
|
|
108
111
|
CrossChainState.markCaughtUpOnResume(state.crossChainState);
|
|
109
112
|
Utils.Dict.forEach(initialState.cache, param => {
|
|
110
113
|
let count = param.count;
|
|
@@ -350,10 +353,39 @@ function isFinalizingIndexes(state) {
|
|
|
350
353
|
}
|
|
351
354
|
}
|
|
352
355
|
|
|
356
|
+
function shouldFinalizeIndexes(state) {
|
|
357
|
+
if (isFinalizingIndexes(state)) {
|
|
358
|
+
return !CrossChainState.isHoldingRealtime(state.crossChainState);
|
|
359
|
+
} else {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
353
364
|
function markCaughtUpIfSettled(state) {
|
|
354
365
|
CrossChainState.markCaughtUpIfSettled(state.crossChainState);
|
|
355
366
|
}
|
|
356
367
|
|
|
368
|
+
function isReadyToEnterReorgThreshold(state, batch) {
|
|
369
|
+
return CrossChainState.isReadyToEnterReorgThreshold(state.crossChainState, batch);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function bindScheduleProcessing(state, scheduleProcessing) {
|
|
373
|
+
state.scheduleProcessing = scheduleProcessing;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function isHoldingRealtime(state) {
|
|
377
|
+
return CrossChainState.isHoldingRealtime(state.crossChainState);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function hasArrivedAtHead(state) {
|
|
381
|
+
return CrossChainState.hasArrivedAtHead(state.crossChainState);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function releaseRealtime(state) {
|
|
385
|
+
CrossChainState.releaseRealtime(state.crossChainState);
|
|
386
|
+
state.scheduleProcessing();
|
|
387
|
+
}
|
|
388
|
+
|
|
357
389
|
function markReady(state, readyAt) {
|
|
358
390
|
CrossChainState.markReady(state.crossChainState, readyAt);
|
|
359
391
|
}
|
|
@@ -447,6 +479,7 @@ function toMetrics(state) {
|
|
|
447
479
|
elapsedSeconds: Performance.secondsSince(state.indexerStartTimeRef),
|
|
448
480
|
targetBufferSize: CrossChainState.targetBufferSize(state.crossChainState),
|
|
449
481
|
isInReorgThreshold: CrossChainState.isInReorgThreshold(state.crossChainState),
|
|
482
|
+
hasArrivedAtHead: CrossChainState.hasArrivedAtHead(state.crossChainState),
|
|
450
483
|
rollbackEnabled: state.config.shouldRollbackOnReorg,
|
|
451
484
|
maxBatchSize: state.config.batchSize,
|
|
452
485
|
preloadSeconds: state.preloadSeconds,
|
|
@@ -844,6 +877,12 @@ export {
|
|
|
844
877
|
isRealtime,
|
|
845
878
|
isFinalizingIndexes,
|
|
846
879
|
markCaughtUpIfSettled,
|
|
880
|
+
isReadyToEnterReorgThreshold,
|
|
881
|
+
shouldFinalizeIndexes,
|
|
882
|
+
bindScheduleProcessing,
|
|
883
|
+
isHoldingRealtime,
|
|
884
|
+
hasArrivedAtHead,
|
|
885
|
+
releaseRealtime,
|
|
847
886
|
markReady,
|
|
848
887
|
rollbackState,
|
|
849
888
|
indexerStartTime,
|
package/src/IndexerState.resi
CHANGED
|
@@ -16,6 +16,7 @@ let make: (
|
|
|
16
16
|
~chainStates: dict<ChainState.t>,
|
|
17
17
|
~isRealtime: bool,
|
|
18
18
|
~targetBufferSize: int=?,
|
|
19
|
+
~holdRealtime: bool=?,
|
|
19
20
|
~committedFrontier: Frontier.t=?,
|
|
20
21
|
~isDevelopmentMode: bool=?,
|
|
21
22
|
~shouldUseTui: bool=?,
|
|
@@ -34,6 +35,7 @@ let makeFromDbState: (
|
|
|
34
35
|
~exitAfterFirstEventBlock: bool=?,
|
|
35
36
|
~reducedPollingInterval: int=?,
|
|
36
37
|
~targetBufferSize: int=?,
|
|
38
|
+
~holdRealtime: bool=?,
|
|
37
39
|
~onError: ErrorHandling.t => unit,
|
|
38
40
|
~onExit: unit => unit=?,
|
|
39
41
|
) => t
|
|
@@ -93,6 +95,14 @@ let shouldSaveHistory: t => dict<bool>
|
|
|
93
95
|
let isRealtime: t => bool
|
|
94
96
|
let isFinalizingIndexes: t => bool
|
|
95
97
|
let markCaughtUpIfSettled: t => unit
|
|
98
|
+
let isReadyToEnterReorgThreshold: (t, ~batch: Batch.t) => bool
|
|
99
|
+
let shouldFinalizeIndexes: t => bool
|
|
100
|
+
// Wires the loop's way back in. IndexerLoop calls this as it starts.
|
|
101
|
+
let bindScheduleProcessing: (t, unit => unit) => unit
|
|
102
|
+
let isHoldingRealtime: t => bool
|
|
103
|
+
let hasArrivedAtHead: t => bool
|
|
104
|
+
// The supervisor's go-ahead for a process driving part of a split run.
|
|
105
|
+
let releaseRealtime: t => unit
|
|
96
106
|
let markReady: (t, ~readyAt: Date.t) => unit
|
|
97
107
|
let rollbackState: t => rollbackState
|
|
98
108
|
let indexerStartTime: t => Date.t
|
package/src/Logging.res
CHANGED
|
@@ -26,6 +26,30 @@ let logLevels = [
|
|
|
26
26
|
|
|
27
27
|
%%private(let logger = ref(None))
|
|
28
28
|
|
|
29
|
+
// Fields every line this process logs carries. Merged into each line rather
|
|
30
|
+
// than bound to a child logger: pino writes a child's bindings and the line's
|
|
31
|
+
// own fields side by side, so a line that names the same key would carry it
|
|
32
|
+
// twice. A fresh object per line, since pino merges the line's fields into
|
|
33
|
+
// whatever this returns.
|
|
34
|
+
%%private(let context: ref<dict<JSON.t>> = ref(Dict.make()))
|
|
35
|
+
%%private(let mixin = () => JSON.Object(context.contents->Dict.copy))
|
|
36
|
+
|
|
37
|
+
// A child logger that binds a field the process already carries would have pino
|
|
38
|
+
// write it twice: a child's bindings and the context are concatenated into the
|
|
39
|
+
// line, not merged. The context is the one place a line names it, which it can
|
|
40
|
+
// be because a process only takes one when its every line is about that chain.
|
|
41
|
+
%%private(
|
|
42
|
+
let withoutContext = (params: 'a) =>
|
|
43
|
+
switch context.contents->Dict.keysToArray {
|
|
44
|
+
| [] => params
|
|
45
|
+
| keys => {
|
|
46
|
+
let narrowed = params->(Utils.magic: 'a => dict<JSON.t>)->Dict.copy
|
|
47
|
+
keys->Array.forEach(key => narrowed->Dict.delete(key))
|
|
48
|
+
narrowed->(Utils.magic: dict<JSON.t> => 'a)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
29
53
|
let makeLogger = (~logStrategy, ~logFilePath, ~defaultFileLogLevel, ~userLogLevel) => {
|
|
30
54
|
// Currently unused - useful if using multiple transports.
|
|
31
55
|
// let pinoRaw = {"target": "pino/file", "level": Config.userLogLevel}
|
|
@@ -56,17 +80,19 @@ let makeLogger = (~logStrategy, ~logFilePath, ~defaultFileLogLevel, ~userLogLeve
|
|
|
56
80
|
...Pino.ECS.make(),
|
|
57
81
|
customLevels: logLevels,
|
|
58
82
|
base,
|
|
83
|
+
mixin,
|
|
59
84
|
},
|
|
60
85
|
Transport.make(pinoFile),
|
|
61
86
|
)
|
|
62
87
|
| EcsConsoleMultistream =>
|
|
63
|
-
makeMultiStreamLogger(~logFile=None, ~options=Some({...Pino.ECS.make(), base}))
|
|
88
|
+
makeMultiStreamLogger(~logFile=None, ~options=Some({...Pino.ECS.make(), base, mixin}))
|
|
64
89
|
| EcsConsole =>
|
|
65
90
|
make({
|
|
66
91
|
...Pino.ECS.make(),
|
|
67
92
|
level: userLogLevel,
|
|
68
93
|
customLevels: logLevels,
|
|
69
94
|
base,
|
|
95
|
+
mixin,
|
|
70
96
|
})
|
|
71
97
|
| FileOnly =>
|
|
72
98
|
makeWithOptionsAndTransport(
|
|
@@ -74,12 +100,13 @@ let makeLogger = (~logStrategy, ~logFilePath, ~defaultFileLogLevel, ~userLogLeve
|
|
|
74
100
|
customLevels: logLevels,
|
|
75
101
|
level: defaultFileLogLevel,
|
|
76
102
|
base,
|
|
103
|
+
mixin,
|
|
77
104
|
},
|
|
78
105
|
Transport.make(pinoFile),
|
|
79
106
|
)
|
|
80
|
-
| ConsoleRaw => makeMultiStreamLogger(~logFile=None, ~options=Some({base
|
|
81
|
-
| ConsolePretty => makeMultiStreamLogger(~logFile=None, ~options=Some({base
|
|
82
|
-
| Both => makeMultiStreamLogger(~logFile=Some(logFilePath), ~options=Some({base
|
|
107
|
+
| ConsoleRaw => makeMultiStreamLogger(~logFile=None, ~options=Some({base, mixin}))
|
|
108
|
+
| ConsolePretty => makeMultiStreamLogger(~logFile=None, ~options=Some({base, mixin}))
|
|
109
|
+
| Both => makeMultiStreamLogger(~logFile=Some(logFilePath), ~options=Some({base, mixin}))
|
|
83
110
|
}
|
|
84
111
|
}
|
|
85
112
|
|
|
@@ -149,10 +176,15 @@ let childFatal = (logger, params: 'a) => {
|
|
|
149
176
|
}
|
|
150
177
|
|
|
151
178
|
let createChild = (~params: 'a) => {
|
|
152
|
-
getLogger()->child(params->createChildParams)
|
|
179
|
+
getLogger()->child(params->withoutContext->createChildParams)
|
|
153
180
|
}
|
|
181
|
+
|
|
182
|
+
// What belongs on every line is the run's to decide; the logger only carries
|
|
183
|
+
// what it is handed. A line that names one of these fields itself wins.
|
|
184
|
+
let setContext = (fields: dict<JSON.t>) => context := fields
|
|
185
|
+
|
|
154
186
|
let createChildFrom = (~logger: t, ~params: 'a) => {
|
|
155
|
-
logger->child(params->createChildParams)
|
|
187
|
+
logger->child(params->withoutContext->createChildParams)
|
|
156
188
|
}
|
|
157
189
|
|
|
158
190
|
@inline
|
package/src/Logging.res.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as Pino from "./bindings/Pino.res.mjs";
|
|
4
4
|
import * as Pino$1 from "pino";
|
|
5
5
|
import * as Utils from "./Utils.res.mjs";
|
|
6
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
6
7
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
8
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
8
9
|
|
|
@@ -53,6 +54,24 @@ let logger = {
|
|
|
53
54
|
contents: undefined
|
|
54
55
|
};
|
|
55
56
|
|
|
57
|
+
let context = {
|
|
58
|
+
contents: {}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function mixin() {
|
|
62
|
+
return Object.assign({}, context.contents);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function withoutContext(params) {
|
|
66
|
+
let keys = Object.keys(context.contents);
|
|
67
|
+
if (keys.length === 0) {
|
|
68
|
+
return params;
|
|
69
|
+
}
|
|
70
|
+
let narrowed = Object.assign({}, params);
|
|
71
|
+
keys.forEach(key => Stdlib_Dict.$$delete(narrowed, key));
|
|
72
|
+
return narrowed;
|
|
73
|
+
}
|
|
74
|
+
|
|
56
75
|
function makeLogger(logStrategy, logFilePath, defaultFileLogLevel, userLogLevel) {
|
|
57
76
|
let pinoFile_target = "pino/file";
|
|
58
77
|
let pinoFile_options = Primitive_option.some(Pino.Transport.makeTransportOptions({
|
|
@@ -71,17 +90,18 @@ function makeLogger(logStrategy, logFilePath, defaultFileLogLevel, userLogLevel)
|
|
|
71
90
|
switch (logStrategy) {
|
|
72
91
|
case "ecs-file" :
|
|
73
92
|
let newrecord = {...Pino.ECS.make()};
|
|
74
|
-
return Pino$1.pino((newrecord.base = base, newrecord.customLevels = logLevels, newrecord), Pino$1.transport(pinoFile));
|
|
93
|
+
return Pino$1.pino((newrecord.base = base, newrecord.mixin = mixin, newrecord.customLevels = logLevels, newrecord), Pino$1.transport(pinoFile));
|
|
75
94
|
case "ecs-console" :
|
|
76
95
|
let newrecord$1 = {...Pino.ECS.make()};
|
|
77
|
-
return Pino$1.pino((newrecord$1.base = base, newrecord$1.customLevels = logLevels, newrecord$1.level = userLogLevel, newrecord$1));
|
|
96
|
+
return Pino$1.pino((newrecord$1.base = base, newrecord$1.mixin = mixin, newrecord$1.customLevels = logLevels, newrecord$1.level = userLogLevel, newrecord$1));
|
|
78
97
|
case "ecs-console-multistream" :
|
|
79
98
|
let newrecord$2 = {...Pino.ECS.make()};
|
|
80
|
-
return makeMultiStreamLogger(undefined, (newrecord$2.base = base, newrecord$2));
|
|
99
|
+
return makeMultiStreamLogger(undefined, (newrecord$2.base = base, newrecord$2.mixin = mixin, newrecord$2));
|
|
81
100
|
case "file-only" :
|
|
82
101
|
return Pino$1.pino({
|
|
83
102
|
level: defaultFileLogLevel,
|
|
84
103
|
customLevels: logLevels,
|
|
104
|
+
mixin: mixin,
|
|
85
105
|
base: base
|
|
86
106
|
}, Pino$1.transport(pinoFile));
|
|
87
107
|
case "console-raw" :
|
|
@@ -89,10 +109,12 @@ function makeLogger(logStrategy, logFilePath, defaultFileLogLevel, userLogLevel)
|
|
|
89
109
|
break;
|
|
90
110
|
case "both-prettyconsole" :
|
|
91
111
|
return makeMultiStreamLogger(logFilePath, {
|
|
112
|
+
mixin: mixin,
|
|
92
113
|
base: base
|
|
93
114
|
});
|
|
94
115
|
}
|
|
95
116
|
return makeMultiStreamLogger(undefined, {
|
|
117
|
+
mixin: mixin,
|
|
96
118
|
base: base
|
|
97
119
|
});
|
|
98
120
|
}
|
|
@@ -171,11 +193,15 @@ function childFatal(logger, params) {
|
|
|
171
193
|
}
|
|
172
194
|
|
|
173
195
|
function createChild(params) {
|
|
174
|
-
return getLogger().child(Pino.createChildParams(params));
|
|
196
|
+
return getLogger().child(Pino.createChildParams(withoutContext(params)));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function setContext(fields) {
|
|
200
|
+
context.contents = fields;
|
|
175
201
|
}
|
|
176
202
|
|
|
177
203
|
function createChildFrom(logger, params) {
|
|
178
|
-
return logger.child(Pino.createChildParams(params));
|
|
204
|
+
return logger.child(Pino.createChildParams(withoutContext(params)));
|
|
179
205
|
}
|
|
180
206
|
|
|
181
207
|
function logAtLevel(logger, level, message, params) {
|
|
@@ -246,6 +272,7 @@ export {
|
|
|
246
272
|
childErrorWithExn,
|
|
247
273
|
childFatal,
|
|
248
274
|
createChild,
|
|
275
|
+
setContext,
|
|
249
276
|
createChildFrom,
|
|
250
277
|
logAtLevel,
|
|
251
278
|
noopLogger,
|