envio 3.3.0-alpha.5 → 3.3.0-alpha.7
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/Address.res +5 -2
- package/src/Address.res.mjs +3 -1
- package/src/BatchProcessing.res +5 -0
- package/src/BatchProcessing.res.mjs +6 -0
- package/src/ChainFetching.res +14 -25
- package/src/ChainFetching.res.mjs +19 -20
- package/src/ChainState.res +4 -4
- package/src/ChainState.res.mjs +2 -2
- package/src/ChainState.resi +1 -1
- package/src/Config.res +44 -0
- package/src/Config.res.mjs +38 -0
- package/src/ContractRegisterContext.res +2 -12
- package/src/ContractRegisterContext.res.mjs +3 -5
- package/src/CrossChainState.res +5 -3
- package/src/CrossChainState.res.mjs +5 -4
- package/src/ExitOnCaughtUp.res +10 -2
- package/src/ExitOnCaughtUp.res.mjs +10 -1
- package/src/FetchState.res +49 -52
- package/src/FetchState.res.mjs +45 -47
- 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 +29 -43
- package/src/SimulateItems.res.mjs +16 -33
- 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/HyperFuelSource.res +1 -0
- package/src/sources/HyperFuelSource.res.mjs +1 -1
- package/src/sources/HyperSync.res +4 -0
- package/src/sources/HyperSync.res.mjs +5 -4
- package/src/sources/HyperSync.resi +1 -0
- package/src/sources/HyperSyncSource.res +2 -0
- package/src/sources/HyperSyncSource.res.mjs +2 -2
- package/src/sources/RpcSource.res +287 -146
- package/src/sources/RpcSource.res.mjs +286 -247
- package/src/sources/SimulateSource.res +2 -0
- package/src/sources/SimulateSource.res.mjs +3 -2
- package/src/sources/Source.res +10 -0
- package/src/sources/SourceManager.res +7 -0
- package/src/sources/SourceManager.res.mjs +2 -1
- package/src/sources/Svm.res +1 -0
- package/src/sources/Svm.res.mjs +1 -1
- package/src/sources/SvmHyperSyncSource.res +2 -0
- package/src/sources/SvmHyperSyncSource.res.mjs +4 -2
|
@@ -74,6 +74,55 @@ 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
|
+
// Provider-specific "too many logs" messages, matched loosely since each names
|
|
101
|
+
// it differently: HyperRPC ("More than 50000 logs returned"), ZkEVM ("query
|
|
102
|
+
// returned more than N results"), LlamaRPC ("query exceeds max results"),
|
|
103
|
+
// 1RPC ("response size should not..."), Optimism ("(backend) response too
|
|
104
|
+
// large"), Arbitrum ("logs matched by query exceeds limit"), Ankr ("block
|
|
105
|
+
// range is too wide"). Kept as one block rather than per-line comments — the
|
|
106
|
+
// ReScript formatter doesn't preserve comments attached to regex literals in
|
|
107
|
+
// an array.
|
|
108
|
+
let patterns = [
|
|
109
|
+
/more than \d+ logs/i,
|
|
110
|
+
/\d+ logs returned/i,
|
|
111
|
+
/too many logs/i,
|
|
112
|
+
/query returned more than \d+ results/i,
|
|
113
|
+
/query exceeds max results/i,
|
|
114
|
+
/response size should not/i,
|
|
115
|
+
/(backend )?response too large/i,
|
|
116
|
+
/logs matched by query exceeds limit/i,
|
|
117
|
+
/block range is too wide/i,
|
|
118
|
+
]
|
|
119
|
+
(exn: exn) =>
|
|
120
|
+
switch exn->getErrorMessage {
|
|
121
|
+
| Some(message) => patterns->Array.some(re => re->RegExp.test(message))
|
|
122
|
+
| None => false
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
77
126
|
let getSuggestedBlockIntervalFromExn = {
|
|
78
127
|
// Unknown provider: "retry with the range 123-456"
|
|
79
128
|
let suggestedRangeRegExp = /retry with the range (\d+)-(\d+)/
|
|
@@ -118,14 +167,6 @@ let getSuggestedBlockIntervalFromExn = {
|
|
|
118
167
|
// when we send request with numeric block range instead of hex
|
|
119
168
|
// Infura, ZkSync: "Try with this block range [0x123,0x456]"
|
|
120
169
|
|
|
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
170
|
let parseMessageForBlockRange = (message: string) => {
|
|
130
171
|
// Helper to extract block range from regex match
|
|
131
172
|
let extractBlockRange = (execResult, ~isMaxRange) =>
|
|
@@ -209,17 +250,9 @@ let getSuggestedBlockIntervalFromExn = {
|
|
|
209
250
|
// Whether it's the max range that the provider allows
|
|
210
251
|
bool,
|
|
211
252
|
)> =>
|
|
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
|
|
253
|
+
switch exn->getErrorMessage {
|
|
254
|
+
| Some(message) => parseMessageForBlockRange(message)
|
|
255
|
+
| None => None
|
|
223
256
|
}
|
|
224
257
|
}
|
|
225
258
|
|
|
@@ -230,11 +263,38 @@ type eventBatchQuery = {
|
|
|
230
263
|
|
|
231
264
|
let maxSuggestedBlockIntervalKey = "max"
|
|
232
265
|
|
|
266
|
+
let getSourceMaxBlockInterval = (mutSuggestedBlockIntervals, ~intervalCeiling) =>
|
|
267
|
+
mutSuggestedBlockIntervals
|
|
268
|
+
->Utils.Dict.dangerouslyGetNonOption(maxSuggestedBlockIntervalKey)
|
|
269
|
+
->Option.getOr(intervalCeiling)
|
|
270
|
+
|
|
271
|
+
type logSelection = {
|
|
272
|
+
addresses: option<array<Address.t>>,
|
|
273
|
+
topicQuery: Rpc.GetLogs.topicQuery,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// A log can satisfy more than one selection when a single event's `where` is an
|
|
277
|
+
// OR of param groups, so dedup the fanned-out responses by (blockNumber,
|
|
278
|
+
// logIndex) — unique per chain — keeping the first occurrence.
|
|
279
|
+
let mergeAndDedupItems = (itemsPerSelection: array<array<EvmRpcClient.rpcEventItem>>) => {
|
|
280
|
+
let seen = Utils.Set.make()
|
|
281
|
+
let merged = []
|
|
282
|
+
itemsPerSelection->Array.forEach(items =>
|
|
283
|
+
items->Array.forEach((item: EvmRpcClient.rpcEventItem) => {
|
|
284
|
+
let key = `${item.log.blockNumber->Int.toString}-${item.log.logIndex->Int.toString}`
|
|
285
|
+
if seen->Utils.Set.has(key)->not {
|
|
286
|
+
seen->Utils.Set.add(key)->ignore
|
|
287
|
+
merged->Array.push(item)->ignore
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
)
|
|
291
|
+
merged
|
|
292
|
+
}
|
|
293
|
+
|
|
233
294
|
let getNextPage = (
|
|
234
295
|
~fromBlock,
|
|
235
296
|
~toBlock,
|
|
236
|
-
~
|
|
237
|
-
~topicQuery: Rpc.GetLogs.topicQuery,
|
|
297
|
+
~logSelections: array<logSelection>,
|
|
238
298
|
~loadBlock,
|
|
239
299
|
~syncConfig as sc: Config.sourceSync,
|
|
240
300
|
~rpcClient: EvmRpcClient.t,
|
|
@@ -252,93 +312,111 @@ let getNextPage = (
|
|
|
252
312
|
)
|
|
253
313
|
|
|
254
314
|
let latestFetchedBlockPromise = loadBlock(toBlock)
|
|
255
|
-
|
|
256
|
-
let
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
315
|
+
|
|
316
|
+
let queryLogs = ({addresses, topicQuery}: logSelection) => {
|
|
317
|
+
Prometheus.SourceRequestCount.increment(~sourceName, ~chainId, ~method="eth_getLogs")
|
|
318
|
+
rpcClient.getLogs({
|
|
319
|
+
fromBlock,
|
|
320
|
+
toBlock,
|
|
321
|
+
?addresses,
|
|
322
|
+
topics: topicQuery->Array.map(filter =>
|
|
323
|
+
switch filter {
|
|
324
|
+
| Rpc.GetLogs.Null => Nullable.null
|
|
325
|
+
| Single(topic) => Nullable.make([topic])
|
|
326
|
+
| Multiple(topics) => Nullable.make(topics)
|
|
327
|
+
}
|
|
328
|
+
),
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let logsPromise = switch logSelections {
|
|
333
|
+
| [] =>
|
|
334
|
+
latestFetchedBlockPromise->Promise.thenResolve((latestFetchedBlockInfo): eventBatchQuery => {
|
|
335
|
+
items: [],
|
|
336
|
+
latestFetchedBlockInfo,
|
|
337
|
+
})
|
|
338
|
+
// Fast path: a single selection needs no cross-request merge or dedup.
|
|
339
|
+
| [logSelection] =>
|
|
340
|
+
logSelection
|
|
341
|
+
->queryLogs
|
|
342
|
+
->Promise.then(async items => {
|
|
343
|
+
{
|
|
344
|
+
items,
|
|
345
|
+
latestFetchedBlockInfo: await latestFetchedBlockPromise,
|
|
265
346
|
}
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
347
|
+
})
|
|
348
|
+
| _ =>
|
|
349
|
+
logSelections
|
|
350
|
+
->Array.map(queryLogs)
|
|
351
|
+
->Promise.all
|
|
352
|
+
->Promise.then(async itemsPerSelection => {
|
|
353
|
+
{
|
|
354
|
+
items: itemsPerSelection->mergeAndDedupItems,
|
|
355
|
+
latestFetchedBlockInfo: await latestFetchedBlockPromise,
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
}
|
|
273
359
|
|
|
274
360
|
[queryTimoutPromise, logsPromise]
|
|
275
361
|
->Promise.race
|
|
276
362
|
->Promise.catch(err => {
|
|
363
|
+
let executedBlockInterval = toBlock - fromBlock + 1
|
|
364
|
+
let shrunkBlockInterval = Pervasives.max(
|
|
365
|
+
1,
|
|
366
|
+
(executedBlockInterval->Int.toFloat *. sc.backoffMultiplicative)->Float.toInt,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
let throwFailedGettingItems = retry =>
|
|
370
|
+
throw(Source.GetItemsError(FailedGettingItems({exn: err, attemptedToBlock: toBlock, retry})))
|
|
371
|
+
let throwResize = interval =>
|
|
372
|
+
throwFailedGettingItems(WithSuggestedToBlock({toBlock: fromBlock + interval - 1}))
|
|
373
|
+
|
|
277
374
|
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
|
-
),
|
|
375
|
+
// "limited to N blocks" — a structural cap on the whole source; only tighten.
|
|
376
|
+
| Some((interval, true)) =>
|
|
377
|
+
let capped = Pervasives.min(
|
|
378
|
+
mutSuggestedBlockIntervals->getSourceMaxBlockInterval(~intervalCeiling=sc.intervalCeiling),
|
|
379
|
+
interval,
|
|
293
380
|
)
|
|
381
|
+
mutSuggestedBlockIntervals->Dict.set(maxSuggestedBlockIntervalKey, capped)
|
|
382
|
+
throwResize(capped)
|
|
383
|
+
// A one-off suggested range ("retry with range X-Y") — apply to this partition.
|
|
384
|
+
| Some((interval, false)) =>
|
|
385
|
+
mutSuggestedBlockIntervals->Dict.set(partitionId, interval)
|
|
386
|
+
throwResize(interval)
|
|
387
|
+
// Density cap with no suggested number (too many logs / response too large):
|
|
388
|
+
// shrink THIS partition and retry immediately (no wait); acceleration
|
|
389
|
+
// re-adapts on the next successful query. The interval>1 guard avoids a
|
|
390
|
+
// no-progress tight loop on a single over-cap block.
|
|
391
|
+
| None if executedBlockInterval > 1 && err->isResponseTooLargeError =>
|
|
392
|
+
mutSuggestedBlockIntervals->Dict.set(partitionId, shrunkBlockInterval)
|
|
393
|
+
throwResize(shrunkBlockInterval)
|
|
394
|
+
// Transient/unknown — shrink this partition and back off.
|
|
294
395
|
| 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
|
-
),
|
|
396
|
+
mutSuggestedBlockIntervals->Dict.set(partitionId, shrunkBlockInterval)
|
|
397
|
+
throwFailedGettingItems(
|
|
398
|
+
WithBackoff({
|
|
399
|
+
message: `Failed getting data for the block range. Will try smaller block range for the next attempt.`,
|
|
400
|
+
backoffMillis: sc.backoffMillis,
|
|
401
|
+
}),
|
|
310
402
|
)
|
|
311
403
|
}
|
|
312
404
|
})
|
|
313
405
|
}
|
|
314
406
|
|
|
315
|
-
type logSelection = {
|
|
316
|
-
addresses: option<array<Address.t>>,
|
|
317
|
-
topicQuery: Rpc.GetLogs.topicQuery,
|
|
318
|
-
}
|
|
319
|
-
|
|
320
407
|
type selectionConfig = {
|
|
321
|
-
|
|
408
|
+
getLogSelectionsOrThrow: (
|
|
409
|
+
~addressesByContractName: dict<array<Address.t>>,
|
|
410
|
+
) => array<logSelection>,
|
|
322
411
|
}
|
|
323
412
|
|
|
324
413
|
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
|
-
})
|
|
414
|
+
let evmEventConfigs =
|
|
415
|
+
selection.eventConfigs->(
|
|
416
|
+
Utils.magic: array<Internal.eventConfig> => array<Internal.evmEventConfig>
|
|
417
|
+
)
|
|
336
418
|
|
|
337
|
-
|
|
338
|
-
staticTopicSelections->LogSelection.compressTopicSelections,
|
|
339
|
-
dynamicEventFilters,
|
|
340
|
-
) {
|
|
341
|
-
| ([], []) =>
|
|
419
|
+
if evmEventConfigs->Utils.Array.isEmpty {
|
|
342
420
|
throw(
|
|
343
421
|
Source.GetItemsError(
|
|
344
422
|
UnsupportedSelection({
|
|
@@ -346,48 +424,110 @@ let getSelectionConfig = (selection: FetchState.selection, ~chain) => {
|
|
|
346
424
|
}),
|
|
347
425
|
),
|
|
348
426
|
)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// eth_getLogs takes one address list and one topic selection per request, so
|
|
430
|
+
// fan out to one request per selection. Each address-bound event is grouped by
|
|
431
|
+
// its contract and later scoped to that contract's own addresses — pooling all
|
|
432
|
+
// contracts' addresses would let one contract's query fetch a sibling's logs,
|
|
433
|
+
// which route back by address and bypass the sibling's filter (routing never
|
|
434
|
+
// re-applies it). Pure-wildcard events carry no address constraint, so they're
|
|
435
|
+
// pooled and resolved once.
|
|
436
|
+
let noAddressTopicSelections = []
|
|
437
|
+
let staticByContract = Dict.make()
|
|
438
|
+
let dynamicByContract = Dict.make()
|
|
439
|
+
let dynamicWildcardByContract = Dict.make()
|
|
440
|
+
let contractNames = Utils.Set.make()
|
|
441
|
+
|
|
442
|
+
evmEventConfigs->Array.forEach(({
|
|
443
|
+
contractName,
|
|
444
|
+
isWildcard,
|
|
445
|
+
dependsOnAddresses,
|
|
446
|
+
getEventFiltersOrThrow,
|
|
447
|
+
}) => {
|
|
448
|
+
let eventFilters = getEventFiltersOrThrow(chain)
|
|
449
|
+
if dependsOnAddresses {
|
|
450
|
+
contractNames->Utils.Set.add(contractName)->ignore
|
|
451
|
+
switch eventFilters {
|
|
452
|
+
| Internal.Static(topicSelections) =>
|
|
453
|
+
staticByContract->Utils.Dict.pushMany(contractName, topicSelections)
|
|
454
|
+
| Dynamic(fn) =>
|
|
455
|
+
(isWildcard ? dynamicWildcardByContract : dynamicByContract)->Utils.Dict.push(
|
|
456
|
+
contractName,
|
|
457
|
+
fn,
|
|
458
|
+
)
|
|
357
459
|
}
|
|
460
|
+
} else {
|
|
461
|
+
noAddressTopicSelections
|
|
462
|
+
->Array.pushMany(
|
|
463
|
+
switch eventFilters {
|
|
464
|
+
| Static(s) => s
|
|
465
|
+
| Dynamic(fn) => fn([])
|
|
466
|
+
},
|
|
467
|
+
)
|
|
468
|
+
->ignore
|
|
358
469
|
}
|
|
359
|
-
|
|
360
|
-
let eventConfig = selection.eventConfigs->Utils.Array.firstUnsafe
|
|
470
|
+
})
|
|
361
471
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
472
|
+
// `compressTopicSelections` folds the filter-less events into a single topic0
|
|
473
|
+
// OR-set, keeping the common case at one request.
|
|
474
|
+
let toLogSelections = (~addresses, topicSelections): array<logSelection> =>
|
|
475
|
+
topicSelections
|
|
476
|
+
->LogSelection.compressTopicSelections
|
|
477
|
+
->Array.map(topicSelection => {
|
|
478
|
+
addresses,
|
|
479
|
+
topicQuery: topicSelection->Rpc.GetLogs.mapTopicQuery,
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
// Address-independent, so resolve once (the wildcard partition reuses this).
|
|
483
|
+
let noAddressLogSelections = toLogSelections(~addresses=None, noAddressTopicSelections)
|
|
484
|
+
|
|
485
|
+
let getLogSelectionsOrThrow = if contractNames->Utils.Set.size === 0 {
|
|
486
|
+
(~addressesByContractName as _) => noAddressLogSelections
|
|
487
|
+
} else {
|
|
488
|
+
(~addressesByContractName): array<logSelection> => {
|
|
489
|
+
let logSelections = noAddressLogSelections->Array.copy
|
|
490
|
+
contractNames->Utils.Set.forEach(contractName => {
|
|
491
|
+
switch addressesByContractName->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
492
|
+
| None
|
|
493
|
+
| Some([]) => ()
|
|
494
|
+
| Some(addresses) =>
|
|
495
|
+
// Static + dynamic non-wildcard filters, scoped to this contract's addresses.
|
|
496
|
+
let addressedTopicSelections = []
|
|
497
|
+
switch staticByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
498
|
+
| Some(s) => addressedTopicSelections->Array.pushMany(s)->ignore
|
|
499
|
+
| None => ()
|
|
500
|
+
}
|
|
501
|
+
switch dynamicByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
502
|
+
| Some(fns) =>
|
|
503
|
+
fns->Array.forEach(fn =>
|
|
504
|
+
addressedTopicSelections->Array.pushMany(fn(addresses))->ignore
|
|
505
|
+
)
|
|
506
|
+
| None => ()
|
|
507
|
+
}
|
|
508
|
+
logSelections
|
|
509
|
+
->Array.pushMany(toLogSelections(~addresses=Some(addresses), addressedTopicSelections))
|
|
510
|
+
->ignore
|
|
511
|
+
|
|
512
|
+
// Dynamic wildcard-by-address filters fold the address into the topics,
|
|
513
|
+
// so they still match any address.
|
|
514
|
+
switch dynamicWildcardByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
515
|
+
| Some(fns) =>
|
|
516
|
+
logSelections
|
|
517
|
+
->Array.pushMany(
|
|
518
|
+
toLogSelections(~addresses=None, fns->Array.flatMap(fn => fn(addresses))),
|
|
519
|
+
)
|
|
520
|
+
->ignore
|
|
521
|
+
| None => ()
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
logSelections
|
|
378
526
|
}
|
|
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
527
|
}
|
|
388
528
|
|
|
389
529
|
{
|
|
390
|
-
|
|
530
|
+
getLogSelectionsOrThrow: getLogSelectionsOrThrow,
|
|
391
531
|
}
|
|
392
532
|
}
|
|
393
533
|
|
|
@@ -886,6 +1026,10 @@ let make = (
|
|
|
886
1026
|
|
|
887
1027
|
let getSelectionConfig = memoGetSelectionConfig(~chain)
|
|
888
1028
|
|
|
1029
|
+
// Per-partition adaptive block interval (AIMD), keyed by partitionId. The
|
|
1030
|
+
// `max` key holds a source-wide ceiling that only ever tightens, set by
|
|
1031
|
+
// structural provider limits ("limited to N blocks"). A partition's own entry
|
|
1032
|
+
// can go stale when partitions merge/split — acceptable, it re-adapts.
|
|
889
1033
|
let mutSuggestedBlockIntervals = Dict.make()
|
|
890
1034
|
|
|
891
1035
|
let client = Rpc.makeClient(url, ~headers?)
|
|
@@ -1018,20 +1162,22 @@ let make = (
|
|
|
1018
1162
|
~knownHeight,
|
|
1019
1163
|
~partitionId,
|
|
1020
1164
|
~selection: FetchState.selection,
|
|
1165
|
+
~itemsTarget as _,
|
|
1021
1166
|
~retry,
|
|
1022
1167
|
~logger as _,
|
|
1023
1168
|
) => {
|
|
1024
1169
|
let startFetchingBatchTimeRef = Performance.now()
|
|
1025
1170
|
|
|
1026
|
-
let
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1171
|
+
let sourceMaxBlockInterval =
|
|
1172
|
+
mutSuggestedBlockIntervals->getSourceMaxBlockInterval(
|
|
1173
|
+
~intervalCeiling=syncConfig.intervalCeiling,
|
|
1174
|
+
)
|
|
1175
|
+
let suggestedBlockInterval = Pervasives.min(
|
|
1031
1176
|
mutSuggestedBlockIntervals
|
|
1032
1177
|
->Utils.Dict.dangerouslyGetNonOption(partitionId)
|
|
1033
|
-
->Option.getOr(syncConfig.initialBlockInterval)
|
|
1034
|
-
|
|
1178
|
+
->Option.getOr(syncConfig.initialBlockInterval),
|
|
1179
|
+
sourceMaxBlockInterval,
|
|
1180
|
+
)
|
|
1035
1181
|
|
|
1036
1182
|
// Always have a toBlock for an RPC worker
|
|
1037
1183
|
let toBlock = switch toBlock {
|
|
@@ -1050,14 +1196,13 @@ let make = (
|
|
|
1050
1196
|
->Promise.thenResolve(json => Some(parseBlockInfo(json)))
|
|
1051
1197
|
: Promise.resolve(None)
|
|
1052
1198
|
|
|
1053
|
-
let {
|
|
1054
|
-
let
|
|
1199
|
+
let {getLogSelectionsOrThrow} = getSelectionConfig(selection)
|
|
1200
|
+
let logSelections = getLogSelectionsOrThrow(~addressesByContractName)
|
|
1055
1201
|
|
|
1056
1202
|
let {items, latestFetchedBlockInfo} = await getNextPage(
|
|
1057
1203
|
~fromBlock,
|
|
1058
1204
|
~toBlock=suggestedToBlock,
|
|
1059
|
-
~
|
|
1060
|
-
~topicQuery,
|
|
1205
|
+
~logSelections,
|
|
1061
1206
|
~loadBlock=blockNumber =>
|
|
1062
1207
|
blockLoader.contents
|
|
1063
1208
|
->LazyLoader.get(blockNumber)
|
|
@@ -1072,20 +1217,16 @@ let make = (
|
|
|
1072
1217
|
|
|
1073
1218
|
let executedBlockInterval = suggestedToBlock - fromBlock + 1
|
|
1074
1219
|
|
|
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
|
|
1220
|
+
// Grow this partition's interval only when the full suggested range was
|
|
1221
|
+
// actually applied (not clamped by a hard toBlock). The min clamps to the
|
|
1222
|
+
// source-wide ceiling, which also stops growth once a structural cap tightened it.
|
|
1223
|
+
// See: https://en.wikipedia.org/wiki/Additive_increase/multiplicative_decrease
|
|
1224
|
+
if executedBlockInterval >= suggestedBlockInterval {
|
|
1084
1225
|
mutSuggestedBlockIntervals->Dict.set(
|
|
1085
1226
|
partitionId,
|
|
1086
1227
|
Pervasives.min(
|
|
1087
1228
|
executedBlockInterval + syncConfig.accelerationAdditive,
|
|
1088
|
-
|
|
1229
|
+
sourceMaxBlockInterval,
|
|
1089
1230
|
),
|
|
1090
1231
|
)
|
|
1091
1232
|
}
|