envio 2.21.1 → 2.21.3-rc
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 +5 -5
- package/src/FetchState.res +3 -3
- package/src/Prometheus.res +32 -0
- package/src/ReorgDetection.res +104 -56
- package/src/sources/SourceManager.res +26 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "v2.21.
|
|
3
|
+
"version": "v2.21.3-rc",
|
|
4
4
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
5
5
|
"bin": "./bin.js",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://envio.dev",
|
|
27
27
|
"optionalDependencies": {
|
|
28
|
-
"envio-linux-x64": "v2.21.
|
|
29
|
-
"envio-linux-arm64": "v2.21.
|
|
30
|
-
"envio-darwin-x64": "v2.21.
|
|
31
|
-
"envio-darwin-arm64": "v2.21.
|
|
28
|
+
"envio-linux-x64": "v2.21.3-rc",
|
|
29
|
+
"envio-linux-arm64": "v2.21.3-rc",
|
|
30
|
+
"envio-darwin-x64": "v2.21.3-rc",
|
|
31
|
+
"envio-darwin-arm64": "v2.21.3-rc"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@envio-dev/hypersync-client": "0.6.5",
|
package/src/FetchState.res
CHANGED
|
@@ -940,14 +940,14 @@ let queueItemBlockNumber = (queueItem: queueItem) => {
|
|
|
940
940
|
let queueItemIsInReorgThreshold = (
|
|
941
941
|
queueItem: queueItem,
|
|
942
942
|
~currentBlockHeight,
|
|
943
|
-
~
|
|
943
|
+
~highestBlockBelowThreshold,
|
|
944
944
|
) => {
|
|
945
945
|
if currentBlockHeight === 0 {
|
|
946
946
|
false
|
|
947
947
|
} else {
|
|
948
948
|
switch queueItem {
|
|
949
|
-
| Item(_) => queueItem->queueItemBlockNumber >
|
|
950
|
-
| NoItem(_) => queueItem->queueItemBlockNumber >
|
|
949
|
+
| Item(_) => queueItem->queueItemBlockNumber > highestBlockBelowThreshold
|
|
950
|
+
| NoItem(_) => queueItem->queueItemBlockNumber > highestBlockBelowThreshold
|
|
951
951
|
}
|
|
952
952
|
}
|
|
953
953
|
}
|
package/src/Prometheus.res
CHANGED
|
@@ -83,6 +83,7 @@ module MakeSafePromMetric = (
|
|
|
83
83
|
let handleInt: (t<'a>, ~labels: 'a, ~value: int) => unit
|
|
84
84
|
let handleFloat: (t<'a>, ~labels: 'a, ~value: float) => unit
|
|
85
85
|
let increment: (t<'a>, ~labels: 'a) => unit
|
|
86
|
+
let incrementMany: (t<'a>, ~labels: 'a, ~value: int) => unit
|
|
86
87
|
} => {
|
|
87
88
|
type t<'a> = {metric: M.t, labelSchema: S.t<'a>}
|
|
88
89
|
|
|
@@ -121,6 +122,13 @@ module MakeSafePromMetric = (
|
|
|
121
122
|
->M.labels(labels->S.reverseConvertToJsonOrThrow(labelSchema))
|
|
122
123
|
->Obj.magic
|
|
123
124
|
)["inc"]()
|
|
125
|
+
|
|
126
|
+
let incrementMany = ({metric, labelSchema}: t<'a>, ~labels: 'a, ~value) =>
|
|
127
|
+
(
|
|
128
|
+
metric
|
|
129
|
+
->M.labels(labels->S.reverseConvertToJsonOrThrow(labelSchema))
|
|
130
|
+
->Obj.magic
|
|
131
|
+
)["inc"](value)
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
module SafeCounter = MakeSafePromMetric({
|
|
@@ -356,6 +364,30 @@ module IndexingPartitions = {
|
|
|
356
364
|
}
|
|
357
365
|
}
|
|
358
366
|
|
|
367
|
+
module IndexingIdleTime = {
|
|
368
|
+
let counter = SafeCounter.makeOrThrow(
|
|
369
|
+
~name="envio_indexing_idle_time",
|
|
370
|
+
~help="The number of milliseconds the indexer source syncing has been idle. A high value may indicate the source sync is a bottleneck.",
|
|
371
|
+
~labelSchema=chainIdLabelsSchema,
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
module IndexingSourceWaitingTime = {
|
|
376
|
+
let counter = SafeCounter.makeOrThrow(
|
|
377
|
+
~name="envio_indexing_source_waiting_time",
|
|
378
|
+
~help="The number of milliseconds the indexer has been waiting for new blocks.",
|
|
379
|
+
~labelSchema=chainIdLabelsSchema,
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
module IndexingQueryTime = {
|
|
384
|
+
let counter = SafeCounter.makeOrThrow(
|
|
385
|
+
~name="envio_indexing_query_time",
|
|
386
|
+
~help="The number of milliseconds spent performing queries to the chain data-source.",
|
|
387
|
+
~labelSchema=chainIdLabelsSchema,
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
359
391
|
module IndexingBufferSize = {
|
|
360
392
|
let gauge = SafeGauge.makeOrThrow(
|
|
361
393
|
~name="envio_indexing_buffer_size",
|
package/src/ReorgDetection.res
CHANGED
|
@@ -15,8 +15,8 @@ type blockData = {
|
|
|
15
15
|
external generalizeBlockDataWithTimestamp: blockDataWithTimestamp => blockData = "%identity"
|
|
16
16
|
|
|
17
17
|
type reorgGuard = {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
rangeLastBlock: blockData,
|
|
19
|
+
prevRangeLastBlock: option<blockData>,
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
type reorgDetected = {
|
|
@@ -36,22 +36,31 @@ let reorgDetectedToLogParams = (reorgDetected: reorgDetected, ~shouldRollbackOnR
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
type reorgResult = NoReorg | ReorgDetected(reorgDetected)
|
|
40
|
+
type validBlockError = NotFound | AlreadyReorgedHashes
|
|
41
|
+
type validBlockResult = result<blockDataWithTimestamp, validBlockError>
|
|
42
|
+
|
|
39
43
|
module LastBlockScannedHashes: {
|
|
40
44
|
type t
|
|
41
45
|
/**Instantiat t with existing data*/
|
|
42
|
-
let makeWithData: (
|
|
46
|
+
let makeWithData: (
|
|
47
|
+
array<blockData>,
|
|
48
|
+
~confirmedBlockThreshold: int,
|
|
49
|
+
~detectedReorgBlock: blockData=?,
|
|
50
|
+
) => t
|
|
43
51
|
|
|
44
52
|
/**Instantiat empty t with no block data*/
|
|
45
53
|
let empty: (~confirmedBlockThreshold: int) => t
|
|
46
54
|
|
|
47
|
-
/** Registers a new reorg guard, prunes
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
/** Registers a new reorg guard, prunes unneeded data, and returns the updated state.
|
|
56
|
+
* Resets internal state if shouldRollbackOnReorg is false (detect-only mode)
|
|
57
|
+
*/
|
|
50
58
|
let registerReorgGuard: (
|
|
51
59
|
t,
|
|
52
60
|
~reorgGuard: reorgGuard,
|
|
53
61
|
~currentBlockHeight: int,
|
|
54
|
-
|
|
62
|
+
~shouldRollbackOnReorg: bool,
|
|
63
|
+
) => (t, reorgResult)
|
|
55
64
|
|
|
56
65
|
/**
|
|
57
66
|
Returns the latest block data which matches block number and hashes in the provided array
|
|
@@ -61,7 +70,7 @@ module LastBlockScannedHashes: {
|
|
|
61
70
|
t,
|
|
62
71
|
~blockNumbersAndHashes: array<blockDataWithTimestamp>,
|
|
63
72
|
~currentBlockHeight: int,
|
|
64
|
-
) =>
|
|
73
|
+
) => validBlockResult
|
|
65
74
|
|
|
66
75
|
let getThresholdBlockNumbers: (t, ~currentBlockHeight: int) => array<int>
|
|
67
76
|
|
|
@@ -76,9 +85,14 @@ module LastBlockScannedHashes: {
|
|
|
76
85
|
// A hash map of recent blockdata by block number to make comparison checks
|
|
77
86
|
// for reorgs.
|
|
78
87
|
dataByBlockNumber: dict<blockData>,
|
|
88
|
+
// The latest block which detected a reorg
|
|
89
|
+
// and should never be valid.
|
|
90
|
+
// We keep track of this to avoid responses
|
|
91
|
+
// with the stale data from other data-source instances.
|
|
92
|
+
detectedReorgBlock: option<blockData>,
|
|
79
93
|
}
|
|
80
94
|
|
|
81
|
-
let makeWithData = (blocks, ~confirmedBlockThreshold) => {
|
|
95
|
+
let makeWithData = (blocks, ~confirmedBlockThreshold, ~detectedReorgBlock=?) => {
|
|
82
96
|
let dataByBlockNumber = Js.Dict.empty()
|
|
83
97
|
|
|
84
98
|
blocks->Belt.Array.forEach(block => {
|
|
@@ -88,12 +102,14 @@ module LastBlockScannedHashes: {
|
|
|
88
102
|
{
|
|
89
103
|
confirmedBlockThreshold,
|
|
90
104
|
dataByBlockNumber,
|
|
105
|
+
detectedReorgBlock,
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
//Instantiates empty LastBlockHashes
|
|
94
109
|
let empty = (~confirmedBlockThreshold) => {
|
|
95
110
|
confirmedBlockThreshold,
|
|
96
111
|
dataByBlockNumber: Js.Dict.empty(),
|
|
112
|
+
detectedReorgBlock: None,
|
|
97
113
|
}
|
|
98
114
|
|
|
99
115
|
let getDataByBlockNumberCopyInThreshold = (
|
|
@@ -122,33 +138,33 @@ module LastBlockScannedHashes: {
|
|
|
122
138
|
{confirmedBlockThreshold} as self: t,
|
|
123
139
|
~reorgGuard: reorgGuard,
|
|
124
140
|
~currentBlockHeight,
|
|
141
|
+
~shouldRollbackOnReorg,
|
|
125
142
|
) => {
|
|
126
143
|
let dataByBlockNumberCopyInThreshold =
|
|
127
144
|
self->getDataByBlockNumberCopyInThreshold(~currentBlockHeight)
|
|
128
145
|
|
|
129
|
-
let {
|
|
146
|
+
let {rangeLastBlock, prevRangeLastBlock} = reorgGuard
|
|
130
147
|
|
|
131
148
|
let maybeReorgDetected = switch dataByBlockNumberCopyInThreshold->Utils.Dict.dangerouslyGetNonOption(
|
|
132
|
-
|
|
149
|
+
rangeLastBlock.blockNumber->Int.toString,
|
|
133
150
|
) {
|
|
134
|
-
| Some(scannedBlock) if scannedBlock.blockHash !==
|
|
151
|
+
| Some(scannedBlock) if scannedBlock.blockHash !== rangeLastBlock.blockHash =>
|
|
135
152
|
Some({
|
|
136
|
-
receivedBlock:
|
|
153
|
+
receivedBlock: rangeLastBlock,
|
|
137
154
|
scannedBlock,
|
|
138
155
|
})
|
|
139
156
|
| _ =>
|
|
140
|
-
switch
|
|
157
|
+
switch prevRangeLastBlock {
|
|
141
158
|
//If parentHash is None, then it's the genesis block (no reorg)
|
|
142
159
|
//Need to check that parentHash matches because of the dynamic contracts
|
|
143
160
|
| None => None
|
|
144
|
-
| Some(
|
|
161
|
+
| Some(prevRangeLastBlock) =>
|
|
145
162
|
switch dataByBlockNumberCopyInThreshold->Utils.Dict.dangerouslyGetNonOption(
|
|
146
|
-
|
|
163
|
+
prevRangeLastBlock.blockNumber->Int.toString,
|
|
147
164
|
) {
|
|
148
|
-
| Some(scannedBlock)
|
|
149
|
-
if scannedBlock.blockHash !== firstBlockParentNumberAndHash.blockHash =>
|
|
165
|
+
| Some(scannedBlock) if scannedBlock.blockHash !== prevRangeLastBlock.blockHash =>
|
|
150
166
|
Some({
|
|
151
|
-
receivedBlock:
|
|
167
|
+
receivedBlock: prevRangeLastBlock,
|
|
152
168
|
scannedBlock,
|
|
153
169
|
})
|
|
154
170
|
| _ => None
|
|
@@ -157,25 +173,37 @@ module LastBlockScannedHashes: {
|
|
|
157
173
|
}
|
|
158
174
|
|
|
159
175
|
switch maybeReorgDetected {
|
|
160
|
-
| Some(reorgDetected) =>
|
|
176
|
+
| Some(reorgDetected) => (
|
|
177
|
+
shouldRollbackOnReorg
|
|
178
|
+
? {
|
|
179
|
+
...self,
|
|
180
|
+
detectedReorgBlock: Some(reorgDetected.scannedBlock),
|
|
181
|
+
}
|
|
182
|
+
: empty(~confirmedBlockThreshold),
|
|
183
|
+
ReorgDetected(reorgDetected),
|
|
184
|
+
)
|
|
161
185
|
| None => {
|
|
162
186
|
dataByBlockNumberCopyInThreshold->Js.Dict.set(
|
|
163
|
-
|
|
164
|
-
|
|
187
|
+
rangeLastBlock.blockNumber->Int.toString,
|
|
188
|
+
rangeLastBlock,
|
|
165
189
|
)
|
|
166
|
-
switch
|
|
190
|
+
switch prevRangeLastBlock {
|
|
167
191
|
| None => ()
|
|
168
|
-
| Some(
|
|
192
|
+
| Some(prevRangeLastBlock) =>
|
|
169
193
|
dataByBlockNumberCopyInThreshold->Js.Dict.set(
|
|
170
|
-
|
|
171
|
-
|
|
194
|
+
prevRangeLastBlock.blockNumber->Int.toString,
|
|
195
|
+
prevRangeLastBlock,
|
|
172
196
|
)
|
|
173
197
|
}
|
|
174
198
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
199
|
+
(
|
|
200
|
+
{
|
|
201
|
+
confirmedBlockThreshold,
|
|
202
|
+
dataByBlockNumber: dataByBlockNumberCopyInThreshold,
|
|
203
|
+
detectedReorgBlock: None,
|
|
204
|
+
},
|
|
205
|
+
NoReorg,
|
|
206
|
+
)
|
|
179
207
|
}
|
|
180
208
|
}
|
|
181
209
|
}
|
|
@@ -186,39 +214,58 @@ module LastBlockScannedHashes: {
|
|
|
186
214
|
~currentBlockHeight,
|
|
187
215
|
) => {
|
|
188
216
|
let verifiedDataByBlockNumber = Js.Dict.empty()
|
|
189
|
-
blockNumbersAndHashes->Array.
|
|
217
|
+
for idx in 0 to blockNumbersAndHashes->Array.length - 1 {
|
|
218
|
+
let blockData = blockNumbersAndHashes->Array.getUnsafe(idx)
|
|
190
219
|
verifiedDataByBlockNumber->Js.Dict.set(blockData.blockNumber->Int.toString, blockData)
|
|
191
|
-
}
|
|
220
|
+
}
|
|
192
221
|
|
|
193
|
-
let
|
|
194
|
-
|
|
195
|
-
|
|
222
|
+
let isAlreadyReorgedResponse = switch self.detectedReorgBlock {
|
|
223
|
+
| Some(detectedReorgBlock) =>
|
|
224
|
+
switch verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(
|
|
225
|
+
detectedReorgBlock.blockNumber->Int.toString,
|
|
226
|
+
) {
|
|
227
|
+
| Some(verifiedBlockData) => verifiedBlockData.blockHash === detectedReorgBlock.blockHash
|
|
228
|
+
| None => false
|
|
229
|
+
}
|
|
230
|
+
| None => false
|
|
231
|
+
}
|
|
196
232
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
->
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
233
|
+
if isAlreadyReorgedResponse {
|
|
234
|
+
Error(AlreadyReorgedHashes)
|
|
235
|
+
} else {
|
|
236
|
+
let dataByBlockNumber = self->getDataByBlockNumberCopyInThreshold(~currentBlockHeight)
|
|
237
|
+
// Js engine automatically orders numeric object keys
|
|
238
|
+
let ascBlockNumberKeys = dataByBlockNumber->Js.Dict.keys
|
|
239
|
+
|
|
240
|
+
let getPrevScannedBlock = idx =>
|
|
241
|
+
switch ascBlockNumberKeys
|
|
242
|
+
->Belt.Array.get(idx - 1)
|
|
243
|
+
->Option.flatMap(key => {
|
|
244
|
+
// We should already validate that the block number is verified at the point
|
|
245
|
+
verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(key)
|
|
246
|
+
}) {
|
|
247
|
+
| Some(data) => Ok(data)
|
|
248
|
+
| None => Error(NotFound)
|
|
249
|
+
}
|
|
204
250
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
251
|
+
let rec loop = idx => {
|
|
252
|
+
switch ascBlockNumberKeys->Belt.Array.get(idx) {
|
|
253
|
+
| Some(blockNumberKey) =>
|
|
254
|
+
let scannedBlock = dataByBlockNumber->Js.Dict.unsafeGet(blockNumberKey)
|
|
255
|
+
switch verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(blockNumberKey) {
|
|
256
|
+
| None =>
|
|
257
|
+
Js.Exn.raiseError(
|
|
258
|
+
`Unexpected case. Couldn't find verified hash for block number ${blockNumberKey}`,
|
|
259
|
+
)
|
|
260
|
+
| Some(verifiedBlockData) if verifiedBlockData.blockHash === scannedBlock.blockHash =>
|
|
261
|
+
loop(idx + 1)
|
|
262
|
+
| Some(_) => getPrevScannedBlock(idx)
|
|
263
|
+
}
|
|
264
|
+
| None => getPrevScannedBlock(idx)
|
|
217
265
|
}
|
|
218
|
-
| None => getPrevScannedBlock(idx)
|
|
219
266
|
}
|
|
267
|
+
loop(0)
|
|
220
268
|
}
|
|
221
|
-
loop(0)
|
|
222
269
|
}
|
|
223
270
|
|
|
224
271
|
/**
|
|
@@ -254,6 +301,7 @@ module LastBlockScannedHashes: {
|
|
|
254
301
|
{
|
|
255
302
|
confirmedBlockThreshold,
|
|
256
303
|
dataByBlockNumber: newDataByBlockNumber,
|
|
304
|
+
detectedReorgBlock: None,
|
|
257
305
|
}
|
|
258
306
|
}
|
|
259
307
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
open Belt
|
|
2
2
|
|
|
3
|
+
type sourceManagerStatus = Idle | WaitingForNewBlock | Querieng
|
|
4
|
+
|
|
3
5
|
// Ideally the ChainFetcher name suits this better
|
|
4
6
|
// But currently the ChainFetcher module is immutable
|
|
5
7
|
// and handles both processing and fetching.
|
|
@@ -7,6 +9,8 @@ open Belt
|
|
|
7
9
|
// with a mutable state for easier reasoning and testing.
|
|
8
10
|
type t = {
|
|
9
11
|
sources: Utils.Set.t<Source.t>,
|
|
12
|
+
mutable statusStart: Hrtime.timeRef,
|
|
13
|
+
mutable status: sourceManagerStatus,
|
|
10
14
|
maxPartitionConcurrency: int,
|
|
11
15
|
newBlockFallbackStallTimeout: int,
|
|
12
16
|
stalledPollingInterval: int,
|
|
@@ -66,7 +70,23 @@ let make = (
|
|
|
66
70
|
newBlockFallbackStallTimeout,
|
|
67
71
|
stalledPollingInterval,
|
|
68
72
|
getHeightRetryInterval,
|
|
73
|
+
statusStart: Hrtime.makeTimer(),
|
|
74
|
+
status: Idle,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let trackNewStatus = (sourceManager: t, ~newStatus) => {
|
|
79
|
+
let promCounter = switch newStatus {
|
|
80
|
+
| Idle => Prometheus.IndexingIdleTime.counter
|
|
81
|
+
| WaitingForNewBlock => Prometheus.IndexingSourceWaitingTime.counter
|
|
82
|
+
| Querieng => Prometheus.IndexingQueryTime.counter
|
|
69
83
|
}
|
|
84
|
+
promCounter->Prometheus.SafeCounter.incrementMany(
|
|
85
|
+
~labels=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
|
|
86
|
+
~value=sourceManager.statusStart->Hrtime.timeSince->Hrtime.toMillis->Hrtime.intFromMillis,
|
|
87
|
+
)
|
|
88
|
+
sourceManager.statusStart = Hrtime.makeTimer()
|
|
89
|
+
sourceManager.status = newStatus
|
|
70
90
|
}
|
|
71
91
|
|
|
72
92
|
let fetchNext = async (
|
|
@@ -96,10 +116,12 @@ let fetchNext = async (
|
|
|
96
116
|
| Some(waitingStateId) if waitingStateId >= stateId => ()
|
|
97
117
|
| Some(_) // Case for the prev state before a rollback
|
|
98
118
|
| None =>
|
|
119
|
+
sourceManager->trackNewStatus(~newStatus=WaitingForNewBlock)
|
|
99
120
|
sourceManager.waitingForNewBlockStateId = Some(stateId)
|
|
100
121
|
let currentBlockHeight = await waitForNewBlock(~currentBlockHeight)
|
|
101
122
|
switch sourceManager.waitingForNewBlockStateId {
|
|
102
123
|
| Some(waitingStateId) if waitingStateId === stateId => {
|
|
124
|
+
sourceManager->trackNewStatus(~newStatus=Idle)
|
|
103
125
|
sourceManager.waitingForNewBlockStateId = None
|
|
104
126
|
onNewBlock(~currentBlockHeight)
|
|
105
127
|
}
|
|
@@ -115,6 +137,7 @@ let fetchNext = async (
|
|
|
115
137
|
~concurrency=sourceManager.fetchingPartitionsCount,
|
|
116
138
|
~chainId=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
|
|
117
139
|
)
|
|
140
|
+
sourceManager->trackNewStatus(~newStatus=Querieng)
|
|
118
141
|
let _ =
|
|
119
142
|
await queries
|
|
120
143
|
->Array.map(q => {
|
|
@@ -125,6 +148,9 @@ let fetchNext = async (
|
|
|
125
148
|
~concurrency=sourceManager.fetchingPartitionsCount,
|
|
126
149
|
~chainId=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
|
|
127
150
|
)
|
|
151
|
+
if sourceManager.fetchingPartitionsCount === 0 {
|
|
152
|
+
sourceManager->trackNewStatus(~newStatus=Idle)
|
|
153
|
+
}
|
|
128
154
|
})
|
|
129
155
|
promise
|
|
130
156
|
})
|