envio 3.3.0-alpha.7 → 3.3.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +23 -5
- package/package.json +6 -6
- package/src/Api.res +1 -1
- package/src/Api.res.mjs +1 -1
- package/src/ChainFetching.res +21 -10
- package/src/ChainFetching.res.mjs +11 -10
- package/src/ChainState.res +328 -165
- package/src/ChainState.res.mjs +205 -100
- package/src/ChainState.resi +16 -7
- package/src/Config.res +11 -27
- package/src/Config.res.mjs +8 -7
- package/src/Core.res +7 -0
- package/src/CrossChainState.res +53 -80
- package/src/CrossChainState.res.mjs +43 -59
- package/src/CrossChainState.resi +1 -1
- package/src/Ecosystem.res +3 -3
- package/src/Ecosystem.res.mjs +3 -3
- package/src/Envio.res +14 -9
- package/src/EventConfigBuilder.res +105 -56
- package/src/EventConfigBuilder.res.mjs +69 -33
- package/src/EventProcessing.res +19 -15
- package/src/EventProcessing.res.mjs +13 -12
- package/src/FetchState.res +375 -171
- package/src/FetchState.res.mjs +249 -251
- package/src/HandlerLoader.res +7 -112
- package/src/HandlerLoader.res.mjs +1 -87
- package/src/HandlerRegister.res +209 -6
- package/src/HandlerRegister.res.mjs +90 -5
- package/src/HandlerRegister.resi +13 -2
- package/src/IndexerState.res +6 -3
- package/src/IndexerState.res.mjs +3 -3
- package/src/IndexerState.resi +1 -1
- package/src/IndexingAddresses.res +10 -7
- package/src/IndexingAddresses.res.mjs +8 -7
- package/src/IndexingAddresses.resi +3 -1
- package/src/Internal.res +82 -36
- package/src/Internal.res.mjs +11 -1
- package/src/Main.res +22 -23
- package/src/Main.res.mjs +15 -17
- package/src/Metrics.res +43 -2
- package/src/Metrics.res.mjs +39 -3
- package/src/Prometheus.res +0 -35
- package/src/Prometheus.res.mjs +33 -68
- package/src/RawEvent.res +7 -2
- package/src/RawEvent.res.mjs +4 -4
- package/src/SimulateItems.res +20 -7
- package/src/SimulateItems.res.mjs +7 -11
- package/src/TestIndexer.res +1 -1
- package/src/TestIndexer.res.mjs +1 -1
- package/src/sources/BlockStore.res +46 -0
- package/src/sources/BlockStore.res.mjs +24 -0
- package/src/sources/EventRouter.res +19 -12
- package/src/sources/EventRouter.res.mjs +7 -5
- package/src/sources/Evm.res +54 -4
- package/src/sources/Evm.res.mjs +43 -4
- package/src/sources/EvmChain.res +14 -18
- package/src/sources/EvmChain.res.mjs +12 -13
- package/src/sources/FieldMask.res +39 -0
- package/src/sources/FieldMask.res.mjs +42 -0
- package/src/sources/Fuel.res +5 -2
- package/src/sources/Fuel.res.mjs +4 -3
- package/src/sources/HyperFuelSource.res +31 -40
- package/src/sources/HyperFuelSource.res.mjs +52 -44
- package/src/sources/HyperSync.res +31 -9
- package/src/sources/HyperSync.res.mjs +47 -31
- package/src/sources/HyperSync.resi +10 -3
- package/src/sources/HyperSyncClient.res +15 -4
- package/src/sources/HyperSyncHeightStream.res +1 -8
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
- package/src/sources/HyperSyncSource.res +37 -58
- package/src/sources/HyperSyncSource.res.mjs +42 -42
- package/src/sources/RpcSource.res +91 -82
- package/src/sources/RpcSource.res.mjs +76 -45
- package/src/sources/RpcWebSocketHeightStream.res +0 -5
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
- package/src/sources/SimulateSource.res +5 -3
- package/src/sources/SimulateSource.res.mjs +12 -4
- package/src/sources/Source.res +19 -5
- package/src/sources/SourceManager.res +62 -6
- package/src/sources/SourceManager.res.mjs +55 -8
- package/src/sources/SourceManager.resi +10 -0
- package/src/sources/Svm.res +14 -10
- package/src/sources/Svm.res.mjs +23 -6
- package/src/sources/SvmHyperSyncClient.res +5 -6
- package/src/sources/SvmHyperSyncSource.res +82 -41
- package/src/sources/SvmHyperSyncSource.res.mjs +86 -41
- package/src/sources/TransactionStore.res +6 -107
- package/src/sources/TransactionStore.res.mjs +5 -86
- package/svm.schema.json +19 -0
package/src/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,42 +91,28 @@ let deriveContractNameByAddress: dict<array<Address.t>> => dict<
|
|
|
90
91
|
result
|
|
91
92
|
})
|
|
92
93
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
let
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// (prevQueryRange 0) has no signal, so it falls back to calculateDefaultEstResponseSize.
|
|
116
|
-
let calculateEstResponseSize = (
|
|
117
|
-
p: partition,
|
|
118
|
-
~fromBlock,
|
|
119
|
-
~toBlock,
|
|
120
|
-
~knownHeight,
|
|
121
|
-
~partitionsCount,
|
|
122
|
-
) =>
|
|
123
|
-
if p.prevQueryRange > 0 {
|
|
124
|
-
let density = p.prevRangeSize->Int.toFloat /. p.prevQueryRange->Int.toFloat
|
|
125
|
-
(toBlock->Option.getOr(knownHeight) - fromBlock + 1)->Int.toFloat *. density
|
|
126
|
-
} else {
|
|
127
|
-
calculateDefaultEstResponseSize(~partitionsCount)
|
|
128
|
-
}
|
|
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
|
+
}
|
|
129
116
|
|
|
130
117
|
// Calculate the chunk range from history using min-of-last-3-ranges heuristic
|
|
131
118
|
let getMinHistoryRange = (p: partition) => {
|
|
@@ -566,7 +553,7 @@ type t = {
|
|
|
566
553
|
// size). Event fetch depth is bounded separately, by CrossChainState's
|
|
567
554
|
// cross-chain admission against the indexer-wide buffer pool.
|
|
568
555
|
maxOnBlockBufferSize: int,
|
|
569
|
-
|
|
556
|
+
onBlockRegistrations: array<Internal.onBlockRegistration>,
|
|
570
557
|
knownHeight: int,
|
|
571
558
|
firstEventBlock: option<int>,
|
|
572
559
|
}
|
|
@@ -642,7 +629,7 @@ let blockItemLogIndex = 16777216
|
|
|
642
629
|
// are generated at once to prevent OOM.
|
|
643
630
|
let appendOnBlockItems = (
|
|
644
631
|
~mutItems: array<Internal.item>,
|
|
645
|
-
~
|
|
632
|
+
~onBlockRegistrations: array<Internal.onBlockRegistration>,
|
|
646
633
|
~indexerStartBlock,
|
|
647
634
|
~fromBlock,
|
|
648
635
|
~maxBlockNumber,
|
|
@@ -662,27 +649,27 @@ let appendOnBlockItems = (
|
|
|
662
649
|
let blockNumber = latestOnBlockBlockNumber.contents + 1
|
|
663
650
|
latestOnBlockBlockNumber := blockNumber
|
|
664
651
|
|
|
665
|
-
for configIdx in 0 to
|
|
666
|
-
let
|
|
652
|
+
for configIdx in 0 to onBlockRegistrations->Array.length - 1 {
|
|
653
|
+
let onBlockRegistration = onBlockRegistrations->Array.getUnsafe(configIdx)
|
|
667
654
|
|
|
668
|
-
let handlerStartBlock = switch
|
|
655
|
+
let handlerStartBlock = switch onBlockRegistration.startBlock {
|
|
669
656
|
| Some(startBlock) => startBlock
|
|
670
657
|
| None => indexerStartBlock
|
|
671
658
|
}
|
|
672
659
|
|
|
673
660
|
if (
|
|
674
661
|
blockNumber >= handlerStartBlock &&
|
|
675
|
-
switch
|
|
662
|
+
switch onBlockRegistration.endBlock {
|
|
676
663
|
| Some(endBlock) => blockNumber <= endBlock
|
|
677
664
|
| None => true
|
|
678
665
|
} &&
|
|
679
|
-
(blockNumber - handlerStartBlock)->Pervasives.mod(
|
|
666
|
+
(blockNumber - handlerStartBlock)->Pervasives.mod(onBlockRegistration.interval) === 0
|
|
680
667
|
) {
|
|
681
668
|
mutItems->Array.push(
|
|
682
669
|
Block({
|
|
683
|
-
|
|
670
|
+
onBlockRegistration,
|
|
684
671
|
blockNumber,
|
|
685
|
-
logIndex: blockItemLogIndex +
|
|
672
|
+
logIndex: blockItemLogIndex + onBlockRegistration.index,
|
|
686
673
|
}),
|
|
687
674
|
)
|
|
688
675
|
newItemsCounter := newItemsCounter.contents + 1
|
|
@@ -706,9 +693,9 @@ let updateInternal = (
|
|
|
706
693
|
): t => {
|
|
707
694
|
let mutItemsRef = ref(mutItems)
|
|
708
695
|
|
|
709
|
-
let latestOnBlockBlockNumber = switch fetchState.
|
|
696
|
+
let latestOnBlockBlockNumber = switch fetchState.onBlockRegistrations {
|
|
710
697
|
| [] => knownHeight
|
|
711
|
-
|
|
|
698
|
+
| onBlockRegistrations => {
|
|
712
699
|
// Calculate the max block number we are going to create items for
|
|
713
700
|
// Use maxOnBlockBufferSize to get the last target item in the buffer
|
|
714
701
|
//
|
|
@@ -736,7 +723,7 @@ let updateInternal = (
|
|
|
736
723
|
|
|
737
724
|
appendOnBlockItems(
|
|
738
725
|
~mutItems,
|
|
739
|
-
~
|
|
726
|
+
~onBlockRegistrations,
|
|
740
727
|
~indexerStartBlock=fetchState.startBlock,
|
|
741
728
|
~fromBlock=fetchState.latestOnBlockBlockNumber,
|
|
742
729
|
~maxBlockNumber,
|
|
@@ -751,7 +738,7 @@ let updateInternal = (
|
|
|
751
738
|
contractConfigs: fetchState.contractConfigs,
|
|
752
739
|
normalSelection: fetchState.normalSelection,
|
|
753
740
|
chainId: fetchState.chainId,
|
|
754
|
-
|
|
741
|
+
onBlockRegistrations: fetchState.onBlockRegistrations,
|
|
755
742
|
maxOnBlockBufferSize: fetchState.maxOnBlockBufferSize,
|
|
756
743
|
optimizedPartitions,
|
|
757
744
|
latestOnBlockBlockNumber,
|
|
@@ -1017,7 +1004,7 @@ let registerDynamicContracts = (
|
|
|
1017
1004
|
// Might contain duplicates which we should filter out
|
|
1018
1005
|
items: array<Internal.item>,
|
|
1019
1006
|
) => {
|
|
1020
|
-
if fetchState.normalSelection.
|
|
1007
|
+
if fetchState.normalSelection.onEventRegistrations->Utils.Array.isEmpty {
|
|
1021
1008
|
// Can the normalSelection be empty?
|
|
1022
1009
|
JsError.throwWithMessage(
|
|
1023
1010
|
"Invalid configuration. No events to fetch for the dynamic contract registration.",
|
|
@@ -1301,8 +1288,8 @@ let handleQueryResult = (
|
|
|
1301
1288
|
// param-level analogue of EventRouter's srcAddress effectiveStartBlock check.
|
|
1302
1289
|
let newItems = newItems->Array.filter(item =>
|
|
1303
1290
|
switch item {
|
|
1304
|
-
| Internal.Event({
|
|
1305
|
-
switch
|
|
1291
|
+
| Internal.Event({onEventRegistration, payload, blockNumber}) =>
|
|
1292
|
+
switch onEventRegistration.clientAddressFilter {
|
|
1306
1293
|
| Some(filter) =>
|
|
1307
1294
|
filter(payload, blockNumber, indexingAddresses->IndexingAddresses.rawForFilter)
|
|
1308
1295
|
| None => true
|
|
@@ -1340,7 +1327,7 @@ let startFetchingQueries = ({optimizedPartitions}: t, ~queries: array<query>) =>
|
|
|
1340
1327
|
fromBlock: q.fromBlock,
|
|
1341
1328
|
toBlock: q.toBlock,
|
|
1342
1329
|
isChunk: q.isChunk,
|
|
1343
|
-
|
|
1330
|
+
itemsTarget: q.itemsTarget,
|
|
1344
1331
|
fetchedBlock: None,
|
|
1345
1332
|
}
|
|
1346
1333
|
|
|
@@ -1364,47 +1351,50 @@ let startFetchingQueries = ({optimizedPartitions}: t, ~queries: array<query>) =>
|
|
|
1364
1351
|
// Most parallel in-flight chunk queries a single partition may have at once.
|
|
1365
1352
|
let maxPendingChunksPerPartition = 10
|
|
1366
1353
|
|
|
1367
|
-
|
|
1368
|
-
|
|
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 = (
|
|
1369
1359
|
queries: array<query>,
|
|
1370
1360
|
~partitionId: string,
|
|
1371
1361
|
~rangeFromBlock: int,
|
|
1372
1362
|
~rangeEndBlock: option<int>,
|
|
1373
1363
|
~knownHeight: int,
|
|
1364
|
+
~chainTargetBlock: int,
|
|
1374
1365
|
~maybeChunkRange: option<int>,
|
|
1375
1366
|
~maxChunks: int,
|
|
1376
1367
|
~partition: partition,
|
|
1377
1368
|
~selection: selection,
|
|
1378
1369
|
~addressesByContractName: dict<array<Address.t>>,
|
|
1379
|
-
~partitionsCount: int,
|
|
1380
1370
|
) => {
|
|
1371
|
+
let cost = ref(0.)
|
|
1381
1372
|
if rangeFromBlock <= knownHeight && maxChunks > 0 {
|
|
1382
1373
|
switch rangeEndBlock {
|
|
1383
1374
|
| Some(endBlock) if rangeFromBlock > endBlock => ()
|
|
1384
1375
|
| _ =>
|
|
1385
1376
|
switch maybeChunkRange {
|
|
1386
1377
|
| None =>
|
|
1378
|
+
let itemsTarget = densityItemsTarget(
|
|
1379
|
+
partition,
|
|
1380
|
+
~fromBlock=rangeFromBlock,
|
|
1381
|
+
~toBlock=rangeEndBlock,
|
|
1382
|
+
~chainTargetBlock,
|
|
1383
|
+
)
|
|
1387
1384
|
queries->Array.push({
|
|
1388
1385
|
partitionId,
|
|
1389
1386
|
fromBlock: rangeFromBlock,
|
|
1390
1387
|
toBlock: rangeEndBlock,
|
|
1391
1388
|
selection,
|
|
1392
1389
|
isChunk: false,
|
|
1393
|
-
|
|
1394
|
-
partition,
|
|
1395
|
-
~fromBlock=rangeFromBlock,
|
|
1396
|
-
~toBlock=rangeEndBlock,
|
|
1397
|
-
~knownHeight,
|
|
1398
|
-
~partitionsCount,
|
|
1399
|
-
),
|
|
1400
|
-
chainId: 0,
|
|
1401
|
-
progress: 0.,
|
|
1390
|
+
itemsTarget,
|
|
1402
1391
|
addressesByContractName,
|
|
1403
1392
|
})
|
|
1393
|
+
cost := cost.contents +. itemsTarget
|
|
1404
1394
|
| Some(chunkRange) =>
|
|
1405
1395
|
let maxBlock = switch rangeEndBlock {
|
|
1406
1396
|
| Some(eb) => eb
|
|
1407
|
-
| None =>
|
|
1397
|
+
| None => chainTargetBlock
|
|
1408
1398
|
}
|
|
1409
1399
|
let chunkSize = Js.Math.ceil_int(chunkRange->Int.toFloat *. 1.8)
|
|
1410
1400
|
if rangeFromBlock + chunkSize * 2 - 1 <= maxBlock {
|
|
@@ -1414,58 +1404,94 @@ let pushQueriesForRange = (
|
|
|
1414
1404
|
chunkIdx.contents < maxChunks && chunkFromBlock.contents + chunkSize - 1 <= maxBlock
|
|
1415
1405
|
) {
|
|
1416
1406
|
let chunkToBlock = chunkFromBlock.contents + chunkSize - 1
|
|
1407
|
+
let itemsTarget = densityItemsTarget(
|
|
1408
|
+
partition,
|
|
1409
|
+
~fromBlock=chunkFromBlock.contents,
|
|
1410
|
+
~toBlock=Some(chunkToBlock),
|
|
1411
|
+
~chainTargetBlock,
|
|
1412
|
+
)
|
|
1417
1413
|
queries->Array.push({
|
|
1418
1414
|
partitionId,
|
|
1419
1415
|
fromBlock: chunkFromBlock.contents,
|
|
1420
1416
|
toBlock: Some(chunkToBlock),
|
|
1421
1417
|
isChunk: true,
|
|
1422
1418
|
selection,
|
|
1423
|
-
|
|
1424
|
-
partition,
|
|
1425
|
-
~fromBlock=chunkFromBlock.contents,
|
|
1426
|
-
~toBlock=Some(chunkToBlock),
|
|
1427
|
-
~knownHeight,
|
|
1428
|
-
~partitionsCount,
|
|
1429
|
-
),
|
|
1430
|
-
chainId: 0,
|
|
1431
|
-
progress: 0.,
|
|
1419
|
+
itemsTarget,
|
|
1432
1420
|
addressesByContractName,
|
|
1433
1421
|
})
|
|
1422
|
+
cost := cost.contents +. itemsTarget
|
|
1434
1423
|
chunkFromBlock := chunkToBlock + 1
|
|
1435
1424
|
chunkIdx := chunkIdx.contents + 1
|
|
1436
1425
|
}
|
|
1437
1426
|
} else {
|
|
1438
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
|
+
)
|
|
1439
1434
|
queries->Array.push({
|
|
1440
1435
|
partitionId,
|
|
1441
1436
|
fromBlock: rangeFromBlock,
|
|
1442
1437
|
toBlock: rangeEndBlock,
|
|
1443
1438
|
selection,
|
|
1444
1439
|
isChunk: rangeEndBlock !== None,
|
|
1445
|
-
|
|
1446
|
-
partition,
|
|
1447
|
-
~fromBlock=rangeFromBlock,
|
|
1448
|
-
~toBlock=rangeEndBlock,
|
|
1449
|
-
~knownHeight,
|
|
1450
|
-
~partitionsCount,
|
|
1451
|
-
),
|
|
1452
|
-
chainId: 0,
|
|
1453
|
-
progress: 0.,
|
|
1440
|
+
itemsTarget,
|
|
1454
1441
|
addressesByContractName,
|
|
1455
1442
|
})
|
|
1443
|
+
cost := cost.contents +. itemsTarget
|
|
1456
1444
|
}
|
|
1457
1445
|
}
|
|
1458
1446
|
}
|
|
1459
1447
|
}
|
|
1448
|
+
cost.contents
|
|
1449
|
+
}
|
|
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>,
|
|
1460
1466
|
}
|
|
1461
1467
|
|
|
1462
|
-
// Candidate queries are sized against the
|
|
1463
|
-
//
|
|
1464
|
-
//
|
|
1465
|
-
//
|
|
1466
|
-
//
|
|
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.
|
|
1467
1491
|
let getNextQuery = (
|
|
1468
1492
|
{optimizedPartitions, blockLag, latestOnBlockBlockNumber, knownHeight, endBlock}: t,
|
|
1493
|
+
~chainTargetBlock: int,
|
|
1494
|
+
~chainTargetItems: float,
|
|
1469
1495
|
) => {
|
|
1470
1496
|
let headBlockNumber = knownHeight - blockLag
|
|
1471
1497
|
if headBlockNumber <= 0 {
|
|
@@ -1480,20 +1506,17 @@ let getNextQuery = (
|
|
|
1480
1506
|
!isOnBlockBehindTheHead,
|
|
1481
1507
|
)
|
|
1482
1508
|
|
|
1483
|
-
let queries = []
|
|
1484
|
-
|
|
1485
1509
|
let partitionsCount = optimizedPartitions.idsInAscOrder->Array.length
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
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 {
|
|
1489
1515
|
let partitionId = optimizedPartitions.idsInAscOrder->Array.getUnsafe(idx)
|
|
1490
1516
|
let p = optimizedPartitions.entities->Dict.getUnsafe(partitionId)
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
let hasPendingQueries = pendingCount > 0
|
|
1495
|
-
|
|
1496
|
-
if hasPendingQueries || isBehindTheHead {
|
|
1517
|
+
if (
|
|
1518
|
+
p.mutPendingQueries->Array.length > 0 || p.latestFetchedBlock.blockNumber < headBlockNumber
|
|
1519
|
+
) {
|
|
1497
1520
|
// Even if there are some partitions waiting for the new block
|
|
1498
1521
|
// We still want to wait for all partitions reaching the head
|
|
1499
1522
|
// because they might update knownHeight in their response
|
|
@@ -1501,46 +1524,84 @@ let getNextQuery = (
|
|
|
1501
1524
|
// and we don't want to poll the head for a few small partitions
|
|
1502
1525
|
shouldWaitForNewBlock := false
|
|
1503
1526
|
}
|
|
1527
|
+
}
|
|
1504
1528
|
|
|
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, _ => [])
|
|
1506
1535
|
|
|
1507
|
-
|
|
1536
|
+
// Compute queryEndBlock for this partition
|
|
1537
|
+
let computeQueryEndBlock = (p: partition) => {
|
|
1508
1538
|
let queryEndBlock = Utils.Math.minOptInt(endBlock, p.mergeBlock)
|
|
1509
|
-
|
|
1539
|
+
switch blockLag {
|
|
1510
1540
|
| 0 => queryEndBlock
|
|
1511
1541
|
| _ =>
|
|
1512
1542
|
// Force head block as an endBlock when blockLag is set
|
|
1513
1543
|
// because otherwise HyperSync might return bigger range
|
|
1514
1544
|
Utils.Math.minOptInt(Some(headBlockNumber), queryEndBlock)
|
|
1515
1545
|
}
|
|
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
|
|
1561
|
+
}
|
|
1562
|
+
existingReservedByPartition->Dict.set(partitionId, cost.contents)
|
|
1563
|
+
chainReserved := chainReserved.contents +. cost.contents
|
|
1564
|
+
}
|
|
1516
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)
|
|
1517
1579
|
let maybeChunkRange = getMinHistoryRange(p)
|
|
1518
1580
|
|
|
1519
|
-
// Walk pending queries to find open ranges and create queries for each
|
|
1520
1581
|
let cursor = ref(p.latestFetchedBlock.blockNumber + 1)
|
|
1521
1582
|
let canContinue = ref(true)
|
|
1583
|
+
let chunksUsedThisCall = ref(0)
|
|
1522
1584
|
let pqIdx = ref(0)
|
|
1523
|
-
while pqIdx.contents <
|
|
1585
|
+
while pqIdx.contents < pendingCount && canContinue.contents {
|
|
1524
1586
|
let pq = p.mutPendingQueries->Array.getUnsafe(pqIdx.contents)
|
|
1525
1587
|
|
|
1526
|
-
// Gap before this pending query → create queries for the gap range
|
|
1527
1588
|
if pq.fromBlock > cursor.contents {
|
|
1528
|
-
|
|
1529
|
-
|
|
1589
|
+
let beforeLen = bucket->Array.length
|
|
1590
|
+
let cost = pushGapFillQueries(
|
|
1591
|
+
bucket,
|
|
1530
1592
|
~partitionId,
|
|
1531
1593
|
~rangeFromBlock=cursor.contents,
|
|
1532
1594
|
~rangeEndBlock=Utils.Math.minOptInt(Some(pq.fromBlock - 1), queryEndBlock),
|
|
1533
1595
|
~knownHeight,
|
|
1596
|
+
~chainTargetBlock,
|
|
1534
1597
|
~maybeChunkRange,
|
|
1535
|
-
~maxChunks=maxPendingChunksPerPartition -
|
|
1536
|
-
pendingCount -
|
|
1537
|
-
(queries->Array.length -
|
|
1538
|
-
partitionQueriesStart),
|
|
1598
|
+
~maxChunks=maxPendingChunksPerPartition - pendingCount - chunksUsedThisCall.contents,
|
|
1539
1599
|
~partition=p,
|
|
1540
1600
|
~selection=p.selection,
|
|
1541
1601
|
~addressesByContractName=p.addressesByContractName,
|
|
1542
|
-
~partitionsCount,
|
|
1543
1602
|
)
|
|
1603
|
+
chunksUsedThisCall := chunksUsedThisCall.contents + (bucket->Array.length - beforeLen)
|
|
1604
|
+
gapFillCost := gapFillCost.contents +. cost
|
|
1544
1605
|
}
|
|
1545
1606
|
switch pq {
|
|
1546
1607
|
| {isChunk: true, toBlock: Some(toBlock), fetchedBlock: Some({blockNumber})}
|
|
@@ -1552,29 +1613,180 @@ let getNextQuery = (
|
|
|
1552
1613
|
pqIdx := pqIdx.contents + 1
|
|
1553
1614
|
}
|
|
1554
1615
|
|
|
1555
|
-
// Tail range after all pending queries
|
|
1556
1616
|
if canContinue.contents {
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
~selection=p.selection,
|
|
1570
|
-
~addressesByContractName=p.addressesByContractName,
|
|
1571
|
-
~partitionsCount,
|
|
1572
|
-
)
|
|
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
|
|
1573
1629
|
}
|
|
1630
|
+
}
|
|
1574
1631
|
|
|
1575
|
-
|
|
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
|
+
}
|
|
1728
|
+
}
|
|
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
|
|
1576
1782
|
}
|
|
1577
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
|
+
|
|
1578
1790
|
if queries->Utils.Array.isEmpty {
|
|
1579
1791
|
if shouldWaitForNewBlock.contents {
|
|
1580
1792
|
WaitingForNewBlock
|
|
@@ -1623,7 +1835,7 @@ Instantiates a fetch state with partitions for initial addresses
|
|
|
1623
1835
|
let make = (
|
|
1624
1836
|
~startBlock,
|
|
1625
1837
|
~endBlock,
|
|
1626
|
-
~
|
|
1838
|
+
~onEventRegistrations: array<Internal.onEventRegistration>,
|
|
1627
1839
|
~contractConfigs: dict<IndexingAddresses.contractConfig>,
|
|
1628
1840
|
~addresses: array<Internal.indexingAddress>,
|
|
1629
1841
|
~maxAddrInPartition,
|
|
@@ -1631,7 +1843,7 @@ let make = (
|
|
|
1631
1843
|
~maxOnBlockBufferSize,
|
|
1632
1844
|
~knownHeight,
|
|
1633
1845
|
~progressBlockNumber=startBlock - 1,
|
|
1634
|
-
~
|
|
1846
|
+
~onBlockRegistrations=[],
|
|
1635
1847
|
~blockLag=0,
|
|
1636
1848
|
~firstEventBlock=None,
|
|
1637
1849
|
): t => {
|
|
@@ -1641,15 +1853,15 @@ let make = (
|
|
|
1641
1853
|
}
|
|
1642
1854
|
|
|
1643
1855
|
let notDependingOnAddresses = []
|
|
1644
|
-
let
|
|
1856
|
+
let normalRegistrations = []
|
|
1645
1857
|
let contractNamesWithNormalEvents = Utils.Set.make()
|
|
1646
1858
|
|
|
1647
|
-
|
|
1648
|
-
if
|
|
1649
|
-
|
|
1650
|
-
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
|
|
1651
1863
|
} else {
|
|
1652
|
-
notDependingOnAddresses->Array.push(
|
|
1864
|
+
notDependingOnAddresses->Array.push(reg)
|
|
1653
1865
|
}
|
|
1654
1866
|
})
|
|
1655
1867
|
|
|
@@ -1661,7 +1873,7 @@ let make = (
|
|
|
1661
1873
|
latestFetchedBlock,
|
|
1662
1874
|
selection: {
|
|
1663
1875
|
dependsOnAddresses: false,
|
|
1664
|
-
|
|
1876
|
+
onEventRegistrations: notDependingOnAddresses,
|
|
1665
1877
|
},
|
|
1666
1878
|
addressesByContractName: Dict.make(),
|
|
1667
1879
|
mergeBlock: None,
|
|
@@ -1676,7 +1888,7 @@ let make = (
|
|
|
1676
1888
|
|
|
1677
1889
|
let normalSelection = {
|
|
1678
1890
|
dependsOnAddresses: true,
|
|
1679
|
-
|
|
1891
|
+
onEventRegistrations: normalRegistrations,
|
|
1680
1892
|
}
|
|
1681
1893
|
|
|
1682
1894
|
let registeringContractsByContract: dict<dict<indexingAddress>> = Dict.make()
|
|
@@ -1712,12 +1924,15 @@ let make = (
|
|
|
1712
1924
|
~progressBlockNumber,
|
|
1713
1925
|
)
|
|
1714
1926
|
|
|
1715
|
-
if
|
|
1927
|
+
if (
|
|
1928
|
+
optimizedPartitions->OptimizedPartitions.count === 0 &&
|
|
1929
|
+
onBlockRegistrations->Utils.Array.isEmpty
|
|
1930
|
+
) {
|
|
1716
1931
|
JsError.throwWithMessage(
|
|
1717
1932
|
`Invalid configuration: Nothing to fetch on chain ${chainId->Int.toString}. ` ++
|
|
1718
1933
|
`addresses=${addresses->Array.length->Int.toString}, ` ++
|
|
1719
|
-
`
|
|
1720
|
-
`
|
|
1934
|
+
`onEventRegistrations=${onEventRegistrations->Array.length->Int.toString}, ` ++
|
|
1935
|
+
`normalRegistrations=${normalRegistrations
|
|
1721
1936
|
->Array.length
|
|
1722
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.`,
|
|
1723
1938
|
)
|
|
@@ -1728,14 +1943,14 @@ let make = (
|
|
|
1728
1943
|
// fetching, so without seeding the buffer here getNextQuery would return
|
|
1729
1944
|
// NothingToQuery and the indexer would get stuck.
|
|
1730
1945
|
let buffer = []
|
|
1731
|
-
let latestOnBlockBlockNumber = if knownHeight > 0 &&
|
|
1946
|
+
let latestOnBlockBlockNumber = if knownHeight > 0 && onBlockRegistrations->Utils.Array.notEmpty {
|
|
1732
1947
|
let maxBlockNumber = switch optimizedPartitions->OptimizedPartitions.getLatestFullyFetchedBlock {
|
|
1733
1948
|
| None => knownHeight
|
|
1734
1949
|
| Some(latestFullyFetchedBlock) => latestFullyFetchedBlock.blockNumber
|
|
1735
1950
|
}
|
|
1736
1951
|
appendOnBlockItems(
|
|
1737
1952
|
~mutItems=buffer,
|
|
1738
|
-
~
|
|
1953
|
+
~onBlockRegistrations,
|
|
1739
1954
|
~indexerStartBlock=startBlock,
|
|
1740
1955
|
~fromBlock=progressBlockNumber,
|
|
1741
1956
|
~maxBlockNumber,
|
|
@@ -1754,7 +1969,7 @@ let make = (
|
|
|
1754
1969
|
latestOnBlockBlockNumber,
|
|
1755
1970
|
normalSelection,
|
|
1756
1971
|
blockLag,
|
|
1757
|
-
|
|
1972
|
+
onBlockRegistrations,
|
|
1758
1973
|
maxOnBlockBufferSize,
|
|
1759
1974
|
knownHeight,
|
|
1760
1975
|
buffer,
|
|
@@ -2004,17 +2219,6 @@ let getProgressPercentage = (fetchState: t) => {
|
|
|
2004
2219
|
}
|
|
2005
2220
|
}
|
|
2006
2221
|
|
|
2007
|
-
// Progress a specific block sits at along the chain, used to order queries from
|
|
2008
|
-
// different chains: a query starting further back (lower %) is fetched first.
|
|
2009
|
-
let getProgressPercentageAt = (fetchState: t, ~blockNumber) => {
|
|
2010
|
-
switch fetchState.firstEventBlock {
|
|
2011
|
-
| None => 0.
|
|
2012
|
-
| Some(firstEventBlock) =>
|
|
2013
|
-
let totalRange = fetchState.knownHeight - firstEventBlock
|
|
2014
|
-
totalRange <= 0 ? 0. : (blockNumber - firstEventBlock)->Int.toFloat /. totalRange->Int.toFloat
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
2222
|
let sortForBatch = {
|
|
2019
2223
|
let hasFullBatch = ({buffer} as fetchState: t, ~batchSizeTarget) => {
|
|
2020
2224
|
switch buffer->Array.get(batchSizeTarget - 1) {
|