envio 3.3.0-alpha.6 → 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/Address.res +5 -2
- package/src/Address.res.mjs +3 -1
- package/src/Api.res +1 -1
- package/src/Api.res.mjs +1 -1
- package/src/ChainFetching.res +34 -34
- package/src/ChainFetching.res.mjs +29 -29
- package/src/ChainState.res +328 -165
- package/src/ChainState.res.mjs +205 -100
- package/src/ChainState.resi +16 -7
- package/src/Config.res +55 -27
- package/src/Config.res.mjs +46 -7
- package/src/ContractRegisterContext.res +2 -12
- package/src/ContractRegisterContext.res.mjs +3 -5
- package/src/Core.res +7 -0
- package/src/CrossChainState.res +54 -79
- package/src/CrossChainState.res.mjs +45 -60
- 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 +383 -182
- package/src/FetchState.res.mjs +260 -264
- 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 +26 -8
- package/src/SimulateItems.res.mjs +11 -14
- 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 +32 -40
- package/src/sources/HyperFuelSource.res.mjs +53 -45
- package/src/sources/HyperSync.res +35 -9
- package/src/sources/HyperSync.res.mjs +52 -35
- package/src/sources/HyperSync.resi +11 -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 +39 -58
- package/src/sources/HyperSyncSource.res.mjs +44 -44
- package/src/sources/RpcSource.res +114 -92
- package/src/sources/RpcSource.res.mjs +77 -46
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +6 -3
- package/src/sources/SimulateSource.res.mjs +13 -5
- package/src/sources/Source.res +25 -5
- package/src/sources/SourceManager.res +66 -3
- package/src/sources/SourceManager.res.mjs +55 -7
- package/src/sources/SourceManager.resi +10 -0
- package/src/sources/Svm.res +15 -10
- package/src/sources/Svm.res.mjs +24 -7
- package/src/sources/SvmHyperSyncClient.res +5 -6
- package/src/sources/SvmHyperSyncSource.res +84 -41
- package/src/sources/SvmHyperSyncSource.res.mjs +90 -43
- package/src/sources/TransactionStore.res +6 -107
- package/src/sources/TransactionStore.res.mjs +5 -86
- package/svm.schema.json +19 -0
package/src/FetchState.res
CHANGED
|
@@ -7,15 +7,19 @@ type blockNumberAndTimestamp = {
|
|
|
7
7
|
|
|
8
8
|
type blockNumberAndLogIndex = {blockNumber: int, logIndex: int}
|
|
9
9
|
|
|
10
|
-
type selection = {
|
|
10
|
+
type selection = {
|
|
11
|
+
onEventRegistrations: array<Internal.onEventRegistration>,
|
|
12
|
+
dependsOnAddresses: bool,
|
|
13
|
+
}
|
|
11
14
|
|
|
12
15
|
type pendingQuery = {
|
|
13
16
|
fromBlock: int,
|
|
14
17
|
toBlock: option<int>,
|
|
15
18
|
isChunk: bool,
|
|
16
|
-
//
|
|
17
|
-
// shared buffer budget can account for what's already
|
|
18
|
-
|
|
19
|
+
// Items this in-flight query is targeting (server maxNumLogs-style cap), carried
|
|
20
|
+
// from the query so the shared buffer budget can account for what's already
|
|
21
|
+
// being fetched.
|
|
22
|
+
itemsTarget: float,
|
|
19
23
|
// Stores latestFetchedBlock when query completes. Only needed to persist
|
|
20
24
|
// timestamp while earlier queries are still pending before updating
|
|
21
25
|
// the partition's latestFetchedBlock.
|
|
@@ -59,15 +63,12 @@ type query = {
|
|
|
59
63
|
fromBlock: int,
|
|
60
64
|
toBlock: option<int>,
|
|
61
65
|
isChunk: bool,
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
// (furthest-behind first) without allocating a side tuple per query.
|
|
69
|
-
mutable chainId: int,
|
|
70
|
-
mutable progress: float,
|
|
66
|
+
// Items this query targets: the server-side maxNumLogs-style cap, and the
|
|
67
|
+
// unit the chain's per-tick budget is reserved/consumed in. For a partition
|
|
68
|
+
// with known density this is density × the query's block range; for a
|
|
69
|
+
// partition with no signal yet it's whatever budget share the query was
|
|
70
|
+
// sized against.
|
|
71
|
+
itemsTarget: float,
|
|
71
72
|
selection: selection,
|
|
72
73
|
addressesByContractName: dict<array<Address.t>>,
|
|
73
74
|
}
|
|
@@ -90,24 +91,28 @@ let deriveContractNameByAddress: dict<array<Address.t>> => dict<
|
|
|
90
91
|
result
|
|
91
92
|
})
|
|
92
93
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
94
|
+
// Floor for a query's itemsTarget. Also used by SourceManager as the floor for
|
|
95
|
+
// its own maxNumLogs-style safety net.
|
|
96
|
+
let minItemsTarget = 2_000.
|
|
97
|
+
|
|
98
|
+
// Ceiling for an unknown-density probe query's itemsTarget. A partition with no
|
|
99
|
+
// trusted density yet gets one open-ended probe; without a cap it would claim its
|
|
100
|
+
// full even share of the chain's (possibly whole-pool) budget and starve sibling
|
|
101
|
+
// chains needing their own first probe in the same cross-chain tick.
|
|
102
|
+
let maxItemsTarget = 10_000.
|
|
103
|
+
|
|
104
|
+
// itemsTarget for a query over [fromBlock, toBlock], from the partition's event
|
|
105
|
+
// density (items/block derived from its last response). toBlock None is the
|
|
106
|
+
// open-ended tail, capped at chainTargetBlock — the soft per-tick horizon the
|
|
107
|
+
// owning chain wants to reach (see getNextQuery). A partition that responded
|
|
108
|
+
// with no items has density 0, so its queries cost 0 — correct, they don't
|
|
109
|
+
// fill the buffer. Only called for partitions with a known density
|
|
110
|
+
// (prevQueryRange > 0); partitions with no signal yet are sized instead
|
|
111
|
+
// against their even split of the chain's range budget.
|
|
112
|
+
let densityItemsTarget = (p: partition, ~fromBlock, ~toBlock, ~chainTargetBlock) => {
|
|
113
|
+
let density = p.prevRangeSize->Int.toFloat /. p.prevQueryRange->Int.toFloat
|
|
114
|
+
(toBlock->Option.getOr(chainTargetBlock) - fromBlock + 1)->Int.toFloat *. density
|
|
115
|
+
}
|
|
111
116
|
|
|
112
117
|
// Calculate the chunk range from history using min-of-last-3-ranges heuristic
|
|
113
118
|
let getMinHistoryRange = (p: partition) => {
|
|
@@ -545,10 +550,10 @@ type t = {
|
|
|
545
550
|
// Buffer of items ordered from earliest to latest
|
|
546
551
|
buffer: array<Internal.item>,
|
|
547
552
|
// Caps how far ahead onBlock items are pre-generated (set to 2x the batch
|
|
548
|
-
// size).
|
|
549
|
-
// chain
|
|
553
|
+
// size). Event fetch depth is bounded separately, by CrossChainState's
|
|
554
|
+
// cross-chain admission against the indexer-wide buffer pool.
|
|
550
555
|
maxOnBlockBufferSize: int,
|
|
551
|
-
|
|
556
|
+
onBlockRegistrations: array<Internal.onBlockRegistration>,
|
|
552
557
|
knownHeight: int,
|
|
553
558
|
firstEventBlock: option<int>,
|
|
554
559
|
}
|
|
@@ -624,7 +629,7 @@ let blockItemLogIndex = 16777216
|
|
|
624
629
|
// are generated at once to prevent OOM.
|
|
625
630
|
let appendOnBlockItems = (
|
|
626
631
|
~mutItems: array<Internal.item>,
|
|
627
|
-
~
|
|
632
|
+
~onBlockRegistrations: array<Internal.onBlockRegistration>,
|
|
628
633
|
~indexerStartBlock,
|
|
629
634
|
~fromBlock,
|
|
630
635
|
~maxBlockNumber,
|
|
@@ -644,27 +649,27 @@ let appendOnBlockItems = (
|
|
|
644
649
|
let blockNumber = latestOnBlockBlockNumber.contents + 1
|
|
645
650
|
latestOnBlockBlockNumber := blockNumber
|
|
646
651
|
|
|
647
|
-
for configIdx in 0 to
|
|
648
|
-
let
|
|
652
|
+
for configIdx in 0 to onBlockRegistrations->Array.length - 1 {
|
|
653
|
+
let onBlockRegistration = onBlockRegistrations->Array.getUnsafe(configIdx)
|
|
649
654
|
|
|
650
|
-
let handlerStartBlock = switch
|
|
655
|
+
let handlerStartBlock = switch onBlockRegistration.startBlock {
|
|
651
656
|
| Some(startBlock) => startBlock
|
|
652
657
|
| None => indexerStartBlock
|
|
653
658
|
}
|
|
654
659
|
|
|
655
660
|
if (
|
|
656
661
|
blockNumber >= handlerStartBlock &&
|
|
657
|
-
switch
|
|
662
|
+
switch onBlockRegistration.endBlock {
|
|
658
663
|
| Some(endBlock) => blockNumber <= endBlock
|
|
659
664
|
| None => true
|
|
660
665
|
} &&
|
|
661
|
-
(blockNumber - handlerStartBlock)->Pervasives.mod(
|
|
666
|
+
(blockNumber - handlerStartBlock)->Pervasives.mod(onBlockRegistration.interval) === 0
|
|
662
667
|
) {
|
|
663
668
|
mutItems->Array.push(
|
|
664
669
|
Block({
|
|
665
|
-
|
|
670
|
+
onBlockRegistration,
|
|
666
671
|
blockNumber,
|
|
667
|
-
logIndex: blockItemLogIndex +
|
|
672
|
+
logIndex: blockItemLogIndex + onBlockRegistration.index,
|
|
668
673
|
}),
|
|
669
674
|
)
|
|
670
675
|
newItemsCounter := newItemsCounter.contents + 1
|
|
@@ -688,9 +693,9 @@ let updateInternal = (
|
|
|
688
693
|
): t => {
|
|
689
694
|
let mutItemsRef = ref(mutItems)
|
|
690
695
|
|
|
691
|
-
let latestOnBlockBlockNumber = switch fetchState.
|
|
696
|
+
let latestOnBlockBlockNumber = switch fetchState.onBlockRegistrations {
|
|
692
697
|
| [] => knownHeight
|
|
693
|
-
|
|
|
698
|
+
| onBlockRegistrations => {
|
|
694
699
|
// Calculate the max block number we are going to create items for
|
|
695
700
|
// Use maxOnBlockBufferSize to get the last target item in the buffer
|
|
696
701
|
//
|
|
@@ -718,7 +723,7 @@ let updateInternal = (
|
|
|
718
723
|
|
|
719
724
|
appendOnBlockItems(
|
|
720
725
|
~mutItems,
|
|
721
|
-
~
|
|
726
|
+
~onBlockRegistrations,
|
|
722
727
|
~indexerStartBlock=fetchState.startBlock,
|
|
723
728
|
~fromBlock=fetchState.latestOnBlockBlockNumber,
|
|
724
729
|
~maxBlockNumber,
|
|
@@ -733,7 +738,7 @@ let updateInternal = (
|
|
|
733
738
|
contractConfigs: fetchState.contractConfigs,
|
|
734
739
|
normalSelection: fetchState.normalSelection,
|
|
735
740
|
chainId: fetchState.chainId,
|
|
736
|
-
|
|
741
|
+
onBlockRegistrations: fetchState.onBlockRegistrations,
|
|
737
742
|
maxOnBlockBufferSize: fetchState.maxOnBlockBufferSize,
|
|
738
743
|
optimizedPartitions,
|
|
739
744
|
latestOnBlockBlockNumber,
|
|
@@ -999,7 +1004,7 @@ let registerDynamicContracts = (
|
|
|
999
1004
|
// Might contain duplicates which we should filter out
|
|
1000
1005
|
items: array<Internal.item>,
|
|
1001
1006
|
) => {
|
|
1002
|
-
if fetchState.normalSelection.
|
|
1007
|
+
if fetchState.normalSelection.onEventRegistrations->Utils.Array.isEmpty {
|
|
1003
1008
|
// Can the normalSelection be empty?
|
|
1004
1009
|
JsError.throwWithMessage(
|
|
1005
1010
|
"Invalid configuration. No events to fetch for the dynamic contract registration.",
|
|
@@ -1283,8 +1288,8 @@ let handleQueryResult = (
|
|
|
1283
1288
|
// param-level analogue of EventRouter's srcAddress effectiveStartBlock check.
|
|
1284
1289
|
let newItems = newItems->Array.filter(item =>
|
|
1285
1290
|
switch item {
|
|
1286
|
-
| Internal.Event({
|
|
1287
|
-
switch
|
|
1291
|
+
| Internal.Event({onEventRegistration, payload, blockNumber}) =>
|
|
1292
|
+
switch onEventRegistration.clientAddressFilter {
|
|
1288
1293
|
| Some(filter) =>
|
|
1289
1294
|
filter(payload, blockNumber, indexingAddresses->IndexingAddresses.rawForFilter)
|
|
1290
1295
|
| None => true
|
|
@@ -1322,7 +1327,7 @@ let startFetchingQueries = ({optimizedPartitions}: t, ~queries: array<query>) =>
|
|
|
1322
1327
|
fromBlock: q.fromBlock,
|
|
1323
1328
|
toBlock: q.toBlock,
|
|
1324
1329
|
isChunk: q.isChunk,
|
|
1325
|
-
|
|
1330
|
+
itemsTarget: q.itemsTarget,
|
|
1326
1331
|
fetchedBlock: None,
|
|
1327
1332
|
}
|
|
1328
1333
|
|
|
@@ -1346,45 +1351,50 @@ let startFetchingQueries = ({optimizedPartitions}: t, ~queries: array<query>) =>
|
|
|
1346
1351
|
// Most parallel in-flight chunk queries a single partition may have at once.
|
|
1347
1352
|
let maxPendingChunksPerPartition = 10
|
|
1348
1353
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1354
|
+
// Fills a gap range (a hole left between completed/pending chunks, e.g. from an
|
|
1355
|
+
// out-of-order partial response) unconditionally against the partition's real
|
|
1356
|
+
// density — gaps are already-committed range, not subject to this tick's
|
|
1357
|
+
// water-fill budget. Returns the created queries' total itemsTarget.
|
|
1358
|
+
let pushGapFillQueries = (
|
|
1351
1359
|
queries: array<query>,
|
|
1352
1360
|
~partitionId: string,
|
|
1353
1361
|
~rangeFromBlock: int,
|
|
1354
1362
|
~rangeEndBlock: option<int>,
|
|
1355
|
-
~
|
|
1363
|
+
~knownHeight: int,
|
|
1364
|
+
~chainTargetBlock: int,
|
|
1356
1365
|
~maybeChunkRange: option<int>,
|
|
1357
1366
|
~maxChunks: int,
|
|
1358
1367
|
~partition: partition,
|
|
1359
1368
|
~selection: selection,
|
|
1360
1369
|
~addressesByContractName: dict<array<Address.t>>,
|
|
1361
1370
|
) => {
|
|
1362
|
-
|
|
1371
|
+
let cost = ref(0.)
|
|
1372
|
+
if rangeFromBlock <= knownHeight && maxChunks > 0 {
|
|
1363
1373
|
switch rangeEndBlock {
|
|
1364
1374
|
| Some(endBlock) if rangeFromBlock > endBlock => ()
|
|
1365
1375
|
| _ =>
|
|
1366
1376
|
switch maybeChunkRange {
|
|
1367
1377
|
| None =>
|
|
1378
|
+
let itemsTarget = densityItemsTarget(
|
|
1379
|
+
partition,
|
|
1380
|
+
~fromBlock=rangeFromBlock,
|
|
1381
|
+
~toBlock=rangeEndBlock,
|
|
1382
|
+
~chainTargetBlock,
|
|
1383
|
+
)
|
|
1368
1384
|
queries->Array.push({
|
|
1369
1385
|
partitionId,
|
|
1370
1386
|
fromBlock: rangeFromBlock,
|
|
1371
1387
|
toBlock: rangeEndBlock,
|
|
1372
1388
|
selection,
|
|
1373
1389
|
isChunk: false,
|
|
1374
|
-
|
|
1375
|
-
partition,
|
|
1376
|
-
~fromBlock=rangeFromBlock,
|
|
1377
|
-
~toBlock=rangeEndBlock,
|
|
1378
|
-
~maxQueryBlockNumber,
|
|
1379
|
-
),
|
|
1380
|
-
chainId: 0,
|
|
1381
|
-
progress: 0.,
|
|
1390
|
+
itemsTarget,
|
|
1382
1391
|
addressesByContractName,
|
|
1383
1392
|
})
|
|
1393
|
+
cost := cost.contents +. itemsTarget
|
|
1384
1394
|
| Some(chunkRange) =>
|
|
1385
1395
|
let maxBlock = switch rangeEndBlock {
|
|
1386
1396
|
| Some(eb) => eb
|
|
1387
|
-
| None =>
|
|
1397
|
+
| None => chainTargetBlock
|
|
1388
1398
|
}
|
|
1389
1399
|
let chunkSize = Js.Math.ceil_int(chunkRange->Int.toFloat *. 1.8)
|
|
1390
1400
|
if rangeFromBlock + chunkSize * 2 - 1 <= maxBlock {
|
|
@@ -1394,102 +1404,119 @@ let pushQueriesForRange = (
|
|
|
1394
1404
|
chunkIdx.contents < maxChunks && chunkFromBlock.contents + chunkSize - 1 <= maxBlock
|
|
1395
1405
|
) {
|
|
1396
1406
|
let chunkToBlock = chunkFromBlock.contents + chunkSize - 1
|
|
1407
|
+
let itemsTarget = densityItemsTarget(
|
|
1408
|
+
partition,
|
|
1409
|
+
~fromBlock=chunkFromBlock.contents,
|
|
1410
|
+
~toBlock=Some(chunkToBlock),
|
|
1411
|
+
~chainTargetBlock,
|
|
1412
|
+
)
|
|
1397
1413
|
queries->Array.push({
|
|
1398
1414
|
partitionId,
|
|
1399
1415
|
fromBlock: chunkFromBlock.contents,
|
|
1400
1416
|
toBlock: Some(chunkToBlock),
|
|
1401
1417
|
isChunk: true,
|
|
1402
1418
|
selection,
|
|
1403
|
-
|
|
1404
|
-
partition,
|
|
1405
|
-
~fromBlock=chunkFromBlock.contents,
|
|
1406
|
-
~toBlock=Some(chunkToBlock),
|
|
1407
|
-
~maxQueryBlockNumber,
|
|
1408
|
-
),
|
|
1409
|
-
chainId: 0,
|
|
1410
|
-
progress: 0.,
|
|
1419
|
+
itemsTarget,
|
|
1411
1420
|
addressesByContractName,
|
|
1412
1421
|
})
|
|
1422
|
+
cost := cost.contents +. itemsTarget
|
|
1413
1423
|
chunkFromBlock := chunkToBlock + 1
|
|
1414
1424
|
chunkIdx := chunkIdx.contents + 1
|
|
1415
1425
|
}
|
|
1416
1426
|
} else {
|
|
1417
1427
|
// Not enough room for 2 chunks, fall back to a single query
|
|
1428
|
+
let itemsTarget = densityItemsTarget(
|
|
1429
|
+
partition,
|
|
1430
|
+
~fromBlock=rangeFromBlock,
|
|
1431
|
+
~toBlock=rangeEndBlock,
|
|
1432
|
+
~chainTargetBlock,
|
|
1433
|
+
)
|
|
1418
1434
|
queries->Array.push({
|
|
1419
1435
|
partitionId,
|
|
1420
1436
|
fromBlock: rangeFromBlock,
|
|
1421
1437
|
toBlock: rangeEndBlock,
|
|
1422
1438
|
selection,
|
|
1423
1439
|
isChunk: rangeEndBlock !== None,
|
|
1424
|
-
|
|
1425
|
-
partition,
|
|
1426
|
-
~fromBlock=rangeFromBlock,
|
|
1427
|
-
~toBlock=rangeEndBlock,
|
|
1428
|
-
~maxQueryBlockNumber,
|
|
1429
|
-
),
|
|
1430
|
-
chainId: 0,
|
|
1431
|
-
progress: 0.,
|
|
1440
|
+
itemsTarget,
|
|
1432
1441
|
addressesByContractName,
|
|
1433
1442
|
})
|
|
1443
|
+
cost := cost.contents +. itemsTarget
|
|
1434
1444
|
}
|
|
1435
1445
|
}
|
|
1436
1446
|
}
|
|
1437
1447
|
}
|
|
1448
|
+
cost.contents
|
|
1438
1449
|
}
|
|
1439
1450
|
|
|
1451
|
+
// Per-partition mutable state threaded through the water-fill rounds below.
|
|
1452
|
+
type waterFillState = {
|
|
1453
|
+
partitionId: string,
|
|
1454
|
+
p: partition,
|
|
1455
|
+
mutable cursor: int,
|
|
1456
|
+
mutable chunksUsedThisCall: int,
|
|
1457
|
+
// Existing mutPendingQueries count before this call — fixed for the call,
|
|
1458
|
+
// used with chunksUsedThisCall against maxPendingChunksPerPartition.
|
|
1459
|
+
pendingCount: int,
|
|
1460
|
+
queryEndBlock: option<int>,
|
|
1461
|
+
maybeChunkRange: option<int>,
|
|
1462
|
+
// This partition's own slot in queriesByPartitionIndex — gap-fill and every
|
|
1463
|
+
// water-fill round push here directly, so query order falls out of
|
|
1464
|
+
// idsInAscOrder for free (see getNextQuery) instead of a final sort pass.
|
|
1465
|
+
bucket: array<query>,
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// Candidate queries are sized against chainTargetBlock, the soft querying
|
|
1469
|
+
// horizon the owning chain wants to reach this tick — derived by ChainState
|
|
1470
|
+
// from its share of the indexer-wide buffer budget and its chain-level event
|
|
1471
|
+
// density. chainTargetBlock is never used as a hard query end, only to (a)
|
|
1472
|
+
// select which partitions are "in range" this tick and (b) size an open-ended
|
|
1473
|
+
// query with no other ceiling: the true hard bounds stay
|
|
1474
|
+
// endBlock/mergeBlock/the lagged head.
|
|
1475
|
+
//
|
|
1476
|
+
// In-range partitions evenly split chainTargetItems (this chain's target
|
|
1477
|
+
// total footprint — in-flight plus new). A partition already holding more
|
|
1478
|
+
// than its even share (from earlier ticks' in-flight queries) is skipped this
|
|
1479
|
+
// round so its share flows to the others; the split is recomputed every round
|
|
1480
|
+
// against the shrinking set of partitions still with range left, so leftover
|
|
1481
|
+
// keeps redistributing until the budget or the range runs out.
|
|
1482
|
+
//
|
|
1483
|
+
// A partition with a trusted density (two or more responses — see
|
|
1484
|
+
// getMinHistoryRange) always emits at least one full-size chunk/query once
|
|
1485
|
+
// included in a round, sized by its real density — uncapped, may overshoot
|
|
1486
|
+
// that round's share (the server also enforces itemsTarget via a
|
|
1487
|
+
// maxNumLogs-style cap, so an overshoot truncates the response rather than
|
|
1488
|
+
// the buffer). A partition with no trusted density yet (zero or one
|
|
1489
|
+
// response — a single sample is too noisy to size by) emits one query sized
|
|
1490
|
+
// exactly to its round's share instead.
|
|
1440
1491
|
let getNextQuery = (
|
|
1441
|
-
{
|
|
1442
|
-
~
|
|
1443
|
-
~
|
|
1492
|
+
{optimizedPartitions, blockLag, latestOnBlockBlockNumber, knownHeight, endBlock}: t,
|
|
1493
|
+
~chainTargetBlock: int,
|
|
1494
|
+
~chainTargetItems: float,
|
|
1444
1495
|
) => {
|
|
1445
1496
|
let headBlockNumber = knownHeight - blockLag
|
|
1446
1497
|
if headBlockNumber <= 0 {
|
|
1447
1498
|
WaitingForNewBlock
|
|
1448
|
-
} else if budget <= 0 {
|
|
1449
|
-
// No room left in the shared buffer pool for this chain; wait for processing
|
|
1450
|
-
// to drain before fetching more.
|
|
1451
|
-
NothingToQuery
|
|
1452
1499
|
} else {
|
|
1453
1500
|
let isOnBlockBehindTheHead = latestOnBlockBlockNumber < headBlockNumber
|
|
1454
1501
|
let shouldWaitForNewBlock = ref(
|
|
1455
|
-
switch
|
|
1502
|
+
switch endBlock {
|
|
1456
1503
|
| Some(endBlock) => headBlockNumber < endBlock
|
|
1457
1504
|
| None => true
|
|
1458
1505
|
} &&
|
|
1459
1506
|
!isOnBlockBehindTheHead,
|
|
1460
1507
|
)
|
|
1461
1508
|
|
|
1462
|
-
// Fetch at most `budget` items past the ready frontier (plus what's already
|
|
1463
|
-
// in flight) so processing always has buffer without ballooning memory.
|
|
1464
|
-
// budget already excludes items at/below the frontier (they're in the shared
|
|
1465
|
-
// totalReadyCount), so offset the index by bufferReadyCount — otherwise the
|
|
1466
|
-
// ready prefix is subtracted twice and the buffer caps at a fraction of its
|
|
1467
|
-
// target. A partition that fetched further is skipped until the buffer drains.
|
|
1468
|
-
let maxQueryBlockNumber = {
|
|
1469
|
-
switch buffer->Array.get(
|
|
1470
|
-
fetchState->bufferReadyCount + budget + chainPendingBudget->Float.toInt - 1,
|
|
1471
|
-
) {
|
|
1472
|
-
| Some(item) =>
|
|
1473
|
-
// Just in case check that we don't query beyond the current block
|
|
1474
|
-
Pervasives.min(item->Internal.getItemBlockNumber, knownHeight)
|
|
1475
|
-
| None => knownHeight
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
let queries = []
|
|
1480
|
-
|
|
1481
1509
|
let partitionsCount = optimizedPartitions.idsInAscOrder->Array.length
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1510
|
+
|
|
1511
|
+
// Every partition is visited once here regardless of whether it gets a
|
|
1512
|
+
// query pushed below — waiting-for-new-block bookkeeping shouldn't depend
|
|
1513
|
+
// on this tick's budget.
|
|
1514
|
+
for idx in 0 to partitionsCount - 1 {
|
|
1485
1515
|
let partitionId = optimizedPartitions.idsInAscOrder->Array.getUnsafe(idx)
|
|
1486
1516
|
let p = optimizedPartitions.entities->Dict.getUnsafe(partitionId)
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
let hasPendingQueries = pendingCount > 0
|
|
1491
|
-
|
|
1492
|
-
if hasPendingQueries || isBehindTheHead {
|
|
1517
|
+
if (
|
|
1518
|
+
p.mutPendingQueries->Array.length > 0 || p.latestFetchedBlock.blockNumber < headBlockNumber
|
|
1519
|
+
) {
|
|
1493
1520
|
// Even if there are some partitions waiting for the new block
|
|
1494
1521
|
// We still want to wait for all partitions reaching the head
|
|
1495
1522
|
// because they might update knownHeight in their response
|
|
@@ -1497,54 +1524,84 @@ let getNextQuery = (
|
|
|
1497
1524
|
// and we don't want to poll the head for a few small partitions
|
|
1498
1525
|
shouldWaitForNewBlock := false
|
|
1499
1526
|
}
|
|
1527
|
+
}
|
|
1500
1528
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1529
|
+
// One bucket per partition, in idsInAscOrder order — gap-fill and
|
|
1530
|
+
// water-fill both push into a partition's own bucket, so flattening at
|
|
1531
|
+
// the end (see below) reproduces idsInAscOrder without a sort.
|
|
1532
|
+
let queriesByPartitionIndex: array<
|
|
1533
|
+
array<query>,
|
|
1534
|
+
> = Array.fromInitializer(~length=partitionsCount, _ => [])
|
|
1535
|
+
|
|
1536
|
+
// Compute queryEndBlock for this partition
|
|
1537
|
+
let computeQueryEndBlock = (p: partition) => {
|
|
1538
|
+
let queryEndBlock = Utils.Math.minOptInt(endBlock, p.mergeBlock)
|
|
1539
|
+
switch blockLag {
|
|
1506
1540
|
| 0 => queryEndBlock
|
|
1507
1541
|
| _ =>
|
|
1508
1542
|
// Force head block as an endBlock when blockLag is set
|
|
1509
1543
|
// because otherwise HyperSync might return bigger range
|
|
1510
1544
|
Utils.Math.minOptInt(Some(headBlockNumber), queryEndBlock)
|
|
1511
1545
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// Each partition's existing in-flight itemsTarget, summed once and reused
|
|
1549
|
+
// both for chainReserved (the call-wide fresh-budget ceiling below) and to
|
|
1550
|
+
// seed each partition's water-fill footprint — so a partition already
|
|
1551
|
+
// holding in-flight queries is counted toward its even share and doesn't
|
|
1552
|
+
// get topped up past the others.
|
|
1553
|
+
let existingReservedByPartition = Dict.make()
|
|
1554
|
+
let chainReserved = ref(0.)
|
|
1555
|
+
for idx in 0 to partitionsCount - 1 {
|
|
1556
|
+
let partitionId = optimizedPartitions.idsInAscOrder->Array.getUnsafe(idx)
|
|
1557
|
+
let p = optimizedPartitions.entities->Dict.getUnsafe(partitionId)
|
|
1558
|
+
let cost = ref(0.)
|
|
1559
|
+
for pqIdx in 0 to p.mutPendingQueries->Array.length - 1 {
|
|
1560
|
+
cost := cost.contents +. (p.mutPendingQueries->Array.getUnsafe(pqIdx)).itemsTarget
|
|
1520
1561
|
}
|
|
1562
|
+
existingReservedByPartition->Dict.set(partitionId, cost.contents)
|
|
1563
|
+
chainReserved := chainReserved.contents +. cost.contents
|
|
1564
|
+
}
|
|
1521
1565
|
|
|
1566
|
+
// Phase A: gap-fill. Walk each partition's pending queries once,
|
|
1567
|
+
// unconditionally filling any hole (e.g. from an out-of-order partial
|
|
1568
|
+
// chunk response) — uncapped, same as before this tick's water-fill was
|
|
1569
|
+
// introduced. This also determines each partition's post-gap cursor and
|
|
1570
|
+
// whether it's blocked on an unresolved single-shot query.
|
|
1571
|
+
let fillStates = []
|
|
1572
|
+
let gapFillCost = ref(0.)
|
|
1573
|
+
for idx in 0 to partitionsCount - 1 {
|
|
1574
|
+
let partitionId = optimizedPartitions.idsInAscOrder->Array.getUnsafe(idx)
|
|
1575
|
+
let p = optimizedPartitions.entities->Dict.getUnsafe(partitionId)
|
|
1576
|
+
let bucket = queriesByPartitionIndex->Array.getUnsafe(idx)
|
|
1577
|
+
let pendingCount = p.mutPendingQueries->Array.length
|
|
1578
|
+
let queryEndBlock = computeQueryEndBlock(p)
|
|
1522
1579
|
let maybeChunkRange = getMinHistoryRange(p)
|
|
1523
1580
|
|
|
1524
|
-
// Walk pending queries to find open ranges and create queries for each
|
|
1525
1581
|
let cursor = ref(p.latestFetchedBlock.blockNumber + 1)
|
|
1526
1582
|
let canContinue = ref(true)
|
|
1583
|
+
let chunksUsedThisCall = ref(0)
|
|
1527
1584
|
let pqIdx = ref(0)
|
|
1528
|
-
while pqIdx.contents <
|
|
1585
|
+
while pqIdx.contents < pendingCount && canContinue.contents {
|
|
1529
1586
|
let pq = p.mutPendingQueries->Array.getUnsafe(pqIdx.contents)
|
|
1530
1587
|
|
|
1531
|
-
// Gap before this pending query → create queries for the gap range
|
|
1532
1588
|
if pq.fromBlock > cursor.contents {
|
|
1533
|
-
|
|
1534
|
-
|
|
1589
|
+
let beforeLen = bucket->Array.length
|
|
1590
|
+
let cost = pushGapFillQueries(
|
|
1591
|
+
bucket,
|
|
1535
1592
|
~partitionId,
|
|
1536
1593
|
~rangeFromBlock=cursor.contents,
|
|
1537
1594
|
~rangeEndBlock=Utils.Math.minOptInt(Some(pq.fromBlock - 1), queryEndBlock),
|
|
1538
|
-
~
|
|
1595
|
+
~knownHeight,
|
|
1596
|
+
~chainTargetBlock,
|
|
1539
1597
|
~maybeChunkRange,
|
|
1540
|
-
~maxChunks=maxPendingChunksPerPartition -
|
|
1541
|
-
pendingCount -
|
|
1542
|
-
(queries->Array.length -
|
|
1543
|
-
partitionQueriesStart),
|
|
1598
|
+
~maxChunks=maxPendingChunksPerPartition - pendingCount - chunksUsedThisCall.contents,
|
|
1544
1599
|
~partition=p,
|
|
1545
1600
|
~selection=p.selection,
|
|
1546
1601
|
~addressesByContractName=p.addressesByContractName,
|
|
1547
1602
|
)
|
|
1603
|
+
chunksUsedThisCall := chunksUsedThisCall.contents + (bucket->Array.length - beforeLen)
|
|
1604
|
+
gapFillCost := gapFillCost.contents +. cost
|
|
1548
1605
|
}
|
|
1549
1606
|
switch pq {
|
|
1550
1607
|
| {isChunk: true, toBlock: Some(toBlock), fetchedBlock: Some({blockNumber})}
|
|
@@ -1556,28 +1613,180 @@ let getNextQuery = (
|
|
|
1556
1613
|
pqIdx := pqIdx.contents + 1
|
|
1557
1614
|
}
|
|
1558
1615
|
|
|
1559
|
-
// Tail range after all pending queries
|
|
1560
1616
|
if canContinue.contents {
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
~selection=p.selection,
|
|
1574
|
-
~addressesByContractName=p.addressesByContractName,
|
|
1575
|
-
)
|
|
1617
|
+
fillStates
|
|
1618
|
+
->Array.push({
|
|
1619
|
+
partitionId,
|
|
1620
|
+
p,
|
|
1621
|
+
cursor: cursor.contents,
|
|
1622
|
+
chunksUsedThisCall: chunksUsedThisCall.contents,
|
|
1623
|
+
pendingCount,
|
|
1624
|
+
queryEndBlock,
|
|
1625
|
+
maybeChunkRange,
|
|
1626
|
+
bucket,
|
|
1627
|
+
})
|
|
1628
|
+
->ignore
|
|
1576
1629
|
}
|
|
1630
|
+
}
|
|
1577
1631
|
|
|
1578
|
-
|
|
1632
|
+
// Fresh budget for this call — what's left of chainTargetItems after
|
|
1633
|
+
// existing in-flight queries and this tick's gap-fill. The water-fill
|
|
1634
|
+
// rounds below hand it out; a partition already holding a large share
|
|
1635
|
+
// (seeded below) gets proportionally less so totals stay even.
|
|
1636
|
+
let rangeItemsTarget = Pervasives.max(
|
|
1637
|
+
0.,
|
|
1638
|
+
chainTargetItems -. chainReserved.contents -. gapFillCost.contents,
|
|
1639
|
+
)
|
|
1640
|
+
|
|
1641
|
+
// Each in-range partition's running footprint: existing in-flight plus any
|
|
1642
|
+
// gap-fill just pushed into its bucket. The water-fill levels every
|
|
1643
|
+
// partition up toward a shared line, so this seed is what a partition
|
|
1644
|
+
// starts the fill already holding.
|
|
1645
|
+
let reservedByPartition = Dict.make()
|
|
1646
|
+
fillStates->Array.forEach(fs => {
|
|
1647
|
+
let cost = ref(existingReservedByPartition->Dict.getUnsafe(fs.partitionId))
|
|
1648
|
+
for i in 0 to fs.bucket->Array.length - 1 {
|
|
1649
|
+
cost := cost.contents +. (fs.bucket->Array.getUnsafe(i)).itemsTarget
|
|
1650
|
+
}
|
|
1651
|
+
reservedByPartition->Dict.set(fs.partitionId, cost.contents)
|
|
1652
|
+
})
|
|
1653
|
+
|
|
1654
|
+
// Emits this round's queries for one partition, given its share of the
|
|
1655
|
+
// range budget. Mutates fs.cursor/chunksUsedThisCall and returns the
|
|
1656
|
+
// itemsTarget consumed.
|
|
1657
|
+
//
|
|
1658
|
+
// Density is trusted only once the chunking heuristic is active
|
|
1659
|
+
// (maybeChunkRange requires prevQueryRange AND prevPrevQueryRange, i.e.
|
|
1660
|
+
// two responses — see getMinHistoryRange) — a single response is noisy
|
|
1661
|
+
// (a first query that happens to land on an unusually dense/sparse block
|
|
1662
|
+
// would otherwise mis-size the very next one), so it's treated the same
|
|
1663
|
+
// as a partition with no signal at all: sized exactly to this round's
|
|
1664
|
+
// share instead of an uncapped, one-sample density estimate.
|
|
1665
|
+
let emitQueries = (fs: waterFillState, ~budget: float) => {
|
|
1666
|
+
let p = fs.p
|
|
1667
|
+
let maxBlock = switch fs.queryEndBlock {
|
|
1668
|
+
| Some(eb) => eb
|
|
1669
|
+
| None => chainTargetBlock
|
|
1670
|
+
}
|
|
1671
|
+
switch fs.maybeChunkRange {
|
|
1672
|
+
| Some(minHistoryRange) =>
|
|
1673
|
+
// Chunking active: strict chunks with a hard endBlock, uncapped real
|
|
1674
|
+
// density — at least one full chunk this round even if budget falls
|
|
1675
|
+
// short.
|
|
1676
|
+
let density = p.prevRangeSize->Int.toFloat /. p.prevQueryRange->Int.toFloat
|
|
1677
|
+
let chunkSize = Js.Math.ceil_int(minHistoryRange->Int.toFloat *. 1.8)
|
|
1678
|
+
let chunkCost = density *. chunkSize->Int.toFloat
|
|
1679
|
+
let maxChunksRemaining =
|
|
1680
|
+
maxPendingChunksPerPartition - fs.pendingCount - fs.chunksUsedThisCall
|
|
1681
|
+
let affordable = if chunkCost > 0. {
|
|
1682
|
+
Math.floor(budget /. chunkCost)->Float.toInt
|
|
1683
|
+
} else {
|
|
1684
|
+
maxChunksRemaining
|
|
1685
|
+
}
|
|
1686
|
+
let numChunks = Pervasives.max(1, Pervasives.min(affordable, maxChunksRemaining))
|
|
1687
|
+
let consumed = ref(0.)
|
|
1688
|
+
let created = ref(0)
|
|
1689
|
+
let chunkFromBlock = ref(fs.cursor)
|
|
1690
|
+
while created.contents < numChunks && chunkFromBlock.contents <= maxBlock {
|
|
1691
|
+
let chunkToBlock = Pervasives.min(chunkFromBlock.contents + chunkSize - 1, maxBlock)
|
|
1692
|
+
let itemsTarget = density *. (chunkToBlock - chunkFromBlock.contents + 1)->Int.toFloat
|
|
1693
|
+
fs.bucket->Array.push({
|
|
1694
|
+
partitionId: fs.partitionId,
|
|
1695
|
+
fromBlock: chunkFromBlock.contents,
|
|
1696
|
+
toBlock: Some(chunkToBlock),
|
|
1697
|
+
isChunk: true,
|
|
1698
|
+
selection: p.selection,
|
|
1699
|
+
itemsTarget,
|
|
1700
|
+
addressesByContractName: p.addressesByContractName,
|
|
1701
|
+
})
|
|
1702
|
+
consumed := consumed.contents +. itemsTarget
|
|
1703
|
+
chunkFromBlock := chunkToBlock + 1
|
|
1704
|
+
created := created.contents + 1
|
|
1705
|
+
}
|
|
1706
|
+
fs.cursor = chunkFromBlock.contents
|
|
1707
|
+
fs.chunksUsedThisCall = fs.chunksUsedThisCall + created.contents
|
|
1708
|
+
consumed.contents
|
|
1709
|
+
| None =>
|
|
1710
|
+
// No trusted density yet (zero or one response): one open query sized
|
|
1711
|
+
// to this round's share, capped so a single probe can't swallow the
|
|
1712
|
+
// whole (possibly cross-chain-wide) budget and starve other partitions
|
|
1713
|
+
// or chains before they get their own first probe.
|
|
1714
|
+
let itemsTarget = Pervasives.min(Math.round(budget), maxItemsTarget)
|
|
1715
|
+
fs.bucket->Array.push({
|
|
1716
|
+
partitionId: fs.partitionId,
|
|
1717
|
+
fromBlock: fs.cursor,
|
|
1718
|
+
toBlock: fs.queryEndBlock,
|
|
1719
|
+
isChunk: false,
|
|
1720
|
+
selection: p.selection,
|
|
1721
|
+
itemsTarget,
|
|
1722
|
+
addressesByContractName: p.addressesByContractName,
|
|
1723
|
+
})
|
|
1724
|
+
fs.cursor = maxBlock + 1
|
|
1725
|
+
fs.chunksUsedThisCall = fs.chunksUsedThisCall + 1
|
|
1726
|
+
itemsTarget
|
|
1727
|
+
}
|
|
1579
1728
|
}
|
|
1580
1729
|
|
|
1730
|
+
let isInRange = (fs: waterFillState) =>
|
|
1731
|
+
fs.cursor <= chainTargetBlock &&
|
|
1732
|
+
switch fs.queryEndBlock {
|
|
1733
|
+
| Some(eb) => fs.cursor <= eb
|
|
1734
|
+
| None => true
|
|
1735
|
+
} &&
|
|
1736
|
+
fs.pendingCount + fs.chunksUsedThisCall < maxPendingChunksPerPartition
|
|
1737
|
+
|
|
1738
|
+
// Water-fill. Range membership is fixed by chainTargetBlock/queryEndBlock,
|
|
1739
|
+
// so the in-range partitions are filtered once up front; each round levels
|
|
1740
|
+
// every still-not-filled partition up toward a shared line and keeps only
|
|
1741
|
+
// those still in range for the next round.
|
|
1742
|
+
//
|
|
1743
|
+
// The line is (remaining fresh budget + those partitions' current
|
|
1744
|
+
// footprint) / count: a partition already holding more than the line gets
|
|
1745
|
+
// nothing and drops out (its head start is its whole share), while the
|
|
1746
|
+
// rest are topped up to the line — so leftover from an under-using or
|
|
1747
|
+
// filled partition redistributes to whoever can still use it, and totals
|
|
1748
|
+
// stay even regardless of processing order (the line is fixed before the
|
|
1749
|
+
// round's emits).
|
|
1750
|
+
//
|
|
1751
|
+
// No explicit round cap: a partition survives a round only by advancing
|
|
1752
|
+
// chunksUsedThisCall (capped at maxPendingChunksPerPartition) or by
|
|
1753
|
+
// consuming fresh budget, so the not-filled set drains on its own and the
|
|
1754
|
+
// loop also stops once the whole budget is reserved.
|
|
1755
|
+
let notFilledPartitions = ref(fillStates->Array.filter(isInRange))
|
|
1756
|
+
let reservedFromRange = ref(0.)
|
|
1757
|
+
while (
|
|
1758
|
+
notFilledPartitions.contents->Array.length > 0 &&
|
|
1759
|
+
reservedFromRange.contents < rangeItemsTarget
|
|
1760
|
+
) {
|
|
1761
|
+
let n = notFilledPartitions.contents->Array.length
|
|
1762
|
+
let footprintSum = ref(0.)
|
|
1763
|
+
notFilledPartitions.contents->Array.forEach(fs =>
|
|
1764
|
+
footprintSum := footprintSum.contents +. reservedByPartition->Dict.getUnsafe(fs.partitionId)
|
|
1765
|
+
)
|
|
1766
|
+
let line =
|
|
1767
|
+
(rangeItemsTarget -. reservedFromRange.contents +. footprintSum.contents) /. n->Int.toFloat
|
|
1768
|
+
let next = []
|
|
1769
|
+
notFilledPartitions.contents->Array.forEach(fs => {
|
|
1770
|
+
let reserved = reservedByPartition->Dict.getUnsafe(fs.partitionId)
|
|
1771
|
+
let budget = line -. reserved
|
|
1772
|
+
if budget > 0. {
|
|
1773
|
+
let consumed = emitQueries(fs, ~budget)
|
|
1774
|
+
reservedByPartition->Dict.set(fs.partitionId, reserved +. consumed)
|
|
1775
|
+
reservedFromRange := reservedFromRange.contents +. consumed
|
|
1776
|
+
if fs->isInRange {
|
|
1777
|
+
next->Array.push(fs)->ignore
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
})
|
|
1781
|
+
notFilledPartitions := next
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// Each partition pushed only into its own bucket (indexed by its
|
|
1785
|
+
// idsInAscOrder position), so flattening reproduces idsInAscOrder order
|
|
1786
|
+
// directly — no sort needed even though water-fill rounds interleave
|
|
1787
|
+
// across partitions.
|
|
1788
|
+
let queries = queriesByPartitionIndex->Array.flat
|
|
1789
|
+
|
|
1581
1790
|
if queries->Utils.Array.isEmpty {
|
|
1582
1791
|
if shouldWaitForNewBlock.contents {
|
|
1583
1792
|
WaitingForNewBlock
|
|
@@ -1626,7 +1835,7 @@ Instantiates a fetch state with partitions for initial addresses
|
|
|
1626
1835
|
let make = (
|
|
1627
1836
|
~startBlock,
|
|
1628
1837
|
~endBlock,
|
|
1629
|
-
~
|
|
1838
|
+
~onEventRegistrations: array<Internal.onEventRegistration>,
|
|
1630
1839
|
~contractConfigs: dict<IndexingAddresses.contractConfig>,
|
|
1631
1840
|
~addresses: array<Internal.indexingAddress>,
|
|
1632
1841
|
~maxAddrInPartition,
|
|
@@ -1634,7 +1843,7 @@ let make = (
|
|
|
1634
1843
|
~maxOnBlockBufferSize,
|
|
1635
1844
|
~knownHeight,
|
|
1636
1845
|
~progressBlockNumber=startBlock - 1,
|
|
1637
|
-
~
|
|
1846
|
+
~onBlockRegistrations=[],
|
|
1638
1847
|
~blockLag=0,
|
|
1639
1848
|
~firstEventBlock=None,
|
|
1640
1849
|
): t => {
|
|
@@ -1644,15 +1853,15 @@ let make = (
|
|
|
1644
1853
|
}
|
|
1645
1854
|
|
|
1646
1855
|
let notDependingOnAddresses = []
|
|
1647
|
-
let
|
|
1856
|
+
let normalRegistrations = []
|
|
1648
1857
|
let contractNamesWithNormalEvents = Utils.Set.make()
|
|
1649
1858
|
|
|
1650
|
-
|
|
1651
|
-
if
|
|
1652
|
-
|
|
1653
|
-
contractNamesWithNormalEvents->Utils.Set.add(
|
|
1859
|
+
onEventRegistrations->Array.forEach(reg => {
|
|
1860
|
+
if reg.dependsOnAddresses {
|
|
1861
|
+
normalRegistrations->Array.push(reg)
|
|
1862
|
+
contractNamesWithNormalEvents->Utils.Set.add(reg.eventConfig.contractName)->ignore
|
|
1654
1863
|
} else {
|
|
1655
|
-
notDependingOnAddresses->Array.push(
|
|
1864
|
+
notDependingOnAddresses->Array.push(reg)
|
|
1656
1865
|
}
|
|
1657
1866
|
})
|
|
1658
1867
|
|
|
@@ -1664,7 +1873,7 @@ let make = (
|
|
|
1664
1873
|
latestFetchedBlock,
|
|
1665
1874
|
selection: {
|
|
1666
1875
|
dependsOnAddresses: false,
|
|
1667
|
-
|
|
1876
|
+
onEventRegistrations: notDependingOnAddresses,
|
|
1668
1877
|
},
|
|
1669
1878
|
addressesByContractName: Dict.make(),
|
|
1670
1879
|
mergeBlock: None,
|
|
@@ -1679,7 +1888,7 @@ let make = (
|
|
|
1679
1888
|
|
|
1680
1889
|
let normalSelection = {
|
|
1681
1890
|
dependsOnAddresses: true,
|
|
1682
|
-
|
|
1891
|
+
onEventRegistrations: normalRegistrations,
|
|
1683
1892
|
}
|
|
1684
1893
|
|
|
1685
1894
|
let registeringContractsByContract: dict<dict<indexingAddress>> = Dict.make()
|
|
@@ -1715,12 +1924,15 @@ let make = (
|
|
|
1715
1924
|
~progressBlockNumber,
|
|
1716
1925
|
)
|
|
1717
1926
|
|
|
1718
|
-
if
|
|
1927
|
+
if (
|
|
1928
|
+
optimizedPartitions->OptimizedPartitions.count === 0 &&
|
|
1929
|
+
onBlockRegistrations->Utils.Array.isEmpty
|
|
1930
|
+
) {
|
|
1719
1931
|
JsError.throwWithMessage(
|
|
1720
1932
|
`Invalid configuration: Nothing to fetch on chain ${chainId->Int.toString}. ` ++
|
|
1721
1933
|
`addresses=${addresses->Array.length->Int.toString}, ` ++
|
|
1722
|
-
`
|
|
1723
|
-
`
|
|
1934
|
+
`onEventRegistrations=${onEventRegistrations->Array.length->Int.toString}, ` ++
|
|
1935
|
+
`normalRegistrations=${normalRegistrations
|
|
1724
1936
|
->Array.length
|
|
1725
1937
|
->Int.toString}. ` ++ `Make sure that you provided at least one contract address to index, or have events with Wildcard mode enabled, or have onBlock handlers.`,
|
|
1726
1938
|
)
|
|
@@ -1731,14 +1943,14 @@ let make = (
|
|
|
1731
1943
|
// fetching, so without seeding the buffer here getNextQuery would return
|
|
1732
1944
|
// NothingToQuery and the indexer would get stuck.
|
|
1733
1945
|
let buffer = []
|
|
1734
|
-
let latestOnBlockBlockNumber = if knownHeight > 0 &&
|
|
1946
|
+
let latestOnBlockBlockNumber = if knownHeight > 0 && onBlockRegistrations->Utils.Array.notEmpty {
|
|
1735
1947
|
let maxBlockNumber = switch optimizedPartitions->OptimizedPartitions.getLatestFullyFetchedBlock {
|
|
1736
1948
|
| None => knownHeight
|
|
1737
1949
|
| Some(latestFullyFetchedBlock) => latestFullyFetchedBlock.blockNumber
|
|
1738
1950
|
}
|
|
1739
1951
|
appendOnBlockItems(
|
|
1740
1952
|
~mutItems=buffer,
|
|
1741
|
-
~
|
|
1953
|
+
~onBlockRegistrations,
|
|
1742
1954
|
~indexerStartBlock=startBlock,
|
|
1743
1955
|
~fromBlock=progressBlockNumber,
|
|
1744
1956
|
~maxBlockNumber,
|
|
@@ -1757,7 +1969,7 @@ let make = (
|
|
|
1757
1969
|
latestOnBlockBlockNumber,
|
|
1758
1970
|
normalSelection,
|
|
1759
1971
|
blockLag,
|
|
1760
|
-
|
|
1972
|
+
onBlockRegistrations,
|
|
1761
1973
|
maxOnBlockBufferSize,
|
|
1762
1974
|
knownHeight,
|
|
1763
1975
|
buffer,
|
|
@@ -2007,17 +2219,6 @@ let getProgressPercentage = (fetchState: t) => {
|
|
|
2007
2219
|
}
|
|
2008
2220
|
}
|
|
2009
2221
|
|
|
2010
|
-
// Progress a specific block sits at along the chain, used to order queries from
|
|
2011
|
-
// different chains: a query starting further back (lower %) is fetched first.
|
|
2012
|
-
let getProgressPercentageAt = (fetchState: t, ~blockNumber) => {
|
|
2013
|
-
switch fetchState.firstEventBlock {
|
|
2014
|
-
| None => 0.
|
|
2015
|
-
| Some(firstEventBlock) =>
|
|
2016
|
-
let totalRange = fetchState.knownHeight - firstEventBlock
|
|
2017
|
-
totalRange <= 0 ? 0. : (blockNumber - firstEventBlock)->Int.toFloat /. totalRange->Int.toFloat
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
2222
|
let sortForBatch = {
|
|
2022
2223
|
let hasFullBatch = ({buffer} as fetchState: t, ~batchSizeTarget) => {
|
|
2023
2224
|
switch buffer->Array.get(batchSizeTarget - 1) {
|