envio 3.3.0-alpha.5 → 3.3.0-alpha.6
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/package.json +6 -6
- package/src/BatchProcessing.res +5 -0
- package/src/BatchProcessing.res.mjs +6 -0
- package/src/ExitOnCaughtUp.res +10 -2
- package/src/ExitOnCaughtUp.res.mjs +10 -1
- package/src/IndexerState.res +4 -0
- package/src/IndexerState.res.mjs +8 -1
- package/src/IndexerState.resi +1 -0
- package/src/SimulateDeadInputTracker.res +85 -0
- package/src/SimulateDeadInputTracker.res.mjs +73 -0
- package/src/SimulateDeadInputTracker.resi +12 -0
- package/src/SimulateItems.res +23 -42
- package/src/SimulateItems.res.mjs +12 -30
- package/src/TestIndexer.res +3 -27
- package/src/TestIndexer.res.mjs +2 -9
- package/src/sources/EvmChain.res +1 -1
- package/src/sources/EvmChain.res.mjs +1 -1
- package/src/sources/RpcSource.res +274 -146
- package/src/sources/RpcSource.res.mjs +285 -246
- package/src/sources/SimulateSource.res +1 -0
- package/src/sources/SimulateSource.res.mjs +2 -1
- package/src/sources/Source.res +4 -0
|
@@ -74,6 +74,47 @@ let getKnownRawBlockWithBackoff = async (
|
|
|
74
74
|
}
|
|
75
75
|
result.contents->Option.getOrThrow
|
|
76
76
|
}
|
|
77
|
+
// Pulls the provider error message out of either a parsed Rpc.JsonRpcError or
|
|
78
|
+
// the raw napi JsExn shape (`error.error.message`), so classifiers below don't
|
|
79
|
+
// each have to re-derive it.
|
|
80
|
+
let getErrorMessage = (exn: exn): option<string> =>
|
|
81
|
+
switch exn {
|
|
82
|
+
| Rpc.JsonRpcError({message}) => Some(message)
|
|
83
|
+
| JsExn(error) =>
|
|
84
|
+
try {
|
|
85
|
+
let message: string = (error->Obj.magic)["error"]["message"]
|
|
86
|
+
message->S.assertOrThrow(S.string)
|
|
87
|
+
Some(message)
|
|
88
|
+
} catch {
|
|
89
|
+
| _ => None
|
|
90
|
+
}
|
|
91
|
+
| _ => None
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Deterministic "the range returned too much data" errors that carry no numeric
|
|
95
|
+
// block-range suggestion (HyperRPC's 50k-log cap, response-size and result-count
|
|
96
|
+
// limits). They depend on log density, not on a fixed block window, so waiting
|
|
97
|
+
// never helps — the same range always re-trips the same cap. The reaction is to
|
|
98
|
+
// shrink the range and retry immediately, ratcheting the max range down.
|
|
99
|
+
let isResponseTooLargeError = {
|
|
100
|
+
let patterns = [
|
|
101
|
+
/more than \d+ logs/i, // HyperRPC: "More than 50000 logs returned"
|
|
102
|
+
/\d+ logs returned/i,
|
|
103
|
+
/too many logs/i,
|
|
104
|
+
/query returned more than \d+ results/i, // ZkEVM
|
|
105
|
+
/query exceeds max results/i, // LlamaRPC
|
|
106
|
+
/response size should not/i, // 1RPC
|
|
107
|
+
/(backend )?response too large/i, // Optimism
|
|
108
|
+
/logs matched by query exceeds limit/i, // Arbitrum
|
|
109
|
+
/block range is too wide/i, // Ankr
|
|
110
|
+
]
|
|
111
|
+
(exn: exn) =>
|
|
112
|
+
switch exn->getErrorMessage {
|
|
113
|
+
| Some(message) => patterns->Array.some(re => re->RegExp.test(message))
|
|
114
|
+
| None => false
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
77
118
|
let getSuggestedBlockIntervalFromExn = {
|
|
78
119
|
// Unknown provider: "retry with the range 123-456"
|
|
79
120
|
let suggestedRangeRegExp = /retry with the range (\d+)-(\d+)/
|
|
@@ -118,14 +159,6 @@ let getSuggestedBlockIntervalFromExn = {
|
|
|
118
159
|
// when we send request with numeric block range instead of hex
|
|
119
160
|
// Infura, ZkSync: "Try with this block range [0x123,0x456]"
|
|
120
161
|
|
|
121
|
-
// Future handling needed for these providers that don't suggest ranges:
|
|
122
|
-
// - Ankr: "block range is too wide"
|
|
123
|
-
// - 1RPC: "response size should not greater than 10000000 bytes"
|
|
124
|
-
// - ZkEVM: "query returned more than 10000 results"
|
|
125
|
-
// - LlamaRPC: "query exceeds max results"
|
|
126
|
-
// - Optimism: "backend response too large" or "Block range is too large"
|
|
127
|
-
// - Arbitrum: "logs matched by query exceeds limit of 10000"
|
|
128
|
-
|
|
129
162
|
let parseMessageForBlockRange = (message: string) => {
|
|
130
163
|
// Helper to extract block range from regex match
|
|
131
164
|
let extractBlockRange = (execResult, ~isMaxRange) =>
|
|
@@ -209,17 +242,9 @@ let getSuggestedBlockIntervalFromExn = {
|
|
|
209
242
|
// Whether it's the max range that the provider allows
|
|
210
243
|
bool,
|
|
211
244
|
)> =>
|
|
212
|
-
switch exn {
|
|
213
|
-
|
|
|
214
|
-
|
|
|
215
|
-
try {
|
|
216
|
-
let message: string = (error->Obj.magic)["error"]["message"]
|
|
217
|
-
message->S.assertOrThrow(S.string)
|
|
218
|
-
parseMessageForBlockRange(message)
|
|
219
|
-
} catch {
|
|
220
|
-
| _ => None
|
|
221
|
-
}
|
|
222
|
-
| _ => None
|
|
245
|
+
switch exn->getErrorMessage {
|
|
246
|
+
| Some(message) => parseMessageForBlockRange(message)
|
|
247
|
+
| None => None
|
|
223
248
|
}
|
|
224
249
|
}
|
|
225
250
|
|
|
@@ -230,11 +255,38 @@ type eventBatchQuery = {
|
|
|
230
255
|
|
|
231
256
|
let maxSuggestedBlockIntervalKey = "max"
|
|
232
257
|
|
|
258
|
+
let getSourceMaxBlockInterval = (mutSuggestedBlockIntervals, ~intervalCeiling) =>
|
|
259
|
+
mutSuggestedBlockIntervals
|
|
260
|
+
->Utils.Dict.dangerouslyGetNonOption(maxSuggestedBlockIntervalKey)
|
|
261
|
+
->Option.getOr(intervalCeiling)
|
|
262
|
+
|
|
263
|
+
type logSelection = {
|
|
264
|
+
addresses: option<array<Address.t>>,
|
|
265
|
+
topicQuery: Rpc.GetLogs.topicQuery,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// A log can satisfy more than one selection when a single event's `where` is an
|
|
269
|
+
// OR of param groups, so dedup the fanned-out responses by (blockNumber,
|
|
270
|
+
// logIndex) — unique per chain — keeping the first occurrence.
|
|
271
|
+
let mergeAndDedupItems = (itemsPerSelection: array<array<EvmRpcClient.rpcEventItem>>) => {
|
|
272
|
+
let seen = Utils.Set.make()
|
|
273
|
+
let merged = []
|
|
274
|
+
itemsPerSelection->Array.forEach(items =>
|
|
275
|
+
items->Array.forEach((item: EvmRpcClient.rpcEventItem) => {
|
|
276
|
+
let key = `${item.log.blockNumber->Int.toString}-${item.log.logIndex->Int.toString}`
|
|
277
|
+
if seen->Utils.Set.has(key)->not {
|
|
278
|
+
seen->Utils.Set.add(key)->ignore
|
|
279
|
+
merged->Array.push(item)->ignore
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
)
|
|
283
|
+
merged
|
|
284
|
+
}
|
|
285
|
+
|
|
233
286
|
let getNextPage = (
|
|
234
287
|
~fromBlock,
|
|
235
288
|
~toBlock,
|
|
236
|
-
~
|
|
237
|
-
~topicQuery: Rpc.GetLogs.topicQuery,
|
|
289
|
+
~logSelections: array<logSelection>,
|
|
238
290
|
~loadBlock,
|
|
239
291
|
~syncConfig as sc: Config.sourceSync,
|
|
240
292
|
~rpcClient: EvmRpcClient.t,
|
|
@@ -252,93 +304,109 @@ let getNextPage = (
|
|
|
252
304
|
)
|
|
253
305
|
|
|
254
306
|
let latestFetchedBlockPromise = loadBlock(toBlock)
|
|
255
|
-
|
|
256
|
-
let
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
307
|
+
|
|
308
|
+
let queryLogs = ({addresses, topicQuery}: logSelection) => {
|
|
309
|
+
Prometheus.SourceRequestCount.increment(~sourceName, ~chainId, ~method="eth_getLogs")
|
|
310
|
+
rpcClient.getLogs({
|
|
311
|
+
fromBlock,
|
|
312
|
+
toBlock,
|
|
313
|
+
?addresses,
|
|
314
|
+
topics: topicQuery->Array.map(filter =>
|
|
315
|
+
switch filter {
|
|
316
|
+
| Rpc.GetLogs.Null => Nullable.null
|
|
317
|
+
| Single(topic) => Nullable.make([topic])
|
|
318
|
+
| Multiple(topics) => Nullable.make(topics)
|
|
319
|
+
}
|
|
320
|
+
),
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let logsPromise = switch logSelections {
|
|
325
|
+
| [] =>
|
|
326
|
+
latestFetchedBlockPromise->Promise.thenResolve((latestFetchedBlockInfo): eventBatchQuery => {
|
|
327
|
+
items: [],
|
|
328
|
+
latestFetchedBlockInfo,
|
|
329
|
+
})
|
|
330
|
+
// Fast path: a single selection needs no cross-request merge or dedup.
|
|
331
|
+
| [logSelection] =>
|
|
332
|
+
logSelection
|
|
333
|
+
->queryLogs
|
|
334
|
+
->Promise.then(async items => {
|
|
335
|
+
{
|
|
336
|
+
items,
|
|
337
|
+
latestFetchedBlockInfo: await latestFetchedBlockPromise,
|
|
265
338
|
}
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
339
|
+
})
|
|
340
|
+
| _ =>
|
|
341
|
+
logSelections
|
|
342
|
+
->Array.map(queryLogs)
|
|
343
|
+
->Promise.all
|
|
344
|
+
->Promise.then(async itemsPerSelection => {
|
|
345
|
+
{
|
|
346
|
+
items: itemsPerSelection->mergeAndDedupItems,
|
|
347
|
+
latestFetchedBlockInfo: await latestFetchedBlockPromise,
|
|
348
|
+
}
|
|
349
|
+
})
|
|
350
|
+
}
|
|
273
351
|
|
|
274
352
|
[queryTimoutPromise, logsPromise]
|
|
275
353
|
->Promise.race
|
|
276
354
|
->Promise.catch(err => {
|
|
355
|
+
let executedBlockInterval = toBlock - fromBlock + 1
|
|
356
|
+
let shrunkBlockInterval =
|
|
357
|
+
Pervasives.max(1, (executedBlockInterval->Int.toFloat *. sc.backoffMultiplicative)->Float.toInt)
|
|
358
|
+
|
|
359
|
+
let throwFailedGettingItems = retry =>
|
|
360
|
+
throw(Source.GetItemsError(FailedGettingItems({exn: err, attemptedToBlock: toBlock, retry})))
|
|
361
|
+
let throwResize = interval =>
|
|
362
|
+
throwFailedGettingItems(WithSuggestedToBlock({toBlock: fromBlock + interval - 1}))
|
|
363
|
+
|
|
277
364
|
switch getSuggestedBlockIntervalFromExn(err) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
throw(
|
|
284
|
-
Source.GetItemsError(
|
|
285
|
-
FailedGettingItems({
|
|
286
|
-
exn: err,
|
|
287
|
-
attemptedToBlock: toBlock,
|
|
288
|
-
retry: WithSuggestedToBlock({
|
|
289
|
-
toBlock: fromBlock + nextBlockIntervalTry - 1,
|
|
290
|
-
}),
|
|
291
|
-
}),
|
|
292
|
-
),
|
|
365
|
+
// "limited to N blocks" — a structural cap on the whole source; only tighten.
|
|
366
|
+
| Some((interval, true)) =>
|
|
367
|
+
let capped = Pervasives.min(
|
|
368
|
+
mutSuggestedBlockIntervals->getSourceMaxBlockInterval(~intervalCeiling=sc.intervalCeiling),
|
|
369
|
+
interval,
|
|
293
370
|
)
|
|
371
|
+
mutSuggestedBlockIntervals->Dict.set(maxSuggestedBlockIntervalKey, capped)
|
|
372
|
+
throwResize(capped)
|
|
373
|
+
// A one-off suggested range ("retry with range X-Y") — apply to this partition.
|
|
374
|
+
| Some((interval, false)) =>
|
|
375
|
+
mutSuggestedBlockIntervals->Dict.set(partitionId, interval)
|
|
376
|
+
throwResize(interval)
|
|
377
|
+
// Density cap with no suggested number (too many logs / response too large):
|
|
378
|
+
// shrink THIS partition and retry immediately (no wait); acceleration
|
|
379
|
+
// re-adapts on the next successful query. The interval>1 guard avoids a
|
|
380
|
+
// no-progress tight loop on a single over-cap block.
|
|
381
|
+
| None if executedBlockInterval > 1 && err->isResponseTooLargeError =>
|
|
382
|
+
mutSuggestedBlockIntervals->Dict.set(partitionId, shrunkBlockInterval)
|
|
383
|
+
throwResize(shrunkBlockInterval)
|
|
384
|
+
// Transient/unknown — shrink this partition and back off.
|
|
294
385
|
| None =>
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
Source.FailedGettingItems({
|
|
302
|
-
exn: err,
|
|
303
|
-
attemptedToBlock: toBlock,
|
|
304
|
-
retry: WithBackoff({
|
|
305
|
-
message: `Failed getting data for the block range. Will try smaller block range for the next attempt.`,
|
|
306
|
-
backoffMillis: sc.backoffMillis,
|
|
307
|
-
}),
|
|
308
|
-
}),
|
|
309
|
-
),
|
|
386
|
+
mutSuggestedBlockIntervals->Dict.set(partitionId, shrunkBlockInterval)
|
|
387
|
+
throwFailedGettingItems(
|
|
388
|
+
WithBackoff({
|
|
389
|
+
message: `Failed getting data for the block range. Will try smaller block range for the next attempt.`,
|
|
390
|
+
backoffMillis: sc.backoffMillis,
|
|
391
|
+
}),
|
|
310
392
|
)
|
|
311
393
|
}
|
|
312
394
|
})
|
|
313
395
|
}
|
|
314
396
|
|
|
315
|
-
type logSelection = {
|
|
316
|
-
addresses: option<array<Address.t>>,
|
|
317
|
-
topicQuery: Rpc.GetLogs.topicQuery,
|
|
318
|
-
}
|
|
319
|
-
|
|
320
397
|
type selectionConfig = {
|
|
321
|
-
|
|
398
|
+
getLogSelectionsOrThrow: (
|
|
399
|
+
~addressesByContractName: dict<array<Address.t>>,
|
|
400
|
+
) => array<logSelection>,
|
|
322
401
|
}
|
|
323
402
|
|
|
324
403
|
let getSelectionConfig = (selection: FetchState.selection, ~chain) => {
|
|
325
|
-
let
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
->(Utils.magic: array<Internal.eventConfig> => array<Internal.evmEventConfig>)
|
|
330
|
-
->Array.forEach(({getEventFiltersOrThrow}) => {
|
|
331
|
-
switch getEventFiltersOrThrow(chain) {
|
|
332
|
-
| Static(s) => staticTopicSelections->Array.pushMany(s)->ignore
|
|
333
|
-
| Dynamic(fn) => dynamicEventFilters->Array.push(fn)->ignore
|
|
334
|
-
}
|
|
335
|
-
})
|
|
404
|
+
let evmEventConfigs =
|
|
405
|
+
selection.eventConfigs->(
|
|
406
|
+
Utils.magic: array<Internal.eventConfig> => array<Internal.evmEventConfig>
|
|
407
|
+
)
|
|
336
408
|
|
|
337
|
-
|
|
338
|
-
staticTopicSelections->LogSelection.compressTopicSelections,
|
|
339
|
-
dynamicEventFilters,
|
|
340
|
-
) {
|
|
341
|
-
| ([], []) =>
|
|
409
|
+
if evmEventConfigs->Utils.Array.isEmpty {
|
|
342
410
|
throw(
|
|
343
411
|
Source.GetItemsError(
|
|
344
412
|
UnsupportedSelection({
|
|
@@ -346,48 +414,110 @@ let getSelectionConfig = (selection: FetchState.selection, ~chain) => {
|
|
|
346
414
|
}),
|
|
347
415
|
),
|
|
348
416
|
)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// eth_getLogs takes one address list and one topic selection per request, so
|
|
420
|
+
// fan out to one request per selection. Each address-bound event is grouped by
|
|
421
|
+
// its contract and later scoped to that contract's own addresses — pooling all
|
|
422
|
+
// contracts' addresses would let one contract's query fetch a sibling's logs,
|
|
423
|
+
// which route back by address and bypass the sibling's filter (routing never
|
|
424
|
+
// re-applies it). Pure-wildcard events carry no address constraint, so they're
|
|
425
|
+
// pooled and resolved once.
|
|
426
|
+
let noAddressTopicSelections = []
|
|
427
|
+
let staticByContract = Dict.make()
|
|
428
|
+
let dynamicByContract = Dict.make()
|
|
429
|
+
let dynamicWildcardByContract = Dict.make()
|
|
430
|
+
let contractNames = Utils.Set.make()
|
|
431
|
+
|
|
432
|
+
evmEventConfigs->Array.forEach(({
|
|
433
|
+
contractName,
|
|
434
|
+
isWildcard,
|
|
435
|
+
dependsOnAddresses,
|
|
436
|
+
getEventFiltersOrThrow,
|
|
437
|
+
}) => {
|
|
438
|
+
let eventFilters = getEventFiltersOrThrow(chain)
|
|
439
|
+
if dependsOnAddresses {
|
|
440
|
+
contractNames->Utils.Set.add(contractName)->ignore
|
|
441
|
+
switch eventFilters {
|
|
442
|
+
| Internal.Static(topicSelections) =>
|
|
443
|
+
staticByContract->Utils.Dict.pushMany(contractName, topicSelections)
|
|
444
|
+
| Dynamic(fn) =>
|
|
445
|
+
(isWildcard ? dynamicWildcardByContract : dynamicByContract)->Utils.Dict.push(
|
|
446
|
+
contractName,
|
|
447
|
+
fn,
|
|
448
|
+
)
|
|
357
449
|
}
|
|
450
|
+
} else {
|
|
451
|
+
noAddressTopicSelections
|
|
452
|
+
->Array.pushMany(
|
|
453
|
+
switch eventFilters {
|
|
454
|
+
| Static(s) => s
|
|
455
|
+
| Dynamic(fn) => fn([])
|
|
456
|
+
},
|
|
457
|
+
)
|
|
458
|
+
->ignore
|
|
358
459
|
}
|
|
359
|
-
|
|
360
|
-
let eventConfig = selection.eventConfigs->Utils.Array.firstUnsafe
|
|
460
|
+
})
|
|
361
461
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
462
|
+
// `compressTopicSelections` folds the filter-less events into a single topic0
|
|
463
|
+
// OR-set, keeping the common case at one request.
|
|
464
|
+
let toLogSelections = (~addresses, topicSelections): array<logSelection> =>
|
|
465
|
+
topicSelections
|
|
466
|
+
->LogSelection.compressTopicSelections
|
|
467
|
+
->Array.map(topicSelection => {
|
|
468
|
+
addresses,
|
|
469
|
+
topicQuery: topicSelection->Rpc.GetLogs.mapTopicQuery,
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
// Address-independent, so resolve once (the wildcard partition reuses this).
|
|
473
|
+
let noAddressLogSelections = toLogSelections(~addresses=None, noAddressTopicSelections)
|
|
474
|
+
|
|
475
|
+
let getLogSelectionsOrThrow = if contractNames->Utils.Set.size === 0 {
|
|
476
|
+
(~addressesByContractName as _) => noAddressLogSelections
|
|
477
|
+
} else {
|
|
478
|
+
(~addressesByContractName): array<logSelection> => {
|
|
479
|
+
let logSelections = noAddressLogSelections->Array.copy
|
|
480
|
+
contractNames->Utils.Set.forEach(contractName => {
|
|
481
|
+
switch addressesByContractName->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
482
|
+
| None
|
|
483
|
+
| Some([]) => ()
|
|
484
|
+
| Some(addresses) =>
|
|
485
|
+
// Static + dynamic non-wildcard filters, scoped to this contract's addresses.
|
|
486
|
+
let addressedTopicSelections = []
|
|
487
|
+
switch staticByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
488
|
+
| Some(s) => addressedTopicSelections->Array.pushMany(s)->ignore
|
|
489
|
+
| None => ()
|
|
490
|
+
}
|
|
491
|
+
switch dynamicByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
492
|
+
| Some(fns) =>
|
|
493
|
+
fns->Array.forEach(fn =>
|
|
494
|
+
addressedTopicSelections->Array.pushMany(fn(addresses))->ignore
|
|
495
|
+
)
|
|
496
|
+
| None => ()
|
|
497
|
+
}
|
|
498
|
+
logSelections
|
|
499
|
+
->Array.pushMany(toLogSelections(~addresses=Some(addresses), addressedTopicSelections))
|
|
500
|
+
->ignore
|
|
501
|
+
|
|
502
|
+
// Dynamic wildcard-by-address filters fold the address into the topics,
|
|
503
|
+
// so they still match any address.
|
|
504
|
+
switch dynamicWildcardByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
505
|
+
| Some(fns) =>
|
|
506
|
+
logSelections
|
|
507
|
+
->Array.pushMany(
|
|
508
|
+
toLogSelections(~addresses=None, fns->Array.flatMap(fn => fn(addresses))),
|
|
509
|
+
)
|
|
510
|
+
->ignore
|
|
511
|
+
| None => ()
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
})
|
|
515
|
+
logSelections
|
|
378
516
|
}
|
|
379
|
-
| _ =>
|
|
380
|
-
throw(
|
|
381
|
-
Source.GetItemsError(
|
|
382
|
-
UnsupportedSelection({
|
|
383
|
-
message: "RPC data-source currently supports event filters only when there's a single wildcard event. Please, create a GitHub issue if it's a blocker for you.",
|
|
384
|
-
}),
|
|
385
|
-
),
|
|
386
|
-
)
|
|
387
517
|
}
|
|
388
518
|
|
|
389
519
|
{
|
|
390
|
-
|
|
520
|
+
getLogSelectionsOrThrow: getLogSelectionsOrThrow,
|
|
391
521
|
}
|
|
392
522
|
}
|
|
393
523
|
|
|
@@ -886,6 +1016,10 @@ let make = (
|
|
|
886
1016
|
|
|
887
1017
|
let getSelectionConfig = memoGetSelectionConfig(~chain)
|
|
888
1018
|
|
|
1019
|
+
// Per-partition adaptive block interval (AIMD), keyed by partitionId. The
|
|
1020
|
+
// `max` key holds a source-wide ceiling that only ever tightens, set by
|
|
1021
|
+
// structural provider limits ("limited to N blocks"). A partition's own entry
|
|
1022
|
+
// can go stale when partitions merge/split — acceptable, it re-adapts.
|
|
889
1023
|
let mutSuggestedBlockIntervals = Dict.make()
|
|
890
1024
|
|
|
891
1025
|
let client = Rpc.makeClient(url, ~headers?)
|
|
@@ -1023,15 +1157,14 @@ let make = (
|
|
|
1023
1157
|
) => {
|
|
1024
1158
|
let startFetchingBatchTimeRef = Performance.now()
|
|
1025
1159
|
|
|
1026
|
-
let
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
| Some(maxSuggestedBlockInterval) => maxSuggestedBlockInterval
|
|
1030
|
-
| None =>
|
|
1160
|
+
let sourceMaxBlockInterval =
|
|
1161
|
+
mutSuggestedBlockIntervals->getSourceMaxBlockInterval(~intervalCeiling=syncConfig.intervalCeiling)
|
|
1162
|
+
let suggestedBlockInterval = Pervasives.min(
|
|
1031
1163
|
mutSuggestedBlockIntervals
|
|
1032
1164
|
->Utils.Dict.dangerouslyGetNonOption(partitionId)
|
|
1033
|
-
->Option.getOr(syncConfig.initialBlockInterval)
|
|
1034
|
-
|
|
1165
|
+
->Option.getOr(syncConfig.initialBlockInterval),
|
|
1166
|
+
sourceMaxBlockInterval,
|
|
1167
|
+
)
|
|
1035
1168
|
|
|
1036
1169
|
// Always have a toBlock for an RPC worker
|
|
1037
1170
|
let toBlock = switch toBlock {
|
|
@@ -1050,14 +1183,13 @@ let make = (
|
|
|
1050
1183
|
->Promise.thenResolve(json => Some(parseBlockInfo(json)))
|
|
1051
1184
|
: Promise.resolve(None)
|
|
1052
1185
|
|
|
1053
|
-
let {
|
|
1054
|
-
let
|
|
1186
|
+
let {getLogSelectionsOrThrow} = getSelectionConfig(selection)
|
|
1187
|
+
let logSelections = getLogSelectionsOrThrow(~addressesByContractName)
|
|
1055
1188
|
|
|
1056
1189
|
let {items, latestFetchedBlockInfo} = await getNextPage(
|
|
1057
1190
|
~fromBlock,
|
|
1058
1191
|
~toBlock=suggestedToBlock,
|
|
1059
|
-
~
|
|
1060
|
-
~topicQuery,
|
|
1192
|
+
~logSelections,
|
|
1061
1193
|
~loadBlock=blockNumber =>
|
|
1062
1194
|
blockLoader.contents
|
|
1063
1195
|
->LazyLoader.get(blockNumber)
|
|
@@ -1072,20 +1204,16 @@ let make = (
|
|
|
1072
1204
|
|
|
1073
1205
|
let executedBlockInterval = suggestedToBlock - fromBlock + 1
|
|
1074
1206
|
|
|
1075
|
-
//
|
|
1076
|
-
//
|
|
1077
|
-
//
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
!(mutSuggestedBlockIntervals->Dict.has(maxSuggestedBlockIntervalKey))
|
|
1081
|
-
) {
|
|
1082
|
-
// Increase batch size going forward, but do not increase past a configured maximum
|
|
1083
|
-
// See: https://en.wikipedia.org/wiki/Additive_increase/multiplicative_decrease
|
|
1207
|
+
// Grow this partition's interval only when the full suggested range was
|
|
1208
|
+
// actually applied (not clamped by a hard toBlock). The min clamps to the
|
|
1209
|
+
// source-wide ceiling, which also stops growth once a structural cap tightened it.
|
|
1210
|
+
// See: https://en.wikipedia.org/wiki/Additive_increase/multiplicative_decrease
|
|
1211
|
+
if executedBlockInterval >= suggestedBlockInterval {
|
|
1084
1212
|
mutSuggestedBlockIntervals->Dict.set(
|
|
1085
1213
|
partitionId,
|
|
1086
1214
|
Pervasives.min(
|
|
1087
1215
|
executedBlockInterval + syncConfig.accelerationAdditive,
|
|
1088
|
-
|
|
1216
|
+
sourceMaxBlockInterval,
|
|
1089
1217
|
),
|
|
1090
1218
|
)
|
|
1091
1219
|
}
|