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
@@ -10,17 +10,6 @@ type options = {
10
10
  addressStore: AddressStore.t,
11
11
  }
12
12
 
13
- // Synthesize a stable logIndex for an SVM instruction so the FetchState
14
- // ordering machinery (which compares by `(blockNumber, logIndex)`) sorts
15
- // instructions deterministically within a slot. The bit packing fits inside
16
- // JS's 53-bit safe-integer range: transactionIndex ≤ ~10k per slot,
17
- // instruction position ≤ 1000 per tx, depth ≤ ~10. Outer-only instructions
18
- // land at `tx * 65536`; inner ones append depth-weighted offsets.
19
- let synthLogIndex = (~transactionIndex, ~instructionAddress) => {
20
- let addrSum = instructionAddress->Array.reduce(0, (acc, n) => acc * 1024 + n + 1)
21
- transactionIndex * 65536 + addrSum
22
- }
23
-
24
13
  // Parse the Rust-decoded instruction (args/accounts arrive as JSON strings to
25
14
  // side-step napi-rs's lack of native JSON passthrough) into the public shape.
26
15
  let parseDecoded = (
@@ -68,7 +57,14 @@ let toSvmInstruction = (
68
57
  }
69
58
 
70
59
  let make = (
71
- {chainId, endpointUrl, apiToken, onEventRegistrations, clientTimeoutMillis, addressStore}: options,
60
+ {
61
+ chainId,
62
+ endpointUrl,
63
+ apiToken,
64
+ onEventRegistrations,
65
+ clientTimeoutMillis,
66
+ addressStore,
67
+ }: options,
72
68
  ): t => {
73
69
  let name = "SvmHyperSync"
74
70
 
@@ -112,6 +108,9 @@ let make = (
112
108
  ~addressSet,
113
109
  ) catch {
114
110
  | exn =>
111
+ // A rate limit or a replica behind the head is recoverable and has its own
112
+ // backoff and failover, so it must not be buried in a generic retry.
113
+ HyperSync.reraiseIfRecoverable(exn)
115
114
  throw(
116
115
  Source.GetItemsError(
117
116
  Source.FailedGettingItems({
@@ -133,17 +132,6 @@ let make = (
133
132
 
134
133
  let parsingRef = Performance.now()
135
134
 
136
- // Per-slot blockTime lookup from the response's `blocks` table, for the
137
- // batch's `latestFetchedBlockTimestamp`. Slots without a block row (rare;
138
- // usually skipped slots) fall back to `None`.
139
- let blockTimeBySlot = Dict.make()
140
- resp.blocks->Array.forEach(b => {
141
- switch b.blockTime {
142
- | Some(t) => blockTimeBySlot->Dict.set(b.slot->Int.toString, t)
143
- | None => ()
144
- }
145
- })
146
-
147
135
  let parsedQueueItems = resp.items->Array.map(item => {
148
136
  // Routing happened in Rust; the item references its registration by
149
137
  // chain-scoped index.
@@ -161,10 +149,13 @@ let make = (
161
149
  onEventRegistration,
162
150
  chainId,
163
151
  blockNumber: item.slot,
164
- logIndex: synthLogIndex(
165
- ~transactionIndex=item.transactionIndex,
166
- ~instructionAddress=item.instructionAddress,
167
- ),
152
+ // A slot orders by `(transactionIndex, instructionAddress)` — the
153
+ // transaction, then the instruction's position in its CPI tree. Both
154
+ // ride the item so the buffer comparator can order on the pair
155
+ // directly; no single integer can hold it (Solana allows a CPI depth
156
+ // of 5, which needs more bits than a JS integer is exact to).
157
+ logIndex: item.transactionIndex,
158
+ orderPath: item.instructionAddress,
168
159
  // The parent transaction is materialised from the store at batch prep.
169
160
  transactionIndex: item.transactionIndex,
170
161
  payload: payload->(Utils.magic: Envio.svmInstruction => Internal.eventPayload),
@@ -173,112 +164,29 @@ let make = (
173
164
 
174
165
  let parsingTimeElapsed = parsingRef->Performance.secondsSince
175
166
  let highestSlot = resp.nextSlot - 1
176
- let latestBlockTime =
177
- blockTimeBySlot
178
- ->Utils.Dict.dangerouslyGetNonOption(highestSlot->Int.toString)
179
- ->Option.getOr(0)
180
-
181
- // Best-effort (slot, blockhash) pairs from the blocks the server returned
182
- // for this range. Gaps (skipped slots, or slots without matched data) are
183
- // fine — reorg detection only compares hashes for slots it has observed.
184
- let blockHashes = resp.blocks->Array.map((b): ReorgDetection.blockData => {
185
- blockNumber: b.slot,
186
- blockHash: b.blockhash,
187
- })
188
167
 
189
168
  let totalTimeElapsed = totalTimeRef->Performance.secondsSince
190
169
 
191
170
  {
192
- latestFetchedBlockTimestamp: latestBlockTime,
193
171
  parsedQueueItems,
194
172
  // Raw transactions kept in Rust; materialised (selected fields) at batch prep.
195
173
  transactionStore: Some(transactionStore),
196
174
  // Raw blocks kept in Rust; materialised onto the payload at batch prep.
197
- blockStore: Some(blockStore),
175
+ // Their (slot, blockhash) pairs also drive reorg detection on merge.
176
+ blockStore,
198
177
  latestFetchedBlockNumber: highestSlot,
199
178
  stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
200
179
  knownHeight,
201
- blockHashes,
202
180
  fromBlockQueried: fromBlock,
203
181
  requestStats,
204
182
  }
205
183
  }
206
184
 
207
- // Fetch (slot, blockhash, blockTime) for blocks in an inclusive slot range,
208
- // paginating on the server's `nextSlot` cursor. `toSlot` is exclusive on the
209
- // wire, so we request `maxSlot + 1`; the caller filters to the exact slots.
210
- let queryBlockDataRange = async (~fromSlot, ~toSlot) => {
211
- let blockDatas = []
212
- let requestStats = []
213
- let fromRef = ref(fromSlot)
214
- let keepGoing = ref(true)
215
- while keepGoing.contents {
216
- let query: SvmHyperSyncClient.query = {
217
- fromSlot: fromRef.contents,
218
- toSlot: toSlot + 1,
219
- includeAllBlocks: true,
220
- fields: {block: [Slot, Blockhash, BlockTime]},
221
- maxNumBlocks: 1000,
222
- }
223
- let timerRef = Performance.now()
224
- // Block-only query; the store pages are empty.
225
- let (resp, _, _) = await client.get(~query)
226
- requestStats
227
- ->Array.push({Source.method: "getBlockHashes", seconds: timerRef->Performance.secondsSince})
228
- ->ignore
229
- resp.data.blocks->Array.forEach(b =>
230
- blockDatas
231
- ->Array.push({
232
- ReorgDetection.blockNumber: b.slot,
233
- blockHash: b.blockhash,
234
- blockTimestamp: b.blockTime->Option.getOr(0),
235
- })
236
- ->ignore
237
- )
238
-
239
- // `nextSlot` is the (exclusive) resume cursor. Stop once it passes the
240
- // range, or fails to advance — the latter guards against an infinite loop.
241
- if resp.nextSlot > toSlot || resp.nextSlot <= fromRef.contents {
242
- keepGoing := false
243
- } else {
244
- fromRef := resp.nextSlot
245
- }
246
- }
247
- (blockDatas, requestStats)
248
- }
249
-
250
- let getBlockHashes = async (~blockNumbers, ~logger as _) =>
251
- switch blockNumbers->Array.get(0) {
252
- | None => {Source.result: Ok([]), requestStats: []}
253
- | Some(firstSlot) =>
254
- try {
255
- let minSlot = ref(firstSlot)
256
- let maxSlot = ref(firstSlot)
257
- let requested = Utils.Set.make()
258
- blockNumbers->Array.forEach(slot => {
259
- if slot < minSlot.contents {
260
- minSlot := slot
261
- }
262
- if slot > maxSlot.contents {
263
- maxSlot := slot
264
- }
265
- requested->Utils.Set.add(slot)->ignore
266
- })
267
- let (blockDatas, requestStats) = await queryBlockDataRange(
268
- ~fromSlot=minSlot.contents,
269
- ~toSlot=maxSlot.contents,
270
- )
271
- // Keep one entry per requested slot; drop duplicates and unrelated slots.
272
- {
273
- Source.result: Ok(
274
- blockDatas->Array.filter(data => requested->Utils.Set.delete(data.blockNumber)),
275
- ),
276
- requestStats,
277
- }
278
- } catch {
279
- | exn => {Source.result: Error(exn), requestStats: []}
280
- }
281
- }
185
+ // Called through the client rather than passed as a value: the client is a
186
+ // napi class, so a detached method reference loses the instance it belongs to.
187
+ let getBlockHashes = HyperSync.makeGetBlockHashes(
188
+ ~query=(~blockNumbers) => client.getBlockHashes(~blockNumbers),
189
+ )
282
190
 
283
191
  {
284
192
  name,
@@ -1,18 +1,13 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Source from "./Source.res.mjs";
4
+ import * as HyperSync from "./HyperSync.res.mjs";
4
5
  import * as Performance from "../bindings/Performance.res.mjs";
5
- import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
6
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
7
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
8
8
  import * as SvmHyperSyncClient from "./SvmHyperSyncClient.res.mjs";
9
9
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
10
10
 
11
- function synthLogIndex(transactionIndex, instructionAddress) {
12
- let addrSum = Stdlib_Array.reduce(instructionAddress, 0, (acc, n) => ((acc << 10) + n | 0) + 1 | 0);
13
- return (transactionIndex << 16) + addrSum | 0;
14
- }
15
-
16
11
  function parseDecoded(d) {
17
12
  let args;
18
13
  try {
@@ -58,7 +53,7 @@ function toSvmInstruction(item, programName, instructionName) {
58
53
  function make(param) {
59
54
  let onEventRegistrations = param.onEventRegistrations;
60
55
  let chainId = param.chainId;
61
- let client = SvmHyperSyncClient.make(param.endpointUrl, param.apiToken, param.clientTimeoutMillis, undefined, undefined, undefined, SvmHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations), param.addressStore);
56
+ let client = SvmHyperSyncClient.make(param.endpointUrl, param.apiToken, param.clientTimeoutMillis, undefined, undefined, SvmHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations), param.addressStore);
62
57
  let getItemsOrThrow = async (fromBlock, toBlock, addressSet, knownHeight, param, selection, itemsTarget, retry, param$1) => {
63
58
  let totalTimeRef = Performance.now();
64
59
  let pageFetchRef = Performance.now();
@@ -76,6 +71,7 @@ function make(param) {
76
71
  match = await client.getEventItems(query, addressSet);
77
72
  } catch (raw_exn) {
78
73
  let exn = Primitive_exceptions.internalToException(raw_exn);
74
+ HyperSync.reraiseIfRecoverable(exn);
79
75
  throw {
80
76
  RE_EXN_ID: Source.GetItemsError,
81
77
  _1: {
@@ -98,14 +94,6 @@ function make(param) {
98
94
  seconds: pageFetchTime
99
95
  }];
100
96
  let parsingRef = Performance.now();
101
- let blockTimeBySlot = {};
102
- resp.blocks.forEach(b => {
103
- let t = b.blockTime;
104
- if (t !== undefined) {
105
- blockTimeBySlot[b.slot.toString()] = t;
106
- return;
107
- }
108
- });
109
97
  let parsedQueueItems = resp.items.map(item => {
110
98
  let onEventRegistration = onEventRegistrations[item.onEventRegistrationIndex];
111
99
  let eventConfig = onEventRegistration.eventConfig;
@@ -115,28 +103,22 @@ function make(param) {
115
103
  onEventRegistration: onEventRegistration,
116
104
  chainId: chainId,
117
105
  blockNumber: item.slot,
118
- logIndex: synthLogIndex(item.transactionIndex, item.instructionAddress),
106
+ logIndex: item.transactionIndex,
107
+ orderPath: item.instructionAddress,
119
108
  transactionIndex: item.transactionIndex,
120
109
  payload: payload
121
110
  };
122
111
  });
123
112
  let parsingTimeElapsed = Performance.secondsSince(parsingRef);
124
113
  let highestSlot = resp.nextSlot - 1 | 0;
125
- let latestBlockTime = Stdlib_Option.getOr(blockTimeBySlot[highestSlot.toString()], 0);
126
- let blockHashes = resp.blocks.map(b => ({
127
- blockHash: b.blockhash,
128
- blockNumber: b.slot
129
- }));
130
114
  let totalTimeElapsed = Performance.secondsSince(totalTimeRef);
131
115
  return {
132
116
  knownHeight: knownHeight,
133
- blockHashes: blockHashes,
134
117
  parsedQueueItems: parsedQueueItems,
135
118
  transactionStore: Primitive_option.some(match[1]),
136
- blockStore: Primitive_option.some(match[2]),
119
+ blockStore: match[2],
137
120
  fromBlockQueried: fromBlock,
138
121
  latestFetchedBlockNumber: highestSlot,
139
- latestFetchedBlockTimestamp: latestBlockTime,
140
122
  stats: {
141
123
  "total time elapsed (s)": totalTimeElapsed,
142
124
  "parsing time (s)": parsingTimeElapsed,
@@ -145,101 +127,7 @@ function make(param) {
145
127
  requestStats: requestStats
146
128
  };
147
129
  };
148
- let queryBlockDataRange = async (fromSlot, toSlot) => {
149
- let blockDatas = [];
150
- let requestStats = [];
151
- let fromRef = fromSlot;
152
- let keepGoing = true;
153
- while (keepGoing) {
154
- let query_toSlot = toSlot + 1 | 0;
155
- let query_includeAllBlocks = true;
156
- let query_fields = {
157
- block: [
158
- "slot",
159
- "blockhash",
160
- "block_time"
161
- ]
162
- };
163
- let query_maxNumBlocks = 1000;
164
- let query = {
165
- fromSlot: fromRef,
166
- toSlot: query_toSlot,
167
- includeAllBlocks: query_includeAllBlocks,
168
- fields: query_fields,
169
- maxNumBlocks: query_maxNumBlocks
170
- };
171
- let timerRef = Performance.now();
172
- let match = await client.get(query);
173
- let resp = match[0];
174
- requestStats.push({
175
- method: "getBlockHashes",
176
- seconds: Performance.secondsSince(timerRef)
177
- });
178
- resp.data.blocks.forEach(b => {
179
- blockDatas.push({
180
- blockHash: b.blockhash,
181
- blockNumber: b.slot,
182
- blockTimestamp: Stdlib_Option.getOr(b.blockTime, 0)
183
- });
184
- });
185
- if (resp.nextSlot > toSlot || resp.nextSlot <= fromRef) {
186
- keepGoing = false;
187
- } else {
188
- fromRef = resp.nextSlot;
189
- }
190
- };
191
- return [
192
- blockDatas,
193
- requestStats
194
- ];
195
- };
196
- let getBlockHashes = async (blockNumbers, param) => {
197
- let firstSlot = blockNumbers[0];
198
- if (firstSlot === undefined) {
199
- return {
200
- result: {
201
- TAG: "Ok",
202
- _0: []
203
- },
204
- requestStats: []
205
- };
206
- }
207
- try {
208
- let minSlot = {
209
- contents: firstSlot
210
- };
211
- let maxSlot = {
212
- contents: firstSlot
213
- };
214
- let requested = new Set();
215
- blockNumbers.forEach(slot => {
216
- if (slot < minSlot.contents) {
217
- minSlot.contents = slot;
218
- }
219
- if (slot > maxSlot.contents) {
220
- maxSlot.contents = slot;
221
- }
222
- requested.add(slot);
223
- });
224
- let match = await queryBlockDataRange(minSlot.contents, maxSlot.contents);
225
- return {
226
- result: {
227
- TAG: "Ok",
228
- _0: match[0].filter(data => requested.delete(data.blockNumber))
229
- },
230
- requestStats: match[1]
231
- };
232
- } catch (raw_exn) {
233
- let exn = Primitive_exceptions.internalToException(raw_exn);
234
- return {
235
- result: {
236
- TAG: "Error",
237
- _0: exn
238
- },
239
- requestStats: []
240
- };
241
- }
242
- };
130
+ let getBlockHashes = HyperSync.makeGetBlockHashes(blockNumbers => client.getBlockHashes(blockNumbers));
243
131
  return {
244
132
  name: "SvmHyperSync",
245
133
  sourceFor: "Sync",
@@ -264,9 +152,8 @@ function make(param) {
264
152
  }
265
153
 
266
154
  export {
267
- synthLogIndex,
268
155
  parseDecoded,
269
156
  toSvmInstruction,
270
157
  make,
271
158
  }
272
- /* SvmHyperSyncClient Not a pure module */
159
+ /* Source Not a pure module */
package/svm.schema.json CHANGED
@@ -511,7 +511,7 @@
511
511
  "type": "string",
512
512
  "enum": [
513
513
  "transactionIndex",
514
- "signatures",
514
+ "signature",
515
515
  "feePayer",
516
516
  "success",
517
517
  "err",
@@ -519,7 +519,8 @@
519
519
  "computeUnitsConsumed",
520
520
  "accountKeys",
521
521
  "recentBlockhash",
522
- "version"
522
+ "version",
523
+ "allSignatures"
523
524
  ]
524
525
  },
525
526
  "SvmBlockField": {