envio 3.6.1 → 3.7.0-subgraph

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 (114) 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 +18 -2
  11. package/src/Config.res.mjs +9 -6
  12. package/src/Core.res +20 -0
  13. package/src/Core.res.mjs +11 -0
  14. package/src/Ecosystem.res +0 -1
  15. package/src/Envio.res +16 -3
  16. package/src/EventConfigBuilder.res +155 -40
  17. package/src/EventConfigBuilder.res.mjs +76 -24
  18. package/src/EventProcessing.res +10 -14
  19. package/src/EventProcessing.res.mjs +12 -10
  20. package/src/EventUtils.res +3 -47
  21. package/src/FetchState.res +124 -104
  22. package/src/FetchState.res.mjs +116 -100
  23. package/src/HandlerLoader.res +20 -8
  24. package/src/HandlerLoader.res.mjs +11 -2
  25. package/src/HandlerRegister.res +108 -2
  26. package/src/HandlerRegister.res.mjs +59 -10
  27. package/src/HandlerRegister.resi +4 -0
  28. package/src/InMemoryStore.res +1 -6
  29. package/src/InMemoryStore.res.mjs +1 -3
  30. package/src/Internal.res +84 -24
  31. package/src/Internal.res.mjs +33 -2
  32. package/src/LazyLoader.res +12 -11
  33. package/src/LazyLoader.res.mjs +13 -7
  34. package/src/Main.res +22 -9
  35. package/src/Main.res.mjs +18 -5
  36. package/src/MemoryStorage.res +756 -0
  37. package/src/MemoryStorage.res.mjs +623 -0
  38. package/src/Persistence.res +4 -2
  39. package/src/PgStorage.res +24 -8
  40. package/src/PgStorage.res.mjs +15 -12
  41. package/src/ReorgDetection.res +0 -222
  42. package/src/ReorgDetection.res.mjs +0 -163
  43. package/src/Rollback.res +14 -15
  44. package/src/Rollback.res.mjs +4 -5
  45. package/src/SimulateItems.res +2 -2
  46. package/src/UserContext.res +358 -51
  47. package/src/UserContext.res.mjs +271 -35
  48. package/src/Utils.res +3 -0
  49. package/src/bindings/ClickHouse.res +16 -11
  50. package/src/bindings/ClickHouse.res.mjs +10 -10
  51. package/src/bindings/Vitest.res +1 -1
  52. package/src/sources/BlockStore.res +115 -5
  53. package/src/sources/BlockStore.res.mjs +25 -0
  54. package/src/sources/Evm.res +0 -1
  55. package/src/sources/Evm.res.mjs +0 -1
  56. package/src/sources/EvmHyperSyncSource.res +19 -84
  57. package/src/sources/EvmHyperSyncSource.res.mjs +24 -60
  58. package/src/sources/Fuel.res +24 -4
  59. package/src/sources/Fuel.res.mjs +23 -3
  60. package/src/sources/FuelHyperSync.res +8 -3
  61. package/src/sources/FuelHyperSync.res.mjs +5 -4
  62. package/src/sources/FuelHyperSync.resi +3 -1
  63. package/src/sources/FuelHyperSyncClient.res +7 -10
  64. package/src/sources/FuelHyperSyncSource.res +18 -45
  65. package/src/sources/FuelHyperSyncSource.res.mjs +18 -35
  66. package/src/sources/HyperSync.res +49 -229
  67. package/src/sources/HyperSync.res.mjs +74 -191
  68. package/src/sources/HyperSync.resi +11 -35
  69. package/src/sources/HyperSyncClient.res +9 -79
  70. package/src/sources/HyperSyncClient.res.mjs +2 -6
  71. package/src/sources/RequestStat.res +5 -0
  72. package/src/sources/RequestStat.res.mjs +2 -0
  73. package/src/sources/RpcSource.res +149 -43
  74. package/src/sources/RpcSource.res.mjs +104 -41
  75. package/src/sources/SimulateSource.res +8 -5
  76. package/src/sources/SimulateSource.res.mjs +6 -6
  77. package/src/sources/Source.res +89 -16
  78. package/src/sources/Source.res.mjs +50 -1
  79. package/src/sources/SourceManager.res +255 -50
  80. package/src/sources/SourceManager.res.mjs +149 -55
  81. package/src/sources/SourceManager.resi +2 -6
  82. package/src/sources/Svm.res +0 -7
  83. package/src/sources/Svm.res.mjs +0 -8
  84. package/src/sources/SvmHyperSyncClient.res +21 -16
  85. package/src/sources/SvmHyperSyncClient.res.mjs +3 -4
  86. package/src/sources/SvmHyperSyncSource.res +25 -117
  87. package/src/sources/SvmHyperSyncSource.res.mjs +8 -121
  88. package/src/subgraph/blocks.ts +176 -0
  89. package/src/subgraph/calls.ts +217 -0
  90. package/src/subgraph/conformance.ts +99 -0
  91. package/src/subgraph/division.ts +110 -0
  92. package/src/subgraph/errors.ts +90 -0
  93. package/src/subgraph/graph-ts-types/VERSION +2 -0
  94. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  95. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  96. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  97. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  98. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  99. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  100. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  101. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  102. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  103. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  104. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  105. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  106. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  107. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  108. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  109. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  110. package/src/subgraph/graph-ts.ts +1871 -0
  111. package/src/subgraph/hosts.ts +148 -0
  112. package/src/subgraph/runtime.ts +845 -0
  113. package/src/subgraph/scope.ts +63 -0
  114. package/svm.schema.json +3 -2
