envio 3.3.0-alpha.7 → 3.3.0-alpha.8
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/index.d.ts +23 -5
- package/package.json +6 -6
- package/src/Api.res +1 -1
- package/src/Api.res.mjs +1 -1
- package/src/ChainFetching.res +21 -10
- package/src/ChainFetching.res.mjs +11 -10
- package/src/ChainState.res +328 -165
- package/src/ChainState.res.mjs +205 -100
- package/src/ChainState.resi +16 -7
- package/src/Config.res +11 -27
- package/src/Config.res.mjs +8 -7
- package/src/Core.res +7 -0
- package/src/CrossChainState.res +53 -80
- package/src/CrossChainState.res.mjs +43 -59
- package/src/CrossChainState.resi +1 -1
- package/src/Ecosystem.res +3 -3
- package/src/Ecosystem.res.mjs +3 -3
- package/src/Envio.res +14 -9
- package/src/EventConfigBuilder.res +105 -56
- package/src/EventConfigBuilder.res.mjs +69 -33
- package/src/EventProcessing.res +19 -15
- package/src/EventProcessing.res.mjs +13 -12
- package/src/FetchState.res +375 -171
- package/src/FetchState.res.mjs +249 -251
- package/src/HandlerLoader.res +7 -112
- package/src/HandlerLoader.res.mjs +1 -87
- package/src/HandlerRegister.res +209 -6
- package/src/HandlerRegister.res.mjs +90 -5
- package/src/HandlerRegister.resi +13 -2
- package/src/IndexerState.res +6 -3
- package/src/IndexerState.res.mjs +3 -3
- package/src/IndexerState.resi +1 -1
- package/src/IndexingAddresses.res +10 -7
- package/src/IndexingAddresses.res.mjs +8 -7
- package/src/IndexingAddresses.resi +3 -1
- package/src/Internal.res +82 -36
- package/src/Internal.res.mjs +11 -1
- package/src/Main.res +22 -23
- package/src/Main.res.mjs +15 -17
- package/src/Metrics.res +43 -2
- package/src/Metrics.res.mjs +39 -3
- package/src/Prometheus.res +0 -35
- package/src/Prometheus.res.mjs +33 -68
- package/src/RawEvent.res +7 -2
- package/src/RawEvent.res.mjs +4 -4
- package/src/SimulateItems.res +20 -7
- package/src/SimulateItems.res.mjs +7 -11
- package/src/TestIndexer.res +1 -1
- package/src/TestIndexer.res.mjs +1 -1
- package/src/sources/BlockStore.res +46 -0
- package/src/sources/BlockStore.res.mjs +24 -0
- package/src/sources/EventRouter.res +19 -12
- package/src/sources/EventRouter.res.mjs +7 -5
- package/src/sources/Evm.res +54 -4
- package/src/sources/Evm.res.mjs +43 -4
- package/src/sources/EvmChain.res +14 -18
- package/src/sources/EvmChain.res.mjs +12 -13
- package/src/sources/FieldMask.res +39 -0
- package/src/sources/FieldMask.res.mjs +42 -0
- package/src/sources/Fuel.res +5 -2
- package/src/sources/Fuel.res.mjs +4 -3
- package/src/sources/HyperFuelSource.res +31 -40
- package/src/sources/HyperFuelSource.res.mjs +52 -44
- package/src/sources/HyperSync.res +31 -9
- package/src/sources/HyperSync.res.mjs +47 -31
- package/src/sources/HyperSync.resi +10 -3
- package/src/sources/HyperSyncClient.res +15 -4
- package/src/sources/HyperSyncHeightStream.res +1 -8
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
- package/src/sources/HyperSyncSource.res +37 -58
- package/src/sources/HyperSyncSource.res.mjs +42 -42
- package/src/sources/RpcSource.res +91 -82
- package/src/sources/RpcSource.res.mjs +76 -45
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +5 -3
- package/src/sources/SimulateSource.res.mjs +12 -4
- package/src/sources/Source.res +19 -5
- package/src/sources/SourceManager.res +62 -6
- package/src/sources/SourceManager.res.mjs +55 -8
- package/src/sources/SourceManager.resi +10 -0
- package/src/sources/Svm.res +14 -10
- package/src/sources/Svm.res.mjs +23 -6
- package/src/sources/SvmHyperSyncClient.res +5 -6
- package/src/sources/SvmHyperSyncSource.res +82 -41
- package/src/sources/SvmHyperSyncSource.res.mjs +86 -41
- package/src/sources/TransactionStore.res +6 -107
- package/src/sources/TransactionStore.res.mjs +5 -86
- package/svm.schema.json +19 -0
|
@@ -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,12 +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
192
|
~logs,
|
|
192
|
-
~block,
|
|
193
193
|
): Envio.svmInstruction => {
|
|
194
194
|
programName,
|
|
195
195
|
instructionName,
|
|
@@ -204,14 +204,13 @@ let toSvmInstruction = (
|
|
|
204
204
|
d8: ?instr.d8,
|
|
205
205
|
params: ?(instr.decoded->Option.map(parseDecoded)),
|
|
206
206
|
?logs,
|
|
207
|
-
block,
|
|
208
207
|
}
|
|
209
208
|
|
|
210
209
|
// Probe the discriminator byte-length ordering longest-first. Stops at the
|
|
211
210
|
// first router hit. Falls back to the `_none` key (program-wide handler) when
|
|
212
211
|
// no discriminator-keyed handler matches.
|
|
213
212
|
let probeRouter = (
|
|
214
|
-
router: EventRouter.t<Internal.
|
|
213
|
+
router: EventRouter.t<Internal.svmOnEventRegistration>,
|
|
215
214
|
programId: SvmTypes.Pubkey.t,
|
|
216
215
|
instr: SvmHyperSyncClient.ResponseTypes.instruction,
|
|
217
216
|
byteLengthsDesc: array<int>,
|
|
@@ -268,9 +267,30 @@ let toQueryTxField = (field: Internal.svmTransactionField): option<
|
|
|
268
267
|
| TokenBalances => None
|
|
269
268
|
}
|
|
270
269
|
|
|
271
|
-
|
|
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 => {
|
|
272
286
|
let name = "SvmHyperSync"
|
|
273
|
-
|
|
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
|
+
)
|
|
274
294
|
|
|
275
295
|
// Built once at startup and handed to the client so `get` decodes matching
|
|
276
296
|
// instructions in Rust rather than per-instruction over the napi boundary.
|
|
@@ -285,7 +305,10 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
285
305
|
},
|
|
286
306
|
)
|
|
287
307
|
|
|
288
|
-
let (eventRouter, programOrderings) = EventRouter.fromSvmEventConfigsOrThrow(
|
|
308
|
+
let (eventRouter, programOrderings) = EventRouter.fromSvmEventConfigsOrThrow(
|
|
309
|
+
onEventRegistrations,
|
|
310
|
+
~chain,
|
|
311
|
+
)
|
|
289
312
|
|
|
290
313
|
// programId.toString -> sorted-desc byte lengths
|
|
291
314
|
let orderingByProgram = Dict.make()
|
|
@@ -332,6 +355,24 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
332
355
|
}
|
|
333
356
|
)
|
|
334
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
|
+
}
|
|
375
|
+
|
|
335
376
|
let getItemsOrThrow = async (
|
|
336
377
|
~fromBlock,
|
|
337
378
|
~toBlock,
|
|
@@ -353,7 +394,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
353
394
|
// field list returns no rows (instructions and blocks are exempt), so each
|
|
354
395
|
// opted-into table needs its columns spelled out here.
|
|
355
396
|
let fields: SvmHyperSyncClient.QueryTypes.fieldSelection = {
|
|
356
|
-
block:
|
|
397
|
+
block: blockQueryFields,
|
|
357
398
|
transaction: ?(needsTransactions ? Some(txQueryFields) : None),
|
|
358
399
|
log: ?(needsLogs ? Some([Slot, TransactionIndex, InstructionAddress, Kind, Message]) : None),
|
|
359
400
|
tokenBalance: ?(
|
|
@@ -372,9 +413,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
372
413
|
maxNumInstructions: itemsTarget,
|
|
373
414
|
}
|
|
374
415
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
let (resp, transactionStore) = try await client.get(~query) catch {
|
|
416
|
+
let (resp, transactionStore, blockStore) = try await client.get(~query) catch {
|
|
378
417
|
| exn =>
|
|
379
418
|
throw(
|
|
380
419
|
Source.GetItemsError(
|
|
@@ -393,11 +432,13 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
393
432
|
)
|
|
394
433
|
}
|
|
395
434
|
let pageFetchTime = pageFetchRef->Performance.secondsSince
|
|
435
|
+
let requestStats = [{Source.method: "getInstructions", seconds: pageFetchTime}]
|
|
396
436
|
|
|
397
437
|
let parsingRef = Performance.now()
|
|
398
438
|
|
|
399
|
-
// Per-slot
|
|
400
|
-
// 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`.
|
|
401
442
|
let blockTimeBySlot = Dict.make()
|
|
402
443
|
resp.data.blocks->Array.forEach(b => {
|
|
403
444
|
switch b.blockTime {
|
|
@@ -447,7 +488,11 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
447
488
|
|
|
448
489
|
switch maybeConfig {
|
|
449
490
|
| None => ()
|
|
450
|
-
| Some(
|
|
491
|
+
| Some(onEventRegistration) =>
|
|
492
|
+
let eventConfig =
|
|
493
|
+
onEventRegistration.eventConfig->(
|
|
494
|
+
Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig
|
|
495
|
+
)
|
|
451
496
|
let logKey =
|
|
452
497
|
instr.slot->Int.toString ++
|
|
453
498
|
":" ++
|
|
@@ -466,28 +511,19 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
466
511
|
)
|
|
467
512
|
)
|
|
468
513
|
|
|
469
|
-
let slotKey = instr.slot->Int.toString
|
|
470
|
-
let blockTime = blockTimeBySlot->Utils.Dict.dangerouslyGetNonOption(slotKey)
|
|
471
514
|
let payload = toSvmInstruction(
|
|
472
515
|
instr,
|
|
473
516
|
~programName=eventConfig.contractName,
|
|
474
517
|
~instructionName=eventConfig.name,
|
|
475
518
|
~logs=eventConfig.includeLogs ? maybeLogs : None,
|
|
476
|
-
~block={
|
|
477
|
-
slot: instr.slot,
|
|
478
|
-
time: blockTime->Option.getOr(0),
|
|
479
|
-
hash: "",
|
|
480
|
-
},
|
|
481
519
|
)
|
|
482
520
|
|
|
483
521
|
parsedQueueItems
|
|
484
522
|
->Array.push(
|
|
485
523
|
Internal.Event({
|
|
486
|
-
|
|
487
|
-
timestamp: blockTime->Option.getOr(0),
|
|
524
|
+
onEventRegistration,
|
|
488
525
|
chain,
|
|
489
526
|
blockNumber: instr.slot,
|
|
490
|
-
blockHash: "",
|
|
491
527
|
logIndex: synthLogIndex(instr),
|
|
492
528
|
// The parent transaction is materialised from the store at batch prep.
|
|
493
529
|
transactionIndex: instr.transactionIndex,
|
|
@@ -522,11 +558,14 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
522
558
|
parsedQueueItems,
|
|
523
559
|
// Raw transactions kept in Rust; materialised (selected fields) at batch prep.
|
|
524
560
|
transactionStore: Some(transactionStore),
|
|
561
|
+
// Raw blocks kept in Rust; materialised onto the payload at batch prep.
|
|
562
|
+
blockStore: Some(blockStore),
|
|
525
563
|
latestFetchedBlockNumber: highestSlot,
|
|
526
564
|
stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
|
|
527
565
|
knownHeight,
|
|
528
566
|
blockHashes,
|
|
529
567
|
fromBlockQueried: fromBlock,
|
|
568
|
+
requestStats,
|
|
530
569
|
}
|
|
531
570
|
}
|
|
532
571
|
|
|
@@ -535,6 +574,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
535
574
|
// wire, so we request `maxSlot + 1`; the caller filters to the exact slots.
|
|
536
575
|
let queryBlockDataRange = async (~fromSlot, ~toSlot) => {
|
|
537
576
|
let blockDatas = []
|
|
577
|
+
let requestStats = []
|
|
538
578
|
let fromRef = ref(fromSlot)
|
|
539
579
|
let keepGoing = ref(true)
|
|
540
580
|
while keepGoing.contents {
|
|
@@ -545,9 +585,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
545
585
|
fields: {block: [Slot, Blockhash, BlockTime]},
|
|
546
586
|
maxNumBlocks: 1000,
|
|
547
587
|
}
|
|
548
|
-
|
|
549
|
-
// Block-only query; the store
|
|
550
|
-
let (resp, _) = await client.get(~query)
|
|
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
|
|
551
594
|
resp.data.blocks->Array.forEach(b =>
|
|
552
595
|
blockDatas
|
|
553
596
|
->Array.push({
|
|
@@ -566,12 +609,12 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
566
609
|
fromRef := resp.nextSlot
|
|
567
610
|
}
|
|
568
611
|
}
|
|
569
|
-
blockDatas
|
|
612
|
+
(blockDatas, requestStats)
|
|
570
613
|
}
|
|
571
614
|
|
|
572
615
|
let getBlockHashes = async (~blockNumbers, ~logger as _) =>
|
|
573
616
|
switch blockNumbers->Array.get(0) {
|
|
574
|
-
| None => Ok([])
|
|
617
|
+
| None => {Source.result: Ok([]), requestStats: []}
|
|
575
618
|
| Some(firstSlot) =>
|
|
576
619
|
try {
|
|
577
620
|
let minSlot = ref(firstSlot)
|
|
@@ -586,14 +629,19 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
586
629
|
}
|
|
587
630
|
requested->Utils.Set.add(slot)->ignore
|
|
588
631
|
})
|
|
589
|
-
let blockDatas = await queryBlockDataRange(
|
|
632
|
+
let (blockDatas, requestStats) = await queryBlockDataRange(
|
|
590
633
|
~fromSlot=minSlot.contents,
|
|
591
634
|
~toSlot=maxSlot.contents,
|
|
592
635
|
)
|
|
593
636
|
// Keep one entry per requested slot; drop duplicates and unrelated slots.
|
|
594
|
-
|
|
637
|
+
{
|
|
638
|
+
Source.result: Ok(
|
|
639
|
+
blockDatas->Array.filter(data => requested->Utils.Set.delete(data.blockNumber)),
|
|
640
|
+
),
|
|
641
|
+
requestStats,
|
|
642
|
+
}
|
|
595
643
|
} catch {
|
|
596
|
-
| exn => Error(exn)
|
|
644
|
+
| exn => {Source.result: Error(exn), requestStats: []}
|
|
597
645
|
}
|
|
598
646
|
}
|
|
599
647
|
|
|
@@ -606,16 +654,9 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
606
654
|
getBlockHashes,
|
|
607
655
|
getHeightOrThrow: async () => {
|
|
608
656
|
let timer = Performance.now()
|
|
609
|
-
let
|
|
657
|
+
let height = await client.getHeight()
|
|
610
658
|
let seconds = timer->Performance.secondsSince
|
|
611
|
-
|
|
612
|
-
Prometheus.SourceRequestCount.addSeconds(
|
|
613
|
-
~sourceName=name,
|
|
614
|
-
~chainId,
|
|
615
|
-
~method="getHeight",
|
|
616
|
-
~seconds,
|
|
617
|
-
)
|
|
618
|
-
h
|
|
659
|
+
{height, requestStats: [{method: "getHeight", seconds}]}
|
|
619
660
|
},
|
|
620
661
|
getItemsOrThrow,
|
|
621
662
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Source from "./Source.res.mjs";
|
|
4
|
-
import * as Prometheus from "../Prometheus.res.mjs";
|
|
5
4
|
import * as EventRouter from "./EventRouter.res.mjs";
|
|
6
5
|
import * as Performance from "../bindings/Performance.res.mjs";
|
|
7
6
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
@@ -166,7 +165,7 @@ function parseDecoded(d) {
|
|
|
166
165
|
};
|
|
167
166
|
}
|
|
168
167
|
|
|
169
|
-
function toSvmInstruction(instr, programName, instructionName, logs
|
|
168
|
+
function toSvmInstruction(instr, programName, instructionName, logs) {
|
|
170
169
|
return {
|
|
171
170
|
programName: programName,
|
|
172
171
|
instructionName: instructionName,
|
|
@@ -180,8 +179,7 @@ function toSvmInstruction(instr, programName, instructionName, logs, block) {
|
|
|
180
179
|
d4: instr.d4,
|
|
181
180
|
d8: instr.d8,
|
|
182
181
|
params: Stdlib_Option.map(instr.decoded, parseDecoded),
|
|
183
|
-
logs: logs
|
|
184
|
-
block: block
|
|
182
|
+
logs: logs
|
|
185
183
|
};
|
|
186
184
|
}
|
|
187
185
|
|
|
@@ -248,13 +246,26 @@ function toQueryTxField(field) {
|
|
|
248
246
|
}
|
|
249
247
|
}
|
|
250
248
|
|
|
249
|
+
function toQueryBlockField(field) {
|
|
250
|
+
switch (field) {
|
|
251
|
+
case "height" :
|
|
252
|
+
return "block_height";
|
|
253
|
+
case "parentSlot" :
|
|
254
|
+
return "parent_slot";
|
|
255
|
+
case "parentHash" :
|
|
256
|
+
return "parent_blockhash";
|
|
257
|
+
default:
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
251
262
|
function make(param) {
|
|
252
|
-
let
|
|
263
|
+
let onEventRegistrations = param.onEventRegistrations;
|
|
253
264
|
let chain = param.chain;
|
|
254
|
-
let
|
|
265
|
+
let eventConfigs = onEventRegistrations.map(reg => reg.eventConfig);
|
|
255
266
|
let programSchemas = buildProgramSchemas(eventConfigs);
|
|
256
267
|
let client = SvmHyperSyncClient.make(param.endpointUrl, param.apiToken, param.clientTimeoutMillis, undefined, undefined, undefined, programSchemas.length !== 0 ? programSchemas : undefined);
|
|
257
|
-
let match = EventRouter.fromSvmEventConfigsOrThrow(
|
|
268
|
+
let match = EventRouter.fromSvmEventConfigsOrThrow(onEventRegistrations, chain);
|
|
258
269
|
let eventRouter = match[0];
|
|
259
270
|
let orderingByProgram = {};
|
|
260
271
|
match[1].forEach(o => {
|
|
@@ -288,15 +299,29 @@ function make(param) {
|
|
|
288
299
|
return true;
|
|
289
300
|
}
|
|
290
301
|
});
|
|
302
|
+
let fields$1 = [
|
|
303
|
+
"slot",
|
|
304
|
+
"blockhash",
|
|
305
|
+
"block_time"
|
|
306
|
+
];
|
|
307
|
+
let selected = new Set();
|
|
308
|
+
eventConfigs.forEach(cfg => {
|
|
309
|
+
cfg.selectedBlockFields.forEach(field => {
|
|
310
|
+
selected.add(field);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
selected.forEach(field => {
|
|
314
|
+
let queryField = toQueryBlockField(field);
|
|
315
|
+
if (queryField !== undefined) {
|
|
316
|
+
fields$1.push(queryField);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
});
|
|
291
320
|
let getItemsOrThrow = async (fromBlock, toBlock, param, contractNameByAddress, knownHeight, param$1, param$2, itemsTarget, retry, logger) => {
|
|
292
321
|
let totalTimeRef = Performance.now();
|
|
293
322
|
let pageFetchRef = Performance.now();
|
|
294
323
|
let instructionSelections = buildInstructionSelections(eventConfigs);
|
|
295
|
-
let fields_block =
|
|
296
|
-
"slot",
|
|
297
|
-
"blockhash",
|
|
298
|
-
"block_time"
|
|
299
|
-
];
|
|
324
|
+
let fields_block = fields$1;
|
|
300
325
|
let fields_transaction = needsTransactions ? fields : undefined;
|
|
301
326
|
let fields_log = needsLogs ? [
|
|
302
327
|
"slot",
|
|
@@ -314,7 +339,7 @@ function make(param) {
|
|
|
314
339
|
"pre_amount",
|
|
315
340
|
"post_amount"
|
|
316
341
|
] : undefined;
|
|
317
|
-
let fields$
|
|
342
|
+
let fields$2 = {
|
|
318
343
|
block: fields_block,
|
|
319
344
|
transaction: fields_transaction,
|
|
320
345
|
log: fields_log,
|
|
@@ -322,7 +347,7 @@ function make(param) {
|
|
|
322
347
|
};
|
|
323
348
|
let query_toSlot = Stdlib_Option.map(toBlock, toBlock => toBlock + 1 | 0);
|
|
324
349
|
let query_instructions = instructionSelections;
|
|
325
|
-
let query_fields = fields$
|
|
350
|
+
let query_fields = fields$2;
|
|
326
351
|
let query_maxNumInstructions = itemsTarget;
|
|
327
352
|
let query = {
|
|
328
353
|
fromSlot: fromBlock,
|
|
@@ -331,7 +356,6 @@ function make(param) {
|
|
|
331
356
|
fields: query_fields,
|
|
332
357
|
maxNumInstructions: query_maxNumInstructions
|
|
333
358
|
};
|
|
334
|
-
Prometheus.SourceRequestCount.increment(name, chain, "getInstructions");
|
|
335
359
|
let match;
|
|
336
360
|
try {
|
|
337
361
|
match = await client.get(query);
|
|
@@ -354,6 +378,10 @@ function make(param) {
|
|
|
354
378
|
}
|
|
355
379
|
let resp = match[0];
|
|
356
380
|
let pageFetchTime = Performance.secondsSince(pageFetchRef);
|
|
381
|
+
let requestStats = [{
|
|
382
|
+
method: "getInstructions",
|
|
383
|
+
seconds: pageFetchTime
|
|
384
|
+
}];
|
|
357
385
|
let parsingRef = Performance.now();
|
|
358
386
|
let blockTimeBySlot = {};
|
|
359
387
|
resp.data.blocks.forEach(b => {
|
|
@@ -388,25 +416,18 @@ function make(param) {
|
|
|
388
416
|
let contractAddress = instr.programId;
|
|
389
417
|
let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, contractNameByAddress);
|
|
390
418
|
if (maybeConfig !== undefined) {
|
|
419
|
+
let eventConfig = maybeConfig.eventConfig;
|
|
391
420
|
let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress);
|
|
392
421
|
let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({
|
|
393
422
|
kind: Stdlib_Option.getOr(log.kind, ""),
|
|
394
423
|
message: Stdlib_Option.getOr(log.message, "")
|
|
395
424
|
})));
|
|
396
|
-
let
|
|
397
|
-
let blockTime = blockTimeBySlot[slotKey];
|
|
398
|
-
let payload = toSvmInstruction(instr, maybeConfig.contractName, maybeConfig.name, maybeConfig.includeLogs ? maybeLogs : undefined, {
|
|
399
|
-
slot: instr.slot,
|
|
400
|
-
time: Stdlib_Option.getOr(blockTime, 0),
|
|
401
|
-
hash: ""
|
|
402
|
-
});
|
|
425
|
+
let payload = toSvmInstruction(instr, eventConfig.contractName, eventConfig.name, eventConfig.includeLogs ? maybeLogs : undefined);
|
|
403
426
|
parsedQueueItems.push({
|
|
404
427
|
kind: 0,
|
|
405
|
-
|
|
406
|
-
timestamp: Stdlib_Option.getOr(blockTime, 0),
|
|
428
|
+
onEventRegistration: maybeConfig,
|
|
407
429
|
chain: chain,
|
|
408
430
|
blockNumber: instr.slot,
|
|
409
|
-
blockHash: "",
|
|
410
431
|
logIndex: synthLogIndex(instr),
|
|
411
432
|
transactionIndex: instr.transactionIndex,
|
|
412
433
|
payload: payload
|
|
@@ -426,6 +447,7 @@ function make(param) {
|
|
|
426
447
|
blockHashes: blockHashes,
|
|
427
448
|
parsedQueueItems: parsedQueueItems,
|
|
428
449
|
transactionStore: Primitive_option.some(match[1]),
|
|
450
|
+
blockStore: Primitive_option.some(match[2]),
|
|
429
451
|
fromBlockQueried: fromBlock,
|
|
430
452
|
latestFetchedBlockNumber: highestSlot,
|
|
431
453
|
latestFetchedBlockTimestamp: latestBlockTime,
|
|
@@ -433,11 +455,13 @@ function make(param) {
|
|
|
433
455
|
"total time elapsed (s)": totalTimeElapsed,
|
|
434
456
|
"parsing time (s)": parsingTimeElapsed,
|
|
435
457
|
"page fetch time (s)": pageFetchTime
|
|
436
|
-
}
|
|
458
|
+
},
|
|
459
|
+
requestStats: requestStats
|
|
437
460
|
};
|
|
438
461
|
};
|
|
439
462
|
let queryBlockDataRange = async (fromSlot, toSlot) => {
|
|
440
463
|
let blockDatas = [];
|
|
464
|
+
let requestStats = [];
|
|
441
465
|
let fromRef = fromSlot;
|
|
442
466
|
let keepGoing = true;
|
|
443
467
|
while (keepGoing) {
|
|
@@ -458,9 +482,13 @@ function make(param) {
|
|
|
458
482
|
fields: query_fields,
|
|
459
483
|
maxNumBlocks: query_maxNumBlocks
|
|
460
484
|
};
|
|
461
|
-
|
|
485
|
+
let timerRef = Performance.now();
|
|
462
486
|
let match = await client.get(query);
|
|
463
487
|
let resp = match[0];
|
|
488
|
+
requestStats.push({
|
|
489
|
+
method: "getBlockHashes",
|
|
490
|
+
seconds: Performance.secondsSince(timerRef)
|
|
491
|
+
});
|
|
464
492
|
resp.data.blocks.forEach(b => {
|
|
465
493
|
blockDatas.push({
|
|
466
494
|
blockHash: b.blockhash,
|
|
@@ -474,14 +502,20 @@ function make(param) {
|
|
|
474
502
|
fromRef = resp.nextSlot;
|
|
475
503
|
}
|
|
476
504
|
};
|
|
477
|
-
return
|
|
505
|
+
return [
|
|
506
|
+
blockDatas,
|
|
507
|
+
requestStats
|
|
508
|
+
];
|
|
478
509
|
};
|
|
479
510
|
let getBlockHashes = async (blockNumbers, param) => {
|
|
480
511
|
let firstSlot = blockNumbers[0];
|
|
481
512
|
if (firstSlot === undefined) {
|
|
482
513
|
return {
|
|
483
|
-
|
|
484
|
-
|
|
514
|
+
result: {
|
|
515
|
+
TAG: "Ok",
|
|
516
|
+
_0: []
|
|
517
|
+
},
|
|
518
|
+
requestStats: []
|
|
485
519
|
};
|
|
486
520
|
}
|
|
487
521
|
try {
|
|
@@ -501,21 +535,27 @@ function make(param) {
|
|
|
501
535
|
}
|
|
502
536
|
requested.add(slot);
|
|
503
537
|
});
|
|
504
|
-
let
|
|
538
|
+
let match = await queryBlockDataRange(minSlot.contents, maxSlot.contents);
|
|
505
539
|
return {
|
|
506
|
-
|
|
507
|
-
|
|
540
|
+
result: {
|
|
541
|
+
TAG: "Ok",
|
|
542
|
+
_0: match[0].filter(data => requested.delete(data.blockNumber))
|
|
543
|
+
},
|
|
544
|
+
requestStats: match[1]
|
|
508
545
|
};
|
|
509
546
|
} catch (raw_exn) {
|
|
510
547
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
511
548
|
return {
|
|
512
|
-
|
|
513
|
-
|
|
549
|
+
result: {
|
|
550
|
+
TAG: "Error",
|
|
551
|
+
_0: exn
|
|
552
|
+
},
|
|
553
|
+
requestStats: []
|
|
514
554
|
};
|
|
515
555
|
}
|
|
516
556
|
};
|
|
517
557
|
return {
|
|
518
|
-
name:
|
|
558
|
+
name: "SvmHyperSync",
|
|
519
559
|
sourceFor: "Sync",
|
|
520
560
|
chain: chain,
|
|
521
561
|
poweredByHyperSync: true,
|
|
@@ -523,11 +563,15 @@ function make(param) {
|
|
|
523
563
|
getBlockHashes: getBlockHashes,
|
|
524
564
|
getHeightOrThrow: async () => {
|
|
525
565
|
let timer = Performance.now();
|
|
526
|
-
let
|
|
566
|
+
let height = await client.getHeight();
|
|
527
567
|
let seconds = Performance.secondsSince(timer);
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
568
|
+
return {
|
|
569
|
+
height: height,
|
|
570
|
+
requestStats: [{
|
|
571
|
+
method: "getHeight",
|
|
572
|
+
seconds: seconds
|
|
573
|
+
}]
|
|
574
|
+
};
|
|
531
575
|
},
|
|
532
576
|
getItemsOrThrow: getItemsOrThrow
|
|
533
577
|
};
|
|
@@ -542,6 +586,7 @@ export {
|
|
|
542
586
|
toSvmInstruction,
|
|
543
587
|
probeRouter,
|
|
544
588
|
toQueryTxField,
|
|
589
|
+
toQueryBlockField,
|
|
545
590
|
make,
|
|
546
591
|
}
|
|
547
|
-
/*
|
|
592
|
+
/* EventRouter Not a pure module */
|
|
@@ -21,42 +21,12 @@ let make = (~ecosystem: Ecosystem.name, ~shouldChecksum: bool): t => {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
//
|
|
25
|
-
// field
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
let pow2: int => float = %raw(`c => Math.pow(2, c)`)
|
|
33
|
-
|
|
34
|
-
// One event's selected transaction fields as a bitmask float (bit `code` set ⇔
|
|
35
|
-
// selected). Each field appears once, so summing `pow2(code)` sets distinct bits
|
|
36
|
-
// with no overlap; the result stays exact in f64 (codes span 0..31, so a mask is
|
|
37
|
-
// at most 2^32-1).
|
|
38
|
-
let maskFromFields = (selectedTransactionFields: Utils.Set.t<string>, ~codes: dict<int>): float => {
|
|
39
|
-
let mask = ref(0.)
|
|
40
|
-
selectedTransactionFields->Utils.Set.forEach(name =>
|
|
41
|
-
switch codes->Utils.Dict.dangerouslyGetNonOption(name) {
|
|
42
|
-
| Some(code) => mask := mask.contents +. pow2(code)
|
|
43
|
-
| None => ()
|
|
44
|
-
}
|
|
45
|
-
)
|
|
46
|
-
mask.contents
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Build an ecosystem's per-event mask function from its ordered field-name
|
|
50
|
-
// array. The field codes are derived once and closed over.
|
|
51
|
-
let makeMaskFn = (fields: array<string>): (Utils.Set.t<string> => float) => {
|
|
52
|
-
let codes = fieldCodes(fields)
|
|
53
|
-
selectedTransactionFields => selectedTransactionFields->maskFromFields(~codes)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Bitwise OR of two per-event masks. Masks fit in 32 bits (≤32 transaction
|
|
57
|
-
// fields), so `>>> 0` recovers the unsigned value that a plain `|` renders
|
|
58
|
-
// negative once bit 31 is set.
|
|
59
|
-
let orMask: (float, float) => float = %raw(`(a, b) => (a | b) >>> 0`)
|
|
24
|
+
// One event's selected transaction fields → store selection bitmask, built from
|
|
25
|
+
// the ecosystem's ordered field-name array (the bit index is the field code
|
|
26
|
+
// shared with the Rust store, `EvmTxField`/`SvmTxField`).
|
|
27
|
+
let makeMaskFn = FieldMask.makeMaskFn
|
|
28
|
+
let orMask = FieldMask.orMask
|
|
29
|
+
let fieldCodes = FieldMask.fieldCodes
|
|
60
30
|
|
|
61
31
|
// Drain another store (a fetch-response page) into this one.
|
|
62
32
|
@send external merge: (t, t) => unit = "merge"
|
|
@@ -77,74 +47,3 @@ external materialize: (
|
|
|
77
47
|
|
|
78
48
|
// Drop transactions for blocks above the given block (rolled back).
|
|
79
49
|
@send external rollback: (t, int) => unit = "rollback"
|
|
80
|
-
|
|
81
|
-
// Materialise each store-backed item's selected transaction fields and write the
|
|
82
|
-
// resulting transaction onto its payload. Every event's mask comes from its own
|
|
83
|
-
// `eventConfig.transactionFieldMask`, so a transaction decodes only the fields
|
|
84
|
-
// the events on it selected — a large field (e.g. `input`) never materialises
|
|
85
|
-
// for events that didn't ask for it. Items that already carry an inline
|
|
86
|
-
// transaction (RPC/simulate/Fuel) are skipped. Store-backed items always get a
|
|
87
|
-
// transaction object — the selected fields, or `{}` when nothing was selected —
|
|
88
|
-
// so `event.transaction` is never `undefined` (matching the inline sources).
|
|
89
|
-
// Deduped per (blockNumber, transactionIndex); each row's mask is the OR of the
|
|
90
|
-
// masks of the events sharing that transaction.
|
|
91
|
-
let materializeItems = async (store: t, ~items: array<Internal.item>) => {
|
|
92
|
-
// Store-backed items arrive in (block, logIndex) order, and a transaction's
|
|
93
|
-
// logs are contiguous within a block, so events sharing a (blockNumber,
|
|
94
|
-
// transactionIndex) are adjacent regardless of event. Group them by extending
|
|
95
|
-
// the current run rather than hashing a string key per item. A key recurring
|
|
96
|
-
// non-adjacently just splits into two groups (one redundant decode) — never
|
|
97
|
-
// incorrect.
|
|
98
|
-
let blockNumbers = []
|
|
99
|
-
let transactionIndices = []
|
|
100
|
-
let masks = []
|
|
101
|
-
let payloadGroups = []
|
|
102
|
-
let anySelected = ref(false)
|
|
103
|
-
|
|
104
|
-
items->Array.forEach(item =>
|
|
105
|
-
switch item {
|
|
106
|
-
| Internal.Event(_) =>
|
|
107
|
-
let eventItem = item->Internal.castUnsafeEventItem
|
|
108
|
-
switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
|
|
109
|
-
| Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
|
|
110
|
-
| None =>
|
|
111
|
-
let {blockNumber, transactionIndex} = eventItem
|
|
112
|
-
let mask = eventItem.eventConfig.transactionFieldMask
|
|
113
|
-
if mask != 0. {
|
|
114
|
-
anySelected := true
|
|
115
|
-
}
|
|
116
|
-
let last = payloadGroups->Array.length - 1
|
|
117
|
-
if (
|
|
118
|
-
last >= 0 &&
|
|
119
|
-
blockNumbers->Array.getUnsafe(last) == blockNumber &&
|
|
120
|
-
transactionIndices->Array.getUnsafe(last) == transactionIndex
|
|
121
|
-
) {
|
|
122
|
-
payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
|
|
123
|
-
masks->Array.setUnsafe(last, orMask(masks->Array.getUnsafe(last), mask))
|
|
124
|
-
} else {
|
|
125
|
-
blockNumbers->Array.push(blockNumber)
|
|
126
|
-
transactionIndices->Array.push(transactionIndex)
|
|
127
|
-
masks->Array.push(mask)
|
|
128
|
-
payloadGroups->Array.push([eventItem.payload])
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
| Internal.Block(_) => ()
|
|
132
|
-
}
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
if payloadGroups->Utils.Array.notEmpty {
|
|
136
|
-
if anySelected.contents {
|
|
137
|
-
let txs = await store->materialize(~blockNumbers, ~transactionIndices, ~masks)
|
|
138
|
-
payloadGroups->Array.forEachWithIndex((payloads, i) => {
|
|
139
|
-
let tx = txs->Array.getUnsafe(i)
|
|
140
|
-
payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
|
|
141
|
-
})
|
|
142
|
-
} else {
|
|
143
|
-
// No event selected any field: stamp an empty transaction object so
|
|
144
|
-
// `event.transaction` is never undefined, without a materialize call.
|
|
145
|
-
payloadGroups->Array.forEach(payloads =>
|
|
146
|
-
payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(%raw(`{}`)))
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|