envio 3.3.0-alpha.0 → 3.3.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/evm.schema.json +10 -0
- package/index.d.ts +45 -25
- package/package.json +7 -7
- package/src/Address.res +5 -2
- package/src/Address.res.mjs +3 -1
- package/src/Api.res +1 -1
- package/src/Api.res.mjs +1 -1
- package/src/Batch.res +0 -6
- package/src/Batch.res.mjs +1 -12
- package/src/BatchProcessing.res +7 -12
- package/src/BatchProcessing.res.mjs +8 -7
- package/src/Bin.res +3 -0
- package/src/Bin.res.mjs +4 -0
- package/src/ChainFetching.res +48 -16
- package/src/ChainFetching.res.mjs +36 -17
- package/src/ChainMetadata.res +1 -11
- package/src/ChainMetadata.res.mjs +1 -10
- package/src/ChainState.res +628 -149
- package/src/ChainState.res.mjs +440 -102
- package/src/ChainState.resi +74 -4
- package/src/Config.res +61 -33
- package/src/Config.res.mjs +50 -10
- package/src/ContractRegisterContext.res +2 -12
- package/src/ContractRegisterContext.res.mjs +3 -5
- package/src/Core.res +14 -3
- package/src/CrossChainState.res +142 -93
- package/src/CrossChainState.res.mjs +88 -60
- package/src/CrossChainState.resi +2 -10
- package/src/Ecosystem.res +5 -6
- package/src/Ecosystem.res.mjs +3 -3
- package/src/Env.res +0 -7
- package/src/Env.res.mjs +0 -6
- package/src/Envio.res +17 -11
- package/src/EnvioGlobal.res +53 -0
- package/src/EnvioGlobal.res.mjs +31 -0
- package/src/EventConfigBuilder.res +177 -88
- package/src/EventConfigBuilder.res.mjs +99 -42
- package/src/EventProcessing.res +74 -28
- package/src/EventProcessing.res.mjs +55 -29
- package/src/ExitOnCaughtUp.res +10 -2
- package/src/ExitOnCaughtUp.res.mjs +10 -1
- package/src/FetchState.res +558 -300
- package/src/FetchState.res.mjs +437 -423
- package/src/HandlerLoader.res +8 -104
- package/src/HandlerLoader.res.mjs +2 -81
- package/src/HandlerRegister.res +518 -144
- package/src/HandlerRegister.res.mjs +285 -133
- package/src/HandlerRegister.resi +24 -8
- package/src/IndexerLoop.res +2 -3
- package/src/IndexerLoop.res.mjs +1 -1
- package/src/IndexerState.res +10 -11
- package/src/IndexerState.res.mjs +14 -11
- package/src/IndexerState.resi +2 -5
- package/src/IndexingAddresses.res +108 -0
- package/src/IndexingAddresses.res.mjs +101 -0
- package/src/IndexingAddresses.resi +34 -0
- package/src/Internal.res +155 -46
- package/src/Internal.res.mjs +30 -2
- package/src/LoadLayer.res +5 -5
- package/src/LoadLayer.res.mjs +6 -6
- package/src/LogSelection.res +102 -165
- package/src/LogSelection.res.mjs +101 -116
- package/src/Main.res +71 -236
- package/src/Main.res.mjs +60 -145
- package/src/Metrics.res +74 -0
- package/src/Metrics.res.mjs +75 -0
- package/src/PgStorage.res +4 -4
- package/src/PgStorage.res.mjs +5 -5
- package/src/Prometheus.res +10 -74
- package/src/Prometheus.res.mjs +124 -186
- package/src/PruneStaleHistory.res +2 -2
- package/src/PruneStaleHistory.res.mjs +3 -3
- package/src/RawEvent.res +7 -2
- package/src/RawEvent.res.mjs +4 -4
- package/src/Rollback.res +3 -3
- package/src/Rollback.res.mjs +4 -4
- package/src/RollbackCommit.res +4 -1
- package/src/RollbackCommit.res.mjs +3 -2
- package/src/SimulateDeadInputTracker.res +85 -0
- package/src/SimulateDeadInputTracker.res.mjs +73 -0
- package/src/SimulateDeadInputTracker.resi +12 -0
- package/src/SimulateItems.res +91 -23
- package/src/SimulateItems.res.mjs +55 -32
- package/src/TestIndexer.res +54 -40
- package/src/TestIndexer.res.mjs +37 -30
- package/src/bindings/Performance.res +7 -0
- package/src/bindings/Performance.res.mjs +21 -0
- package/src/bindings/Performance.resi +7 -0
- package/src/bindings/Viem.res +0 -41
- package/src/bindings/Viem.res.mjs +1 -43
- package/src/sources/BlockStore.res +46 -0
- package/src/sources/BlockStore.res.mjs +24 -0
- package/src/sources/EventRouter.res +36 -33
- package/src/sources/EventRouter.res.mjs +13 -14
- package/src/sources/Evm.res +83 -72
- package/src/sources/Evm.res.mjs +53 -69
- package/src/sources/EvmChain.res +18 -20
- package/src/sources/EvmChain.res.mjs +15 -15
- package/src/sources/EvmRpcClient.res +74 -5
- package/src/sources/EvmRpcClient.res.mjs +13 -4
- package/src/sources/FieldMask.res +39 -0
- package/src/sources/FieldMask.res.mjs +42 -0
- package/src/sources/Fuel.res +9 -6
- package/src/sources/Fuel.res.mjs +5 -10
- package/src/sources/HyperFuelSource.res +46 -50
- package/src/sources/HyperFuelSource.res.mjs +64 -54
- package/src/sources/HyperSync.res +39 -7
- package/src/sources/HyperSync.res.mjs +57 -37
- package/src/sources/HyperSync.resi +12 -2
- package/src/sources/HyperSyncClient.res +22 -111
- package/src/sources/HyperSyncClient.res.mjs +2 -30
- package/src/sources/HyperSyncHeightStream.res +1 -8
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
- package/src/sources/HyperSyncSource.res +94 -107
- package/src/sources/HyperSyncSource.res.mjs +82 -86
- package/src/sources/Rpc.res +15 -47
- package/src/sources/Rpc.res.mjs +25 -56
- package/src/sources/RpcSource.res +320 -467
- package/src/sources/RpcSource.res.mjs +268 -385
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +10 -4
- package/src/sources/SimulateSource.res.mjs +16 -6
- package/src/sources/Source.res +34 -6
- package/src/sources/SourceManager.res +86 -27
- package/src/sources/SourceManager.res.mjs +84 -47
- package/src/sources/SourceManager.resi +12 -3
- package/src/sources/Svm.res +31 -16
- package/src/sources/Svm.res.mjs +36 -13
- package/src/sources/SvmHyperSyncClient.res +5 -32
- package/src/sources/SvmHyperSyncSource.res +159 -134
- package/src/sources/SvmHyperSyncSource.res.mjs +164 -129
- package/src/sources/TransactionStore.res +49 -0
- package/src/sources/TransactionStore.res.mjs +30 -0
- package/src/tui/Tui.res +13 -16
- package/src/tui/Tui.res.mjs +12 -14
- package/svm.schema.json +49 -37
- package/src/bindings/Hrtime.res +0 -58
- package/src/bindings/Hrtime.res.mjs +0 -90
- package/src/bindings/Hrtime.resi +0 -30
|
@@ -4,7 +4,7 @@ type options = {
|
|
|
4
4
|
chain: ChainMap.Chain.t,
|
|
5
5
|
endpointUrl: string,
|
|
6
6
|
apiToken: option<string>,
|
|
7
|
-
|
|
7
|
+
onEventRegistrations: array<Internal.svmOnEventRegistration>,
|
|
8
8
|
clientTimeoutMillis: int,
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -184,13 +184,12 @@ let parseDecoded = (
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// `block` is omitted; it's materialised from the block store at batch prep.
|
|
187
188
|
let toSvmInstruction = (
|
|
188
189
|
instr: SvmHyperSyncClient.ResponseTypes.instruction,
|
|
189
190
|
~programName,
|
|
190
191
|
~instructionName,
|
|
191
|
-
~transaction,
|
|
192
192
|
~logs,
|
|
193
|
-
~block,
|
|
194
193
|
): Envio.svmInstruction => {
|
|
195
194
|
programName,
|
|
196
195
|
instructionName,
|
|
@@ -204,49 +203,23 @@ let toSvmInstruction = (
|
|
|
204
203
|
d4: ?instr.d4,
|
|
205
204
|
d8: ?instr.d8,
|
|
206
205
|
params: ?(instr.decoded->Option.map(parseDecoded)),
|
|
207
|
-
?transaction,
|
|
208
206
|
?logs,
|
|
209
|
-
block,
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
let toSvmTransaction = (tx: SvmHyperSyncClient.ResponseTypes.transaction): Envio.svmTransaction => {
|
|
213
|
-
signatures: tx.signatures,
|
|
214
|
-
accountKeys: tx.accountKeys->SvmTypes.Pubkey.fromStringsUnsafe,
|
|
215
|
-
feePayer: ?(tx.feePayer->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
216
|
-
success: ?tx.success,
|
|
217
|
-
err: ?tx.err,
|
|
218
|
-
// u64 lamports / compute units arrive as `int` over napi. Convert to
|
|
219
|
-
// `bigint` so the public type stays defensible even for pathological values.
|
|
220
|
-
fee: ?(tx.fee->Option.map(BigInt.fromInt)),
|
|
221
|
-
computeUnitsConsumed: ?(tx.computeUnitsConsumed->Option.map(BigInt.fromInt)),
|
|
222
|
-
recentBlockhash: ?tx.recentBlockhash,
|
|
223
|
-
version: ?tx.version,
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
let toSvmTokenBalance = (
|
|
227
|
-
tb: SvmHyperSyncClient.ResponseTypes.tokenBalance,
|
|
228
|
-
): Envio.svmTokenBalance => {
|
|
229
|
-
account: ?(tb.account->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
230
|
-
mint: ?(tb.mint->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
231
|
-
owner: ?(tb.owner->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
232
|
-
preAmount: ?tb.preAmount,
|
|
233
|
-
postAmount: ?tb.postAmount,
|
|
234
207
|
}
|
|
235
208
|
|
|
236
209
|
// Probe the discriminator byte-length ordering longest-first. Stops at the
|
|
237
210
|
// first router hit. Falls back to the `_none` key (program-wide handler) when
|
|
238
211
|
// no discriminator-keyed handler matches.
|
|
239
212
|
let probeRouter = (
|
|
240
|
-
router: EventRouter.t<Internal.
|
|
213
|
+
router: EventRouter.t<Internal.svmOnEventRegistration>,
|
|
241
214
|
programId: SvmTypes.Pubkey.t,
|
|
242
215
|
instr: SvmHyperSyncClient.ResponseTypes.instruction,
|
|
243
216
|
byteLengthsDesc: array<int>,
|
|
244
217
|
~contractAddress,
|
|
245
|
-
~
|
|
218
|
+
~contractNameByAddress,
|
|
246
219
|
) => {
|
|
247
220
|
let probe = (dN: option<string>) => {
|
|
248
221
|
let tag = EventRouter.getSvmEventId(~programId, ~discriminator=dN)
|
|
249
|
-
router->EventRouter.get(~tag, ~contractAddress, ~
|
|
222
|
+
router->EventRouter.get(~tag, ~contractAddress, ~contractNameByAddress)
|
|
250
223
|
}
|
|
251
224
|
|
|
252
225
|
let result = byteLengthsDesc->Array.reduce(None, (acc, len) =>
|
|
@@ -273,9 +246,51 @@ let probeRouter = (
|
|
|
273
246
|
}
|
|
274
247
|
}
|
|
275
248
|
|
|
276
|
-
|
|
249
|
+
// Map a selected transaction field to the extra query-side column it needs.
|
|
250
|
+
// `transactionIndex` is always fetched as the store key, and `tokenBalances`
|
|
251
|
+
// lives in a separate table (requested via `needsTokenBalances`), so neither
|
|
252
|
+
// adds a transaction column here.
|
|
253
|
+
let toQueryTxField = (field: Internal.svmTransactionField): option<
|
|
254
|
+
SvmHyperSyncClient.QueryTypes.transactionField,
|
|
255
|
+
> =>
|
|
256
|
+
switch field {
|
|
257
|
+
| TransactionIndex => None
|
|
258
|
+
| Signatures => Some(Signatures)
|
|
259
|
+
| FeePayer => Some(FeePayer)
|
|
260
|
+
| Success => Some(Success)
|
|
261
|
+
| Err => Some(Err)
|
|
262
|
+
| Fee => Some(Fee)
|
|
263
|
+
| ComputeUnitsConsumed => Some(ComputeUnitsConsumed)
|
|
264
|
+
| AccountKeys => Some(AccountKeys)
|
|
265
|
+
| RecentBlockhash => Some(RecentBlockhash)
|
|
266
|
+
| Version => Some(Version)
|
|
267
|
+
| TokenBalances => None
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Map a selected block field to its HyperSync query column. Slot/Blockhash/
|
|
271
|
+
// BlockTime are requested unconditionally (needed for reorg detection and the
|
|
272
|
+
// item's slot/timestamp), so selecting `slot`/`time`/`hash` adds no extra column.
|
|
273
|
+
let toQueryBlockField = (field: Internal.svmBlockField): option<
|
|
274
|
+
SvmHyperSyncClient.QueryTypes.blockField,
|
|
275
|
+
> =>
|
|
276
|
+
switch field {
|
|
277
|
+
| Slot | Time | Hash => None
|
|
278
|
+
| Height => Some(BlockHeight)
|
|
279
|
+
| ParentSlot => Some(ParentSlot)
|
|
280
|
+
| ParentHash => Some(ParentBlockhash)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let make = (
|
|
284
|
+
{chain, endpointUrl, apiToken, onEventRegistrations, clientTimeoutMillis}: options,
|
|
285
|
+
): t => {
|
|
277
286
|
let name = "SvmHyperSync"
|
|
278
|
-
|
|
287
|
+
|
|
288
|
+
// Definitions drive query/decode building; the registrations drive routing
|
|
289
|
+
// (they carry `isWildcard` and become each decoded item's `onEventRegistration`).
|
|
290
|
+
let eventConfigs =
|
|
291
|
+
onEventRegistrations->Array.map(reg =>
|
|
292
|
+
reg.eventConfig->(Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig)
|
|
293
|
+
)
|
|
279
294
|
|
|
280
295
|
// Built once at startup and handed to the client so `get` decodes matching
|
|
281
296
|
// instructions in Rust rather than per-instruction over the napi boundary.
|
|
@@ -290,7 +305,10 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
290
305
|
},
|
|
291
306
|
)
|
|
292
307
|
|
|
293
|
-
let (eventRouter, programOrderings) = EventRouter.fromSvmEventConfigsOrThrow(
|
|
308
|
+
let (eventRouter, programOrderings) = EventRouter.fromSvmEventConfigsOrThrow(
|
|
309
|
+
onEventRegistrations,
|
|
310
|
+
~chain,
|
|
311
|
+
)
|
|
294
312
|
|
|
295
313
|
// programId.toString -> sorted-desc byte lengths
|
|
296
314
|
let orderingByProgram = Dict.make()
|
|
@@ -298,23 +316,77 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
298
316
|
orderingByProgram->Dict.set(o.programId->SvmTypes.Pubkey.toString, o.byteLengthsDesc)
|
|
299
317
|
)
|
|
300
318
|
|
|
301
|
-
let needsTransactions = eventConfigs->Array.some(cfg => cfg.includeTransaction)
|
|
302
319
|
let needsLogs = eventConfigs->Array.some(cfg => cfg.includeLogs)
|
|
303
|
-
|
|
320
|
+
|
|
321
|
+
// Union of selected transaction fields across the chain's events. Drives both
|
|
322
|
+
// the query column selection (fetch only what's used) and the materialisation
|
|
323
|
+
// mask. `slot`/`transactionIndex` are always fetched as the store key.
|
|
324
|
+
let selectedTxFields = Utils.Set.make()
|
|
325
|
+
eventConfigs->Array.forEach(cfg =>
|
|
326
|
+
cfg.selectedTransactionFields
|
|
327
|
+
->(Utils.magic: Utils.Set.t<string> => Utils.Set.t<Internal.svmTransactionField>)
|
|
328
|
+
->Utils.Set.forEach(field => selectedTxFields->Utils.Set.add(field)->ignore)
|
|
329
|
+
)
|
|
330
|
+
let needsTokenBalances = selectedTxFields->Utils.Set.has(Internal.TokenBalances)
|
|
331
|
+
let txQueryFields = {
|
|
332
|
+
// Slot + TransactionIndex are always fetched so the store can be keyed by
|
|
333
|
+
// (slot, transactionIndex).
|
|
334
|
+
let fields: array<SvmHyperSyncClient.QueryTypes.transactionField> = [Slot, TransactionIndex]
|
|
335
|
+
selectedTxFields
|
|
336
|
+
->Utils.Set.toArray
|
|
337
|
+
->Array.forEach(field =>
|
|
338
|
+
switch toQueryTxField(field) {
|
|
339
|
+
| Some(queryField) => fields->Array.push(queryField)
|
|
340
|
+
| None => ()
|
|
341
|
+
}
|
|
342
|
+
)
|
|
343
|
+
fields
|
|
344
|
+
}
|
|
345
|
+
// The transaction table is fetched only when a selected field is actually read
|
|
346
|
+
// off a stored transaction record. `transactionIndex` materialises from the
|
|
347
|
+
// store key and `tokenBalances` lives in its own table, so neither requires it.
|
|
348
|
+
let needsTransactions =
|
|
349
|
+
selectedTxFields
|
|
350
|
+
->Utils.Set.toArray
|
|
351
|
+
->Array.some(field =>
|
|
352
|
+
switch field {
|
|
353
|
+
| Internal.TransactionIndex | Internal.TokenBalances => false
|
|
354
|
+
| _ => true
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
// Union of selected block fields across the chain's events. `slot`/`time`/
|
|
359
|
+
// `hash` are always fetched (as Slot/BlockTime/Blockhash); the rest are added
|
|
360
|
+
// only when an instruction selected them.
|
|
361
|
+
let blockQueryFields = {
|
|
362
|
+
let fields: array<SvmHyperSyncClient.QueryTypes.blockField> = [Slot, Blockhash, BlockTime]
|
|
363
|
+
let selected = Utils.Set.make()
|
|
364
|
+
eventConfigs->Array.forEach(cfg =>
|
|
365
|
+
cfg.selectedBlockFields->Utils.Set.forEach(field => selected->Utils.Set.add(field)->ignore)
|
|
366
|
+
)
|
|
367
|
+
selected->Utils.Set.forEach(field =>
|
|
368
|
+
switch field->toQueryBlockField {
|
|
369
|
+
| Some(queryField) => fields->Array.push(queryField)->ignore
|
|
370
|
+
| None => ()
|
|
371
|
+
}
|
|
372
|
+
)
|
|
373
|
+
fields
|
|
374
|
+
}
|
|
304
375
|
|
|
305
376
|
let getItemsOrThrow = async (
|
|
306
377
|
~fromBlock,
|
|
307
378
|
~toBlock,
|
|
308
379
|
~addressesByContractName as _,
|
|
309
|
-
~
|
|
380
|
+
~contractNameByAddress,
|
|
310
381
|
~knownHeight,
|
|
311
382
|
~partitionId as _,
|
|
312
383
|
~selection as _,
|
|
384
|
+
~itemsTarget,
|
|
313
385
|
~retry,
|
|
314
386
|
~logger,
|
|
315
387
|
) => {
|
|
316
|
-
let totalTimeRef =
|
|
317
|
-
let pageFetchRef =
|
|
388
|
+
let totalTimeRef = Performance.now()
|
|
389
|
+
let pageFetchRef = Performance.now()
|
|
318
390
|
|
|
319
391
|
let instructionSelections = buildInstructionSelections(eventConfigs)
|
|
320
392
|
// Under the server's default merge mode, requesting a table's columns is
|
|
@@ -322,24 +394,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
322
394
|
// field list returns no rows (instructions and blocks are exempt), so each
|
|
323
395
|
// opted-into table needs its columns spelled out here.
|
|
324
396
|
let fields: SvmHyperSyncClient.QueryTypes.fieldSelection = {
|
|
325
|
-
block:
|
|
326
|
-
transaction: ?(
|
|
327
|
-
needsTransactions
|
|
328
|
-
? Some([
|
|
329
|
-
Slot,
|
|
330
|
-
TransactionIndex,
|
|
331
|
-
Signatures,
|
|
332
|
-
FeePayer,
|
|
333
|
-
Success,
|
|
334
|
-
Err,
|
|
335
|
-
Fee,
|
|
336
|
-
ComputeUnitsConsumed,
|
|
337
|
-
AccountKeys,
|
|
338
|
-
RecentBlockhash,
|
|
339
|
-
Version,
|
|
340
|
-
])
|
|
341
|
-
: None
|
|
342
|
-
),
|
|
397
|
+
block: blockQueryFields,
|
|
398
|
+
transaction: ?(needsTransactions ? Some(txQueryFields) : None),
|
|
343
399
|
log: ?(needsLogs ? Some([Slot, TransactionIndex, InstructionAddress, Kind, Message]) : None),
|
|
344
400
|
tokenBalance: ?(
|
|
345
401
|
needsTokenBalances
|
|
@@ -354,11 +410,10 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
354
410
|
toSlot: ?(toBlock->Option.map(toBlock => toBlock + 1)),
|
|
355
411
|
instructions: instructionSelections,
|
|
356
412
|
fields,
|
|
413
|
+
maxNumInstructions: itemsTarget,
|
|
357
414
|
}
|
|
358
415
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
let resp = try await client.get(~query) catch {
|
|
416
|
+
let (resp, transactionStore, blockStore) = try await client.get(~query) catch {
|
|
362
417
|
| exn =>
|
|
363
418
|
throw(
|
|
364
419
|
Source.GetItemsError(
|
|
@@ -376,12 +431,14 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
376
431
|
),
|
|
377
432
|
)
|
|
378
433
|
}
|
|
379
|
-
let pageFetchTime = pageFetchRef->
|
|
434
|
+
let pageFetchTime = pageFetchRef->Performance.secondsSince
|
|
435
|
+
let requestStats = [{Source.method: "getInstructions", seconds: pageFetchTime}]
|
|
380
436
|
|
|
381
|
-
let parsingRef =
|
|
437
|
+
let parsingRef = Performance.now()
|
|
382
438
|
|
|
383
|
-
// Per-slot
|
|
384
|
-
// without a block row (rare;
|
|
439
|
+
// Per-slot blockTime lookup from the response's `blocks` table, for the
|
|
440
|
+
// batch's `latestFetchedBlockTimestamp`. Slots without a block row (rare;
|
|
441
|
+
// usually skipped slots) fall back to `None`.
|
|
385
442
|
let blockTimeBySlot = Dict.make()
|
|
386
443
|
resp.data.blocks->Array.forEach(b => {
|
|
387
444
|
switch b.blockTime {
|
|
@@ -390,13 +447,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
390
447
|
}
|
|
391
448
|
})
|
|
392
449
|
|
|
393
|
-
// Per (slot, transaction_index) lookup for parent transactions.
|
|
394
|
-
let txByKey = Dict.make()
|
|
395
|
-
resp.data.transactions->Array.forEach(tx => {
|
|
396
|
-
let key = tx.slot->Int.toString ++ ":" ++ tx.transactionIndex->Int.toString
|
|
397
|
-
txByKey->Dict.set(key, tx)
|
|
398
|
-
})
|
|
399
|
-
|
|
400
450
|
// Per (slot, transaction_index, instruction_address) lookup for logs
|
|
401
451
|
// scoped to a single instruction. `instructionAddress: None` logs are
|
|
402
452
|
// attached to no instruction (rare; usually only system messages).
|
|
@@ -418,21 +468,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
418
468
|
}
|
|
419
469
|
})
|
|
420
470
|
|
|
421
|
-
let tokenBalancesByTx = Dict.make()
|
|
422
|
-
if needsTokenBalances {
|
|
423
|
-
resp.data.tokenBalances->Array.forEach(tb => {
|
|
424
|
-
switch tb.transactionIndex {
|
|
425
|
-
| Some(txIdx) =>
|
|
426
|
-
let key = tb.slot->Int.toString ++ ":" ++ txIdx->Int.toString
|
|
427
|
-
switch tokenBalancesByTx->Dict.get(key) {
|
|
428
|
-
| Some(existing) => existing->Array.push(tb)
|
|
429
|
-
| None => tokenBalancesByTx->Dict.set(key, [tb])
|
|
430
|
-
}
|
|
431
|
-
| None => ()
|
|
432
|
-
}
|
|
433
|
-
})
|
|
434
|
-
}
|
|
435
|
-
|
|
436
471
|
let parsedQueueItems = []
|
|
437
472
|
resp.data.instructions->Array.forEach(instr => {
|
|
438
473
|
let programId = instr.programId->SvmTypes.Pubkey.fromStringUnsafe
|
|
@@ -448,26 +483,16 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
448
483
|
instr,
|
|
449
484
|
byteLengths,
|
|
450
485
|
~contractAddress,
|
|
451
|
-
~
|
|
486
|
+
~contractNameByAddress,
|
|
452
487
|
)
|
|
453
488
|
|
|
454
489
|
switch maybeConfig {
|
|
455
490
|
| None => ()
|
|
456
|
-
| Some(
|
|
457
|
-
let
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
maybeTx->Option.map(tx => {
|
|
462
|
-
let maybeBalances =
|
|
463
|
-
tokenBalancesByTx
|
|
464
|
-
->Utils.Dict.dangerouslyGetNonOption(txKey)
|
|
465
|
-
->Option.map(bals => bals->Array.map(toSvmTokenBalance))
|
|
466
|
-
{...tx, tokenBalances: ?maybeBalances}
|
|
467
|
-
})
|
|
468
|
-
} else {
|
|
469
|
-
maybeTx
|
|
470
|
-
}
|
|
491
|
+
| Some(onEventRegistration) =>
|
|
492
|
+
let eventConfig =
|
|
493
|
+
onEventRegistration.eventConfig->(
|
|
494
|
+
Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig
|
|
495
|
+
)
|
|
471
496
|
let logKey =
|
|
472
497
|
instr.slot->Int.toString ++
|
|
473
498
|
":" ++
|
|
@@ -486,30 +511,22 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
486
511
|
)
|
|
487
512
|
)
|
|
488
513
|
|
|
489
|
-
let slotKey = instr.slot->Int.toString
|
|
490
|
-
let blockTime = blockTimeBySlot->Utils.Dict.dangerouslyGetNonOption(slotKey)
|
|
491
514
|
let payload = toSvmInstruction(
|
|
492
515
|
instr,
|
|
493
516
|
~programName=eventConfig.contractName,
|
|
494
517
|
~instructionName=eventConfig.name,
|
|
495
|
-
~transaction=eventConfig.includeTransaction ? maybeTx : None,
|
|
496
518
|
~logs=eventConfig.includeLogs ? maybeLogs : None,
|
|
497
|
-
~block={
|
|
498
|
-
slot: instr.slot,
|
|
499
|
-
time: blockTime->Option.getOr(0),
|
|
500
|
-
hash: "",
|
|
501
|
-
},
|
|
502
519
|
)
|
|
503
520
|
|
|
504
521
|
parsedQueueItems
|
|
505
522
|
->Array.push(
|
|
506
523
|
Internal.Event({
|
|
507
|
-
|
|
508
|
-
timestamp: blockTime->Option.getOr(0),
|
|
524
|
+
onEventRegistration,
|
|
509
525
|
chain,
|
|
510
526
|
blockNumber: instr.slot,
|
|
511
|
-
blockHash: "",
|
|
512
527
|
logIndex: synthLogIndex(instr),
|
|
528
|
+
// The parent transaction is materialised from the store at batch prep.
|
|
529
|
+
transactionIndex: instr.transactionIndex,
|
|
513
530
|
payload: payload->(Utils.magic: Envio.svmInstruction => Internal.eventPayload),
|
|
514
531
|
}),
|
|
515
532
|
)
|
|
@@ -519,7 +536,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
519
536
|
let _ = logger
|
|
520
537
|
})
|
|
521
538
|
|
|
522
|
-
let parsingTimeElapsed = parsingRef->
|
|
539
|
+
let parsingTimeElapsed = parsingRef->Performance.secondsSince
|
|
523
540
|
let highestSlot = resp.nextSlot - 1
|
|
524
541
|
let latestBlockTime =
|
|
525
542
|
blockTimeBySlot
|
|
@@ -534,16 +551,21 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
534
551
|
blockHash: b.blockhash,
|
|
535
552
|
})
|
|
536
553
|
|
|
537
|
-
let totalTimeElapsed = totalTimeRef->
|
|
554
|
+
let totalTimeElapsed = totalTimeRef->Performance.secondsSince
|
|
538
555
|
|
|
539
556
|
{
|
|
540
557
|
latestFetchedBlockTimestamp: latestBlockTime,
|
|
541
558
|
parsedQueueItems,
|
|
559
|
+
// Raw transactions kept in Rust; materialised (selected fields) at batch prep.
|
|
560
|
+
transactionStore: Some(transactionStore),
|
|
561
|
+
// Raw blocks kept in Rust; materialised onto the payload at batch prep.
|
|
562
|
+
blockStore: Some(blockStore),
|
|
542
563
|
latestFetchedBlockNumber: highestSlot,
|
|
543
564
|
stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
|
|
544
565
|
knownHeight,
|
|
545
566
|
blockHashes,
|
|
546
567
|
fromBlockQueried: fromBlock,
|
|
568
|
+
requestStats,
|
|
547
569
|
}
|
|
548
570
|
}
|
|
549
571
|
|
|
@@ -552,6 +574,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
552
574
|
// wire, so we request `maxSlot + 1`; the caller filters to the exact slots.
|
|
553
575
|
let queryBlockDataRange = async (~fromSlot, ~toSlot) => {
|
|
554
576
|
let blockDatas = []
|
|
577
|
+
let requestStats = []
|
|
555
578
|
let fromRef = ref(fromSlot)
|
|
556
579
|
let keepGoing = ref(true)
|
|
557
580
|
while keepGoing.contents {
|
|
@@ -562,8 +585,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
562
585
|
fields: {block: [Slot, Blockhash, BlockTime]},
|
|
563
586
|
maxNumBlocks: 1000,
|
|
564
587
|
}
|
|
565
|
-
|
|
566
|
-
|
|
588
|
+
let timerRef = Performance.now()
|
|
589
|
+
// Block-only query; the store pages are empty.
|
|
590
|
+
let (resp, _, _) = await client.get(~query)
|
|
591
|
+
requestStats
|
|
592
|
+
->Array.push({Source.method: "getBlockHashes", seconds: timerRef->Performance.secondsSince})
|
|
593
|
+
->ignore
|
|
567
594
|
resp.data.blocks->Array.forEach(b =>
|
|
568
595
|
blockDatas
|
|
569
596
|
->Array.push({
|
|
@@ -582,12 +609,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
582
609
|
fromRef := resp.nextSlot
|
|
583
610
|
}
|
|
584
611
|
}
|
|
585
|
-
blockDatas
|
|
612
|
+
(blockDatas, requestStats)
|
|
586
613
|
}
|
|
587
614
|
|
|
588
615
|
let getBlockHashes = async (~blockNumbers, ~logger as _) =>
|
|
589
616
|
switch blockNumbers->Array.get(0) {
|
|
590
|
-
| None => Ok([])
|
|
617
|
+
| None => {Source.result: Ok([]), requestStats: []}
|
|
591
618
|
| Some(firstSlot) =>
|
|
592
619
|
try {
|
|
593
620
|
let minSlot = ref(firstSlot)
|
|
@@ -602,14 +629,19 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
602
629
|
}
|
|
603
630
|
requested->Utils.Set.add(slot)->ignore
|
|
604
631
|
})
|
|
605
|
-
let blockDatas = await queryBlockDataRange(
|
|
632
|
+
let (blockDatas, requestStats) = await queryBlockDataRange(
|
|
606
633
|
~fromSlot=minSlot.contents,
|
|
607
634
|
~toSlot=maxSlot.contents,
|
|
608
635
|
)
|
|
609
636
|
// Keep one entry per requested slot; drop duplicates and unrelated slots.
|
|
610
|
-
|
|
637
|
+
{
|
|
638
|
+
Source.result: Ok(
|
|
639
|
+
blockDatas->Array.filter(data => requested->Utils.Set.delete(data.blockNumber)),
|
|
640
|
+
),
|
|
641
|
+
requestStats,
|
|
642
|
+
}
|
|
611
643
|
} catch {
|
|
612
|
-
| exn => Error(exn)
|
|
644
|
+
| exn => {Source.result: Error(exn), requestStats: []}
|
|
613
645
|
}
|
|
614
646
|
}
|
|
615
647
|
|
|
@@ -621,17 +653,10 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
621
653
|
poweredByHyperSync: true,
|
|
622
654
|
getBlockHashes,
|
|
623
655
|
getHeightOrThrow: async () => {
|
|
624
|
-
let timer =
|
|
625
|
-
let
|
|
626
|
-
let seconds = timer->
|
|
627
|
-
|
|
628
|
-
Prometheus.SourceRequestCount.addSeconds(
|
|
629
|
-
~sourceName=name,
|
|
630
|
-
~chainId,
|
|
631
|
-
~method="getHeight",
|
|
632
|
-
~seconds,
|
|
633
|
-
)
|
|
634
|
-
h
|
|
656
|
+
let timer = Performance.now()
|
|
657
|
+
let height = await client.getHeight()
|
|
658
|
+
let seconds = timer->Performance.secondsSince
|
|
659
|
+
{height, requestStats: [{method: "getHeight", seconds}]}
|
|
635
660
|
},
|
|
636
661
|
getItemsOrThrow,
|
|
637
662
|
}
|