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
@@ -4,6 +4,12 @@
4
4
  // page that is merged in. At batch preparation the selected fields are
5
5
  // materialised in bulk, off the JS thread, in columnar form and zipped into
6
6
  // plain JS objects on the main thread.
7
+ //
8
+ // The store also owns response validation and reorg detection. A response page
9
+ // records conflicts found while it is built; SourceManager rejects those pages
10
+ // before the persistent store is touched. Merging a validated page compares
11
+ // only persistent-vs-response hashes, and pruning keeps the hash of processed
12
+ // blocks still inside the reorg threshold.
7
13
  type t
8
14
 
9
15
  @send external newEvm: (Core.blockStoreCtor, ~shouldChecksum: bool) => t = "newEvm"
@@ -26,8 +32,93 @@ let make = (~ecosystem: Ecosystem.name, ~shouldChecksum: bool): t => {
26
32
  // with the Rust store, `EvmBlockField`).
27
33
  let makeMaskFn = FieldMask.makeMaskFn
28
34
 
29
- // Drain another store (a fetch-response page) into this one.
30
- @send external merge: (t, t) => unit = "merge"
35
+ // Sparse JS blocks accepted by the `fromJs*` page constructors. Every field is
36
+ // optional except the key, so a page can carry anything from a full block to a
37
+ // hash-only reorg observation. The Rust side re-encodes them through the same
38
+ // column fill as fetched blocks.
39
+ type evmBlockInput = {number: int, hash?: string, timestamp?: int}
40
+ type svmBlockInput = {slot: int, hash?: string, time?: int}
41
+ type fuelBlockInput = {height: int, id?: string, time?: int}
42
+
43
+ @send
44
+ external fromJsEvm: (Core.blockStoreCtor, array<evmBlockInput>, bool) => t = "fromJsEvm"
45
+ @send
46
+ external fromJsSvm: (Core.blockStoreCtor, array<svmBlockInput>) => t = "fromJsSvm"
47
+ @send
48
+ external fromJsFuel: (Core.blockStoreCtor, array<fuelBlockInput>) => t = "fromJsFuel"
49
+
50
+ // An ecosystem-agnostic (number, hash, timestamp) observation, mapped onto the
51
+ // ecosystem's own field names when the page is built.
52
+ type inputBlock = {blockNumber: int, blockHash?: string, blockTimestamp?: int}
53
+
54
+ // Build a page from JS-observed blocks (RPC responses, stored reorg
55
+ // checkpoints) for merging into the per-chain store.
56
+ let fromJs = (blocks: array<inputBlock>, ~ecosystem: Ecosystem.name, ~shouldChecksum): t => {
57
+ let ctor = Core.getAddon().blockStore
58
+ switch ecosystem {
59
+ | Evm =>
60
+ ctor->fromJsEvm(
61
+ blocks->Array.map(b => {
62
+ number: b.blockNumber,
63
+ hash: ?b.blockHash,
64
+ timestamp: ?b.blockTimestamp,
65
+ }),
66
+ shouldChecksum,
67
+ )
68
+ | Svm =>
69
+ ctor->fromJsSvm(
70
+ blocks->Array.map(b => {
71
+ slot: b.blockNumber,
72
+ hash: ?b.blockHash,
73
+ time: ?b.blockTimestamp,
74
+ }),
75
+ )
76
+ | Fuel =>
77
+ ctor->fromJsFuel(
78
+ blocks->Array.map(b => {
79
+ height: b.blockNumber,
80
+ id: ?b.blockHash,
81
+ time: ?b.blockTimestamp,
82
+ }),
83
+ )
84
+ }
85
+ }
86
+
87
+ // The lowest merged block at or above the reorg threshold whose received hash
88
+ // differed from the stored one.
89
+ type hashMismatch = {
90
+ blockNumber: int,
91
+ storedHash: string,
92
+ receivedHash: string,
93
+ }
94
+
95
+ // Drain another store (a fetch-response page) into this one, comparing hashes
96
+ // on the way. Blocks below `fromBlock` (outside the reorg threshold) or without
97
+ // a hash on either side are merged without comparison. On a mismatch the page
98
+ // is discarded - the stored hashes stay for the rollback comparison - unless
99
+ // `reportOnly` is set (detect-only mode), which merges anyway so the same
100
+ // mismatch doesn't re-report on every response.
101
+ @send
102
+ external merge: (t, t, ~fromBlock: int, ~reportOnly: bool) => Null.t<hashMismatch> = "merge"
103
+
104
+ // Append a backend page to a logical response store. This always appends rows;
105
+ // any conflict is retained as response metadata for SourceManager to validate.
106
+ @send
107
+ external appendPage: (t, t) => unit = "appendPage"
108
+
109
+ // A conflict observed within the response itself. Such a response is
110
+ // discarded and retried, rather than treated as a chain reorg.
111
+ @send external responseConflict: t => Null.t<hashMismatch> = "responseConflict"
112
+
113
+ // Requested block numbers not covered by this response. SVM gaps count as
114
+ // covered when HyperSync's cursor has fully processed their half-open range;
115
+ // parent slot/hash links are validated separately as response consistency.
116
+ @send external missingHashes: (t, array<int>) => array<int> = "missingHashes"
117
+
118
+ // Compare a validated response store against the persistent store in ascending
119
+ // block order and stop at the first mismatch.
120
+ @send
121
+ external latestValidBlockFromStore: (t, t, array<int>) => Null.t<int> = "latestValidBlockFromStore"
31
122
 
32
123
  // Bulk-materialise blocks off the JS thread, one row per `blockNumbers[i]` key,
33
124
  // decoding only the fields set in that row's own `masks[i]`. Result is aligned
@@ -39,8 +130,27 @@ external materialize: (
39
130
  ~masks: array<float>,
40
131
  ) => promise<array<Internal.eventBlock>> = "materialize"
41
132
 
42
- // Drop blocks at or below the given block (already processed).
43
- @send external prune: (t, int) => unit = "prune"
133
+ // Drop blocks at or below the given block (already processed), keeping the
134
+ // hashes of blocks at or above `keepHashesFrom` for reorg detection.
135
+ @send external prune: (t, int, ~keepHashesFrom: int) => unit = "prune"
44
136
 
45
- // Drop blocks above the given block (rolled back).
137
+ // Drop blocks above the given block (rolled back), hashes included. The
138
+ // rolled-back range is refetched, so keeping any of its hashes would compare
139
+ // the refetch against a fork the rollback already disproved.
46
140
  @send external rollback: (t, int) => unit = "rollback"
141
+
142
+ // Hash of a stored block, if the store still holds it.
143
+ @send external getHash: (t, int) => Null.t<string> = "getHash"
144
+
145
+ // Block numbers in `[fromBlock, belowBlock)` with a stored hash, ascending.
146
+ @send
147
+ external getHashedBlockNumbers: (t, ~fromBlock: int, ~belowBlock: int) => array<int> =
148
+ "getHashedBlockNumbers"
149
+
150
+ // Every stored hash in `[fromBlock, belowBlock)` as two aligned columns,
151
+ // ascending by block number - one crossing for what `getHash` would charge per
152
+ // block.
153
+ type hashedBlocks = {blockNumbers: array<int>, hashes: array<string>}
154
+
155
+ @send
156
+ external getHashes: (t, ~fromBlock: int, ~belowBlock: int) => hashedBlocks = "getHashes"
@@ -15,10 +15,35 @@ function make(ecosystem, shouldChecksum) {
15
15
  }
16
16
  }
