envio 3.6.1 → 3.7.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/index.d.ts +197 -9
- package/package.json +6 -6
- package/src/Batch.res +64 -25
- package/src/Batch.res.mjs +69 -46
- package/src/ChainFetching.res +13 -17
- package/src/ChainFetching.res.mjs +9 -14
- package/src/ChainState.res +160 -119
- package/src/ChainState.res.mjs +75 -46
- package/src/ChainState.resi +15 -12
- package/src/Config.res +9 -2
- package/src/Config.res.mjs +4 -4
- package/src/Core.res +5 -0
- package/src/Ecosystem.res +0 -1
- package/src/Envio.res +16 -3
- package/src/EventConfigBuilder.res +155 -40
- package/src/EventConfigBuilder.res.mjs +76 -24
- package/src/EventProcessing.res +4 -8
- package/src/EventProcessing.res.mjs +4 -4
- package/src/EventUtils.res +3 -47
- package/src/FetchState.res +124 -104
- package/src/FetchState.res.mjs +116 -100
- package/src/HandlerRegister.res +108 -2
- package/src/HandlerRegister.res.mjs +59 -10
- package/src/HandlerRegister.resi +4 -0
- package/src/Internal.res +84 -24
- package/src/Internal.res.mjs +33 -2
- package/src/LazyLoader.res +12 -11
- package/src/LazyLoader.res.mjs +13 -7
- package/src/Main.res +22 -9
- package/src/Main.res.mjs +18 -5
- package/src/MemoryStorage.res +724 -0
- package/src/MemoryStorage.res.mjs +601 -0
- package/src/PgStorage.res +18 -7
- package/src/PgStorage.res.mjs +14 -11
- package/src/ReorgDetection.res +0 -222
- package/src/ReorgDetection.res.mjs +0 -163
- package/src/Rollback.res +14 -15
- package/src/Rollback.res.mjs +4 -5
- package/src/SimulateItems.res +2 -2
- package/src/Utils.res +3 -0
- package/src/bindings/ClickHouse.res +16 -11
- package/src/bindings/ClickHouse.res.mjs +10 -10
- package/src/bindings/Vitest.res +1 -1
- package/src/sources/BlockStore.res +115 -5
- package/src/sources/BlockStore.res.mjs +25 -0
- package/src/sources/Evm.res +0 -1
- package/src/sources/Evm.res.mjs +0 -1
- package/src/sources/EvmHyperSyncSource.res +19 -84
- package/src/sources/EvmHyperSyncSource.res.mjs +24 -60
- package/src/sources/Fuel.res +24 -4
- package/src/sources/Fuel.res.mjs +23 -3
- package/src/sources/FuelHyperSync.res +8 -3
- package/src/sources/FuelHyperSync.res.mjs +5 -4
- package/src/sources/FuelHyperSync.resi +3 -1
- package/src/sources/FuelHyperSyncClient.res +7 -10
- package/src/sources/FuelHyperSyncSource.res +18 -45
- package/src/sources/FuelHyperSyncSource.res.mjs +18 -35
- package/src/sources/HyperSync.res +49 -229
- package/src/sources/HyperSync.res.mjs +74 -191
- package/src/sources/HyperSync.resi +11 -35
- package/src/sources/HyperSyncClient.res +9 -79
- package/src/sources/HyperSyncClient.res.mjs +2 -6
- package/src/sources/RequestStat.res +5 -0
- package/src/sources/RequestStat.res.mjs +2 -0
- package/src/sources/RpcSource.res +149 -43
- package/src/sources/RpcSource.res.mjs +104 -41
- package/src/sources/SimulateSource.res +8 -5
- package/src/sources/SimulateSource.res.mjs +6 -6
- package/src/sources/Source.res +89 -16
- package/src/sources/Source.res.mjs +50 -1
- package/src/sources/SourceManager.res +255 -50
- package/src/sources/SourceManager.res.mjs +149 -55
- package/src/sources/SourceManager.resi +2 -6
- package/src/sources/Svm.res +0 -7
- package/src/sources/Svm.res.mjs +0 -8
- package/src/sources/SvmHyperSyncClient.res +21 -16
- package/src/sources/SvmHyperSyncClient.res.mjs +3 -4
- package/src/sources/SvmHyperSyncSource.res +25 -117
- package/src/sources/SvmHyperSyncSource.res.mjs +8 -121
- package/svm.schema.json +3 -2
|
@@ -9,6 +9,7 @@ import * as Address from "../Address.res.mjs";
|
|
|
9
9
|
import * as ChainId from "../ChainId.res.mjs";
|
|
10
10
|
import * as Logging from "../Logging.res.mjs";
|
|
11
11
|
import * as Internal from "../Internal.res.mjs";
|
|
12
|
+
import * as BlockStore from "./BlockStore.res.mjs";
|
|
12
13
|
import * as LazyLoader from "../LazyLoader.res.mjs";
|
|
13
14
|
import * as Performance from "../bindings/Performance.res.mjs";
|
|
14
15
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
@@ -579,6 +580,16 @@ let fieldRegistryLowercase = makeFieldRegistry(lowercaseAddressSchema);
|
|
|
579
580
|
|
|
580
581
|
let fieldRegistryChecksum = makeFieldRegistry(checksumAddressSchema);
|
|
581
582
|
|
|
583
|
+
function isRpcTransactionField(name) {
|
|
584
|
+
switch (name) {
|
|
585
|
+
case "hash" :
|
|
586
|
+
case "transactionIndex" :
|
|
587
|
+
return true;
|
|
588
|
+
default:
|
|
589
|
+
return Stdlib_Option.isSome(fieldRegistryChecksum[name]);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
582
593
|
function parseFieldsFromJson(mutTransactionAcc, fields, json) {
|
|
583
594
|
fields.forEach(def => {
|
|
584
595
|
let raw = json[def.jsonKey];
|
|
@@ -596,6 +607,22 @@ function parseFieldsFromJson(mutTransactionAcc, fields, json) {
|
|
|
596
607
|
});
|
|
597
608
|
}
|
|
598
609
|
|
|
610
|
+
let effectiveGasPriceFallbackDef = {
|
|
611
|
+
location: "effectiveGasPrice",
|
|
612
|
+
jsonKey: "gasPrice",
|
|
613
|
+
schema: Rpc.hexBigintSchema,
|
|
614
|
+
source: "TransactionOnly"
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
function fillEffectiveGasPriceOrThrow(mutTransactionAcc, txJson) {
|
|
618
|
+
let gasPrice = txJson["gasPrice"];
|
|
619
|
+
if (gasPrice !== undefined) {
|
|
620
|
+
return parseFieldsFromJson(mutTransactionAcc, [effectiveGasPriceFallbackDef], txJson);
|
|
621
|
+
} else {
|
|
622
|
+
return Stdlib_JsError.throwWithMessage(`Neither "effectiveGasPrice" nor "gasPrice" is present in the RPC response for the transaction. Remove "effectiveGasPrice" from the field selection, or index this chain via HyperSync.`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
599
626
|
function makeThrowingGetEventTransaction(getTransactionJson, getReceiptJson, lowercaseAddresses) {
|
|
600
627
|
let fieldRegistry = lowercaseAddresses ? fieldRegistryLowercase : fieldRegistryChecksum;
|
|
601
628
|
let fnsCache = new WeakMap();
|
|
@@ -611,6 +638,9 @@ function makeThrowingGetEventTransaction(getTransactionJson, getReceiptJson, low
|
|
|
611
638
|
let hasHash = {
|
|
612
639
|
contents: false
|
|
613
640
|
};
|
|
641
|
+
let hasEffectiveGasPrice = {
|
|
642
|
+
contents: false
|
|
643
|
+
};
|
|
614
644
|
let txFields = [];
|
|
615
645
|
let receiptFields = [];
|
|
616
646
|
let bothFields = [];
|
|
@@ -627,16 +657,22 @@ function makeThrowingGetEventTransaction(getTransactionJson, getReceiptJson, low
|
|
|
627
657
|
if (def === undefined) {
|
|
628
658
|
return;
|
|
629
659
|
}
|
|
630
|
-
let
|
|
660
|
+
let def$1 = fieldName === "effectiveGasPrice" ? (hasEffectiveGasPrice.contents = true, {
|
|
661
|
+
location: def.location,
|
|
662
|
+
jsonKey: def.jsonKey,
|
|
663
|
+
schema: S$RescriptSchema.nullable(def.schema),
|
|
664
|
+
source: def.source
|
|
665
|
+
}) : def;
|
|
666
|
+
let match = def$1.source;
|
|
631
667
|
switch (match) {
|
|
632
668
|
case "TransactionOnly" :
|
|
633
|
-
txFields.push(def);
|
|
669
|
+
txFields.push(def$1);
|
|
634
670
|
return;
|
|
635
671
|
case "ReceiptOnly" :
|
|
636
|
-
receiptFields.push(def);
|
|
672
|
+
receiptFields.push(def$1);
|
|
637
673
|
return;
|
|
638
674
|
case "Both" :
|
|
639
|
-
bothFields.push(def);
|
|
675
|
+
bothFields.push(def$1);
|
|
640
676
|
return;
|
|
641
677
|
}
|
|
642
678
|
}
|
|
@@ -713,8 +749,20 @@ function makeThrowingGetEventTransaction(getTransactionJson, getReceiptJson, low
|
|
|
713
749
|
if (receiptJson !== undefined) {
|
|
714
750
|
parseFieldsFromJson(mutTransactionAcc, receiptFields, receiptJson);
|
|
715
751
|
}
|
|
716
|
-
|
|
717
|
-
|
|
752
|
+
if (hasEffectiveGasPrice.contents && Stdlib_Option.isNone(mutTransactionAcc["effectiveGasPrice"])) {
|
|
753
|
+
if (txJson !== undefined) {
|
|
754
|
+
fillEffectiveGasPriceOrThrow(mutTransactionAcc, txJson);
|
|
755
|
+
return Promise.resolve(mutTransactionAcc);
|
|
756
|
+
} else {
|
|
757
|
+
return getTransactionJson(log.transactionHash).then(json => {
|
|
758
|
+
fillEffectiveGasPriceOrThrow(mutTransactionAcc, json);
|
|
759
|
+
return mutTransactionAcc;
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
} else {
|
|
763
|
+
return Promise.resolve(mutTransactionAcc);
|
|
764
|
+
}
|
|
765
|
+
}).then(mutTransactionAcc => mutTransactionAcc);
|
|
718
766
|
}
|
|
719
767
|
);
|
|
720
768
|
fnsCache.set(selectedTransactionFields, fn$1);
|
|
@@ -799,6 +847,11 @@ function make(param) {
|
|
|
799
847
|
let receiptLoader = {
|
|
800
848
|
contents: makeReceiptLoader()
|
|
801
849
|
};
|
|
850
|
+
let resetCachedLoaders = () => {
|
|
851
|
+
blockLoader.contents = makeBlockLoader();
|
|
852
|
+
transactionLoader.contents = makeTransactionLoader();
|
|
853
|
+
receiptLoader.contents = makeReceiptLoader();
|
|
854
|
+
};
|
|
802
855
|
let getEventBlockOrThrow = makeThrowingGetEventBlock(blockNumber => LazyLoader.get(blockLoader.contents, blockNumber), lowercaseAddresses);
|
|
803
856
|
let getEventTransactionOrThrow = makeThrowingGetEventTransaction(async transactionHash => {
|
|
804
857
|
let json = await LazyLoader.get(transactionLoader.contents, transactionHash);
|
|
@@ -824,7 +877,7 @@ function make(param) {
|
|
|
824
877
|
let getItemsOrThrow = async (fromBlock, toBlock, addressSet, knownHeight, partitionId, selection, param, retry, param$1) => {
|
|
825
878
|
let startFetchingBatchTimeRef = Performance.now();
|
|
826
879
|
let toBlock$1 = toBlock !== undefined ? Primitive_int.min(toBlock, knownHeight) : knownHeight;
|
|
827
|
-
let
|
|
880
|
+
let firstBlockPromise = fromBlock > 0 && fromBlock <= toBlock$1 ? LazyLoader.get(blockLoader.contents, fromBlock).then(json => parseBlockInfo(json)) : Promise.resolve(undefined);
|
|
828
881
|
if (Utils.$$Array.isEmpty(selection.onEventRegistrations)) {
|
|
829
882
|
throw {
|
|
830
883
|
RE_EXN_ID: Source.GetItemsError,
|
|
@@ -887,8 +940,8 @@ function make(param) {
|
|
|
887
940
|
let match;
|
|
888
941
|
try {
|
|
889
942
|
match = await Promise.all([
|
|
890
|
-
getEventBlockOrThrow(log,
|
|
891
|
-
getEventTransactionOrThrow(log,
|
|
943
|
+
getEventBlockOrThrow(log, onEventRegistration.fieldSelection.blockFields),
|
|
944
|
+
getEventTransactionOrThrow(log, onEventRegistration.fieldSelection.transactionFields)
|
|
892
945
|
]);
|
|
893
946
|
} catch (raw_exn) {
|
|
894
947
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
@@ -942,67 +995,71 @@ function make(param) {
|
|
|
942
995
|
};
|
|
943
996
|
})();
|
|
944
997
|
}));
|
|
945
|
-
let
|
|
998
|
+
let optFirstBlock = await firstBlockPromise;
|
|
946
999
|
let totalTimeElapsed = Performance.secondsSince(startFetchingBatchTimeRef);
|
|
947
|
-
let
|
|
1000
|
+
let observedBlocks = [];
|
|
948
1001
|
let pushBlockInfo = b => {
|
|
949
|
-
|
|
1002
|
+
observedBlocks.push({
|
|
1003
|
+
blockNumber: b.number,
|
|
950
1004
|
blockHash: b.hash,
|
|
951
|
-
|
|
1005
|
+
blockTimestamp: b.timestamp
|
|
952
1006
|
});
|
|
953
1007
|
if (b.number > 0) {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1008
|
+
observedBlocks.push({
|
|
1009
|
+
blockNumber: b.number - 1 | 0,
|
|
1010
|
+
blockHash: b.parentHash
|
|
957
1011
|
});
|
|
958
1012
|
return;
|
|
959
1013
|
}
|
|
960
1014
|
};
|
|
961
1015
|
pushBlockInfo(latestFetchedBlockInfo);
|
|
962
|
-
if (
|
|
963
|
-
pushBlockInfo(
|
|
1016
|
+
if (optFirstBlock !== undefined) {
|
|
1017
|
+
pushBlockInfo(optFirstBlock);
|
|
964
1018
|
}
|
|
965
1019
|
items.forEach(param => {
|
|
966
1020
|
let log = param.log;
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1021
|
+
observedBlocks.push({
|
|
1022
|
+
blockNumber: log.blockNumber,
|
|
1023
|
+
blockHash: log.blockHash
|
|
970
1024
|
});
|
|
971
1025
|
});
|
|
972
1026
|
return {
|
|
973
1027
|
knownHeight: knownHeight,
|
|
974
|
-
blockHashes: blockHashes,
|
|
975
1028
|
parsedQueueItems: parsedQueueItems,
|
|
976
1029
|
transactionStore: undefined,
|
|
977
|
-
blockStore:
|
|
1030
|
+
blockStore: BlockStore.fromJs(observedBlocks, "evm", !lowercaseAddresses),
|
|
978
1031
|
fromBlockQueried: fromBlock,
|
|
979
1032
|
latestFetchedBlockNumber: latestFetchedBlockInfo.number,
|
|
980
|
-
latestFetchedBlockTimestamp: latestFetchedBlockInfo.timestamp,
|
|
981
1033
|
stats: {
|
|
982
1034
|
"total time elapsed (s)": totalTimeElapsed
|
|
983
1035
|
},
|
|
984
1036
|
requestStats: drainRequestStats()
|
|
985
1037
|
};
|
|
986
1038
|
};
|
|
987
|
-
let onReorg =
|
|
988
|
-
blockLoader.contents = makeBlockLoader();
|
|
989
|
-
transactionLoader.contents = makeTransactionLoader();
|
|
990
|
-
receiptLoader.contents = makeReceiptLoader();
|
|
991
|
-
};
|
|
1039
|
+
let onReorg = () => resetCachedLoaders();
|
|
992
1040
|
let getBlockHashes = (blockNumbers, _currentlyUnusedLogger) => Stdlib_Promise.$$catch(Promise.all(blockNumbers.map(blockNum => LazyLoader.get(blockLoader.contents, blockNum))).then(rawBlocks => {
|
|
993
|
-
let
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1041
|
+
let observedBlocks = [];
|
|
1042
|
+
rawBlocks.forEach(json => {
|
|
1043
|
+
let b = parseBlockInfo(json);
|
|
1044
|
+
observedBlocks.push({
|
|
1045
|
+
blockNumber: b.number,
|
|
1046
|
+
blockHash: b.hash,
|
|
1047
|
+
blockTimestamp: b.timestamp
|
|
1048
|
+
});
|
|
1049
|
+
if (b.number > 0) {
|
|
1050
|
+
observedBlocks.push({
|
|
1051
|
+
blockNumber: b.number - 1 | 0,
|
|
1052
|
+
blockHash: b.parentHash
|
|
1053
|
+
});
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
let blockStore = BlockStore.fromJs(observedBlocks, "evm", !lowercaseAddresses);
|
|
1004
1058
|
return {
|
|
1005
|
-
result:
|
|
1059
|
+
result: {
|
|
1060
|
+
TAG: "Ok",
|
|
1061
|
+
_0: blockStore
|
|
1062
|
+
},
|
|
1006
1063
|
requestStats: drainRequestStats()
|
|
1007
1064
|
};
|
|
1008
1065
|
}), exn => Promise.resolve({
|
|
@@ -1041,6 +1098,8 @@ function make(param) {
|
|
|
1041
1098
|
};
|
|
1042
1099
|
}
|
|
1043
1100
|
|
|
1101
|
+
let effectiveGasPriceKey = "effectiveGasPrice";
|
|
1102
|
+
|
|
1044
1103
|
export {
|
|
1045
1104
|
TransactionDataNotFound,
|
|
1046
1105
|
getKnownRawBlock,
|
|
@@ -1058,7 +1117,11 @@ export {
|
|
|
1058
1117
|
makeFieldRegistry,
|
|
1059
1118
|
fieldRegistryLowercase,
|
|
1060
1119
|
fieldRegistryChecksum,
|
|
1120
|
+
isRpcTransactionField,
|
|
1061
1121
|
parseFieldsFromJson,
|
|
1122
|
+
effectiveGasPriceKey,
|
|
1123
|
+
effectiveGasPriceFallbackDef,
|
|
1124
|
+
fillEffectiveGasPriceOrThrow,
|
|
1062
1125
|
makeThrowingGetEventTransaction,
|
|
1063
1126
|
make,
|
|
1064
1127
|
}
|
|
@@ -3,6 +3,7 @@ let make = (
|
|
|
3
3
|
~endBlock: int,
|
|
4
4
|
~chainId: ChainId.t,
|
|
5
5
|
~addressStore: AddressStore.t,
|
|
6
|
+
~ecosystem: Ecosystem.name=Evm,
|
|
6
7
|
): Source.t => {
|
|
7
8
|
let reportedHeight = max(endBlock, 1)
|
|
8
9
|
|
|
@@ -13,7 +14,10 @@ let make = (
|
|
|
13
14
|
poweredByHyperSync: false,
|
|
14
15
|
pollingInterval: 0,
|
|
15
16
|
getBlockHashes: (~blockNumbers as _, ~logger as _) => {
|
|
16
|
-
Promise.resolve({
|
|
17
|
+
Promise.resolve({
|
|
18
|
+
Source.result: Ok(BlockStore.fromJs([], ~ecosystem, ~shouldChecksum=false)),
|
|
19
|
+
requestStats: [],
|
|
20
|
+
})
|
|
17
21
|
},
|
|
18
22
|
getHeightOrThrow: () => {
|
|
19
23
|
// Report at least height 1 so the engine doesn't treat 0 as "no blocks available"
|
|
@@ -101,14 +105,13 @@ let make = (
|
|
|
101
105
|
|
|
102
106
|
Promise.resolve({
|
|
103
107
|
Source.knownHeight: reportedHeight,
|
|
104
|
-
blockHashes: [],
|
|
105
108
|
parsedQueueItems,
|
|
106
|
-
// Simulate keeps the transaction and block inline on the payload; no
|
|
109
|
+
// Simulate keeps the transaction and block inline on the payload; no
|
|
110
|
+
// transaction page and an empty block page (nothing to reorg-check).
|
|
107
111
|
transactionStore: None,
|
|
108
|
-
blockStore:
|
|
112
|
+
blockStore: BlockStore.fromJs([], ~ecosystem, ~shouldChecksum=false),
|
|
109
113
|
fromBlockQueried: fromBlock,
|
|
110
114
|
latestFetchedBlockNumber: toBlockQueried,
|
|
111
|
-
latestFetchedBlockTimestamp: 0,
|
|
112
115
|
stats: {
|
|
113
116
|
totalTimeElapsed: 0.,
|
|
114
117
|
},
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as BlockStore from "./BlockStore.res.mjs";
|
|
3
4
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
4
5
|
|
|
5
|
-
function make(items, endBlock, chainId, addressStore) {
|
|
6
|
+
function make(items, endBlock, chainId, addressStore, ecosystemOpt) {
|
|
7
|
+
let ecosystem = ecosystemOpt !== undefined ? ecosystemOpt : "evm";
|
|
6
8
|
let reportedHeight = Primitive_int.max(endBlock, 1);
|
|
7
9
|
return {
|
|
8
10
|
name: "SimulateSource",
|
|
@@ -13,7 +15,7 @@ function make(items, endBlock, chainId, addressStore) {
|
|
|
13
15
|
getBlockHashes: (param, param$1) => Promise.resolve({
|
|
14
16
|
result: {
|
|
15
17
|
TAG: "Ok",
|
|
16
|
-
_0: []
|
|
18
|
+
_0: BlockStore.fromJs([], ecosystem, false)
|
|
17
19
|
},
|
|
18
20
|
requestStats: []
|
|
19
21
|
}),
|
|
@@ -72,13 +74,11 @@ function make(items, endBlock, chainId, addressStore) {
|
|
|
72
74
|
});
|
|
73
75
|
return Promise.resolve({
|
|
74
76
|
knownHeight: reportedHeight,
|
|
75
|
-
blockHashes: [],
|
|
76
77
|
parsedQueueItems: parsedQueueItems,
|
|
77
78
|
transactionStore: undefined,
|
|
78
|
-
blockStore:
|
|
79
|
+
blockStore: BlockStore.fromJs([], ecosystem, false),
|
|
79
80
|
fromBlockQueried: fromBlock,
|
|
80
81
|
latestFetchedBlockNumber: toBlockQueried,
|
|
81
|
-
latestFetchedBlockTimestamp: 0,
|
|
82
82
|
stats: {
|
|
83
83
|
"total time elapsed (s)": 0
|
|
84
84
|
},
|
|
@@ -91,4 +91,4 @@ function make(items, endBlock, chainId, addressStore) {
|
|
|
91
91
|
export {
|
|
92
92
|
make,
|
|
93
93
|
}
|
|
94
|
-
/*
|
|
94
|
+
/* BlockStore Not a pure module */
|
package/src/sources/Source.res
CHANGED
|
@@ -10,30 +10,82 @@ type blockRangeFetchStats = {
|
|
|
10
10
|
// A single backend request a source method actually made (cache/dedup hits
|
|
11
11
|
// aren't requests), with the time it took. SourceManager aggregates these
|
|
12
12
|
// per (source, method) into the envio_source_request_* metrics.
|
|
13
|
-
type requestStat = {method: string, seconds: float}
|
|
13
|
+
type requestStat = RequestStat.t = {method: string, seconds: float}
|
|
14
|
+
|
|
15
|
+
// Native clients wrap a failure of a multi-request operation in a structured
|
|
16
|
+
// payload, so the source can still return timings when SourceManager retries
|
|
17
|
+
// it. `cause` carries the inner message as a plain error, ready for logging.
|
|
18
|
+
type nativeRequestFailure = {
|
|
19
|
+
cause: exn,
|
|
20
|
+
message: option<string>,
|
|
21
|
+
requestStats: array<requestStat>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Prefix marking a napi error reason as a structured native-failure envelope.
|
|
25
|
+
// Keep in sync with `request_stats.rs` `NATIVE_FAILURE_PREFIX`.
|
|
26
|
+
let nativeFailurePrefix = "ENVIO_NATIVE_FAILURE:"
|
|
27
|
+
|
|
28
|
+
// The envelope `request_stats.rs` writes after the prefix.
|
|
29
|
+
type nativeFailurePayload = {message: string, requestStats: array<requestStat>}
|
|
30
|
+
|
|
31
|
+
let nativeFailurePayloadSchema = S.schema(s => {
|
|
32
|
+
message: s.matches(S.string),
|
|
33
|
+
requestStats: s.matches(
|
|
34
|
+
S.array(
|
|
35
|
+
S.schema(s => {
|
|
36
|
+
method: s.matches(S.string),
|
|
37
|
+
seconds: s.matches(S.float),
|
|
38
|
+
}),
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
let unpackNativeRequestFailure = (exn: exn): nativeRequestFailure => {
|
|
44
|
+
let originalMessage = switch exn->JsExn.anyToExnInternal {
|
|
45
|
+
| JsExn(jsExn) => jsExn->JsExn.message
|
|
46
|
+
| _ => None
|
|
47
|
+
}
|
|
48
|
+
// Only a reason carrying our prefix is one of our envelopes; anything else
|
|
49
|
+
// keeps its original message and cause untouched.
|
|
50
|
+
let decoded = switch originalMessage {
|
|
51
|
+
| Some(message) if message->String.startsWith(nativeFailurePrefix) =>
|
|
52
|
+
try Some(
|
|
53
|
+
message
|
|
54
|
+
->String.slice(~start=nativeFailurePrefix->String.length, ~end=message->String.length)
|
|
55
|
+
->JSON.parseOrThrow
|
|
56
|
+
->S.parseOrThrow(nativeFailurePayloadSchema),
|
|
57
|
+
) catch {
|
|
58
|
+
| _ => None
|
|
59
|
+
}
|
|
60
|
+
| _ => None
|
|
61
|
+
}
|
|
62
|
+
switch decoded {
|
|
63
|
+
| Some({message, requestStats}) => {
|
|
64
|
+
cause: JsError.make(message)->(Utils.magic: JsError.t => exn),
|
|
65
|
+
message: Some(message),
|
|
66
|
+
requestStats,
|
|
67
|
+
}
|
|
68
|
+
| None => {cause: exn, message: originalMessage, requestStats: []}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
14
71
|
|
|
15
72
|
/**
|
|
16
73
|
Thes response returned from a block range fetch
|
|
17
74
|
*/
|
|
18
75
|
type blockRangeFetchResponse = {
|
|
19
76
|
knownHeight: int,
|
|
20
|
-
// Best-effort (blockNumber, blockHash) pairs observed while fetching this range.
|
|
21
|
-
// Used by reorg detection; gaps are OK, no extra requests are made to fill them.
|
|
22
|
-
// Duplicates with the same block number are allowed — registerReorgGuard treats
|
|
23
|
-
// a within-array hash mismatch on the same block number as a reorg.
|
|
24
|
-
blockHashes: array<ReorgDetection.blockData>,
|
|
25
77
|
parsedQueueItems: array<Internal.item>,
|
|
26
78
|
// Page of transactions for this response's items, keyed by (blockNumber,
|
|
27
79
|
// transactionIndex); merged into the chain's store on apply. `None` for
|
|
28
80
|
// sources that keep the transaction inline on the payload (RPC/Fuel/Simulate).
|
|
29
81
|
transactionStore: option<TransactionStore.t>,
|
|
30
|
-
// Page of blocks
|
|
31
|
-
// the chain's store on apply
|
|
32
|
-
// inline on the payload (RPC/
|
|
33
|
-
|
|
82
|
+
// Page of blocks observed while fetching this range, keyed by block number;
|
|
83
|
+
// merged into the chain's store on apply, where its hashes drive reorg
|
|
84
|
+
// detection. Sources that keep the block inline on the payload (RPC/Simulate)
|
|
85
|
+
// contribute hash-only rows built from the block hashes they saw.
|
|
86
|
+
blockStore: BlockStore.t,
|
|
34
87
|
fromBlockQueried: int,
|
|
35
88
|
latestFetchedBlockNumber: int,
|
|
36
|
-
latestFetchedBlockTimestamp: int,
|
|
37
89
|
stats: blockRangeFetchStats,
|
|
38
90
|
requestStats: array<requestStat>,
|
|
39
91
|
}
|
|
@@ -41,16 +93,33 @@ type blockRangeFetchResponse = {
|
|
|
41
93
|
type getHeightResponse = {height: int, requestStats: array<requestStat>}
|
|
42
94
|
|
|
43
95
|
type getBlockHashesResponse = {
|
|
44
|
-
result: result<
|
|
96
|
+
result: result<BlockStore.t, exn>,
|
|
45
97
|
requestStats: array<requestStat>,
|
|
46
98
|
}
|
|
47
99
|
|
|
100
|
+
exception InconsistentResponse({
|
|
101
|
+
method: string,
|
|
102
|
+
blockNumber: option<int>,
|
|
103
|
+
storedHash: option<string>,
|
|
104
|
+
receivedHash: option<string>,
|
|
105
|
+
missingBlockNumbers: array<int>,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// The queried block hasn't reached the backend instance that served the
|
|
109
|
+
// request. Load-balanced backends drift from each other around the head, so
|
|
110
|
+
// this is expected there and resolves by retrying — SourceManager owns the
|
|
111
|
+
// backoff and the decision to fail over, identically for every ecosystem.
|
|
112
|
+
// Carries the timings of the requests the failed operation did make, so a
|
|
113
|
+
// retried request still counts towards the source's metrics.
|
|
114
|
+
exception SourceBehindHead({blockNumber: int, requestStats: array<requestStat>})
|
|
115
|
+
|
|
48
116
|
type getItemsRetry =
|
|
49
117
|
| WithSuggestedToBlock({toBlock: int})
|
|
50
118
|
| WithBackoff({message: string, backoffMillis: int})
|
|
51
119
|
| ImpossibleForTheQuery({message: string})
|
|
52
120
|
|
|
53
|
-
|
|
121
|
+
type rateLimited = {resetMs: int, requestStats: array<requestStat>}
|
|
122
|
+
exception RateLimited(rateLimited)
|
|
54
123
|
|
|
55
124
|
type getItemsError =
|
|
56
125
|
| UnsupportedSelection({message: string})
|
|
@@ -92,7 +161,11 @@ type t = {
|
|
|
92
161
|
~logger: Pino.t,
|
|
93
162
|
) => promise<blockRangeFetchResponse>,
|
|
94
163
|
createHeightSubscription?: (~onHeight: int => unit) => unit => unit,
|
|
95
|
-
// Invoked
|
|
96
|
-
//
|
|
97
|
-
|
|
164
|
+
// Invoked when a reorg or internally inconsistent response means local state
|
|
165
|
+
// may point at an orphaned chain (e.g. the RPC block cache): drop all of it.
|
|
166
|
+
// Deliberately takes no rollback target — the deepest reorged block isn't
|
|
167
|
+
// known until the depth search runs, and that search reads back through this
|
|
168
|
+
// very state, so pruning relative to a target would keep exactly the entries
|
|
169
|
+
// that make it answer wrong.
|
|
170
|
+
onReorg?: unit => unit,
|
|
98
171
|
}
|
|
@@ -1,13 +1,62 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
4
|
+
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
3
5
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
4
6
|
|
|
7
|
+
let nativeFailurePrefix = "ENVIO_NATIVE_FAILURE:";
|
|
8
|
+
|
|
9
|
+
let nativeFailurePayloadSchema = S$RescriptSchema.schema(s => ({
|
|
10
|
+
message: s.m(S$RescriptSchema.string),
|
|
11
|
+
requestStats: s.m(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
|
|
12
|
+
method: s.m(S$RescriptSchema.string),
|
|
13
|
+
seconds: s.m(S$RescriptSchema.float)
|
|
14
|
+
}))))
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
function unpackNativeRequestFailure(exn) {
|
|
18
|
+
let jsExn = Primitive_exceptions.internalToException(exn);
|
|
19
|
+
let originalMessage = jsExn.RE_EXN_ID === "JsExn" ? Stdlib_JsExn.message(jsExn._1) : undefined;
|
|
20
|
+
let decoded;
|
|
21
|
+
if (originalMessage !== undefined && originalMessage.startsWith(nativeFailurePrefix)) {
|
|
22
|
+
try {
|
|
23
|
+
decoded = S$RescriptSchema.parseOrThrow(JSON.parse(originalMessage.slice(nativeFailurePrefix.length, originalMessage.length)), nativeFailurePayloadSchema);
|
|
24
|
+
} catch (exn$1) {
|
|
25
|
+
decoded = undefined;
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
decoded = undefined;
|
|
29
|
+
}
|
|
30
|
+
if (decoded === undefined) {
|
|
31
|
+
return {
|
|
32
|
+
cause: exn,
|
|
33
|
+
message: originalMessage,
|
|
34
|
+
requestStats: []
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
let message = decoded.message;
|
|
38
|
+
return {
|
|
39
|
+
cause: new Error(message),
|
|
40
|
+
message: message,
|
|
41
|
+
requestStats: decoded.requestStats
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let InconsistentResponse = /* @__PURE__ */Primitive_exceptions.create("Source.InconsistentResponse");
|
|
46
|
+
|
|
47
|
+
let SourceBehindHead = /* @__PURE__ */Primitive_exceptions.create("Source.SourceBehindHead");
|
|
48
|
+
|
|
5
49
|
let RateLimited = /* @__PURE__ */Primitive_exceptions.create("Source.RateLimited");
|
|
6
50
|
|
|
7
51
|
let GetItemsError = /* @__PURE__ */Primitive_exceptions.create("Source.GetItemsError");
|
|
8
52
|
|
|
9
53
|
export {
|
|
54
|
+
nativeFailurePrefix,
|
|
55
|
+
nativeFailurePayloadSchema,
|
|
56
|
+
unpackNativeRequestFailure,
|
|
57
|
+
InconsistentResponse,
|
|
58
|
+
SourceBehindHead,
|
|
10
59
|
RateLimited,
|
|
11
60
|
GetItemsError,
|
|
12
61
|
}
|
|
13
|
-
/*
|
|
62
|
+
/* nativeFailurePayloadSchema Not a pure module */
|