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
package/src/IndexerState.res.mjs
CHANGED
|
@@ -97,7 +97,7 @@ function make$2(config, persistence, chainStates, isInReorgThreshold, isRealtime
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
function makeFromDbState(config, persistence, initialState,
|
|
100
|
+
function makeFromDbState(config, persistence, initialState, registrationsByChainId, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, reducedPollingInterval, targetBufferSizeOpt, onError) {
|
|
101
101
|
let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
|
|
102
102
|
let shouldUseTui = shouldUseTuiOpt !== undefined ? shouldUseTuiOpt : false;
|
|
103
103
|
let exitAfterFirstEventBlock = exitAfterFirstEventBlockOpt !== undefined ? exitAfterFirstEventBlockOpt : false;
|
|
@@ -123,7 +123,7 @@ function makeFromDbState(config, persistence, initialState, registrations, isDev
|
|
|
123
123
|
initialState.chains.forEach(resumedChainState => {
|
|
124
124
|
let chain = Config.getChain(config, resumedChainState.id);
|
|
125
125
|
let chainConfig = ChainMap.get(config.chainMap, chain);
|
|
126
|
-
chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config,
|
|
126
|
+
chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, registrationsByChainId, reducedPollingInterval);
|
|
127
127
|
});
|
|
128
128
|
let allChainsReady = {
|
|
129
129
|
contents: initialState.chains.length !== 0
|
|
@@ -231,7 +231,7 @@ function invalidateInflight(state) {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
function applyBatchProgress(state, batch) {
|
|
234
|
-
CrossChainState.applyBatchProgress(state.crossChainState, batch);
|
|
234
|
+
CrossChainState.applyBatchProgress(state.crossChainState, batch, state.config.ecosystem.blockTimestampName);
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
function isProcessing(state) {
|
package/src/IndexerState.resi
CHANGED
|
@@ -44,7 +44,7 @@ let makeFromDbState: (
|
|
|
44
44
|
~config: Config.t,
|
|
45
45
|
~persistence: Persistence.t,
|
|
46
46
|
~initialState: Persistence.initialState,
|
|
47
|
-
~
|
|
47
|
+
~registrationsByChainId: HandlerRegister.registrationsByChainId,
|
|
48
48
|
~isDevelopmentMode: bool=?,
|
|
49
49
|
~shouldUseTui: bool=?,
|
|
50
50
|
~exitAfterFirstEventBlock: bool=?,
|
|
@@ -8,15 +8,18 @@ let deriveEffectiveStartBlock = (~registrationBlock: int, ~contractStartBlock: o
|
|
|
8
8
|
Pervasives.max(Pervasives.max(registrationBlock, 0), contractStartBlock->Option.getOr(0))
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
let makeContractConfigs = (~
|
|
11
|
+
let makeContractConfigs = (~onEventRegistrations: array<Internal.onEventRegistration>): dict<
|
|
12
|
+
contractConfig,
|
|
13
|
+
> => {
|
|
12
14
|
let contractConfigs: dict<contractConfig> = Dict.make()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
onEventRegistrations->Array.forEach(reg => {
|
|
16
|
+
let contractName = reg.eventConfig.contractName
|
|
17
|
+
switch contractConfigs->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
15
18
|
| Some({startBlock}) =>
|
|
16
19
|
contractConfigs->Dict.set(
|
|
17
|
-
|
|
20
|
+
contractName,
|
|
18
21
|
{
|
|
19
|
-
startBlock: switch (startBlock,
|
|
22
|
+
startBlock: switch (startBlock, reg.startBlock) {
|
|
20
23
|
| (Some(a), Some(b)) => Some(Pervasives.min(a, b))
|
|
21
24
|
| (Some(_) as s, None) | (None, Some(_) as s) => s
|
|
22
25
|
| (None, None) => None
|
|
@@ -25,9 +28,9 @@ let makeContractConfigs = (~eventConfigs: array<Internal.eventConfig>): dict<con
|
|
|
25
28
|
)
|
|
26
29
|
| None =>
|
|
27
30
|
contractConfigs->Dict.set(
|
|
28
|
-
|
|
31
|
+
contractName,
|
|
29
32
|
{
|
|
30
|
-
startBlock:
|
|
33
|
+
startBlock: reg.startBlock,
|
|
31
34
|
},
|
|
32
35
|
)
|
|
33
36
|
}
|
|
@@ -8,14 +8,15 @@ function deriveEffectiveStartBlock(registrationBlock, contractStartBlock) {
|
|
|
8
8
|
return Primitive_int.max(Primitive_int.max(registrationBlock, 0), Stdlib_Option.getOr(contractStartBlock, 0));
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function makeContractConfigs(
|
|
11
|
+
function makeContractConfigs(onEventRegistrations) {
|
|
12
12
|
let contractConfigs = {};
|
|
13
|
-
|
|
14
|
-
let
|
|
13
|
+
onEventRegistrations.forEach(reg => {
|
|
14
|
+
let contractName = reg.eventConfig.contractName;
|
|
15
|
+
let match = contractConfigs[contractName];
|
|
15
16
|
if (match !== undefined) {
|
|
16
17
|
let startBlock = match.startBlock;
|
|
17
|
-
let match$1 =
|
|
18
|
-
contractConfigs[
|
|
18
|
+
let match$1 = reg.startBlock;
|
|
19
|
+
contractConfigs[contractName] = {
|
|
19
20
|
startBlock: startBlock !== undefined ? (
|
|
20
21
|
match$1 !== undefined ? Primitive_int.min(startBlock, match$1) : startBlock
|
|
21
22
|
) : (
|
|
@@ -24,8 +25,8 @@ function makeContractConfigs(eventConfigs) {
|
|
|
24
25
|
};
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
27
|
-
contractConfigs[
|
|
28
|
-
startBlock:
|
|
28
|
+
contractConfigs[contractName] = {
|
|
29
|
+
startBlock: reg.startBlock
|
|
29
30
|
};
|
|
30
31
|
});
|
|
31
32
|
return contractConfigs;
|
|
@@ -6,7 +6,9 @@ type t
|
|
|
6
6
|
|
|
7
7
|
let deriveEffectiveStartBlock: (~registrationBlock: int, ~contractStartBlock: option<int>) => int
|
|
8
8
|
|
|
9
|
-
let makeContractConfigs: (
|
|
9
|
+
let makeContractConfigs: (
|
|
10
|
+
~onEventRegistrations: array<Internal.onEventRegistration>,
|
|
11
|
+
) => dict<contractConfig>
|
|
10
12
|
|
|
11
13
|
let makeIndexingAddress: (
|
|
12
14
|
~contract: Internal.indexingAddress,
|
package/src/Internal.res
CHANGED
|
@@ -166,6 +166,19 @@ let allSvmTransactionFields: array<svmTransactionField> = [
|
|
|
166
166
|
]
|
|
167
167
|
let svmTransactionFieldSchema = S.enum(allSvmTransactionFields)
|
|
168
168
|
|
|
169
|
+
// All SVM block fields. `slot`/`time`/`hash` are always included; the rest are
|
|
170
|
+
// selectable via `field_selection.block_fields` (see `allSvmBlockFields`).
|
|
171
|
+
type svmBlockField =
|
|
172
|
+
| @as("slot") Slot
|
|
173
|
+
| @as("time") Time
|
|
174
|
+
| @as("hash") Hash
|
|
175
|
+
| @as("height") Height
|
|
176
|
+
| @as("parentSlot") ParentSlot
|
|
177
|
+
| @as("parentHash") ParentHash
|
|
178
|
+
|
|
179
|
+
let allSvmBlockFields: array<svmBlockField> = [Height, ParentSlot, ParentHash]
|
|
180
|
+
let svmBlockFieldSchema = S.enum(allSvmBlockFields)
|
|
181
|
+
|
|
169
182
|
// Static sets of nullable field names — used by RpcSource and HyperSyncSource to wrap schemas with S.nullable
|
|
170
183
|
let evmNullableBlockFields = Utils.Set.fromArray(
|
|
171
184
|
(
|
|
@@ -295,8 +308,8 @@ type genericEvent<'params, 'block, 'transaction> = {
|
|
|
295
308
|
block: 'block,
|
|
296
309
|
}
|
|
297
310
|
|
|
298
|
-
// Opaque internally — block
|
|
299
|
-
//
|
|
311
|
+
// Opaque internally — the block number needed by the runtime lives on the
|
|
312
|
+
// item instead.
|
|
300
313
|
type event
|
|
301
314
|
|
|
302
315
|
// Opaque payload an item carries. A source builds an ecosystem-specific
|
|
@@ -312,6 +325,11 @@ type eventPayload
|
|
|
312
325
|
@get external getPayloadTransaction: eventPayload => Nullable.t<eventTransaction> = "transaction"
|
|
313
326
|
@set external setPayloadTransaction: (eventPayload, eventTransaction) => unit = "transaction"
|
|
314
327
|
|
|
328
|
+
// Generic access to the payload's `block`: written/enriched at batch prep for
|
|
329
|
+
// store-backed ecosystems (EVM/SVM HyperSync) and present inline otherwise.
|
|
330
|
+
@get external getPayloadBlock: eventPayload => Nullable.t<eventBlock> = "block"
|
|
331
|
+
@set external setPayloadBlock: (eventPayload, eventBlock) => unit = "block"
|
|
332
|
+
|
|
315
333
|
type genericLoaderArgs<'event, 'context> = {
|
|
316
334
|
event: 'event,
|
|
317
335
|
context: 'context,
|
|
@@ -390,31 +408,17 @@ type indexingContract = {
|
|
|
390
408
|
effectiveStartBlock: int,
|
|
391
409
|
}
|
|
392
410
|
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
411
|
+
// Definition of an event/instruction we know how to decode: identity + decode
|
|
412
|
+
// schemas + chain-independent field selection. A pure function of the ABI +
|
|
413
|
+
// config, shared across chains. `private` so it can only be coerced from an
|
|
414
|
+
// ecosystem variant (fields never overwritten), which lets sources cast the
|
|
415
|
+
// base back down to evm/fuel/svm safely.
|
|
397
416
|
type eventConfig = private {
|
|
398
417
|
id: string,
|
|
399
418
|
name: string,
|
|
400
419
|
contractName: string,
|
|
401
|
-
isWildcard: bool,
|
|
402
|
-
// Whether the event has an event filter which uses addresses
|
|
403
|
-
filterByAddresses: bool,
|
|
404
|
-
// Usually always false for wildcard events
|
|
405
|
-
// But might be true for wildcard event with dynamic event filter by addresses
|
|
406
|
-
dependsOnAddresses: bool,
|
|
407
|
-
// Precompiled predicate (EVM only) for events that filter an indexed address
|
|
408
|
-
// param by registered addresses. Given the decoded event and the log's block
|
|
409
|
-
// number, drops an event whose param-address isn't registered at/before that
|
|
410
|
-
// block — the param-level analogue of EventRouter's srcAddress
|
|
411
|
-
// `effectiveStartBlock` check. Absent otherwise.
|
|
412
|
-
clientAddressFilter?: (eventPayload, int, dict<indexingContract>) => bool,
|
|
413
|
-
handler: option<handler>,
|
|
414
|
-
contractRegister: option<contractRegister>,
|
|
415
420
|
paramsRawEventSchema: S.schema<eventParams>,
|
|
416
421
|
simulateParamsSchema: S.schema<eventParams>,
|
|
417
|
-
startBlock: option<int>,
|
|
418
422
|
// Field names selected for the chain's transaction-store materialisation
|
|
419
423
|
// (camelCase, matching the ecosystem's `transactionFields`). Stored as a
|
|
420
424
|
// string set so the shared mask logic is ecosystem-agnostic; sources recover
|
|
@@ -425,6 +429,12 @@ type eventConfig = private {
|
|
|
425
429
|
// so each transaction decodes only the fields its event selected. `0.` when
|
|
426
430
|
// nothing is selected or the ecosystem carries the transaction inline (Fuel).
|
|
427
431
|
transactionFieldMask: float,
|
|
432
|
+
// Selected block fields precompiled to the block-store selection bitmask (bit
|
|
433
|
+
// per ecosystem field code). `0.` for ecosystems that carry the block fully
|
|
434
|
+
// inline (RPC/Fuel). The EVM selection always includes number/timestamp/hash,
|
|
435
|
+
// so an EVM mask always has their bits set; SVM stamps slot/time/hash inline
|
|
436
|
+
// from the response and its mask is the user's selection alone.
|
|
437
|
+
blockFieldMask: float,
|
|
428
438
|
}
|
|
429
439
|
|
|
430
440
|
type fuelEventKind =
|
|
@@ -460,18 +470,16 @@ type eventFilters =
|
|
|
460
470
|
|
|
461
471
|
type evmEventConfig = {
|
|
462
472
|
...eventConfig,
|
|
463
|
-
getEventFiltersOrThrow: ChainMap.Chain.t => eventFilters,
|
|
464
473
|
selectedBlockFields: Utils.Set.t<evmBlockField>,
|
|
465
474
|
sighash: string,
|
|
466
475
|
topicCount: int,
|
|
467
476
|
paramsMetadata: array<paramMeta>,
|
|
468
477
|
}
|
|
469
478
|
|
|
470
|
-
// Shared formula for `
|
|
471
|
-
// `EventConfigBuilder.build
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
// there simply pass it through as `false`.
|
|
479
|
+
// Shared formula for a registration's `dependsOnAddresses`. Kept here so the
|
|
480
|
+
// `EventConfigBuilder.build*OnEventRegistration` builders stay in sync. Fuel
|
|
481
|
+
// and SVM events always have `filterByAddresses=false`, so callers there pass
|
|
482
|
+
// it through as `false`.
|
|
475
483
|
let dependsOnAddresses = (~isWildcard, ~filterByAddresses) => !isWildcard || filterByAddresses
|
|
476
484
|
|
|
477
485
|
type evmContractConfig = {
|
|
@@ -490,6 +498,10 @@ type svmAccountFilterGroup = array<svmAccountFilter>
|
|
|
490
498
|
|
|
491
499
|
type svmInstructionEventConfig = {
|
|
492
500
|
...eventConfig,
|
|
501
|
+
/** Block fields selected via `field_selection.block_fields` (`slot` is always
|
|
502
|
+
included and excluded from this set). Drives the block query columns;
|
|
503
|
+
precompiled to `blockFieldMask` for store materialisation. */
|
|
504
|
+
selectedBlockFields: Utils.Set.t<svmBlockField>,
|
|
493
505
|
/** Base58 Solana program id this instruction belongs to. */
|
|
494
506
|
programId: SvmTypes.Pubkey.t,
|
|
495
507
|
/** Hex-encoded discriminator. `None` matches every instruction in the program. */
|
|
@@ -516,6 +528,44 @@ type svmInstructionEventConfig = {
|
|
|
516
528
|
definedTypes: JSON.t,
|
|
517
529
|
}
|
|
518
530
|
|
|
531
|
+
// Per-(event, chain) registration produced when user handler code registers an
|
|
532
|
+
// event (`onEvent`) or a dynamic contract registers. References its definition
|
|
533
|
+
// by value as `.eventConfig` and adds the handler binding plus the
|
|
534
|
+
// registration/`where`-derived fetch state. Not `private`: Fuel/SVM
|
|
535
|
+
// registrations add no ecosystem-specific fields (so they're bare aliases that
|
|
536
|
+
// must stay directly constructable), and the evm→base cast in sources is sound
|
|
537
|
+
// by ecosystem homogeneity — an EVM chain only ever holds `evmOnEventRegistration`s.
|
|
538
|
+
type onEventRegistration = {
|
|
539
|
+
eventConfig: eventConfig,
|
|
540
|
+
handler: option<handler>,
|
|
541
|
+
contractRegister: option<contractRegister>,
|
|
542
|
+
isWildcard: bool,
|
|
543
|
+
// Whether the event has an event filter which uses addresses.
|
|
544
|
+
filterByAddresses: bool,
|
|
545
|
+
// Usually always false for wildcard events, but might be true for a wildcard
|
|
546
|
+
// event with a dynamic event filter by addresses.
|
|
547
|
+
dependsOnAddresses: bool,
|
|
548
|
+
// Precompiled predicate for events that filter an indexed address param by
|
|
549
|
+
// registered addresses (see `EventConfigBuilder.buildAddressFilter`); drops a
|
|
550
|
+
// decoded event whose param-address isn't registered at/before the log's
|
|
551
|
+
// block. Absent otherwise.
|
|
552
|
+
clientAddressFilter?: (eventPayload, int, dict<indexingContract>) => bool,
|
|
553
|
+
// Final start block: the contract/chain config value, overridden by a
|
|
554
|
+
// `where.block.number._gte` when the registered `where` supplies one.
|
|
555
|
+
startBlock: option<int>,
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
type evmOnEventRegistration = {
|
|
559
|
+
...onEventRegistration,
|
|
560
|
+
getEventFiltersOrThrow: ChainMap.Chain.t => eventFilters,
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Fuel and SVM registrations add no ecosystem-specific fetch state (their
|
|
564
|
+
// filters are config-derived and live on the definition), so they're bare
|
|
565
|
+
// aliases of the base registration.
|
|
566
|
+
type fuelOnEventRegistration = onEventRegistration
|
|
567
|
+
type svmOnEventRegistration = onEventRegistration
|
|
568
|
+
|
|
519
569
|
type svmProgramConfig = {
|
|
520
570
|
name: string,
|
|
521
571
|
programId: SvmTypes.Pubkey.t,
|
|
@@ -536,11 +586,9 @@ type dcs = array<indexingAddress>
|
|
|
536
586
|
// to make item properly unboxed
|
|
537
587
|
type eventItem = private {
|
|
538
588
|
kind: [#0],
|
|
539
|
-
|
|
540
|
-
timestamp: int,
|
|
589
|
+
onEventRegistration: onEventRegistration,
|
|
541
590
|
chain: ChainMap.Chain.t,
|
|
542
591
|
blockNumber: int,
|
|
543
|
-
blockHash: string,
|
|
544
592
|
logIndex: int,
|
|
545
593
|
// Within-block transaction index — the key into the per-chain transaction
|
|
546
594
|
// store. Unused (0) for ecosystems that carry the transaction inline (Fuel).
|
|
@@ -575,7 +623,7 @@ type onBlockArgs = {
|
|
|
575
623
|
context: handlerContext,
|
|
576
624
|
}
|
|
577
625
|
|
|
578
|
-
type
|
|
626
|
+
type onBlockRegistration = {
|
|
579
627
|
// When there are multiple onBlock handlers per chain,
|
|
580
628
|
// we want to use the order they are defined for sorting
|
|
581
629
|
index: int,
|
|
@@ -591,16 +639,14 @@ type onBlockConfig = {
|
|
|
591
639
|
type item =
|
|
592
640
|
| @as(0)
|
|
593
641
|
Event({
|
|
594
|
-
|
|
595
|
-
timestamp: int,
|
|
642
|
+
onEventRegistration: onEventRegistration,
|
|
596
643
|
chain: ChainMap.Chain.t,
|
|
597
644
|
blockNumber: int,
|
|
598
|
-
blockHash: string,
|
|
599
645
|
logIndex: int,
|
|
600
646
|
transactionIndex: int,
|
|
601
647
|
payload: eventPayload,
|
|
602
648
|
})
|
|
603
|
-
| @as(1) Block({
|
|
649
|
+
| @as(1) Block({onBlockRegistration: onBlockRegistration, blockNumber: int, logIndex: int})
|
|
604
650
|
|
|
605
651
|
external castUnsafeEventItem: item => eventItem = "%identity"
|
|
606
652
|
|
|
@@ -612,7 +658,7 @@ external getItemLogIndex: item => int = "logIndex"
|
|
|
612
658
|
let getItemChainId = item =>
|
|
613
659
|
switch item {
|
|
614
660
|
| Event({chain}) => chain->ChainMap.Chain.toChainId
|
|
615
|
-
| Block({
|
|
661
|
+
| Block({onBlockRegistration: {chainId}}) => chainId
|
|
616
662
|
}
|
|
617
663
|
|
|
618
664
|
@get
|
package/src/Internal.res.mjs
CHANGED
|
@@ -90,6 +90,14 @@ let allSvmTransactionFields = [
|
|
|
90
90
|
|
|
91
91
|
let svmTransactionFieldSchema = S$RescriptSchema.$$enum(allSvmTransactionFields);
|
|
92
92
|
|
|
93
|
+
let allSvmBlockFields = [
|
|
94
|
+
"height",
|
|
95
|
+
"parentSlot",
|
|
96
|
+
"parentHash"
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
let svmBlockFieldSchema = S$RescriptSchema.$$enum(allSvmBlockFields);
|
|
100
|
+
|
|
93
101
|
let evmNullableBlockFields = new Set([
|
|
94
102
|
"nonce",
|
|
95
103
|
"difficulty",
|
|
@@ -141,7 +149,7 @@ function getItemChainId(item) {
|
|
|
141
149
|
if (item.kind === 0) {
|
|
142
150
|
return item.chain;
|
|
143
151
|
} else {
|
|
144
|
-
return item.
|
|
152
|
+
return item.onBlockRegistration.chainId;
|
|
145
153
|
}
|
|
146
154
|
}
|
|
147
155
|
|
|
@@ -178,6 +186,8 @@ export {
|
|
|
178
186
|
evmTransactionFieldSchema,
|
|
179
187
|
allSvmTransactionFields,
|
|
180
188
|
svmTransactionFieldSchema,
|
|
189
|
+
allSvmBlockFields,
|
|
190
|
+
svmBlockFieldSchema,
|
|
181
191
|
evmNullableBlockFields,
|
|
182
192
|
evmNullableTransactionFields,
|
|
183
193
|
dependsOnAddresses,
|
package/src/Main.res
CHANGED
|
@@ -82,7 +82,7 @@ let getInitialChainState = (~chainId: int): option<Persistence.initialChainState
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
// Importing `generated` must not trigger `Config.
|
|
85
|
+
// Importing `generated` must not trigger `Config.load()`,
|
|
86
86
|
// so the exported indexer calls this lazily on first `indexer.chains` access.
|
|
87
87
|
let buildChainsObject = (~config: Config.t) => {
|
|
88
88
|
let chainIds = []
|
|
@@ -372,7 +372,7 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
372
372
|
// math at `FetchState.res:619` stays untouched.
|
|
373
373
|
let onBlockFn = (rawOptions: 'a, handler: 'b) => {
|
|
374
374
|
HandlerRegister.throwIfFinishedRegistration(~methodName="onBlock")
|
|
375
|
-
let config = Config.
|
|
375
|
+
let config = Config.load()
|
|
376
376
|
let ecosystem = config.ecosystem
|
|
377
377
|
let raw =
|
|
378
378
|
rawOptions->(
|
|
@@ -471,13 +471,13 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
471
471
|
// `Api.res` calls `getGlobalIndexer()` at envio-package load, so the keys
|
|
472
472
|
// array is memoized lazily: an early `createEffect` / `S` import that
|
|
473
473
|
// never touches the indexer must not trigger a config parse. The memo is
|
|
474
|
-
// safe because `Config.
|
|
474
|
+
// safe because `Config.load` is itself pure.
|
|
475
475
|
let keysMemo: ref<option<array<string>>> = ref(None)
|
|
476
476
|
let getKeys = () =>
|
|
477
477
|
switch keysMemo.contents {
|
|
478
478
|
| Some(k) => k
|
|
479
479
|
| None => {
|
|
480
|
-
let keys = switch Config.
|
|
480
|
+
let keys = switch Config.load().ecosystem.name {
|
|
481
481
|
| Evm | Fuel => [
|
|
482
482
|
"name",
|
|
483
483
|
"description",
|
|
@@ -505,15 +505,15 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
505
505
|
|
|
506
506
|
let get = (~prop: string) =>
|
|
507
507
|
switch prop {
|
|
508
|
-
| "name" => Config.
|
|
508
|
+
| "name" => Config.load().name->(Utils.magic: string => unknown)
|
|
509
509
|
| "description" =>
|
|
510
|
-
Config.
|
|
510
|
+
Config.load().description->(Utils.magic: option<string> => unknown)
|
|
511
511
|
| "chainIds" => {
|
|
512
|
-
let (_, chainIds) = buildChainsObject(~config=Config.
|
|
512
|
+
let (_, chainIds) = buildChainsObject(~config=Config.load())
|
|
513
513
|
chainIds->(Utils.magic: array<int> => unknown)
|
|
514
514
|
}
|
|
515
515
|
| "chains" => {
|
|
516
|
-
let (chains, _) = buildChainsObject(~config=Config.
|
|
516
|
+
let (chains, _) = buildChainsObject(~config=Config.load())
|
|
517
517
|
chains->(Utils.magic: {..} => unknown)
|
|
518
518
|
}
|
|
519
519
|
| "onEvent" => onEventFn->Utils.magic
|
|
@@ -657,7 +657,7 @@ type mainArgs = Yargs.parsedArgs<args>
|
|
|
657
657
|
let getEnvioInfo = () => Config.getPublicConfigJson()->Config.stripSensitiveData
|
|
658
658
|
|
|
659
659
|
let migrate = async (~reset) => {
|
|
660
|
-
let config = Config.
|
|
660
|
+
let config = Config.load()
|
|
661
661
|
let persistence = PgStorage.makePersistenceFromConfig(~config)
|
|
662
662
|
await persistence->Persistence.init(
|
|
663
663
|
~reset,
|
|
@@ -670,7 +670,7 @@ let migrate = async (~reset) => {
|
|
|
670
670
|
}
|
|
671
671
|
|
|
672
672
|
let dropSchema = async () => {
|
|
673
|
-
let config = Config.
|
|
673
|
+
let config = Config.load()
|
|
674
674
|
let persistence = PgStorage.makePersistenceFromConfig(~config)
|
|
675
675
|
await persistence.storage.reset()
|
|
676
676
|
await persistence.storage.close()
|
|
@@ -685,7 +685,7 @@ let start = async (
|
|
|
685
685
|
~reset=false,
|
|
686
686
|
~isTest=false,
|
|
687
687
|
~exitAfterFirstEventBlock=false,
|
|
688
|
-
~patchConfig: option<(Config.t, HandlerRegister.
|
|
688
|
+
~patchConfig: option<(Config.t, HandlerRegister.registrationsByChainId) => Config.t>=?,
|
|
689
689
|
) => {
|
|
690
690
|
let mainArgs: mainArgs = process->argv->Yargs.hideBin->Yargs.yargs->Yargs.argv
|
|
691
691
|
let explicitTui = switch mainArgs.tuiOff {
|
|
@@ -699,31 +699,30 @@ let start = async (
|
|
|
699
699
|
}
|
|
700
700
|
// Initialize persistence first so the exported indexer value contains state from the database
|
|
701
701
|
// when handler files are loaded (they may access the indexer at module top level).
|
|
702
|
-
let
|
|
702
|
+
let config = Config.load()
|
|
703
703
|
// isDevelopmentMode controls whether the indexer stays alive after all
|
|
704
704
|
// chains finish (keepProcessAlive) and whether the console API is exposed.
|
|
705
705
|
// Set by `envio dev` via the public config's `isDev` field; `envio start`
|
|
706
706
|
// leaves it false so the process exits cleanly when indexing completes.
|
|
707
|
-
let isDevelopmentMode = !isTest &&
|
|
707
|
+
let isDevelopmentMode = !isTest && config.isDev
|
|
708
708
|
let persistence = switch persistence {
|
|
709
709
|
| Some(p) => p
|
|
710
|
-
| None => PgStorage.makePersistenceFromConfig(~config
|
|
710
|
+
| None => PgStorage.makePersistenceFromConfig(~config)
|
|
711
711
|
}
|
|
712
712
|
globalPersistenceRef := Some(persistence)
|
|
713
713
|
await persistence->Persistence.init(
|
|
714
714
|
~reset,
|
|
715
|
-
~chainConfigs=
|
|
715
|
+
~chainConfigs=config.chainMap->ChainMap.values,
|
|
716
716
|
~envioInfo=getEnvioInfo(),
|
|
717
717
|
~resetCommand=isDevelopmentMode ? "envio dev -r" : "envio start -r",
|
|
718
718
|
~runCommand=Some(isDevelopmentMode ? "envio dev" : "envio start"),
|
|
719
719
|
)
|
|
720
720
|
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
//
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
)
|
|
721
|
+
// Loads user handler files, which register handler/contractRegister/where
|
|
722
|
+
// state into the global `HandlerRegister` registry as a side effect; this
|
|
723
|
+
// returns that state resolved into per-chain registrations. `config` itself
|
|
724
|
+
// is never mutated by registration — it holds only event definitions.
|
|
725
|
+
let registrationsByChainId = await HandlerLoader.registerAllHandlers(~config)
|
|
727
726
|
let config = if isTest {
|
|
728
727
|
{...config, shouldRollbackOnReorg: false}
|
|
729
728
|
} else {
|
|
@@ -731,7 +730,7 @@ let start = async (
|
|
|
731
730
|
}
|
|
732
731
|
|
|
733
732
|
let config = switch patchConfig {
|
|
734
|
-
| Some(patchConfig) => patchConfig(config,
|
|
733
|
+
| Some(patchConfig) => patchConfig(config, registrationsByChainId)
|
|
735
734
|
| None => config
|
|
736
735
|
}
|
|
737
736
|
// The single fatal-error handler, invoked once via IndexerState.errorExit.
|
|
@@ -778,7 +777,7 @@ let start = async (
|
|
|
778
777
|
~config,
|
|
779
778
|
~persistence,
|
|
780
779
|
~initialState=persistence->Persistence.getInitializedState,
|
|
781
|
-
~
|
|
780
|
+
~registrationsByChainId,
|
|
782
781
|
~isDevelopmentMode,
|
|
783
782
|
~shouldUseTui,
|
|
784
783
|
~exitAfterFirstEventBlock,
|
package/src/Main.res.mjs
CHANGED
|
@@ -296,7 +296,7 @@ function getGlobalIndexer() {
|
|
|
296
296
|
};
|
|
297
297
|
let onBlockFn = (rawOptions, handler) => {
|
|
298
298
|
HandlerRegister.throwIfFinishedRegistration("onBlock");
|
|
299
|
-
let config = Config.
|
|
299
|
+
let config = Config.load();
|
|
300
300
|
let ecosystem = config.ecosystem;
|
|
301
301
|
let match = buildChainsObject(config);
|
|
302
302
|
let chains = match[0];
|
|
@@ -350,7 +350,7 @@ function getGlobalIndexer() {
|
|
|
350
350
|
if (k !== undefined) {
|
|
351
351
|
return k;
|
|
352
352
|
}
|
|
353
|
-
let match = Config.
|
|
353
|
+
let match = Config.load().ecosystem.name;
|
|
354
354
|
let keys;
|
|
355
355
|
let exit = 0;
|
|
356
356
|
switch (match) {
|
|
@@ -388,15 +388,15 @@ function getGlobalIndexer() {
|
|
|
388
388
|
let get = prop => {
|
|
389
389
|
switch (prop) {
|
|
390
390
|
case "chainIds" :
|
|
391
|
-
return buildChainsObject(Config.
|
|
391
|
+
return buildChainsObject(Config.load())[1];
|
|
392
392
|
case "chains" :
|
|
393
|
-
return buildChainsObject(Config.
|
|
393
|
+
return buildChainsObject(Config.load())[0];
|
|
394
394
|
case "contractRegister" :
|
|
395
395
|
return contractRegisterFn;
|
|
396
396
|
case "description" :
|
|
397
|
-
return Config.
|
|
397
|
+
return Config.load().description;
|
|
398
398
|
case "name" :
|
|
399
|
-
return Config.
|
|
399
|
+
return Config.load().name;
|
|
400
400
|
case "onEvent" :
|
|
401
401
|
return onEventFn;
|
|
402
402
|
case "onInstruction" :
|
|
@@ -501,14 +501,14 @@ function getEnvioInfo() {
|
|
|
501
501
|
}
|
|
502
502
|
|
|
503
503
|
async function migrate(reset) {
|
|
504
|
-
let config = Config.
|
|
504
|
+
let config = Config.load();
|
|
505
505
|
let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
|
|
506
506
|
await Persistence.init(persistence, ChainMap.values(config.chainMap), Config.stripSensitiveData(Config.getPublicConfigJson()), "envio local db-migrate setup", undefined, reset);
|
|
507
507
|
return await persistence.storage.close();
|
|
508
508
|
}
|
|
509
509
|
|
|
510
510
|
async function dropSchema() {
|
|
511
|
-
let config = Config.
|
|
511
|
+
let config = Config.load();
|
|
512
512
|
let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
|
|
513
513
|
await persistence.storage.reset();
|
|
514
514
|
return await persistence.storage.close();
|
|
@@ -526,14 +526,12 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
526
526
|
let shouldUseTui = isTest ? false : (
|
|
527
527
|
explicitTui !== undefined ? explicitTui : !Envio.isNonInteractive()
|
|
528
528
|
);
|
|
529
|
-
let
|
|
530
|
-
let isDevelopmentMode = !isTest &&
|
|
531
|
-
let persistence$1 = persistence !== undefined ? persistence : PgStorage.makePersistenceFromConfig(
|
|
529
|
+
let config = Config.load();
|
|
530
|
+
let isDevelopmentMode = !isTest && config.isDev;
|
|
531
|
+
let persistence$1 = persistence !== undefined ? persistence : PgStorage.makePersistenceFromConfig(config, undefined);
|
|
532
532
|
globalPersistenceRef.contents = persistence$1;
|
|
533
|
-
await Persistence.init(persistence$1, ChainMap.values(
|
|
534
|
-
let
|
|
535
|
-
let registrations = match[1];
|
|
536
|
-
let config = match[0];
|
|
533
|
+
await Persistence.init(persistence$1, ChainMap.values(config.chainMap), Config.stripSensitiveData(Config.getPublicConfigJson()), isDevelopmentMode ? "envio dev -r" : "envio start -r", isDevelopmentMode ? "envio dev" : "envio start", reset);
|
|
534
|
+
let registrationsByChainId = await HandlerLoader.registerAllHandlers(config);
|
|
537
535
|
let config$1 = isTest ? ({
|
|
538
536
|
name: config.name,
|
|
539
537
|
description: config.description,
|
|
@@ -555,7 +553,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
555
553
|
allEntities: config.allEntities,
|
|
556
554
|
allEnums: config.allEnums
|
|
557
555
|
}) : config;
|
|
558
|
-
let config$2 = patchConfig !== undefined ? patchConfig(config$1,
|
|
556
|
+
let config$2 = patchConfig !== undefined ? patchConfig(config$1, registrationsByChainId) : config$1;
|
|
559
557
|
let onErrorReject = {
|
|
560
558
|
contents: undefined
|
|
561
559
|
};
|
|
@@ -593,7 +591,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
593
591
|
};
|
|
594
592
|
}, persistence$1, isDevelopmentMode);
|
|
595
593
|
}
|
|
596
|
-
let state = IndexerState.makeFromDbState(config$2, persistence$1, Persistence.getInitializedState(persistence$1),
|
|
594
|
+
let state = IndexerState.makeFromDbState(config$2, persistence$1, Persistence.getInitializedState(persistence$1), registrationsByChainId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, undefined, undefined, onError);
|
|
597
595
|
if (shouldUseTui) {
|
|
598
596
|
Tui.start(() => state);
|
|
599
597
|
}
|
package/src/Metrics.res
CHANGED
|
@@ -18,16 +18,57 @@ let renderGauge = (~name, ~help, ~chains: dict<'a>, ~value: 'a => int) => {
|
|
|
18
18
|
out.contents
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
let sourceRequestTotalName = "envio_source_request_total"
|
|
22
|
+
let sourceRequestTotalHelp = "The number of requests made to data sources."
|
|
23
|
+
let sourceRequestSecondsTotalName = "envio_source_request_seconds_total"
|
|
24
|
+
let sourceRequestSecondsTotalHelp = "Cumulative time spent on data source requests."
|
|
25
|
+
|
|
26
|
+
// Hand-rolls both HELP/TYPE blocks and their samples straight from
|
|
27
|
+
// SourceManager's aggregates — fully our own state, no prom-client registry
|
|
28
|
+
// involved. A leading "\n" on each block since, unlike prom-client's own
|
|
29
|
+
// metrics() output, renderGauge's output right before this doesn't end in one.
|
|
30
|
+
// Skips a method's seconds line when it has no timing (e.g. heightSubscription,
|
|
31
|
+
// which only ever records a count), and skips both blocks entirely when there's
|
|
32
|
+
// nothing to report at all.
|
|
33
|
+
let renderSourceRequests = (~samples: array<SourceManager.requestStatSample>) => {
|
|
34
|
+
if samples->Utils.Array.isEmpty {
|
|
35
|
+
""
|
|
36
|
+
} else {
|
|
37
|
+
let countOut = ref(
|
|
38
|
+
`\n# HELP ${sourceRequestTotalName} ${sourceRequestTotalHelp}\n# TYPE ${sourceRequestTotalName} counter`,
|
|
39
|
+
)
|
|
40
|
+
let secondsOut = ref(
|
|
41
|
+
`\n# HELP ${sourceRequestSecondsTotalName} ${sourceRequestSecondsTotalHelp}\n# TYPE ${sourceRequestSecondsTotalName} counter`,
|
|
42
|
+
)
|
|
43
|
+
samples->Array.forEach(sample => {
|
|
44
|
+
let labels = `{source="${sample.sourceName}",chainId="${sample.chainId->Int.toString}",method="${sample.method}"}`
|
|
45
|
+
countOut :=
|
|
46
|
+
countOut.contents ++ `\n${sourceRequestTotalName}${labels} ${sample.count->Int.toString}`
|
|
47
|
+
if sample.seconds !== 0. {
|
|
48
|
+
secondsOut :=
|
|
49
|
+
secondsOut.contents ++
|
|
50
|
+
`\n${sourceRequestSecondsTotalName}${labels} ${sample.seconds->Float.toString}`
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
countOut.contents ++ secondsOut.contents
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
21
57
|
let collect = async (~state: option<IndexerState.t>) => {
|
|
22
58
|
let base = await PromClient.defaultRegister->PromClient.metrics
|
|
23
59
|
switch state {
|
|
24
60
|
| None => base
|
|
25
61
|
| Some(state) =>
|
|
62
|
+
let chainStates = state->IndexerState.chainStates
|
|
63
|
+
let sourceRequestSamples =
|
|
64
|
+
chainStates
|
|
65
|
+
->Dict.valuesToArray
|
|
66
|
+
->Array.flatMap(cs => cs->ChainState.sourceManager->SourceManager.getRequestStatSamples)
|
|
26
67
|
`${base}${renderGauge(
|
|
27
68
|
~name=indexingAddressesName,
|
|
28
69
|
~help=indexingAddressesHelp,
|
|
29
|
-
~chains=
|
|
70
|
+
~chains=chainStates,
|
|
30
71
|
~value=cs => (cs->ChainState.toChainData).numAddresses,
|
|
31
|
-
)}\n`
|
|
72
|
+
)}${renderSourceRequests(~samples=sourceRequestSamples)}\n`
|
|
32
73
|
}
|
|
33
74
|
}
|