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.
Files changed (80) hide show
  1. package/index.d.ts +197 -9
  2. package/package.json +6 -6
  3. package/src/Batch.res +64 -25
  4. package/src/Batch.res.mjs +69 -46
  5. package/src/ChainFetching.res +13 -17
  6. package/src/ChainFetching.res.mjs +9 -14
  7. package/src/ChainState.res +160 -119
  8. package/src/ChainState.res.mjs +75 -46
  9. package/src/ChainState.resi +15 -12
  10. package/src/Config.res +9 -2
  11. package/src/Config.res.mjs +4 -4
  12. package/src/Core.res +5 -0
  13. package/src/Ecosystem.res +0 -1
  14. package/src/Envio.res +16 -3
  15. package/src/EventConfigBuilder.res +155 -40
  16. package/src/EventConfigBuilder.res.mjs +76 -24
  17. package/src/EventProcessing.res +4 -8
  18. package/src/EventProcessing.res.mjs +4 -4
  19. package/src/EventUtils.res +3 -47
  20. package/src/FetchState.res +124 -104
  21. package/src/FetchState.res.mjs +116 -100
  22. package/src/HandlerRegister.res +108 -2
  23. package/src/HandlerRegister.res.mjs +59 -10
  24. package/src/HandlerRegister.resi +4 -0
  25. package/src/Internal.res +84 -24
  26. package/src/Internal.res.mjs +33 -2
  27. package/src/LazyLoader.res +12 -11
  28. package/src/LazyLoader.res.mjs +13 -7
  29. package/src/Main.res +22 -9
  30. package/src/Main.res.mjs +18 -5
  31. package/src/MemoryStorage.res +724 -0
  32. package/src/MemoryStorage.res.mjs +601 -0
  33. package/src/PgStorage.res +18 -7
  34. package/src/PgStorage.res.mjs +14 -11
  35. package/src/ReorgDetection.res +0 -222
  36. package/src/ReorgDetection.res.mjs +0 -163
  37. package/src/Rollback.res +14 -15
  38. package/src/Rollback.res.mjs +4 -5
  39. package/src/SimulateItems.res +2 -2
  40. package/src/Utils.res +3 -0
  41. package/src/bindings/ClickHouse.res +16 -11
  42. package/src/bindings/ClickHouse.res.mjs +10 -10
  43. package/src/bindings/Vitest.res +1 -1
  44. package/src/sources/BlockStore.res +115 -5
  45. package/src/sources/BlockStore.res.mjs +25 -0
  46. package/src/sources/Evm.res +0 -1
  47. package/src/sources/Evm.res.mjs +0 -1
  48. package/src/sources/EvmHyperSyncSource.res +19 -84
  49. package/src/sources/EvmHyperSyncSource.res.mjs +24 -60
  50. package/src/sources/Fuel.res +24 -4
  51. package/src/sources/Fuel.res.mjs +23 -3
  52. package/src/sources/FuelHyperSync.res +8 -3
  53. package/src/sources/FuelHyperSync.res.mjs +5 -4
  54. package/src/sources/FuelHyperSync.resi +3 -1
  55. package/src/sources/FuelHyperSyncClient.res +7 -10
  56. package/src/sources/FuelHyperSyncSource.res +18 -45
  57. package/src/sources/FuelHyperSyncSource.res.mjs +18 -35
  58. package/src/sources/HyperSync.res +49 -229
  59. package/src/sources/HyperSync.res.mjs +74 -191
  60. package/src/sources/HyperSync.resi +11 -35
  61. package/src/sources/HyperSyncClient.res +9 -79
  62. package/src/sources/HyperSyncClient.res.mjs +2 -6
  63. package/src/sources/RequestStat.res +5 -0
  64. package/src/sources/RequestStat.res.mjs +2 -0
  65. package/src/sources/RpcSource.res +149 -43
  66. package/src/sources/RpcSource.res.mjs +104 -41
  67. package/src/sources/SimulateSource.res +8 -5
  68. package/src/sources/SimulateSource.res.mjs +6 -6
  69. package/src/sources/Source.res +89 -16
  70. package/src/sources/Source.res.mjs +50 -1
  71. package/src/sources/SourceManager.res +255 -50
  72. package/src/sources/SourceManager.res.mjs +149 -55
  73. package/src/sources/SourceManager.resi +2 -6
  74. package/src/sources/Svm.res +0 -7
  75. package/src/sources/Svm.res.mjs +0 -8
  76. package/src/sources/SvmHyperSyncClient.res +21 -16
  77. package/src/sources/SvmHyperSyncClient.res.mjs +3 -4
  78. package/src/sources/SvmHyperSyncSource.res +25 -117
  79. package/src/sources/SvmHyperSyncSource.res.mjs +8 -121
  80. 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 match = def.source;
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
- return mutTransactionAcc;
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 firstBlockParentPromise = fromBlock > 0 ? LazyLoader.get(blockLoader.contents, fromBlock - 1 | 0).then(json => parseBlockInfo(json)) : Promise.resolve(undefined);
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, eventConfig.selectedBlockFields),
891
- getEventTransactionOrThrow(log, eventConfig.selectedTransactionFields)
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 optFirstBlockParent = await firstBlockParentPromise;
998
+ let optFirstBlock = await firstBlockPromise;
946
999
  let totalTimeElapsed = Performance.secondsSince(startFetchingBatchTimeRef);
947
- let blockHashes = [];
1000
+ let observedBlocks = [];
948
1001
  let pushBlockInfo = b => {
949
- blockHashes.push({
1002
+ observedBlocks.push({
1003
+ blockNumber: b.number,
950
1004
  blockHash: b.hash,
951
- blockNumber: b.number
1005
+ blockTimestamp: b.timestamp
952
1006
  });
953
1007
  if (b.number > 0) {
954
- blockHashes.push({
955
- blockHash: b.parentHash,
956
- blockNumber: b.number - 1 | 0
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 (optFirstBlockParent !== undefined) {
963
- pushBlockInfo(optFirstBlockParent);
1016
+ if (optFirstBlock !== undefined) {
1017
+ pushBlockInfo(optFirstBlock);
964
1018
  }
965
1019
  items.forEach(param => {
966
1020
  let log = param.log;
967
- blockHashes.push({
968
- blockHash: log.blockHash,
969
- blockNumber: log.blockNumber
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: undefined,
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 = param => {
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 result = {
994
- TAG: "Ok",
995
- _0: rawBlocks.map(json => {
996
- let b = parseBlockInfo(json);
997
- return {
998
- blockHash: b.hash,
999
- blockNumber: b.number,
1000
- blockTimestamp: b.timestamp
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: 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({Source.result: Ok([]), requestStats: []})
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 store pages.
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: None,
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: undefined,
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
- /* No side effect */
94
+ /* BlockStore Not a pure module */
@@ -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 for this response's items, keyed by block number; merged into
31
- // the chain's store on apply. `None` for sources that keep the block fully
32
- // inline on the payload (RPC/Fuel/Simulate).
33
- blockStore: option<BlockStore.t>,
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<array<ReorgDetection.blockDataWithTimestamp>, exn>,
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
- exception RateLimited({resetMs: int})
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 by SourceManager once a rollback target is known so the source can
96
- // drop any state that may now point at an orphaned chain (e.g. RPC block cache).
97
- onReorg?: (~rollbackTargetBlock: int) => unit,
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
- /* No side effect */
62
+ /* nativeFailurePayloadSchema Not a pure module */