17
17
 
18
+ function fromJs(blocks, ecosystem, shouldChecksum) {
19
+ let ctor = Core.getAddon().BlockStore;
20
+ switch (ecosystem) {
21
+ case "evm" :
22
+ return ctor.fromJsEvm(blocks.map(b => ({
23
+ number: b.blockNumber,
24
+ hash: b.blockHash,
25
+ timestamp: b.blockTimestamp
26
+ })), shouldChecksum);
27
+ case "fuel" :
28
+ return ctor.fromJsFuel(blocks.map(b => ({
29
+ height: b.blockNumber,
30
+ id: b.blockHash,
31
+ time: b.blockTimestamp
32
+ })));
33
+ case "svm" :
34
+ return ctor.fromJsSvm(blocks.map(b => ({
35
+ slot: b.blockNumber,
36
+ hash: b.blockHash,
37
+ time: b.blockTimestamp
38
+ })));
39
+ }
40
+ }
41
+
18
42
  let makeMaskFn = FieldMask.makeMaskFn;
19
43
 
20
44
  export {
21
45
  make,
22
46
  makeMaskFn,
47
+ fromJs,
23
48
  }
24
49
  /* Core Not a pure module */
@@ -76,7 +76,6 @@ let make = (~logger: Pino.t): Ecosystem.t => {
76
76
  blockNumberName: "number",
77
77
  blockTimestampName: "timestamp",
78
78
  blockHashName: "hash",
79
- cleanUpRawEventFieldsInPlace,
80
79
  onBlockMethodName: "onBlock",
81
80
  // EVM filter shape: `{block: {number: {_gte?, _lte?, _every?}}}`.
82
81
  // The inner range chunk is returned as raw `S.unknown` and parsed a
@@ -55,7 +55,6 @@ function make(logger) {
55
55
  blockNumberName: "number",
56
56
  blockTimestampName: "timestamp",
57
57
  blockHashName: "hash",
58
- cleanUpRawEventFieldsInPlace: cleanUpRawEventFieldsInPlace,
59
58
  onBlockMethodName: "onBlock",
60
59
  onBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("number", S$RescriptSchema.unknown))))),
