envio 3.0.0-alpha.2 → 3.0.0-alpha.4
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/README.md +2 -2
- package/evm.schema.json +44 -34
- package/fuel.schema.json +32 -21
- package/index.d.ts +4 -1
- package/index.js +1 -0
- package/package.json +7 -6
- package/src/Batch.res.mjs +1 -1
- package/src/Benchmark.res +394 -0
- package/src/Benchmark.res.mjs +398 -0
- package/src/ChainFetcher.res +459 -0
- package/src/ChainFetcher.res.mjs +281 -0
- package/src/ChainManager.res +179 -0
- package/src/ChainManager.res.mjs +139 -0
- package/src/Config.res +15 -1
- package/src/Config.res.mjs +28 -5
- package/src/Ecosystem.res +9 -124
- package/src/Ecosystem.res.mjs +19 -160
- package/src/Env.res +0 -1
- package/src/Env.res.mjs +0 -3
- package/src/Envio.gen.ts +9 -1
- package/src/Envio.res +12 -9
- package/src/EventProcessing.res +476 -0
- package/src/EventProcessing.res.mjs +341 -0
- package/src/FetchState.res +54 -29
- package/src/FetchState.res.mjs +62 -35
- package/src/GlobalState.res +1169 -0
- package/src/GlobalState.res.mjs +1196 -0
- package/src/Internal.res +43 -1
- package/src/LoadLayer.res +444 -0
- package/src/LoadLayer.res.mjs +296 -0
- package/src/LoadLayer.resi +32 -0
- package/src/Prometheus.res +8 -8
- package/src/Prometheus.res.mjs +10 -10
- package/src/ReorgDetection.res +6 -10
- package/src/ReorgDetection.res.mjs +6 -6
- package/src/Types.ts +1 -1
- package/src/UserContext.res +356 -0
- package/src/UserContext.res.mjs +238 -0
- package/src/Utils.res +15 -0
- package/src/Utils.res.mjs +18 -0
- package/src/bindings/ClickHouse.res +31 -1
- package/src/bindings/ClickHouse.res.mjs +27 -1
- package/src/bindings/DateFns.res +71 -0
- package/src/bindings/DateFns.res.mjs +22 -0
- package/src/bindings/Ethers.res +27 -63
- package/src/bindings/Ethers.res.mjs +18 -65
- package/src/sources/Evm.res +87 -0
- package/src/sources/Evm.res.mjs +105 -0
- package/src/sources/EvmChain.res +95 -0
- package/src/sources/EvmChain.res.mjs +61 -0
- package/src/sources/Fuel.res +19 -34
- package/src/sources/Fuel.res.mjs +34 -16
- package/src/sources/FuelSDK.res +37 -0
- package/src/sources/FuelSDK.res.mjs +29 -0
- package/src/sources/HyperFuel.res +2 -2
- package/src/sources/HyperFuel.resi +1 -1
- package/src/sources/HyperFuelClient.res +2 -2
- package/src/sources/HyperFuelSource.res +8 -8
- package/src/sources/HyperFuelSource.res.mjs +5 -5
- package/src/sources/HyperSyncHeightStream.res +28 -110
- package/src/sources/HyperSyncHeightStream.res.mjs +30 -63
- package/src/sources/HyperSyncSource.res +16 -18
- package/src/sources/HyperSyncSource.res.mjs +25 -25
- package/src/sources/Rpc.res +43 -0
- package/src/sources/Rpc.res.mjs +31 -0
- package/src/sources/RpcSource.res +13 -8
- package/src/sources/RpcSource.res.mjs +12 -7
- package/src/sources/Source.res +3 -2
- package/src/sources/SourceManager.res +183 -108
- package/src/sources/SourceManager.res.mjs +162 -99
- package/src/sources/SourceManager.resi +4 -5
- package/src/sources/Svm.res +59 -0
- package/src/sources/Svm.res.mjs +79 -0
- package/src/bindings/Ethers.gen.ts +0 -14
|
@@ -503,10 +503,12 @@ let make = (
|
|
|
503
503
|
|
|
504
504
|
let mutSuggestedBlockIntervals = Js.Dict.empty()
|
|
505
505
|
|
|
506
|
+
let client = Rest.client(url)
|
|
507
|
+
|
|
506
508
|
let makeTransactionLoader = () =>
|
|
507
509
|
LazyLoader.make(
|
|
508
510
|
~loaderFn=transactionHash =>
|
|
509
|
-
|
|
511
|
+
Rpc.GetTransactionByHash.route->Rest.fetch(transactionHash, ~client),
|
|
510
512
|
~onError=(am, ~exn) => {
|
|
511
513
|
Logging.error({
|
|
512
514
|
"err": exn->Utils.prettifyExn,
|
|
@@ -560,7 +562,12 @@ let make = (
|
|
|
560
562
|
)
|
|
561
563
|
let getEventTransactionOrThrow = makeThrowingGetEventTransaction(
|
|
562
564
|
~getTransactionFields=Ethers.JsonRpcProvider.makeGetTransactionFields(
|
|
563
|
-
~getTransactionByHash=
|
|
565
|
+
~getTransactionByHash=async transactionHash => {
|
|
566
|
+
switch await transactionLoader.contents->LazyLoader.get(transactionHash) {
|
|
567
|
+
| Some(tx) => tx
|
|
568
|
+
| None => Js.Exn.raiseError(`Transaction not found for hash: ${transactionHash}`)
|
|
569
|
+
}
|
|
570
|
+
},
|
|
564
571
|
~lowercaseAddresses,
|
|
565
572
|
),
|
|
566
573
|
)
|
|
@@ -601,7 +608,7 @@ let make = (
|
|
|
601
608
|
~toBlock,
|
|
602
609
|
~addressesByContractName,
|
|
603
610
|
~indexingContracts,
|
|
604
|
-
~
|
|
611
|
+
~knownHeight,
|
|
605
612
|
~partitionId,
|
|
606
613
|
~selection: FetchState.selection,
|
|
607
614
|
~retry as _,
|
|
@@ -621,8 +628,8 @@ let make = (
|
|
|
621
628
|
|
|
622
629
|
// Always have a toBlock for an RPC worker
|
|
623
630
|
let toBlock = switch toBlock {
|
|
624
|
-
| Some(toBlock) => Pervasives.min(toBlock,
|
|
625
|
-
| None =>
|
|
631
|
+
| Some(toBlock) => Pervasives.min(toBlock, knownHeight)
|
|
632
|
+
| None => knownHeight
|
|
626
633
|
}
|
|
627
634
|
|
|
628
635
|
let suggestedToBlock = Pervasives.min(fromBlock + suggestedBlockInterval - 1, toBlock)
|
|
@@ -874,7 +881,7 @@ let make = (
|
|
|
874
881
|
stats: {
|
|
875
882
|
totalTimeElapsed: totalTimeElapsed,
|
|
876
883
|
},
|
|
877
|
-
|
|
884
|
+
knownHeight,
|
|
878
885
|
reorgGuard,
|
|
879
886
|
fromBlockQueried: fromBlock,
|
|
880
887
|
}
|
|
@@ -902,8 +909,6 @@ let make = (
|
|
|
902
909
|
->Promise.catch(exn => exn->Error->Promise.resolve)
|
|
903
910
|
}
|
|
904
911
|
|
|
905
|
-
let client = Rest.client(url)
|
|
906
|
-
|
|
907
912
|
{
|
|
908
913
|
name,
|
|
909
914
|
sourceFor,
|
|
@@ -518,9 +518,10 @@ function make(param) {
|
|
|
518
518
|
var provider = Ethers.JsonRpcProvider.make(url, chain);
|
|
519
519
|
var getSelectionConfig = memoGetSelectionConfig(chain);
|
|
520
520
|
var mutSuggestedBlockIntervals = {};
|
|
521
|
+
var client = Rest.client(url, undefined);
|
|
521
522
|
var makeTransactionLoader = function () {
|
|
522
523
|
return LazyLoader.make((function (transactionHash) {
|
|
523
|
-
return
|
|
524
|
+
return Rest.$$fetch(Rpc.GetTransactionByHash.route, transactionHash, client);
|
|
524
525
|
}), (function (am, exn) {
|
|
525
526
|
Logging.error({
|
|
526
527
|
err: Utils.prettifyExn(exn),
|
|
@@ -559,8 +560,13 @@ function make(param) {
|
|
|
559
560
|
var getEventBlockOrThrow = makeThrowingGetEventBlock(function (blockNumber) {
|
|
560
561
|
return LazyLoader.get(blockLoader.contents, blockNumber);
|
|
561
562
|
});
|
|
562
|
-
var getEventTransactionOrThrow = makeThrowingGetEventTransaction(Ethers.JsonRpcProvider.makeGetTransactionFields((function (
|
|
563
|
-
|
|
563
|
+
var getEventTransactionOrThrow = makeThrowingGetEventTransaction(Ethers.JsonRpcProvider.makeGetTransactionFields((async function (transactionHash) {
|
|
564
|
+
var tx = await LazyLoader.get(transactionLoader.contents, transactionHash);
|
|
565
|
+
if (tx !== undefined) {
|
|
566
|
+
return tx;
|
|
567
|
+
} else {
|
|
568
|
+
return Js_exn.raiseError("Transaction not found for hash: " + transactionHash);
|
|
569
|
+
}
|
|
564
570
|
}), lowercaseAddresses));
|
|
565
571
|
var contractNameAbiMapping = {};
|
|
566
572
|
Belt_Array.forEach(param.contracts, (function (contract) {
|
|
@@ -604,11 +610,11 @@ function make(param) {
|
|
|
604
610
|
return HyperSyncClient.Decoder.fromSignatures(allEventSignatures);
|
|
605
611
|
}
|
|
606
612
|
};
|
|
607
|
-
var getItemsOrThrow = async function (fromBlock, toBlock, addressesByContractName, indexingContracts,
|
|
613
|
+
var getItemsOrThrow = async function (fromBlock, toBlock, addressesByContractName, indexingContracts, knownHeight, partitionId, selection, param, param$1) {
|
|
608
614
|
var startFetchingBatchTimeRef = Hrtime.makeTimer();
|
|
609
615
|
var maxSuggestedBlockInterval = mutSuggestedBlockIntervals[maxSuggestedBlockIntervalKey];
|
|
610
616
|
var suggestedBlockInterval = maxSuggestedBlockInterval !== undefined ? maxSuggestedBlockInterval : Belt_Option.getWithDefault(mutSuggestedBlockIntervals[partitionId], syncConfig.initialBlockInterval);
|
|
611
|
-
var toBlock$1 = toBlock !== undefined && toBlock <
|
|
617
|
+
var toBlock$1 = toBlock !== undefined && toBlock < knownHeight ? toBlock : knownHeight;
|
|
612
618
|
var suggestedToBlock = Caml.int_max(Caml.int_min((fromBlock + suggestedBlockInterval | 0) - 1 | 0, toBlock$1), fromBlock);
|
|
613
619
|
var firstBlockParentPromise = fromBlock > 0 ? LazyLoader.get(blockLoader.contents, fromBlock - 1 | 0).then(function (res) {
|
|
614
620
|
return res;
|
|
@@ -785,7 +791,7 @@ function make(param) {
|
|
|
785
791
|
prevRangeLastBlock: reorgGuard_prevRangeLastBlock
|
|
786
792
|
};
|
|
787
793
|
return {
|
|
788
|
-
|
|
794
|
+
knownHeight: knownHeight,
|
|
789
795
|
reorgGuard: reorgGuard,
|
|
790
796
|
parsedQueueItems: parsedQueueItems,
|
|
791
797
|
fromBlockQueried: fromBlock,
|
|
@@ -819,7 +825,6 @@ function make(param) {
|
|
|
819
825
|
});
|
|
820
826
|
}));
|
|
821
827
|
};
|
|
822
|
-
var client = Rest.client(url, undefined);
|
|
823
828
|
return {
|
|
824
829
|
name: name,
|
|
825
830
|
sourceFor: param.sourceFor,
|
package/src/sources/Source.res
CHANGED
|
@@ -11,7 +11,7 @@ type blockRangeFetchStats = {
|
|
|
11
11
|
Thes response returned from a block range fetch
|
|
12
12
|
*/
|
|
13
13
|
type blockRangeFetchResponse = {
|
|
14
|
-
|
|
14
|
+
knownHeight: int,
|
|
15
15
|
reorgGuard: ReorgDetection.reorgGuard,
|
|
16
16
|
parsedQueueItems: array<Internal.item>,
|
|
17
17
|
fromBlockQueried: int,
|
|
@@ -50,10 +50,11 @@ type t = {
|
|
|
50
50
|
~toBlock: option<int>,
|
|
51
51
|
~addressesByContractName: dict<array<Address.t>>,
|
|
52
52
|
~indexingContracts: dict<Internal.indexingContract>,
|
|
53
|
-
~
|
|
53
|
+
~knownHeight: int,
|
|
54
54
|
~partitionId: string,
|
|
55
55
|
~selection: FetchState.selection,
|
|
56
56
|
~retry: int,
|
|
57
57
|
~logger: Pino.t,
|
|
58
58
|
) => promise<blockRangeFetchResponse>,
|
|
59
|
+
createHeightSubscription?: (~onHeight: int => unit) => unit => unit,
|
|
59
60
|
}
|
|
@@ -2,13 +2,21 @@ open Belt
|
|
|
2
2
|
|
|
3
3
|
type sourceManagerStatus = Idle | WaitingForNewBlock | Querieng
|
|
4
4
|
|
|
5
|
+
type sourceState = {
|
|
6
|
+
source: Source.t,
|
|
7
|
+
mutable knownHeight: int,
|
|
8
|
+
mutable unsubscribe: option<unit => unit>,
|
|
9
|
+
mutable pendingHeightResolvers: array<int => unit>,
|
|
10
|
+
mutable disabled: bool,
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
// Ideally the ChainFetcher name suits this better
|
|
6
14
|
// But currently the ChainFetcher module is immutable
|
|
7
15
|
// and handles both processing and fetching.
|
|
8
16
|
// So this module is to encapsulate the fetching logic only
|
|
9
17
|
// with a mutable state for easier reasoning and testing.
|
|
10
18
|
type t = {
|
|
11
|
-
|
|
19
|
+
sourcesState: array<sourceState>,
|
|
12
20
|
mutable statusStart: Hrtime.timeRef,
|
|
13
21
|
mutable status: sourceManagerStatus,
|
|
14
22
|
maxPartitionConcurrency: int,
|
|
@@ -63,7 +71,13 @@ let make = (
|
|
|
63
71
|
)
|
|
64
72
|
{
|
|
65
73
|
maxPartitionConcurrency,
|
|
66
|
-
|
|
74
|
+
sourcesState: sources->Array.map(source => {
|
|
75
|
+
source,
|
|
76
|
+
knownHeight: 0,
|
|
77
|
+
unsubscribe: None,
|
|
78
|
+
pendingHeightResolvers: [],
|
|
79
|
+
disabled: false,
|
|
80
|
+
}),
|
|
67
81
|
activeSource: initialActiveSource,
|
|
68
82
|
waitingForNewBlockStateId: None,
|
|
69
83
|
fetchingPartitionsCount: 0,
|
|
@@ -92,7 +106,6 @@ let trackNewStatus = (sourceManager: t, ~newStatus) => {
|
|
|
92
106
|
let fetchNext = async (
|
|
93
107
|
sourceManager: t,
|
|
94
108
|
~fetchState: FetchState.t,
|
|
95
|
-
~currentBlockHeight,
|
|
96
109
|
~executeQuery,
|
|
97
110
|
~waitForNewBlock,
|
|
98
111
|
~onNewBlock,
|
|
@@ -100,13 +113,14 @@ let fetchNext = async (
|
|
|
100
113
|
) => {
|
|
101
114
|
let {maxPartitionConcurrency} = sourceManager
|
|
102
115
|
|
|
103
|
-
|
|
116
|
+
let nextQuery = fetchState->FetchState.getNextQuery(
|
|
104
117
|
~concurrencyLimit={
|
|
105
118
|
maxPartitionConcurrency - sourceManager.fetchingPartitionsCount
|
|
106
119
|
},
|
|
107
|
-
~currentBlockHeight,
|
|
108
120
|
~stateId,
|
|
109
|
-
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
switch nextQuery {
|
|
110
124
|
| ReachedMaxConcurrency
|
|
111
125
|
| NothingToQuery => ()
|
|
112
126
|
| WaitingForNewBlock =>
|
|
@@ -116,12 +130,12 @@ let fetchNext = async (
|
|
|
116
130
|
| None =>
|
|
117
131
|
sourceManager->trackNewStatus(~newStatus=WaitingForNewBlock)
|
|
118
132
|
sourceManager.waitingForNewBlockStateId = Some(stateId)
|
|
119
|
-
let
|
|
133
|
+
let knownHeight = await waitForNewBlock(~knownHeight=fetchState.knownHeight)
|
|
120
134
|
switch sourceManager.waitingForNewBlockStateId {
|
|
121
135
|
| Some(waitingStateId) if waitingStateId === stateId => {
|
|
122
136
|
sourceManager->trackNewStatus(~newStatus=Idle)
|
|
123
137
|
sourceManager.waitingForNewBlockStateId = None
|
|
124
|
-
onNewBlock(~
|
|
138
|
+
onNewBlock(~knownHeight)
|
|
125
139
|
}
|
|
126
140
|
| Some(_) // Don't reset it if we are waiting for another state
|
|
127
141
|
| None => ()
|
|
@@ -159,91 +173,144 @@ let fetchNext = async (
|
|
|
159
173
|
|
|
160
174
|
type status = Active | Stalled | Done
|
|
161
175
|
|
|
176
|
+
let disableSource = (sourceState: sourceState) => {
|
|
177
|
+
if !sourceState.disabled {
|
|
178
|
+
sourceState.disabled = true
|
|
179
|
+
switch sourceState.unsubscribe {
|
|
180
|
+
| Some(unsubscribe) => unsubscribe()
|
|
181
|
+
| None => ()
|
|
182
|
+
}
|
|
183
|
+
true
|
|
184
|
+
} else {
|
|
185
|
+
false
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
162
189
|
let getSourceNewHeight = async (
|
|
163
190
|
sourceManager,
|
|
164
|
-
~
|
|
165
|
-
~
|
|
191
|
+
~sourceState: sourceState,
|
|
192
|
+
~knownHeight,
|
|
166
193
|
~status: ref<status>,
|
|
167
194
|
~logger,
|
|
168
195
|
) => {
|
|
169
|
-
let
|
|
196
|
+
let source = sourceState.source
|
|
197
|
+
let initialHeight = sourceState.knownHeight
|
|
198
|
+
let newHeight = ref(initialHeight)
|
|
170
199
|
let retry = ref(0)
|
|
171
200
|
|
|
172
|
-
while newHeight.contents <=
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
201
|
+
while newHeight.contents <= knownHeight && status.contents !== Done {
|
|
202
|
+
// If subscription exists, wait for next height event
|
|
203
|
+
switch sourceState.unsubscribe {
|
|
204
|
+
| Some(_) =>
|
|
205
|
+
let height = await Promise.make((resolve, _reject) => {
|
|
206
|
+
sourceState.pendingHeightResolvers->Array.push(resolve)
|
|
178
207
|
})
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
source.
|
|
208
|
+
|
|
209
|
+
// Only accept heights greater than initialHeight
|
|
210
|
+
if height > initialHeight {
|
|
211
|
+
newHeight := height
|
|
212
|
+
}
|
|
213
|
+
| None =>
|
|
214
|
+
// No subscription, use REST polling
|
|
215
|
+
try {
|
|
216
|
+
// Use to detect if the source is taking too long to respond
|
|
217
|
+
let endTimer = Prometheus.SourceGetHeightDuration.startTimer({
|
|
218
|
+
"source": source.name,
|
|
219
|
+
"chainId": source.chain->ChainMap.Chain.toChainId,
|
|
220
|
+
})
|
|
221
|
+
let height = await source.getHeightOrThrow()
|
|
222
|
+
endTimer()
|
|
223
|
+
|
|
224
|
+
newHeight := height
|
|
225
|
+
if height <= knownHeight {
|
|
226
|
+
retry := 0
|
|
227
|
+
|
|
228
|
+
// If createHeightSubscription is available and height hasn't changed,
|
|
229
|
+
// create subscription instead of polling
|
|
230
|
+
switch source.createHeightSubscription {
|
|
231
|
+
| Some(createSubscription) =>
|
|
232
|
+
let unsubscribe = createSubscription(~onHeight=newHeight => {
|
|
233
|
+
sourceState.knownHeight = newHeight
|
|
234
|
+
// Resolve all pending height resolvers
|
|
235
|
+
let resolvers = sourceState.pendingHeightResolvers
|
|
236
|
+
sourceState.pendingHeightResolvers = []
|
|
237
|
+
resolvers->Array.forEach(resolve => resolve(newHeight))
|
|
238
|
+
})
|
|
239
|
+
sourceState.unsubscribe = Some(unsubscribe)
|
|
240
|
+
| None =>
|
|
241
|
+
// Slowdown polling when the chain isn't progressing
|
|
242
|
+
let pollingInterval = if status.contents === Stalled {
|
|
243
|
+
sourceManager.stalledPollingInterval
|
|
244
|
+
} else {
|
|
245
|
+
source.pollingInterval
|
|
246
|
+
}
|
|
247
|
+
await Utils.delay(pollingInterval)
|
|
248
|
+
}
|
|
190
249
|
}
|
|
191
|
-
|
|
250
|
+
} catch {
|
|
251
|
+
| exn =>
|
|
252
|
+
let retryInterval = sourceManager.getHeightRetryInterval(~retry=retry.contents)
|
|
253
|
+
logger->Logging.childTrace({
|
|
254
|
+
"msg": `Height retrieval from ${source.name} source failed. Retrying in ${retryInterval->Int.toString}ms.`,
|
|
255
|
+
"source": source.name,
|
|
256
|
+
"err": exn->Utils.prettifyExn,
|
|
257
|
+
})
|
|
258
|
+
retry := retry.contents + 1
|
|
259
|
+
await Utils.delay(retryInterval)
|
|
192
260
|
}
|
|
193
|
-
} catch {
|
|
194
|
-
| exn =>
|
|
195
|
-
let retryInterval = sourceManager.getHeightRetryInterval(~retry=retry.contents)
|
|
196
|
-
logger->Logging.childTrace({
|
|
197
|
-
"msg": `Height retrieval from ${source.name} source failed. Retrying in ${retryInterval->Int.toString}ms.`,
|
|
198
|
-
"source": source.name,
|
|
199
|
-
"err": exn->Utils.prettifyExn,
|
|
200
|
-
})
|
|
201
|
-
retry := retry.contents + 1
|
|
202
|
-
await Utils.delay(retryInterval)
|
|
203
261
|
}
|
|
204
262
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
263
|
+
|
|
264
|
+
// Update Prometheus only if height increased
|
|
265
|
+
if newHeight.contents > initialHeight {
|
|
266
|
+
Prometheus.SourceHeight.set(
|
|
267
|
+
~sourceName=source.name,
|
|
268
|
+
~chainId=source.chain->ChainMap.Chain.toChainId,
|
|
269
|
+
~blockNumber=newHeight.contents,
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
210
273
|
newHeight.contents
|
|
211
274
|
}
|
|
212
275
|
|
|
213
276
|
// Polls for a block height greater than the given block number to ensure a new block is available for indexing.
|
|
214
|
-
let waitForNewBlock = async (sourceManager: t, ~
|
|
215
|
-
let {
|
|
277
|
+
let waitForNewBlock = async (sourceManager: t, ~knownHeight) => {
|
|
278
|
+
let {sourcesState} = sourceManager
|
|
216
279
|
|
|
217
280
|
let logger = Logging.createChild(
|
|
218
281
|
~params={
|
|
219
282
|
"chainId": sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
|
|
220
|
-
"
|
|
283
|
+
"knownHeight": knownHeight,
|
|
221
284
|
},
|
|
222
285
|
)
|
|
223
286
|
logger->Logging.childTrace("Initiating check for new blocks.")
|
|
224
287
|
|
|
225
288
|
// Only include Live sources if we've actually synced some blocks
|
|
226
|
-
// (
|
|
289
|
+
// (knownHeight > 0 means we've fetched at least one batch)
|
|
227
290
|
// This prevents Live RPC from winning the initial height race and
|
|
228
291
|
// becoming activeSource, which would bypass HyperSync's smart block detection
|
|
229
|
-
let isInitialHeightFetch =
|
|
292
|
+
let isInitialHeightFetch = knownHeight === 0
|
|
230
293
|
|
|
231
294
|
let syncSources = []
|
|
232
295
|
let fallbackSources = []
|
|
233
|
-
|
|
234
|
-
|
|
296
|
+
sourcesState->Array.forEach(sourceState => {
|
|
297
|
+
let source = sourceState.source
|
|
298
|
+
if sourceState.disabled {
|
|
299
|
+
// Skip disabled sources
|
|
300
|
+
()
|
|
301
|
+
} else if (
|
|
235
302
|
source.sourceFor === Sync ||
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
303
|
+
// Include Live sources only after initial sync has started
|
|
304
|
+
// Live sources are optimized for real-time indexing with lower latency
|
|
305
|
+
source.sourceFor === Live && !isInitialHeightFetch ||
|
|
306
|
+
// Even if the active source is a fallback, still include
|
|
307
|
+
// it to the list. So we don't wait for a timeout again
|
|
308
|
+
// if all main sync sources are still not valid
|
|
309
|
+
source === sourceManager.activeSource
|
|
243
310
|
) {
|
|
244
|
-
syncSources->Array.push(
|
|
311
|
+
syncSources->Array.push(sourceState)
|
|
245
312
|
} else {
|
|
246
|
-
fallbackSources->Array.push(
|
|
313
|
+
fallbackSources->Array.push(sourceState)
|
|
247
314
|
}
|
|
248
315
|
})
|
|
249
316
|
|
|
@@ -251,10 +318,10 @@ let waitForNewBlock = async (sourceManager: t, ~currentBlockHeight) => {
|
|
|
251
318
|
|
|
252
319
|
let (source, newBlockHeight) = await Promise.race(
|
|
253
320
|
syncSources
|
|
254
|
-
->Array.map(async
|
|
321
|
+
->Array.map(async sourceState => {
|
|
255
322
|
(
|
|
256
|
-
source,
|
|
257
|
-
await sourceManager->getSourceNewHeight(~
|
|
323
|
+
sourceState.source,
|
|
324
|
+
await sourceManager->getSourceNewHeight(~sourceState, ~knownHeight, ~status, ~logger),
|
|
258
325
|
)
|
|
259
326
|
})
|
|
260
327
|
->Array.concat([
|
|
@@ -278,15 +345,10 @@ let waitForNewBlock = async (sourceManager: t, ~currentBlockHeight) => {
|
|
|
278
345
|
// Promise.race will be forever pending if fallbackSources is empty
|
|
279
346
|
// which is good for this use case
|
|
280
347
|
Promise.race(
|
|
281
|
-
fallbackSources->Array.map(async
|
|
348
|
+
fallbackSources->Array.map(async sourceState => {
|
|
282
349
|
(
|
|
283
|
-
source,
|
|
284
|
-
await sourceManager->getSourceNewHeight(
|
|
285
|
-
~source,
|
|
286
|
-
~currentBlockHeight,
|
|
287
|
-
~status,
|
|
288
|
-
~logger,
|
|
289
|
-
),
|
|
350
|
+
sourceState.source,
|
|
351
|
+
await sourceManager->getSourceNewHeight(~sourceState, ~knownHeight, ~status, ~logger),
|
|
290
352
|
)
|
|
291
353
|
}),
|
|
292
354
|
)
|
|
@@ -309,11 +371,11 @@ let waitForNewBlock = async (sourceManager: t, ~currentBlockHeight) => {
|
|
|
309
371
|
newBlockHeight
|
|
310
372
|
}
|
|
311
373
|
|
|
312
|
-
let
|
|
374
|
+
let getNextSyncSourceState = (
|
|
313
375
|
sourceManager,
|
|
314
376
|
// This is needed to include the Fallback source to rotation
|
|
315
|
-
~
|
|
316
|
-
~
|
|
377
|
+
~initialSourceState: sourceState,
|
|
378
|
+
~currentSourceState: sourceState,
|
|
317
379
|
// After multiple failures start returning fallback sources as well
|
|
318
380
|
// But don't try it when main sync sources fail because of invalid configuration
|
|
319
381
|
// note: The logic might be changed in the future
|
|
@@ -324,18 +386,23 @@ let getNextSyncSource = (
|
|
|
324
386
|
|
|
325
387
|
let hasActive = ref(false)
|
|
326
388
|
|
|
327
|
-
sourceManager.
|
|
328
|
-
|
|
389
|
+
sourceManager.sourcesState->Array.forEach(sourceState => {
|
|
390
|
+
let source = sourceState.source
|
|
391
|
+
|
|
392
|
+
// Skip disabled sources
|
|
393
|
+
if sourceState.disabled {
|
|
394
|
+
()
|
|
395
|
+
} else if sourceState === currentSourceState {
|
|
329
396
|
hasActive := true
|
|
330
397
|
} else if (
|
|
331
398
|
switch source.sourceFor {
|
|
332
399
|
| Sync => true
|
|
333
400
|
// Live sources should NOT be used for historical sync rotation
|
|
334
401
|
// They are only meant for real-time indexing once synced
|
|
335
|
-
| Live | Fallback => attemptFallbacks ||
|
|
402
|
+
| Live | Fallback => attemptFallbacks || sourceState === initialSourceState
|
|
336
403
|
}
|
|
337
404
|
) {
|
|
338
|
-
(hasActive.contents ? after : before)->Array.push(
|
|
405
|
+
(hasActive.contents ? after : before)->Array.push(sourceState)
|
|
339
406
|
}
|
|
340
407
|
})
|
|
341
408
|
|
|
@@ -344,12 +411,12 @@ let getNextSyncSource = (
|
|
|
344
411
|
| None =>
|
|
345
412
|
switch before->Array.get(0) {
|
|
346
413
|
| Some(s) => s
|
|
347
|
-
| None =>
|
|
414
|
+
| None => currentSourceState
|
|
348
415
|
}
|
|
349
416
|
}
|
|
350
417
|
}
|
|
351
418
|
|
|
352
|
-
let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~
|
|
419
|
+
let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~knownHeight) => {
|
|
353
420
|
let toBlockRef = ref(
|
|
354
421
|
switch query.target {
|
|
355
422
|
| Head => None
|
|
@@ -360,12 +427,16 @@ let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~currentBl
|
|
|
360
427
|
)
|
|
361
428
|
let responseRef = ref(None)
|
|
362
429
|
let retryRef = ref(0)
|
|
363
|
-
let
|
|
364
|
-
|
|
430
|
+
let initialSourceState =
|
|
431
|
+
sourceManager.sourcesState
|
|
432
|
+
->Js.Array2.find(s => s.source === sourceManager.activeSource)
|
|
433
|
+
->Option.getUnsafe
|
|
434
|
+
let sourceStateRef = ref(initialSourceState)
|
|
365
435
|
let shouldUpdateActiveSource = ref(false)
|
|
366
436
|
|
|
367
437
|
while responseRef.contents->Option.isNone {
|
|
368
|
-
let
|
|
438
|
+
let sourceState = sourceStateRef.contents
|
|
439
|
+
let source = sourceState.source
|
|
369
440
|
let toBlock = toBlockRef.contents
|
|
370
441
|
let retry = retryRef.contents
|
|
371
442
|
|
|
@@ -389,7 +460,7 @@ let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~currentBl
|
|
|
389
460
|
~addressesByContractName=query.addressesByContractName,
|
|
390
461
|
~indexingContracts=query.indexingContracts,
|
|
391
462
|
~partitionId=query.partitionId,
|
|
392
|
-
~
|
|
463
|
+
~knownHeight,
|
|
393
464
|
~selection=query.selection,
|
|
394
465
|
~retry,
|
|
395
466
|
~logger,
|
|
@@ -406,15 +477,19 @@ let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~currentBl
|
|
|
406
477
|
switch error {
|
|
407
478
|
| UnsupportedSelection(_)
|
|
408
479
|
| FailedGettingFieldSelection(_) => {
|
|
409
|
-
let
|
|
480
|
+
let nextSourceState =
|
|
481
|
+
sourceManager->getNextSyncSourceState(
|
|
482
|
+
~initialSourceState,
|
|
483
|
+
~currentSourceState=sourceState,
|
|
484
|
+
)
|
|
410
485
|
|
|
411
|
-
// These errors are impossible to recover, so we
|
|
412
|
-
//
|
|
413
|
-
let
|
|
486
|
+
// These errors are impossible to recover, so we disable the source
|
|
487
|
+
// so it's not attempted anymore
|
|
488
|
+
let notAlreadyDisabled = disableSource(sourceState)
|
|
414
489
|
|
|
415
490
|
// In case there are multiple partitions
|
|
416
491
|
// failing at the same time. Log only once
|
|
417
|
-
if
|
|
492
|
+
if notAlreadyDisabled {
|
|
418
493
|
switch error {
|
|
419
494
|
| UnsupportedSelection({message}) => logger->Logging.childError(message)
|
|
420
495
|
| FailedGettingFieldSelection({exn, message, blockNumber, logIndex}) =>
|
|
@@ -428,7 +503,7 @@ let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~currentBl
|
|
|
428
503
|
}
|
|
429
504
|
}
|
|
430
505
|
|
|
431
|
-
if
|
|
506
|
+
if nextSourceState === sourceState {
|
|
432
507
|
%raw(`null`)->ErrorHandling.mkLogAndRaise(
|
|
433
508
|
~logger,
|
|
434
509
|
~msg="The indexer doesn't have data-sources which can continue fetching. Please, check the error logs or reach out to the Envio team.",
|
|
@@ -436,9 +511,9 @@ let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~currentBl
|
|
|
436
511
|
} else {
|
|
437
512
|
logger->Logging.childInfo({
|
|
438
513
|
"msg": "Switching to another data-source",
|
|
439
|
-
"source":
|
|
514
|
+
"source": nextSourceState.source.name,
|
|
440
515
|
})
|
|
441
|
-
|
|
516
|
+
sourceStateRef := nextSourceState
|
|
442
517
|
shouldUpdateActiveSource := true
|
|
443
518
|
retryRef := 0
|
|
444
519
|
}
|
|
@@ -452,14 +527,14 @@ let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~currentBl
|
|
|
452
527
|
toBlockRef := Some(toBlock)
|
|
453
528
|
retryRef := 0
|
|
454
529
|
| FailedGettingItems({exn, attemptedToBlock, retry: ImpossibleForTheQuery({message})}) =>
|
|
455
|
-
let
|
|
456
|
-
sourceManager->
|
|
457
|
-
~
|
|
458
|
-
~
|
|
530
|
+
let nextSourceState =
|
|
531
|
+
sourceManager->getNextSyncSourceState(
|
|
532
|
+
~initialSourceState,
|
|
533
|
+
~currentSourceState=sourceState,
|
|
459
534
|
~attemptFallbacks=true,
|
|
460
535
|
)
|
|
461
536
|
|
|
462
|
-
let hasAnotherSource =
|
|
537
|
+
let hasAnotherSource = nextSourceState !== initialSourceState
|
|
463
538
|
|
|
464
539
|
logger->Logging.childWarn({
|
|
465
540
|
"msg": message ++ (hasAnotherSource ? " - Attempting to another source" : ""),
|
|
@@ -473,7 +548,7 @@ let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~currentBl
|
|
|
473
548
|
~msg="The indexer doesn't have data-sources which can continue fetching. Please, check the error logs or reach out to the Envio team.",
|
|
474
549
|
)
|
|
475
550
|
} else {
|
|
476
|
-
|
|
551
|
+
sourceStateRef := nextSourceState
|
|
477
552
|
shouldUpdateActiveSource := false
|
|
478
553
|
retryRef := 0
|
|
479
554
|
}
|
|
@@ -488,19 +563,19 @@ let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~currentBl
|
|
|
488
563
|
// just keep the value high
|
|
489
564
|
let attemptFallbacks = retry >= 10
|
|
490
565
|
|
|
491
|
-
let
|
|
566
|
+
let nextSourceState = switch retry {
|
|
492
567
|
// Don't attempt a switch on first two failure
|
|
493
|
-
| 0 | 1 =>
|
|
568
|
+
| 0 | 1 => sourceState
|
|
494
569
|
| _ =>
|
|
495
570
|
// Then try to switch every second failure
|
|
496
571
|
if retry->mod(2) === 0 {
|
|
497
|
-
sourceManager->
|
|
498
|
-
~
|
|
572
|
+
sourceManager->getNextSyncSourceState(
|
|
573
|
+
~initialSourceState,
|
|
499
574
|
~attemptFallbacks,
|
|
500
|
-
~
|
|
575
|
+
~currentSourceState=sourceState,
|
|
501
576
|
)
|
|
502
577
|
} else {
|
|
503
|
-
|
|
578
|
+
sourceState
|
|
504
579
|
}
|
|
505
580
|
}
|
|
506
581
|
|
|
@@ -514,13 +589,13 @@ let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~currentBl
|
|
|
514
589
|
"err": exn->Utils.prettifyExn,
|
|
515
590
|
})
|
|
516
591
|
|
|
517
|
-
let shouldSwitch =
|
|
592
|
+
let shouldSwitch = nextSourceState !== sourceState
|
|
518
593
|
if shouldSwitch {
|
|
519
594
|
logger->Logging.childInfo({
|
|
520
595
|
"msg": "Switching to another data-source",
|
|
521
|
-
"source":
|
|
596
|
+
"source": nextSourceState.source.name,
|
|
522
597
|
})
|
|
523
|
-
|
|
598
|
+
sourceStateRef := nextSourceState
|
|
524
599
|
shouldUpdateActiveSource := true
|
|
525
600
|
} else {
|
|
526
601
|
await Utils.delay(Pervasives.min(backoffMillis, 60_000))
|
|
@@ -534,7 +609,7 @@ let executeQuery = async (sourceManager: t, ~query: FetchState.query, ~currentBl
|
|
|
534
609
|
}
|
|
535
610
|
|
|
536
611
|
if shouldUpdateActiveSource.contents {
|
|
537
|
-
sourceManager.activeSource =
|
|
612
|
+
sourceManager.activeSource = sourceStateRef.contents.source
|
|
538
613
|
}
|
|
539
614
|
|
|
540
615
|
responseRef.contents->Option.getUnsafe
|