envio 3.5.0-alpha.0 → 3.5.0-alpha.2
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 +10 -6
- package/package.json +6 -6
- package/src/BatchProcessing.res +13 -0
- package/src/BatchProcessing.res.mjs +4 -0
- package/src/Bin.res +2 -2
- package/src/ChainFetching.res +5 -5
- package/src/ChainFetching.res.mjs +3 -4
- package/src/ChainState.res +44 -36
- package/src/ChainState.res.mjs +36 -31
- package/src/ChainState.resi +2 -12
- package/src/Change.res +3 -3
- package/src/Config.res +4 -4
- package/src/Config.res.mjs +1 -10
- package/src/Core.res +4 -35
- package/src/Core.res.mjs +1 -31
- package/src/CrossChainState.res +2 -20
- package/src/CrossChainState.res.mjs +2 -6
- package/src/EntityId.res +15 -0
- package/src/EntityId.res.mjs +9 -0
- package/src/Env.res +16 -0
- package/src/Env.res.mjs +6 -0
- package/src/EventConfigBuilder.res +1 -58
- package/src/EventConfigBuilder.res.mjs +1 -33
- package/src/FetchState.res +663 -484
- package/src/FetchState.res.mjs +491 -440
- package/src/HandlerRegister.res +45 -0
- package/src/HandlerRegister.res.mjs +43 -0
- package/src/InMemoryStore.res +11 -4
- package/src/InMemoryTable.res +12 -10
- package/src/InMemoryTable.res.mjs +6 -5
- package/src/IndexerState.res +59 -2
- package/src/IndexerState.res.mjs +48 -3
- package/src/IndexerState.resi +2 -0
- package/src/Internal.res +17 -8
- package/src/LogSelection.res +2 -1
- package/src/Metrics.res +37 -6
- package/src/Metrics.res.mjs +5 -1
- package/src/Persistence.res +1 -1
- package/src/PgStorage.res +55 -22
- package/src/PgStorage.res.mjs +25 -17
- package/src/SimulateItems.res +2 -2
- package/src/TestIndexer.res +6 -5
- package/src/TestIndexer.res.mjs +3 -2
- package/src/UserContext.res +3 -3
- package/src/Writing.res +12 -2
- package/src/Writing.res.mjs +13 -2
- package/src/bindings/ClickHouse.res +5 -3
- package/src/bindings/ClickHouse.res.mjs +2 -5
- package/src/bindings/Vitest.res +22 -1
- package/src/bindings/Vitest.res.mjs +15 -0
- package/src/db/EntityHistory.res +16 -7
- package/src/db/EntityHistory.res.mjs +7 -6
- package/src/db/Table.res +40 -9
- package/src/db/Table.res.mjs +44 -6
- package/src/sources/AddressSet.res +48 -0
- package/src/sources/AddressSet.res.mjs +11 -0
- package/src/sources/AddressStore.res +152 -0
- package/src/sources/AddressStore.res.mjs +97 -0
- package/src/sources/EvmChain.res +3 -0
- package/src/sources/EvmChain.res.mjs +4 -2
- package/src/sources/EvmHyperSyncSource.res +9 -9
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcClient.res +10 -3
- package/src/sources/EvmRpcClient.res.mjs +3 -3
- package/src/sources/FuelHyperSync.res +4 -3
- package/src/sources/FuelHyperSync.res.mjs +3 -3
- package/src/sources/FuelHyperSync.resi +2 -1
- package/src/sources/FuelHyperSyncClient.res +12 -6
- package/src/sources/FuelHyperSyncClient.res.mjs +2 -2
- package/src/sources/FuelHyperSyncSource.res +7 -4
- package/src/sources/FuelHyperSyncSource.res.mjs +3 -3
- package/src/sources/HyperSync.res +9 -5
- package/src/sources/HyperSync.res.mjs +3 -3
- package/src/sources/HyperSync.resi +3 -2
- package/src/sources/HyperSyncClient.res +24 -10
- package/src/sources/HyperSyncClient.res.mjs +4 -4
- package/src/sources/RpcSource.res +71 -67
- package/src/sources/RpcSource.res.mjs +4 -4
- package/src/sources/SimulateSource.res +26 -11
- package/src/sources/SimulateSource.res.mjs +13 -5
- package/src/sources/Source.res +8 -4
- package/src/sources/SourceManager.res +2 -3
- package/src/sources/SourceManager.res.mjs +2 -3
- package/src/sources/Svm.res +1 -2
- package/src/sources/Svm.res.mjs +1 -1
- package/src/sources/SvmHyperSyncClient.res +14 -4
- package/src/sources/SvmHyperSyncClient.res.mjs +2 -2
- package/src/sources/SvmHyperSyncSource.res +11 -6
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/src/IndexingAddresses.res +0 -108
- package/src/IndexingAddresses.res.mjs +0 -101
- package/src/IndexingAddresses.resi +0 -34
package/src/FetchState.res
CHANGED
|
@@ -9,18 +9,27 @@ type blockNumberAndLogIndex = {blockNumber: int, logIndex: int}
|
|
|
9
9
|
|
|
10
10
|
type selection = {
|
|
11
11
|
onEventRegistrations: array<Internal.onEventRegistration>,
|
|
12
|
+
// Whether the partition's queries are built from its own address list
|
|
13
|
+
// (server-side address filtering). This is the *partition* property; not to
|
|
14
|
+
// be confused with the per-registration `Internal.onEventRegistration.dependsOnAddresses`.
|
|
12
15
|
dependsOnAddresses: bool,
|
|
16
|
+
// Contract names this query fetches address-free even though their
|
|
17
|
+
// registrations depend on addresses. The source passes these to the client so
|
|
18
|
+
// their log selections carry no server-side address filter; the client then
|
|
19
|
+
// gates each item against the chain's address store instead of the
|
|
20
|
+
// partition's set. Absent for normal partitions.
|
|
21
|
+
clientFilteredContracts?: array<string>,
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
type pendingQuery = {
|
|
16
25
|
fromBlock: int,
|
|
17
26
|
toBlock: option<int>,
|
|
18
27
|
isChunk: bool,
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
//
|
|
28
|
+
// Server maxNumLogs-style cap for this in-flight query. None for bounded
|
|
29
|
+
// chunk/gap-fill queries, which send no cap (their toBlock is the bound).
|
|
30
|
+
itemsTarget: option<int>,
|
|
31
|
+
// Estimated items this in-flight query will return, carried from the query so
|
|
32
|
+
// the shared buffer budget can account for what's already being fetched.
|
|
24
33
|
itemsEst: int,
|
|
25
34
|
// Stores latestFetchedBlock when query completes. Only needed to persist
|
|
26
35
|
// timestamp while earlier queries are still pending before updating
|
|
@@ -40,10 +49,13 @@ type partition = {
|
|
|
40
49
|
// which added all its events to the queue
|
|
41
50
|
latestFetchedBlock: blockNumberAndTimestamp,
|
|
42
51
|
selection: selection,
|
|
43
|
-
|
|
52
|
+
// The partition's slice of the chain's address index. Ordered by
|
|
53
|
+
// (effectiveStartBlock, address) inside Rust, so a partition layout is a pure
|
|
54
|
+
// function of which addresses it holds — not of the order they arrived in.
|
|
55
|
+
addresses: AddressSet.t,
|
|
44
56
|
mergeBlock: option<int>,
|
|
45
57
|
// When set, partition indexes a single dynamic contract type.
|
|
46
|
-
//
|
|
58
|
+
// `addresses` must then contain only addresses for this contract.
|
|
47
59
|
dynamicContract: option<string>,
|
|
48
60
|
// Mutated in place and shared across fetchState versions (updateInternal
|
|
49
61
|
// copies the record, not this array): startFetchingQueries inserts,
|
|
@@ -72,44 +84,29 @@ type query = {
|
|
|
72
84
|
fromBlock: int,
|
|
73
85
|
toBlock: option<int>,
|
|
74
86
|
isChunk: bool,
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
//
|
|
87
|
+
// Server-side maxNumLogs-style cap. Some only for open-ended probes, whose
|
|
88
|
+
// range isn't otherwise bounded; None for bounded chunk/gap-fill queries,
|
|
89
|
+
// whose toBlock already bounds the response so a cap would only self-truncate
|
|
90
|
+
// (worse under client-side address filtering, which counts filtered-out items
|
|
91
|
+
// toward the cap).
|
|
92
|
+
itemsTarget: option<int>,
|
|
93
|
+
// Density × the query's block range for a known-density partition, the
|
|
94
|
+
// query's budget share otherwise. This is the unit the chain's per-tick
|
|
95
|
+
// budget is reserved/consumed in and that sizes every query.
|
|
83
96
|
itemsEst: int,
|
|
84
97
|
selection: selection,
|
|
85
|
-
|
|
98
|
+
addresses: AddressSet.t,
|
|
86
99
|
}
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
// routing. 1:1 today (each address belongs to one contract), so no key
|
|
90
|
-
// collisions. Memoized on the addressesByContractName object so a partition's
|
|
91
|
-
// many responses share one derivation and a large factory never rebuilds the
|
|
92
|
-
// whole index; sound because the dict is immutable after construction (every
|
|
93
|
-
// mutation produces a new dict).
|
|
94
|
-
let deriveContractNameByAddress: dict<array<Address.t>> => dict<
|
|
95
|
-
string,
|
|
96
|
-
> = Utils.WeakMap.memoize(addressesByContractName => {
|
|
97
|
-
let result = Dict.make()
|
|
98
|
-
addressesByContractName->Utils.Dict.forEachWithKey((addresses, contractName) => {
|
|
99
|
-
for i in 0 to addresses->Array.length - 1 {
|
|
100
|
-
result->Dict.set(addresses->Array.getUnsafe(i)->Address.toString, contractName)
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
result
|
|
104
|
-
})
|
|
101
|
+
let withAddresses = (p: partition, addresses: AddressSet.t) => {...p, addresses}
|
|
105
102
|
|
|
106
|
-
//
|
|
103
|
+
// itemsEst for a query over [fromBlock, toBlock] at the given event density
|
|
107
104
|
// (items/block). toBlock None is the open-ended tail, capped at
|
|
108
105
|
// chainTargetBlock — the soft per-tick horizon the owning chain wants to reach
|
|
109
106
|
// (see getNextQuery).
|
|
110
107
|
let densityItemsTarget = (~density, ~fromBlock, ~toBlock, ~chainTargetBlock) => {
|
|
111
|
-
// Floor at 1: the reservation
|
|
112
|
-
//
|
|
108
|
+
// Floor at 1: this is the budget reservation, and for a probe also its server
|
|
109
|
+
// cap — a 0 cap would ask the backend for nothing.
|
|
113
110
|
Pervasives.max(
|
|
114
111
|
1,
|
|
115
112
|
((toBlock->Option.getOr(chainTargetBlock) - fromBlock + 1)->Int.toFloat *. density)
|
|
@@ -159,6 +156,12 @@ module OptimizedPartitions = {
|
|
|
159
156
|
// Tracks all contract names that have been dynamically added.
|
|
160
157
|
// Never reset - used to determine when to split existing partitions.
|
|
161
158
|
dynamicContracts: Utils.Set.t<string>,
|
|
159
|
+
// Contract names switched to client-side address filtering once their
|
|
160
|
+
// registered address count crossed the server-side threshold. Sticky for
|
|
161
|
+
// the run: their addresses live only in the index, their events are fetched
|
|
162
|
+
// by the single address-free partition, and new dynamic addresses get a
|
|
163
|
+
// bounded backfill up to its frontier instead of a standing partition.
|
|
164
|
+
clientFilteredContracts: Utils.Set.t<string>,
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
@inline
|
|
@@ -184,10 +187,7 @@ module OptimizedPartitions = {
|
|
|
184
187
|
~maxAddrInPartition: int,
|
|
185
188
|
~nextPartitionIndexRef: ref<int>,
|
|
186
189
|
) => {
|
|
187
|
-
let combinedAddresses =
|
|
188
|
-
p1.addressesByContractName
|
|
189
|
-
->Dict.getUnsafe(contractName)
|
|
190
|
-
->Array.concat(p2.addressesByContractName->Dict.getUnsafe(contractName))
|
|
190
|
+
let combinedAddresses = p1.addresses->AddressSet.merge(p2.addresses)
|
|
191
191
|
|
|
192
192
|
let p1Below = p1.latestFetchedBlock.blockNumber < potentialMergeBlock
|
|
193
193
|
let p2Below = p2.latestFetchedBlock.blockNumber < potentialMergeBlock
|
|
@@ -224,7 +224,7 @@ module OptimizedPartitions = {
|
|
|
224
224
|
selection: p1.selection,
|
|
225
225
|
latestFetchedBlock: {blockNumber: potentialMergeBlock, blockTimestamp: 0},
|
|
226
226
|
mergeBlock: None,
|
|
227
|
-
|
|
227
|
+
addresses: p1.addresses, // replaced below
|
|
228
228
|
mutPendingQueries: [],
|
|
229
229
|
sourceRangeCapacity: minRange,
|
|
230
230
|
prevSourceRangeCapacity: minRange,
|
|
@@ -234,29 +234,28 @@ module OptimizedPartitions = {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
// Apply address split on the continuing partition
|
|
237
|
-
if combinedAddresses->
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
237
|
+
if combinedAddresses->AddressSet.size > maxAddrInPartition {
|
|
238
|
+
completed
|
|
239
|
+
->Array.push(
|
|
240
|
+
continuingBase->withAddresses(
|
|
241
|
+
combinedAddresses->AddressSet.slice(~offset=0, ~limit=Some(maxAddrInPartition)),
|
|
242
|
+
),
|
|
243
|
+
)
|
|
244
|
+
->ignore
|
|
245
245
|
let restId = nextPartitionIndexRef.contents->Int.toString
|
|
246
246
|
nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
|
|
247
247
|
completed
|
|
248
248
|
->Array.push({
|
|
249
|
-
...continuingBase
|
|
249
|
+
...continuingBase->withAddresses(
|
|
250
|
+
combinedAddresses->AddressSet.slice(~offset=maxAddrInPartition, ~limit=None),
|
|
251
|
+
),
|
|
250
252
|
id: restId,
|
|
251
|
-
addressesByContractName: abcRest,
|
|
252
253
|
mutPendingQueries: [],
|
|
253
254
|
})
|
|
254
255
|
->ignore
|
|
255
256
|
completed
|
|
256
257
|
} else {
|
|
257
|
-
|
|
258
|
-
abc->Dict.set(contractName, combinedAddresses)
|
|
259
|
-
completed->Array.push({...continuingBase, addressesByContractName: abc})->ignore
|
|
258
|
+
completed->Array.push(continuingBase->withAddresses(combinedAddresses))->ignore
|
|
260
259
|
completed
|
|
261
260
|
}
|
|
262
261
|
}
|
|
@@ -283,6 +282,7 @@ module OptimizedPartitions = {
|
|
|
283
282
|
~maxAddrInPartition,
|
|
284
283
|
~nextPartitionIndex: int,
|
|
285
284
|
~dynamicContracts: Utils.Set.t<string>,
|
|
285
|
+
~clientFilteredContracts: Utils.Set.t<string>,
|
|
286
286
|
) => {
|
|
287
287
|
let newPartitions = []
|
|
288
288
|
let mergingPartitions = Dict.make()
|
|
@@ -303,7 +303,7 @@ module OptimizedPartitions = {
|
|
|
303
303
|
{mergeBlock: Some(_)} =>
|
|
304
304
|
newPartitions->Array.push(p)->ignore
|
|
305
305
|
| {dynamicContract: Some(contractName)} =>
|
|
306
|
-
let pAddressesCount = p.
|
|
306
|
+
let pAddressesCount = p.addresses->AddressSet.countFor(contractName)
|
|
307
307
|
// Compute merge block: last pending query's toBlock, or lfb if idle
|
|
308
308
|
let potentialMergeBlock = switch p.mutPendingQueries->Utils.Array.last {
|
|
309
309
|
| Some({isChunk: true, toBlock: Some(toBlock)}) => Some(toBlock)
|
|
@@ -416,6 +416,7 @@ module OptimizedPartitions = {
|
|
|
416
416
|
maxAddrInPartition,
|
|
417
417
|
nextPartitionIndex: nextPartitionIndexRef.contents,
|
|
418
418
|
dynamicContracts,
|
|
419
|
+
clientFilteredContracts,
|
|
419
420
|
}
|
|
420
421
|
}
|
|
421
422
|
|
|
@@ -467,14 +468,37 @@ module OptimizedPartitions = {
|
|
|
467
468
|
}
|
|
468
469
|
}
|
|
469
470
|
|
|
470
|
-
let handleQueryResponse = (
|
|
471
|
+
let rec handleQueryResponse = (
|
|
472
|
+
optimizedPartitions: t,
|
|
473
|
+
~query,
|
|
474
|
+
~knownHeight,
|
|
475
|
+
~itemsCount,
|
|
476
|
+
~latestFetchedBlock: blockNumberAndTimestamp,
|
|
477
|
+
) =>
|
|
478
|
+
switch optimizedPartitions.entities->Utils.Dict.dangerouslyGetNonOption(query.partitionId) {
|
|
479
|
+
// The partition was absorbed into the address-free client-filtered partition
|
|
480
|
+
// while this query was in flight. Its items are still merged into the buffer
|
|
481
|
+
// by the caller (and deduped); there's no partition bookkeeping left to do,
|
|
482
|
+
// and its reservation is released by ChainState regardless.
|
|
483
|
+
| None => optimizedPartitions
|
|
484
|
+
| Some(p) =>
|
|
485
|
+
optimizedPartitions->handleQueryResponseForPartition(
|
|
486
|
+
~p,
|
|
487
|
+
~query,
|
|
488
|
+
~knownHeight,
|
|
489
|
+
~itemsCount,
|
|
490
|
+
~latestFetchedBlock,
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
and handleQueryResponseForPartition = (
|
|
471
495
|
optimizedPartitions: t,
|
|
496
|
+
~p: partition,
|
|
472
497
|
~query,
|
|
473
498
|
~knownHeight,
|
|
474
499
|
~itemsCount,
|
|
475
500
|
~latestFetchedBlock: blockNumberAndTimestamp,
|
|
476
501
|
) => {
|
|
477
|
-
let p = optimizedPartitions->getOrThrow(~partitionId=query.partitionId)
|
|
478
502
|
let mutEntities = optimizedPartitions.entities->Utils.Dict.shallowCopy
|
|
479
503
|
|
|
480
504
|
// Mark query as fetched
|
|
@@ -508,8 +532,12 @@ module OptimizedPartitions = {
|
|
|
508
532
|
if latestFetchedBlock.blockNumber < queryToBlock {
|
|
509
533
|
// Partial response is direct capacity evidence — unless it was
|
|
510
534
|
// truncated by our own itemsTarget cap: that reflects the
|
|
511
|
-
// reservation we asked for, not what the server could return.
|
|
512
|
-
|
|
535
|
+
// reservation we asked for, not what the server could return. A
|
|
536
|
+
// capless bounded query can only have been truncated by the source.
|
|
537
|
+
switch query.itemsTarget {
|
|
538
|
+
| None => true
|
|
539
|
+
| Some(itemsTarget) => itemsCount < itemsTarget
|
|
540
|
+
}
|
|
513
541
|
} else {
|
|
514
542
|
// A full response updates only when the query's intended range
|
|
515
543
|
// covers at least the partition's current chunk range — meaning it
|
|
@@ -549,6 +577,7 @@ module OptimizedPartitions = {
|
|
|
549
577
|
~maxAddrInPartition=optimizedPartitions.maxAddrInPartition,
|
|
550
578
|
~nextPartitionIndex=optimizedPartitions.nextPartitionIndex,
|
|
551
579
|
~dynamicContracts=optimizedPartitions.dynamicContracts,
|
|
580
|
+
~clientFilteredContracts=optimizedPartitions.clientFilteredContracts,
|
|
552
581
|
)
|
|
553
582
|
} else {
|
|
554
583
|
let updatedMainPartition = {
|
|
@@ -598,6 +627,7 @@ module OptimizedPartitions = {
|
|
|
598
627
|
~maxAddrInPartition=optimizedPartitions.maxAddrInPartition,
|
|
599
628
|
~nextPartitionIndex=optimizedPartitions.nextPartitionIndex,
|
|
600
629
|
~dynamicContracts=optimizedPartitions.dynamicContracts,
|
|
630
|
+
~clientFilteredContracts=optimizedPartitions.clientFilteredContracts,
|
|
601
631
|
)
|
|
602
632
|
}
|
|
603
633
|
}
|
|
@@ -617,8 +647,6 @@ type t = {
|
|
|
617
647
|
startBlock: int,
|
|
618
648
|
endBlock: option<int>,
|
|
619
649
|
normalSelection: selection,
|
|
620
|
-
// By contract name
|
|
621
|
-
contractConfigs: dict<IndexingAddresses.contractConfig>,
|
|
622
650
|
// Not used for logic - only metadata
|
|
623
651
|
chainId: int,
|
|
624
652
|
// The block number of the latest block which was added to the queue
|
|
@@ -638,6 +666,10 @@ type t = {
|
|
|
638
666
|
onBlockRegistrations: array<Internal.onBlockRegistration>,
|
|
639
667
|
knownHeight: int,
|
|
640
668
|
firstEventBlock: option<int>,
|
|
669
|
+
// Per-contract registered-address count past which a dynamic contract is
|
|
670
|
+
// switched to client-side filtering. None disables the switch, leaving every
|
|
671
|
+
// contract filtered server-side.
|
|
672
|
+
clientFilterAddressThreshold: option<int>,
|
|
641
673
|
}
|
|
642
674
|
|
|
643
675
|
@inline
|
|
@@ -892,7 +924,6 @@ let updateInternal = (
|
|
|
892
924
|
let updatedFetchState = {
|
|
893
925
|
startBlock: fetchState.startBlock,
|
|
894
926
|
endBlock: fetchState.endBlock,
|
|
895
|
-
contractConfigs: fetchState.contractConfigs,
|
|
896
927
|
normalSelection: fetchState.normalSelection,
|
|
897
928
|
chainId: fetchState.chainId,
|
|
898
929
|
onBlockRegistrations: fetchState.onBlockRegistrations,
|
|
@@ -907,43 +938,238 @@ let updateInternal = (
|
|
|
907
938
|
| blockItems => base->mergeIntoBuffer(blockItems)
|
|
908
939
|
},
|
|
909
940
|
firstEventBlock: fetchState.firstEventBlock,
|
|
941
|
+
clientFilterAddressThreshold: fetchState.clientFilterAddressThreshold,
|
|
910
942
|
}
|
|
911
943
|
|
|
912
944
|
updatedFetchState
|
|
913
945
|
}
|
|
914
946
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
~
|
|
947
|
+
// Move a contract to client-side address filtering, recording why.
|
|
948
|
+
let addClientFilteredContract = (
|
|
949
|
+
clientFilteredContracts: Utils.Set.t<string>,
|
|
950
|
+
~contractName,
|
|
951
|
+
~chainId,
|
|
952
|
+
~addressCount,
|
|
953
|
+
~threshold,
|
|
919
954
|
) => {
|
|
920
|
-
|
|
955
|
+
clientFilteredContracts->Utils.Set.add(contractName)->ignore
|
|
956
|
+
Logging.createChild(
|
|
921
957
|
~params={
|
|
922
|
-
"chainId":
|
|
923
|
-
"
|
|
924
|
-
"
|
|
925
|
-
"
|
|
958
|
+
"chainId": chainId,
|
|
959
|
+
"contractName": contractName,
|
|
960
|
+
"addressCount": addressCount,
|
|
961
|
+
"threshold": threshold,
|
|
926
962
|
},
|
|
963
|
+
)->Logging.childTrace(
|
|
964
|
+
"Switching contract to client-side address filtering: registered address count crossed the server-side threshold.",
|
|
927
965
|
)
|
|
928
|
-
logger->Logging.childWarn(`Skipping contract registration: Contract address is already registered for one contract and cannot be registered for another contract.`)
|
|
929
966
|
}
|
|
930
967
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
968
|
+
// The block a partition will have fetched to once everything on its pending
|
|
969
|
+
// queue lands — not the block it has fetched to now. A backfill has to reach it
|
|
970
|
+
// rather than the frontier: a query that was routed before this batch's
|
|
971
|
+
// addresses reached the address store carries no events for them, yet it still
|
|
972
|
+
// advances the frontier over its range on arrival, and nothing else would ever
|
|
973
|
+
// refetch it. An in-flight open-ended query has no toBlock of its own; it can't
|
|
974
|
+
// return past the chain's known height, so that bounds it.
|
|
975
|
+
let claimedFetchedBlock = (p: partition, ~knownHeight) =>
|
|
976
|
+
p.mutPendingQueries->Array.reduce(p.latestFetchedBlock.blockNumber, (max, q) =>
|
|
977
|
+
Pervasives.max(
|
|
978
|
+
max,
|
|
979
|
+
switch q.fetchedBlock {
|
|
980
|
+
| Some({blockNumber}) => blockNumber
|
|
981
|
+
| None => q.toBlock->Option.getOr(knownHeight)
|
|
982
|
+
},
|
|
983
|
+
)
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
// Fold every client-filtered contract's server-side partitions into client-side
|
|
987
|
+
// fetching without tearing down established state:
|
|
988
|
+
// - The standing address-free partition (no mergeBlock) keeps its id, frontier,
|
|
989
|
+
// in-flight queries and learned density. Only when a newly-switched contract
|
|
990
|
+
// must be added does its selection change — under a fresh id, so in-flight
|
|
991
|
+
// responses built from the old selection are orphaned instead of advancing
|
|
992
|
+
// the frontier past ranges the new contract wasn't fetched for.
|
|
993
|
+
// - Partitions absorbed below the standing partition's claimed block
|
|
994
|
+
// (single-contract dynamic partitions and config partitions all of whose
|
|
995
|
+
// contracts are client-filtered, plus any prior backfill) become one bounded
|
|
996
|
+
// backfill partition covering [their min frontier, that claimed block]:
|
|
997
|
+
// getNextQuery caps its queries at mergeBlock and handleQueryResponse deletes
|
|
998
|
+
// it on arrival. The overlap it re-delivers is deduped by mergeIntoBuffer, and
|
|
999
|
+
// the re-fetch doubles as history for freshly registered addresses: events
|
|
1000
|
+
// dropped before the address was registered now pass the address gate.
|
|
1001
|
+
// - A partition mixing client-filtered and server-side contracts stays,
|
|
1002
|
+
// stripped of the client-filtered contracts' addresses — the address-free
|
|
1003
|
+
// partition covers those logs, so fetching them server-side too would only
|
|
1004
|
+
// produce duplicates. Its frontier bounds the backfill from below so the
|
|
1005
|
+
// stripped contracts lose no range.
|
|
1006
|
+
//
|
|
1007
|
+
// Registrations come from the address-free partitions plus `normalSelection`
|
|
1008
|
+
// filtered to client-filtered contracts, so coverage never depends on which
|
|
1009
|
+
// partitions happened to be absorbed (a stripped contract may have no
|
|
1010
|
+
// absorbable partition at all).
|
|
1011
|
+
let collapseClientFilteredContracts = (
|
|
1012
|
+
partitions: array<partition>,
|
|
1013
|
+
~clientFilteredContracts: Utils.Set.t<string>,
|
|
1014
|
+
~normalSelection: selection,
|
|
1015
|
+
~nextPartitionIndexRef: ref<int>,
|
|
1016
|
+
~addressStore: AddressStore.t,
|
|
1017
|
+
~knownHeight: int,
|
|
1018
|
+
) => {
|
|
1019
|
+
if clientFilteredContracts->Utils.Set.size === 0 {
|
|
1020
|
+
partitions
|
|
1021
|
+
} else {
|
|
1022
|
+
let kept = []
|
|
1023
|
+
let standingRef = ref(None)
|
|
1024
|
+
let backfills = []
|
|
1025
|
+
let absorbedPartitions = []
|
|
1026
|
+
let strippedFrontiers = []
|
|
1027
|
+
|
|
1028
|
+
partitions->Array.forEach(p =>
|
|
1029
|
+
switch p {
|
|
1030
|
+
| {selection: {dependsOnAddresses: false}, mergeBlock: None}
|
|
1031
|
+
if standingRef.contents->Option.isNone =>
|
|
1032
|
+
standingRef := Some(p)
|
|
1033
|
+
| {selection: {dependsOnAddresses: false}, mergeBlock: Some(_)} =>
|
|
1034
|
+
backfills->Array.push(p)->ignore
|
|
1035
|
+
| {selection: {dependsOnAddresses: false}} => absorbedPartitions->Array.push(p)->ignore
|
|
1036
|
+
| _ =>
|
|
1037
|
+
let contractNames = p.addresses->AddressSet.contractNames
|
|
1038
|
+
let serverSideNames =
|
|
1039
|
+
contractNames->Array.filter(c => !(clientFilteredContracts->Utils.Set.has(c)))
|
|
1040
|
+
if serverSideNames->Array.length === contractNames->Array.length {
|
|
1041
|
+
kept->Array.push(p)->ignore
|
|
1042
|
+
} else if serverSideNames->Utils.Array.isEmpty {
|
|
1043
|
+
absorbedPartitions->Array.push(p)->ignore
|
|
1044
|
+
} else {
|
|
1045
|
+
strippedFrontiers->Array.push(p.latestFetchedBlock)->ignore
|
|
1046
|
+
kept
|
|
1047
|
+
->Array.push(p->withAddresses(p.addresses->AddressSet.filterByContracts(serverSideNames)))
|
|
1048
|
+
->ignore
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
)
|
|
938
1052
|
|
|
939
|
-
let
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1053
|
+
let selectionChanged = switch standingRef.contents {
|
|
1054
|
+
| Some(standing) =>
|
|
1055
|
+
standing.selection.clientFilteredContracts->Option.mapOr(0, Array.length) !==
|
|
1056
|
+
clientFilteredContracts->Utils.Set.size
|
|
1057
|
+
| None => true
|
|
944
1058
|
}
|
|
945
|
-
|
|
946
|
-
|
|
1059
|
+
|
|
1060
|
+
if (
|
|
1061
|
+
absorbedPartitions->Utils.Array.isEmpty &&
|
|
1062
|
+
strippedFrontiers->Utils.Array.isEmpty &&
|
|
1063
|
+
!selectionChanged
|
|
1064
|
+
) {
|
|
1065
|
+
// Nothing to fold in and no newly-switched contract: leave the standing
|
|
1066
|
+
// partition (and any in-progress backfill) untouched.
|
|
1067
|
+
partitions
|
|
1068
|
+
} else {
|
|
1069
|
+
let minFrontierRef: ref<option<blockNumberAndTimestamp>> = ref(None)
|
|
1070
|
+
let considerFrontier = (b: blockNumberAndTimestamp) =>
|
|
1071
|
+
switch minFrontierRef.contents {
|
|
1072
|
+
| Some(m) if m.blockNumber <= b.blockNumber => ()
|
|
1073
|
+
| _ => minFrontierRef := Some(b)
|
|
1074
|
+
}
|
|
1075
|
+
absorbedPartitions->Array.forEach(p => considerFrontier(p.latestFetchedBlock))
|
|
1076
|
+
backfills->Array.forEach(p => considerFrontier(p.latestFetchedBlock))
|
|
1077
|
+
strippedFrontiers->Array.forEach(considerFrontier)
|
|
1078
|
+
|
|
1079
|
+
let regByIndex = Dict.make()
|
|
1080
|
+
let addRegs = (regs: array<Internal.onEventRegistration>) =>
|
|
1081
|
+
regs->Array.forEach(reg => regByIndex->Dict.set(reg.index->Int.toString, reg))
|
|
1082
|
+
switch standingRef.contents {
|
|
1083
|
+
| Some(standing) => addRegs(standing.selection.onEventRegistrations)
|
|
1084
|
+
| None => ()
|
|
1085
|
+
}
|
|
1086
|
+
absorbedPartitions->Array.forEach(p =>
|
|
1087
|
+
if !p.selection.dependsOnAddresses {
|
|
1088
|
+
addRegs(p.selection.onEventRegistrations)
|
|
1089
|
+
}
|
|
1090
|
+
)
|
|
1091
|
+
addRegs(
|
|
1092
|
+
normalSelection.onEventRegistrations->Array.filter(reg =>
|
|
1093
|
+
clientFilteredContracts->Utils.Set.has(reg.eventConfig.contractName)
|
|
1094
|
+
),
|
|
1095
|
+
)
|
|
1096
|
+
let newSelection = {
|
|
1097
|
+
dependsOnAddresses: false,
|
|
1098
|
+
onEventRegistrations: regByIndex->Dict.valuesToArray,
|
|
1099
|
+
clientFilteredContracts: clientFilteredContracts->Utils.Set.toArray,
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
switch standingRef.contents {
|
|
1103
|
+
| None =>
|
|
1104
|
+
// First switch: no standing partition to preserve, so create it at the
|
|
1105
|
+
// min frontier directly — the whole range above is unfetched for the
|
|
1106
|
+
// client-filtered side anyway.
|
|
1107
|
+
switch minFrontierRef.contents {
|
|
1108
|
+
| None => kept
|
|
1109
|
+
| Some(minFrontier) =>
|
|
1110
|
+
let minRange = getMinQueryRange(absorbedPartitions)
|
|
1111
|
+
let id = nextPartitionIndexRef.contents->Int.toString
|
|
1112
|
+
nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
|
|
1113
|
+
kept
|
|
1114
|
+
->Array.push({
|
|
1115
|
+
id,
|
|
1116
|
+
latestFetchedBlock: minFrontier,
|
|
1117
|
+
selection: newSelection,
|
|
1118
|
+
addresses: addressStore->AddressStore.emptySet,
|
|
1119
|
+
mergeBlock: None,
|
|
1120
|
+
dynamicContract: None,
|
|
1121
|
+
mutPendingQueries: [],
|
|
1122
|
+
sourceRangeCapacity: minRange,
|
|
1123
|
+
prevSourceRangeCapacity: minRange,
|
|
1124
|
+
eventDensity: None,
|
|
1125
|
+
latestSourceRangeCapacityUpdateBlock: 0,
|
|
1126
|
+
})
|
|
1127
|
+
->ignore
|
|
1128
|
+
kept
|
|
1129
|
+
}
|
|
1130
|
+
| Some(standing) =>
|
|
1131
|
+
let standingOut = if selectionChanged {
|
|
1132
|
+
let id = nextPartitionIndexRef.contents->Int.toString
|
|
1133
|
+
nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
|
|
1134
|
+
{...standing, id, selection: newSelection, mutPendingQueries: []}
|
|
1135
|
+
} else {
|
|
1136
|
+
standing
|
|
1137
|
+
}
|
|
1138
|
+
kept->Array.push(standingOut)->ignore
|
|
1139
|
+
// Read off standingOut, not standing: a selection change orphans the
|
|
1140
|
+
// in-flight queries (fresh id, empty queue), so there the frontier is
|
|
1141
|
+
// all the partition will ever claim.
|
|
1142
|
+
let catchUpToBlock = standingOut->claimedFetchedBlock(~knownHeight)
|
|
1143
|
+
switch minFrontierRef.contents {
|
|
1144
|
+
| Some(minFrontier) if minFrontier.blockNumber < catchUpToBlock =>
|
|
1145
|
+
let id = nextPartitionIndexRef.contents->Int.toString
|
|
1146
|
+
nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
|
|
1147
|
+
kept
|
|
1148
|
+
->Array.push({
|
|
1149
|
+
id,
|
|
1150
|
+
latestFetchedBlock: minFrontier,
|
|
1151
|
+
selection: newSelection,
|
|
1152
|
+
addresses: addressStore->AddressStore.emptySet,
|
|
1153
|
+
mergeBlock: Some(catchUpToBlock),
|
|
1154
|
+
dynamicContract: None,
|
|
1155
|
+
mutPendingQueries: [],
|
|
1156
|
+
// Same query shape as the standing partition, so inherit its
|
|
1157
|
+
// learned range and density instead of probing from scratch.
|
|
1158
|
+
sourceRangeCapacity: standing.sourceRangeCapacity,
|
|
1159
|
+
prevSourceRangeCapacity: standing.prevSourceRangeCapacity,
|
|
1160
|
+
eventDensity: standing.eventDensity,
|
|
1161
|
+
latestSourceRangeCapacityUpdateBlock: 0,
|
|
1162
|
+
})
|
|
1163
|
+
->ignore
|
|
1164
|
+
// An absorbed frontier at/above the claimed block needs no backfill:
|
|
1165
|
+
// nothing has fetched past there yet, so the standing partition still
|
|
1166
|
+
// covers it going forward.
|
|
1167
|
+
| _ => ()
|
|
1168
|
+
}
|
|
1169
|
+
kept
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
947
1173
|
}
|
|
948
1174
|
|
|
949
1175
|
/**
|
|
@@ -953,14 +1179,19 @@ Phase 2: Merge non-dynamic partitions together to reduce unnecessary concurrency
|
|
|
953
1179
|
Returns OptimizedPartitions.t directly.
|
|
954
1180
|
(Dynamic partitions are merged by OptimizedPartitions.make automatically)
|
|
955
1181
|
*/
|
|
956
|
-
let
|
|
957
|
-
|
|
1182
|
+
let createPartitions = (
|
|
1183
|
+
// One set per contract, holding the addresses that still need a partition:
|
|
1184
|
+
// a batch's fresh registrations, or the survivors of a rolled-back partition.
|
|
1185
|
+
~registeringSetsByContract: dict<AddressSet.t>,
|
|
1186
|
+
~addressStore: AddressStore.t,
|
|
958
1187
|
~dynamicContracts: Utils.Set.t<string>,
|
|
1188
|
+
~clientFilteredContracts: Utils.Set.t<string>,
|
|
959
1189
|
~normalSelection: selection,
|
|
960
1190
|
~maxAddrInPartition: int,
|
|
961
1191
|
~nextPartitionIndex: int,
|
|
962
1192
|
~existingPartitions: array<partition>,
|
|
963
1193
|
~progressBlockNumber: int,
|
|
1194
|
+
~knownHeight: int,
|
|
964
1195
|
): // Floor for latestFetchedBlock (use progressBlockNumber from make, or 0 for registerDynamicContracts)
|
|
965
1196
|
OptimizedPartitions.t => {
|
|
966
1197
|
let nextPartitionIndexRef = ref(nextPartitionIndex)
|
|
@@ -969,90 +1200,67 @@ OptimizedPartitions.t => {
|
|
|
969
1200
|
let dynamicPartitions = []
|
|
970
1201
|
let nonDynamicPartitions = []
|
|
971
1202
|
|
|
972
|
-
let contractNames =
|
|
1203
|
+
let contractNames = registeringSetsByContract->Dict.keysToArray
|
|
973
1204
|
for cIdx in 0 to contractNames->Array.length - 1 {
|
|
974
1205
|
let contractName = contractNames->Array.getUnsafe(cIdx)
|
|
975
|
-
let
|
|
976
|
-
let addresses =
|
|
977
|
-
registeringContracts->Dict.keysToArray->(Utils.magic: array<string> => array<Address.t>)
|
|
1206
|
+
let contractSet = registeringSetsByContract->Dict.getUnsafe(contractName)
|
|
978
1207
|
|
|
979
1208
|
let isDynamic = dynamicContracts->Utils.Set.has(contractName)
|
|
980
1209
|
let partitions = isDynamic ? dynamicPartitions : nonDynamicPartitions
|
|
981
1210
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
| Some(nextStartBlockKey) => {
|
|
1003
|
-
let nextStartBlock = nextStartBlockKey->Int.fromString->Option.getUnsafe
|
|
1004
|
-
let shouldJoinCurrentStartBlock =
|
|
1005
|
-
nextStartBlock - startBlockRef.contents < OptimizedPartitions.tooFarBlockRange
|
|
1006
|
-
|
|
1007
|
-
// Addresses with different start blocks within range share a partition;
|
|
1008
|
-
// events before each address's effectiveStartBlock are dropped on the
|
|
1009
|
-
// client side (the event's clientAddressFilter for address-valued
|
|
1010
|
-
// params).
|
|
1011
|
-
if shouldJoinCurrentStartBlock {
|
|
1012
|
-
addressesRef :=
|
|
1013
|
-
addressesRef.contents->Array.concat(byStartBlock->Dict.getUnsafe(nextStartBlockKey))
|
|
1014
|
-
false
|
|
1015
|
-
} else {
|
|
1016
|
-
true
|
|
1017
|
-
}
|
|
1211
|
+
// A set is ordered by effectiveStartBlock, so its start-block groups are
|
|
1212
|
+
// ascending and each group's addresses are a contiguous slice.
|
|
1213
|
+
let groups = contractSet->AddressSet.startBlockGroups
|
|
1214
|
+
let offsetRef = ref(0)
|
|
1215
|
+
let groupIdx = ref(0)
|
|
1216
|
+
while groupIdx.contents < groups->Array.length {
|
|
1217
|
+
let startBlock = (groups->Array.getUnsafe(groupIdx.contents)).startBlock
|
|
1218
|
+
// Addresses with different start blocks within range share a partition;
|
|
1219
|
+
// events before each address's effectiveStartBlock are dropped by the
|
|
1220
|
+
// source's address gate.
|
|
1221
|
+
let countRef = ref(0)
|
|
1222
|
+
let nextIdx = ref(groupIdx.contents)
|
|
1223
|
+
let joining = ref(true)
|
|
1224
|
+
while joining.contents && nextIdx.contents < groups->Array.length {
|
|
1225
|
+
let group = groups->Array.getUnsafe(nextIdx.contents)
|
|
1226
|
+
if group.startBlock - startBlock < OptimizedPartitions.tooFarBlockRange {
|
|
1227
|
+
countRef := countRef.contents + group.count
|
|
1228
|
+
nextIdx := nextIdx.contents + 1
|
|
1229
|
+
} else {
|
|
1230
|
+
joining := false
|
|
1018
1231
|
}
|
|
1019
1232
|
}
|
|
1020
1233
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
switch maybeNextStartBlockKey {
|
|
1049
|
-
| None => ()
|
|
1050
|
-
| Some(nextStartBlockKey) => {
|
|
1051
|
-
startBlockRef := nextStartBlockKey->Int.fromString->Option.getUnsafe
|
|
1052
|
-
addressesRef := byStartBlock->Dict.getUnsafe(nextStartBlockKey)
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1234
|
+
let latestFetchedBlock = {
|
|
1235
|
+
blockNumber: Pervasives.max(startBlock - 1, progressBlockNumber),
|
|
1236
|
+
blockTimestamp: 0,
|
|
1237
|
+
}
|
|
1238
|
+
let remainingRef = ref(countRef.contents)
|
|
1239
|
+
let chunkOffsetRef = ref(offsetRef.contents)
|
|
1240
|
+
while remainingRef.contents > 0 {
|
|
1241
|
+
let take = Pervasives.min(remainingRef.contents, maxAddrInPartition)
|
|
1242
|
+
let pAddresses =
|
|
1243
|
+
contractSet->AddressSet.slice(~offset=chunkOffsetRef.contents, ~limit=Some(take))
|
|
1244
|
+
partitions->Array.push({
|
|
1245
|
+
id: nextPartitionIndexRef.contents->Int.toString,
|
|
1246
|
+
latestFetchedBlock,
|
|
1247
|
+
selection: normalSelection,
|
|
1248
|
+
dynamicContract: isDynamic ? Some(contractName) : None,
|
|
1249
|
+
addresses: pAddresses,
|
|
1250
|
+
mergeBlock: None,
|
|
1251
|
+
mutPendingQueries: [],
|
|
1252
|
+
sourceRangeCapacity: 0,
|
|
1253
|
+
prevSourceRangeCapacity: 0,
|
|
1254
|
+
eventDensity: None,
|
|
1255
|
+
latestSourceRangeCapacityUpdateBlock: 0,
|
|
1256
|
+
})
|
|
1257
|
+
nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
|
|
1258
|
+
chunkOffsetRef := chunkOffsetRef.contents + take
|
|
1259
|
+
remainingRef := remainingRef.contents - take
|
|
1055
1260
|
}
|
|
1261
|
+
|
|
1262
|
+
offsetRef := offsetRef.contents + countRef.contents
|
|
1263
|
+
groupIdx := nextIdx.contents
|
|
1056
1264
|
}
|
|
1057
1265
|
}
|
|
1058
1266
|
|
|
@@ -1072,29 +1280,14 @@ OptimizedPartitions.t => {
|
|
|
1072
1280
|
let currentPBlock = currentP.latestFetchedBlock.blockNumber
|
|
1073
1281
|
let nextPBlock = nextP.latestFetchedBlock.blockNumber
|
|
1074
1282
|
|
|
1075
|
-
|
|
1076
|
-
let totalCount =
|
|
1077
|
-
currentP.addressesByContractName->addressesByContractNameCount +
|
|
1078
|
-
nextP.addressesByContractName->addressesByContractNameCount
|
|
1283
|
+
let totalCount = currentP.addresses->AddressSet.size + nextP.addresses->AddressSet.size
|
|
1079
1284
|
|
|
1080
1285
|
if totalCount > maxAddrInPartition {
|
|
1081
1286
|
// Exceeds address limit - don't merge, keep partitions separate
|
|
1082
1287
|
mergedNonDynamic->Array.push(currentP)->ignore
|
|
1083
1288
|
currentPRef := nextP
|
|
1084
1289
|
} else {
|
|
1085
|
-
|
|
1086
|
-
let mergedAddresses = nextP.addressesByContractName->Utils.Dict.shallowCopy
|
|
1087
|
-
let currentContractNames = currentP.addressesByContractName->Dict.keysToArray
|
|
1088
|
-
for jdx in 0 to currentContractNames->Array.length - 1 {
|
|
1089
|
-
let cn = currentContractNames->Array.getUnsafe(jdx)
|
|
1090
|
-
let currentAddrs = currentP.addressesByContractName->Dict.getUnsafe(cn)
|
|
1091
|
-
switch mergedAddresses->Utils.Dict.dangerouslyGetNonOption(cn) {
|
|
1092
|
-
| Some(existingAddrs) =>
|
|
1093
|
-
// Use concat (non-mutating) to avoid corrupting nextP's arrays
|
|
1094
|
-
mergedAddresses->Dict.set(cn, existingAddrs->Array.concat(currentAddrs))
|
|
1095
|
-
| None => mergedAddresses->Dict.set(cn, currentAddrs)
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1290
|
+
let mergedAddresses = nextP.addresses->AddressSet.merge(currentP.addresses)
|
|
1098
1291
|
|
|
1099
1292
|
let isTooFar = currentPBlock + OptimizedPartitions.tooFarBlockRange < nextPBlock
|
|
1100
1293
|
|
|
@@ -1106,16 +1299,10 @@ OptimizedPartitions.t => {
|
|
|
1106
1299
|
mergeBlock: currentPBlock < nextPBlock ? Some(nextPBlock) : None,
|
|
1107
1300
|
})
|
|
1108
1301
|
->ignore
|
|
1109
|
-
currentPRef :=
|
|
1110
|
-
...nextP,
|
|
1111
|
-
addressesByContractName: mergedAddresses,
|
|
1112
|
-
}
|
|
1302
|
+
currentPRef := nextP->withAddresses(mergedAddresses)
|
|
1113
1303
|
} else {
|
|
1114
1304
|
// Close: push next's addresses into current
|
|
1115
|
-
currentPRef :=
|
|
1116
|
-
...currentP,
|
|
1117
|
-
addressesByContractName: mergedAddresses,
|
|
1118
|
-
}
|
|
1305
|
+
currentPRef := currentP->withAddresses(mergedAddresses)
|
|
1119
1306
|
}
|
|
1120
1307
|
}
|
|
1121
1308
|
|
|
@@ -1127,18 +1314,37 @@ OptimizedPartitions.t => {
|
|
|
1127
1314
|
|
|
1128
1315
|
let mergedPartitions = mergedNonDynamic->Array.concat(dynamicPartitions)
|
|
1129
1316
|
|
|
1130
|
-
// Final step: concat existing partitions with phase 1+2 result
|
|
1317
|
+
// Final step: concat existing partitions with phase 1+2 result, collapse any
|
|
1318
|
+
// client-filtered contracts into the single address-free partition, and optimize.
|
|
1319
|
+
let allPartitions =
|
|
1320
|
+
existingPartitions
|
|
1321
|
+
->Array.concat(mergedPartitions)
|
|
1322
|
+
->collapseClientFilteredContracts(
|
|
1323
|
+
~clientFilteredContracts,
|
|
1324
|
+
~normalSelection,
|
|
1325
|
+
~nextPartitionIndexRef,
|
|
1326
|
+
~addressStore,
|
|
1327
|
+
~knownHeight,
|
|
1328
|
+
)
|
|
1131
1329
|
OptimizedPartitions.make(
|
|
1132
|
-
~partitions=
|
|
1330
|
+
~partitions=allPartitions,
|
|
1133
1331
|
~maxAddrInPartition,
|
|
1134
1332
|
~nextPartitionIndex=nextPartitionIndexRef.contents,
|
|
1135
1333
|
~dynamicContracts,
|
|
1334
|
+
~clientFilteredContracts,
|
|
1136
1335
|
)
|
|
1137
1336
|
}
|
|
1138
1337
|
|
|
1139
1338
|
let registerDynamicContracts = (
|
|
1140
1339
|
fetchState: t,
|
|
1141
|
-
~
|
|
1340
|
+
~addressStore: AddressStore.t,
|
|
1341
|
+
// How far an in-flight open-ended query may end up claiming. These
|
|
1342
|
+
// registrations usually come out of a response that is applied right after
|
|
1343
|
+
// this call, and an unbounded query can reach past the height known when it
|
|
1344
|
+
// was dispatched — so the caller folds that response's own frontier and
|
|
1345
|
+
// height in here. Defaults to what the fetch state knows, which is all
|
|
1346
|
+
// there is to go on when no response is being applied.
|
|
1347
|
+
~claimCeiling=fetchState.knownHeight,
|
|
1142
1348
|
// These are raw items which might have dynamic contracts received from contractRegister call.
|
|
1143
1349
|
// Might contain duplicates which we should filter out
|
|
1144
1350
|
items: array<Internal.item>,
|
|
@@ -1150,167 +1356,130 @@ let registerDynamicContracts = (
|
|
|
1150
1356
|
)
|
|
1151
1357
|
}
|
|
1152
1358
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
//
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
let noEventsAddresses: dict<indexingAddress> = Dict.make()
|
|
1160
|
-
// Batch-level view of all addresses registered so far (across contracts,
|
|
1161
|
-
// including no-events ones), so two contracts registering the same address
|
|
1162
|
-
// within one batch conflict the same way as against indexingAddresses.
|
|
1163
|
-
let registeringAddresses: dict<indexingAddress> = Dict.make()
|
|
1164
|
-
|
|
1359
|
+
// Flatten every item's dcs into one batch, remembering where each came from
|
|
1360
|
+
// so the rejected ones can be spliced back out of their item (which is what
|
|
1361
|
+
// keeps them from being persisted twice).
|
|
1362
|
+
let registrations: array<AddressStore.registration> = []
|
|
1363
|
+
let sourceDcs: array<Internal.dcs> = []
|
|
1364
|
+
let sourceIndexes: array<int> = []
|
|
1165
1365
|
for itemIdx in 0 to items->Array.length - 1 {
|
|
1166
|
-
|
|
1167
|
-
switch item->Internal.getItemDcs {
|
|
1366
|
+
switch items->Array.getUnsafe(itemIdx)->Internal.getItemDcs {
|
|
1168
1367
|
| None => ()
|
|
1169
1368
|
| Some(dcs) =>
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
~contractStartBlock,
|
|
1185
|
-
),
|
|
1186
|
-
}
|
|
1187
|
-
// Prevent registering already indexing contracts
|
|
1188
|
-
switch indexingAddresses->IndexingAddresses.get(dc.address->Address.toString) {
|
|
1189
|
-
| Some(existingContract) =>
|
|
1190
|
-
// FIXME: Instead of filtering out duplicates,
|
|
1191
|
-
// we should check the block number first.
|
|
1192
|
-
// If new registration with earlier block number
|
|
1193
|
-
// we should register it for the missing block range
|
|
1194
|
-
if existingContract.contractName != dc.contractName {
|
|
1195
|
-
fetchState->warnDifferentContractType(~existingContract, ~dc=dcWithStartBlock)
|
|
1196
|
-
} else if existingContract.effectiveStartBlock > dcWithStartBlock.effectiveStartBlock {
|
|
1197
|
-
let logger = Logging.createChild(
|
|
1198
|
-
~params={
|
|
1199
|
-
"chainId": fetchState.chainId,
|
|
1200
|
-
"contractAddress": dc.address->Address.toString,
|
|
1201
|
-
"existingBlockNumber": existingContract.effectiveStartBlock,
|
|
1202
|
-
"newBlockNumber": dcWithStartBlock.effectiveStartBlock,
|
|
1203
|
-
},
|
|
1204
|
-
)
|
|
1205
|
-
logger->Logging.childWarn(`Skipping contract registration: Contract address is already registered at a later block number. Currently registration of the same contract address is not supported by Envio. Reach out to us if it's a problem for you.`)
|
|
1206
|
-
}
|
|
1207
|
-
shouldRemove := true
|
|
1208
|
-
| None =>
|
|
1209
|
-
let shouldUpdate = switch registeringAddresses->Utils.Dict.dangerouslyGetNonOption(
|
|
1210
|
-
dc.address->Address.toString,
|
|
1211
|
-
) {
|
|
1212
|
-
| Some(registeringContract) if registeringContract.contractName != dc.contractName =>
|
|
1213
|
-
fetchState->warnDifferentContractType(
|
|
1214
|
-
~existingContract=registeringContract,
|
|
1215
|
-
~dc=dcWithStartBlock,
|
|
1216
|
-
)
|
|
1217
|
-
false
|
|
1218
|
-
| Some(_) => // Since the DC is registered by an earlier item in the query
|
|
1219
|
-
// FIXME: This unsafely relies on the asc order of the items
|
|
1220
|
-
// which is 99% true, but there were cases when the source ordering was wrong
|
|
1221
|
-
false
|
|
1222
|
-
| None => true
|
|
1223
|
-
}
|
|
1224
|
-
if shouldUpdate {
|
|
1225
|
-
earliestRegisteringEventBlockNumber :=
|
|
1226
|
-
Pervasives.min(
|
|
1227
|
-
earliestRegisteringEventBlockNumber.contents,
|
|
1228
|
-
dcWithStartBlock.effectiveStartBlock,
|
|
1229
|
-
)
|
|
1230
|
-
registeringContractsByContract
|
|
1231
|
-
->Utils.Dict.getOrInsertEmptyDict(dc.contractName)
|
|
1232
|
-
->Dict.set(dc.address->Address.toString, dcWithStartBlock)
|
|
1233
|
-
registeringAddresses->Dict.set(dc.address->Address.toString, dcWithStartBlock)
|
|
1234
|
-
} else {
|
|
1235
|
-
shouldRemove := true
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
| None =>
|
|
1239
|
-
let dcAsIndexingAddress: indexingAddress = {
|
|
1240
|
-
address: dc.address,
|
|
1241
|
-
contractName: dc.contractName,
|
|
1242
|
-
registrationBlock: dc.registrationBlock,
|
|
1243
|
-
effectiveStartBlock: IndexingAddresses.deriveEffectiveStartBlock(
|
|
1244
|
-
~registrationBlock=dc.registrationBlock,
|
|
1245
|
-
~contractStartBlock=None,
|
|
1246
|
-
),
|
|
1247
|
-
}
|
|
1248
|
-
// Prevent duplicate logging/persistence when the same address is
|
|
1249
|
-
// already tracked on fetchState, either from the db on startup or
|
|
1250
|
-
// from an earlier registration in this batch.
|
|
1251
|
-
switch indexingAddresses->IndexingAddresses.get(dc.address->Address.toString) {
|
|
1252
|
-
| Some(existingContract) =>
|
|
1253
|
-
if existingContract.contractName != dc.contractName {
|
|
1254
|
-
fetchState->warnDifferentContractType(~existingContract, ~dc=dcAsIndexingAddress)
|
|
1255
|
-
}
|
|
1256
|
-
shouldRemove := true
|
|
1257
|
-
| None =>
|
|
1258
|
-
switch registeringAddresses->Utils.Dict.dangerouslyGetNonOption(
|
|
1259
|
-
dc.address->Address.toString,
|
|
1260
|
-
) {
|
|
1261
|
-
| Some(existingContract) =>
|
|
1262
|
-
if existingContract.contractName != dc.contractName {
|
|
1263
|
-
fetchState->warnDifferentContractType(~existingContract, ~dc=dcAsIndexingAddress)
|
|
1264
|
-
}
|
|
1265
|
-
// Otherwise already queued for persistence by an earlier item in this batch.
|
|
1266
|
-
shouldRemove := true
|
|
1267
|
-
| None =>
|
|
1268
|
-
let logger = Logging.createChild(
|
|
1269
|
-
~params={
|
|
1270
|
-
"chainId": fetchState.chainId,
|
|
1271
|
-
"contractAddress": dc.address->Address.toString,
|
|
1272
|
-
"contractName": dc.contractName,
|
|
1273
|
-
},
|
|
1274
|
-
)
|
|
1275
|
-
// Persist the address to the db so a future config change that
|
|
1276
|
-
// adds events for this contract can pick it up on restart, but
|
|
1277
|
-
// skip partition registration since there's nothing to fetch.
|
|
1278
|
-
logger->Logging.childWarn(`Persisting contract registration without fetching: Contract doesn't have any events to fetch. It'll be picked up on restart if you add events for the contract.`)
|
|
1279
|
-
noEventsAddresses->Dict.set(dc.address->Address.toString, dcAsIndexingAddress)
|
|
1280
|
-
registeringAddresses->Dict.set(dc.address->Address.toString, dcAsIndexingAddress)
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1369
|
+
for dcIdx in 0 to dcs->Array.length - 1 {
|
|
1370
|
+
let dc = dcs->Array.getUnsafe(dcIdx)
|
|
1371
|
+
registrations
|
|
1372
|
+
->Array.push({
|
|
1373
|
+
address: dc.address,
|
|
1374
|
+
contractName: dc.contractName,
|
|
1375
|
+
registrationBlock: dc.registrationBlock,
|
|
1376
|
+
})
|
|
1377
|
+
->ignore
|
|
1378
|
+
sourceDcs->Array.push(dcs)->ignore
|
|
1379
|
+
sourceIndexes->Array.push(dcIdx)->ignore
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1284
1383
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1384
|
+
// Ids are handed out in registration order, so a cursor taken now selects
|
|
1385
|
+
// exactly what this batch adds.
|
|
1386
|
+
let idCursor = addressStore->AddressStore.nextId
|
|
1387
|
+
// The store resolves each address against both what it already holds and the
|
|
1388
|
+
// batch's own earlier entries, so two contracts claiming one address inside a
|
|
1389
|
+
// single batch conflict the same way as across batches.
|
|
1390
|
+
let verdicts = addressStore->AddressStore.registerBatch(registrations)
|
|
1391
|
+
|
|
1392
|
+
let warn = (~contractAddress, ~params, message) =>
|
|
1393
|
+
Logging.createChild(
|
|
1394
|
+
~params={
|
|
1395
|
+
"chainId": fetchState.chainId,
|
|
1396
|
+
"contractAddress": contractAddress,
|
|
1397
|
+
"details": params,
|
|
1398
|
+
},
|
|
1399
|
+
)->Logging.childWarn(message)
|
|
1400
|
+
|
|
1401
|
+
let registeringContractNames = []
|
|
1402
|
+
let hasNoEventsUpdatesRef = ref(false)
|
|
1403
|
+
for idx in 0 to verdicts->Array.length - 1 {
|
|
1404
|
+
let registration = registrations->Array.getUnsafe(idx)
|
|
1405
|
+
let contractAddress = registration.address->Address.toString
|
|
1406
|
+
let keep = switch verdicts->Array.getUnsafe(idx) {
|
|
1407
|
+
| Added(_) =>
|
|
1408
|
+
if !(registeringContractNames->Array.includes(registration.contractName)) {
|
|
1409
|
+
registeringContractNames->Array.push(registration.contractName)->ignore
|
|
1410
|
+
}
|
|
1411
|
+
true
|
|
1412
|
+
| NoEvents(_) =>
|
|
1413
|
+
hasNoEventsUpdatesRef := true
|
|
1414
|
+
// Persist the address to the db so a future config change that adds
|
|
1415
|
+
// events for this contract can pick it up on restart, but skip partition
|
|
1416
|
+
// registration since there's nothing to fetch.
|
|
1417
|
+
warn(
|
|
1418
|
+
~contractAddress,
|
|
1419
|
+
~params={"contractName": registration.contractName},
|
|
1420
|
+
`Persisting contract registration without fetching: Contract doesn't have any events to fetch. It'll be picked up on restart if you add events for the contract.`,
|
|
1421
|
+
)
|
|
1422
|
+
true
|
|
1423
|
+
| Conflict({existingContractName}) =>
|
|
1424
|
+
warn(
|
|
1425
|
+
~contractAddress,
|
|
1426
|
+
~params={
|
|
1427
|
+
"existingContractType": existingContractName,
|
|
1428
|
+
"newContractType": registration.contractName,
|
|
1429
|
+
},
|
|
1430
|
+
`Skipping contract registration: Contract address is already registered for one contract and cannot be registered for another contract.`,
|
|
1431
|
+
)
|
|
1432
|
+
false
|
|
1433
|
+
| Duplicate({effectiveStartBlock, existingEffectiveStartBlock}) =>
|
|
1434
|
+
// FIXME: Instead of filtering out duplicates, we should check the block
|
|
1435
|
+
// number first. If a new registration has an earlier block number we
|
|
1436
|
+
// should register it for the missing block range.
|
|
1437
|
+
if existingEffectiveStartBlock > effectiveStartBlock {
|
|
1438
|
+
warn(
|
|
1439
|
+
~contractAddress,
|
|
1440
|
+
~params={
|
|
1441
|
+
"existingBlockNumber": existingEffectiveStartBlock,
|
|
1442
|
+
"newBlockNumber": effectiveStartBlock,
|
|
1443
|
+
},
|
|
1444
|
+
`Skipping contract registration: Contract address is already registered at a later block number. Currently registration of the same contract address is not supported by Envio. Reach out to us if it's a problem for you.`,
|
|
1445
|
+
)
|
|
1292
1446
|
}
|
|
1447
|
+
false
|
|
1448
|
+
| Invalid =>
|
|
1449
|
+
warn(
|
|
1450
|
+
~contractAddress,
|
|
1451
|
+
~params={"contractName": registration.contractName},
|
|
1452
|
+
`Skipping contract registration: Not a valid address for this chain's ecosystem.`,
|
|
1453
|
+
)
|
|
1454
|
+
false
|
|
1455
|
+
}
|
|
1456
|
+
if !keep {
|
|
1457
|
+
// Mark for removal; the splice happens below, back to front, so earlier
|
|
1458
|
+
// indexes stay valid.
|
|
1459
|
+
sourceIndexes->Array.setUnsafe(idx, -1 - sourceIndexes->Array.getUnsafe(idx))
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
for idx in verdicts->Array.length - 1 downto 0 {
|
|
1463
|
+
let marked = sourceIndexes->Array.getUnsafe(idx)
|
|
1464
|
+
if marked < 0 {
|
|
1465
|
+
let _ =
|
|
1466
|
+
sourceDcs->Array.getUnsafe(idx)->Array.splice(~start=-1 - marked, ~remove=1, ~insert=[])
|
|
1293
1467
|
}
|
|
1294
1468
|
}
|
|
1295
1469
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
switch (dcContractNamesToStore, hasNoEventsUpdates) {
|
|
1299
|
-
// Dont update anything when everything was filter out
|
|
1470
|
+
switch (registeringContractNames, hasNoEventsUpdatesRef.contents) {
|
|
1471
|
+
// Dont update anything when everything was filtered out
|
|
1300
1472
|
| ([], false) => fetchState
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
// partitions since there's nothing to fetch for them.
|
|
1305
|
-
indexingAddresses->IndexingAddresses.register(noEventsAddresses)
|
|
1306
|
-
fetchState
|
|
1473
|
+
// Only dcs for contracts without events. The store already tracks them so
|
|
1474
|
+
// subsequent registrations see them, but there are no partitions to touch.
|
|
1475
|
+
| ([], true) => fetchState
|
|
1307
1476
|
| (_, _) => {
|
|
1308
1477
|
let newPartitions = []
|
|
1309
1478
|
let dynamicContractsRef = ref(fetchState.optimizedPartitions.dynamicContracts)
|
|
1310
1479
|
let mutExistingPartitions = fetchState.optimizedPartitions.entities->Dict.valuesToArray
|
|
1311
1480
|
|
|
1312
|
-
for idx in 0 to
|
|
1313
|
-
let contractName =
|
|
1481
|
+
for idx in 0 to registeringContractNames->Array.length - 1 {
|
|
1482
|
+
let contractName = registeringContractNames->Array.getUnsafe(idx)
|
|
1314
1483
|
|
|
1315
1484
|
// When a new contract name is added as a dynamic contract for the first time (not in dynamicContracts set):
|
|
1316
1485
|
// Walks through existing partitions that have addresses for this contract name
|
|
@@ -1323,12 +1492,12 @@ let registerDynamicContracts = (
|
|
|
1323
1492
|
|
|
1324
1493
|
for idx in 0 to mutExistingPartitions->Array.length - 1 {
|
|
1325
1494
|
let p = mutExistingPartitions->Array.getUnsafe(idx)
|
|
1326
|
-
switch p.
|
|
1327
|
-
|
|
|
1328
|
-
|
|
|
1495
|
+
switch p.addresses->AddressSet.countFor(contractName) {
|
|
1496
|
+
| 0 => () // Skip partitions which don't have our contract
|
|
1497
|
+
| _ =>
|
|
1329
1498
|
// Also filter out partitions which are 100% not mergable
|
|
1330
1499
|
if p.selection.dependsOnAddresses && p.mergeBlock === None {
|
|
1331
|
-
let allPartitionContractNames = p.
|
|
1500
|
+
let allPartitionContractNames = p.addresses->AddressSet.contractNames
|
|
1332
1501
|
switch allPartitionContractNames {
|
|
1333
1502
|
| [_] =>
|
|
1334
1503
|
mutExistingPartitions->Array.setUnsafe(
|
|
@@ -1351,26 +1520,20 @@ let registerDynamicContracts = (
|
|
|
1351
1520
|
(fetchState.optimizedPartitions.nextPartitionIndex +
|
|
1352
1521
|
newPartitions->Array.length)->Int.toString
|
|
1353
1522
|
|
|
1354
|
-
let
|
|
1355
|
-
|
|
1356
|
-
restAddressesByContractName->Utils.Dict.deleteInPlace(contractName)
|
|
1357
|
-
|
|
1523
|
+
let restNames =
|
|
1524
|
+
allPartitionContractNames->Array.filter(name => name !== contractName)
|
|
1358
1525
|
mutExistingPartitions->Array.setUnsafe(
|
|
1359
1526
|
idx,
|
|
1360
|
-
|
|
1361
|
-
...p,
|
|
1362
|
-
addressesByContractName: restAddressesByContractName,
|
|
1363
|
-
},
|
|
1527
|
+
p->withAddresses(p.addresses->AddressSet.filterByContracts(restNames)),
|
|
1364
1528
|
)
|
|
1365
1529
|
|
|
1366
|
-
let
|
|
1367
|
-
addressesByContractName->Dict.set(contractName, addresses)
|
|
1530
|
+
let splitAddresses = p.addresses->AddressSet.filterByContracts([contractName])
|
|
1368
1531
|
newPartitions->Array.push({
|
|
1369
1532
|
id: newPartitionId,
|
|
1370
1533
|
latestFetchedBlock: p.latestFetchedBlock,
|
|
1371
1534
|
selection: fetchState.normalSelection,
|
|
1372
1535
|
dynamicContract: Some(contractName),
|
|
1373
|
-
|
|
1536
|
+
addresses: splitAddresses,
|
|
1374
1537
|
mergeBlock: None,
|
|
1375
1538
|
mutPendingQueries: p.mutPendingQueries,
|
|
1376
1539
|
sourceRangeCapacity: p.sourceRangeCapacity,
|
|
@@ -1385,22 +1548,59 @@ let registerDynamicContracts = (
|
|
|
1385
1548
|
}
|
|
1386
1549
|
}
|
|
1387
1550
|
}
|
|
1551
|
+
}
|
|
1388
1552
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1553
|
+
// Switch any dynamic contract that has just crossed the server-side
|
|
1554
|
+
// address threshold to client-side filtering. Sticky: the set only grows, and
|
|
1555
|
+
// collapse in createPartitions folds the contract's
|
|
1556
|
+
// partitions into the single address-free partition.
|
|
1557
|
+
// Clone the sticky set before mutating so this update owns its copy and
|
|
1558
|
+
// older fetchState snapshots keep theirs (Utils.Set.add mutates in place).
|
|
1559
|
+
let clientFilteredContracts = switch fetchState.clientFilterAddressThreshold {
|
|
1560
|
+
| Some(threshold) =>
|
|
1561
|
+
let clientFilteredContracts =
|
|
1562
|
+
fetchState.optimizedPartitions.clientFilteredContracts
|
|
1563
|
+
->Utils.Set.toArray
|
|
1564
|
+
->Utils.Set.fromArray
|
|
1565
|
+
dynamicContractsRef.contents
|
|
1566
|
+
->Utils.Set.toArray
|
|
1567
|
+
->Array.forEach(contractName => {
|
|
1568
|
+
let addressCount = addressStore->AddressStore.contractCount(contractName)
|
|
1569
|
+
if !(clientFilteredContracts->Utils.Set.has(contractName)) && addressCount > threshold {
|
|
1570
|
+
clientFilteredContracts->addClientFilteredContract(
|
|
1571
|
+
~contractName,
|
|
1572
|
+
~chainId=fetchState.chainId,
|
|
1573
|
+
~addressCount,
|
|
1574
|
+
~threshold,
|
|
1575
|
+
)
|
|
1576
|
+
}
|
|
1577
|
+
})
|
|
1578
|
+
clientFilteredContracts
|
|
1579
|
+
| None => fetchState.optimizedPartitions.clientFilteredContracts
|
|
1391
1580
|
}
|
|
1392
|
-
// Include no-events dcs so later batches detect conflicts against them.
|
|
1393
|
-
indexingAddresses->IndexingAddresses.register(noEventsAddresses)
|
|
1394
1581
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1582
|
+
// Only this batch's additions need partitions; everything already
|
|
1583
|
+
// registered has one.
|
|
1584
|
+
let registeringSetsByContract = Dict.make()
|
|
1585
|
+
registeringContractNames->Array.forEach(contractName => {
|
|
1586
|
+
registeringSetsByContract->Dict.set(
|
|
1587
|
+
contractName,
|
|
1588
|
+
addressStore->AddressStore.makeSet(~contractName, ~options={minId: idCursor}),
|
|
1589
|
+
)
|
|
1590
|
+
})
|
|
1591
|
+
|
|
1592
|
+
let optimizedPartitions = createPartitions(
|
|
1593
|
+
~registeringSetsByContract,
|
|
1594
|
+
~addressStore,
|
|
1397
1595
|
~dynamicContracts=dynamicContractsRef.contents,
|
|
1596
|
+
~clientFilteredContracts,
|
|
1398
1597
|
~normalSelection=fetchState.normalSelection,
|
|
1399
1598
|
~maxAddrInPartition=fetchState.optimizedPartitions.maxAddrInPartition,
|
|
1400
1599
|
~nextPartitionIndex=fetchState.optimizedPartitions.nextPartitionIndex +
|
|
1401
1600
|
newPartitions->Array.length,
|
|
1402
1601
|
~existingPartitions=mutExistingPartitions->Array.concat(newPartitions),
|
|
1403
1602
|
~progressBlockNumber=0,
|
|
1603
|
+
~knownHeight=Pervasives.max(claimCeiling, fetchState.knownHeight),
|
|
1404
1604
|
)
|
|
1405
1605
|
|
|
1406
1606
|
fetchState->updateInternal(~optimizedPartitions)
|
|
@@ -1408,26 +1608,6 @@ let registerDynamicContracts = (
|
|
|
1408
1608
|
}
|
|
1409
1609
|
}
|
|
1410
1610
|
|
|
1411
|
-
// Drop events an address-param filter rejects. A merged partition may
|
|
1412
|
-
// over-fetch a wildcard event whose indexed address param references an
|
|
1413
|
-
// address registered after the log's block; `clientAddressFilter` is the
|
|
1414
|
-
// param-level analogue of the srcAddress effectiveStartBlock check.
|
|
1415
|
-
let filterByClientAddress = (
|
|
1416
|
-
items: array<Internal.item>,
|
|
1417
|
-
~indexingAddresses: IndexingAddresses.t,
|
|
1418
|
-
): array<Internal.item> =>
|
|
1419
|
-
items->Array.filter(item =>
|
|
1420
|
-
switch item {
|
|
1421
|
-
| Internal.Event({payload, blockNumber}) as item =>
|
|
1422
|
-
switch (item->Internal.castUnsafeEventItem).onEventRegistration.clientAddressFilter {
|
|
1423
|
-
| Some(filter) =>
|
|
1424
|
-
filter(payload, blockNumber, indexingAddresses->IndexingAddresses.rawForFilter)
|
|
1425
|
-
| None => true
|
|
1426
|
-
}
|
|
1427
|
-
| _ => true
|
|
1428
|
-
}
|
|
1429
|
-
)
|
|
1430
|
-
|
|
1431
1611
|
/*
|
|
1432
1612
|
Updates fetchState with a response for a given query.
|
|
1433
1613
|
Returns Error if the partition with given query cannot be found (unexpected)
|
|
@@ -1505,26 +1685,19 @@ let maxInFlightChunksPerPartition = 12
|
|
|
1505
1685
|
// Most parallel in-flight queries a single chain may have at once, across all
|
|
1506
1686
|
// its partitions. Bounds source load on chains with many partitions, where the
|
|
1507
1687
|
// per-partition cap alone would admit thousands of concurrent queries.
|
|
1508
|
-
let maxChainConcurrency =
|
|
1688
|
+
let maxChainConcurrency = Env.maxChainConcurrency
|
|
1509
1689
|
|
|
1510
1690
|
// Chunk spans grow by this factor over the smallest recently observed source
|
|
1511
1691
|
// range, so the pipeline keeps probing for more capacity instead of locking in
|
|
1512
1692
|
// the first measurement.
|
|
1513
1693
|
let chunkRangeGrowthFactor = 1.8
|
|
1514
1694
|
|
|
1515
|
-
// Push one density-priced query and return its itemsEst
|
|
1516
|
-
//
|
|
1517
|
-
//
|
|
1518
|
-
// range
|
|
1519
|
-
//
|
|
1520
|
-
// A
|
|
1521
|
-
// itemsTargetFloor: its range is already the hard bound on the response, so a
|
|
1522
|
-
// low density estimate shrinking the cap only buys self-truncated responses —
|
|
1523
|
-
// each one a wasted roundtrip that opens a gap and pollutes nothing but our
|
|
1524
|
-
// own pipeline. The floor is the indexer target split across the chain's
|
|
1525
|
-
// concurrency slots, so even every in-flight bounded query hitting its floored
|
|
1526
|
-
// cap at once overshoots the pool by at most ~one buffer target. Open-ended
|
|
1527
|
-
// queries keep the pure density cap — there it's the only bound at all.
|
|
1695
|
+
// Push one density-priced query and return its itemsEst, the density estimate
|
|
1696
|
+
// that sizes the query and the chain's budget reservation. These queries are
|
|
1697
|
+
// chunks and gap-fills: their toBlock is a tight bound sized to the source's
|
|
1698
|
+
// range capacity, so they send no server cap (itemsTarget None) — a cap would
|
|
1699
|
+
// only self-truncate the bounded range, worse under client-side address
|
|
1700
|
+
// filtering. A rare unbounded call caps at the estimate as its only bound.
|
|
1528
1701
|
let pushDensityPricedQuery = (
|
|
1529
1702
|
queries: array<query>,
|
|
1530
1703
|
~partitionId,
|
|
@@ -1532,19 +1705,11 @@ let pushDensityPricedQuery = (
|
|
|
1532
1705
|
~toBlock,
|
|
1533
1706
|
~isChunk,
|
|
1534
1707
|
~density,
|
|
1535
|
-
~chunkItemsMultiplier,
|
|
1536
1708
|
~chainTargetBlock,
|
|
1537
|
-
~itemsTargetFloor,
|
|
1538
1709
|
~selection,
|
|
1539
|
-
~
|
|
1710
|
+
~addresses,
|
|
1540
1711
|
) => {
|
|
1541
1712
|
let itemsEst = densityItemsTarget(~density, ~fromBlock, ~toBlock, ~chainTargetBlock)
|
|
1542
|
-
let itemsTarget = densityItemsTarget(
|
|
1543
|
-
~density=density *. chunkItemsMultiplier,
|
|
1544
|
-
~fromBlock,
|
|
1545
|
-
~toBlock,
|
|
1546
|
-
~chainTargetBlock,
|
|
1547
|
-
)
|
|
1548
1713
|
queries
|
|
1549
1714
|
->Array.push({
|
|
1550
1715
|
partitionId,
|
|
@@ -1553,11 +1718,11 @@ let pushDensityPricedQuery = (
|
|
|
1553
1718
|
selection,
|
|
1554
1719
|
isChunk,
|
|
1555
1720
|
itemsTarget: switch toBlock {
|
|
1556
|
-
| Some(_) =>
|
|
1557
|
-
| None =>
|
|
1721
|
+
| Some(_) => None
|
|
1722
|
+
| None => Some(itemsEst)
|
|
1558
1723
|
},
|
|
1559
1724
|
itemsEst,
|
|
1560
|
-
|
|
1725
|
+
addresses,
|
|
1561
1726
|
})
|
|
1562
1727
|
->ignore
|
|
1563
1728
|
itemsEst
|
|
@@ -1585,10 +1750,8 @@ let pushGapFillQueries = (
|
|
|
1585
1750
|
~maxChunks: int,
|
|
1586
1751
|
~partition: partition,
|
|
1587
1752
|
~partitionBudget: float,
|
|
1588
|
-
~chunkItemsMultiplier: float,
|
|
1589
|
-
~itemsTargetFloor: int,
|
|
1590
1753
|
~selection: selection,
|
|
1591
|
-
~
|
|
1754
|
+
~addresses: AddressSet.t,
|
|
1592
1755
|
) => {
|
|
1593
1756
|
// Gaps past the chain's target block wait: they regenerate from the
|
|
1594
1757
|
// pending-walk each tick and fill once the target reaches them. The lagged
|
|
@@ -1610,11 +1773,9 @@ let pushGapFillQueries = (
|
|
|
1610
1773
|
~toBlock=rangeEndBlock,
|
|
1611
1774
|
~isChunk,
|
|
1612
1775
|
~density,
|
|
1613
|
-
~chunkItemsMultiplier,
|
|
1614
1776
|
~chainTargetBlock,
|
|
1615
|
-
~itemsTargetFloor,
|
|
1616
1777
|
~selection,
|
|
1617
|
-
~
|
|
1778
|
+
~addresses,
|
|
1618
1779
|
)
|
|
1619
1780
|
->ignore
|
|
1620
1781
|
switch (partition->getTrustedDensity, maybeChunkRange) {
|
|
@@ -1634,11 +1795,9 @@ let pushGapFillQueries = (
|
|
|
1634
1795
|
~toBlock=Some(chunkToBlock),
|
|
1635
1796
|
~isChunk=true,
|
|
1636
1797
|
~density,
|
|
1637
|
-
~chunkItemsMultiplier,
|
|
1638
1798
|
~chainTargetBlock,
|
|
1639
|
-
~itemsTargetFloor,
|
|
1640
1799
|
~selection,
|
|
1641
|
-
~
|
|
1800
|
+
~addresses,
|
|
1642
1801
|
)
|
|
1643
1802
|
->ignore
|
|
1644
1803
|
chunkFromBlock := chunkToBlock + 1
|
|
@@ -1684,8 +1843,6 @@ let walkPartitionPending = (
|
|
|
1684
1843
|
~candidates: array<query>,
|
|
1685
1844
|
~headBlockNumber: int,
|
|
1686
1845
|
~chainTargetBlock: int,
|
|
1687
|
-
~chunkItemsMultiplier: float,
|
|
1688
|
-
~itemsTargetFloor: int,
|
|
1689
1846
|
~partitionBudget: float,
|
|
1690
1847
|
~queryEndBlock: option<int>,
|
|
1691
1848
|
): option<partitionFillState> => {
|
|
@@ -1712,10 +1869,8 @@ let walkPartitionPending = (
|
|
|
1712
1869
|
~maxChunks=maxInFlightChunksPerPartition - inFlightCount - chunksUsedThisCall.contents,
|
|
1713
1870
|
~partition=p,
|
|
1714
1871
|
~partitionBudget,
|
|
1715
|
-
~chunkItemsMultiplier,
|
|
1716
|
-
~itemsTargetFloor,
|
|
1717
1872
|
~selection=p.selection,
|
|
1718
|
-
~
|
|
1873
|
+
~addresses=p.addresses,
|
|
1719
1874
|
)
|
|
1720
1875
|
chunksUsedThisCall := chunksUsedThisCall.contents + (candidates->Array.length - beforeLen)
|
|
1721
1876
|
}
|
|
@@ -1757,14 +1912,12 @@ let pushForwardCandidates = (
|
|
|
1757
1912
|
// bound, see getNextQuery.
|
|
1758
1913
|
~inRangeStates: array<partitionFillState>,
|
|
1759
1914
|
// The full in-range partition count, pre-truncation. Probe sizing divides by
|
|
1760
|
-
// this so each probe's itemsEst
|
|
1761
|
-
//
|
|
1762
|
-
//
|
|
1915
|
+
// this so each probe's itemsEst stays the honest per-partition share for
|
|
1916
|
+
// budget control — sizing by the (fewer) admittable queries would let every
|
|
1917
|
+
// accepted probe over-fetch its share.
|
|
1763
1918
|
~inRangeCount: int,
|
|
1764
1919
|
~chainTargetBlock: int,
|
|
1765
1920
|
~freshBudget: float,
|
|
1766
|
-
~chunkItemsMultiplier: float,
|
|
1767
|
-
~itemsTargetFloor: int,
|
|
1768
1921
|
) => {
|
|
1769
1922
|
// Even share of the fresh budget across the partitions actually fetching
|
|
1770
1923
|
// this tick (not every partition — so budget isn't stranded on ones below
|
|
@@ -1818,11 +1971,9 @@ let pushForwardCandidates = (
|
|
|
1818
1971
|
~toBlock=Some(chunkToBlock),
|
|
1819
1972
|
~isChunk=true,
|
|
1820
1973
|
~density,
|
|
1821
|
-
~chunkItemsMultiplier,
|
|
1822
1974
|
~chainTargetBlock,
|
|
1823
|
-
~itemsTargetFloor,
|
|
1824
1975
|
~selection=p.selection,
|
|
1825
|
-
~
|
|
1976
|
+
~addresses=p.addresses,
|
|
1826
1977
|
)
|
|
1827
1978
|
generatedItems := generatedItems.contents +. itemsEst->Int.toFloat
|
|
1828
1979
|
chunkFromBlock := chunkToBlock + 1
|
|
@@ -1834,7 +1985,7 @@ let pushForwardCandidates = (
|
|
|
1834
1985
|
// across the partitions fetching this tick. With no range to the target
|
|
1835
1986
|
// fall back to an even share of the fresh budget, so cold chains and
|
|
1836
1987
|
// caught-up partitions still probe.
|
|
1837
|
-
let
|
|
1988
|
+
let itemsEst = if rangeToTarget > 0 {
|
|
1838
1989
|
Pervasives.max(
|
|
1839
1990
|
1,
|
|
1840
1991
|
Math.round(
|
|
@@ -1853,9 +2004,11 @@ let pushForwardCandidates = (
|
|
|
1853
2004
|
toBlock: fs.queryEndBlock,
|
|
1854
2005
|
isChunk: false,
|
|
1855
2006
|
selection: p.selection,
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2007
|
+
// An open-ended probe's range isn't bounded to source capacity, so it
|
|
2008
|
+
// keeps a server cap at its estimate to protect the shared buffer.
|
|
2009
|
+
itemsTarget: Some(itemsEst),
|
|
2010
|
+
itemsEst,
|
|
2011
|
+
addresses: p.addresses,
|
|
1859
2012
|
})
|
|
1860
2013
|
->ignore
|
|
1861
2014
|
}
|
|
@@ -1958,10 +2111,6 @@ let getNextQuery = (
|
|
|
1958
2111
|
{optimizedPartitions, blockLag, latestOnBlockBlockNumber, knownHeight, endBlock}: t,
|
|
1959
2112
|
~chainTargetBlock: int,
|
|
1960
2113
|
~chainTargetItems: float,
|
|
1961
|
-
~chunkItemsMultiplier: float=1.,
|
|
1962
|
-
// Floor for bounded queries' server cap (see pushDensityPricedQuery) —
|
|
1963
|
-
// targetBufferSize / maxChainConcurrency from the cross-chain scheduler.
|
|
1964
|
-
~itemsTargetFloor: int=0,
|
|
1965
2114
|
) => {
|
|
1966
2115
|
let headBlockNumber = knownHeight - blockLag
|
|
1967
2116
|
if headBlockNumber <= 0 {
|
|
@@ -2079,8 +2228,6 @@ let getNextQuery = (
|
|
|
2079
2228
|
~candidates,
|
|
2080
2229
|
~headBlockNumber,
|
|
2081
2230
|
~chainTargetBlock,
|
|
2082
|
-
~chunkItemsMultiplier,
|
|
2083
|
-
~itemsTargetFloor,
|
|
2084
2231
|
~partitionBudget,
|
|
2085
2232
|
~queryEndBlock=computeQueryEndBlock(p),
|
|
2086
2233
|
) {
|
|
@@ -2124,8 +2271,6 @@ let getNextQuery = (
|
|
|
2124
2271
|
~inRangeCount,
|
|
2125
2272
|
~chainTargetBlock,
|
|
2126
2273
|
~freshBudget,
|
|
2127
|
-
~chunkItemsMultiplier,
|
|
2128
|
-
~itemsTargetFloor,
|
|
2129
2274
|
)
|
|
2130
2275
|
|
|
2131
2276
|
acceptCandidates(
|
|
@@ -2187,7 +2332,7 @@ let make = (
|
|
|
2187
2332
|
~startBlock,
|
|
2188
2333
|
~endBlock,
|
|
2189
2334
|
~onEventRegistrations: array<Internal.onEventRegistration>,
|
|
2190
|
-
~
|
|
2335
|
+
~addressStore: AddressStore.t,
|
|
2191
2336
|
~addresses: array<Internal.indexingAddress>,
|
|
2192
2337
|
~maxAddrInPartition,
|
|
2193
2338
|
~chainId,
|
|
@@ -2197,6 +2342,7 @@ let make = (
|
|
|
2197
2342
|
~onBlockRegistrations=[],
|
|
2198
2343
|
~blockLag=0,
|
|
2199
2344
|
~firstEventBlock=None,
|
|
2345
|
+
~clientFilterAddressThreshold=None,
|
|
2200
2346
|
): t => {
|
|
2201
2347
|
let latestFetchedBlock = {
|
|
2202
2348
|
blockTimestamp: 0,
|
|
@@ -2226,7 +2372,7 @@ let make = (
|
|
|
2226
2372
|
dependsOnAddresses: false,
|
|
2227
2373
|
onEventRegistrations: notDependingOnAddresses,
|
|
2228
2374
|
},
|
|
2229
|
-
|
|
2375
|
+
addresses: addressStore->AddressStore.emptySet,
|
|
2230
2376
|
mergeBlock: None,
|
|
2231
2377
|
dynamicContract: None,
|
|
2232
2378
|
mutPendingQueries: [],
|
|
@@ -2242,8 +2388,22 @@ let make = (
|
|
|
2242
2388
|
onEventRegistrations: normalRegistrations,
|
|
2243
2389
|
}
|
|
2244
2390
|
|
|
2245
|
-
|
|
2391
|
+
// Every address the chain indexes goes into the store — including ones whose
|
|
2392
|
+
// contract has no address-dependent events, so a later registration of the
|
|
2393
|
+
// same address still conflicts and the address is still persisted.
|
|
2394
|
+
addressStore
|
|
2395
|
+
->AddressStore.registerBatch(
|
|
2396
|
+
addresses->Array.map((contract): AddressStore.registration => {
|
|
2397
|
+
address: contract.address,
|
|
2398
|
+
contractName: contract.contractName,
|
|
2399
|
+
registrationBlock: contract.registrationBlock,
|
|
2400
|
+
}),
|
|
2401
|
+
)
|
|
2402
|
+
->ignore
|
|
2403
|
+
|
|
2246
2404
|
let dynamicContracts = Utils.Set.make()
|
|
2405
|
+
let clientFilteredContracts = Utils.Set.make()
|
|
2406
|
+
let registeringSetsByContract = Dict.make()
|
|
2247
2407
|
|
|
2248
2408
|
addresses->Array.forEach(contract => {
|
|
2249
2409
|
let contractName = contract.contractName
|
|
@@ -2251,12 +2411,12 @@ let make = (
|
|
|
2251
2411
|
// Only addresses whose contract has events that depend on addresses get
|
|
2252
2412
|
// registered for active fetching via partitions.
|
|
2253
2413
|
if contractNamesWithNormalEvents->Utils.Set.has(contractName) {
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2414
|
+
if !(registeringSetsByContract->Dict.has(contractName)) {
|
|
2415
|
+
registeringSetsByContract->Dict.set(
|
|
2416
|
+
contractName,
|
|
2417
|
+
addressStore->AddressStore.makeSet(~contractName),
|
|
2418
|
+
)
|
|
2419
|
+
}
|
|
2260
2420
|
|
|
2261
2421
|
// Detect dynamic contracts by registrationBlock
|
|
2262
2422
|
if contract.registrationBlock !== -1 {
|
|
@@ -2265,14 +2425,36 @@ let make = (
|
|
|
2265
2425
|
}
|
|
2266
2426
|
})
|
|
2267
2427
|
|
|
2268
|
-
|
|
2269
|
-
|
|
2428
|
+
// Switch any contract already over the server-side address threshold to
|
|
2429
|
+
// client-side filtering at creation — a config contract with a large static
|
|
2430
|
+
// address list, or a dynamic contract restored from a large persisted set.
|
|
2431
|
+
switch clientFilterAddressThreshold {
|
|
2432
|
+
| Some(threshold) =>
|
|
2433
|
+
registeringSetsByContract->Utils.Dict.forEachWithKey((set, contractName) => {
|
|
2434
|
+
let addressCount = set->AddressSet.size
|
|
2435
|
+
if addressCount > threshold {
|
|
2436
|
+
clientFilteredContracts->addClientFilteredContract(
|
|
2437
|
+
~contractName,
|
|
2438
|
+
~chainId,
|
|
2439
|
+
~addressCount,
|
|
2440
|
+
~threshold,
|
|
2441
|
+
)
|
|
2442
|
+
}
|
|
2443
|
+
})
|
|
2444
|
+
| None => ()
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
let optimizedPartitions = createPartitions(
|
|
2448
|
+
~registeringSetsByContract,
|
|
2449
|
+
~addressStore,
|
|
2270
2450
|
~dynamicContracts,
|
|
2451
|
+
~clientFilteredContracts,
|
|
2271
2452
|
~normalSelection,
|
|
2272
2453
|
~maxAddrInPartition,
|
|
2273
2454
|
~nextPartitionIndex=partitions->Array.length,
|
|
2274
2455
|
~existingPartitions=partitions, // wildcard partition(s) if any
|
|
2275
2456
|
~progressBlockNumber,
|
|
2457
|
+
~knownHeight,
|
|
2276
2458
|
)
|
|
2277
2459
|
|
|
2278
2460
|
if (
|
|
@@ -2313,7 +2495,6 @@ let make = (
|
|
|
2313
2495
|
|
|
2314
2496
|
let fetchState = {
|
|
2315
2497
|
optimizedPartitions,
|
|
2316
|
-
contractConfigs,
|
|
2317
2498
|
chainId,
|
|
2318
2499
|
startBlock,
|
|
2319
2500
|
endBlock,
|
|
@@ -2325,6 +2506,7 @@ let make = (
|
|
|
2325
2506
|
knownHeight,
|
|
2326
2507
|
buffer,
|
|
2327
2508
|
firstEventBlock,
|
|
2509
|
+
clientFilterAddressThreshold,
|
|
2328
2510
|
}
|
|
2329
2511
|
|
|
2330
2512
|
fetchState
|
|
@@ -2365,16 +2547,29 @@ Always recreates optimized partitions to avoid duplicate addresses:
|
|
|
2365
2547
|
- Non-wildcard with lfb <= target: keep, adjust pending queries and mergeBlock
|
|
2366
2548
|
- Non-wildcard with lfb > target: delete, track addresses for recreation
|
|
2367
2549
|
*/
|
|
2368
|
-
let rollback = (fetchState: t, ~
|
|
2369
|
-
// Step 1: Prune addresses registered after the target block. The pruned
|
|
2370
|
-
// then the source of truth for partition cleanup below — an address
|
|
2371
|
-
//
|
|
2372
|
-
|
|
2550
|
+
let rollback = (fetchState: t, ~addressStore: AddressStore.t, ~targetBlockNumber) => {
|
|
2551
|
+
// Step 1: Prune addresses registered after the target block. The pruned store
|
|
2552
|
+
// is then the source of truth for partition cleanup below — an address
|
|
2553
|
+
// survives iff `filterByRegistrationBlock` keeps it.
|
|
2554
|
+
addressStore->AddressStore.rollback(targetBlockNumber)->ignore
|
|
2373
2555
|
|
|
2374
2556
|
// Step 2: Categorize partitions
|
|
2375
2557
|
let keptPartitions = []
|
|
2376
2558
|
let nextKeptIdRef = ref(0)
|
|
2377
|
-
let
|
|
2559
|
+
let registeringSetsByContract: dict<AddressSet.t> = Dict.make()
|
|
2560
|
+
let collectForRecreation = (set: AddressSet.t) =>
|
|
2561
|
+
set
|
|
2562
|
+
->AddressSet.contractNames
|
|
2563
|
+
->Array.forEach(contractName => {
|
|
2564
|
+
let contractSet = set->AddressSet.filterByContracts([contractName])
|
|
2565
|
+
registeringSetsByContract->Dict.set(
|
|
2566
|
+
contractName,
|
|
2567
|
+
switch registeringSetsByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
2568
|
+
| Some(existing) => existing->AddressSet.merge(contractSet)
|
|
2569
|
+
| None => contractSet
|
|
2570
|
+
},
|
|
2571
|
+
)
|
|
2572
|
+
})
|
|
2378
2573
|
|
|
2379
2574
|
let partitions = fetchState.optimizedPartitions.entities->Dict.valuesToArray
|
|
2380
2575
|
for idx in 0 to partitions->Array.length - 1 {
|
|
@@ -2397,46 +2592,27 @@ let rollback = (fetchState: t, ~indexingAddresses: IndexingAddresses.t, ~targetB
|
|
|
2397
2592
|
|
|
2398
2593
|
// Non-wildcard with lfb > target: delete, collect addresses for recreation
|
|
2399
2594
|
| _ if p.latestFetchedBlock.blockNumber > targetBlockNumber =>
|
|
2400
|
-
p.
|
|
2401
|
-
addresses->Array.forEach(address => {
|
|
2402
|
-
switch indexingAddresses->IndexingAddresses.get(address->Address.toString) {
|
|
2403
|
-
| Some(indexingContract) =>
|
|
2404
|
-
let registeringContracts =
|
|
2405
|
-
registeringContractsByContract->Utils.Dict.getOrInsertEmptyDict(contractName)
|
|
2406
|
-
registeringContracts->Dict.set(address->Address.toString, indexingContract)
|
|
2407
|
-
| None => ()
|
|
2408
|
-
}
|
|
2409
|
-
})
|
|
2410
|
-
})
|
|
2595
|
+
collectForRecreation(p.addresses->AddressSet.filterByRegistrationBlock(targetBlockNumber))
|
|
2411
2596
|
|
|
2412
2597
|
// Non-wildcard with lfb <= target: keep, adjust pending queries and mergeBlock
|
|
2413
|
-
|
|
|
2598
|
+
| _ => {
|
|
2414
2599
|
// Cap mergeBlock at target
|
|
2415
2600
|
let mergeBlock = switch p.mergeBlock {
|
|
2416
2601
|
| Some(mergeBlock) if mergeBlock > targetBlockNumber => Some(targetBlockNumber)
|
|
2417
2602
|
| other => other
|
|
2418
2603
|
}
|
|
2419
2604
|
|
|
2420
|
-
// Drop addresses pruned from the
|
|
2421
|
-
let
|
|
2422
|
-
|
|
2423
|
-
let keptAddresses =
|
|
2424
|
-
addresses->Array.filter(address =>
|
|
2425
|
-
indexingAddresses->IndexingAddresses.get(address->Address.toString)->Option.isSome
|
|
2426
|
-
)
|
|
2427
|
-
if keptAddresses->Array.length > 0 {
|
|
2428
|
-
rollbackedAddressesByContractName->Dict.set(contractName, keptAddresses)
|
|
2429
|
-
}
|
|
2430
|
-
})
|
|
2605
|
+
// Drop addresses pruned from the store
|
|
2606
|
+
let rollbackedAddresses =
|
|
2607
|
+
p.addresses->AddressSet.filterByRegistrationBlock(targetBlockNumber)
|
|
2431
2608
|
|
|
2432
|
-
if !(
|
|
2609
|
+
if !(rollbackedAddresses->AddressSet.isEmpty) {
|
|
2433
2610
|
let id = nextKeptIdRef.contents->Int.toString
|
|
2434
2611
|
nextKeptIdRef := nextKeptIdRef.contents + 1
|
|
2435
2612
|
keptPartitions
|
|
2436
2613
|
->Array.push({
|
|
2437
|
-
...p,
|
|
2614
|
+
...p->withAddresses(rollbackedAddresses),
|
|
2438
2615
|
id,
|
|
2439
|
-
addressesByContractName: rollbackedAddressesByContractName,
|
|
2440
2616
|
mutPendingQueries: rollbackPendingQueries(p.mutPendingQueries, ~targetBlockNumber),
|
|
2441
2617
|
mergeBlock,
|
|
2442
2618
|
})
|
|
@@ -2447,14 +2623,17 @@ let rollback = (fetchState: t, ~indexingAddresses: IndexingAddresses.t, ~targetB
|
|
|
2447
2623
|
}
|
|
2448
2624
|
|
|
2449
2625
|
// Step 3: Recreate partitions from deleted partition addresses
|
|
2450
|
-
let optimizedPartitions =
|
|
2451
|
-
~
|
|
2626
|
+
let optimizedPartitions = createPartitions(
|
|
2627
|
+
~registeringSetsByContract,
|
|
2628
|
+
~addressStore,
|
|
2452
2629
|
~dynamicContracts=fetchState.optimizedPartitions.dynamicContracts,
|
|
2630
|
+
~clientFilteredContracts=fetchState.optimizedPartitions.clientFilteredContracts,
|
|
2453
2631
|
~normalSelection=fetchState.normalSelection,
|
|
2454
2632
|
~maxAddrInPartition=fetchState.optimizedPartitions.maxAddrInPartition,
|
|
2455
2633
|
~nextPartitionIndex=nextKeptIdRef.contents,
|
|
2456
2634
|
~existingPartitions=keptPartitions,
|
|
2457
2635
|
~progressBlockNumber=targetBlockNumber,
|
|
2636
|
+
~knownHeight=fetchState.knownHeight,
|
|
2458
2637
|
)
|
|
2459
2638
|
|
|
2460
2639
|
// Step 4: Update state
|