61
60
  onEventBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.strict(S$RescriptSchema.object(s2 => s2.f("number", S$RescriptSchema.unknown)))))),
@@ -79,7 +79,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
79
79
  payload: {
80
80
  contractName: onEventRegistration.eventConfig.contractName,
81
81
  eventName: onEventRegistration.eventConfig.name,
82
- chainId: chainId,
82
+ chainId,
83
83
  params: item.params,
84
84
  srcAddress,
85
85
  logIndex,
@@ -112,33 +112,23 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
112
112
  ~addressSet,
113
113
  ~clientFilteredContracts=selection.clientFilteredContracts,
114
114
  ) catch {
115
- | HyperSync.GetLogs.Error(error) =>
115
+ | HyperSync.GetLogs.Error(WrongInstance) =>
116
+ throw(Source.SourceBehindHead({blockNumber: fromBlock, requestStats: []}))
117
+ | HyperSync.GetLogs.Error(UnexpectedMissingParams({missingParams})) =>
116
118
  throw(
117
119
  Source.GetItemsError(
118
120
  Source.FailedGettingItems({
119
121
  exn: %raw(`null`),
120
122
  attemptedToBlock: toBlock->Option.getOr(knownHeight),
121
- retry: switch error {
122
- | WrongInstance =>
123
- let backoffMillis = switch retry {
124
- | 0 => 100
125
- | _ => 500 * retry
126
- }
127
- WithBackoff({
128
- message: `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 ${backoffMillis->Int.toString}ms.`,
129
- backoffMillis,
130
- })
131
- | UnexpectedMissingParams({missingParams}) =>
132
- ImpossibleForTheQuery({
133
- message: `Source returned invalid data with missing required fields: ${missingParams->Array.joinUnsafe(
134
- ", ",
135
- )}`,
136
- })
137
- },
123
+ retry: ImpossibleForTheQuery({
124
+ message: `Source returned invalid data with missing required fields: ${missingParams->Array.joinUnsafe(
125
+ ", ",
126
+ )}`,
127
+ }),
138
128
  }),
139
129
  ),
140
130
  )
141
- | Source.RateLimited(_) as exn => throw(exn)
131
+ | (Source.RateLimited(_) | Source.SourceBehindHead(_)) as exn => throw(exn)
142
132
  | exn =>
143
133
  throw(
144
134
  Source.GetItemsError(
@@ -173,13 +163,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
173
163
  //Parse page items into queue items
174
164
  let parsedQueueItems = []
175
165
 
176
- // Block headers are returned once per number; items reference them by blockNumber.
177
- let blocksByNumber = Utils.Map.make()
178
- pageUnsafe.blocks->Array.forEach(block => {
179
- blocksByNumber->Utils.Map.set(block.number, block)->ignore
180
- })
181
- let getBlock = blockNumber => blocksByNumber->Utils.Map.unsafeGet(blockNumber)
182
-
183
166
  pageUnsafe.items->Array.forEach(item => {
184
167
  let onEventRegistration = onEventRegistrations->Array.getUnsafe(item.onEventRegistrationIndex)
185
168
  parsedQueueItems
@@ -189,43 +172,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
189
172
 
190
173
  let parsingTimeElapsed = parsingTimeRef->Performance.secondsSince
191
174
 
192
- // Collect (blockNumber, blockHash) pairs we already have from the response —
193
- // one per returned block plus, when present, the rollbackGuard's head block
194
- // and the parent of the range's first block. Duplicates are allowed; reorg
195
- // detection notices same-block-number-different-hash collisions itself.
196
- let blockHashes = []
197
- pageUnsafe.blocks->Array.forEach(block => {
198
- blockHashes
199
- ->Array.push({ReorgDetection.blockNumber: block.number, blockHash: block.hash})
200
- ->ignore
201
- })
202
- switch pageUnsafe.rollbackGuard {
203
- | None => ()
204
- | Some({blockNumber, hash, firstBlockNumber, firstParentHash}) => {
205
- blockHashes->Array.push({ReorgDetection.blockNumber, blockHash: hash})->ignore
206
- blockHashes
207
- ->Array.push({
208
- ReorgDetection.blockNumber: firstBlockNumber - 1,
209
- blockHash: firstParentHash,
210
- })
211
- ->ignore
212
- }
213
- }
214
-
215
- // Best-effort timestamp for the queried-range head: prefer the rollbackGuard
216
- // (set at the head for unconfirmed blocks), otherwise the last item if it
217
- // happens to be in the range's last block. 0 is a tolerated placeholder
218
- // when neither is available (FetchState already uses 0 in several spots).
219
- let latestFetchedBlockTimestamp = switch pageUnsafe.rollbackGuard {
220
- | Some({timestamp}) => timestamp
221
- | None =>
222
- switch pageUnsafe.items->Array.get(pageUnsafe.items->Array.length - 1) {
223
- | Some(item) if item.blockNumber == heighestBlockQueried =>
224
- getBlock(item.blockNumber).timestamp
225
- | _ => 0
226
- }
227
- }
228
-
229
175
  let totalTimeElapsed = totalTimeRef->Performance.secondsSince
230
176
 
231
177
  let stats = {
@@ -235,30 +181,24 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
235
181
  }
236
182
 
237
183
  {
238
- latestFetchedBlockTimestamp,
239
184
  parsedQueueItems,
240
185
  transactionStore: Some(pageUnsafe.transactionStore),
241
- blockStore: Some(pageUnsafe.blockStore),
186
+ // The page store also carries the rollbackGuard's blocks (head block and
187
+ // parent of the range's first block), inserted on the Rust side.
188
+ blockStore: pageUnsafe.blockStore,
242
189
  latestFetchedBlockNumber: heighestBlockQueried,
243
190
  stats,
244
191
  knownHeight,
245
- blockHashes,
246
192
  fromBlockQueried: fromBlock,
247
193
  requestStats,
248
194
  }
249
195
  }
250
196
 
251
- let getBlockHashes = (~blockNumbers, ~logger) =>
252
- HyperSync.queryBlockDataMulti(
253
- ~client,
254
- ~blockNumbers,
255
- ~sourceName=name,
256
- ~chainId=chainId,
257
- ~logger,
258
- )->Promise.thenResolve(((queryRes, requestStats)) => {
259
- Source.result: queryRes->HyperSync.mapExn,
260
- requestStats,
261
- })
197
+ // Called through the client rather than passed as a value: the client is a
198
+ // napi class, so a detached method reference loses the instance it belongs to.
199
+ let getBlockHashes = HyperSync.makeGetBlockHashes(
200
+ ~query=(~blockNumbers) => client.getBlockHashes(~blockNumbers),
201
+ )
262
202
 
263
203
  {
264
204
  name,
@@ -287,11 +227,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
287
227
  },
288
228
  getItemsOrThrow,
289
229
  createHeightSubscription: (~onHeight) =>
290
- HyperSyncHeightStream.subscribe(
291
- ~hyperSyncUrl=endpointUrl,
292
- ~apiToken,
293
- ~chainId=chainId,
294
- ~onHeight,
295
- ),
230
+ HyperSyncHeightStream.subscribe(~hyperSyncUrl=endpointUrl, ~apiToken, ~chainId, ~onHeight),
296
231
  }
297
232
  }
@@ -22,7 +22,6 @@ function make(param) {
22
22
  let onEventRegistrations = param.onEventRegistrations;
23
23
  let endpointUrl = param.endpointUrl;
24
24
  let chainId = param.chainId;
25
- let name = "HyperSync";
26
25
  let apiToken$1 = apiToken !== undefined ? apiToken : Stdlib_JsError.throwWithMessage(`An Envio API token is required for using HyperSync as a data-source.
27
26
  Set the ENVIO_API_TOKEN environment variable in your .env file.
28
27
  Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
@@ -58,22 +57,16 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
58
57
  let pageUnsafe;
59
58
  try {
60
59
  pageUnsafe = await HyperSync.GetLogs.query(client, fromBlock, toBlock, itemsTarget, selection.onEventRegistrations.map(reg => reg.index), addressSet, selection.clientFilteredContracts);
61
- } catch (raw_error) {
62
- let error = Primitive_exceptions.internalToException(raw_error);
63
- if (error.RE_EXN_ID === HyperSync.GetLogs.$$Error) {
64
- let error$1 = error._1;
65
- let tmp;
66
- if (typeof error$1 !== "object") {
67
- let backoffMillis = retry !== 0 ? 500 * retry | 0 : 100;
68
- tmp = {
69
- TAG: "WithBackoff",
70
- message: `Block #` + fromBlock.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 ` + backoffMillis.toString() + `ms.`,
71
- backoffMillis: backoffMillis
72
- };
73
- } else {
74
- tmp = {
75
- TAG: "ImpossibleForTheQuery",
76
- message: `Source returned invalid data with missing required fields: ` + error$1.missingParams.join(", ")
60
+ } catch (raw_exn) {
61
+ let exn = Primitive_exceptions.internalToException(raw_exn);
62
+ if (exn.RE_EXN_ID === HyperSync.GetLogs.$$Error) {
63
+ let match = exn._1;
64
+ if (typeof match !== "object") {
65
+ throw {
66
+ RE_EXN_ID: Source.SourceBehindHead,
67
+ blockNumber: fromBlock,
68
+ requestStats: [],
69
+ Error: new Error()
77
70
  };
78
71
  }
79
72
  throw {
@@ -82,19 +75,25 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
82
75
  TAG: "FailedGettingItems",
83
76
  exn: null,
84
77
  attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight),
85
- retry: tmp
78
+ retry: {
79
+ TAG: "ImpossibleForTheQuery",
80
+ message: `Source returned invalid data with missing required fields: ` + match.missingParams.join(", ")
81
+ }
86
82
  },
87
83
  Error: new Error()
88
84
  };
89
85
  }
90
- if (error.RE_EXN_ID === Source.RateLimited) {
91
- throw error;
86
+ if (exn.RE_EXN_ID === Source.RateLimited) {
87
+ throw exn;
88
+ }
89
+ if (exn.RE_EXN_ID === Source.SourceBehindHead) {
90
+ throw exn;
92
91
  }
93
92
  throw {
94
93
  RE_EXN_ID: Source.GetItemsError,
95
94
  _1: {
96
95
  TAG: "FailedGettingItems",
97
- exn: error,
96
+ exn: exn,
98
97
  attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight),
99
98
  retry: {
100
99
  TAG: "WithBackoff",
@@ -114,41 +113,11 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
114
113
  let heighestBlockQueried = pageUnsafe.nextBlock - 1 | 0;
115
114
  let parsingTimeRef = Performance.now();
116
115
  let parsedQueueItems = [];
117
- let blocksByNumber = new Map();
118
- pageUnsafe.blocks.forEach(block => {
119
- blocksByNumber.set(block.number, block);
120
- });
121
116
  pageUnsafe.items.forEach(item => {
122
117
  let onEventRegistration = onEventRegistrations[item.onEventRegistrationIndex];
123
118
  parsedQueueItems.push(makeEventBatchQueueItem(item, onEventRegistration));
124
119
  });
125
120
  let parsingTimeElapsed = Performance.secondsSince(parsingTimeRef);
126
- let blockHashes = [];
127
- pageUnsafe.blocks.forEach(block => {
128
- blockHashes.push({
129
- blockHash: block.hash,
130
- blockNumber: block.number
131
- });
132
- });
133
- let match = pageUnsafe.rollbackGuard;
134
- if (match !== undefined) {
135
- blockHashes.push({
136
- blockHash: match.hash,
137
- blockNumber: match.blockNumber
138
- });
139
- blockHashes.push({
140
- blockHash: match.firstParentHash,
141
- blockNumber: match.firstBlockNumber - 1 | 0
142
- });
143
- }
144
- let match$1 = pageUnsafe.rollbackGuard;
145
- let latestFetchedBlockTimestamp;
146
- if (match$1 !== undefined) {
147
- latestFetchedBlockTimestamp = match$1.timestamp;
148
- } else {
149
- let item = pageUnsafe.items[pageUnsafe.items.length - 1 | 0];
150
- latestFetchedBlockTimestamp = item !== undefined && item.blockNumber === heighestBlockQueried ? blocksByNumber.get(item.blockNumber).timestamp : 0;
151
- }
152
121
  let totalTimeElapsed = Performance.secondsSince(totalTimeRef);
153
122
  let stats_parsing$unknowntime$unknown$lpars$rpar = parsingTimeElapsed;
154
123
  let stats_page$unknownfetch$unknowntime$unknown$lpars$rpar = pageFetchTime;
@@ -159,23 +128,18 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
159
128
  };
160
129
  return {
161
130
  knownHeight: knownHeight$1,
162
- blockHashes: blockHashes,
163
131
  parsedQueueItems: parsedQueueItems,
164
132
  transactionStore: Primitive_option.some(pageUnsafe.transactionStore),
165
- blockStore: Primitive_option.some(pageUnsafe.blockStore),
133
+ blockStore: pageUnsafe.blockStore,
166
134
  fromBlockQueried: fromBlock,
167
135
  latestFetchedBlockNumber: heighestBlockQueried,
168
- latestFetchedBlockTimestamp: latestFetchedBlockTimestamp,
169
136
  stats: stats,
170
137
  requestStats: requestStats
171
138
  };
172
139
  };
173
- let getBlockHashes = (blockNumbers, logger) => HyperSync.queryBlockDataMulti(client, blockNumbers, name, chainId, logger).then(param => ({
174
- result: HyperSync.mapExn(param[0]),
175
- requestStats: param[1]
176
- }));
140
+ let getBlockHashes = HyperSync.makeGetBlockHashes(blockNumbers => client.getBlockHashes(blockNumbers));
177
141
  return {
178
- name: name,
142
+ name: "HyperSync",
179
143
  sourceFor: "Sync",
180
144
  chainId: chainId,
181
145
  poweredByHyperSync: true,
@@ -232,4 +196,4 @@ export {
232
196
  isUnauthorizedError,
233
197
  make,
234
198
  }
235
- /* Logging Not a pure module */
199
+ /* Source Not a pure module */
@@ -1,5 +1,7 @@
1
1
  // Fuel's concrete item payload. Erased to `Internal.eventPayload` on the item
2
- // and recovered here via `toPayload`.
2
+ // and recovered here via `toPayload`. The block lives raw in the per-chain
3
+ // store and is written onto the payload at batch prep (like HyperSync EVM);
4
+ // simulate builds it inline.
3
5
  type payload = {
4
6
  contractName: string,
5
7
  eventName: string,
@@ -8,11 +10,24 @@ type payload = {
8
10
  srcAddress: Address.t,
9
11
  logIndex: int,
10
12
  transaction: Internal.eventTransaction,
11
- block: Internal.eventBlock,
13
+ block?: Internal.eventBlock,
12
14
  }
13
15
  external fromPayload: payload => Internal.eventPayload = "%identity"
14
16
  external toPayload: Internal.eventPayload => payload = "%identity"
15
17
 
18
+ // Ordered block field names. The index of each is the field code shared with
19
+ // the Rust store (`FuelBlockField`) — keep this order in sync.
20
+ let blockFields = ["height", "id", "time"]
21
+
22
+ // Fuel has no per-event block field selection: every event materialises the
23
+ // full (height, time, id) trio, matching what the source always queries.
24
+ let eventBlockFieldMask = BlockStore.makeMaskFn(blockFields)
25
+
26
+ // Fuel keeps the transaction inline on the event payload rather than in the
27
+ // per-chain store, so there is nothing to select and no field code to hold.
28
+ let transactionFields = []
29
+ let eventTransactionFieldMask = TransactionStore.makeMaskFn(transactionFields)
30
+
16
31
  let cleanUpRawEventFieldsInPlace: JSON.t => unit = %raw(`fields => {
17
32
  delete fields.id
18
33
  delete fields.height
@@ -24,7 +39,6 @@ let make = (~logger: Pino.t): Ecosystem.t => {
24
39
  blockNumberName: "height",
25
40
  blockTimestampName: "time",
26
41
  blockHashName: "id",
27
- cleanUpRawEventFieldsInPlace,
28
42
  onBlockMethodName: "onBlock",
29
43
  // Fuel filter shape: `{block: {height: {_gte?, _lte?, _every?}}}`.
30
44
  // Inner range chunk parsed by `blockRangeSchema` in `Main.res`.
@@ -56,7 +70,13 @@ let make = (~logger: Pino.t): Ecosystem.t => {
56
70
  ),
57
71
  toRawEvent: eventItem => {
58
72
  let payload = eventItem.payload->toPayload
59
- let header = payload.block->(Utils.magic: Internal.eventBlock => {"id": string, "time": int})
73
+ // Store-backed payloads get `block` written at batch prep and simulate
74
+ // carries it from the start, so it's present by the time a raw event is built.
75
+ let header = switch payload.block {
76
+ | Some(block) => block->(Utils.magic: Internal.eventBlock => {"id": string, "time": int})
77
+ | None =>
78
+ JsError.throwWithMessage("Unexpected case: The event block is missing for a raw event")
79
+ }
60
80
  eventItem->RawEvent.make(
61
81
  ~block=payload.block,
62
82
  ~transaction=payload.transaction,
@@ -2,7 +2,23 @@
2
2
 
3
3
  import * as Logging from "../Logging.res.mjs";
4
4
  import * as RawEvent from "../RawEvent.res.mjs";
5
+ import * as BlockStore from "./BlockStore.res.mjs";
6
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
7
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
5
8
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
9
+ import * as TransactionStore from "./TransactionStore.res.mjs";
10
+
11
+ let blockFields = [
12
+ "height",
13
+ "id",
14
+ "time"
15
+ ];
16
+
17
+ let eventBlockFieldMask = BlockStore.makeMaskFn(blockFields);
18
+
19
+ let transactionFields = [];
20
+
21
+ let eventTransactionFieldMask = TransactionStore.makeMaskFn(transactionFields);
6
22
 
7
23
  let cleanUpRawEventFieldsInPlace = (fields => {
8
24
  delete fields.id
@@ -16,7 +32,6 @@ function make(logger) {
16
32
  blockNumberName: "height",
17
33
  blockTimestampName: "time",
18
34
  blockHashName: "id",
19
- cleanUpRawEventFieldsInPlace: cleanUpRawEventFieldsInPlace,
20
35
  onBlockMethodName: "onBlock",
21
36
  onBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("height", S$RescriptSchema.unknown))))),
22
37
  onEventBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.strict(S$RescriptSchema.object(s2 => s2.f("height", S$RescriptSchema.unknown)))))),
@@ -32,14 +47,19 @@ function make(logger) {
32
47
  }),
33
48
  toRawEvent: eventItem => {
34
49
  let payload = eventItem.payload;
35
- let header = payload.block;
50
+ let block = payload.block;
51
+ let header = block !== undefined ? Primitive_option.valFromOption(block) : Stdlib_JsError.throwWithMessage("Unexpected case: The event block is missing for a raw event");
36
52
  return RawEvent.make(eventItem, payload.block, payload.transaction, payload.params, payload.srcAddress, header.id, header.time, cleanUpRawEventFieldsInPlace);
37
53
  }
38
54
  };
39
55
  }
40
56
 
41
57
  export {
58
+ blockFields,
59
+ eventBlockFieldMask,
60
+ transactionFields,
61
+ eventTransactionFieldMask,
42
62
  cleanUpRawEventFieldsInPlace,
43
63
  make,
44
64
  }
45
- /* Logging Not a pure module */
65
+ /* eventBlockFieldMask Not a pure module */
@@ -1,9 +1,11 @@
1
1
  type logsQueryPage = {
2
2
  items: array<FuelHyperSyncClient.EventItems.item>,
3
3
  // Blocks referenced by `items`, one per height.
4
- blocks: array<FuelHyperSyncClient.EventItems.block>,
5
4
  nextBlock: int,
6
5
  archiveHeight: int,
6
+ // Page store owning this page's raw blocks (built on the Rust side); its ids
7
+ // drive reorg detection when merged into the chain store.
8
+ blockStore: BlockStore.t,
7
9
  }
8
10
 
9
11
  module GetLogs = {
@@ -53,7 +55,10 @@ module GetLogs = {
53
55
  clientFilteredContracts,
54
56
  }
55
57
 
56
- let res = switch await client->FuelHyperSyncClient.getEventItems(query, addressSet) {
58
+ let (res, blockStore) = switch await client->FuelHyperSyncClient.getEventItems(
59
+ query,
60
+ addressSet,
61
+ ) {
57
62
  | res => res
58
63
  | exception exn =>
59
64
  switch exn->extractMissingParams {
@@ -67,9 +72,9 @@ module GetLogs = {
67
72
  }
68
73
  {
69
74
  items: res.items,
70
- blocks: res.blocks,
71
75
  nextBlock: res.nextBlock,
72
76
  archiveHeight: res.archiveHeight->Option.getOr(0), // TODO: FIXME: Shouldn't have a default here
77
+ blockStore,
73
78
  }
74
79
  }
75
80
  }