envio 3.3.0-alpha.7 → 3.3.0-alpha.9
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 +401 -171
- package/src/ChainState.res.mjs +270 -104
- package/src/ChainState.resi +19 -7
- package/src/Config.res +11 -27
- package/src/Config.res.mjs +8 -7
- package/src/Core.res +7 -0
- package/src/CrossChainState.res +82 -82
- package/src/CrossChainState.res.mjs +52 -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/EnvioGlobal.res +53 -0
- package/src/EnvioGlobal.res.mjs +31 -0
- package/src/EventConfigBuilder.res +117 -69
- package/src/EventConfigBuilder.res.mjs +71 -34
- package/src/EventProcessing.res +19 -15
- package/src/EventProcessing.res.mjs +13 -12
- package/src/FetchState.res +410 -185
- package/src/FetchState.res.mjs +268 -253
- package/src/HandlerLoader.res +8 -113
- package/src/HandlerLoader.res.mjs +2 -88
- package/src/HandlerRegister.res +518 -144
- package/src/HandlerRegister.res.mjs +285 -133
- package/src/HandlerRegister.resi +24 -8
- 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 +104 -39
- package/src/Internal.res.mjs +11 -1
- package/src/LogSelection.res +102 -165
- package/src/LogSelection.res.mjs +101 -116
- package/src/Main.res +49 -164
- package/src/Main.res.mjs +39 -104
- 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/RollbackCommit.res +4 -1
- package/src/RollbackCommit.res.mjs +3 -2
- 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 +58 -5
- package/src/sources/Evm.res.mjs +44 -5
- 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 +8 -3
- package/src/sources/Fuel.res.mjs +5 -4
- 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 +58 -90
- package/src/sources/HyperSyncSource.res.mjs +57 -66
- package/src/sources/RpcSource.res +118 -120
- package/src/sources/RpcSource.res.mjs +92 -74
- 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 +60 -10
- package/src/sources/SourceManager.res.mjs +54 -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,25 @@ 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
|
-
logger->Logging.childInfo(
|
|
190
|
-
`The event${if notRegisteredEvents->Array.length > 1 {
|
|
191
|
-
"s"
|
|
192
|
-
} else {
|
|
193
|
-
""
|
|
194
|
-
}} ${notRegisteredEvents
|
|
195
|
-
->Array.map(eventConfig => `${eventConfig.contractName}.${eventConfig.name}`)
|
|
196
|
-
->Array.joinUnsafe(", ")} don't have an event handler and skipped for indexing.`,
|
|
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 {
|
|
208
|
-
JsError.throwWithMessage(
|
|
209
|
-
`The start block for onBlock handler "${onBlockConfig.name}" is less than the chain start block (${startBlock->Int.toString}). This is not supported yet.`,
|
|
210
|
-
)
|
|
211
|
-
}
|
|
212
|
-
switch endBlock {
|
|
213
|
-
| Some(chainEndBlock) =>
|
|
214
|
-
if onBlockConfig.endBlock->Option.getOr(chainEndBlock) > chainEndBlock {
|
|
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
|
-
}
|
|
224
|
-
|
|
225
|
-
let contractConfigs = IndexingAddresses.makeContractConfigs(~eventConfigs)
|
|
139
|
+
let contractConfigs = IndexingAddresses.makeContractConfigs(~onEventRegistrations)
|
|
226
140
|
let indexingAddressIndex = IndexingAddresses.make(~contractConfigs, ~addresses=indexingAddresses)
|
|
227
141
|
|
|
228
142
|
let fetchState = FetchState.make(
|
|
@@ -232,7 +146,7 @@ let makeInternal = (
|
|
|
232
146
|
~progressBlockNumber,
|
|
233
147
|
~startBlock,
|
|
234
148
|
~endBlock,
|
|
235
|
-
~
|
|
149
|
+
~onEventRegistrations,
|
|
236
150
|
~maxOnBlockBufferSize=2 * config.batchSize,
|
|
237
151
|
~knownHeight,
|
|
238
152
|
~chainId=chainConfig.id,
|
|
@@ -241,7 +155,7 @@ let makeInternal = (
|
|
|
241
155
|
!config.shouldRollbackOnReorg || isInReorgThreshold ? 0 : chainConfig.maxReorgDepth,
|
|
242
156
|
chainConfig.blockLag,
|
|
243
157
|
),
|
|
244
|
-
~
|
|
158
|
+
~onBlockRegistrations,
|
|
245
159
|
~firstEventBlock,
|
|
246
160
|
)
|
|
247
161
|
|
|
@@ -258,16 +172,6 @@ let makeInternal = (
|
|
|
258
172
|
let lowercaseAddresses = config.lowercaseAddresses
|
|
259
173
|
let sources = switch chainConfig.sourceConfig {
|
|
260
174
|
| 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
175
|
let evmRpcs: array<EvmChain.rpc> = rpcs->Array.map((rpc): EvmChain.rpc => {
|
|
272
176
|
let syncConfig = rpc.syncConfig
|
|
273
177
|
let ws = rpc.ws
|
|
@@ -282,7 +186,9 @@ let makeInternal = (
|
|
|
282
186
|
})
|
|
283
187
|
EvmChain.makeSources(
|
|
284
188
|
~chain,
|
|
285
|
-
~
|
|
189
|
+
~onEventRegistrations=onEventRegistrations->(
|
|
190
|
+
Utils.magic: array<Internal.onEventRegistration> => array<Internal.evmOnEventRegistration>
|
|
191
|
+
),
|
|
286
192
|
~hyperSync=hypersync,
|
|
287
193
|
~rpcs=evmRpcs,
|
|
288
194
|
~lowercaseAddresses,
|
|
@@ -300,17 +206,13 @@ let makeInternal = (
|
|
|
300
206
|
| (Some(hypersyncUrl), _) =>
|
|
301
207
|
// HyperSync drives instruction sync. A configured RPC is ignored for now
|
|
302
208
|
// (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
209
|
let apiToken = Env.envioApiToken
|
|
308
210
|
[
|
|
309
211
|
SvmHyperSyncSource.make({
|
|
310
212
|
chain,
|
|
311
213
|
endpointUrl: hypersyncUrl,
|
|
312
214
|
apiToken,
|
|
313
|
-
|
|
215
|
+
onEventRegistrations,
|
|
314
216
|
clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
|
|
315
217
|
}),
|
|
316
218
|
]
|
|
@@ -319,6 +221,14 @@ let makeInternal = (
|
|
|
319
221
|
| Config.CustomSources(sources) => sources
|
|
320
222
|
}
|
|
321
223
|
|
|
224
|
+
// Seed chain density from whatever progress this chain already has (from a
|
|
225
|
+
// resumed DB state, or 0 on a fresh chain) — refined per-batch afterwards.
|
|
226
|
+
let chainDensity = switch fetchState.firstEventBlock {
|
|
227
|
+
| Some(firstEventBlock) if progressBlockNumber > firstEventBlock && numEventsProcessed > 0. =>
|
|
228
|
+
Some(numEventsProcessed /. (progressBlockNumber - firstEventBlock)->Int.toFloat)
|
|
229
|
+
| _ => None
|
|
230
|
+
}
|
|
231
|
+
|
|
322
232
|
make(
|
|
323
233
|
~chainConfig,
|
|
324
234
|
~fetchState,
|
|
@@ -341,17 +251,27 @@ let makeInternal = (
|
|
|
341
251
|
~ecosystem=config.ecosystem.name,
|
|
342
252
|
~shouldChecksum=!lowercaseAddresses,
|
|
343
253
|
),
|
|
254
|
+
~chainDensity,
|
|
255
|
+
~blockStore=BlockStore.make(
|
|
256
|
+
~ecosystem=config.ecosystem.name,
|
|
257
|
+
~shouldChecksum=!lowercaseAddresses,
|
|
258
|
+
),
|
|
344
259
|
~logger,
|
|
345
260
|
)
|
|
346
261
|
}
|
|
347
262
|
|
|
348
|
-
let makeFromConfig = (
|
|
263
|
+
let makeFromConfig = (
|
|
264
|
+
chainConfig: Config.chain,
|
|
265
|
+
~config,
|
|
266
|
+
~registrationsByChainId,
|
|
267
|
+
~knownHeight,
|
|
268
|
+
) => {
|
|
349
269
|
let logger = Logging.createChild(~params={"chainId": chainConfig.id})
|
|
350
270
|
|
|
351
271
|
makeInternal(
|
|
352
272
|
~chainConfig,
|
|
353
273
|
~config,
|
|
354
|
-
~
|
|
274
|
+
~registrationsByChainId,
|
|
355
275
|
~startBlock=chainConfig.startBlock,
|
|
356
276
|
~endBlock=chainConfig.endBlock,
|
|
357
277
|
~reorgCheckpoints=[],
|
|
@@ -377,7 +297,7 @@ let makeFromDbState = (
|
|
|
377
297
|
~isInReorgThreshold,
|
|
378
298
|
~isRealtime,
|
|
379
299
|
~config,
|
|
380
|
-
~
|
|
300
|
+
~registrationsByChainId,
|
|
381
301
|
~reducedPollingInterval=?,
|
|
382
302
|
) => {
|
|
383
303
|
let chainId = chainConfig.id
|
|
@@ -397,7 +317,7 @@ let makeFromDbState = (
|
|
|
397
317
|
~startBlock=resumedChainState.startBlock,
|
|
398
318
|
~endBlock=resumedChainState.endBlock,
|
|
399
319
|
~config,
|
|
400
|
-
~
|
|
320
|
+
~registrationsByChainId,
|
|
401
321
|
~reorgCheckpoints,
|
|
402
322
|
~maxReorgDepth=resumedChainState.maxReorgDepth,
|
|
403
323
|
~firstEventBlock=resumedChainState.firstEventBlockNumber,
|
|
@@ -435,8 +355,7 @@ let contractAddresses = (cs: t, ~contractName) =>
|
|
|
435
355
|
let bufferSize = (cs: t) => cs.fetchState->FetchState.bufferSize
|
|
436
356
|
let bufferReadyCount = (cs: t) => cs.fetchState->FetchState.bufferReadyCount
|
|
437
357
|
let getProgressPercentage = (cs: t) => cs.fetchState->FetchState.getProgressPercentage
|
|
438
|
-
let
|
|
439
|
-
cs.fetchState->FetchState.getProgressPercentageAt(~blockNumber)
|
|
358
|
+
let chainDensity = (cs: t) => cs.chainDensity
|
|
440
359
|
let hasReadyItem = (cs: t) =>
|
|
441
360
|
cs.fetchState->FetchState.isActivelyIndexing && cs.fetchState->FetchState.hasReadyItem
|
|
442
361
|
let isReadyToEnterReorgThreshold = (cs: t) => cs.fetchState->FetchState.isReadyToEnterReorgThreshold
|
|
@@ -447,7 +366,8 @@ let isReadyToEnterReorgThreshold = (cs: t) => cs.fetchState->FetchState.isReadyT
|
|
|
447
366
|
let startFetchingQueries = (cs: t, ~queries: array<FetchState.query>) => {
|
|
448
367
|
cs.fetchState->FetchState.startFetchingQueries(~queries)
|
|
449
368
|
cs.pendingBudget =
|
|
450
|
-
cs.pendingBudget +.
|
|
369
|
+
cs.pendingBudget +.
|
|
370
|
+
queries->Array.reduce(0., (acc, query) => acc +. query.itemsTarget->Int.toFloat)
|
|
451
371
|
}
|
|
452
372
|
|
|
453
373
|
// Drop every in-flight query and release their reservations together, keeping
|
|
@@ -457,10 +377,109 @@ let resetPendingQueries = (cs: t) => {
|
|
|
457
377
|
cs.pendingBudget = 0.
|
|
458
378
|
}
|
|
459
379
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
//
|
|
463
|
-
|
|
380
|
+
let isReady = (cs: t) => cs.timestampCaughtUpToHeadOrEndblock !== None
|
|
381
|
+
|
|
382
|
+
// Block span over which a batch fully replaces the chain density estimate;
|
|
383
|
+
// smaller batches blend in proportionally, so a few sparse/dense blocks only
|
|
384
|
+
// nudge it.
|
|
385
|
+
let densityBlendWindow = 100.
|
|
386
|
+
|
|
387
|
+
// The last block this chain can fetch right now: the head, or endBlock when
|
|
388
|
+
// it's below the head.
|
|
389
|
+
let fetchCeiling = (cs: t) => {
|
|
390
|
+
let fetchState = cs.fetchState
|
|
391
|
+
switch fetchState.endBlock {
|
|
392
|
+
| Some(endBlock) => Pervasives.min(endBlock, fetchState.knownHeight)
|
|
393
|
+
| None => fetchState.knownHeight
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// This chain's share of the indexer-wide buffer budget turned into a soft
|
|
398
|
+
// target block: via chain density, or the fetch ceiling when density isn't
|
|
399
|
+
// known yet.
|
|
400
|
+
let targetBlock = (cs: t, ~chainTargetItems: float) => {
|
|
401
|
+
let fetchState = cs.fetchState
|
|
402
|
+
let fetchCeiling = cs->fetchCeiling
|
|
403
|
+
let bufferBlockNumber = fetchState->FetchState.bufferBlockNumber
|
|
404
|
+
switch cs.chainDensity {
|
|
405
|
+
| Some(density) if density > 0. =>
|
|
406
|
+
Pervasives.min(
|
|
407
|
+
fetchCeiling,
|
|
408
|
+
bufferBlockNumber + Math.ceil(chainTargetItems /. density)->Float.toInt,
|
|
409
|
+
)
|
|
410
|
+
| _ => fetchCeiling
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Block range that cross-chain progress alignment maps fractions over: from
|
|
415
|
+
// the first block that can hold this chain's events to the last block it will
|
|
416
|
+
// fetch.
|
|
417
|
+
let progressRange = (cs: t) => {
|
|
418
|
+
let fetchState = cs.fetchState
|
|
419
|
+
let lower = fetchState.firstEventBlock->Option.getOr(fetchState.startBlock)
|
|
420
|
+
let upper = switch fetchState.endBlock {
|
|
421
|
+
| Some(endBlock) => endBlock
|
|
422
|
+
| None => fetchState.knownHeight
|
|
423
|
+
}
|
|
424
|
+
(lower, upper)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// A degenerate range (chain already at or past its last block) maps to 1 so it
|
|
428
|
+
// never constrains the other chains. Clamped at 0 for the initial -1 fetch
|
|
429
|
+
// frontier — the only possible blockNumber below the range's lower bound.
|
|
430
|
+
let progressAtBlock = (cs: t, ~blockNumber) => {
|
|
431
|
+
let (lower, upper) = cs->progressRange
|
|
432
|
+
upper <= lower
|
|
433
|
+
? 1.
|
|
434
|
+
: Pervasives.max(
|
|
435
|
+
0.,
|
|
436
|
+
Pervasives.min(1., (blockNumber - lower)->Int.toFloat /. (upper - lower)->Int.toFloat),
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let blockAtProgress = (cs: t, ~progress) => {
|
|
441
|
+
let (lower, upper) = cs->progressRange
|
|
442
|
+
lower + Math.ceil(progress *. (upper - lower)->Int.toFloat)->Float.toInt
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Propose queries sized against this chain's target block. Called by
|
|
446
|
+
// CrossChainState's waterfall, furthest-behind chain first, with
|
|
447
|
+
// chainTargetItems set to whatever budget remains at that point and
|
|
448
|
+
// maxTargetBlock set to the most-behind chain's progress mapped onto this
|
|
449
|
+
// chain, so a chain with budget can't run further ahead than the chain the
|
|
450
|
+
// whole pool is prioritizing.
|
|
451
|
+
let getNextQuery = (cs: t, ~chainTargetItems: float, ~maxTargetBlock=?) => {
|
|
452
|
+
let chainTargetBlock = cs->targetBlock(~chainTargetItems)
|
|
453
|
+
let chainTargetBlock = switch maxTargetBlock {
|
|
454
|
+
| Some(maxTargetBlock) => Pervasives.min(chainTargetBlock, maxTargetBlock)
|
|
455
|
+
| None => chainTargetBlock
|
|
456
|
+
}
|
|
457
|
+
// When the target block is clamped (head/endBlock/cross-chain alignment) a
|
|
458
|
+
// known-density chain can't use the whole handed budget — cap the fresh part
|
|
459
|
+
// at what the clamped range actually costs (in-flight reservations stay on
|
|
460
|
+
// top: they're already accounted and shouldn't crowd out new partitions), so
|
|
461
|
+
// the waterfall's leftover flows to the next chain in the same tick instead
|
|
462
|
+
// of being held by an oversized probe.
|
|
463
|
+
let chainTargetItems = switch cs.chainDensity {
|
|
464
|
+
| Some(density) if density > 0. =>
|
|
465
|
+
let rangeCost =
|
|
466
|
+
density *. (chainTargetBlock - cs.fetchState->FetchState.bufferBlockNumber)->Int.toFloat
|
|
467
|
+
// 3x headroom only for a chain already caught up once and polling the
|
|
468
|
+
// head: there a single query covers the whole remaining range, and a
|
|
469
|
+
// slightly denser-than-expected range would otherwise truncate at the
|
|
470
|
+
// server cap and force an immediate catch-up query for the last blocks.
|
|
471
|
+
// During backfill the range never fits one query anyway, so headroom
|
|
472
|
+
// would just hold budget away from other chains.
|
|
473
|
+
let rangeCost =
|
|
474
|
+
chainTargetBlock >= cs->fetchCeiling && cs->isReady ? rangeCost *. 3. : rangeCost
|
|
475
|
+
Pervasives.min(chainTargetItems, Math.ceil(rangeCost) +. cs.pendingBudget)
|
|
476
|
+
| _ =>
|
|
477
|
+
// No density signal yet: bound the probe so one unknown chain doesn't
|
|
478
|
+
// hold the whole pool while it takes its first measurements.
|
|
479
|
+
Pervasives.min(chainTargetItems, 5_000. +. cs.pendingBudget)
|
|
480
|
+
}
|
|
481
|
+
cs.fetchState->FetchState.getNextQuery(~chainTargetBlock, ~chainTargetItems)
|
|
482
|
+
}
|
|
464
483
|
|
|
465
484
|
// Run a fetch tick for this chain against its sources, feeding the owned fetch
|
|
466
485
|
// frontier to the source manager.
|
|
@@ -498,8 +517,6 @@ let getHighestBlockBelowThreshold = (cs: t): int => {
|
|
|
498
517
|
|
|
499
518
|
let isActivelyIndexing = (cs: t) => cs.fetchState->FetchState.isActivelyIndexing
|
|
500
519
|
|
|
501
|
-
let isReady = (cs: t) => cs.timestampCaughtUpToHeadOrEndblock !== None
|
|
502
|
-
|
|
503
520
|
// True once the fetch frontier has reached the head/endBlock for this chain.
|
|
504
521
|
let isFetchingAtHead = (cs: t) => cs.fetchState->FetchState.isFetchingAtHead
|
|
505
522
|
|
|
@@ -511,21 +528,189 @@ let isAtHeadWithoutEndBlock = (cs: t) =>
|
|
|
511
528
|
// --- State transitions. The chain state is mutated only through these; each
|
|
512
529
|
// owns a cohesive update so callers don't juggle individual fields. ---
|
|
513
530
|
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
531
|
+
// Per-store grouping data a single pass over `items` produces (see
|
|
532
|
+
// `groupBatchItems`), fed into `TransactionStore`/`BlockStore`'s `materialize`.
|
|
533
|
+
type transactionGroups = {
|
|
534
|
+
txBlockNumbers: array<int>,
|
|
535
|
+
transactionIndices: array<int>,
|
|
536
|
+
transactionMasks: array<float>,
|
|
537
|
+
payloadGroups: array<array<Internal.eventPayload>>,
|
|
538
|
+
anyTransactionFieldSelected: bool,
|
|
539
|
+
}
|
|
540
|
+
type blockGroups = {
|
|
541
|
+
blockBlockNumbers: array<int>,
|
|
542
|
+
blockMasks: array<float>,
|
|
543
|
+
blockItemGroups: array<array<Internal.eventItem>>,
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Walk `items` once to build both the transaction-store and block-store
|
|
547
|
+
// grouping data, instead of `TransactionStore`/`BlockStore` each re-walking the
|
|
548
|
+
// same batch independently. Items already arrive in (blockNumber, logIndex)
|
|
549
|
+
// order, so a (blockNumber, transactionIndex) run and a blockNumber-only run
|
|
550
|
+
// each stay adjacent; every item is checked against both, in the same pass.
|
|
551
|
+
// `includeBlocks` skips the block side entirely for Fuel, which carries the
|
|
552
|
+
// block inline and has no store.
|
|
553
|
+
let groupBatchItems = (items: array<Internal.item>, ~includeBlocks: bool): (
|
|
554
|
+
transactionGroups,
|
|
555
|
+
blockGroups,
|
|
556
|
+
) => {
|
|
557
|
+
let txBlockNumbers = []
|
|
558
|
+
let transactionIndices = []
|
|
559
|
+
let transactionMasks = []
|
|
560
|
+
let payloadGroups = []
|
|
561
|
+
let anyTransactionFieldSelected = ref(false)
|
|
562
|
+
|
|
563
|
+
let blockBlockNumbers = []
|
|
564
|
+
let blockMasks = []
|
|
565
|
+
let blockItemGroups = []
|
|
566
|
+
|
|
567
|
+
items->Array.forEach(item =>
|
|
568
|
+
switch item {
|
|
569
|
+
| Internal.Event(_) =>
|
|
570
|
+
let eventItem = item->Internal.castUnsafeEventItem
|
|
571
|
+
let {blockNumber} = eventItem
|
|
572
|
+
|
|
573
|
+
switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
|
|
574
|
+
| Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
|
|
575
|
+
| None =>
|
|
576
|
+
let {transactionIndex} = eventItem
|
|
577
|
+
let mask = eventItem.onEventRegistration.eventConfig.transactionFieldMask
|
|
578
|
+
if mask != 0. {
|
|
579
|
+
anyTransactionFieldSelected := true
|
|
580
|
+
}
|
|
581
|
+
let last = payloadGroups->Array.length - 1
|
|
582
|
+
if (
|
|
583
|
+
last >= 0 &&
|
|
584
|
+
txBlockNumbers->Array.getUnsafe(last) == blockNumber &&
|
|
585
|
+
transactionIndices->Array.getUnsafe(last) == transactionIndex
|
|
586
|
+
) {
|
|
587
|
+
payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
|
|
588
|
+
transactionMasks->Array.setUnsafe(
|
|
589
|
+
last,
|
|
590
|
+
FieldMask.orMask(transactionMasks->Array.getUnsafe(last), mask),
|
|
591
|
+
)
|
|
592
|
+
} else {
|
|
593
|
+
txBlockNumbers->Array.push(blockNumber)
|
|
594
|
+
transactionIndices->Array.push(transactionIndex)
|
|
595
|
+
transactionMasks->Array.push(mask)
|
|
596
|
+
payloadGroups->Array.push([eventItem.payload])
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if includeBlocks {
|
|
601
|
+
switch eventItem.payload->Internal.getPayloadBlock->Nullable.toOption {
|
|
602
|
+
| Some(_) => () // RPC/simulate/Fuel carry the block inline.
|
|
603
|
+
| None =>
|
|
604
|
+
let mask = eventItem.onEventRegistration.eventConfig.blockFieldMask
|
|
605
|
+
let last = blockItemGroups->Array.length - 1
|
|
606
|
+
if last >= 0 && blockBlockNumbers->Array.getUnsafe(last) == blockNumber {
|
|
607
|
+
blockItemGroups->Array.getUnsafe(last)->Array.push(eventItem)
|
|
608
|
+
blockMasks->Array.setUnsafe(
|
|
609
|
+
last,
|
|
610
|
+
FieldMask.orMask(blockMasks->Array.getUnsafe(last), mask),
|
|
611
|
+
)
|
|
612
|
+
} else {
|
|
613
|
+
blockBlockNumbers->Array.push(blockNumber)
|
|
614
|
+
blockMasks->Array.push(mask)
|
|
615
|
+
blockItemGroups->Array.push([eventItem])
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
| Internal.Block(_) => ()
|
|
620
|
+
}
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
(
|
|
624
|
+
{
|
|
625
|
+
txBlockNumbers,
|
|
626
|
+
transactionIndices,
|
|
627
|
+
transactionMasks,
|
|
628
|
+
payloadGroups,
|
|
629
|
+
anyTransactionFieldSelected: anyTransactionFieldSelected.contents,
|
|
630
|
+
},
|
|
631
|
+
{blockBlockNumbers, blockMasks, blockItemGroups},
|
|
632
|
+
)
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Materialise a `TransactionStore` against precomputed groups (see
|
|
636
|
+
// `groupBatchItems`). Store-backed items always get a transaction object — the
|
|
637
|
+
// selected fields, or `{}` when nothing was selected — so `event.transaction`
|
|
638
|
+
// is never `undefined` (matching the inline sources).
|
|
639
|
+
let applyTransactionGroups = async (store: TransactionStore.t, g: transactionGroups) => {
|
|
640
|
+
if g.payloadGroups->Utils.Array.notEmpty {
|
|
641
|
+
if g.anyTransactionFieldSelected {
|
|
642
|
+
let txs = await store->TransactionStore.materialize(
|
|
643
|
+
~blockNumbers=g.txBlockNumbers,
|
|
644
|
+
~transactionIndices=g.transactionIndices,
|
|
645
|
+
~masks=g.transactionMasks,
|
|
646
|
+
)
|
|
647
|
+
g.payloadGroups->Array.forEachWithIndex((payloads, i) => {
|
|
648
|
+
let tx = txs->Array.getUnsafe(i)
|
|
649
|
+
payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
|
|
650
|
+
})
|
|
651
|
+
} else {
|
|
652
|
+
g.payloadGroups->Array.forEach(payloads =>
|
|
653
|
+
payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(%raw(`{}`)))
|
|
654
|
+
)
|
|
655
|
+
}
|
|
528
656
|
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Materialise a `BlockStore` against precomputed groups (see `groupBatchItems`).
|
|
660
|
+
let applyBlockGroups = async (store: BlockStore.t, g: blockGroups) => {
|
|
661
|
+
if g.blockItemGroups->Utils.Array.notEmpty {
|
|
662
|
+
let blocks = await store->BlockStore.materialize(
|
|
663
|
+
~blockNumbers=g.blockBlockNumbers,
|
|
664
|
+
~masks=g.blockMasks,
|
|
665
|
+
)
|
|
666
|
+
g.blockItemGroups->Array.forEachWithIndex((group, i) => {
|
|
667
|
+
let block = blocks->Array.getUnsafe(i)
|
|
668
|
+
group->Array.forEach(ei => ei.payload->Internal.setPayloadBlock(block))
|
|
669
|
+
})
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
let includeBlocksForEcosystem = (ecosystem: Ecosystem.name) =>
|
|
674
|
+
switch ecosystem {
|
|
675
|
+
| Evm | Svm => true
|
|
676
|
+
| Fuel => false
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Materialise the chain stores' selected transaction and block fields onto a
|
|
680
|
+
// batch's items at batch prep (the persistent-store path). A single pass over
|
|
681
|
+
// `items` (`groupBatchItems`) builds both stores' selection masks before the
|
|
682
|
+
// two independent materialize calls run concurrently.
|
|
683
|
+
let materializeBatchItems = async (cs: t, ~items: array<Internal.item>, ~ecosystem) => {
|
|
684
|
+
let (txGroups, blockGroups) =
|
|
685
|
+
items->groupBatchItems(~includeBlocks=ecosystem->includeBlocksForEcosystem)
|
|
686
|
+
let _ = await Promise.all2((
|
|
687
|
+
cs.transactionStore->applyTransactionGroups(txGroups),
|
|
688
|
+
cs.blockStore->applyBlockGroups(blockGroups),
|
|
689
|
+
))
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Materialise a fetch-response page's transactions and blocks onto its items
|
|
693
|
+
// before contract-register handlers read them. `None` pages (RPC/Fuel/Simulate
|
|
694
|
+
// keep them inline) are a no-op.
|
|
695
|
+
let materializePageItems = async (
|
|
696
|
+
~items: array<Internal.item>,
|
|
697
|
+
~transactionStore: option<TransactionStore.t>,
|
|
698
|
+
~blockStore: option<BlockStore.t>,
|
|
699
|
+
~ecosystem,
|
|
700
|
+
) => {
|
|
701
|
+
let (txGroups, blockGroups) =
|
|
702
|
+
items->groupBatchItems(~includeBlocks=ecosystem->includeBlocksForEcosystem)
|
|
703
|
+
let _ = await Promise.all2((
|
|
704
|
+
switch transactionStore {
|
|
705
|
+
| Some(store) => store->applyTransactionGroups(txGroups)
|
|
706
|
+
| None => Promise.resolve()
|
|
707
|
+
},
|
|
708
|
+
switch blockStore {
|
|
709
|
+
| Some(store) => store->applyBlockGroups(blockGroups)
|
|
710
|
+
| None => Promise.resolve()
|
|
711
|
+
},
|
|
712
|
+
))
|
|
713
|
+
}
|
|
529
714
|
|
|
530
715
|
let handleQueryResult = (
|
|
531
716
|
cs: t,
|
|
@@ -534,14 +719,19 @@ let handleQueryResult = (
|
|
|
534
719
|
~newItemsWithDcs,
|
|
535
720
|
~latestFetchedBlock,
|
|
536
721
|
~knownHeight,
|
|
537
|
-
~transactionStore as
|
|
722
|
+
~transactionStore as txPage: option<TransactionStore.t>,
|
|
723
|
+
~blockStore as blockPage: option<BlockStore.t>,
|
|
538
724
|
) => {
|
|
539
|
-
// Merge this response's
|
|
540
|
-
// its items to the buffer. Inline
|
|
541
|
-
switch
|
|
725
|
+
// Merge this response's pages into the chain stores in lockstep with appending
|
|
726
|
+
// its items to the buffer. Inline sources contribute no page.
|
|
727
|
+
switch txPage {
|
|
542
728
|
| Some(page) => cs.transactionStore->TransactionStore.merge(page)
|
|
543
729
|
| None => ()
|
|
544
730
|
}
|
|
731
|
+
switch blockPage {
|
|
732
|
+
| Some(page) => cs.blockStore->BlockStore.merge(page)
|
|
733
|
+
| None => ()
|
|
734
|
+
}
|
|
545
735
|
|
|
546
736
|
let fs = switch newItemsWithDcs {
|
|
547
737
|
| [] => cs.fetchState
|
|
@@ -563,7 +753,7 @@ let handleQueryResult = (
|
|
|
563
753
|
->FetchState.updateKnownHeight(~knownHeight)
|
|
564
754
|
|
|
565
755
|
// The query is no longer in flight, so release its reservation.
|
|
566
|
-
cs.pendingBudget = Pervasives.max(0., cs.pendingBudget -. query.
|
|
756
|
+
cs.pendingBudget = Pervasives.max(0., cs.pendingBudget -. query.itemsTarget->Int.toFloat)
|
|
567
757
|
}
|
|
568
758
|
|
|
569
759
|
// Run reorg detection against a fetch response and commit the updated guard.
|
|
@@ -670,8 +860,9 @@ let advanceAfterBatch = (cs: t, ~batch: Batch.t, ~enteringReorgThreshold) =>
|
|
|
670
860
|
// Commit a processed batch's progress for this chain (progress block, events
|
|
671
861
|
// processed, head/safe-checkpoint tracking, first event block). Emits the
|
|
672
862
|
// per-chain progress metrics. Readiness is decided by CrossChainState once every
|
|
673
|
-
// chain is caught up (see markReady).
|
|
674
|
-
|
|
863
|
+
// chain is caught up (see markReady). `blockTimestampName` is the ecosystem's
|
|
864
|
+
// block-timestamp field, read off the payload block for the latency metric.
|
|
865
|
+
let applyBatchProgress = (cs: t, ~batch: Batch.t, ~blockTimestampName: string) => {
|
|
675
866
|
let chainId = cs.chainConfig.id
|
|
676
867
|
|
|
677
868
|
switch batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(chainId) {
|
|
@@ -689,15 +880,27 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t) => {
|
|
|
689
880
|
)
|
|
690
881
|
}
|
|
691
882
|
|
|
692
|
-
// Calculate and set latency metrics
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
883
|
+
// Calculate and set latency metrics. The payload block is materialised or
|
|
884
|
+
// inline by processing time; its timestamp may still be absent (e.g. an
|
|
885
|
+
// SVM slot with no block row) — the metric is skipped then.
|
|
886
|
+
switch batch
|
|
887
|
+
->Batch.findLastEventItem(~chainId)
|
|
888
|
+
->Option.flatMap(eventItem =>
|
|
889
|
+
eventItem.payload
|
|
890
|
+
->Internal.getPayloadBlock
|
|
891
|
+
->Nullable.toOption
|
|
892
|
+
)
|
|
893
|
+
->Option.flatMap(block =>
|
|
894
|
+
block
|
|
895
|
+
->(Utils.magic: Internal.eventBlock => dict<unknown>)
|
|
896
|
+
->Utils.Dict.dangerouslyGetNonOption(blockTimestampName)
|
|
897
|
+
) {
|
|
898
|
+
| Some(blockTimestamp) =>
|
|
899
|
+
let blockTimestampMs = blockTimestamp->(Utils.magic: unknown => int) * 1000
|
|
900
|
+
Prometheus.ProgressLatency.set(
|
|
901
|
+
~latencyMs=Date.now()->Float.toInt - blockTimestampMs,
|
|
902
|
+
~chainId,
|
|
903
|
+
)
|
|
701
904
|
| None => ()
|
|
702
905
|
}
|
|
703
906
|
|
|
@@ -712,10 +915,35 @@ let applyBatchProgress = (cs: t, ~batch: Batch.t) => {
|
|
|
712
915
|
}
|
|
713
916
|
}
|
|
714
917
|
|
|
918
|
+
// Chain-wide density update: seed with the batch's own events/block on
|
|
919
|
+
// the first update, then blend weighted by the batch's block span — a
|
|
920
|
+
// few sparse/dense blocks barely nudge the estimate, while a
|
|
921
|
+
// window-sized batch replaces it.
|
|
922
|
+
let deltaBlocks = chainAfterBatch.progressBlockNumber - cs.committedProgressBlockNumber
|
|
923
|
+
if deltaBlocks > 0 {
|
|
924
|
+
let deltaEvents = chainAfterBatch.totalEventsProcessed -. cs.numEventsProcessed
|
|
925
|
+
// Don't seed a density before the first event is seen — a progress-only
|
|
926
|
+
// batch would otherwise set it to 0, matching the resume-seed guard.
|
|
927
|
+
switch (cs.chainDensity, deltaEvents > 0.) {
|
|
928
|
+
| (None, false) => ()
|
|
929
|
+
| _ =>
|
|
930
|
+
let batchDensity = deltaEvents /. deltaBlocks->Int.toFloat
|
|
931
|
+
cs.chainDensity = Some(
|
|
932
|
+
switch cs.chainDensity {
|
|
933
|
+
| None => batchDensity
|
|
934
|
+
| Some(oldDensity) =>
|
|
935
|
+
let alpha = Pervasives.min(1., deltaBlocks->Int.toFloat /. densityBlendWindow)
|
|
936
|
+
oldDensity *. (1. -. alpha) +. batchDensity *. alpha
|
|
937
|
+
},
|
|
938
|
+
)
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
715
942
|
cs.committedProgressBlockNumber = chainAfterBatch.progressBlockNumber
|
|
716
943
|
cs.numEventsProcessed = chainAfterBatch.totalEventsProcessed
|
|
717
|
-
// Processed blocks' transactions are no longer needed.
|
|
944
|
+
// Processed blocks' transactions and blocks are no longer needed.
|
|
718
945
|
cs.transactionStore->TransactionStore.prune(chainAfterBatch.progressBlockNumber)
|
|
946
|
+
cs.blockStore->BlockStore.prune(chainAfterBatch.progressBlockNumber)
|
|
719
947
|
cs.isProgressAtHead = cs.isProgressAtHead || chainAfterBatch.isProgressAtHeadWhenBatchCreated
|
|
720
948
|
switch cs.safeCheckpointTracking {
|
|
721
949
|
| Some(safeCheckpointTracking) =>
|
|
@@ -794,6 +1022,7 @@ let rollback = (
|
|
|
794
1022
|
~targetBlockNumber=newProgressBlockNumber,
|
|
795
1023
|
)
|
|
796
1024
|
cs.transactionStore->TransactionStore.rollback(newProgressBlockNumber)
|
|
1025
|
+
cs.blockStore->BlockStore.rollback(newProgressBlockNumber)
|
|
797
1026
|
cs.committedProgressBlockNumber = newProgressBlockNumber
|
|
798
1027
|
cs.numEventsProcessed = newTotalEventsProcessed
|
|
799
1028
|
| None =>
|
|
@@ -808,6 +1037,7 @@ let rollback = (
|
|
|
808
1037
|
~targetBlockNumber=rollbackTargetBlockNumber,
|
|
809
1038
|
)
|
|
810
1039
|
cs.transactionStore->TransactionStore.rollback(rollbackTargetBlockNumber)
|
|
1040
|
+
cs.blockStore->BlockStore.rollback(rollbackTargetBlockNumber)
|
|
811
1041
|
cs.committedProgressBlockNumber = Pervasives.min(
|
|
812
1042
|
cs.committedProgressBlockNumber,
|
|
813
1043
|
rollbackTargetBlockNumber,
|