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/ChainState.res
CHANGED
|
@@ -14,16 +14,24 @@ type t = {
|
|
|
14
14
|
mutable timestampCaughtUpToHeadOrEndblock: option<Date.t>,
|
|
15
15
|
mutable committedProgressBlockNumber: int,
|
|
16
16
|
mutable numEventsProcessed: float,
|
|
17
|
-
// Running sum of in-flight queries'
|
|
17
|
+
// Running sum of in-flight queries' itemsTarget, kept here so the
|
|
18
18
|
// scheduler doesn't re-sum pending queries on every tick. Incremented when
|
|
19
19
|
// queries are dispatched, decremented as their responses land.
|
|
20
20
|
mutable pendingBudget: float,
|
|
21
|
+
// Chain-wide events/block, used to turn a chain's item budget into a target
|
|
22
|
+
// block for query sizing. Seeded from cumulative progress on construction,
|
|
23
|
+
// then smoothed with an EMA on every batch (see applyBatchProgress). None
|
|
24
|
+
// until the chain has processed at least one event.
|
|
25
|
+
mutable chainDensity: option<float>,
|
|
21
26
|
mutable reorgDetection: ReorgDetection.t,
|
|
22
27
|
mutable safeCheckpointTracking: option<SafeCheckpointTracking.t>,
|
|
23
28
|
// Holds this chain's transactions (kept in Rust) keyed by (blockNumber,
|
|
24
29
|
// transactionIndex). Fetch responses merge their page in; entries are pruned
|
|
25
30
|
// as the chain progresses and dropped above the target on rollback.
|
|
26
31
|
transactionStore: TransactionStore.t,
|
|
32
|
+
// Holds this chain's blocks (kept in Rust) keyed by block number. Same merge /
|
|
33
|
+
// prune / rollback lifecycle as the transaction store.
|
|
34
|
+
blockStore: BlockStore.t,
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
// Per-chain shape returned by the status API.
|
|
@@ -70,6 +78,8 @@ let make = (
|
|
|
70
78
|
~timestampCaughtUpToHeadOrEndblock=None,
|
|
71
79
|
~isProgressAtHead=false,
|
|
72
80
|
~transactionStore=TransactionStore.make(~ecosystem=Ecosystem.Evm, ~shouldChecksum=false),
|
|
81
|
+
~chainDensity=None,
|
|
82
|
+
~blockStore=BlockStore.make(~ecosystem=Ecosystem.Evm, ~shouldChecksum=false),
|
|
73
83
|
~logger: Pino.t,
|
|
74
84
|
): t => {
|
|
75
85
|
logger,
|
|
@@ -82,9 +92,11 @@ let make = (
|
|
|
82
92
|
committedProgressBlockNumber,
|
|
83
93
|
numEventsProcessed,
|
|
84
94
|
pendingBudget: 0.,
|
|
95
|
+
chainDensity,
|
|
85
96
|
reorgDetection,
|
|
86
97
|
safeCheckpointTracking,
|
|
87
98
|
transactionStore,
|
|
99
|
+
blockStore,
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
let makeInternal = (
|
|
@@ -95,7 +107,7 @@ let makeInternal = (
|
|
|
95
107
|
~firstEventBlock=None,
|
|
96
108
|
~progressBlockNumber,
|
|
97
109
|
~config: Config.t,
|
|
98
|
-
~
|
|
110
|
+
~registrationsByChainId: HandlerRegister.registrationsByChainId,
|
|
99
111
|
~logger,
|
|
100
112
|
~timestampCaughtUpToHeadOrEndblock,
|
|
101
113
|
~numEventsProcessed,
|
|
@@ -106,123 +118,44 @@ let makeInternal = (
|
|
|
106
118
|
~knownHeight=0,
|
|
107
119
|
~reducedPollingInterval=?,
|
|
108
120
|
): t => {
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
let eventConfigs: array<Internal.eventConfig> = []
|
|
117
|
-
|
|
118
|
-
let notRegisteredEvents = []
|
|
121
|
+
// Handler binding + `where`-derived fetch state, and onBlock registrations,
|
|
122
|
+
// are already collected by `HandlerRegister.finishRegistration`, keyed by
|
|
123
|
+
// chain - this just looks up this chain's slice.
|
|
124
|
+
let {onEventRegistrations, onBlockRegistrations} =
|
|
125
|
+
registrationsByChainId
|
|
126
|
+
->Utils.Dict.dangerouslyGetNonOption(chainConfig.id->Int.toString)
|
|
127
|
+
->Option.getOr({onEventRegistrations: [], onBlockRegistrations: []})
|
|
119
128
|
|
|
120
129
|
chainConfig.contracts->Array.forEach(contract => {
|
|
121
|
-
let contractName = contract.name
|
|
122
|
-
|
|
123
|
-
contract.events->Array.forEach(eventConfig => {
|
|
124
|
-
let {isWildcard} = eventConfig
|
|
125
|
-
let hasContractRegister = eventConfig.contractRegister->Option.isSome
|
|
126
|
-
|
|
127
|
-
// Should validate the events
|
|
128
|
-
eventRouter->EventRouter.addOrThrow(
|
|
129
|
-
eventConfig.id,
|
|
130
|
-
(),
|
|
131
|
-
~contractName,
|
|
132
|
-
~chain=ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id),
|
|
133
|
-
~eventName=eventConfig.name,
|
|
134
|
-
~isWildcard,
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
// Filter out non-preRegistration events on preRegistration phase
|
|
138
|
-
// so we don't care about it in fetch state and workers anymore
|
|
139
|
-
let shouldBeIncluded = if config.enableRawEvents {
|
|
140
|
-
true
|
|
141
|
-
} else {
|
|
142
|
-
let isRegistered = hasContractRegister || eventConfig.handler->Option.isSome
|
|
143
|
-
if !isRegistered {
|
|
144
|
-
notRegisteredEvents->Array.push(eventConfig)
|
|
145
|
-
}
|
|
146
|
-
isRegistered
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check if event has Static([]) filters (from a dynamic where
|
|
150
|
-
// callback returning `false` / SkipAll for this chain).
|
|
151
|
-
// If so, skip it entirely - it should never be fetched
|
|
152
|
-
let shouldSkip = try {
|
|
153
|
-
let getEventFiltersOrThrow = (
|
|
154
|
-
eventConfig->(Utils.magic: Internal.eventConfig => Internal.evmEventConfig)
|
|
155
|
-
).getEventFiltersOrThrow
|
|
156
|
-
|
|
157
|
-
// Check for non-evm chains
|
|
158
|
-
if (
|
|
159
|
-
getEventFiltersOrThrow->(Utils.magic: (ChainMap.Chain.t => Internal.eventFilters) => bool)
|
|
160
|
-
) {
|
|
161
|
-
switch getEventFiltersOrThrow(ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id)) {
|
|
162
|
-
| Static([]) => true
|
|
163
|
-
| _ => false
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
false
|
|
167
|
-
}
|
|
168
|
-
} catch {
|
|
169
|
-
// Can throw when filter is invalid
|
|
170
|
-
// Don't skip an event in this case. Let it throw in a better place - source code
|
|
171
|
-
| _ => false
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if shouldBeIncluded && !shouldSkip {
|
|
175
|
-
eventConfigs->Array.push(eventConfig)
|
|
176
|
-
}
|
|
177
|
-
})
|
|
178
|
-
|
|
179
130
|
switch contract.startBlock {
|
|
180
131
|
| Some(startBlock) if startBlock < chainConfig.startBlock =>
|
|
181
132
|
JsError.throwWithMessage(
|
|
182
|
-
`The start block for contract "${
|
|
133
|
+
`The start block for contract "${contract.name}" is less than the chain start block. This is not supported yet.`,
|
|
183
134
|
)
|
|
184
135
|
| _ => ()
|
|
185
136
|
}
|
|
186
137
|
})
|
|
187
138
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
let onBlockConfigs =
|
|
201
|
-
registrations.onBlockByChainId->Utils.Dict.dangerouslyGetNonOption(chainConfig.id->Int.toString)
|
|
202
|
-
switch onBlockConfigs {
|
|
203
|
-
| Some(onBlockConfigs) =>
|
|
204
|
-
// TODO: Move it to the HandlerRegister module
|
|
205
|
-
// so the error is thrown with better stack trace
|
|
206
|
-
onBlockConfigs->Array.forEach(onBlockConfig => {
|
|
207
|
-
if onBlockConfig.startBlock->Option.getOr(startBlock) < startBlock {
|
|
139
|
+
// TODO: Move it to the HandlerRegister module
|
|
140
|
+
// so the error is thrown with better stack trace
|
|
141
|
+
onBlockRegistrations->Array.forEach(onBlockRegistration => {
|
|
142
|
+
if onBlockRegistration.startBlock->Option.getOr(startBlock) < startBlock {
|
|
143
|
+
JsError.throwWithMessage(
|
|
144
|
+
`The start block for onBlock handler "${onBlockRegistration.name}" is less than the chain start block (${startBlock->Int.toString}). This is not supported yet.`,
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
switch endBlock {
|
|
148
|
+
| Some(chainEndBlock) =>
|
|
149
|
+
if onBlockRegistration.endBlock->Option.getOr(chainEndBlock) > chainEndBlock {
|
|
208
150
|
JsError.throwWithMessage(
|
|
209
|
-
`The
|
|
151
|
+
`The end block for onBlock handler "${onBlockRegistration.name}" is greater than the chain end block (${chainEndBlock->Int.toString}). This is not supported yet.`,
|
|
210
152
|
)
|
|
211
153
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
JsError.throwWithMessage(
|
|
216
|
-
`The end block for onBlock handler "${onBlockConfig.name}" is greater than the chain end block (${chainEndBlock->Int.toString}). This is not supported yet.`,
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
| None => ()
|
|
220
|
-
}
|
|
221
|
-
})
|
|
222
|
-
| None => ()
|
|
223
|
-
}
|
|
154
|
+
| None => ()
|
|
155
|
+
}
|
|
156
|
+
})
|
|
224
157
|
|
|
225
|
-
let contractConfigs = IndexingAddresses.makeContractConfigs(~
|
|
158
|
+
let contractConfigs = IndexingAddresses.makeContractConfigs(~onEventRegistrations)
|
|
226
159
|
let indexingAddressIndex = IndexingAddresses.make(~contractConfigs, ~addresses=indexingAddresses)
|
|
227
160
|
|
|
228
161
|
let fetchState = FetchState.make(
|
|
@@ -232,7 +165,7 @@ let makeInternal = (
|
|
|
232
165
|
~progressBlockNumber,
|
|
233
166
|
~startBlock,
|
|
234
167
|
~endBlock,
|
|
235
|
-
~
|
|
168
|
+
~onEventRegistrations,
|
|
236
169
|
~maxOnBlockBufferSize=2 * config.batchSize,
|
|
237
170
|
~knownHeight,
|
|
238
171
|
~chainId=chainConfig.id,
|
|
@@ -241,7 +174,7 @@ let makeInternal = (
|
|
|
241
174
|
!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth,
|
|
242
175
|
chainConfig.blockLag,
|
|
243
176
|
),
|
|
244
|
-
~
|
|
177
|
+
~onBlockRegistrations,
|
|
245
178
|
~firstEventBlock,
|
|
246
179
|
)
|
|
247
180
|
|
|
@@ -258,16 +191,6 @@ let makeInternal = (
|
|
|
258
191
|
let lowercaseAddresses = config.lowercaseAddresses
|
|
259
192
|
let sources = switch chainConfig.sourceConfig {
|
|
260
193
|
| Config.EvmSourceConfig({hypersync, rpcs}) =>
|
|
261
|
-
// Build Internal.evmContractConfig from contracts for EvmChain.makeSources
|
|
262
|
-
let evmContracts: array<Internal.evmContractConfig> = chainConfig.contracts->Array.map((
|
|
263
|
-
contract
|
|
264
|
-
): Internal.evmContractConfig => {
|
|
265
|
-
name: contract.name,
|
|
266
|
-
abi: contract.abi,
|
|
267
|
-
events: contract.events->(
|
|
268
|
-
Utils.magic: array<Internal.eventConfig> => array<Internal.evmEventConfig>
|
|
269
|
-
),
|
|
270
|
-
})
|
|
271
194
|
let evmRpcs: array<EvmChain.rpc> = rpcs->Array.map((rpc): EvmChain.rpc => {
|
|
272
195
|
let syncConfig = rpc.syncConfig
|
|
273
196
|
let ws = rpc.ws
|
|
@@ -282,7 +205,9 @@ let makeInternal = (
|
|
|
282
205
|
})
|
|
283
206
|
EvmChain.makeSources(
|
|
284
207
|
~chain,
|
|
285
|
-
~
|
|
208
|
+
~onEventRegistrations=onEventRegistrations->(
|
|
209
|
+
Utils.magic: array<Internal.onEventRegistration> => array<Internal.evmOnEventRegistration>
|
|
210
|
+
),
|
|
286
211
|
~hyperSync=hypersync,
|
|
287
212
|
~rpcs=evmRpcs,
|
|
288
213
|
~lowercaseAddresses,
|
|
@@ -300,17 +225,13 @@ let makeInternal = (
|
|
|
300
225
|
| (Some(hypersyncUrl), _) =>
|
|
301
226
|
// HyperSync drives instruction sync. A configured RPC is ignored for now
|
|
302
227
|
// (RPC fallback isn't wired up yet).
|
|
303
|
-
let svmEventConfigs =
|
|
304
|
-
chainConfig.contracts
|
|
305
|
-
->Array.flatMap(contract => contract.events)
|
|
306
|
-
->(Utils.magic: array<Internal.eventConfig> => array<Internal.svmInstructionEventConfig>)
|
|
307
228
|
let apiToken = Env.envioApiToken
|
|
308
229
|
[
|
|
309
230
|
SvmHyperSyncSource.make({
|
|
310
231
|
chain,
|
|
311
232
|
endpointUrl: hypersyncUrl,
|
|
312
233
|
apiToken,
|
|
313
|
-
|
|
234
|
+
onEventRegistrations,
|
|
314
235
|
clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
|
|
315
236
|
}),
|
|
316
237
|
]
|
|
@@ -319,6 +240,14 @@ let makeInternal = (
|
|
|
319
240
|
| Config.CustomSources(sources) => sources
|
|
320
241
|
}
|
|
321
242
|
|
|
243
|
+
// Seed chain density from whatever progress this chain already has (from a
|
|
244
|
+
// resumed DB state, or 0 on a fresh chain) — refined per-batch afterwards.
|
|
245
|
+
let chainDensity = switch fetchState.firstEventBlock {
|
|
246
|
+
| Some(firstEventBlock) if progressBlockNumber > firstEventBlock && numEventsProcessed > 0. =>
|
|
247
|
+
Some(numEventsProcessed /. (progressBlockNumber - firstEventBlock)->Int.toFloat)
|
|
248
|
+
| _ => None
|
|
249
|
+
}
|
|
250
|
+
|
|
322
251
|
make(
|
|
323
252
|
~chainConfig,
|
|
324
253
|
~fetchState,
|
|
@@ -341,17 +270,27 @@ let makeInternal = (
|
|
|
341
270
|
~ecosystem=config.ecosystem.name,
|
|
342
271
|
~shouldChecksum=!lowercaseAddresses,
|
|
343
272
|
),
|
|
273
|
+
~chainDensity,
|
|
274
|
+
~blockStore=BlockStore.make(
|
|
275
|
+
~ecosystem=config.ecosystem.name,
|
|
276
|
+
~shouldChecksum=!lowercaseAddresses,
|
|
277
|
+
),
|
|
344
278
|
~logger,
|
|
345
279
|
)
|
|
346
280
|
}
|
|
347
281
|
|
|
348
|
-
let makeFromConfig = (
|
|
282
|
+
let makeFromConfig = (
|
|
283
|
+
chainConfig: Config.chain,
|
|
284
|
+
~config,
|
|
285
|
+
~registrationsByChainId,
|
|
286
|
+
~knownHeight,
|
|
287
|
+
) => {
|
|
349
288
|
let logger = Logging.createChild(~params={"chainId": chainConfig.id})
|
|
350
289
|
|
|
351
290
|
makeInternal(
|
|
352
291
|
~chainConfig,
|
|
353
292
|
~config,
|
|
354
|
-
~
|
|
293
|
+
~registrationsByChainId,
|
|
355
294
|
~startBlock=chainConfig.startBlock,
|
|
356
295
|
~endBlock=chainConfig.endBlock,
|
|
357
296
|
~reorgCheckpoints=[],
|
|
@@ -377,7 +316,7 @@ let makeFromDbState = (
|
|
|
377
316
|
~isInReorgThreshold,
|
|
378
317
|
~isRealtime,
|
|
379
318
|
~config,
|
|
380
|
-
~
|
|
319
|
+
~registrationsByChainId,
|
|
381
320
|
~reducedPollingInterval=?,
|
|
382
321
|
) => {
|
|
383
322
|
let chainId = chainConfig.id
|
|
@@ -397,7 +336,7 @@ let makeFromDbState = (
|
|
|
397
336
|
~startBlock=resumedChainState.startBlock,
|
|
398
337
|
~endBlock=resumedChainState.endBlock,
|
|
399
338
|
~config,
|
|
400
|
-
~
|
|
339
|
+
~registrationsByChainId,
|
|
401
340
|
~reorgCheckpoints,
|
|
402
341
|
~maxReorgDepth=resumedChainState.maxReorgDepth,
|
|
403
342
|
~firstEventBlock=resumedChainState.firstEventBlockNumber,
|
|
@@ -435,8 +374,7 @@ let contractAddresses = (cs: t, ~contractName) =>
|
|
|
435
374
|
let bufferSize = (cs: t) => cs.fetchState->FetchState.bufferSize
|
|
436
375
|
let bufferReadyCount = (cs: t) => cs.fetchState->FetchState.bufferReadyCount
|
|
437
376
|
let getProgressPercentage = (cs: t) => cs.fetchState->FetchState.getProgressPercentage
|
|
438
|
-
let
|
|
439
|
-
cs.fetchState->FetchState.getProgressPercentageAt(~blockNumber)
|
|
377
|
+
let chainDensity = (cs: t) => cs.chainDensity
|
|
440
378
|
let hasReadyItem = (cs: t) =>
|
|
441
379
|
cs.fetchState->FetchState.isActivelyIndexing && cs.fetchState->FetchState.hasReadyItem
|
|
442
380
|
let isReadyToEnterReorgThreshold = (cs: t) => cs.fetchState->FetchState.isReadyToEnterReorgThreshold
|
|
@@ -447,7 +385,7 @@ let isReadyToEnterReorgThreshold = (cs: t) => cs.fetchState->FetchState.isReadyT
|
|
|
447
385
|
let startFetchingQueries = (cs: t, ~queries: array<FetchState.query>) => {
|
|
448
386
|
cs.fetchState->FetchState.startFetchingQueries(~queries)
|
|
449
387
|
cs.pendingBudget =
|
|
450
|
-
cs.pendingBudget +. queries->Array.reduce(0., (acc, query) => acc +. query.
|
|
388
|
+
cs.pendingBudget +. queries->Array.reduce(0., (acc, query) => acc +. query.itemsTarget)
|
|
451
389
|
}
|
|
452
390
|
|
|
453
391
|
// Drop every in-flight query and release their reservations together, keeping
|
|
@@ -457,10 +395,25 @@ let resetPendingQueries = (cs: t) => {
|
|
|
457
395
|
cs.pendingBudget = 0.
|
|
458
396
|
}
|
|
459
397
|
|
|
460
|
-
//
|
|
461
|
-
// (
|
|
462
|
-
//
|
|
463
|
-
|
|
398
|
+
// Turn this chain's share of the indexer-wide buffer budget into a soft
|
|
399
|
+
// target block (via chain density, or knownHeight when density isn't known
|
|
400
|
+
// yet), then propose queries sized against it. Called by CrossChainState's
|
|
401
|
+
// waterfall, furthest-behind chain first, with chainTargetItems set to
|
|
402
|
+
// whatever budget remains at that point in the waterfall.
|
|
403
|
+
let getNextQuery = (cs: t, ~chainTargetItems: float) => {
|
|
404
|
+
let fetchState = cs.fetchState
|
|
405
|
+
let knownHeight = fetchState.knownHeight
|
|
406
|
+
let bufferBlockNumber = fetchState->FetchState.bufferBlockNumber
|
|
407
|
+
let chainTargetBlock = switch cs.chainDensity {
|
|
408
|
+
| Some(density) if density > 0. =>
|
|
409
|
+
Pervasives.min(
|
|
410
|
+
knownHeight,
|
|
411
|
+
bufferBlockNumber + Math.ceil(chainTargetItems /. density)->Float.toInt,
|
|
412
|
+
)
|
|
413
|
+
| _ => knownHeight
|
|
414
|
+
}
|
|
415
|
+
fetchState->FetchState.getNextQuery(~chainTargetBlock, ~chainTargetItems)
|
|
416
|
+
}
|
|
464
417
|
|
|
465
418
|
// Run a fetch tick for this chain against its sources, feeding the owned fetch
|
|
466
419
|
// frontier to the source manager.
|
|
@@ -511,21 +464,189 @@ let isAtHeadWithoutEndBlock = (cs: t) =>
|
|
|
511
464
|
// --- State transitions. The chain state is mutated only through these; each
|
|
512
465
|
// owns a cohesive update so callers don't juggle individual fields. ---
|
|
513
466
|
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
467
|
+
// Per-store grouping data a single pass over `items` produces (see
|
|
468
|
+
// `groupBatchItems`), fed into `TransactionStore`/`BlockStore`'s `materialize`.
|
|
469
|
+
type transactionGroups = {
|
|
470
|
+
txBlockNumbers: array<int>,
|
|
471
|
+
transactionIndices: array<int>,
|
|
472
|
+
transactionMasks: array<float>,
|
|
473
|
+
payloadGroups: array<array<Internal.eventPayload>>,
|
|
474
|
+
anyTransactionFieldSelected: bool,
|
|
475
|
+
}
|
|
476
|
+
type blockGroups = {
|
|
477
|
+
blockBlockNumbers: array<int>,
|
|
478
|
+
blockMasks: array<float>,
|
|
479
|
+
blockItemGroups: array<array<Internal.eventItem>>,
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Walk `items` once to build both the transaction-store and block-store
|
|
483
|
+
// grouping data, instead of `TransactionStore`/`BlockStore` each re-walking the
|
|
484
|
+
// same batch independently. Items already arrive in (blockNumber, logIndex)
|
|
485
|
+
// order, so a (blockNumber, transactionIndex) run and a blockNumber-only run
|
|
486
|
+
// each stay adjacent; every item is checked against both, in the same pass.
|
|
487
|
+
// `includeBlocks` skips the block side entirely for Fuel, which carries the
|
|
488
|
+
// block inline and has no store.
|
|
489
|
+
let groupBatchItems = (items: array<Internal.item>, ~includeBlocks: bool): (
|
|
490
|
+
transactionGroups,
|
|
491
|
+
blockGroups,
|
|
492
|
+
) => {
|
|
493
|
+
let txBlockNumbers = []
|
|
494
|
+
let transactionIndices = []
|
|
495
|
+
let transactionMasks = []
|
|
496
|
+
let payloadGroups = []
|
|
497
|
+
let anyTransactionFieldSelected = ref(false)
|
|
498
|
+
|
|
499
|
+
let blockBlockNumbers = []
|
|
500
|
+
let blockMasks = []
|
|
501
|
+
let blockItemGroups = []
|
|
502
|
+
|
|
503
|
+
items->Array.forEach(item =>
|
|
504
|
+
switch item {
|
|
505
|
+
| Internal.Event(_) =>
|
|
506
|
+
let eventItem = item->Internal.castUnsafeEventItem
|
|
507
|
+
let {blockNumber} = eventItem
|
|
508
|
+
|
|
509
|
+
switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
|
|
510
|
+
| Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
|
|
511
|
+
| None =>
|
|
512
|
+
let {transactionIndex} = eventItem
|
|
513
|
+
let mask = eventItem.onEventRegistration.eventConfig.transactionFieldMask
|
|
514
|
+
if mask != 0. {
|
|
515
|
+
anyTransactionFieldSelected := true
|
|
516
|
+
}
|
|
517
|
+
let last = payloadGroups->Array.length - 1
|
|
518
|
+
if (
|
|
519
|
+
last >= 0 &&
|
|
520
|
+
txBlockNumbers->Array.getUnsafe(last) == blockNumber &&
|
|
521
|
+
transactionIndices->Array.getUnsafe(last) == transactionIndex
|
|
522
|
+
) {
|
|
523
|
+
payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
|
|
524
|
+
transactionMasks->Array.setUnsafe(
|
|
525
|
+
last,
|
|
526
|
+
FieldMask.orMask(transactionMasks->Array.getUnsafe(last), mask),
|
|
527
|
+
)
|
|
528
|
+
} else {
|
|
529
|
+
txBlockNumbers->Array.push(blockNumber)
|
|
530
|
+
transactionIndices->Array.push(transactionIndex)
|
|
531
|
+
transactionMasks->Array.push(mask)
|
|
532
|
+
payloadGroups->Array.push([eventItem.payload])
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if includeBlocks {
|
|
537
|
+
switch eventItem.payload->Internal.getPayloadBlock->Nullable.toOption {
|
|
538
|
+
| Some(_) => () // RPC/simulate/Fuel carry the block inline.
|
|
539
|
+
| None =>
|
|
540
|
+
let mask = eventItem.onEventRegistration.eventConfig.blockFieldMask
|
|
541
|
+
let last = blockItemGroups->Array.length - 1
|
|
542
|
+
if last >= 0 && blockBlockNumbers->Array.getUnsafe(last) == blockNumber {
|
|
543
|
+
blockItemGroups->Array.getUnsafe(last)->Array.push(eventItem)
|
|
544
|
+
blockMasks->Array.setUnsafe(
|
|
545
|
+
last,
|
|
546
|
+
FieldMask.orMask(blockMasks->Array.getUnsafe(last), mask),
|
|
547
|
+
)
|
|
548
|
+
} else {
|
|
549
|
+
blockBlockNumbers->Array.push(blockNumber)
|
|
550
|
+
blockMasks->Array.push(mask)
|
|
551
|
+
blockItemGroups->Array.push([eventItem])
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
| Internal.Block(_) => ()
|
|
556
|
+
}
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
(
|
|
560
|
+
{
|
|
561
|
+
txBlockNumbers,
|
|
562
|
+
transactionIndices,
|
|
563
|
+
transactionMasks,
|
|
564
|
+
payloadGroups,
|
|
565
|
+
anyTransactionFieldSelected: anyTransactionFieldSelected.contents,
|
|
566
|
+
},
|
|
567
|
+
{blockBlockNumbers, blockMasks, blockItemGroups},
|
|
568
|
+
)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Materialise a `TransactionStore` against precomputed groups (see
|
|
572
|
+
// `groupBatchItems`). Store-backed items always get a transaction object — the
|
|
573
|
+
// selected fields, or `{}` when nothing was selected — so `event.transaction`
|
|
574
|
+
// is never `undefined` (matching the inline sources).
|
|
575
|
+
let applyTransactionGroups = async (store: TransactionStore.t, g: transactionGroups) => {
|
|
576
|
+
if g.payloadGroups->Utils.Array.notEmpty {
|
|
577
|
+
if g.anyTransactionFieldSelected {
|
|
578
|
+
let txs = await store->TransactionStore.materialize(
|
|
579
|
+
~blockNumbers=g.txBlockNumbers,
|
|
580
|
+
~transactionIndices=g.transactionIndices,
|
|
581
|
+
~masks=g.transactionMasks,
|
|
582
|
+
)
|
|
583
|
+
g.payloadGroups->Array.forEachWithIndex((payloads, i) => {
|
|
584
|
+
let tx = txs->Array.getUnsafe(i)
|
|
585
|
+
payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
|
|
586
|
+
})
|
|
587
|
+
} else {
|
|
588
|
+
g.payloadGroups->Array.forEach(payloads =>
|
|
589
|
+
payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(%raw(`{}`)))
|
|
590
|
+
)
|
|
591
|
+
}
|
|
528
592
|
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Materialise a `BlockStore` against precomputed groups (see `groupBatchItems`).
|
|
596
|
+
let applyBlockGroups = async (store: BlockStore.t, g: blockGroups) => {
|
|
597
|
+
if g.blockItemGroups->Utils.Array.notEmpty {
|
|
598
|
+
let blocks = await store->BlockStore.materialize(
|
|
599
|
+
~blockNumbers=g.blockBlockNumbers,
|
|
600
|
+
~masks=g.blockMasks,
|
|
601
|
+
)
|
|
602
|
+
g.blockItemGroups->Array.forEachWithIndex((group, i) => {
|
|
603
|
+
let block = blocks->Array.getUnsafe(i)
|
|
604
|
+
group->Array.forEach(ei => ei.payload->Internal.setPayloadBlock(block))
|
|
605
|
+
})
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
let includeBlocksForEcosystem = (ecosystem: Ecosystem.name) =>
|
|
610
|
+
switch ecosystem {
|
|
611
|
+
| Evm | Svm => true
|
|
612
|
+
| Fuel => false
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Materialise the chain stores' selected transaction and block fields onto a
|
|
616
|
+
// batch's items at batch prep (the persistent-store path). A single pass over
|
|
617
|
+
// `items` (`groupBatchItems`) builds both stores' selection masks before the
|
|
618
|
+
// two independent materialize calls run concurrently.
|
|
619
|
+
let materializeBatchItems = async (cs: t, ~items: array<Internal.item>, ~ecosystem) => {
|
|
620
|
+
let (txGroups, blockGroups) =
|
|
621
|
+
items->groupBatchItems(~includeBlocks=ecosystem->includeBlocksForEcosystem)
|
|
622
|
+
let _ = await Promise.all2((
|
|
623
|
+
cs.transactionStore->applyTransactionGroups(txGroups),
|
|
624
|
+
cs.blockStore->applyBlockGroups(blockGroups),
|
|
625
|
+
))
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Materialise a fetch-response page's transactions and blocks onto its items
|
|
629
|
+
// before contract-register handlers read them. `None` pages (RPC/Fuel/Simulate
|
|
630
|
+
// keep them inline) are a no-op.
|
|
631
|
+
let materializePageItems = async (
|
|
632
|
+
~items: array<Internal.item>,
|
|
633
|
+
~transactionStore: option<TransactionStore.t>,
|
|
634
|
+
~blockStore: option<BlockStore.t>,
|
|
635
|
+
~ecosystem,
|
|
636
|
+
) => {
|
|
637
|
+
let (txGroups, blockGroups) =
|
|
638
|
+
items->groupBatchItems(~includeBlocks=ecosystem->includeBlocksForEcosystem)
|
|
639
|
+
let _ = await Promise.all2((
|
|
640
|
+
switch transactionStore {
|
|
641
|
+
| Some(store) => store->applyTransactionGroups(txGroups)
|
|
642
|
+
| None => Promise.resolve()
|
|
643
|
+
},
|
|
644
|
+
switch blockStore {
|
|
645
|
+
| Some(store) => store->applyBlockGroups(blockGroups)
|
|
646
|
+
| None => Promise.resolve()
|
|
647
|
+
},
|
|
648
|
+
))
|
|
649
|
+
}
|
|
529
650
|
|
|
530
651
|
let handleQueryResult = (
|
|
531
652
|
cs: t,
|
|
@@ -534,14 +655,19 @@ let handleQueryResult = (
|
|
|
534
655
|
~newItemsWithDcs,
|
|
535
656
|
~latestFetchedBlock,
|
|
536
657
|
~knownHeight,
|
|
537
|
-
~transactionStore as
|
|
658
|
+
~transactionStore as txPage: option<TransactionStore.t>,
|
|
659
|
+
~blockStore as blockPage: option<BlockStore.t>,
|
|
538
660
|
) => {
|
|
539
|
-
// Merge this response's
|
|
540
|
-
// its items to the buffer. Inline
|
|
541
|
-
switch
|
|
661
|
+
// Merge this response's pages into the chain stores in lockstep with appending
|
|
662
|
+
// its items to the buffer. Inline sources contribute no page.
|
|
663
|
+
switch txPage {
|
|
542
664
|
| Some(page) => cs.transactionStore->TransactionStore.merge(page)
|
|
543
665
|
| None => ()
|
|
544
666
|
}
|
|
667
|
+
switch blockPage {
|
|
668
|
+
| Some(page) => cs.blockStore->BlockStore.merge(page)
|
|
669
|
+
| None => ()
|
|
670
|
+
}
|
|
545
671
|
|
|
546
672
|
let fs = switch newItemsWithDcs {
|
|
547
673
|
| [] => cs.fetchState
|
|
@@ -563,7 +689,7 @@ let handleQueryResult = (
|
|
|
563
689
|
->FetchState.updateKnownHeight(~knownHeight)
|
|
564
690
|
|
|
565
691
|
// The query is no longer in flight, so release its reservation.
|
|
566
|
-
cs.pendingBudget = Pervasives.max(0., cs.pendingBudget -. query.
|
|
692
|
+
cs.pendingBudget = Pervasives.max(0., cs.pendingBudget -. query.itemsTarget)
|
|
567
693
|
}
|
|
568
694
|
|
|
569
695
|
// Run reorg detection against a fetch response and commit the updated guard.
|
|
@@ -670,8 +796,9 @@ let advanceAfterBatch = (cs: t, ~batch: Batch.t, ~enteringReorgThreshold) =>
|
|
|
670
796
|
// Commit a processed batch's progress for this chain (progress block, events
|
|
671
797
|
// processed, head/safe-checkpoint tracking, first event block). Emits the
|
|
672
798
|
// per-chain progress metrics. Readiness is decided by CrossChainState once every
|
|
673
|
-
// chain is caught up (see markReady).
|
|
674
|
-
|
|
799
|
+
// chain is caught up (see markReady). `blockTimestampName` is the ecosystem's
|
|
800
|
+
// block-timestamp field, read off the payload block for the latency metric.
|
|
801
|
+
let applyBatchProgress = (cs: t, ~batch: Batch.t, ~blockTimestampName: string) => {
|
|
675
802
|
let chainId = cs.chainConfig.id
|
|
676
803
|
|
|
677
804
|
switch batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(chainId) {
|
|
@@ -689,15 +816,27 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t) => {
|
|
|
689
816
|
)
|
|
690
817
|
}
|
|
691
818
|
|
|
692
|
-
// Calculate and set latency metrics
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
819
|
+
// Calculate and set latency metrics. The payload block is materialised or
|
|
820
|
+
// inline by processing time; its timestamp may still be absent (e.g. an
|
|
821
|
+
// SVM slot with no block row) — the metric is skipped then.
|
|
822
|
+
switch batch
|
|
823
|
+
->Batch.findLastEventItem(~chainId)
|
|
824
|
+
->Option.flatMap(eventItem =>
|
|
825
|
+
eventItem.payload
|
|
826
|
+
->Internal.getPayloadBlock
|
|
827
|
+
->Nullable.toOption
|
|
828
|
+
)
|
|
829
|
+
->Option.flatMap(block =>
|
|
830
|
+
block
|
|
831
|
+
->(Utils.magic: Internal.eventBlock => dict<unknown>)
|
|
832
|
+
->Utils.Dict.dangerouslyGetNonOption(blockTimestampName)
|
|
833
|
+
) {
|
|
834
|
+
| Some(blockTimestamp) =>
|
|
835
|
+
let blockTimestampMs = blockTimestamp->(Utils.magic: unknown => int) * 1000
|
|
836
|
+
Prometheus.ProgressLatency.set(
|
|
837
|
+
~latencyMs=Date.now()->Float.toInt - blockTimestampMs,
|
|
838
|
+
~chainId,
|
|
839
|
+
)
|
|
701
840
|
| None => ()
|
|
702
841
|
}
|
|
703
842
|
|
|
@@ -712,10 +851,32 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t) => {
|
|
|
712
851
|
}
|
|
713
852
|
}
|
|
714
853
|
|
|
854
|
+
// Chain-wide density update: seed with the batch's own events/block on
|
|
855
|
+
// the first update, then smooth incrementally so a run of a few
|
|
856
|
+
// sparse/dense blocks doesn't swing the target block estimate.
|
|
857
|
+
let deltaBlocks = chainAfterBatch.progressBlockNumber - cs.committedProgressBlockNumber
|
|
858
|
+
if deltaBlocks > 0 {
|
|
859
|
+
let deltaEvents = chainAfterBatch.totalEventsProcessed -. cs.numEventsProcessed
|
|
860
|
+
// Don't seed a density before the first event is seen — a progress-only
|
|
861
|
+
// batch would otherwise set it to 0, matching the resume-seed guard.
|
|
862
|
+
switch (cs.chainDensity, deltaEvents > 0.) {
|
|
863
|
+
| (None, false) => ()
|
|
864
|
+
| _ =>
|
|
865
|
+
let batchDensity = deltaEvents /. deltaBlocks->Int.toFloat
|
|
866
|
+
cs.chainDensity = Some(
|
|
867
|
+
switch cs.chainDensity {
|
|
868
|
+
| None => batchDensity
|
|
869
|
+
| Some(oldDensity) => (oldDensity +. batchDensity) /. 2.
|
|
870
|
+
},
|
|
871
|
+
)
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
715
875
|
cs.committedProgressBlockNumber = chainAfterBatch.progressBlockNumber
|
|
716
876
|
cs.numEventsProcessed = chainAfterBatch.totalEventsProcessed
|
|
717
|
-
// Processed blocks' transactions are no longer needed.
|
|
877
|
+
// Processed blocks' transactions and blocks are no longer needed.
|
|
718
878
|
cs.transactionStore->TransactionStore.prune(chainAfterBatch.progressBlockNumber)
|
|
879
|
+
cs.blockStore->BlockStore.prune(chainAfterBatch.progressBlockNumber)
|
|
719
880
|
cs.isProgressAtHead = cs.isProgressAtHead || chainAfterBatch.isProgressAtHeadWhenBatchCreated
|
|
720
881
|
switch cs.safeCheckpointTracking {
|
|
721
882
|
| Some(safeCheckpointTracking) =>
|
|
@@ -794,6 +955,7 @@ let rollback = (
|
|
|
794
955
|
~targetBlockNumber=newProgressBlockNumber,
|
|
795
956
|
)
|
|
796
957
|
cs.transactionStore->TransactionStore.rollback(newProgressBlockNumber)
|
|
958
|
+
cs.blockStore->BlockStore.rollback(newProgressBlockNumber)
|
|
797
959
|
cs.committedProgressBlockNumber = newProgressBlockNumber
|
|
798
960
|
cs.numEventsProcessed = newTotalEventsProcessed
|
|
799
961
|
| None =>
|
|
@@ -808,6 +970,7 @@ let rollback = (
|
|
|
808
970
|
~targetBlockNumber=rollbackTargetBlockNumber,
|
|
809
971
|
)
|
|
810
972
|
cs.transactionStore->TransactionStore.rollback(rollbackTargetBlockNumber)
|
|
973
|
+
cs.blockStore->BlockStore.rollback(rollbackTargetBlockNumber)
|
|
811
974
|
cs.committedProgressBlockNumber = Pervasives.min(
|
|
812
975
|
cs.committedProgressBlockNumber,
|
|
813
976
|
rollbackTargetBlockNumber,
|