envio 3.1.1 → 3.2.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/evm.schema.json +83 -11
- package/fuel.schema.json +83 -11
- package/index.d.ts +184 -3
- package/package.json +6 -6
- package/src/Batch.res +2 -2
- package/src/ChainFetcher.res +27 -3
- package/src/ChainFetcher.res.mjs +17 -3
- package/src/ChainManager.res +163 -0
- package/src/ChainManager.res.mjs +136 -0
- package/src/Config.res +213 -30
- package/src/Config.res.mjs +102 -41
- package/src/Core.res +16 -10
- package/src/Ecosystem.res +0 -3
- package/src/Env.res +2 -2
- package/src/Env.res.mjs +2 -2
- package/src/Envio.res +101 -2
- package/src/Envio.res.mjs +2 -3
- package/src/EventConfigBuilder.res +52 -0
- package/src/EventConfigBuilder.res.mjs +32 -0
- package/src/EventUtils.res +2 -2
- package/src/FetchState.res +126 -71
- package/src/FetchState.res.mjs +73 -51
- package/src/GlobalState.res +219 -363
- package/src/GlobalState.res.mjs +314 -491
- package/src/GlobalStateManager.res +49 -59
- package/src/GlobalStateManager.res.mjs +5 -4
- package/src/GlobalStateManager.resi +1 -1
- package/src/HandlerLoader.res +12 -1
- package/src/HandlerLoader.res.mjs +6 -1
- package/src/HandlerRegister.res +9 -9
- package/src/HandlerRegister.res.mjs +9 -9
- package/src/Hasura.res +102 -32
- package/src/Hasura.res.mjs +88 -34
- package/src/InMemoryStore.res +10 -1
- package/src/InMemoryStore.res.mjs +4 -1
- package/src/InMemoryTable.res +83 -136
- package/src/InMemoryTable.res.mjs +57 -86
- package/src/Internal.res +54 -5
- package/src/Internal.res.mjs +2 -8
- package/src/LazyLoader.res +2 -2
- package/src/LazyLoader.res.mjs +3 -3
- package/src/LoadLayer.res +47 -60
- package/src/LoadLayer.res.mjs +28 -50
- package/src/LoadLayer.resi +2 -5
- package/src/LogSelection.res +4 -4
- package/src/LogSelection.res.mjs +5 -7
- package/src/Logging.res +1 -1
- package/src/Main.res +61 -2
- package/src/Main.res.mjs +37 -1
- package/src/Persistence.res +3 -16
- package/src/PgStorage.res +125 -114
- package/src/PgStorage.res.mjs +112 -95
- package/src/Ports.res +5 -0
- package/src/Ports.res.mjs +9 -0
- package/src/Prometheus.res +3 -3
- package/src/Prometheus.res.mjs +4 -4
- package/src/ReorgDetection.res +4 -4
- package/src/ReorgDetection.res.mjs +4 -5
- package/src/SafeCheckpointTracking.res +16 -16
- package/src/SafeCheckpointTracking.res.mjs +2 -2
- package/src/SimulateItems.res +10 -14
- package/src/SimulateItems.res.mjs +5 -2
- package/src/Sink.res +1 -1
- package/src/Sink.res.mjs +1 -2
- package/src/SvmTypes.res +9 -0
- package/src/SvmTypes.res.mjs +14 -0
- package/src/TestIndexer.res +17 -57
- package/src/TestIndexer.res.mjs +14 -48
- package/src/TestIndexerProxyStorage.res +23 -23
- package/src/TestIndexerProxyStorage.res.mjs +12 -15
- package/src/Throttler.res +2 -2
- package/src/Time.res +2 -2
- package/src/Time.res.mjs +2 -2
- package/src/UserContext.res +19 -118
- package/src/UserContext.res.mjs +10 -66
- package/src/Utils.res +15 -15
- package/src/Utils.res.mjs +7 -8
- package/src/adapters/MarkBatchProcessedAdapter.res +5 -0
- package/src/adapters/MarkBatchProcessedAdapter.res.mjs +14 -0
- package/src/bindings/BigDecimal.res +1 -1
- package/src/bindings/BigDecimal.res.mjs +2 -2
- package/src/bindings/ClickHouse.res +8 -6
- package/src/bindings/ClickHouse.res.mjs +5 -5
- package/src/bindings/Hrtime.res +1 -1
- package/src/bindings/Pino.res +2 -2
- package/src/bindings/Pino.res.mjs +3 -4
- package/src/db/EntityFilter.res +410 -0
- package/src/db/EntityFilter.res.mjs +424 -0
- package/src/db/EntityHistory.res +1 -1
- package/src/db/EntityHistory.res.mjs +1 -1
- package/src/db/InternalTable.res +10 -10
- package/src/db/InternalTable.res.mjs +41 -45
- package/src/db/Schema.res +2 -2
- package/src/db/Schema.res.mjs +3 -3
- package/src/db/Table.res +106 -22
- package/src/db/Table.res.mjs +84 -35
- package/src/sources/EventRouter.res +67 -2
- package/src/sources/EventRouter.res.mjs +45 -3
- package/src/sources/Evm.res +0 -7
- package/src/sources/Evm.res.mjs +0 -15
- package/src/sources/EvmChain.res +1 -1
- package/src/sources/EvmChain.res.mjs +1 -2
- package/src/sources/EvmRpcClient.res +42 -0
- package/src/sources/EvmRpcClient.res.mjs +64 -0
- package/src/sources/Fuel.res +0 -7
- package/src/sources/Fuel.res.mjs +0 -15
- package/src/sources/HyperFuelSource.res +5 -4
- package/src/sources/HyperFuelSource.res.mjs +2 -2
- package/src/sources/HyperSyncClient.res +9 -5
- package/src/sources/HyperSyncClient.res.mjs +2 -2
- package/src/sources/HyperSyncHeightStream.res +2 -2
- package/src/sources/HyperSyncHeightStream.res.mjs +2 -2
- package/src/sources/HyperSyncSource.res +10 -9
- package/src/sources/HyperSyncSource.res.mjs +4 -4
- package/src/sources/Rpc.res +1 -5
- package/src/sources/Rpc.res.mjs +1 -9
- package/src/sources/RpcSource.res +57 -21
- package/src/sources/RpcSource.res.mjs +47 -20
- package/src/sources/RpcWebSocketHeightStream.res +1 -1
- package/src/sources/SourceManager.res +3 -2
- package/src/sources/SourceManager.res.mjs +1 -1
- package/src/sources/Svm.res +3 -10
- package/src/sources/Svm.res.mjs +4 -18
- package/src/sources/SvmHyperSyncClient.res +265 -0
- package/src/sources/SvmHyperSyncClient.res.mjs +28 -0
- package/src/sources/SvmHyperSyncSource.res +638 -0
- package/src/sources/SvmHyperSyncSource.res.mjs +557 -0
- package/src/tui/Tui.res +9 -2
- package/src/tui/Tui.res.mjs +18 -3
- package/src/tui/components/BufferedProgressBar.res +2 -2
- package/src/tui/components/TuiData.res +3 -0
- package/svm.schema.json +523 -14
- package/src/TableIndices.res +0 -115
- package/src/TableIndices.res.mjs +0 -144
|
@@ -2,6 +2,12 @@ open Source
|
|
|
2
2
|
|
|
3
3
|
exception QueryTimout(string)
|
|
4
4
|
|
|
5
|
+
// eth_getTransactionByHash/eth_getTransactionReceipt returning null is usually
|
|
6
|
+
// transient: a load-balanced provider can route the lookup to a node that
|
|
7
|
+
// hasn't caught up with the one that served eth_getLogs. Must stay retryable,
|
|
8
|
+
// unlike other field-selection failures which disable the source.
|
|
9
|
+
exception TransactionDataNotFound({message: string})
|
|
10
|
+
|
|
5
11
|
// Minimal block data needed for infrastructure (reorg guard, timestamps, etc.)
|
|
6
12
|
type blockInfo = {
|
|
7
13
|
number: int,
|
|
@@ -14,7 +20,7 @@ let getKnownRawBlock = async (~client, ~blockNumber) =>
|
|
|
14
20
|
switch await Rpc.getRawBlock(~client, ~blockNumber) {
|
|
15
21
|
| Some(json) => json
|
|
16
22
|
| None =>
|
|
17
|
-
JsError.throwWithMessage(`RPC returned null for blockNumber ${blockNumber->
|
|
23
|
+
JsError.throwWithMessage(`RPC returned null for blockNumber ${blockNumber->Int.toString}`)
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
// Extract infrastructure fields (number, timestamp, hash) from raw block JSON
|
|
@@ -56,7 +62,7 @@ let getKnownRawBlockWithBackoff = async (
|
|
|
56
62
|
| exception err =>
|
|
57
63
|
Logging.warn({
|
|
58
64
|
"err": err->Utils.prettifyExn,
|
|
59
|
-
"msg": `Issue while running fetching batch of events from the RPC. Will wait ${currentBackoff.contents->
|
|
65
|
+
"msg": `Issue while running fetching batch of events from the RPC. Will wait ${currentBackoff.contents->Int.toString}ms and try again.`,
|
|
60
66
|
"source": sourceName,
|
|
61
67
|
"chainId": chain->ChainMap.Chain.toChainId,
|
|
62
68
|
"type": "EXPONENTIAL_BACKOFF",
|
|
@@ -241,9 +247,7 @@ let getNextPage = (
|
|
|
241
247
|
let queryTimoutPromise =
|
|
242
248
|
Time.resolvePromiseAfterDelay(~delayMilliseconds=sc.queryTimeoutMillis)->Promise.then(() =>
|
|
243
249
|
Promise.reject(
|
|
244
|
-
QueryTimout(
|
|
245
|
-
`Query took longer than ${Belt.Int.toString(sc.queryTimeoutMillis / 1000)} seconds`,
|
|
246
|
-
),
|
|
250
|
+
QueryTimout(`Query took longer than ${Int.toString(sc.queryTimeoutMillis / 1000)} seconds`),
|
|
247
251
|
)
|
|
248
252
|
)
|
|
249
253
|
|
|
@@ -287,7 +291,7 @@ let getNextPage = (
|
|
|
287
291
|
| None =>
|
|
288
292
|
let executedBlockInterval = toBlock - fromBlock + 1
|
|
289
293
|
let nextBlockIntervalTry =
|
|
290
|
-
(executedBlockInterval->
|
|
294
|
+
(executedBlockInterval->Int.toFloat *. sc.backoffMultiplicative)->Float.toInt
|
|
291
295
|
mutSuggestedBlockIntervals->Dict.set(partitionId, nextBlockIntervalTry)
|
|
292
296
|
throw(
|
|
293
297
|
Source.GetItemsError(
|
|
@@ -320,7 +324,7 @@ let getSelectionConfig = (selection: FetchState.selection, ~chain) => {
|
|
|
320
324
|
|
|
321
325
|
selection.eventConfigs
|
|
322
326
|
->(Utils.magic: array<Internal.eventConfig> => array<Internal.evmEventConfig>)
|
|
323
|
-
->
|
|
327
|
+
->Array.forEach(({getEventFiltersOrThrow}) => {
|
|
324
328
|
switch getEventFiltersOrThrow(chain) {
|
|
325
329
|
| Static(s) => staticTopicSelections->Array.pushMany(s)->ignore
|
|
326
330
|
| Dynamic(fn) => dynamicEventFilters->Array.push(fn)->ignore
|
|
@@ -527,6 +531,12 @@ let makeThrowingGetEventBlock = (
|
|
|
527
531
|
}
|
|
528
532
|
}
|
|
529
533
|
|
|
534
|
+
// `number`, `timestamp` and `hash` are always part of the selected block
|
|
535
|
+
// fields, so they can be read from the assembled block at item construction.
|
|
536
|
+
@get external getBlockNumber: Internal.eventBlock => int = "number"
|
|
537
|
+
@get external getBlockTimestamp: Internal.eventBlock => int = "timestamp"
|
|
538
|
+
@get external getBlockHash: Internal.eventBlock => string = "hash"
|
|
539
|
+
|
|
530
540
|
// Field source classification for RPC calls
|
|
531
541
|
type fieldSource = TransactionOnly | ReceiptOnly | Both
|
|
532
542
|
|
|
@@ -863,7 +873,7 @@ let make = (
|
|
|
863
873
|
let urlHost = switch Utils.Url.getHostFromUrl(url) {
|
|
864
874
|
| None =>
|
|
865
875
|
JsError.throwWithMessage(
|
|
866
|
-
`The RPC url for chain ${chainId->
|
|
876
|
+
`The RPC url for chain ${chainId->Int.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`,
|
|
867
877
|
)
|
|
868
878
|
| Some(host) => host
|
|
869
879
|
}
|
|
@@ -874,6 +884,7 @@ let make = (
|
|
|
874
884
|
let mutSuggestedBlockIntervals = Dict.make()
|
|
875
885
|
|
|
876
886
|
let client = Rpc.makeClient(url)
|
|
887
|
+
let rpcClient = EvmRpcClient.make(~url)
|
|
877
888
|
|
|
878
889
|
let makeTransactionLoader = () =>
|
|
879
890
|
LazyLoader.make(
|
|
@@ -889,7 +900,7 @@ let make = (
|
|
|
889
900
|
Logging.error({
|
|
890
901
|
"err": exn->Utils.prettifyExn,
|
|
891
902
|
"msg": `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ${(am._retryDelayMillis / 1000)
|
|
892
|
-
->
|
|
903
|
+
->Int.toString} seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`,
|
|
893
904
|
"source": name,
|
|
894
905
|
"chainId": chain->ChainMap.Chain.toChainId,
|
|
895
906
|
"metadata": {
|
|
@@ -917,7 +928,7 @@ let make = (
|
|
|
917
928
|
Logging.error({
|
|
918
929
|
"err": exn->Utils.prettifyExn,
|
|
919
930
|
"msg": `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ${(am._retryDelayMillis / 1000)
|
|
920
|
-
->
|
|
931
|
+
->Int.toString} seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`,
|
|
921
932
|
"source": name,
|
|
922
933
|
"chainId": chain->ChainMap.Chain.toChainId,
|
|
923
934
|
"metadata": {
|
|
@@ -944,7 +955,7 @@ let make = (
|
|
|
944
955
|
Logging.error({
|
|
945
956
|
"err": exn->Utils.prettifyExn,
|
|
946
957
|
"msg": `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ${(am._retryDelayMillis / 1000)
|
|
947
|
-
->
|
|
958
|
+
->Int.toString} seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`,
|
|
948
959
|
"source": name,
|
|
949
960
|
"chainId": chain->ChainMap.Chain.toChainId,
|
|
950
961
|
"metadata": {
|
|
@@ -969,14 +980,21 @@ let make = (
|
|
|
969
980
|
~getTransactionJson=async transactionHash => {
|
|
970
981
|
switch await transactionLoader.contents->LazyLoader.get(transactionHash) {
|
|
971
982
|
| Some(json) => json
|
|
972
|
-
| None =>
|
|
983
|
+
| None =>
|
|
984
|
+
throw(
|
|
985
|
+
TransactionDataNotFound({message: `Transaction not found for hash: ${transactionHash}`}),
|
|
986
|
+
)
|
|
973
987
|
}
|
|
974
988
|
},
|
|
975
989
|
~getReceiptJson=async transactionHash => {
|
|
976
990
|
switch await receiptLoader.contents->LazyLoader.get(transactionHash) {
|
|
977
991
|
| Some(json) => json
|
|
978
992
|
| None =>
|
|
979
|
-
|
|
993
|
+
throw(
|
|
994
|
+
TransactionDataNotFound({
|
|
995
|
+
message: `Transaction receipt not found for hash: ${transactionHash}`,
|
|
996
|
+
}),
|
|
997
|
+
)
|
|
980
998
|
}
|
|
981
999
|
},
|
|
982
1000
|
~lowercaseAddresses,
|
|
@@ -1020,7 +1038,7 @@ let make = (
|
|
|
1020
1038
|
~knownHeight,
|
|
1021
1039
|
~partitionId,
|
|
1022
1040
|
~selection: FetchState.selection,
|
|
1023
|
-
~retry
|
|
1041
|
+
~retry,
|
|
1024
1042
|
~logger as _,
|
|
1025
1043
|
) => {
|
|
1026
1044
|
let startFetchingBatchTimeRef = Hrtime.makeTimer()
|
|
@@ -1032,7 +1050,7 @@ let make = (
|
|
|
1032
1050
|
| None =>
|
|
1033
1051
|
mutSuggestedBlockIntervals
|
|
1034
1052
|
->Utils.Dict.dangerouslyGetNonOption(partitionId)
|
|
1035
|
-
->
|
|
1053
|
+
->Option.getOr(syncConfig.initialBlockInterval)
|
|
1036
1054
|
}
|
|
1037
1055
|
|
|
1038
1056
|
// Always have a toBlock for an RPC worker
|
|
@@ -1093,7 +1111,7 @@ let make = (
|
|
|
1093
1111
|
}
|
|
1094
1112
|
|
|
1095
1113
|
// Convert RPC logs to HyperSync events
|
|
1096
|
-
let hyperSyncEvents = logs->
|
|
1114
|
+
let hyperSyncEvents = logs->Array.map(convertLogToHyperSyncEvent)
|
|
1097
1115
|
|
|
1098
1116
|
// Decode using HyperSyncClient decoder
|
|
1099
1117
|
let parsedEvents = try await getHscDecoder().decodeLogs(hyperSyncEvents) catch {
|
|
@@ -1145,6 +1163,23 @@ let make = (
|
|
|
1145
1163
|
~selectedTransactionFields=eventConfig.selectedTransactionFields,
|
|
1146
1164
|
),
|
|
1147
1165
|
)) catch {
|
|
1166
|
+
| TransactionDataNotFound({message}) =>
|
|
1167
|
+
let backoffMillis = switch retry {
|
|
1168
|
+
| 0 => 100
|
|
1169
|
+
| _ => 500 * retry
|
|
1170
|
+
}
|
|
1171
|
+
throw(
|
|
1172
|
+
Source.GetItemsError(
|
|
1173
|
+
FailedGettingItems({
|
|
1174
|
+
exn: %raw(`null`),
|
|
1175
|
+
attemptedToBlock: toBlock,
|
|
1176
|
+
retry: WithBackoff({
|
|
1177
|
+
message: `${message}. The RPC provider might be load-balanced between nodes that drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${backoffMillis->Int.toString}ms.`,
|
|
1178
|
+
backoffMillis,
|
|
1179
|
+
}),
|
|
1180
|
+
}),
|
|
1181
|
+
),
|
|
1182
|
+
)
|
|
1148
1183
|
| exn =>
|
|
1149
1184
|
throw(
|
|
1150
1185
|
Source.GetItemsError(
|
|
@@ -1160,8 +1195,9 @@ let make = (
|
|
|
1160
1195
|
|
|
1161
1196
|
Internal.Event({
|
|
1162
1197
|
eventConfig: (eventConfig :> Internal.eventConfig),
|
|
1163
|
-
timestamp: block->
|
|
1164
|
-
blockNumber: block->
|
|
1198
|
+
timestamp: block->getBlockTimestamp,
|
|
1199
|
+
blockNumber: block->getBlockNumber,
|
|
1200
|
+
blockHash: block->getBlockHash,
|
|
1165
1201
|
chain,
|
|
1166
1202
|
logIndex: log.logIndex,
|
|
1167
1203
|
event: {
|
|
@@ -1204,7 +1240,7 @@ let make = (
|
|
|
1204
1240
|
| Some(b) => pushBlockInfo(b)
|
|
1205
1241
|
| None => ()
|
|
1206
1242
|
}
|
|
1207
|
-
logs->
|
|
1243
|
+
logs->Array.forEach(log =>
|
|
1208
1244
|
blockHashes
|
|
1209
1245
|
->Array.push({ReorgDetection.blockNumber: log.blockNumber, blockHash: log.blockHash})
|
|
1210
1246
|
->ignore
|
|
@@ -1254,7 +1290,7 @@ let make = (
|
|
|
1254
1290
|
}
|
|
1255
1291
|
|
|
1256
1292
|
let createHeightSubscription =
|
|
1257
|
-
ws->
|
|
1293
|
+
ws->Option.map(wsUrl =>
|
|
1258
1294
|
(~onHeight) => RpcWebSocketHeightStream.subscribe(~wsUrl, ~chainId, ~onHeight)
|
|
1259
1295
|
)
|
|
1260
1296
|
|
|
@@ -1269,7 +1305,7 @@ let make = (
|
|
|
1269
1305
|
getHeightOrThrow: async () => {
|
|
1270
1306
|
let timerRef = Hrtime.makeTimer()
|
|
1271
1307
|
let height = try {
|
|
1272
|
-
await
|
|
1308
|
+
await rpcClient.getHeight()
|
|
1273
1309
|
} catch {
|
|
1274
1310
|
| exn =>
|
|
1275
1311
|
let seconds = timerRef->Hrtime.timeSince->Hrtime.toSecondsFloat
|
|
@@ -9,13 +9,12 @@ import * as Source from "./Source.res.mjs";
|
|
|
9
9
|
import * as Address from "../Address.res.mjs";
|
|
10
10
|
import * as Logging from "../Logging.res.mjs";
|
|
11
11
|
import * as Internal from "../Internal.res.mjs";
|
|
12
|
-
import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.js";
|
|
13
12
|
import * as FetchState from "../FetchState.res.mjs";
|
|
14
13
|
import * as LazyLoader from "../LazyLoader.res.mjs";
|
|
15
14
|
import * as Prometheus from "../Prometheus.res.mjs";
|
|
16
15
|
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
17
|
-
import * as Belt_Option from "@rescript/runtime/lib/es6/Belt_Option.js";
|
|
18
16
|
import * as EventRouter from "./EventRouter.res.mjs";
|
|
17
|
+
import * as EvmRpcClient from "./EvmRpcClient.res.mjs";
|
|
19
18
|
import * as LogSelection from "../LogSelection.res.mjs";
|
|
20
19
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
21
20
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
@@ -30,12 +29,14 @@ import * as RpcWebSocketHeightStream from "./RpcWebSocketHeightStream.res.mjs";
|
|
|
30
29
|
|
|
31
30
|
let QueryTimout = /* @__PURE__ */Primitive_exceptions.create("RpcSource.QueryTimout");
|
|
32
31
|
|
|
32
|
+
let TransactionDataNotFound = /* @__PURE__ */Primitive_exceptions.create("RpcSource.TransactionDataNotFound");
|
|
33
|
+
|
|
33
34
|
async function getKnownRawBlock(client, blockNumber) {
|
|
34
35
|
let json = await Rpc.getRawBlock(client, blockNumber);
|
|
35
36
|
if (json !== undefined) {
|
|
36
37
|
return json;
|
|
37
38
|
} else {
|
|
38
|
-
return Stdlib_JsError.throwWithMessage(`RPC returned null for blockNumber ` +
|
|
39
|
+
return Stdlib_JsError.throwWithMessage(`RPC returned null for blockNumber ` + blockNumber.toString());
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -62,7 +63,7 @@ async function getKnownRawBlockWithBackoff(client, sourceName, chain, blockNumbe
|
|
|
62
63
|
let err = Primitive_exceptions.internalToException(raw_err);
|
|
63
64
|
Logging.warn({
|
|
64
65
|
err: Utils.prettifyExn(err),
|
|
65
|
-
msg: `Issue while running fetching batch of events from the RPC. Will wait ` +
|
|
66
|
+
msg: `Issue while running fetching batch of events from the RPC. Will wait ` + currentBackoff.toString() + `ms and try again.`,
|
|
66
67
|
source: sourceName,
|
|
67
68
|
chainId: chain,
|
|
68
69
|
type: "EXPONENTIAL_BACKOFF"
|
|
@@ -223,7 +224,7 @@ let maxSuggestedBlockIntervalKey = "max";
|
|
|
223
224
|
function getNextPage(fromBlock, toBlock, addresses, topicQuery, loadBlock, sc, client, mutSuggestedBlockIntervals, partitionId, sourceName, chainId) {
|
|
224
225
|
let queryTimoutPromise = Time.resolvePromiseAfterDelay(sc.queryTimeoutMillis).then(() => Promise.reject({
|
|
225
226
|
RE_EXN_ID: QueryTimout,
|
|
226
|
-
_1: `Query took longer than ` +
|
|
227
|
+
_1: `Query took longer than ` + (sc.queryTimeoutMillis / 1000 | 0).toString() + ` seconds`
|
|
227
228
|
}));
|
|
228
229
|
let latestFetchedBlockPromise = loadBlock(toBlock);
|
|
229
230
|
Prometheus.SourceRequestCount.increment(sourceName, chainId, "eth_getLogs");
|
|
@@ -281,7 +282,7 @@ function getNextPage(fromBlock, toBlock, addresses, topicQuery, loadBlock, sc, c
|
|
|
281
282
|
function getSelectionConfig(selection, chain) {
|
|
282
283
|
let staticTopicSelections = [];
|
|
283
284
|
let dynamicEventFilters = [];
|
|
284
|
-
|
|
285
|
+
selection.eventConfigs.forEach(param => {
|
|
285
286
|
let s = param.getEventFiltersOrThrow(chain);
|
|
286
287
|
if (s.TAG === "Static") {
|
|
287
288
|
staticTopicSelections.push(...s._0);
|
|
@@ -924,17 +925,18 @@ function make(param) {
|
|
|
924
925
|
let url = param.url;
|
|
925
926
|
let syncConfig = param.syncConfig;
|
|
926
927
|
let host = Utils.Url.getHostFromUrl(url);
|
|
927
|
-
let urlHost = host !== undefined ? host : Stdlib_JsError.throwWithMessage(`The RPC url for chain ` +
|
|
928
|
+
let urlHost = host !== undefined ? host : Stdlib_JsError.throwWithMessage(`The RPC url for chain ` + chain.toString() + ` is in incorrect format. The RPC url needs to start with either http:// or https://`);
|
|
928
929
|
let name = `RPC (` + urlHost + `)`;
|
|
929
930
|
let getSelectionConfig = memoGetSelectionConfig(chain);
|
|
930
931
|
let mutSuggestedBlockIntervals = {};
|
|
931
932
|
let client = Rpc.makeClient(url);
|
|
933
|
+
let rpcClient = EvmRpcClient.make(url, undefined);
|
|
932
934
|
let makeTransactionLoader = () => LazyLoader.make(transactionHash => {
|
|
933
935
|
Prometheus.SourceRequestCount.increment(name, chain, "eth_getTransactionByHash");
|
|
934
936
|
return Rest.fetch(Rpc.GetTransactionByHash.rawRoute, transactionHash, client);
|
|
935
937
|
}, (am, exn) => Logging.error({
|
|
936
938
|
err: Utils.prettifyExn(exn),
|
|
937
|
-
msg: `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ` +
|
|
939
|
+
msg: `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ` + (am._retryDelayMillis / 1000 | 0).toString() + ` seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`,
|
|
938
940
|
source: name,
|
|
939
941
|
chainId: chain,
|
|
940
942
|
metadata: {
|
|
@@ -944,7 +946,7 @@ function make(param) {
|
|
|
944
946
|
}), undefined, undefined, undefined, undefined);
|
|
945
947
|
let makeBlockLoader = () => LazyLoader.make(blockNumber => getKnownRawBlockWithBackoff(client, name, chain, blockNumber, 1000), (am, exn) => Logging.error({
|
|
946
948
|
err: Utils.prettifyExn(exn),
|
|
947
|
-
msg: `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ` +
|
|
949
|
+
msg: `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ` + (am._retryDelayMillis / 1000 | 0).toString() + ` seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`,
|
|
948
950
|
source: name,
|
|
949
951
|
chainId: chain,
|
|
950
952
|
metadata: {
|
|
@@ -957,7 +959,7 @@ function make(param) {
|
|
|
957
959
|
return Rest.fetch(Rpc.GetTransactionReceipt.rawRoute, transactionHash, client);
|
|
958
960
|
}, (am, exn) => Logging.error({
|
|
959
961
|
err: Utils.prettifyExn(exn),
|
|
960
|
-
msg: `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ` +
|
|
962
|
+
msg: `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ` + (am._retryDelayMillis / 1000 | 0).toString() + ` seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`,
|
|
961
963
|
source: name,
|
|
962
964
|
chainId: chain,
|
|
963
965
|
metadata: {
|
|
@@ -979,16 +981,22 @@ function make(param) {
|
|
|
979
981
|
let json = await LazyLoader.get(transactionLoader.contents, transactionHash);
|
|
980
982
|
if (json !== undefined) {
|
|
981
983
|
return json;
|
|
982
|
-
} else {
|
|
983
|
-
return Stdlib_JsError.throwWithMessage(`Transaction not found for hash: ` + transactionHash);
|
|
984
984
|
}
|
|
985
|
+
throw {
|
|
986
|
+
RE_EXN_ID: TransactionDataNotFound,
|
|
987
|
+
message: `Transaction not found for hash: ` + transactionHash,
|
|
988
|
+
Error: new Error()
|
|
989
|
+
};
|
|
985
990
|
}, async transactionHash => {
|
|
986
991
|
let json = await LazyLoader.get(receiptLoader.contents, transactionHash);
|
|
987
992
|
if (json !== undefined) {
|
|
988
993
|
return json;
|
|
989
|
-
} else {
|
|
990
|
-
return Stdlib_JsError.throwWithMessage(`Transaction receipt not found for hash: ` + transactionHash);
|
|
991
994
|
}
|
|
995
|
+
throw {
|
|
996
|
+
RE_EXN_ID: TransactionDataNotFound,
|
|
997
|
+
message: `Transaction receipt not found for hash: ` + transactionHash,
|
|
998
|
+
Error: new Error()
|
|
999
|
+
};
|
|
992
1000
|
}, lowercaseAddresses);
|
|
993
1001
|
let convertLogToHyperSyncEvent = log => {
|
|
994
1002
|
let hyperSyncLog_removed = log.removed;
|
|
@@ -1027,10 +1035,10 @@ function make(param) {
|
|
|
1027
1035
|
hscDecoder.contents = decoder$1;
|
|
1028
1036
|
return decoder$1;
|
|
1029
1037
|
};
|
|
1030
|
-
let getItemsOrThrow = async (fromBlock, toBlock, addressesByContractName, indexingAddresses, knownHeight, partitionId, selection,
|
|
1038
|
+
let getItemsOrThrow = async (fromBlock, toBlock, addressesByContractName, indexingAddresses, knownHeight, partitionId, selection, retry, param) => {
|
|
1031
1039
|
let startFetchingBatchTimeRef = Hrtime.makeTimer();
|
|
1032
1040
|
let maxSuggestedBlockInterval = mutSuggestedBlockIntervals[maxSuggestedBlockIntervalKey];
|
|
1033
|
-
let suggestedBlockInterval = maxSuggestedBlockInterval !== undefined ? maxSuggestedBlockInterval :
|
|
1041
|
+
let suggestedBlockInterval = maxSuggestedBlockInterval !== undefined ? maxSuggestedBlockInterval : Stdlib_Option.getOr(mutSuggestedBlockIntervals[partitionId], syncConfig.initialBlockInterval);
|
|
1034
1042
|
let toBlock$1 = toBlock !== undefined ? Primitive_int.min(toBlock, knownHeight) : knownHeight;
|
|
1035
1043
|
let suggestedToBlock = Primitive_int.max(Primitive_int.min((fromBlock + suggestedBlockInterval | 0) - 1 | 0, toBlock$1), fromBlock);
|
|
1036
1044
|
let firstBlockParentPromise = fromBlock > 0 ? LazyLoader.get(blockLoader.contents, fromBlock - 1 | 0).then(json => parseBlockInfo(json)) : Promise.resolve(undefined);
|
|
@@ -1043,7 +1051,7 @@ function make(param) {
|
|
|
1043
1051
|
if (executedBlockInterval >= suggestedBlockInterval && !(maxSuggestedBlockIntervalKey in mutSuggestedBlockIntervals)) {
|
|
1044
1052
|
mutSuggestedBlockIntervals[partitionId] = Primitive_int.min(executedBlockInterval + syncConfig.accelerationAdditive | 0, syncConfig.intervalCeiling);
|
|
1045
1053
|
}
|
|
1046
|
-
let hyperSyncEvents =
|
|
1054
|
+
let hyperSyncEvents = logs.map(convertLogToHyperSyncEvent);
|
|
1047
1055
|
let parsedEvents;
|
|
1048
1056
|
try {
|
|
1049
1057
|
parsedEvents = await getHscDecoder().decodeLogs(hyperSyncEvents);
|
|
@@ -1085,6 +1093,23 @@ function make(param) {
|
|
|
1085
1093
|
]);
|
|
1086
1094
|
} catch (raw_exn) {
|
|
1087
1095
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
1096
|
+
if (exn.RE_EXN_ID === TransactionDataNotFound) {
|
|
1097
|
+
let backoffMillis = retry !== 0 ? 500 * retry | 0 : 100;
|
|
1098
|
+
throw {
|
|
1099
|
+
RE_EXN_ID: Source.GetItemsError,
|
|
1100
|
+
_1: {
|
|
1101
|
+
TAG: "FailedGettingItems",
|
|
1102
|
+
exn: null,
|
|
1103
|
+
attemptedToBlock: toBlock$1,
|
|
1104
|
+
retry: {
|
|
1105
|
+
TAG: "WithBackoff",
|
|
1106
|
+
message: exn.message + `. The RPC provider might be load-balanced between nodes that drift independently slightly from the head. Indexing should continue correctly after retrying the query in ` + backoffMillis.toString() + `ms.`,
|
|
1107
|
+
backoffMillis: backoffMillis
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
Error: new Error()
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1088
1113
|
throw {
|
|
1089
1114
|
RE_EXN_ID: Source.GetItemsError,
|
|
1090
1115
|
_1: {
|
|
@@ -1104,6 +1129,7 @@ function make(param) {
|
|
|
1104
1129
|
timestamp: block.timestamp,
|
|
1105
1130
|
chain: chain,
|
|
1106
1131
|
blockNumber: block.number,
|
|
1132
|
+
blockHash: block.hash,
|
|
1107
1133
|
logIndex: log.logIndex,
|
|
1108
1134
|
event: {
|
|
1109
1135
|
contractName: eventConfig.contractName,
|
|
@@ -1138,7 +1164,7 @@ function make(param) {
|
|
|
1138
1164
|
if (optFirstBlockParent !== undefined) {
|
|
1139
1165
|
pushBlockInfo(optFirstBlockParent);
|
|
1140
1166
|
}
|
|
1141
|
-
|
|
1167
|
+
logs.forEach(log => {
|
|
1142
1168
|
blockHashes.push({
|
|
1143
1169
|
blockHash: log.blockHash,
|
|
1144
1170
|
blockNumber: log.blockNumber
|
|
@@ -1175,7 +1201,7 @@ function make(param) {
|
|
|
1175
1201
|
TAG: "Error",
|
|
1176
1202
|
_0: exn
|
|
1177
1203
|
}));
|
|
1178
|
-
let createHeightSubscription =
|
|
1204
|
+
let createHeightSubscription = Stdlib_Option.map(param.ws, wsUrl => (onHeight => RpcWebSocketHeightStream.subscribe(wsUrl, chain, onHeight)));
|
|
1179
1205
|
return {
|
|
1180
1206
|
name: name,
|
|
1181
1207
|
sourceFor: param.sourceFor,
|
|
@@ -1187,7 +1213,7 @@ function make(param) {
|
|
|
1187
1213
|
let timerRef = Hrtime.makeTimer();
|
|
1188
1214
|
let height;
|
|
1189
1215
|
try {
|
|
1190
|
-
height = await
|
|
1216
|
+
height = await rpcClient.getHeight();
|
|
1191
1217
|
} catch (exn) {
|
|
1192
1218
|
let seconds = Hrtime.toSecondsFloat(Hrtime.timeSince(timerRef));
|
|
1193
1219
|
Prometheus.SourceRequestCount.increment(name, chain, "eth_blockNumber");
|
|
@@ -1207,6 +1233,7 @@ function make(param) {
|
|
|
1207
1233
|
|
|
1208
1234
|
export {
|
|
1209
1235
|
QueryTimout,
|
|
1236
|
+
TransactionDataNotFound,
|
|
1210
1237
|
getKnownRawBlock,
|
|
1211
1238
|
parseBlockInfo,
|
|
1212
1239
|
getKnownRawBlockWithBackoff,
|
|
@@ -82,7 +82,7 @@ let subscribe = (~wsUrl, ~chainId, ~onHeight: int => unit): (unit => unit) => {
|
|
|
82
82
|
let rec scheduleReconnect = () => {
|
|
83
83
|
if !isUnsubscribed.contents && errorCount.contents < retryCount {
|
|
84
84
|
let duration =
|
|
85
|
-
baseDuration * Math.pow(2.0, ~exp=errorCount.contents->
|
|
85
|
+
baseDuration * Math.pow(2.0, ~exp=errorCount.contents->Int.toFloat)->Float.toInt
|
|
86
86
|
let _ = setTimeout(() => {
|
|
87
87
|
if !isUnsubscribed.contents {
|
|
88
88
|
startConnection()
|
|
@@ -338,7 +338,7 @@ let getSourceNewHeight = async (
|
|
|
338
338
|
// that go quiet together don't all start polling at the same instant.
|
|
339
339
|
let half = stallTimeout / 2
|
|
340
340
|
let pollingFallback = Utils.delay(
|
|
341
|
-
half + (Math.random() *. half->
|
|
341
|
+
half + (Math.random() *. half->Int.toFloat)->Float.toInt,
|
|
342
342
|
)->Promise.then(async () => {
|
|
343
343
|
logger->Logging.childTrace({
|
|
344
344
|
"msg": "onHeight subscription stale, switching to polling fallback",
|
|
@@ -546,10 +546,11 @@ let waitForNewBlock = async (sourceManager: t, ~knownHeight, ~isRealtime, ~reduc
|
|
|
546
546
|
})
|
|
547
547
|
->Array.concat([
|
|
548
548
|
Utils.delay(stallTimeout)->Promise.then(() => {
|
|
549
|
-
// Build fallback: sources not in mainSources with a valid role, even with recent lastFailedAt
|
|
549
|
+
// Build fallback: non-disabled sources not in mainSources with a valid role, even with recent lastFailedAt
|
|
550
550
|
let fallbackSources = []
|
|
551
551
|
sourcesState->Array.forEach(sourceState => {
|
|
552
552
|
if (
|
|
553
|
+
!sourceState.disabled &&
|
|
553
554
|
!(mainSources->Array.includes(sourceState)) &&
|
|
554
555
|
getSourceRole(
|
|
555
556
|
~sourceFor=sourceState.source.sourceFor,
|
|
@@ -429,7 +429,7 @@ async function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPo
|
|
|
429
429
|
]).concat([Utils.delay(stallTimeout).then(() => {
|
|
430
430
|
let fallbackSources = [];
|
|
431
431
|
sourcesState.forEach(sourceState => {
|
|
432
|
-
if (!mainSources.includes(sourceState) && Stdlib_Option.isSome(getSourceRole(sourceState.source.sourceFor, isRealtime, sourceManager.hasRealtime))) {
|
|
432
|
+
if (!sourceState.disabled && !mainSources.includes(sourceState) && Stdlib_Option.isSome(getSourceRole(sourceState.source.sourceFor, isRealtime, sourceManager.hasRealtime))) {
|
|
433
433
|
fallbackSources.push(sourceState);
|
|
434
434
|
return;
|
|
435
435
|
}
|
package/src/sources/Svm.res
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
@get external getNumber: Internal.eventBlock => int = "height"
|
|
2
|
-
@get external getTimestamp: Internal.eventBlock => int = "time"
|
|
3
|
-
@get external getId: Internal.eventBlock => string = "hash"
|
|
4
|
-
|
|
5
1
|
let cleanUpRawEventFieldsInPlace: JSON.t => unit = %raw(`fields => {
|
|
6
2
|
delete fields.hash
|
|
7
3
|
delete fields.height
|
|
@@ -15,9 +11,6 @@ let ecosystem: Ecosystem.t = {
|
|
|
15
11
|
blockNumberName: "height",
|
|
16
12
|
blockTimestampName: "time",
|
|
17
13
|
blockHashName: "hash",
|
|
18
|
-
getNumber,
|
|
19
|
-
getTimestamp,
|
|
20
|
-
getId,
|
|
21
14
|
cleanUpRawEventFieldsInPlace,
|
|
22
15
|
onBlockMethodName: "onSlot",
|
|
23
16
|
// SVM filter shape: `{slot: {_gte?, _lte?, _every?}}`.
|
|
@@ -39,14 +32,14 @@ module GetFinalizedSlot = {
|
|
|
39
32
|
)
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
let makeRPCSource = (~chain, ~rpc: string): Source.t => {
|
|
35
|
+
let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): Source.t => {
|
|
43
36
|
let client = Rest.client(rpc)
|
|
44
37
|
let chainId = chain->ChainMap.Chain.toChainId
|
|
45
38
|
|
|
46
39
|
let urlHost = switch Utils.Url.getHostFromUrl(rpc) {
|
|
47
40
|
| None =>
|
|
48
41
|
JsError.throwWithMessage(
|
|
49
|
-
`The RPC url for chain ${chainId->
|
|
42
|
+
`The RPC url for chain ${chainId->Int.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`,
|
|
50
43
|
)
|
|
51
44
|
| Some(host) => host
|
|
52
45
|
}
|
|
@@ -54,7 +47,7 @@ let makeRPCSource = (~chain, ~rpc: string): Source.t => {
|
|
|
54
47
|
|
|
55
48
|
{
|
|
56
49
|
name,
|
|
57
|
-
sourceFor
|
|
50
|
+
sourceFor,
|
|
58
51
|
chain,
|
|
59
52
|
poweredByHyperSync: false,
|
|
60
53
|
pollingInterval: 10_000,
|
package/src/sources/Svm.res.mjs
CHANGED
|
@@ -18,18 +18,6 @@ let ecosystem_blockFields = ["slot"];
|
|
|
18
18
|
|
|
19
19
|
let ecosystem_transactionFields = [];
|
|
20
20
|
|
|
21
|
-
function ecosystem_getNumber(prim) {
|
|
22
|
-
return prim.height;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function ecosystem_getTimestamp(prim) {
|
|
26
|
-
return prim.time;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function ecosystem_getId(prim) {
|
|
30
|
-
return prim.hash;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
21
|
let ecosystem_onBlockFilterSchema = S$RescriptSchema.object(s => s.f("slot", S$RescriptSchema.option(S$RescriptSchema.unknown)));
|
|
34
22
|
|
|
35
23
|
let ecosystem_onEventBlockFilterSchema = S$RescriptSchema.object(param => {});
|
|
@@ -41,9 +29,6 @@ let ecosystem = {
|
|
|
41
29
|
blockNumberName: "height",
|
|
42
30
|
blockTimestampName: "time",
|
|
43
31
|
blockHashName: "hash",
|
|
44
|
-
getNumber: ecosystem_getNumber,
|
|
45
|
-
getTimestamp: ecosystem_getTimestamp,
|
|
46
|
-
getId: ecosystem_getId,
|
|
47
32
|
cleanUpRawEventFieldsInPlace: cleanUpRawEventFieldsInPlace,
|
|
48
33
|
onBlockMethodName: "onSlot",
|
|
49
34
|
onBlockFilterSchema: ecosystem_onBlockFilterSchema,
|
|
@@ -60,14 +45,15 @@ let GetFinalizedSlot = {
|
|
|
60
45
|
route: route
|
|
61
46
|
};
|
|
62
47
|
|
|
63
|
-
function makeRPCSource(chain, rpc) {
|
|
48
|
+
function makeRPCSource(chain, rpc, sourceForOpt) {
|
|
49
|
+
let sourceFor = sourceForOpt !== undefined ? sourceForOpt : "Sync";
|
|
64
50
|
let client = Rest.client(rpc, undefined);
|
|
65
51
|
let host = Utils.Url.getHostFromUrl(rpc);
|
|
66
|
-
let urlHost = host !== undefined ? host : Stdlib_JsError.throwWithMessage(`The RPC url for chain ` +
|
|
52
|
+
let urlHost = host !== undefined ? host : Stdlib_JsError.throwWithMessage(`The RPC url for chain ` + chain.toString() + ` is in incorrect format. The RPC url needs to start with either http:// or https://`);
|
|
67
53
|
let name = `RPC (` + urlHost + `)`;
|
|
68
54
|
return {
|
|
69
55
|
name: name,
|
|
70
|
-
sourceFor:
|
|
56
|
+
sourceFor: sourceFor,
|
|
71
57
|
chain: chain,
|
|
72
58
|
poweredByHyperSync: false,
|
|
73
59
|
pollingInterval: 10000,
|