@@ -36,9 +36,9 @@ async function query(client, fromBlock, toBlock, registrationIndexes, addressSet
36
36
  registrationIndexes: registrationIndexes,
37
37
  clientFilteredContracts: clientFilteredContracts
38
38
  };
39
- let res;
39
+ let match;
40
40
  try {
41
- res = await client.getEventItems(query$1, addressSet);
41
+ match = await client.getEventItems(query$1, addressSet);
42
42
  } catch (raw_exn) {
43
43
  let exn = Primitive_exceptions.internalToException(raw_exn);
44
44
  let missingParams = extractMissingParams(exn);
@@ -54,6 +54,7 @@ async function query(client, fromBlock, toBlock, registrationIndexes, addressSet
54
54
  }
55
55
  throw exn;
56
56
  }
57
+ let res = match[0];
57
58
  if (res.nextBlock <= fromBlock) {
58
59
  throw {
59
60
  RE_EXN_ID: $$Error,
@@ -63,9 +64,9 @@ async function query(client, fromBlock, toBlock, registrationIndexes, addressSet
63
64
  }
64
65
  return {
65
66
  items: res.items,
66
- blocks: res.blocks,
67
67
  nextBlock: res.nextBlock,
68
- archiveHeight: Stdlib_Option.getOr(res.archiveHeight, 0)
68
+ archiveHeight: Stdlib_Option.getOr(res.archiveHeight, 0),
69
+ blockStore: match[1]
69
70
  };
70
71
  }
71
72
 
@@ -1,8 +1,10 @@
1
1
  type logsQueryPage = {
2
2
  items: array<FuelHyperSyncClient.EventItems.item>,
3
- blocks: array<FuelHyperSyncClient.EventItems.block>,
4
3
  nextBlock: int,
5
4
  archiveHeight: int,
5
+ // Page store owning this page's raw blocks (built on the Rust side); its ids
6
+ // drive reorg detection when merged into the chain store.
7
+ blockStore: BlockStore.t,
6
8
  }
7
9
 
8
10
  module GetLogs: {
@@ -87,17 +87,9 @@ module EventItems = {
87
87
  to?: string,
88
88
  }
89
89
 
90
- type block = {
91
- id: string,
92
- height: int,
93
- time: int,
94
- }
95
-
96
90
  type response = {
97
91
  archiveHeight: option<int>,
98
92
  nextBlock: int,
99
- // One block per height; items reference them by `blockHeight`.
100
- blocks: array<block>,
101
93
  items: array<item>,
102
94
  }
103
95
  }
@@ -121,9 +113,14 @@ let make = (cfg: cfg, ~eventRegistrations, ~addressStore) => {
121
113
  )
122
114
  }
