envio 3.13.0-alpha.0 → 3.13.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 -8
- package/src/BatchProcessing.res +4 -5
- package/src/BatchProcessing.res.mjs +4 -11
- package/src/ChainFetching.res +18 -38
- package/src/ChainFetching.res.mjs +14 -20
- package/src/ChainState.res +113 -77
- package/src/ChainState.res.mjs +90 -73
- package/src/ChainState.resi +14 -5
- package/src/Config.res +14 -17
- package/src/Config.res.mjs +5 -20
- package/src/CrossChainState.res +68 -20
- package/src/CrossChainState.res.mjs +48 -5
- package/src/CrossChainState.resi +1 -0
- package/src/Env.res +2 -2
- package/src/ErrorHandling.res +15 -4
- package/src/ErrorHandling.res.mjs +16 -5
- package/src/FinalizeBackfill.res +21 -9
- package/src/FinalizeBackfill.res.mjs +17 -4
- package/src/HandlerRegister.res +0 -37
- package/src/HandlerRegister.res.mjs +0 -18
- package/src/IndexerState.res +2 -0
- package/src/IndexerState.res.mjs +5 -0
- package/src/IndexerState.resi +1 -0
- package/src/Logging.res +6 -38
- package/src/Logging.res.mjs +5 -32
- package/src/Main.res +6 -9
- package/src/Main.res.mjs +3 -5
- package/src/Metrics.res +9 -4
- package/src/Metrics.res.mjs +3 -3
- package/src/Persistence.res +6 -3
- package/src/Persistence.res.mjs +5 -3
- package/src/PgStorage.res +2 -2
- package/src/PgStorage.res.mjs +2 -2
- package/src/Rollback.res +3 -3
- package/src/Server.res +0 -3
- package/src/SimulateItems.res +1 -1
- package/src/SimulateItems.res.mjs +1 -1
- package/src/Supervisor.res +236 -109
- package/src/Supervisor.res.mjs +185 -55
- package/src/TestIndexer.res +6 -3
- package/src/TestIndexer.res.mjs +3 -3
- package/src/Worker.res +6 -0
- package/src/Worker.res.mjs +2 -1
- package/src/bindings/NodeJs.res +25 -16
- package/src/bindings/NodeJs.res.mjs +8 -1
- package/src/sources/AddressSet.res +5 -3
- package/src/sources/AddressStore.res +19 -11
- package/src/sources/AddressStore.res.mjs +2 -7
- package/src/sources/BlockStore.res +10 -9
- package/src/sources/BlockStore.res.mjs +4 -4
- package/src/sources/ChainSources.res +4 -0
- package/src/sources/ChainSources.res.mjs +2 -2
- package/src/sources/EvmChain.res +4 -0
- package/src/sources/EvmChain.res.mjs +3 -1
- package/src/sources/EvmEventItem.res +56 -0
- package/src/sources/EvmEventItem.res.mjs +32 -0
- package/src/sources/EvmHyperSyncSource.res +7 -37
- package/src/sources/EvmHyperSyncSource.res.mjs +8 -29
- package/src/sources/EvmRpcClient.res +41 -59
- package/src/sources/EvmRpcClient.res.mjs +4 -54
- package/src/sources/EvmRpcWs.res +4 -1
- package/src/sources/EvmRpcWs.res.mjs +6 -2
- package/src/sources/FuelHyperSyncSource.res +6 -4
- package/src/sources/FuelHyperSyncSource.res.mjs +7 -9
- package/src/sources/HeightFeed.res +7 -1
- package/src/sources/HeightFeed.res.mjs +9 -7
- package/src/sources/HyperSyncClient.res +1 -14
- package/src/sources/RequestStat.res +7 -0
- package/src/sources/RequestStat.res.mjs +14 -1
- package/src/sources/RpcSource.res +100 -1043
- package/src/sources/RpcSource.res.mjs +81 -1056
- package/src/sources/SimulateSource.res +2 -5
- package/src/sources/SimulateSource.res.mjs +2 -3
- package/src/sources/Source.res +34 -12
- package/src/sources/Source.res.mjs +11 -0
- package/src/sources/SourceManager.res +111 -31
- package/src/sources/SourceManager.res.mjs +65 -31
- package/src/sources/SourceManager.resi +4 -0
- package/src/sources/StartBlockResolver.res +8 -5
- package/src/sources/StartBlockResolver.res.mjs +4 -2
- package/src/sources/SvmHyperSyncSource.res +4 -2
- package/src/sources/SvmHyperSyncSource.res.mjs +4 -6
- package/src/sources/TransactionStore.res +9 -6
- package/src/sources/TransactionStore.res.mjs +2 -2
- package/src/tui/Tui.res +4 -18
- package/src/tui/Tui.res.mjs +3 -6
- package/src/LazyLoader.res +0 -135
- package/src/LazyLoader.res.mjs +0 -118
- package/src/bindings/SDSL.res +0 -12
- package/src/bindings/SDSL.res.mjs +0 -9
- package/src/bindings/Yargs.res +0 -8
- package/src/bindings/Yargs.res.mjs +0 -2
- package/src/sources/Rpc.res +0 -185
- package/src/sources/Rpc.res.mjs +0 -183
|
@@ -17,7 +17,7 @@ let make = (
|
|
|
17
17
|
pollingInterval: 0,
|
|
18
18
|
getBlockHashes: (~blockNumbers as _, ~logger as _) => {
|
|
19
19
|
Promise.resolve({
|
|
20
|
-
Source.result: Ok(BlockStore.fromJs([], ~ecosystem
|
|
20
|
+
Source.result: Ok(BlockStore.fromJs([], ~ecosystem)),
|
|
21
21
|
requestStats: [],
|
|
22
22
|
})
|
|
23
23
|
},
|
|
@@ -112,10 +112,7 @@ let make = (
|
|
|
112
112
|
// EVM/Fuel keep transaction and block inline. SVM simulate returns a
|
|
113
113
|
// store page so materialize + activity attach run the same path as HyperSync.
|
|
114
114
|
transactionStore,
|
|
115
|
-
blockStore: blockStore->Option.getOr(
|
|
116
|
-
BlockStore.fromJs([], ~ecosystem, ~shouldChecksum=false),
|
|
117
|
-
),
|
|
118
|
-
fromBlockQueried: fromBlock,
|
|
115
|
+
blockStore: blockStore->Option.getOr(BlockStore.fromJs([], ~ecosystem)),
|
|
119
116
|
latestFetchedBlockNumber: toBlockQueried,
|
|
120
117
|
stats: {
|
|
121
118
|
totalTimeElapsed: 0.,
|
|
@@ -19,7 +19,7 @@ function make(items, endBlock, chainId, addressStore, ecosystemOpt, transactionS
|
|
|
19
19
|
getBlockHashes: (param, param$1) => Promise.resolve({
|
|
20
20
|
result: {
|
|
21
21
|
TAG: "Ok",
|
|
22
|
-
_0: BlockStore.fromJs([], ecosystem
|
|
22
|
+
_0: BlockStore.fromJs([], ecosystem)
|
|
23
23
|
},
|
|
24
24
|
requestStats: []
|
|
25
25
|
}),
|
|
@@ -80,8 +80,7 @@ function make(items, endBlock, chainId, addressStore, ecosystemOpt, transactionS
|
|
|
80
80
|
knownHeight: reportedHeight,
|
|
81
81
|
parsedQueueItems: parsedQueueItems,
|
|
82
82
|
transactionStore: transactionStore,
|
|
83
|
-
blockStore: Stdlib_Option.getOr(blockStore, BlockStore.fromJs([], ecosystem
|
|
84
|
-
fromBlockQueried: fromBlock,
|
|
83
|
+
blockStore: Stdlib_Option.getOr(blockStore, BlockStore.fromJs([], ecosystem)),
|
|
85
84
|
latestFetchedBlockNumber: toBlockQueried,
|
|
86
85
|
stats: {
|
|
87
86
|
"total time elapsed (s)": 0
|
package/src/sources/Source.res
CHANGED
|
@@ -78,14 +78,13 @@ type blockRangeFetchResponse = {
|
|
|
78
78
|
parsedQueueItems: array<Internal.item>,
|
|
79
79
|
// Page of transactions for this response's items, keyed by (blockNumber,
|
|
80
80
|
// transactionIndex); merged into the chain's store on apply. `None` for
|
|
81
|
-
// sources that keep the transaction inline on the payload (
|
|
81
|
+
// sources that keep the transaction inline on the payload (Fuel/Simulate).
|
|
82
82
|
transactionStore: option<TransactionStore.t>,
|
|
83
83
|
// Page of blocks observed while fetching this range, keyed by block number;
|
|
84
84
|
// merged into the chain's store on apply, where its hashes drive reorg
|
|
85
|
-
// detection.
|
|
86
|
-
//
|
|
85
|
+
// detection. A source that keeps the block inline on the payload (Simulate)
|
|
86
|
+
// contributes hash-only rows built from the block hashes it saw.
|
|
87
87
|
blockStore: BlockStore.t,
|
|
88
|
-
fromBlockQueried: int,
|
|
89
88
|
latestFetchedBlockNumber: int,
|
|
90
89
|
stats: blockRangeFetchStats,
|
|
91
90
|
requestStats: array<requestStat>,
|
|
@@ -122,13 +121,35 @@ type getItemsRetry =
|
|
|
122
121
|
type rateLimited = {resetMs: int, requestStats: array<requestStat>}
|
|
123
122
|
exception RateLimited(rateLimited)
|
|
124
123
|
|
|
124
|
+
// The two failures that can follow requests carry their timings, so a page
|
|
125
|
+
// that ends in a retry still counts towards the source's metrics — the same
|
|
126
|
+
// contract `RateLimited` and `SourceBehindHead` keep.
|
|
125
127
|
type getItemsError =
|
|
126
128
|
| UnsupportedSelection({message: string})
|
|
127
|
-
| FailedGettingFieldSelection({
|
|
128
|
-
|
|
129
|
+
| FailedGettingFieldSelection({
|
|
130
|
+
// The cause, where the source has one beyond `message`.
|
|
131
|
+
exn?: exn,
|
|
132
|
+
blockNumber: int,
|
|
133
|
+
message: string,
|
|
134
|
+
requestStats: array<requestStat>,
|
|
135
|
+
})
|
|
136
|
+
| FailedGettingItems({
|
|
137
|
+
// The cause, where the source has one beyond the retry's own message.
|
|
138
|
+
exn?: exn,
|
|
139
|
+
attemptedToBlock: int,
|
|
140
|
+
retry: getItemsRetry,
|
|
141
|
+
requestStats: array<requestStat>,
|
|
142
|
+
})
|
|
129
143
|
|
|
130
144
|
exception GetItemsError(getItemsError)
|
|
131
145
|
|
|
146
|
+
let getItemsErrorRequestStats = (error: getItemsError) =>
|
|
147
|
+
switch error {
|
|
148
|
+
// Raised before anything is requested.
|
|
149
|
+
| UnsupportedSelection(_) => []
|
|
150
|
+
| FailedGettingFieldSelection({requestStats}) | FailedGettingItems({requestStats}) => requestStats
|
|
151
|
+
}
|
|
152
|
+
|
|
132
153
|
type sourceFor = Sync | Fallback | Realtime
|
|
133
154
|
|
|
134
155
|
// Why a height subscription's connection ended. Closed, so a consumer decides
|
|
@@ -222,11 +243,12 @@ type t = {
|
|
|
222
243
|
~onHeight: int => unit,
|
|
223
244
|
~onStatus: heightSubscriptionStatus => unit,
|
|
224
245
|
) => unit => unit,
|
|
225
|
-
// Invoked when a reorg or internally inconsistent response means
|
|
226
|
-
// may
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
// that
|
|
246
|
+
// Invoked when a reorg or an internally inconsistent response means what the
|
|
247
|
+
// source is holding may describe an orphaned chain — for RPC, the reads still
|
|
248
|
+
// in flight, which anything asking afterwards would otherwise join. The
|
|
249
|
+
// stores are rolled back separately; this is only about what the source
|
|
250
|
+
// itself carries. Deliberately takes no rollback target: the deepest reorged
|
|
251
|
+
// block isn't known until the depth search runs, and that search reads back
|
|
252
|
+
// through this very state.
|
|
231
253
|
onReorg?: unit => unit,
|
|
232
254
|
}
|
|
@@ -50,6 +50,16 @@ let RateLimited = /* @__PURE__ */Primitive_exceptions.create("Source.RateLimited
|
|
|
50
50
|
|
|
51
51
|
let GetItemsError = /* @__PURE__ */Primitive_exceptions.create("Source.GetItemsError");
|
|
52
52
|
|
|
53
|
+
function getItemsErrorRequestStats(error) {
|
|
54
|
+
switch (error.TAG) {
|
|
55
|
+
case "UnsupportedSelection" :
|
|
56
|
+
return [];
|
|
57
|
+
case "FailedGettingFieldSelection" :
|
|
58
|
+
case "FailedGettingItems" :
|
|
59
|
+
return error.requestStats;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
function downReasonLabel(reason) {
|
|
54
64
|
if (typeof reason === "object") {
|
|
55
65
|
return `http-` + reason._0.toString();
|
|
@@ -82,6 +92,7 @@ export {
|
|
|
82
92
|
SourceBehindHead,
|
|
83
93
|
RateLimited,
|
|
84
94
|
GetItemsError,
|
|
95
|
+
getItemsErrorRequestStats,
|
|
85
96
|
downReasonLabel,
|
|
86
97
|
}
|
|
87
98
|
/* nativeFailurePayloadSchema Not a pure module */
|
|
@@ -84,6 +84,10 @@ type t = {
|
|
|
84
84
|
// Should take into consideration partitions fetching for previous states (before rollback)
|
|
85
85
|
mutable fetchingPartitionsCount: int,
|
|
86
86
|
recoveryTimeout: float,
|
|
87
|
+
// Why a source was last disabled for good. A disabled source is never
|
|
88
|
+
// retried, so by the time the manager runs out of sources this is the reason
|
|
89
|
+
// it did — and the only thing that tells a caller what to change.
|
|
90
|
+
mutable disableReason: option<string>,
|
|
87
91
|
mutable hasRealtime: bool,
|
|
88
92
|
mutable committedRateLimitTimeMs: float,
|
|
89
93
|
mutable rateLimitWaiters: int,
|
|
@@ -386,6 +390,7 @@ let make = (
|
|
|
386
390
|
idleSeconds: 0.,
|
|
387
391
|
waitingForNewBlockSeconds: 0.,
|
|
388
392
|
queryingSeconds: 0.,
|
|
393
|
+
disableReason: None,
|
|
389
394
|
hasRealtime,
|
|
390
395
|
committedRateLimitTimeMs: 0.0,
|
|
391
396
|
rateLimitWaiters: 0,
|
|
@@ -637,10 +642,20 @@ let retryBehindHead = async (
|
|
|
637
642
|
)
|
|
638
643
|
}
|
|
639
644
|
|
|
640
|
-
// A
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
|
|
645
|
+
// A query that has failed this many times running has outlived every failover
|
|
646
|
+
// the schedule above can offer: it has rotated through the chain's sources
|
|
647
|
+
// several times over and none of them answered. Whatever the reported cause,
|
|
648
|
+
// the chain is no longer making progress and the operator has to hear about it.
|
|
649
|
+
let stallRetries = 13
|
|
650
|
+
|
|
651
|
+
// Which level a retry is reported at, and what it says once the retries have
|
|
652
|
+
// outlasted the rotation.
|
|
653
|
+
let stalledLog = (~retry, ~warnAfter, ~msg, ~stalled) =>
|
|
654
|
+
if retry >= stallRetries {
|
|
655
|
+
(Logging.childError, msg ++ stalled)
|
|
656
|
+
} else {
|
|
657
|
+
(retry >= warnAfter ? Logging.childWarn : Logging.childTrace, msg)
|
|
658
|
+
}
|
|
644
659
|
|
|
645
660
|
// The response contradicted itself (the same block twice with different hashes,
|
|
646
661
|
// or a requested hash missing). It may be a reorg mid-request, so refetch before
|
|
@@ -657,14 +672,12 @@ let retryInconsistentResponse = async (
|
|
|
657
672
|
) => {
|
|
658
673
|
let backoffMillis = retry->retryBackoffMillis
|
|
659
674
|
let msg = `Received a partial indicator of a possible reorg from the ${sourceState.source.name} source while fetching ${method}. Retrying the request to better identify whether a reorg happened.`
|
|
660
|
-
let (log, msg) =
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
(retry >= 2 ? Logging.childWarn : Logging.childTrace, msg)
|
|
667
|
-
}
|
|
675
|
+
let (log, msg) = stalledLog(
|
|
676
|
+
~retry,
|
|
677
|
+
~warnAfter=2,
|
|
678
|
+
~msg,
|
|
679
|
+
~stalled=" It has disagreed with itself on every attempt for several minutes now, so this chain has stopped making progress - the endpoint is likely serving blocks and logs from nodes on different chains.",
|
|
680
|
+
)
|
|
668
681
|
logger->log({
|
|
669
682
|
"msg": msg,
|
|
670
683
|
"method": method,
|
|
@@ -686,6 +699,45 @@ let retryInconsistentResponse = async (
|
|
|
686
699
|
)
|
|
687
700
|
}
|
|
688
701
|
|
|
702
|
+
// The source could not serve the page and reported nothing that changes the
|
|
703
|
+
// query, so the only move left is to wait and let the schedule try another
|
|
704
|
+
// source. Escalates once the retries have outlasted every failover.
|
|
705
|
+
let retryFailedPage = async (
|
|
706
|
+
sourceManager: t,
|
|
707
|
+
sourceState: sourceState,
|
|
708
|
+
~retry,
|
|
709
|
+
~isRealtime,
|
|
710
|
+
~logger: Pino.t,
|
|
711
|
+
~msg: string,
|
|
712
|
+
~attemptedToBlock: int,
|
|
713
|
+
~backoffMillis: int,
|
|
714
|
+
~minBackoffMillis=0,
|
|
715
|
+
~err: option<exn>,
|
|
716
|
+
~excludedSources=?,
|
|
717
|
+
) => {
|
|
718
|
+
let (log, msg) = stalledLog(
|
|
719
|
+
~retry,
|
|
720
|
+
~warnAfter=4,
|
|
721
|
+
~msg,
|
|
722
|
+
~stalled=" No source available to this chain has served this query on any attempt since, so indexing has stopped making progress.",
|
|
723
|
+
)
|
|
724
|
+
logger->log({
|
|
725
|
+
"msg": msg,
|
|
726
|
+
"toBlock": attemptedToBlock,
|
|
727
|
+
"backOffMilliseconds": backoffMillis,
|
|
728
|
+
"retry": retry,
|
|
729
|
+
"err": err->Option.map(Utils.prettifyExn),
|
|
730
|
+
})
|
|
731
|
+
await sourceManager->backoffBeforeRetry(
|
|
732
|
+
sourceState,
|
|
733
|
+
~retry,
|
|
734
|
+
~isRealtime,
|
|
735
|
+
~backoffMillis,
|
|
736
|
+
~minBackoffMillis,
|
|
737
|
+
~excludedSources?,
|
|
738
|
+
)
|
|
739
|
+
}
|
|
740
|
+
|
|
689
741
|
// Polls for a block height greater than the given block number to ensure a new block is available for indexing.
|
|
690
742
|
/*
|
|
691
743
|
Resolves at the first height above `knownHeight` that any eligible source
|
|
@@ -895,7 +947,13 @@ let executeQuery = async (
|
|
|
895
947
|
~knownHeight,
|
|
896
948
|
~isRealtime,
|
|
897
949
|
) => {
|
|
898
|
-
|
|
950
|
+
// Read when the manager actually runs out of sources, not on the way in: the
|
|
951
|
+
// reason is recorded by the failure that disabled the last one.
|
|
952
|
+
let noSourcesError = () => switch sourceManager.disableReason {
|
|
953
|
+
| Some(reason) =>
|
|
954
|
+
`The indexer doesn't have data-sources which can continue fetching. The last one was disabled because ${reason}`
|
|
955
|
+
| None => "The indexer doesn't have data-sources which can continue fetching. Please, check the error logs or reach out to the Envio team."
|
|
956
|
+
}
|
|
899
957
|
|
|
900
958
|
// Sources where the query is impossible - lazily allocated, excluded for the duration of this query
|
|
901
959
|
let excludedSourcesRef = ref(None)
|
|
@@ -923,7 +981,7 @@ let executeQuery = async (
|
|
|
923
981
|
s
|
|
924
982
|
| None =>
|
|
925
983
|
let logger = Logging.createChild(~params={"chainId": sourceManager.activeSource.chainId})
|
|
926
|
-
%raw(`null`)->ErrorHandling.mkLogAndRaise(~logger, ~msg=noSourcesError)
|
|
984
|
+
%raw(`null`)->ErrorHandling.mkLogAndRaise(~logger, ~msg=noSourcesError())
|
|
927
985
|
}
|
|
928
986
|
sourceManager.activeSource = sourceState.source
|
|
929
987
|
let source = sourceState.source
|
|
@@ -999,6 +1057,9 @@ let executeQuery = async (
|
|
|
999
1057
|
retryRef := retryRef.contents + 1
|
|
1000
1058
|
|
|
1001
1059
|
| Source.GetItemsError(error) =>
|
|
1060
|
+
// A page that ends in a retry still made requests; count them, as the
|
|
1061
|
+
// rate-limit and behind-head arms above do.
|
|
1062
|
+
sourceState->recordRequestStats(error->Source.getItemsErrorRequestStats)
|
|
1002
1063
|
switch error {
|
|
1003
1064
|
| UnsupportedSelection(_)
|
|
1004
1065
|
| FailedGettingFieldSelection(_) => {
|
|
@@ -1010,13 +1071,15 @@ let executeQuery = async (
|
|
|
1010
1071
|
// failing at the same time. Log only once
|
|
1011
1072
|
if notAlreadyDisabled {
|
|
1012
1073
|
switch error {
|
|
1013
|
-
| UnsupportedSelection({message}) =>
|
|
1014
|
-
|
|
1074
|
+
| UnsupportedSelection({message}) =>
|
|
1075
|
+
sourceManager.disableReason = Some(message)
|
|
1076
|
+
logger->Logging.childError(message)
|
|
1077
|
+
| FailedGettingFieldSelection({?exn, message, blockNumber}) =>
|
|
1078
|
+
sourceManager.disableReason = Some(message)
|
|
1015
1079
|
logger->Logging.childError({
|
|
1016
1080
|
"msg": message,
|
|
1017
|
-
"err": exn->Utils.prettifyExn,
|
|
1081
|
+
"err": exn->Option.map(Utils.prettifyExn),
|
|
1018
1082
|
"blockNumber": blockNumber,
|
|
1019
|
-
"logIndex": logIndex,
|
|
1020
1083
|
})
|
|
1021
1084
|
| _ => ()
|
|
1022
1085
|
}
|
|
@@ -1024,6 +1087,26 @@ let executeQuery = async (
|
|
|
1024
1087
|
|
|
1025
1088
|
retryRef := 0
|
|
1026
1089
|
}
|
|
1090
|
+
// A suggestion that doesn't narrow the range asks for the query that just
|
|
1091
|
+
// failed. Retrying it unchanged and without a wait makes no progress and
|
|
1092
|
+
// never counts against the source, so pace it like any other failure and
|
|
1093
|
+
// let the schedule move on to one that can answer.
|
|
1094
|
+
| FailedGettingItems({?exn, attemptedToBlock, retry: WithSuggestedToBlock({toBlock})})
|
|
1095
|
+
if toBlock >= attemptedToBlock =>
|
|
1096
|
+
await sourceManager->retryFailedPage(
|
|
1097
|
+
sourceState,
|
|
1098
|
+
~retry,
|
|
1099
|
+
~isRealtime,
|
|
1100
|
+
~logger,
|
|
1101
|
+
~msg=`The ${source.name} source rejected the block range and suggested #${toBlock->Int.toString}, which is no narrower than the #${attemptedToBlock->Int.toString} it just refused.`,
|
|
1102
|
+
~attemptedToBlock,
|
|
1103
|
+
~backoffMillis=retry->retryBackoffMillis,
|
|
1104
|
+
~minBackoffMillis=minRecoverableBackoffMillis,
|
|
1105
|
+
~err=exn,
|
|
1106
|
+
~excludedSources=?excludedSourcesRef.contents,
|
|
1107
|
+
)
|
|
1108
|
+
retryRef := retryRef.contents + 1
|
|
1109
|
+
|
|
1027
1110
|
| FailedGettingItems({attemptedToBlock, retry: WithSuggestedToBlock({toBlock})}) =>
|
|
1028
1111
|
logger->Logging.childTrace({
|
|
1029
1112
|
"msg": "Failed getting data for the block range. Immediately retrying with the suggested block range from response.",
|
|
@@ -1031,8 +1114,10 @@ let executeQuery = async (
|
|
|
1031
1114
|
"suggestedToBlock": toBlock,
|
|
1032
1115
|
})
|
|
1033
1116
|
toBlockRef := Some(toBlock)
|
|
1117
|
+
// The next attempt asks a strictly smaller question, so it starts a
|
|
1118
|
+
// fresh schedule rather than continuing this one.
|
|
1034
1119
|
retryRef := 0
|
|
1035
|
-
| FailedGettingItems({exn, attemptedToBlock, retry: ImpossibleForTheQuery({message})}) =>
|
|
1120
|
+
| FailedGettingItems({?exn, attemptedToBlock, retry: ImpossibleForTheQuery({message})}) =>
|
|
1036
1121
|
// Don't set lastFailedAt - the source isn't broken, the query just can't work on it
|
|
1037
1122
|
let excludedSources = switch excludedSourcesRef.contents {
|
|
1038
1123
|
| Some(s) => s
|
|
@@ -1046,25 +1131,20 @@ let executeQuery = async (
|
|
|
1046
1131
|
logger->Logging.childWarn({
|
|
1047
1132
|
"msg": message ++ " - Attempting another source",
|
|
1048
1133
|
"toBlock": attemptedToBlock,
|
|
1049
|
-
"err": exn->Utils.prettifyExn,
|
|
1134
|
+
"err": exn->Option.map(Utils.prettifyExn),
|
|
1050
1135
|
})
|
|
1051
1136
|
retryRef := 0
|
|
1052
1137
|
|
|
1053
|
-
| FailedGettingItems({exn, attemptedToBlock, retry: WithBackoff({message, backoffMillis})}) =>
|
|
1054
|
-
|
|
1055
|
-
let log = retry >= 4 ? Logging.childWarn : Logging.childTrace
|
|
1056
|
-
logger->log({
|
|
1057
|
-
"msg": message,
|
|
1058
|
-
"toBlock": attemptedToBlock,
|
|
1059
|
-
"backOffMilliseconds": backoffMillis,
|
|
1060
|
-
"retry": retry,
|
|
1061
|
-
"err": exn->Utils.prettifyExn,
|
|
1062
|
-
})
|
|
1063
|
-
await sourceManager->backoffBeforeRetry(
|
|
1138
|
+
| FailedGettingItems({?exn, attemptedToBlock, retry: WithBackoff({message, backoffMillis})}) =>
|
|
1139
|
+
await sourceManager->retryFailedPage(
|
|
1064
1140
|
sourceState,
|
|
1065
1141
|
~retry,
|
|
1066
1142
|
~isRealtime,
|
|
1143
|
+
~logger,
|
|
1144
|
+
~msg=message,
|
|
1145
|
+
~attemptedToBlock,
|
|
1067
1146
|
~backoffMillis,
|
|
1147
|
+
~err=exn,
|
|
1068
1148
|
~excludedSources=?excludedSourcesRef.contents,
|
|
1069
1149
|
)
|
|
1070
1150
|
retryRef := retryRef.contents + 1
|
|
@@ -311,6 +311,7 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
|
|
|
311
311
|
waitingLogged: false,
|
|
312
312
|
fetchingPartitionsCount: 0,
|
|
313
313
|
recoveryTimeout: recoveryTimeout,
|
|
314
|
+
disableReason: undefined,
|
|
314
315
|
hasRealtime: hasRealtime,
|
|
315
316
|
committedRateLimitTimeMs: 0.0,
|
|
316
317
|
rateLimitWaiters: 0,
|
|
@@ -509,16 +510,24 @@ async function retryBehindHead(sourceManager, sourceState, retry, isRealtime, lo
|
|
|
509
510
|
return await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, 50, excludedSources);
|
|
510
511
|
}
|
|
511
512
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
let match = retry >= 13 ? [
|
|
513
|
+
function stalledLog(retry, warnAfter, msg, stalled) {
|
|
514
|
+
if (retry >= 13) {
|
|
515
|
+
return [
|
|
516
516
|
Logging.childError,
|
|
517
|
-
msg +
|
|
518
|
-
]
|
|
519
|
-
|
|
517
|
+
msg + stalled
|
|
518
|
+
];
|
|
519
|
+
} else {
|
|
520
|
+
return [
|
|
521
|
+
retry >= warnAfter ? Logging.childWarn : Logging.childTrace,
|
|
520
522
|
msg
|
|
521
523
|
];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function retryInconsistentResponse(sourceManager, sourceState, retry, isRealtime, logger, method, err, excludedSources) {
|
|
528
|
+
let backoffMillis = Utils.expBackoff(100, retry, 60000);
|
|
529
|
+
let msg = `Received a partial indicator of a possible reorg from the ` + sourceState.source.name + ` source while fetching ` + method + `. Retrying the request to better identify whether a reorg happened.`;
|
|
530
|
+
let match = stalledLog(retry, 2, msg, " It has disagreed with itself on every attempt for several minutes now, so this chain has stopped making progress - the endpoint is likely serving blocks and logs from nodes on different chains.");
|
|
522
531
|
match[0](logger, {
|
|
523
532
|
msg: match[1],
|
|
524
533
|
method: method,
|
|
@@ -530,6 +539,19 @@ async function retryInconsistentResponse(sourceManager, sourceState, retry, isRe
|
|
|
530
539
|
return await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, 50, excludedSources);
|
|
531
540
|
}
|
|
532
541
|
|
|
542
|
+
async function retryFailedPage(sourceManager, sourceState, retry, isRealtime, logger, msg, attemptedToBlock, backoffMillis, minBackoffMillisOpt, err, excludedSources) {
|
|
543
|
+
let minBackoffMillis = minBackoffMillisOpt !== undefined ? minBackoffMillisOpt : 0;
|
|
544
|
+
let match = stalledLog(retry, 4, msg, " No source available to this chain has served this query on any attempt since, so indexing has stopped making progress.");
|
|
545
|
+
match[0](logger, {
|
|
546
|
+
msg: match[1],
|
|
547
|
+
toBlock: attemptedToBlock,
|
|
548
|
+
backOffMilliseconds: backoffMillis,
|
|
549
|
+
retry: retry,
|
|
550
|
+
err: Stdlib_Option.map(err, Utils.prettifyExn)
|
|
551
|
+
});
|
|
552
|
+
return await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, minBackoffMillis, excludedSources);
|
|
553
|
+
}
|
|
554
|
+
|
|
533
555
|
function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling) {
|
|
534
556
|
let sourcesState = sourceManager.sourcesState;
|
|
535
557
|
let logger = Logging.createChild({
|
|
@@ -650,6 +672,14 @@ function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling)
|
|
|
650
672
|
}
|
|
651
673
|
|
|
652
674
|
async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
675
|
+
let noSourcesError = () => {
|
|
676
|
+
let reason = sourceManager.disableReason;
|
|
677
|
+
if (reason !== undefined) {
|
|
678
|
+
return `The indexer doesn't have data-sources which can continue fetching. The last one was disabled because ` + reason;
|
|
679
|
+
} else {
|
|
680
|
+
return "The indexer doesn't have data-sources which can continue fetching. Please, check the error logs or reach out to the Envio team.";
|
|
681
|
+
}
|
|
682
|
+
};
|
|
653
683
|
let excludedSourcesRef;
|
|
654
684
|
let toBlockRef = query.toBlock;
|
|
655
685
|
let responseRef;
|
|
@@ -674,7 +704,7 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
|
674
704
|
let logger$1 = Logging.createChild({
|
|
675
705
|
chainId: sourceManager.activeSource.chainId
|
|
676
706
|
});
|
|
677
|
-
sourceState = ErrorHandling.mkLogAndRaise(logger$1,
|
|
707
|
+
sourceState = ErrorHandling.mkLogAndRaise(logger$1, noSourcesError(), null);
|
|
678
708
|
}
|
|
679
709
|
sourceManager.activeSource = sourceState.source;
|
|
680
710
|
let source = sourceState.source;
|
|
@@ -713,6 +743,8 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
|
713
743
|
retryRef = retryRef + 1 | 0;
|
|
714
744
|
} else if (err.RE_EXN_ID === Source.GetItemsError) {
|
|
715
745
|
let error = err._1;
|
|
746
|
+
let requestStats = Source.getItemsErrorRequestStats(error);
|
|
747
|
+
recordStatsInto(sourceState.requestStats, requestStats);
|
|
716
748
|
let exit = 0;
|
|
717
749
|
switch (error.TAG) {
|
|
718
750
|
case "UnsupportedSelection" :
|
|
@@ -726,25 +758,21 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
|
726
758
|
switch (match$1.TAG) {
|
|
727
759
|
case "WithSuggestedToBlock" :
|
|
728
760
|
let toBlock$1 = match$1.toBlock;
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
761
|
+
if (toBlock$1 >= attemptedToBlock) {
|
|
762
|
+
await retryFailedPage(sourceManager, sourceState, retry, isRealtime, logger$2, `The ` + source.name + ` source rejected the block range and suggested #` + toBlock$1.toString() + `, which is no narrower than the #` + attemptedToBlock.toString() + ` it just refused.`, attemptedToBlock, Utils.expBackoff(100, retry, 60000), 50, exn, excludedSourcesRef);
|
|
763
|
+
retryRef = retryRef + 1 | 0;
|
|
764
|
+
} else {
|
|
765
|
+
Logging.childTrace(logger$2, {
|
|
766
|
+
msg: "Failed getting data for the block range. Immediately retrying with the suggested block range from response.",
|
|
767
|
+
toBlock: attemptedToBlock,
|
|
768
|
+
suggestedToBlock: toBlock$1
|
|
769
|
+
});
|
|
770
|
+
toBlockRef = toBlock$1;
|
|
771
|
+
retryRef = 0;
|
|
772
|
+
}
|
|
736
773
|
break;
|
|
737
774
|
case "WithBackoff" :
|
|
738
|
-
|
|
739
|
-
let log = retry >= 4 ? Logging.childWarn : Logging.childTrace;
|
|
740
|
-
log(logger$2, {
|
|
741
|
-
msg: match$1.message,
|
|
742
|
-
toBlock: attemptedToBlock,
|
|
743
|
-
backOffMilliseconds: backoffMillis,
|
|
744
|
-
retry: retry,
|
|
745
|
-
err: Utils.prettifyExn(exn)
|
|
746
|
-
});
|
|
747
|
-
await backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, undefined, excludedSourcesRef);
|
|
775
|
+
await retryFailedPage(sourceManager, sourceState, retry, isRealtime, logger$2, match$1.message, attemptedToBlock, match$1.backoffMillis, undefined, exn, excludedSourcesRef);
|
|
748
776
|
retryRef = retryRef + 1 | 0;
|
|
749
777
|
break;
|
|
750
778
|
case "ImpossibleForTheQuery" :
|
|
@@ -761,7 +789,7 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
|
761
789
|
Logging.childWarn(logger$2, {
|
|
762
790
|
msg: match$1.message + " - Attempting another source",
|
|
763
791
|
toBlock: attemptedToBlock,
|
|
764
|
-
err: Utils.prettifyExn
|
|
792
|
+
err: Stdlib_Option.map(exn, Utils.prettifyExn)
|
|
765
793
|
});
|
|
766
794
|
retryRef = 0;
|
|
767
795
|
break;
|
|
@@ -773,14 +801,17 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
|
|
|
773
801
|
if (notAlreadyDisabled) {
|
|
774
802
|
switch (error.TAG) {
|
|
775
803
|
case "UnsupportedSelection" :
|
|
776
|
-
|
|
804
|
+
let message = error.message;
|
|
805
|
+
sourceManager.disableReason = message;
|
|
806
|
+
Logging.childError(logger$2, message);
|
|
777
807
|
break;
|
|
778
808
|
case "FailedGettingFieldSelection" :
|
|
809
|
+
let message$1 = error.message;
|
|
810
|
+
sourceManager.disableReason = message$1;
|
|
779
811
|
Logging.childError(logger$2, {
|
|
780
|
-
msg:
|
|
781
|
-
err:
|
|
782
|
-
blockNumber: error.blockNumber
|
|
783
|
-
logIndex: error.logIndex
|
|
812
|
+
msg: message$1,
|
|
813
|
+
err: Stdlib_Option.map(error.exn, Utils.prettifyExn),
|
|
814
|
+
blockNumber: error.blockNumber
|
|
784
815
|
});
|
|
785
816
|
break;
|
|
786
817
|
case "FailedGettingItems" :
|
|
@@ -863,6 +894,8 @@ async function getBlockHashes(sourceManager, blockNumbers, isRealtime) {
|
|
|
863
894
|
return responseRef;
|
|
864
895
|
}
|
|
865
896
|
|
|
897
|
+
let stallRetries = 13;
|
|
898
|
+
|
|
866
899
|
export {
|
|
867
900
|
getSourceRole,
|
|
868
901
|
make,
|
|
@@ -877,6 +910,7 @@ export {
|
|
|
877
910
|
onReorg,
|
|
878
911
|
dispatch,
|
|
879
912
|
waitForNewBlock,
|
|
913
|
+
stallRetries,
|
|
880
914
|
executeQuery,
|
|
881
915
|
makeGetHeightRetryInterval,
|
|
882
916
|
getBlockHashes,
|
|
@@ -74,6 +74,10 @@ let waitForNewBlock: (
|
|
|
74
74
|
~reducedPolling: bool,
|
|
75
75
|
) => promise<int>
|
|
76
76
|
|
|
77
|
+
// The retry at which a query that no source can answer stops being a hiccup
|
|
78
|
+
// and gets reported as a stalled chain.
|
|
79
|
+
let stallRetries: int
|
|
80
|
+
|
|
77
81
|
let executeQuery: (
|
|
78
82
|
t,
|
|
79
83
|
~query: FetchState.query,
|
|
@@ -14,12 +14,15 @@
|
|
|
14
14
|
// sources the chain goes on to index with; those are built later, once
|
|
15
15
|
// registrations exist.
|
|
16
16
|
let makeProbeSources = (chainConfig: Config.chain, ~lowercaseAddresses): array<Source.t> => {
|
|
17
|
-
let addressStore = AddressStore.make(
|
|
18
|
-
|
|
19
|
-
~
|
|
20
|
-
~
|
|
17
|
+
let addressStore = AddressStore.make(~ecosystem=chainConfig.ecosystem, ~contracts=[])
|
|
18
|
+
ChainSources.make(
|
|
19
|
+
~chainConfig,
|
|
20
|
+
~onEventRegistrations=[],
|
|
21
|
+
~addressStore,
|
|
22
|
+
~lowercaseAddresses,
|
|
23
|
+
~blockStore=BlockStore.make(~ecosystem=chainConfig.ecosystem),
|
|
24
|
+
~transactionStore=TransactionStore.make(~ecosystem=chainConfig.ecosystem),
|
|
21
25
|
)
|
|
22
|
-
ChainSources.make(~chainConfig, ~onEventRegistrations=[], ~addressStore, ~lowercaseAddresses)
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
// How long to keep asking a chain that won't answer.
|
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
import * as Utils from "../Utils.res.mjs";
|
|
4
4
|
import * as ChainId from "../ChainId.res.mjs";
|
|
5
5
|
import * as Logging from "../Logging.res.mjs";
|
|
6
|
+
import * as BlockStore from "./BlockStore.res.mjs";
|
|
6
7
|
import * as AddressStore from "./AddressStore.res.mjs";
|
|
7
8
|
import * as ChainSources from "./ChainSources.res.mjs";
|
|
8
9
|
import * as SourceManager from "./SourceManager.res.mjs";
|
|
9
10
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
10
11
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
12
|
+
import * as TransactionStore from "./TransactionStore.res.mjs";
|
|
11
13
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
12
14
|
|
|
13
15
|
function makeProbeSources(chainConfig, lowercaseAddresses) {
|
|
14
|
-
let addressStore = AddressStore.make(chainConfig.ecosystem,
|
|
15
|
-
return ChainSources.make(chainConfig, [], addressStore, lowercaseAddresses);
|
|
16
|
+
let addressStore = AddressStore.make(chainConfig.ecosystem, []);
|
|
17
|
+
return ChainSources.make(chainConfig, [], addressStore, lowercaseAddresses, BlockStore.make(chainConfig.ecosystem), TransactionStore.make(chainConfig.ecosystem));
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
function candidateSources(sources) {
|
|
@@ -158,6 +158,8 @@ let make = (
|
|
|
158
158
|
let totalTimeRef = Performance.now()
|
|
159
159
|
let pageFetchRef = Performance.now()
|
|
160
160
|
|
|
161
|
+
let fetchStats = () => RequestStat.single(~method="getInstructions", ~sentAt=pageFetchRef)
|
|
162
|
+
|
|
161
163
|
let query: SvmHyperSyncClient.EventItems.query = {
|
|
162
164
|
fromSlot: fromBlock,
|
|
163
165
|
toSlot: toBlock,
|
|
@@ -179,6 +181,7 @@ let make = (
|
|
|
179
181
|
throw(
|
|
180
182
|
Source.GetItemsError(
|
|
181
183
|
Source.FailedGettingItems({
|
|
184
|
+
requestStats: fetchStats(),
|
|
182
185
|
exn,
|
|
183
186
|
attemptedToBlock: toBlock->Option.getOr(knownHeight),
|
|
184
187
|
retry: WithBackoff({
|
|
@@ -193,7 +196,7 @@ let make = (
|
|
|
193
196
|
)
|
|
194
197
|
}
|
|
195
198
|
let pageFetchTime = pageFetchRef->Performance.secondsSince
|
|
196
|
-
let requestStats =
|
|
199
|
+
let requestStats = fetchStats()
|
|
197
200
|
|
|
198
201
|
let parsingRef = Performance.now()
|
|
199
202
|
|
|
@@ -244,7 +247,6 @@ let make = (
|
|
|
244
247
|
latestFetchedBlockNumber: highestSlot,
|
|
245
248
|
stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
|
|
246
249
|
knownHeight,
|
|
247
|
-
fromBlockQueried: fromBlock,
|
|
248
250
|
requestStats,
|
|
249
251
|
}
|
|
250
252
|
}
|