123
115
 
116
+ // Returns the routed items/blocks plus the page's block store (Fuel blocks
117
+ // keyed by height); the store's ids drive reorg detection on merge.
124
118
  @send
125
- external getEventItems: (t, EventItems.query, AddressSet.t) => promise<EventItems.response> =
126
- "getEventItems"
119
+ external getEventItems: (
120
+ t,
121
+ EventItems.query,
122
+ AddressSet.t,
123
+ ) => promise<(EventItems.response, BlockStore.t)> = "getEventItems"
127
124
 
128
125
  @send
129
126
  external getHeight: t => promise<int> = "getHeight"
@@ -25,7 +25,9 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
25
25
 
26
26
  let client = switch FuelHyperSyncClient.make(
27
27
  {url: endpointUrl, apiToken},
28
- ~eventRegistrations=FuelHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations),
28
+ ~eventRegistrations=FuelHyperSyncClient.Registration.fromOnEventRegistrations(
29
+ onEventRegistrations,
30
+ ),
29
31
  ~addressStore,
30
32
  ) {
31
33
  | client => client
@@ -57,29 +59,19 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
57
59
  ~addressSet,
58
60
  ~clientFilteredContracts=selection.clientFilteredContracts,
59
61
  ) catch {
60
- | FuelHyperSync.GetLogs.Error(error) =>
62
+ | FuelHyperSync.GetLogs.Error(WrongInstance) =>
63
+ throw(Source.SourceBehindHead({blockNumber: fromBlock, requestStats: []}))
64
+ | FuelHyperSync.GetLogs.Error(UnexpectedMissingParams({missingParams})) =>
61
65
  throw(
62
66
  Source.GetItemsError(
63
67
  Source.FailedGettingItems({
64
68
  exn: %raw(`null`),
65
69
  attemptedToBlock: toBlock->Option.getOr(knownHeight),
66
- retry: switch error {
67
- | WrongInstance =>
68
- let backoffMillis = switch retry {
69
- | 0 => 100
70
- | _ => 500 * retry
71
- }
72
- WithBackoff({
73
- message: `Block #${fromBlock->Int.toString} not found in FuelHyperSync. HyperFuel has multiple instances and it's possible that they drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${backoffMillis->Int.toString}ms.`,
74
- backoffMillis,
75
- })
76
- | UnexpectedMissingParams({missingParams}) =>
77
- ImpossibleForTheQuery({
78
- message: `Source returned invalid data with missing required fields: ${missingParams->Array.joinUnsafe(
79
- ", ",
80
- )}`,
81
- })
82
- },
70
+ retry: ImpossibleForTheQuery({
71
+ message: `Source returned invalid data with missing required fields: ${missingParams->Array.joinUnsafe(
72
+ ", ",
73
+ )}`,
74
+ }),
83
75
  }),
84
76
  ),
85
77
  )
@@ -114,12 +106,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
114
106
 
115
107
  let parsingTimeRef = Performance.now()
116
108
 
117
- // Blocks are returned once per height; items reference them by blockHeight.
118
- let blocksByHeight = Utils.Map.make()
119
- pageUnsafe.blocks->Array.forEach(block => {
120
- blocksByHeight->Utils.Map.set(block.height, block)->ignore
121
- })
122
-
123
109
  let parsedQueueItems = pageUnsafe.items->Array.map(item => {
124
110
  // Routing happened in Rust; the item references its registration by
125
111
  // chain-scoped index.
@@ -128,8 +114,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
128
114
  onEventRegistration.eventConfig->(
129
115
  Utils.magic: Internal.eventConfig => Internal.fuelEventConfig
130
116
  )
131
- // Presence of every routed item's block is validated in Rust.
132
- let block = blocksByHeight->Utils.Map.unsafeGet(item.blockHeight)
133
117
 
134
118
  let params = switch eventConfig.kind {
135
119
  | LogData({decode}) =>
@@ -178,12 +162,13 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
178
162
  payload: {
179
163
  contractName: eventConfig.contractName,
180
164
  eventName: eventConfig.name,
181
- chainId: chainId,
165
+ chainId,
182
166
  params,
183
167
  transaction: {
184
168
  "id": item.txId,
185
169
  }->Obj.magic, // TODO: Obj.magic needed until the field selection types are not configurable for Fuel and Evm separately
186
- block: block->Obj.magic,
170
+ // `block` is omitted; it's materialised from the block store onto the
171
+ // payload at batch prep (like EVM/SVM).
187
172
  srcAddress: item.srcAddress,
188
173
  logIndex: item.receiptIndex,
189
174
  }->Fuel.fromPayload,
@@ -192,18 +177,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
192
177
 
193
178
  let parsingTimeElapsed = parsingTimeRef->Performance.secondsSince
194
179
 
195
- // Fuel never rolls back on reorg, so block hashes here are purely informational
196
- // for detect-only logging via ReorgDetection.
197
- let blockHashes = pageUnsafe.blocks->Array.map(block => {
198
- ReorgDetection.blockNumber: block.height,
199
- blockHash: block.id,
200
- })
201
-
202
- let latestFetchedBlockTimestamp = switch blocksByHeight->Utils.Map.get(heighestBlockQueried) {
203
- | Some(block) => block.time
204
- | None => 0
205
- }
206
-
207
180
  let totalTimeElapsed = totalTimeRef->Performance.secondsSince
208
181
 
209
182
  let stats = {
@@ -213,15 +186,15 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
213
186
  }
214
187
 
215
188
  {
216
- latestFetchedBlockTimestamp,
217
189
  parsedQueueItems,
218
- // Fuel keeps transaction and block on the payload; no store pages.
190
+ // Fuel keeps transaction and block inline on the payload; the block store
191
+ // carries only the (height, id) rows that drive reorg detection (Fuel
192
+ // never rolls back, so it's detect-only logging).
219
193
  transactionStore: None,
220
- blockStore: None,
194
+ blockStore: pageUnsafe.blockStore,
221
195
  latestFetchedBlockNumber: heighestBlockQueried,
222
196
  stats,
223
197
  knownHeight,
224
- blockHashes,
225
198
  fromBlockQueried: fromBlock,
226
199
  requestStats,
227
200
  }
@@ -38,22 +38,16 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
38
38
  let pageUnsafe;
39
39
  try {
40
40
  pageUnsafe = await FuelHyperSync.GetLogs.query(client, fromBlock, toBlock, selection.onEventRegistrations.map(reg => reg.index), addressSet, selection.clientFilteredContracts);
41
- } catch (raw_error) {
42
- let error = Primitive_exceptions.internalToException(raw_error);
43
- if (error.RE_EXN_ID === FuelHyperSync.GetLogs.$$Error) {
44
- let error$1 = error._1;
45
- let tmp;
46
- if (typeof error$1 !== "object") {
47
- let backoffMillis = retry !== 0 ? 500 * retry | 0 : 100;
48
- tmp = {
49
- TAG: "WithBackoff",
50
- message: `Block #` + fromBlock.toString() + ` not found in FuelHyperSync. HyperFuel has multiple instances and it's possible that they drift independently slightly from the head. Indexing should continue correctly after retrying the query in ` + backoffMillis.toString() + `ms.`,
51
- backoffMillis: backoffMillis
52
- };
53
- } else {
54
- tmp = {
55
- TAG: "ImpossibleForTheQuery",
56
- message: `Source returned invalid data with missing required fields: ` + error$1.missingParams.join(", ")
41
+ } catch (raw_exn) {
42
+ let exn = Primitive_exceptions.internalToException(raw_exn);
43
+ if (exn.RE_EXN_ID === FuelHyperSync.GetLogs.$$Error) {
44
+ let match = exn._1;
45
+ if (typeof match !== "object") {
46
+ throw {
47
+ RE_EXN_ID: Source.SourceBehindHead,
48
+ blockNumber: fromBlock,
49
+ requestStats: [],
50
+ Error: new Error()
57
51
  };
58
52
  }
59
53
  throw {
@@ -62,7 +56,10 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
62
56
  TAG: "FailedGettingItems",
63
57
  exn: null,
64
58
  attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight),
65
- retry: tmp
59
+ retry: {
60
+ TAG: "ImpossibleForTheQuery",
61
+ message: `Source returned invalid data with missing required fields: ` + match.missingParams.join(", ")
62
+ }
66
63
  },
67
64
  Error: new Error()
68
65
  };
@@ -71,7 +68,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
71
68
  RE_EXN_ID: Source.GetItemsError,
72
69
  _1: {
73
70
  TAG: "FailedGettingItems",
74
- exn: error,
71
+ exn: exn,
75
72
  attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight),
76
73
  retry: {
77
74
  TAG: "WithBackoff",
@@ -90,14 +87,9 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
90
87
  let knownHeight$1 = pageUnsafe.archiveHeight;
91
88
  let heighestBlockQueried = pageUnsafe.nextBlock - 1 | 0;
92
89
  let parsingTimeRef = Performance.now();
93
- let blocksByHeight = new Map();
94
- pageUnsafe.blocks.forEach(block => {
95
- blocksByHeight.set(block.height, block);
96
- });
97
90
  let parsedQueueItems = pageUnsafe.items.map(item => {
98
91
  let onEventRegistration = onEventRegistrations[item.onEventRegistrationIndex];
99
92
  let eventConfig = onEventRegistration.eventConfig;
100
- let block = blocksByHeight.get(item.blockHeight);
101
93
  let match = eventConfig.kind;
102
94
  let params;
103
95
  let exit = 0;
@@ -158,18 +150,11 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
158
150
  logIndex: item.receiptIndex,
159
151
  transaction: {
160
152
  id: item.txId
161
- },
162
- block: block
153
+ }
163
154
  }
164
155
  };
165
156
  });
166
157
  let parsingTimeElapsed = Performance.secondsSince(parsingTimeRef);
167
- let blockHashes = pageUnsafe.blocks.map(block => ({
168
- blockHash: block.id,
169
- blockNumber: block.height
170
- }));
171
- let block = blocksByHeight.get(heighestBlockQueried);
172
- let latestFetchedBlockTimestamp = block !== undefined ? block.time : 0;
173
158
  let totalTimeElapsed = Performance.secondsSince(totalTimeRef);
174
159
  let stats_parsing$unknowntime$unknown$lpars$rpar = parsingTimeElapsed;
175
160
  let stats_page$unknownfetch$unknowntime$unknown$lpars$rpar = pageFetchTime;
@@ -180,13 +165,11 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
180
165
  };
181
166
  return {
182
167
  knownHeight: knownHeight$1,
183
- blockHashes: blockHashes,
184
168
  parsedQueueItems: parsedQueueItems,
185
169
  transactionStore: undefined,
186
- blockStore: undefined,
170
+ blockStore: pageUnsafe.blockStore,
187
171
  fromBlockQueried: fromBlock,
188
172
  latestFetchedBlockNumber: heighestBlockQueried,
189
- latestFetchedBlockTimestamp: latestFetchedBlockTimestamp,
190
173
  stats: stats,
191
174
  requestStats: requestStats
192
175
  };
@@ -249,4 +232,4 @@ export {
249
232
  isUnauthorizedError,
250
233
  make,
251
234
  }
252
- /* Logging Not a pure module */
235
+ /* Source Not a pure module */
@@ -1,59 +1,65 @@
1
- let reraisIfRateLimited = exn =>
2
- switch exn->JsExn.anyToExnInternal {
3
- | JsExn(e) =>
4
- switch e->JsExn.message {
5
- | Some(msg) if msg->String.startsWith("RATE_LIMITED:") =>
6
- let resetMs =
7
- msg
8
- ->String.slice(~start=13, ~end=msg->String.length)
9
- ->Int.fromString
10
- ->Option.getOr(1000)
11
- throw(Source.RateLimited({resetMs: resetMs}))
12
- | _ => ()
1
+ // A napi error carries nothing but a message, so the native clients signal the
2
+ // recoverable conditions SourceManager knows how to retry as a `PREFIX:<int>`
3
+ // marker. Keep in sync with `request_stats.rs`.
4
+ let rateLimitedPrefix = "RATE_LIMITED:"
5
+ let behindHeadPrefix = "SOURCE_BEHIND_HEAD:"
6
+
7
+ let markerValue = (msg, ~prefix) =>
8
+ msg->String.slice(~start=prefix->String.length, ~end=msg->String.length)->Int.fromString
9
+
10
+ let mapNativeFailure = (failure: Source.nativeRequestFailure) => {
11
+ switch failure.message {
12
+ | Some(msg) if msg->String.startsWith(rateLimitedPrefix) =>
13
+ Source.RateLimited({
14
+ resetMs: msg->markerValue(~prefix=rateLimitedPrefix)->Option.getOr(1000),
15
+ requestStats: failure.requestStats,
16
+ })
17
+ | Some(msg) if msg->String.startsWith(behindHeadPrefix) =>
18
+ Source.SourceBehindHead({
19
+ blockNumber: msg->markerValue(~prefix=behindHeadPrefix)->Option.getOr(0),
20
+ requestStats: failure.requestStats,
21
+ })
22
+ | _ => failure.cause
23
+ }
24
+ }
25
+
26
+ let mapNativeFailureExn = exn => exn->Source.unpackNativeRequestFailure->mapNativeFailure
27
+
28
+ // Every HyperSync client paginates block hashes in Rust and returns the page
29
+ // store with the timings of the requests it took. A failure arrives as the
30
+ // native envelope, which carries those same timings, so the caller records them
31
+ // either way.
32
+ let makeGetBlockHashes = (
33
+ ~query: (~blockNumbers: array<int>) => promise<(BlockStore.t, array<Source.requestStat>)>,
34
+ ) =>
35
+ async (~blockNumbers, ~logger as _) => {
36
+ let (result, requestStats) = try {
37
+ let (blockStore, requestStats) = await query(~blockNumbers)
38
+ (Ok(blockStore), requestStats)
39
+ } catch {
40
+ | exn =>
41
+ let failure = exn->Source.unpackNativeRequestFailure
42
+ (Error(failure->mapNativeFailure), failure.requestStats)
13
43
  }
44
+ {Source.result, requestStats}
45
+ }
46
+
47
+ let reraiseIfRecoverable = exn =>
48
+ switch exn->mapNativeFailureExn {
49
+ | (Source.RateLimited(_) | Source.SourceBehindHead(_)) as exn => throw(exn)
14
50
  | _ => ()
15
51
  }
16
52
 
17
53
  type logsQueryPage = {
18
54
  items: array<HyperSyncClient.EventItems.item>,
19
- // Headers for every returned block, deduplicated by block number.
20
- blocks: array<HyperSyncClient.EventItems.blockHeader>,
21
55
  nextBlock: int,
22
56
  archiveHeight: int,
23
- rollbackGuard: option<HyperSyncClient.ResponseTypes.rollbackGuard>,
24
57
  // Page store owning this page's raw transactions.
25
58
  transactionStore: TransactionStore.t,
26
59
  // Page store owning this page's raw blocks.
27
60
  blockStore: BlockStore.t,
28
61
  }
29
62
 
30
- type missingParams = {
31
- queryName: string,
32
- missingParams: array<string>,
33
- }
34
- type queryError = UnexpectedMissingParams(missingParams)
35
-
36
- exception HyperSyncQueryError(queryError)
37
-
38
- let queryErrorToExn = queryError => {
39
- HyperSyncQueryError(queryError)
40
- }
41
-
42
- let queryErrorToMsq = (e: queryError): string => {
43
- switch e {
44
- | UnexpectedMissingParams({queryName, missingParams}) =>
45
- `${queryName} query failed due to unexpected missing params on response:
46
- ${missingParams->Array.joinUnsafe(", ")}`
47
- }
48
- }
49
-
50
- type queryResponse<'a> = result<'a, queryError>
51
- let mapExn = (queryResponse: queryResponse<'a>) =>
52
- switch queryResponse {
53
- | Ok(v) => Ok(v)
54
- | Error(err) => err->queryErrorToExn->Error
55
- }
56
-
57
63
  module GetLogs = {
58
64
  type error =
59
65
  | UnexpectedMissingParams({missingParams: array<string>})
@@ -109,7 +115,7 @@ module GetLogs = {
109
115
  ) {
110
116
  | res => res
111
117
  | exception exn =>
112
- reraisIfRateLimited(exn)
118
+ reraiseIfRecoverable(exn)
113
119
  switch extractMissingParams(exn) {
114
120
  | Some(missingParams) => throw(Error(UnexpectedMissingParams({missingParams: missingParams})))
115
121
  | None => throw(exn)
@@ -122,196 +128,10 @@ module GetLogs = {
122
128
 
123
129
  {
124
130
  items: res.items,
125
- blocks: res.blocks,
126
131
  nextBlock: res.nextBlock,
127
132
  archiveHeight: res.archiveHeight->Option.getOr(0), //Archive Height is only None if height is 0
128
- rollbackGuard: res.rollbackGuard,
129
133
  transactionStore,
130
134
  blockStore,
131
135
  }
132
136
  }
133
137
  }
134
-
135
- module BlockData = {
136
- let makeRequestBody = (~fromBlock, ~toBlock): HyperSyncClient.QueryTypes.query => {
137
- fromBlock,
138
- toBlockExclusive: toBlock + 1,
139
- fieldSelection: {
140
- block: [Number, Hash, Timestamp],
141
- },
142
- includeAllBlocks: true,
143
- }
144
-
145
- let convertResponse = (res: HyperSyncClient.queryResponse): queryResponse<
146
- array<ReorgDetection.blockDataWithTimestamp>,
147
- > => {
148
- res.data.blocks
149
- ->Array.map(block => {
150
- switch block {
151
- | {number: blockNumber, hash: blockHash, timestamp: blockTimestamp} =>
152
- Ok(
153
- (
154
- {
155
- blockTimestamp,
156
- blockNumber,
157
- blockHash,
158
- }: ReorgDetection.blockDataWithTimestamp
159
- ),
160
- )
161
- | _ =>
162
- let missingParams =
163
- [
164
- block.number->Utils.Option.mapNone("block.number"),
165
- block.timestamp->Utils.Option.mapNone("block.timestamp"),
166
- block.hash->Utils.Option.mapNone("block.hash"),
167
- ]->Array.filterMap(p => p)
168
-
169
- Error(
170
- UnexpectedMissingParams({
171
- queryName: "query block data HyperSync",
172
- missingParams,
173
- }),
174
- )
175
- }
176
- })
177
- ->Utils.Array.transposeResults
178
- }
179
-
180
- let rec queryBlockData = async (
181
- ~client: HyperSyncClient.t,
182
- ~fromBlock,
183
- ~toBlock,
184
- ~sourceName,
185
- ~chainId,
186
- ~logger,
187
- ~requestStats: array<Source.requestStat>,
188
- ): queryResponse<array<ReorgDetection.blockDataWithTimestamp>> => {
189
- let body = makeRequestBody(~fromBlock, ~toBlock)
190
-
191
- let logger = Logging.createChildFrom(
192
- ~logger,
193
- ~params={
194
- "logType": "HyperSync get block hash query",
195
- "fromBlock": fromBlock,
196
- "toBlock": toBlock,
197
- },
198
- )
199
-
200
- let timerRef = Performance.now()
201
- let maybeSuccessfulRes = switch await client.get(~query=body) {
202
- | exception exn =>
203
- reraisIfRateLimited(exn)
204
- None
205
- | res if res.nextBlock <= fromBlock => None
206
- | res => Some(res)
207
- }
208
- requestStats
209
- ->Array.push({Source.method: "getBlockHashes", seconds: timerRef->Performance.secondsSince})
210
- ->ignore
211
-
212
- // If the block is not found, retry the query. This can occur since replicas of hypersync might not have caught up yet
213
- switch maybeSuccessfulRes {
214
- | None => {
215
- let delayMilliseconds = 100
216
- logger->Logging.childInfo(
217
- `Block #${fromBlock->Int.toString} not found in HyperSync. HyperSync has multiple instances and it's possible that they drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${delayMilliseconds->Int.toString}ms.`,
218
- )
219
- await Time.resolvePromiseAfterDelay(~delayMilliseconds)
220
- await queryBlockData(
221
- ~client,
222
- ~fromBlock,
223
- ~toBlock,
224
- ~sourceName,
225
- ~chainId,
226
- ~logger,
227
- ~requestStats,
228
- )
229
- }
230
- | Some(res) =>
231
- switch res->convertResponse {
232
- | Error(_) as err => err
233
- | Ok(datas) if res.nextBlock <= toBlock => {
234
- let restRes = await queryBlockData(
235
- ~client,
236
- ~fromBlock=res.nextBlock,
237
- ~toBlock,
238
- ~sourceName,
239
- ~chainId,
240
- ~logger,
241
- ~requestStats,
242
- )
243
- restRes->Result.map(rest => datas->Array.concat(rest))
244
- }
245
- | Ok(_) as ok => ok
246
- }
247
- }
248
- }
249
-
250
- let queryBlockDataMulti = async (
251
- ~client: HyperSyncClient.t,
252
- ~blockNumbers,
253
- ~sourceName,
254
- ~chainId,
255
- ~logger,
256
- ): (queryResponse<array<ReorgDetection.blockDataWithTimestamp>>, array<Source.requestStat>) => {
257
- let requestStats = []
258
- let result = switch blockNumbers->Array.get(0) {
259
- | None => Ok([])
260
- | Some(firstBlock) => {
261
- let fromBlock = ref(firstBlock)
262
- let toBlock = ref(firstBlock)
263
- let set = Utils.Set.make()
264
- for idx in 0 to blockNumbers->Array.length - 1 {
265
- let blockNumber = blockNumbers->Array.getUnsafe(idx)
266
- if blockNumber < fromBlock.contents {
267
- fromBlock := blockNumber
268
- }
269
- if blockNumber > toBlock.contents {
270
- toBlock := blockNumber
271
- }
272
- set->Utils.Set.add(blockNumber)->ignore
273
- }
274
- if toBlock.contents - fromBlock.contents > 1000 {
275
- JsError.throwWithMessage(
276
- `Invalid block data request. Range of block numbers is too large. Max range is 1000. Requested range: ${fromBlock.contents->Int.toString}-${toBlock.contents->Int.toString}`,
277
- )
278
- }
279
- let res = await queryBlockData(
280
- ~fromBlock=fromBlock.contents,
281
- ~toBlock=toBlock.contents,
282
- ~client,
283
- ~sourceName,
284
- ~chainId,
285
- ~logger,
286
- ~requestStats,
287
- )
288
- let filtered = res->Result.map(datas => {
289
- datas->Array.filter(data => set->Utils.Set.delete(data.blockNumber))
290
- })
291
- if set->Utils.Set.size > 0 {
292
- JsError.throwWithMessage(
293
- `Invalid response. Failed to get block data for block numbers: ${set
294
- ->Utils.Set.toArray
295
- ->Array.joinUnsafe(", ")}`,
296
- )
297
- }
298
- filtered
299
- }
300
- }
301
- (result, requestStats)
302
- }
303
- }
304
-
305
- let queryBlockData = (~client, ~blockNumber, ~sourceName, ~chainId, ~logger) => {
306
- let requestStats = []
307
- BlockData.queryBlockData(
308
- ~client,
309
- ~fromBlock=blockNumber,
310
- ~toBlock=blockNumber,
311
- ~sourceName,
312
- ~chainId,
313
- ~logger,
314
- ~requestStats,
315
- )->Promise.thenResolve(res => (res->Result.map(res => res->Array.get(0)), requestStats))
316
- }
317
- let queryBlockDataMulti = BlockData.queryBlockDataMulti