envio 3.5.0-alpha.1 → 3.5.0-alpha.3
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 +27 -0
- package/src/BatchProcessing.res.mjs +12 -0
- package/src/ChainFetching.res +5 -5
- package/src/ChainFetching.res.mjs +3 -4
- package/src/ChainState.res +48 -25
- package/src/ChainState.res.mjs +23 -29
- package/src/ChainState.resi +2 -6
- package/src/Change.res +3 -3
- package/src/Config.res +2 -6
- package/src/Config.res.mjs +2 -12
- package/src/Core.res +3 -0
- package/src/CrossChainState.res +33 -10
- package/src/CrossChainState.res.mjs +21 -6
- package/src/CrossChainState.resi +3 -0
- package/src/EntityId.res +15 -0
- package/src/EntityId.res.mjs +9 -0
- package/src/EventConfigBuilder.res +1 -58
- package/src/EventConfigBuilder.res.mjs +1 -33
- package/src/FetchState.res +510 -460
- package/src/FetchState.res.mjs +413 -428
- package/src/FinalizeBackfill.res +34 -0
- package/src/FinalizeBackfill.res.mjs +26 -0
- package/src/HandlerRegister.res +45 -0
- package/src/HandlerRegister.res.mjs +43 -0
- package/src/InMemoryStore.res +11 -4
- package/src/InMemoryTable.res +26 -24
- package/src/InMemoryTable.res.mjs +19 -18
- package/src/IndexerState.res +71 -2
- package/src/IndexerState.res.mjs +67 -3
- package/src/IndexerState.resi +5 -0
- package/src/Internal.res +17 -8
- package/src/LoadLayer.res +9 -1
- package/src/LoadLayer.res.mjs +1 -0
- package/src/LogSelection.res +2 -1
- package/src/Metrics.res +37 -6
- package/src/Metrics.res.mjs +5 -1
- package/src/Persistence.res +12 -1
- package/src/PgStorage.res +411 -57
- package/src/PgStorage.res.mjs +262 -41
- package/src/SimulateItems.res +2 -2
- package/src/TestIndexer.res +9 -5
- package/src/TestIndexer.res.mjs +5 -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/EntityFilter.res +0 -4
- package/src/db/EntityFilter.res.mjs +1 -8
- package/src/db/EntityHistory.res +16 -7
- package/src/db/EntityHistory.res.mjs +7 -6
- package/src/db/IndexRegistry.res +219 -0
- package/src/db/IndexRegistry.res.mjs +195 -0
- package/src/db/InternalTable.res +7 -0
- package/src/db/InternalTable.res.mjs +7 -0
- package/src/db/Table.res +53 -22
- package/src/db/Table.res.mjs +56 -18
- 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 +6 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcClient.res +6 -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 +6 -4
- package/src/sources/HyperSync.res.mjs +2 -3
- package/src/sources/HyperSync.resi +1 -1
- package/src/sources/HyperSyncClient.res +18 -9
- package/src/sources/HyperSyncClient.res.mjs +4 -4
- package/src/sources/RpcSource.res +15 -10
- package/src/sources/RpcSource.res.mjs +3 -4
- package/src/sources/SimulateSource.res +26 -11
- package/src/sources/SimulateSource.res.mjs +13 -5
- package/src/sources/Source.res +4 -2
- 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 +12 -3
- package/src/sources/SvmHyperSyncClient.res.mjs +2 -2
- package/src/sources/SvmHyperSyncSource.res +10 -5
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/src/IndexingAddresses.res +0 -151
- package/src/IndexingAddresses.res.mjs +0 -130
- package/src/IndexingAddresses.resi +0 -39
package/src/FetchState.res
CHANGED
|
@@ -15,9 +15,9 @@ type selection = {
|
|
|
15
15
|
dependsOnAddresses: bool,
|
|
16
16
|
// Contract names this query fetches address-free even though their
|
|
17
17
|
// registrations depend on addresses. The source passes these to the client so
|
|
18
|
-
// their log selections carry no server-side address filter
|
|
19
|
-
//
|
|
20
|
-
//
|
|
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
21
|
clientFilteredContracts?: array<string>,
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -49,10 +49,13 @@ type partition = {
|
|
|
49
49
|
// which added all its events to the queue
|
|
50
50
|
latestFetchedBlock: blockNumberAndTimestamp,
|
|
51
51
|
selection: selection,
|
|
52
|
-
|
|
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,
|
|
53
56
|
mergeBlock: option<int>,
|
|
54
57
|
// When set, partition indexes a single dynamic contract type.
|
|
55
|
-
//
|
|
58
|
+
// `addresses` must then contain only addresses for this contract.
|
|
56
59
|
dynamicContract: option<string>,
|
|
57
60
|
// Mutated in place and shared across fetchState versions (updateInternal
|
|
58
61
|
// copies the record, not this array): startFetchingQueries inserts,
|
|
@@ -92,26 +95,10 @@ type query = {
|
|
|
92
95
|
// budget is reserved/consumed in and that sizes every query.
|
|
93
96
|
itemsEst: int,
|
|
94
97
|
selection: selection,
|
|
95
|
-
|
|
98
|
+
addresses: AddressSet.t,
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
|
|
99
|
-
// routing. 1:1 today (each address belongs to one contract), so no key
|
|
100
|
-
// collisions. Memoized on the addressesByContractName object so a partition's
|
|
101
|
-
// many responses share one derivation and a large factory never rebuilds the
|
|
102
|
-
// whole index; sound because the dict is immutable after construction (every
|
|
103
|
-
// mutation produces a new dict).
|
|
104
|
-
let deriveContractNameByAddress: dict<array<Address.t>> => dict<
|
|
105
|
-
string,
|
|
106
|
-
> = Utils.WeakMap.memoize(addressesByContractName => {
|
|
107
|
-
let result = Dict.make()
|
|
108
|
-
addressesByContractName->Utils.Dict.forEachWithKey((addresses, contractName) => {
|
|
109
|
-
for i in 0 to addresses->Array.length - 1 {
|
|
110
|
-
result->Dict.set(addresses->Array.getUnsafe(i)->Address.toString, contractName)
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
result
|
|
114
|
-
})
|
|
101
|
+
let withAddresses = (p: partition, addresses: AddressSet.t) => {...p, addresses}
|
|
115
102
|
|
|
116
103
|
// itemsEst for a query over [fromBlock, toBlock] at the given event density
|
|
117
104
|
// (items/block). toBlock None is the open-ended tail, capped at
|
|
@@ -200,10 +187,7 @@ module OptimizedPartitions = {
|
|
|
200
187
|
~maxAddrInPartition: int,
|
|
201
188
|
~nextPartitionIndexRef: ref<int>,
|
|
202
189
|
) => {
|
|
203
|
-
let combinedAddresses =
|
|
204
|
-
p1.addressesByContractName
|
|
205
|
-
->Dict.getUnsafe(contractName)
|
|
206
|
-
->Array.concat(p2.addressesByContractName->Dict.getUnsafe(contractName))
|
|
190
|
+
let combinedAddresses = p1.addresses->AddressSet.merge(p2.addresses)
|
|
207
191
|
|
|
208
192
|
let p1Below = p1.latestFetchedBlock.blockNumber < potentialMergeBlock
|
|
209
193
|
let p2Below = p2.latestFetchedBlock.blockNumber < potentialMergeBlock
|
|
@@ -240,7 +224,7 @@ module OptimizedPartitions = {
|
|
|
240
224
|
selection: p1.selection,
|
|
241
225
|
latestFetchedBlock: {blockNumber: potentialMergeBlock, blockTimestamp: 0},
|
|
242
226
|
mergeBlock: None,
|
|
243
|
-
|
|
227
|
+
addresses: p1.addresses, // replaced below
|
|
244
228
|
mutPendingQueries: [],
|
|
245
229
|
sourceRangeCapacity: minRange,
|
|
246
230
|
prevSourceRangeCapacity: minRange,
|
|
@@ -250,29 +234,28 @@ module OptimizedPartitions = {
|
|
|
250
234
|
}
|
|
251
235
|
|
|
252
236
|
// Apply address split on the continuing partition
|
|
253
|
-
if combinedAddresses->
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
261
245
|
let restId = nextPartitionIndexRef.contents->Int.toString
|
|
262
246
|
nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
|
|
263
247
|
completed
|
|
264
248
|
->Array.push({
|
|
265
|
-
...continuingBase
|
|
249
|
+
...continuingBase->withAddresses(
|
|
250
|
+
combinedAddresses->AddressSet.slice(~offset=maxAddrInPartition, ~limit=None),
|
|
251
|
+
),
|
|
266
252
|
id: restId,
|
|
267
|
-
addressesByContractName: abcRest,
|
|
268
253
|
mutPendingQueries: [],
|
|
269
254
|
})
|
|
270
255
|
->ignore
|
|
271
256
|
completed
|
|
272
257
|
} else {
|
|
273
|
-
|
|
274
|
-
abc->Dict.set(contractName, combinedAddresses)
|
|
275
|
-
completed->Array.push({...continuingBase, addressesByContractName: abc})->ignore
|
|
258
|
+
completed->Array.push(continuingBase->withAddresses(combinedAddresses))->ignore
|
|
276
259
|
completed
|
|
277
260
|
}
|
|
278
261
|
}
|
|
@@ -287,6 +270,47 @@ module OptimizedPartitions = {
|
|
|
287
270
|
let ascSortFn = (a, b) =>
|
|
288
271
|
Int.compare(a.latestFetchedBlock.blockNumber, b.latestFetchedBlock.blockNumber)
|
|
289
272
|
|
|
273
|
+
// Contracts a standing address-free partition already fetches client-side.
|
|
274
|
+
// Addresses registered for them after that partition passed get a normal
|
|
275
|
+
// address-bound partition which dies once it catches up (see make), instead
|
|
276
|
+
// of being folded into the address-free one.
|
|
277
|
+
let anchoredContracts = (partitions: array<partition>) => {
|
|
278
|
+
let set = Utils.Set.make()
|
|
279
|
+
partitions->Array.forEach(p =>
|
|
280
|
+
switch (p.mergeBlock, p.selection.clientFilteredContracts) {
|
|
281
|
+
| (None, Some(contractNames)) =>
|
|
282
|
+
contractNames->Array.forEach(name => set->Utils.Set.add(name)->ignore)
|
|
283
|
+
| _ => ()
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
set
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// The furthest block an address-free partition's already-dispatched queries
|
|
290
|
+
// can deliver without the addresses registered from now on. A settled query
|
|
291
|
+
// claims only what it actually fetched — the rest of its range is re-queried
|
|
292
|
+
// later, by then against the updated address store — while an in-flight
|
|
293
|
+
// bounded query claims its whole toBlock. An in-flight unbounded query has no
|
|
294
|
+
// ceiling at all, so nothing is safe to stop a catch-up partition at yet.
|
|
295
|
+
let getAnchorSafeBlock = (p: partition) => {
|
|
296
|
+
let safeRef = ref(Some(p.latestFetchedBlock.blockNumber))
|
|
297
|
+
p.mutPendingQueries->Array.forEach(pq =>
|
|
298
|
+
switch (safeRef.contents, pq) {
|
|
299
|
+
| (None, _) => ()
|
|
300
|
+
| (Some(safe), {fetchedBlock: Some({blockNumber})}) =>
|
|
301
|
+
if blockNumber > safe {
|
|
302
|
+
safeRef := Some(blockNumber)
|
|
303
|
+
}
|
|
304
|
+
| (Some(safe), {toBlock: Some(toBlock)}) =>
|
|
305
|
+
if toBlock > safe {
|
|
306
|
+
safeRef := Some(toBlock)
|
|
307
|
+
}
|
|
308
|
+
| (Some(_), _) => safeRef := None
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
safeRef.contents
|
|
312
|
+
}
|
|
313
|
+
|
|
290
314
|
/**
|
|
291
315
|
* Optimizes partitions by finding opportunities to merge partitions that
|
|
292
316
|
* are behind other partitions with same/superset of contract names.
|
|
@@ -305,22 +329,52 @@ module OptimizedPartitions = {
|
|
|
305
329
|
let mergingPartitions = Dict.make()
|
|
306
330
|
let nextPartitionIndexRef = ref(nextPartitionIndex)
|
|
307
331
|
|
|
332
|
+
// Where a catch-up partition for a client-filtered contract may stop, keyed
|
|
333
|
+
// by contract name. A contract absent from the dict while present in
|
|
334
|
+
// anchoredContractsSet has an unbounded query in flight on its address-free
|
|
335
|
+
// partition — its catch-up can't be bounded yet, and a later call will bound it.
|
|
336
|
+
let anchorSafeBlocks = Dict.make()
|
|
337
|
+
let anchoredContractsSet = Utils.Set.make()
|
|
338
|
+
if clientFilteredContracts->Utils.Set.size > 0 {
|
|
339
|
+
partitions->Array.forEach(p =>
|
|
340
|
+
switch (p.mergeBlock, p.selection.clientFilteredContracts) {
|
|
341
|
+
| (None, Some(contractNames)) =>
|
|
342
|
+
let safeBlock = p->getAnchorSafeBlock
|
|
343
|
+
contractNames->Array.forEach(name => {
|
|
344
|
+
anchoredContractsSet->Utils.Set.add(name)->ignore
|
|
345
|
+
switch safeBlock {
|
|
346
|
+
| Some(safeBlock) => anchorSafeBlocks->Dict.set(name, safeBlock)
|
|
347
|
+
| None => ()
|
|
348
|
+
}
|
|
349
|
+
})
|
|
350
|
+
| _ => ()
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
308
355
|
for idx in 0 to partitions->Array.length - 1 {
|
|
309
356
|
let p = partitions->Array.getUnsafe(idx)
|
|
310
357
|
switch p {
|
|
358
|
+
// For now don't merge partitions with mergeBlock,
|
|
359
|
+
// assuming they are already merged,
|
|
360
|
+
// TODO: Although there might be cases with too far away mergeBlock,
|
|
361
|
+
// which is worth merging.
|
|
362
|
+
// A partition already at or past its merge block is done — normally
|
|
363
|
+
// handleQueryResponse removes it when the response lands, but a rollback
|
|
364
|
+
// can cap the merge block at the rolled-back frontier, leaving no
|
|
365
|
+
// response to ever remove it on.
|
|
366
|
+
| {mergeBlock: Some(mergeBlock)} =>
|
|
367
|
+
if p.latestFetchedBlock.blockNumber < mergeBlock {
|
|
368
|
+
newPartitions->Array.push(p)->ignore
|
|
369
|
+
}
|
|
311
370
|
// Since it's not a dynamic contract partition,
|
|
312
371
|
// there's no need for merge logic
|
|
313
372
|
| {dynamicContract: None}
|
|
314
373
|
| // Wildcard doesn't need merging
|
|
315
|
-
{selection: {dependsOnAddresses: false}}
|
|
316
|
-
| // For now don't merge partitions with mergeBlock,
|
|
317
|
-
// assuming they are already merged,
|
|
318
|
-
// TODO: Although there might be cases with too far away mergeBlock,
|
|
319
|
-
// which is worth merging
|
|
320
|
-
{mergeBlock: Some(_)} =>
|
|
374
|
+
{selection: {dependsOnAddresses: false}} =>
|
|
321
375
|
newPartitions->Array.push(p)->ignore
|
|
322
376
|
| {dynamicContract: Some(contractName)} =>
|
|
323
|
-
let pAddressesCount = p.
|
|
377
|
+
let pAddressesCount = p.addresses->AddressSet.countFor(contractName)
|
|
324
378
|
// Compute merge block: last pending query's toBlock, or lfb if idle
|
|
325
379
|
let potentialMergeBlock = switch p.mutPendingQueries->Utils.Array.last {
|
|
326
380
|
| Some({isChunk: true, toBlock: Some(toBlock)}) => Some(toBlock)
|
|
@@ -415,14 +469,40 @@ module OptimizedPartitions = {
|
|
|
415
469
|
newPartitions->Array.push(currentPRef.contents)->ignore
|
|
416
470
|
}
|
|
417
471
|
|
|
472
|
+
// A dynamic partition for a contract the address-free partition already
|
|
473
|
+
// fetches is a catch-up for addresses registered after that partition
|
|
474
|
+
// passed them. It merges by disappearing once it reaches the last block
|
|
475
|
+
// that partition's dispatched queries could have missed them on — its
|
|
476
|
+
// addresses are in the chain's store, so there is nothing to hand over.
|
|
477
|
+
let finalPartitions = if anchoredContractsSet->Utils.Set.size === 0 {
|
|
478
|
+
newPartitions
|
|
479
|
+
} else {
|
|
480
|
+
let anchored = []
|
|
481
|
+
newPartitions->Array.forEach(p =>
|
|
482
|
+
switch p {
|
|
483
|
+
| {dynamicContract: Some(contractName), mergeBlock: None}
|
|
484
|
+
if anchoredContractsSet->Utils.Set.has(contractName) =>
|
|
485
|
+
switch anchorSafeBlocks->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
486
|
+
| None => anchored->Array.push(p)->ignore
|
|
487
|
+
| Some(anchorSafeBlock) =>
|
|
488
|
+
if p.latestFetchedBlock.blockNumber < anchorSafeBlock {
|
|
489
|
+
anchored->Array.push({...p, mergeBlock: Some(anchorSafeBlock)})->ignore
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
| _ => anchored->Array.push(p)->ignore
|
|
493
|
+
}
|
|
494
|
+
)
|
|
495
|
+
anchored
|
|
496
|
+
}
|
|
497
|
+
|
|
418
498
|
// Sort partitions by latestFetchedBlock ascending
|
|
419
|
-
let _ =
|
|
499
|
+
let _ = finalPartitions->Array.sort(ascSortFn)
|
|
420
500
|
|
|
421
|
-
let partitionsCount =
|
|
501
|
+
let partitionsCount = finalPartitions->Array.length
|
|
422
502
|
let idsInAscOrder = Utils.Array.jsArrayCreate(partitionsCount)
|
|
423
503
|
let entities = Dict.make()
|
|
424
504
|
for idx in 0 to partitionsCount - 1 {
|
|
425
|
-
let p =
|
|
505
|
+
let p = finalPartitions->Array.getUnsafe(idx)
|
|
426
506
|
idsInAscOrder->Array.setUnsafe(idx, p.id)
|
|
427
507
|
entities->Dict.set(p.id, p)
|
|
428
508
|
}
|
|
@@ -664,8 +744,6 @@ type t = {
|
|
|
664
744
|
startBlock: int,
|
|
665
745
|
endBlock: option<int>,
|
|
666
746
|
normalSelection: selection,
|
|
667
|
-
// By contract name
|
|
668
|
-
contractConfigs: dict<IndexingAddresses.contractConfig>,
|
|
669
747
|
// Not used for logic - only metadata
|
|
670
748
|
chainId: int,
|
|
671
749
|
// The block number of the latest block which was added to the queue
|
|
@@ -686,8 +764,7 @@ type t = {
|
|
|
686
764
|
knownHeight: int,
|
|
687
765
|
firstEventBlock: option<int>,
|
|
688
766
|
// Per-contract registered-address count past which a dynamic contract is
|
|
689
|
-
// switched to client-side filtering. None disables the switch
|
|
690
|
-
// (sources that can't filter client-side, e.g. SVM/Fuel), leaving every
|
|
767
|
+
// switched to client-side filtering. None disables the switch, leaving every
|
|
691
768
|
// contract filtered server-side.
|
|
692
769
|
clientFilterAddressThreshold: option<int>,
|
|
693
770
|
}
|
|
@@ -944,7 +1021,6 @@ let updateInternal = (
|
|
|
944
1021
|
let updatedFetchState = {
|
|
945
1022
|
startBlock: fetchState.startBlock,
|
|
946
1023
|
endBlock: fetchState.endBlock,
|
|
947
|
-
contractConfigs: fetchState.contractConfigs,
|
|
948
1024
|
normalSelection: fetchState.normalSelection,
|
|
949
1025
|
chainId: fetchState.chainId,
|
|
950
1026
|
onBlockRegistrations: fetchState.onBlockRegistrations,
|
|
@@ -965,40 +1041,6 @@ let updateInternal = (
|
|
|
965
1041
|
updatedFetchState
|
|
966
1042
|
}
|
|
967
1043
|
|
|
968
|
-
let warnDifferentContractType = (
|
|
969
|
-
fetchState,
|
|
970
|
-
~existingContract: indexingAddress,
|
|
971
|
-
~dc: indexingAddress,
|
|
972
|
-
) => {
|
|
973
|
-
let logger = Logging.createChild(
|
|
974
|
-
~params={
|
|
975
|
-
"chainId": fetchState.chainId,
|
|
976
|
-
"contractAddress": dc.address->Address.toString,
|
|
977
|
-
"existingContractType": existingContract.contractName,
|
|
978
|
-
"newContractType": dc.contractName,
|
|
979
|
-
},
|
|
980
|
-
)
|
|
981
|
-
logger->Logging.childWarn(`Skipping contract registration: Contract address is already registered for one contract and cannot be registered for another contract.`)
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
let addressesByContractNameCount = (addressesByContractName: dict<array<Address.t>>) => {
|
|
985
|
-
let numAddresses = ref(0)
|
|
986
|
-
addressesByContractName->Utils.Dict.forEach(addresses => {
|
|
987
|
-
numAddresses := numAddresses.contents + addresses->Array.length
|
|
988
|
-
})
|
|
989
|
-
numAddresses.contents
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
let addressesByContractNameGetAll = (addressesByContractName: dict<array<Address.t>>) => {
|
|
993
|
-
let all = []
|
|
994
|
-
addressesByContractName->Utils.Dict.forEach(addresses => {
|
|
995
|
-
for idx in 0 to addresses->Array.length - 1 {
|
|
996
|
-
all->Array.push(addresses->Array.getUnsafe(idx))->ignore
|
|
997
|
-
}
|
|
998
|
-
})
|
|
999
|
-
all
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
1044
|
// Move a contract to client-side address filtering, recording why.
|
|
1003
1045
|
let addClientFilteredContract = (
|
|
1004
1046
|
clientFilteredContracts: Utils.Set.t<string>,
|
|
@@ -1006,20 +1048,44 @@ let addClientFilteredContract = (
|
|
|
1006
1048
|
~chainId,
|
|
1007
1049
|
~addressCount,
|
|
1008
1050
|
~threshold,
|
|
1051
|
+
// A resumed fetch state re-derives the switch from the persisted addresses,
|
|
1052
|
+
// but it already happened - and was logged - in the run that crossed the
|
|
1053
|
+
// threshold.
|
|
1054
|
+
~shouldLog=true,
|
|
1009
1055
|
) => {
|
|
1010
1056
|
clientFilteredContracts->Utils.Set.add(contractName)->ignore
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1057
|
+
if shouldLog {
|
|
1058
|
+
Logging.createChild(
|
|
1059
|
+
~params={
|
|
1060
|
+
"chainId": chainId,
|
|
1061
|
+
"contractName": contractName,
|
|
1062
|
+
"addressCount": addressCount,
|
|
1063
|
+
"threshold": threshold,
|
|
1064
|
+
},
|
|
1065
|
+
)->Logging.childTrace(
|
|
1066
|
+
"Switching contract to client-side address filtering: registered address count crossed the server-side threshold.",
|
|
1067
|
+
)
|
|
1068
|
+
}
|
|
1021
1069
|
}
|
|
1022
1070
|
|
|
1071
|
+
// The block a partition will have fetched to once everything on its pending
|
|
1072
|
+
// queue lands — not the block it has fetched to now. A backfill has to reach it
|
|
1073
|
+
// rather than the frontier: a query that was routed before this batch's
|
|
1074
|
+
// addresses reached the address store carries no events for them, yet it still
|
|
1075
|
+
// advances the frontier over its range on arrival, and nothing else would ever
|
|
1076
|
+
// refetch it. An in-flight open-ended query has no toBlock of its own; it can't
|
|
1077
|
+
// return past the chain's known height, so that bounds it.
|
|
1078
|
+
let claimedFetchedBlock = (p: partition, ~knownHeight) =>
|
|
1079
|
+
p.mutPendingQueries->Array.reduce(p.latestFetchedBlock.blockNumber, (max, q) =>
|
|
1080
|
+
Pervasives.max(
|
|
1081
|
+
max,
|
|
1082
|
+
switch q.fetchedBlock {
|
|
1083
|
+
| Some({blockNumber}) => blockNumber
|
|
1084
|
+
| None => q.toBlock->Option.getOr(knownHeight)
|
|
1085
|
+
},
|
|
1086
|
+
)
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1023
1089
|
// Fold every client-filtered contract's server-side partitions into client-side
|
|
1024
1090
|
// fetching without tearing down established state:
|
|
1025
1091
|
// - The standing address-free partition (no mergeBlock) keeps its id, frontier,
|
|
@@ -1027,14 +1093,18 @@ let addClientFilteredContract = (
|
|
|
1027
1093
|
// must be added does its selection change — under a fresh id, so in-flight
|
|
1028
1094
|
// responses built from the old selection are orphaned instead of advancing
|
|
1029
1095
|
// the frontier past ranges the new contract wasn't fetched for.
|
|
1030
|
-
// - Partitions absorbed below the standing
|
|
1031
|
-
// partitions and config partitions all of whose
|
|
1032
|
-
// client-filtered, plus any prior backfill) become one bounded
|
|
1033
|
-
// partition covering [their min frontier,
|
|
1034
|
-
// caps its queries at mergeBlock and handleQueryResponse deletes
|
|
1035
|
-
// arrival. The overlap it re-delivers is deduped by mergeIntoBuffer, and
|
|
1036
|
-
// re-fetch doubles as history for freshly registered addresses: events
|
|
1037
|
-
// dropped before the address was registered now pass the
|
|
1096
|
+
// - Partitions absorbed below the standing partition's claimed block
|
|
1097
|
+
// (single-contract dynamic partitions and config partitions all of whose
|
|
1098
|
+
// contracts are client-filtered, plus any prior backfill) become one bounded
|
|
1099
|
+
// backfill partition covering [their min frontier, that claimed block]:
|
|
1100
|
+
// getNextQuery caps its queries at mergeBlock and handleQueryResponse deletes
|
|
1101
|
+
// it on arrival. The overlap it re-delivers is deduped by mergeIntoBuffer, and
|
|
1102
|
+
// the re-fetch doubles as history for freshly registered addresses: events
|
|
1103
|
+
// dropped before the address was registered now pass the address gate.
|
|
1104
|
+
// - A dynamic partition for a contract the standing partition already covers is
|
|
1105
|
+
// left alone: it is a catch-up for addresses registered after that partition
|
|
1106
|
+
// passed them, and OptimizedPartitions.make bounds it against the standing
|
|
1107
|
+
// partition's own claims instead.
|
|
1038
1108
|
// - A partition mixing client-filtered and server-side contracts stays,
|
|
1039
1109
|
// stripped of the client-filtered contracts' addresses — the address-free
|
|
1040
1110
|
// partition covers those logs, so fetching them server-side too would only
|
|
@@ -1050,6 +1120,12 @@ let collapseClientFilteredContracts = (
|
|
|
1050
1120
|
~clientFilteredContracts: Utils.Set.t<string>,
|
|
1051
1121
|
~normalSelection: selection,
|
|
1052
1122
|
~nextPartitionIndexRef: ref<int>,
|
|
1123
|
+
~addressStore: AddressStore.t,
|
|
1124
|
+
// Frontiers of the addresses that were never given a server-side partition
|
|
1125
|
+
// because their contract is already client-filtered. They stand in for the
|
|
1126
|
+
// partitions that would otherwise have been created only to be absorbed here.
|
|
1127
|
+
~clientFilteredFrontiers: array<blockNumberAndTimestamp>=[],
|
|
1128
|
+
~knownHeight: int,
|
|
1053
1129
|
) => {
|
|
1054
1130
|
if clientFilteredContracts->Utils.Set.size === 0 {
|
|
1055
1131
|
partitions
|
|
@@ -1059,6 +1135,7 @@ let collapseClientFilteredContracts = (
|
|
|
1059
1135
|
let backfills = []
|
|
1060
1136
|
let absorbedPartitions = []
|
|
1061
1137
|
let strippedFrontiers = []
|
|
1138
|
+
let anchoredContracts = partitions->OptimizedPartitions.anchoredContracts
|
|
1062
1139
|
|
|
1063
1140
|
partitions->Array.forEach(p =>
|
|
1064
1141
|
switch p {
|
|
@@ -1069,18 +1146,29 @@ let collapseClientFilteredContracts = (
|
|
|
1069
1146
|
backfills->Array.push(p)->ignore
|
|
1070
1147
|
| {selection: {dependsOnAddresses: false}} => absorbedPartitions->Array.push(p)->ignore
|
|
1071
1148
|
| _ =>
|
|
1072
|
-
let contractNames = p.
|
|
1073
|
-
let
|
|
1074
|
-
contractNames->Array.filter(c => clientFilteredContracts->Utils.Set.has(c))
|
|
1075
|
-
if
|
|
1149
|
+
let contractNames = p.addresses->AddressSet.contractNames
|
|
1150
|
+
let serverSideNames =
|
|
1151
|
+
contractNames->Array.filter(c => !(clientFilteredContracts->Utils.Set.has(c)))
|
|
1152
|
+
if serverSideNames->Array.length === contractNames->Array.length {
|
|
1153
|
+
kept->Array.push(p)->ignore
|
|
1154
|
+
} else if (
|
|
1155
|
+
switch p.dynamicContract {
|
|
1156
|
+
| Some(contractName) => anchoredContracts->Utils.Set.has(contractName)
|
|
1157
|
+
| None => false
|
|
1158
|
+
}
|
|
1159
|
+
) {
|
|
1160
|
+
// A catch-up partition for addresses registered after the address-free
|
|
1161
|
+
// partition already covered their contract. It is the correctness
|
|
1162
|
+
// barrier for those addresses until it catches up — absorbing it would
|
|
1163
|
+
// hand that job back to a guessed ceiling.
|
|
1076
1164
|
kept->Array.push(p)->ignore
|
|
1077
|
-
} else if
|
|
1165
|
+
} else if serverSideNames->Utils.Array.isEmpty {
|
|
1078
1166
|
absorbedPartitions->Array.push(p)->ignore
|
|
1079
1167
|
} else {
|
|
1080
|
-
let stripped = p.addressesByContractName->Utils.Dict.shallowCopy
|
|
1081
|
-
clientFilteredNames->Array.forEach(c => stripped->Utils.Dict.deleteInPlace(c))
|
|
1082
1168
|
strippedFrontiers->Array.push(p.latestFetchedBlock)->ignore
|
|
1083
|
-
kept
|
|
1169
|
+
kept
|
|
1170
|
+
->Array.push(p->withAddresses(p.addresses->AddressSet.filterByContracts(serverSideNames)))
|
|
1171
|
+
->ignore
|
|
1084
1172
|
}
|
|
1085
1173
|
}
|
|
1086
1174
|
)
|
|
@@ -1095,6 +1183,7 @@ let collapseClientFilteredContracts = (
|
|
|
1095
1183
|
if (
|
|
1096
1184
|
absorbedPartitions->Utils.Array.isEmpty &&
|
|
1097
1185
|
strippedFrontiers->Utils.Array.isEmpty &&
|
|
1186
|
+
clientFilteredFrontiers->Utils.Array.isEmpty &&
|
|
1098
1187
|
!selectionChanged
|
|
1099
1188
|
) {
|
|
1100
1189
|
// Nothing to fold in and no newly-switched contract: leave the standing
|
|
@@ -1110,6 +1199,7 @@ let collapseClientFilteredContracts = (
|
|
|
1110
1199
|
absorbedPartitions->Array.forEach(p => considerFrontier(p.latestFetchedBlock))
|
|
1111
1200
|
backfills->Array.forEach(p => considerFrontier(p.latestFetchedBlock))
|
|
1112
1201
|
strippedFrontiers->Array.forEach(considerFrontier)
|
|
1202
|
+
clientFilteredFrontiers->Array.forEach(considerFrontier)
|
|
1113
1203
|
|
|
1114
1204
|
let regByIndex = Dict.make()
|
|
1115
1205
|
let addRegs = (regs: array<Internal.onEventRegistration>) =>
|
|
@@ -1150,7 +1240,7 @@ let collapseClientFilteredContracts = (
|
|
|
1150
1240
|
id,
|
|
1151
1241
|
latestFetchedBlock: minFrontier,
|
|
1152
1242
|
selection: newSelection,
|
|
1153
|
-
|
|
1243
|
+
addresses: addressStore->AddressStore.emptySet,
|
|
1154
1244
|
mergeBlock: None,
|
|
1155
1245
|
dynamicContract: None,
|
|
1156
1246
|
mutPendingQueries: [],
|
|
@@ -1171,8 +1261,12 @@ let collapseClientFilteredContracts = (
|
|
|
1171
1261
|
standing
|
|
1172
1262
|
}
|
|
1173
1263
|
kept->Array.push(standingOut)->ignore
|
|
1264
|
+
// Read off standingOut, not standing: a selection change orphans the
|
|
1265
|
+
// in-flight queries (fresh id, empty queue), so there the frontier is
|
|
1266
|
+
// all the partition will ever claim.
|
|
1267
|
+
let catchUpToBlock = standingOut->claimedFetchedBlock(~knownHeight)
|
|
1174
1268
|
switch minFrontierRef.contents {
|
|
1175
|
-
| Some(minFrontier) if minFrontier.blockNumber <
|
|
1269
|
+
| Some(minFrontier) if minFrontier.blockNumber < catchUpToBlock =>
|
|
1176
1270
|
let id = nextPartitionIndexRef.contents->Int.toString
|
|
1177
1271
|
nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
|
|
1178
1272
|
kept
|
|
@@ -1180,8 +1274,8 @@ let collapseClientFilteredContracts = (
|
|
|
1180
1274
|
id,
|
|
1181
1275
|
latestFetchedBlock: minFrontier,
|
|
1182
1276
|
selection: newSelection,
|
|
1183
|
-
|
|
1184
|
-
mergeBlock: Some(
|
|
1277
|
+
addresses: addressStore->AddressStore.emptySet,
|
|
1278
|
+
mergeBlock: Some(catchUpToBlock),
|
|
1185
1279
|
dynamicContract: None,
|
|
1186
1280
|
mutPendingQueries: [],
|
|
1187
1281
|
// Same query shape as the standing partition, so inherit its
|
|
@@ -1192,8 +1286,9 @@ let collapseClientFilteredContracts = (
|
|
|
1192
1286
|
latestSourceRangeCapacityUpdateBlock: 0,
|
|
1193
1287
|
})
|
|
1194
1288
|
->ignore
|
|
1195
|
-
// An absorbed frontier at/above the
|
|
1196
|
-
//
|
|
1289
|
+
// An absorbed frontier at/above the claimed block needs no backfill:
|
|
1290
|
+
// nothing has fetched past there yet, so the standing partition still
|
|
1291
|
+
// covers it going forward.
|
|
1197
1292
|
| _ => ()
|
|
1198
1293
|
}
|
|
1199
1294
|
kept
|
|
@@ -1209,8 +1304,11 @@ Phase 2: Merge non-dynamic partitions together to reduce unnecessary concurrency
|
|
|
1209
1304
|
Returns OptimizedPartitions.t directly.
|
|
1210
1305
|
(Dynamic partitions are merged by OptimizedPartitions.make automatically)
|
|
1211
1306
|
*/
|
|
1212
|
-
let
|
|
1213
|
-
|
|
1307
|
+
let createPartitions = (
|
|
1308
|
+
// One set per contract, holding the addresses that still need a partition:
|
|
1309
|
+
// a batch's fresh registrations, or the survivors of a rolled-back partition.
|
|
1310
|
+
~registeringSetsByContract: dict<AddressSet.t>,
|
|
1311
|
+
~addressStore: AddressStore.t,
|
|
1214
1312
|
~dynamicContracts: Utils.Set.t<string>,
|
|
1215
1313
|
~clientFilteredContracts: Utils.Set.t<string>,
|
|
1216
1314
|
~normalSelection: selection,
|
|
@@ -1218,6 +1316,7 @@ let createPartitionsFromIndexingAddresses = (
|
|
|
1218
1316
|
~nextPartitionIndex: int,
|
|
1219
1317
|
~existingPartitions: array<partition>,
|
|
1220
1318
|
~progressBlockNumber: int,
|
|
1319
|
+
~knownHeight: int,
|
|
1221
1320
|
): // Floor for latestFetchedBlock (use progressBlockNumber from make, or 0 for registerDynamicContracts)
|
|
1222
1321
|
OptimizedPartitions.t => {
|
|
1223
1322
|
let nextPartitionIndexRef = ref(nextPartitionIndex)
|
|
@@ -1225,73 +1324,81 @@ OptimizedPartitions.t => {
|
|
|
1225
1324
|
// ── Phase 1: Create per-contract-name partitions ──
|
|
1226
1325
|
let dynamicPartitions = []
|
|
1227
1326
|
let nonDynamicPartitions = []
|
|
1327
|
+
let clientFilteredFrontiers = []
|
|
1228
1328
|
|
|
1229
|
-
|
|
1329
|
+
// Contracts an address-free partition already fetches. Their new addresses
|
|
1330
|
+
// need a partition of their own: the address-free partition passed the blocks
|
|
1331
|
+
// they were registered at without them in the store.
|
|
1332
|
+
let anchoredContracts = existingPartitions->OptimizedPartitions.anchoredContracts
|
|
1333
|
+
|
|
1334
|
+
let contractNames = registeringSetsByContract->Dict.keysToArray
|
|
1230
1335
|
for cIdx in 0 to contractNames->Array.length - 1 {
|
|
1231
1336
|
let contractName = contractNames->Array.getUnsafe(cIdx)
|
|
1232
|
-
let
|
|
1233
|
-
let addresses =
|
|
1234
|
-
registeringContracts->Dict.keysToArray->(Utils.magic: array<string> => array<Address.t>)
|
|
1337
|
+
let contractSet = registeringSetsByContract->Dict.getUnsafe(contractName)
|
|
1235
1338
|
|
|
1236
|
-
let
|
|
1339
|
+
let isAnchored = anchoredContracts->Utils.Set.has(contractName)
|
|
1340
|
+
// A catch-up partition must go through the dynamic merge logic — that's
|
|
1341
|
+
// what bounds it against its anchor and removes it once it catches up.
|
|
1342
|
+
let isDynamic = dynamicContracts->Utils.Set.has(contractName) || isAnchored
|
|
1237
1343
|
let partitions = isDynamic ? dynamicPartitions : nonDynamicPartitions
|
|
1238
1344
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1345
|
+
// A set is ordered by effectiveStartBlock, so its start-block groups are
|
|
1346
|
+
// ascending and each group's addresses are a contiguous slice.
|
|
1347
|
+
let groups = contractSet->AddressSet.startBlockGroups
|
|
1348
|
+
|
|
1349
|
+
if clientFilteredContracts->Utils.Set.has(contractName) && !isAnchored {
|
|
1350
|
+
// The contract is switching to client-side filtering in this very call, so
|
|
1351
|
+
// the collapse below folds everything it has into the address-free
|
|
1352
|
+
// partition anyway. Chunking these addresses by maxAddrInPartition would
|
|
1353
|
+
// only build partitions for it to absorb - hundreds of them for a contract
|
|
1354
|
+
// big enough to be client-filtered in the first place. All the collapse
|
|
1355
|
+
// needs is the earliest block the addresses aren't covered from, which is
|
|
1356
|
+
// the first group's since groups are ascending.
|
|
1357
|
+
switch groups->Array.get(0) {
|
|
1358
|
+
| Some({startBlock}) =>
|
|
1359
|
+
clientFilteredFrontiers->Array.push({
|
|
1360
|
+
blockNumber: Pervasives.max(startBlock - 1, progressBlockNumber),
|
|
1361
|
+
blockTimestamp: 0,
|
|
1362
|
+
})
|
|
1363
|
+
| None => ()
|
|
1364
|
+
}
|
|
1365
|
+
} else {
|
|
1366
|
+
let offsetRef = ref(0)
|
|
1367
|
+
let groupIdx = ref(0)
|
|
1368
|
+
while groupIdx.contents < groups->Array.length {
|
|
1369
|
+
let startBlock = (groups->Array.getUnsafe(groupIdx.contents)).startBlock
|
|
1370
|
+
// Addresses with different start blocks within range share a partition;
|
|
1371
|
+
// events before each address's effectiveStartBlock are dropped by the
|
|
1372
|
+
// source's address gate.
|
|
1373
|
+
let countRef = ref(0)
|
|
1374
|
+
let nextIdx = ref(groupIdx.contents)
|
|
1375
|
+
let joining = ref(true)
|
|
1376
|
+
while joining.contents && nextIdx.contents < groups->Array.length {
|
|
1377
|
+
let group = groups->Array.getUnsafe(nextIdx.contents)
|
|
1378
|
+
if group.startBlock - startBlock < OptimizedPartitions.tooFarBlockRange {
|
|
1379
|
+
countRef := countRef.contents + group.count
|
|
1380
|
+
nextIdx := nextIdx.contents + 1
|
|
1272
1381
|
} else {
|
|
1273
|
-
|
|
1382
|
+
joining := false
|
|
1274
1383
|
}
|
|
1275
1384
|
}
|
|
1276
|
-
}
|
|
1277
1385
|
|
|
1278
|
-
if shouldAllocateNewPartition {
|
|
1279
1386
|
let latestFetchedBlock = {
|
|
1280
|
-
blockNumber: Pervasives.max(
|
|
1387
|
+
blockNumber: Pervasives.max(startBlock - 1, progressBlockNumber),
|
|
1281
1388
|
blockTimestamp: 0,
|
|
1282
1389
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
let
|
|
1288
|
-
|
|
1390
|
+
let remainingRef = ref(countRef.contents)
|
|
1391
|
+
let chunkOffsetRef = ref(offsetRef.contents)
|
|
1392
|
+
while remainingRef.contents > 0 {
|
|
1393
|
+
let take = Pervasives.min(remainingRef.contents, maxAddrInPartition)
|
|
1394
|
+
let pAddresses =
|
|
1395
|
+
contractSet->AddressSet.slice(~offset=chunkOffsetRef.contents, ~limit=Some(take))
|
|
1289
1396
|
partitions->Array.push({
|
|
1290
1397
|
id: nextPartitionIndexRef.contents->Int.toString,
|
|
1291
1398
|
latestFetchedBlock,
|
|
1292
1399
|
selection: normalSelection,
|
|
1293
1400
|
dynamicContract: isDynamic ? Some(contractName) : None,
|
|
1294
|
-
|
|
1401
|
+
addresses: pAddresses,
|
|
1295
1402
|
mergeBlock: None,
|
|
1296
1403
|
mutPendingQueries: [],
|
|
1297
1404
|
sourceRangeCapacity: 0,
|
|
@@ -1300,15 +1407,12 @@ OptimizedPartitions.t => {
|
|
|
1300
1407
|
latestSourceRangeCapacityUpdateBlock: 0,
|
|
1301
1408
|
})
|
|
1302
1409
|
nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
|
|
1410
|
+
chunkOffsetRef := chunkOffsetRef.contents + take
|
|
1411
|
+
remainingRef := remainingRef.contents - take
|
|
1303
1412
|
}
|
|
1304
1413
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
| Some(nextStartBlockKey) => {
|
|
1308
|
-
startBlockRef := nextStartBlockKey->Int.fromString->Option.getUnsafe
|
|
1309
|
-
addressesRef := byStartBlock->Dict.getUnsafe(nextStartBlockKey)
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1414
|
+
offsetRef := offsetRef.contents + countRef.contents
|
|
1415
|
+
groupIdx := nextIdx.contents
|
|
1312
1416
|
}
|
|
1313
1417
|
}
|
|
1314
1418
|
}
|
|
@@ -1329,29 +1433,14 @@ OptimizedPartitions.t => {
|
|
|
1329
1433
|
let currentPBlock = currentP.latestFetchedBlock.blockNumber
|
|
1330
1434
|
let nextPBlock = nextP.latestFetchedBlock.blockNumber
|
|
1331
1435
|
|
|
1332
|
-
|
|
1333
|
-
let totalCount =
|
|
1334
|
-
currentP.addressesByContractName->addressesByContractNameCount +
|
|
1335
|
-
nextP.addressesByContractName->addressesByContractNameCount
|
|
1436
|
+
let totalCount = currentP.addresses->AddressSet.size + nextP.addresses->AddressSet.size
|
|
1336
1437
|
|
|
1337
1438
|
if totalCount > maxAddrInPartition {
|
|
1338
1439
|
// Exceeds address limit - don't merge, keep partitions separate
|
|
1339
1440
|
mergedNonDynamic->Array.push(currentP)->ignore
|
|
1340
1441
|
currentPRef := nextP
|
|
1341
1442
|
} else {
|
|
1342
|
-
|
|
1343
|
-
let mergedAddresses = nextP.addressesByContractName->Utils.Dict.shallowCopy
|
|
1344
|
-
let currentContractNames = currentP.addressesByContractName->Dict.keysToArray
|
|
1345
|
-
for jdx in 0 to currentContractNames->Array.length - 1 {
|
|
1346
|
-
let cn = currentContractNames->Array.getUnsafe(jdx)
|
|
1347
|
-
let currentAddrs = currentP.addressesByContractName->Dict.getUnsafe(cn)
|
|
1348
|
-
switch mergedAddresses->Utils.Dict.dangerouslyGetNonOption(cn) {
|
|
1349
|
-
| Some(existingAddrs) =>
|
|
1350
|
-
// Use concat (non-mutating) to avoid corrupting nextP's arrays
|
|
1351
|
-
mergedAddresses->Dict.set(cn, existingAddrs->Array.concat(currentAddrs))
|
|
1352
|
-
| None => mergedAddresses->Dict.set(cn, currentAddrs)
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1443
|
+
let mergedAddresses = nextP.addresses->AddressSet.merge(currentP.addresses)
|
|
1355
1444
|
|
|
1356
1445
|
let isTooFar = currentPBlock + OptimizedPartitions.tooFarBlockRange < nextPBlock
|
|
1357
1446
|
|
|
@@ -1363,16 +1452,10 @@ OptimizedPartitions.t => {
|
|
|
1363
1452
|
mergeBlock: currentPBlock < nextPBlock ? Some(nextPBlock) : None,
|
|
1364
1453
|
})
|
|
1365
1454
|
->ignore
|
|
1366
|
-
currentPRef :=
|
|
1367
|
-
...nextP,
|
|
1368
|
-
addressesByContractName: mergedAddresses,
|
|
1369
|
-
}
|
|
1455
|
+
currentPRef := nextP->withAddresses(mergedAddresses)
|
|
1370
1456
|
} else {
|
|
1371
1457
|
// Close: push next's addresses into current
|
|
1372
|
-
currentPRef :=
|
|
1373
|
-
...currentP,
|
|
1374
|
-
addressesByContractName: mergedAddresses,
|
|
1375
|
-
}
|
|
1458
|
+
currentPRef := currentP->withAddresses(mergedAddresses)
|
|
1376
1459
|
}
|
|
1377
1460
|
}
|
|
1378
1461
|
|
|
@@ -1393,6 +1476,9 @@ OptimizedPartitions.t => {
|
|
|
1393
1476
|
~clientFilteredContracts,
|
|
1394
1477
|
~normalSelection,
|
|
1395
1478
|
~nextPartitionIndexRef,
|
|
1479
|
+
~addressStore,
|
|
1480
|
+
~clientFilteredFrontiers,
|
|
1481
|
+
~knownHeight,
|
|
1396
1482
|
)
|
|
1397
1483
|
OptimizedPartitions.make(
|
|
1398
1484
|
~partitions=allPartitions,
|
|
@@ -1405,7 +1491,14 @@ OptimizedPartitions.t => {
|
|
|
1405
1491
|
|
|
1406
1492
|
let registerDynamicContracts = (
|
|
1407
1493
|
fetchState: t,
|
|
1408
|
-
~
|
|
1494
|
+
~addressStore: AddressStore.t,
|
|
1495
|
+
// How far an in-flight open-ended query may end up claiming. These
|
|
1496
|
+
// registrations usually come out of a response that is applied right after
|
|
1497
|
+
// this call, and an unbounded query can reach past the height known when it
|
|
1498
|
+
// was dispatched — so the caller folds that response's own frontier and
|
|
1499
|
+
// height in here. Defaults to what the fetch state knows, which is all
|
|
1500
|
+
// there is to go on when no response is being applied.
|
|
1501
|
+
~claimCeiling=fetchState.knownHeight,
|
|
1409
1502
|
// These are raw items which might have dynamic contracts received from contractRegister call.
|
|
1410
1503
|
// Might contain duplicates which we should filter out
|
|
1411
1504
|
items: array<Internal.item>,
|
|
@@ -1417,167 +1510,130 @@ let registerDynamicContracts = (
|
|
|
1417
1510
|
)
|
|
1418
1511
|
}
|
|
1419
1512
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
//
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
let noEventsAddresses: dict<indexingAddress> = Dict.make()
|
|
1427
|
-
// Batch-level view of all addresses registered so far (across contracts,
|
|
1428
|
-
// including no-events ones), so two contracts registering the same address
|
|
1429
|
-
// within one batch conflict the same way as against indexingAddresses.
|
|
1430
|
-
let registeringAddresses: dict<indexingAddress> = Dict.make()
|
|
1431
|
-
|
|
1513
|
+
// Flatten every item's dcs into one batch, remembering where each came from
|
|
1514
|
+
// so the rejected ones can be spliced back out of their item (which is what
|
|
1515
|
+
// keeps them from being persisted twice).
|
|
1516
|
+
let registrations: array<AddressStore.registration> = []
|
|
1517
|
+
let sourceDcs: array<Internal.dcs> = []
|
|
1518
|
+
let sourceIndexes: array<int> = []
|
|
1432
1519
|
for itemIdx in 0 to items->Array.length - 1 {
|
|
1433
|
-
|
|
1434
|
-
switch item->Internal.getItemDcs {
|
|
1520
|
+
switch items->Array.getUnsafe(itemIdx)->Internal.getItemDcs {
|
|
1435
1521
|
| None => ()
|
|
1436
1522
|
| Some(dcs) =>
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
~contractStartBlock,
|
|
1452
|
-
),
|
|
1453
|
-
}
|
|
1454
|
-
// Prevent registering already indexing contracts
|
|
1455
|
-
switch indexingAddresses->IndexingAddresses.get(dc.address->Address.toString) {
|
|
1456
|
-
| Some(existingContract) =>
|
|
1457
|
-
// FIXME: Instead of filtering out duplicates,
|
|
1458
|
-
// we should check the block number first.
|
|
1459
|
-
// If new registration with earlier block number
|
|
1460
|
-
// we should register it for the missing block range
|
|
1461
|
-
if existingContract.contractName != dc.contractName {
|
|
1462
|
-
fetchState->warnDifferentContractType(~existingContract, ~dc=dcWithStartBlock)
|
|
1463
|
-
} else if existingContract.effectiveStartBlock > dcWithStartBlock.effectiveStartBlock {
|
|
1464
|
-
let logger = Logging.createChild(
|
|
1465
|
-
~params={
|
|
1466
|
-
"chainId": fetchState.chainId,
|
|
1467
|
-
"contractAddress": dc.address->Address.toString,
|
|
1468
|
-
"existingBlockNumber": existingContract.effectiveStartBlock,
|
|
1469
|
-
"newBlockNumber": dcWithStartBlock.effectiveStartBlock,
|
|
1470
|
-
},
|
|
1471
|
-
)
|
|
1472
|
-
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.`)
|
|
1473
|
-
}
|
|
1474
|
-
shouldRemove := true
|
|
1475
|
-
| None =>
|
|
1476
|
-
let shouldUpdate = switch registeringAddresses->Utils.Dict.dangerouslyGetNonOption(
|
|
1477
|
-
dc.address->Address.toString,
|
|
1478
|
-
) {
|
|
1479
|
-
| Some(registeringContract) if registeringContract.contractName != dc.contractName =>
|
|
1480
|
-
fetchState->warnDifferentContractType(
|
|
1481
|
-
~existingContract=registeringContract,
|
|
1482
|
-
~dc=dcWithStartBlock,
|
|
1483
|
-
)
|
|
1484
|
-
false
|
|
1485
|
-
| Some(_) => // Since the DC is registered by an earlier item in the query
|
|
1486
|
-
// FIXME: This unsafely relies on the asc order of the items
|
|
1487
|
-
// which is 99% true, but there were cases when the source ordering was wrong
|
|
1488
|
-
false
|
|
1489
|
-
| None => true
|
|
1490
|
-
}
|
|
1491
|
-
if shouldUpdate {
|
|
1492
|
-
earliestRegisteringEventBlockNumber :=
|
|
1493
|
-
Pervasives.min(
|
|
1494
|
-
earliestRegisteringEventBlockNumber.contents,
|
|
1495
|
-
dcWithStartBlock.effectiveStartBlock,
|
|
1496
|
-
)
|
|
1497
|
-
registeringContractsByContract
|
|
1498
|
-
->Utils.Dict.getOrInsertEmptyDict(dc.contractName)
|
|
1499
|
-
->Dict.set(dc.address->Address.toString, dcWithStartBlock)
|
|
1500
|
-
registeringAddresses->Dict.set(dc.address->Address.toString, dcWithStartBlock)
|
|
1501
|
-
} else {
|
|
1502
|
-
shouldRemove := true
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
| None =>
|
|
1506
|
-
let dcAsIndexingAddress: indexingAddress = {
|
|
1507
|
-
address: dc.address,
|
|
1508
|
-
contractName: dc.contractName,
|
|
1509
|
-
registrationBlock: dc.registrationBlock,
|
|
1510
|
-
effectiveStartBlock: IndexingAddresses.deriveEffectiveStartBlock(
|
|
1511
|
-
~registrationBlock=dc.registrationBlock,
|
|
1512
|
-
~contractStartBlock=None,
|
|
1513
|
-
),
|
|
1514
|
-
}
|
|
1515
|
-
// Prevent duplicate logging/persistence when the same address is
|
|
1516
|
-
// already tracked on fetchState, either from the db on startup or
|
|
1517
|
-
// from an earlier registration in this batch.
|
|
1518
|
-
switch indexingAddresses->IndexingAddresses.get(dc.address->Address.toString) {
|
|
1519
|
-
| Some(existingContract) =>
|
|
1520
|
-
if existingContract.contractName != dc.contractName {
|
|
1521
|
-
fetchState->warnDifferentContractType(~existingContract, ~dc=dcAsIndexingAddress)
|
|
1522
|
-
}
|
|
1523
|
-
shouldRemove := true
|
|
1524
|
-
| None =>
|
|
1525
|
-
switch registeringAddresses->Utils.Dict.dangerouslyGetNonOption(
|
|
1526
|
-
dc.address->Address.toString,
|
|
1527
|
-
) {
|
|
1528
|
-
| Some(existingContract) =>
|
|
1529
|
-
if existingContract.contractName != dc.contractName {
|
|
1530
|
-
fetchState->warnDifferentContractType(~existingContract, ~dc=dcAsIndexingAddress)
|
|
1531
|
-
}
|
|
1532
|
-
// Otherwise already queued for persistence by an earlier item in this batch.
|
|
1533
|
-
shouldRemove := true
|
|
1534
|
-
| None =>
|
|
1535
|
-
let logger = Logging.createChild(
|
|
1536
|
-
~params={
|
|
1537
|
-
"chainId": fetchState.chainId,
|
|
1538
|
-
"contractAddress": dc.address->Address.toString,
|
|
1539
|
-
"contractName": dc.contractName,
|
|
1540
|
-
},
|
|
1541
|
-
)
|
|
1542
|
-
// Persist the address to the db so a future config change that
|
|
1543
|
-
// adds events for this contract can pick it up on restart, but
|
|
1544
|
-
// skip partition registration since there's nothing to fetch.
|
|
1545
|
-
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.`)
|
|
1546
|
-
noEventsAddresses->Dict.set(dc.address->Address.toString, dcAsIndexingAddress)
|
|
1547
|
-
registeringAddresses->Dict.set(dc.address->Address.toString, dcAsIndexingAddress)
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1523
|
+
for dcIdx in 0 to dcs->Array.length - 1 {
|
|
1524
|
+
let dc = dcs->Array.getUnsafe(dcIdx)
|
|
1525
|
+
registrations
|
|
1526
|
+
->Array.push({
|
|
1527
|
+
address: dc.address,
|
|
1528
|
+
contractName: dc.contractName,
|
|
1529
|
+
registrationBlock: dc.registrationBlock,
|
|
1530
|
+
})
|
|
1531
|
+
->ignore
|
|
1532
|
+
sourceDcs->Array.push(dcs)->ignore
|
|
1533
|
+
sourceIndexes->Array.push(dcIdx)->ignore
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1551
1537
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1538
|
+
// Ids are handed out in registration order, so a cursor taken now selects
|
|
1539
|
+
// exactly what this batch adds.
|
|
1540
|
+
let idCursor = addressStore->AddressStore.nextId
|
|
1541
|
+
// The store resolves each address against both what it already holds and the
|
|
1542
|
+
// batch's own earlier entries, so two contracts claiming one address inside a
|
|
1543
|
+
// single batch conflict the same way as across batches.
|
|
1544
|
+
let verdicts = addressStore->AddressStore.registerBatch(registrations)
|
|
1545
|
+
|
|
1546
|
+
let warn = (~contractAddress, ~params, message) =>
|
|
1547
|
+
Logging.createChild(
|
|
1548
|
+
~params={
|
|
1549
|
+
"chainId": fetchState.chainId,
|
|
1550
|
+
"contractAddress": contractAddress,
|
|
1551
|
+
"details": params,
|
|
1552
|
+
},
|
|
1553
|
+
)->Logging.childWarn(message)
|
|
1554
|
+
|
|
1555
|
+
let registeringContractNames = []
|
|
1556
|
+
let hasNoEventsUpdatesRef = ref(false)
|
|
1557
|
+
for idx in 0 to verdicts->Array.length - 1 {
|
|
1558
|
+
let registration = registrations->Array.getUnsafe(idx)
|
|
1559
|
+
let contractAddress = registration.address->Address.toString
|
|
1560
|
+
let keep = switch verdicts->Array.getUnsafe(idx) {
|
|
1561
|
+
| Added(_) =>
|
|
1562
|
+
if !(registeringContractNames->Array.includes(registration.contractName)) {
|
|
1563
|
+
registeringContractNames->Array.push(registration.contractName)->ignore
|
|
1559
1564
|
}
|
|
1565
|
+
true
|
|
1566
|
+
| NoEvents(_) =>
|
|
1567
|
+
hasNoEventsUpdatesRef := true
|
|
1568
|
+
// Persist the address to the db so a future config change that adds
|
|
1569
|
+
// events for this contract can pick it up on restart, but skip partition
|
|
1570
|
+
// registration since there's nothing to fetch.
|
|
1571
|
+
warn(
|
|
1572
|
+
~contractAddress,
|
|
1573
|
+
~params={"contractName": registration.contractName},
|
|
1574
|
+
`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.`,
|
|
1575
|
+
)
|
|
1576
|
+
true
|
|
1577
|
+
| Conflict({existingContractName}) =>
|
|
1578
|
+
warn(
|
|
1579
|
+
~contractAddress,
|
|
1580
|
+
~params={
|
|
1581
|
+
"existingContractType": existingContractName,
|
|
1582
|
+
"newContractType": registration.contractName,
|
|
1583
|
+
},
|
|
1584
|
+
`Skipping contract registration: Contract address is already registered for one contract and cannot be registered for another contract.`,
|
|
1585
|
+
)
|
|
1586
|
+
false
|
|
1587
|
+
| Duplicate({effectiveStartBlock, existingEffectiveStartBlock}) =>
|
|
1588
|
+
// FIXME: Instead of filtering out duplicates, we should check the block
|
|
1589
|
+
// number first. If a new registration has an earlier block number we
|
|
1590
|
+
// should register it for the missing block range.
|
|
1591
|
+
if existingEffectiveStartBlock > effectiveStartBlock {
|
|
1592
|
+
warn(
|
|
1593
|
+
~contractAddress,
|
|
1594
|
+
~params={
|
|
1595
|
+
"existingBlockNumber": existingEffectiveStartBlock,
|
|
1596
|
+
"newBlockNumber": effectiveStartBlock,
|
|
1597
|
+
},
|
|
1598
|
+
`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.`,
|
|
1599
|
+
)
|
|
1600
|
+
}
|
|
1601
|
+
false
|
|
1602
|
+
| Invalid =>
|
|
1603
|
+
warn(
|
|
1604
|
+
~contractAddress,
|
|
1605
|
+
~params={"contractName": registration.contractName},
|
|
1606
|
+
`Skipping contract registration: Not a valid address for this chain's ecosystem.`,
|
|
1607
|
+
)
|
|
1608
|
+
false
|
|
1609
|
+
}
|
|
1610
|
+
if !keep {
|
|
1611
|
+
// Mark for removal; the splice happens below, back to front, so earlier
|
|
1612
|
+
// indexes stay valid.
|
|
1613
|
+
sourceIndexes->Array.setUnsafe(idx, -1 - sourceIndexes->Array.getUnsafe(idx))
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
for idx in verdicts->Array.length - 1 downto 0 {
|
|
1617
|
+
let marked = sourceIndexes->Array.getUnsafe(idx)
|
|
1618
|
+
if marked < 0 {
|
|
1619
|
+
let _ =
|
|
1620
|
+
sourceDcs->Array.getUnsafe(idx)->Array.splice(~start=-1 - marked, ~remove=1, ~insert=[])
|
|
1560
1621
|
}
|
|
1561
1622
|
}
|
|
1562
1623
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
switch (dcContractNamesToStore, hasNoEventsUpdates) {
|
|
1566
|
-
// Dont update anything when everything was filter out
|
|
1624
|
+
switch (registeringContractNames, hasNoEventsUpdatesRef.contents) {
|
|
1625
|
+
// Dont update anything when everything was filtered out
|
|
1567
1626
|
| ([], false) => fetchState
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
// partitions since there's nothing to fetch for them.
|
|
1572
|
-
indexingAddresses->IndexingAddresses.register(noEventsAddresses)
|
|
1573
|
-
fetchState
|
|
1627
|
+
// Only dcs for contracts without events. The store already tracks them so
|
|
1628
|
+
// subsequent registrations see them, but there are no partitions to touch.
|
|
1629
|
+
| ([], true) => fetchState
|
|
1574
1630
|
| (_, _) => {
|
|
1575
1631
|
let newPartitions = []
|
|
1576
1632
|
let dynamicContractsRef = ref(fetchState.optimizedPartitions.dynamicContracts)
|
|
1577
1633
|
let mutExistingPartitions = fetchState.optimizedPartitions.entities->Dict.valuesToArray
|
|
1578
1634
|
|
|
1579
|
-
for idx in 0 to
|
|
1580
|
-
let contractName =
|
|
1635
|
+
for idx in 0 to registeringContractNames->Array.length - 1 {
|
|
1636
|
+
let contractName = registeringContractNames->Array.getUnsafe(idx)
|
|
1581
1637
|
|
|
1582
1638
|
// When a new contract name is added as a dynamic contract for the first time (not in dynamicContracts set):
|
|
1583
1639
|
// Walks through existing partitions that have addresses for this contract name
|
|
@@ -1590,12 +1646,12 @@ let registerDynamicContracts = (
|
|
|
1590
1646
|
|
|
1591
1647
|
for idx in 0 to mutExistingPartitions->Array.length - 1 {
|
|
1592
1648
|
let p = mutExistingPartitions->Array.getUnsafe(idx)
|
|
1593
|
-
switch p.
|
|
1594
|
-
|
|
|
1595
|
-
|
|
|
1649
|
+
switch p.addresses->AddressSet.countFor(contractName) {
|
|
1650
|
+
| 0 => () // Skip partitions which don't have our contract
|
|
1651
|
+
| _ =>
|
|
1596
1652
|
// Also filter out partitions which are 100% not mergable
|
|
1597
1653
|
if p.selection.dependsOnAddresses && p.mergeBlock === None {
|
|
1598
|
-
let allPartitionContractNames = p.
|
|
1654
|
+
let allPartitionContractNames = p.addresses->AddressSet.contractNames
|
|
1599
1655
|
switch allPartitionContractNames {
|
|
1600
1656
|
| [_] =>
|
|
1601
1657
|
mutExistingPartitions->Array.setUnsafe(
|
|
@@ -1618,26 +1674,20 @@ let registerDynamicContracts = (
|
|
|
1618
1674
|
(fetchState.optimizedPartitions.nextPartitionIndex +
|
|
1619
1675
|
newPartitions->Array.length)->Int.toString
|
|
1620
1676
|
|
|
1621
|
-
let
|
|
1622
|
-
|
|
1623
|
-
restAddressesByContractName->Utils.Dict.deleteInPlace(contractName)
|
|
1624
|
-
|
|
1677
|
+
let restNames =
|
|
1678
|
+
allPartitionContractNames->Array.filter(name => name !== contractName)
|
|
1625
1679
|
mutExistingPartitions->Array.setUnsafe(
|
|
1626
1680
|
idx,
|
|
1627
|
-
|
|
1628
|
-
...p,
|
|
1629
|
-
addressesByContractName: restAddressesByContractName,
|
|
1630
|
-
},
|
|
1681
|
+
p->withAddresses(p.addresses->AddressSet.filterByContracts(restNames)),
|
|
1631
1682
|
)
|
|
1632
1683
|
|
|
1633
|
-
let
|
|
1634
|
-
addressesByContractName->Dict.set(contractName, addresses)
|
|
1684
|
+
let splitAddresses = p.addresses->AddressSet.filterByContracts([contractName])
|
|
1635
1685
|
newPartitions->Array.push({
|
|
1636
1686
|
id: newPartitionId,
|
|
1637
1687
|
latestFetchedBlock: p.latestFetchedBlock,
|
|
1638
1688
|
selection: fetchState.normalSelection,
|
|
1639
1689
|
dynamicContract: Some(contractName),
|
|
1640
|
-
|
|
1690
|
+
addresses: splitAddresses,
|
|
1641
1691
|
mergeBlock: None,
|
|
1642
1692
|
mutPendingQueries: p.mutPendingQueries,
|
|
1643
1693
|
sourceRangeCapacity: p.sourceRangeCapacity,
|
|
@@ -1652,16 +1702,11 @@ let registerDynamicContracts = (
|
|
|
1652
1702
|
}
|
|
1653
1703
|
}
|
|
1654
1704
|
}
|
|
1655
|
-
|
|
1656
|
-
let registeringContracts = registeringContractsByContract->Dict.getUnsafe(contractName)
|
|
1657
|
-
indexingAddresses->IndexingAddresses.register(registeringContracts)
|
|
1658
1705
|
}
|
|
1659
|
-
// Include no-events dcs so later batches detect conflicts against them.
|
|
1660
|
-
indexingAddresses->IndexingAddresses.register(noEventsAddresses)
|
|
1661
1706
|
|
|
1662
1707
|
// Switch any dynamic contract that has just crossed the server-side
|
|
1663
1708
|
// address threshold to client-side filtering. Sticky: the set only grows, and
|
|
1664
|
-
// collapse in
|
|
1709
|
+
// collapse in createPartitions folds the contract's
|
|
1665
1710
|
// partitions into the single address-free partition.
|
|
1666
1711
|
// Clone the sticky set before mutating so this update owns its copy and
|
|
1667
1712
|
// older fetchState snapshots keep theirs (Utils.Set.add mutates in place).
|
|
@@ -1674,7 +1719,7 @@ let registerDynamicContracts = (
|
|
|
1674
1719
|
dynamicContractsRef.contents
|
|
1675
1720
|
->Utils.Set.toArray
|
|
1676
1721
|
->Array.forEach(contractName => {
|
|
1677
|
-
let addressCount =
|
|
1722
|
+
let addressCount = addressStore->AddressStore.contractCount(contractName)
|
|
1678
1723
|
if !(clientFilteredContracts->Utils.Set.has(contractName)) && addressCount > threshold {
|
|
1679
1724
|
clientFilteredContracts->addClientFilteredContract(
|
|
1680
1725
|
~contractName,
|
|
@@ -1688,8 +1733,19 @@ let registerDynamicContracts = (
|
|
|
1688
1733
|
| None => fetchState.optimizedPartitions.clientFilteredContracts
|
|
1689
1734
|
}
|
|
1690
1735
|
|
|
1691
|
-
|
|
1692
|
-
|
|
1736
|
+
// Only this batch's additions need partitions; everything already
|
|
1737
|
+
// registered has one.
|
|
1738
|
+
let registeringSetsByContract = Dict.make()
|
|
1739
|
+
registeringContractNames->Array.forEach(contractName => {
|
|
1740
|
+
registeringSetsByContract->Dict.set(
|
|
1741
|
+
contractName,
|
|
1742
|
+
addressStore->AddressStore.makeSet(~contractName, ~options={minId: idCursor}),
|
|
1743
|
+
)
|
|
1744
|
+
})
|
|
1745
|
+
|
|
1746
|
+
let optimizedPartitions = createPartitions(
|
|
1747
|
+
~registeringSetsByContract,
|
|
1748
|
+
~addressStore,
|
|
1693
1749
|
~dynamicContracts=dynamicContractsRef.contents,
|
|
1694
1750
|
~clientFilteredContracts,
|
|
1695
1751
|
~normalSelection=fetchState.normalSelection,
|
|
@@ -1698,6 +1754,7 @@ let registerDynamicContracts = (
|
|
|
1698
1754
|
newPartitions->Array.length,
|
|
1699
1755
|
~existingPartitions=mutExistingPartitions->Array.concat(newPartitions),
|
|
1700
1756
|
~progressBlockNumber=0,
|
|
1757
|
+
~knownHeight=Pervasives.max(claimCeiling, fetchState.knownHeight),
|
|
1701
1758
|
)
|
|
1702
1759
|
|
|
1703
1760
|
fetchState->updateInternal(~optimizedPartitions)
|
|
@@ -1705,33 +1762,6 @@ let registerDynamicContracts = (
|
|
|
1705
1762
|
}
|
|
1706
1763
|
}
|
|
1707
1764
|
|
|
1708
|
-
// Drop events an address-param filter rejects. A merged partition may
|
|
1709
|
-
// over-fetch a wildcard event whose indexed address param references an
|
|
1710
|
-
// address registered after the log's block; `clientAddressFilter` is the
|
|
1711
|
-
// param-level analogue of the srcAddress effectiveStartBlock check.
|
|
1712
|
-
let filterByClientAddress = (
|
|
1713
|
-
items: array<Internal.item>,
|
|
1714
|
-
~indexingAddresses: IndexingAddresses.t,
|
|
1715
|
-
): array<Internal.item> =>
|
|
1716
|
-
items->Array.filter(item =>
|
|
1717
|
-
switch item {
|
|
1718
|
-
| Internal.Event({payload, blockNumber}) as item =>
|
|
1719
|
-
let reg = (item->Internal.castUnsafeEventItem).onEventRegistration
|
|
1720
|
-
switch reg.clientAddressFilter {
|
|
1721
|
-
| Some(filter) =>
|
|
1722
|
-
filter(
|
|
1723
|
-
payload,
|
|
1724
|
-
blockNumber,
|
|
1725
|
-
indexingAddresses->IndexingAddresses.forContract(
|
|
1726
|
-
~contractName=reg.eventConfig.contractName,
|
|
1727
|
-
),
|
|
1728
|
-
)
|
|
1729
|
-
| None => true
|
|
1730
|
-
}
|
|
1731
|
-
| _ => true
|
|
1732
|
-
}
|
|
1733
|
-
)
|
|
1734
|
-
|
|
1735
1765
|
/*
|
|
1736
1766
|
Updates fetchState with a response for a given query.
|
|
1737
1767
|
Returns Error if the partition with given query cannot be found (unexpected)
|
|
@@ -1831,7 +1861,7 @@ let pushDensityPricedQuery = (
|
|
|
1831
1861
|
~density,
|
|
1832
1862
|
~chainTargetBlock,
|
|
1833
1863
|
~selection,
|
|
1834
|
-
~
|
|
1864
|
+
~addresses,
|
|
1835
1865
|
) => {
|
|
1836
1866
|
let itemsEst = densityItemsTarget(~density, ~fromBlock, ~toBlock, ~chainTargetBlock)
|
|
1837
1867
|
queries
|
|
@@ -1846,7 +1876,7 @@ let pushDensityPricedQuery = (
|
|
|
1846
1876
|
| None => Some(itemsEst)
|
|
1847
1877
|
},
|
|
1848
1878
|
itemsEst,
|
|
1849
|
-
|
|
1879
|
+
addresses,
|
|
1850
1880
|
})
|
|
1851
1881
|
->ignore
|
|
1852
1882
|
itemsEst
|
|
@@ -1875,7 +1905,7 @@ let pushGapFillQueries = (
|
|
|
1875
1905
|
~partition: partition,
|
|
1876
1906
|
~partitionBudget: float,
|
|
1877
1907
|
~selection: selection,
|
|
1878
|
-
~
|
|
1908
|
+
~addresses: AddressSet.t,
|
|
1879
1909
|
) => {
|
|
1880
1910
|
// Gaps past the chain's target block wait: they regenerate from the
|
|
1881
1911
|
// pending-walk each tick and fill once the target reaches them. The lagged
|
|
@@ -1899,7 +1929,7 @@ let pushGapFillQueries = (
|
|
|
1899
1929
|
~density,
|
|
1900
1930
|
~chainTargetBlock,
|
|
1901
1931
|
~selection,
|
|
1902
|
-
~
|
|
1932
|
+
~addresses,
|
|
1903
1933
|
)
|
|
1904
1934
|
->ignore
|
|
1905
1935
|
switch (partition->getTrustedDensity, maybeChunkRange) {
|
|
@@ -1921,7 +1951,7 @@ let pushGapFillQueries = (
|
|
|
1921
1951
|
~density,
|
|
1922
1952
|
~chainTargetBlock,
|
|
1923
1953
|
~selection,
|
|
1924
|
-
~
|
|
1954
|
+
~addresses,
|
|
1925
1955
|
)
|
|
1926
1956
|
->ignore
|
|
1927
1957
|
chunkFromBlock := chunkToBlock + 1
|
|
@@ -1994,7 +2024,7 @@ let walkPartitionPending = (
|
|
|
1994
2024
|
~partition=p,
|
|
1995
2025
|
~partitionBudget,
|
|
1996
2026
|
~selection=p.selection,
|
|
1997
|
-
~
|
|
2027
|
+
~addresses=p.addresses,
|
|
1998
2028
|
)
|
|
1999
2029
|
chunksUsedThisCall := chunksUsedThisCall.contents + (candidates->Array.length - beforeLen)
|
|
2000
2030
|
}
|
|
@@ -2097,7 +2127,7 @@ let pushForwardCandidates = (
|
|
|
2097
2127
|
~density,
|
|
2098
2128
|
~chainTargetBlock,
|
|
2099
2129
|
~selection=p.selection,
|
|
2100
|
-
~
|
|
2130
|
+
~addresses=p.addresses,
|
|
2101
2131
|
)
|
|
2102
2132
|
generatedItems := generatedItems.contents +. itemsEst->Int.toFloat
|
|
2103
2133
|
chunkFromBlock := chunkToBlock + 1
|
|
@@ -2132,7 +2162,7 @@ let pushForwardCandidates = (
|
|
|
2132
2162
|
// keeps a server cap at its estimate to protect the shared buffer.
|
|
2133
2163
|
itemsTarget: Some(itemsEst),
|
|
2134
2164
|
itemsEst,
|
|
2135
|
-
|
|
2165
|
+
addresses: p.addresses,
|
|
2136
2166
|
})
|
|
2137
2167
|
->ignore
|
|
2138
2168
|
}
|
|
@@ -2456,7 +2486,7 @@ let make = (
|
|
|
2456
2486
|
~startBlock,
|
|
2457
2487
|
~endBlock,
|
|
2458
2488
|
~onEventRegistrations: array<Internal.onEventRegistration>,
|
|
2459
|
-
~
|
|
2489
|
+
~addressStore: AddressStore.t,
|
|
2460
2490
|
~addresses: array<Internal.indexingAddress>,
|
|
2461
2491
|
~maxAddrInPartition,
|
|
2462
2492
|
~chainId,
|
|
@@ -2467,6 +2497,7 @@ let make = (
|
|
|
2467
2497
|
~blockLag=0,
|
|
2468
2498
|
~firstEventBlock=None,
|
|
2469
2499
|
~clientFilterAddressThreshold=None,
|
|
2500
|
+
~isResumed=false,
|
|
2470
2501
|
): t => {
|
|
2471
2502
|
let latestFetchedBlock = {
|
|
2472
2503
|
blockTimestamp: 0,
|
|
@@ -2496,7 +2527,7 @@ let make = (
|
|
|
2496
2527
|
dependsOnAddresses: false,
|
|
2497
2528
|
onEventRegistrations: notDependingOnAddresses,
|
|
2498
2529
|
},
|
|
2499
|
-
|
|
2530
|
+
addresses: addressStore->AddressStore.emptySet,
|
|
2500
2531
|
mergeBlock: None,
|
|
2501
2532
|
dynamicContract: None,
|
|
2502
2533
|
mutPendingQueries: [],
|
|
@@ -2512,9 +2543,22 @@ let make = (
|
|
|
2512
2543
|
onEventRegistrations: normalRegistrations,
|
|
2513
2544
|
}
|
|
2514
2545
|
|
|
2515
|
-
|
|
2546
|
+
// Every address the chain indexes goes into the store — including ones whose
|
|
2547
|
+
// contract has no address-dependent events, so a later registration of the
|
|
2548
|
+
// same address still conflicts and the address is still persisted.
|
|
2549
|
+
addressStore
|
|
2550
|
+
->AddressStore.registerBatch(
|
|
2551
|
+
addresses->Array.map((contract): AddressStore.registration => {
|
|
2552
|
+
address: contract.address,
|
|
2553
|
+
contractName: contract.contractName,
|
|
2554
|
+
registrationBlock: contract.registrationBlock,
|
|
2555
|
+
}),
|
|
2556
|
+
)
|
|
2557
|
+
->ignore
|
|
2558
|
+
|
|
2516
2559
|
let dynamicContracts = Utils.Set.make()
|
|
2517
2560
|
let clientFilteredContracts = Utils.Set.make()
|
|
2561
|
+
let registeringSetsByContract = Dict.make()
|
|
2518
2562
|
|
|
2519
2563
|
addresses->Array.forEach(contract => {
|
|
2520
2564
|
let contractName = contract.contractName
|
|
@@ -2522,12 +2566,12 @@ let make = (
|
|
|
2522
2566
|
// Only addresses whose contract has events that depend on addresses get
|
|
2523
2567
|
// registered for active fetching via partitions.
|
|
2524
2568
|
if contractNamesWithNormalEvents->Utils.Set.has(contractName) {
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2569
|
+
if !(registeringSetsByContract->Dict.has(contractName)) {
|
|
2570
|
+
registeringSetsByContract->Dict.set(
|
|
2571
|
+
contractName,
|
|
2572
|
+
addressStore->AddressStore.makeSet(~contractName),
|
|
2573
|
+
)
|
|
2574
|
+
}
|
|
2531
2575
|
|
|
2532
2576
|
// Detect dynamic contracts by registrationBlock
|
|
2533
2577
|
if contract.registrationBlock !== -1 {
|
|
@@ -2541,22 +2585,24 @@ let make = (
|
|
|
2541
2585
|
// address list, or a dynamic contract restored from a large persisted set.
|
|
2542
2586
|
switch clientFilterAddressThreshold {
|
|
2543
2587
|
| Some(threshold) =>
|
|
2544
|
-
|
|
2545
|
-
let addressCount =
|
|
2588
|
+
registeringSetsByContract->Utils.Dict.forEachWithKey((set, contractName) => {
|
|
2589
|
+
let addressCount = set->AddressSet.size
|
|
2546
2590
|
if addressCount > threshold {
|
|
2547
2591
|
clientFilteredContracts->addClientFilteredContract(
|
|
2548
2592
|
~contractName,
|
|
2549
2593
|
~chainId,
|
|
2550
2594
|
~addressCount,
|
|
2551
2595
|
~threshold,
|
|
2596
|
+
~shouldLog=!isResumed,
|
|
2552
2597
|
)
|
|
2553
2598
|
}
|
|
2554
2599
|
})
|
|
2555
2600
|
| None => ()
|
|
2556
2601
|
}
|
|
2557
2602
|
|
|
2558
|
-
let optimizedPartitions =
|
|
2559
|
-
~
|
|
2603
|
+
let optimizedPartitions = createPartitions(
|
|
2604
|
+
~registeringSetsByContract,
|
|
2605
|
+
~addressStore,
|
|
2560
2606
|
~dynamicContracts,
|
|
2561
2607
|
~clientFilteredContracts,
|
|
2562
2608
|
~normalSelection,
|
|
@@ -2564,6 +2610,7 @@ let make = (
|
|
|
2564
2610
|
~nextPartitionIndex=partitions->Array.length,
|
|
2565
2611
|
~existingPartitions=partitions, // wildcard partition(s) if any
|
|
2566
2612
|
~progressBlockNumber,
|
|
2613
|
+
~knownHeight,
|
|
2567
2614
|
)
|
|
2568
2615
|
|
|
2569
2616
|
if (
|
|
@@ -2604,7 +2651,6 @@ let make = (
|
|
|
2604
2651
|
|
|
2605
2652
|
let fetchState = {
|
|
2606
2653
|
optimizedPartitions,
|
|
2607
|
-
contractConfigs,
|
|
2608
2654
|
chainId,
|
|
2609
2655
|
startBlock,
|
|
2610
2656
|
endBlock,
|
|
@@ -2657,16 +2703,29 @@ Always recreates optimized partitions to avoid duplicate addresses:
|
|
|
2657
2703
|
- Non-wildcard with lfb <= target: keep, adjust pending queries and mergeBlock
|
|
2658
2704
|
- Non-wildcard with lfb > target: delete, track addresses for recreation
|
|
2659
2705
|
*/
|
|
2660
|
-
let rollback = (fetchState: t, ~
|
|
2661
|
-
// Step 1: Prune addresses registered after the target block. The pruned
|
|
2662
|
-
// then the source of truth for partition cleanup below — an address
|
|
2663
|
-
//
|
|
2664
|
-
|
|
2706
|
+
let rollback = (fetchState: t, ~addressStore: AddressStore.t, ~targetBlockNumber) => {
|
|
2707
|
+
// Step 1: Prune addresses registered after the target block. The pruned store
|
|
2708
|
+
// is then the source of truth for partition cleanup below — an address
|
|
2709
|
+
// survives iff `filterByRegistrationBlock` keeps it.
|
|
2710
|
+
addressStore->AddressStore.rollback(targetBlockNumber)->ignore
|
|
2665
2711
|
|
|
2666
2712
|
// Step 2: Categorize partitions
|
|
2667
2713
|
let keptPartitions = []
|
|
2668
2714
|
let nextKeptIdRef = ref(0)
|
|
2669
|
-
let
|
|
2715
|
+
let registeringSetsByContract: dict<AddressSet.t> = Dict.make()
|
|
2716
|
+
let collectForRecreation = (set: AddressSet.t) =>
|
|
2717
|
+
set
|
|
2718
|
+
->AddressSet.contractNames
|
|
2719
|
+
->Array.forEach(contractName => {
|
|
2720
|
+
let contractSet = set->AddressSet.filterByContracts([contractName])
|
|
2721
|
+
registeringSetsByContract->Dict.set(
|
|
2722
|
+
contractName,
|
|
2723
|
+
switch registeringSetsByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
2724
|
+
| Some(existing) => existing->AddressSet.merge(contractSet)
|
|
2725
|
+
| None => contractSet
|
|
2726
|
+
},
|
|
2727
|
+
)
|
|
2728
|
+
})
|
|
2670
2729
|
|
|
2671
2730
|
let partitions = fetchState.optimizedPartitions.entities->Dict.valuesToArray
|
|
2672
2731
|
for idx in 0 to partitions->Array.length - 1 {
|
|
@@ -2683,52 +2742,41 @@ let rollback = (fetchState: t, ~indexingAddresses: IndexingAddresses.t, ~targetB
|
|
|
2683
2742
|
latestFetchedBlock: p.latestFetchedBlock.blockNumber > targetBlockNumber
|
|
2684
2743
|
? {blockNumber: targetBlockNumber, blockTimestamp: 0}
|
|
2685
2744
|
: p.latestFetchedBlock,
|
|
2745
|
+
// Everything above the target is refetched by whichever partition this
|
|
2746
|
+
// one was catching up to, so there is nothing left to catch up on past
|
|
2747
|
+
// it. createPartitions drops the partition outright when the capped
|
|
2748
|
+
// block is already reached.
|
|
2749
|
+
mergeBlock: switch p.mergeBlock {
|
|
2750
|
+
| Some(mergeBlock) if mergeBlock > targetBlockNumber => Some(targetBlockNumber)
|
|
2751
|
+
| other => other
|
|
2752
|
+
},
|
|
2686
2753
|
mutPendingQueries: rollbackPendingQueries(p.mutPendingQueries, ~targetBlockNumber),
|
|
2687
2754
|
})
|
|
2688
2755
|
->ignore
|
|
2689
2756
|
|
|
2690
2757
|
// Non-wildcard with lfb > target: delete, collect addresses for recreation
|
|
2691
2758
|
| _ if p.latestFetchedBlock.blockNumber > targetBlockNumber =>
|
|
2692
|
-
p.
|
|
2693
|
-
addresses->Array.forEach(address => {
|
|
2694
|
-
switch indexingAddresses->IndexingAddresses.get(address->Address.toString) {
|
|
2695
|
-
| Some(indexingContract) =>
|
|
2696
|
-
let registeringContracts =
|
|
2697
|
-
registeringContractsByContract->Utils.Dict.getOrInsertEmptyDict(contractName)
|
|
2698
|
-
registeringContracts->Dict.set(address->Address.toString, indexingContract)
|
|
2699
|
-
| None => ()
|
|
2700
|
-
}
|
|
2701
|
-
})
|
|
2702
|
-
})
|
|
2759
|
+
collectForRecreation(p.addresses->AddressSet.filterByRegistrationBlock(targetBlockNumber))
|
|
2703
2760
|
|
|
2704
2761
|
// Non-wildcard with lfb <= target: keep, adjust pending queries and mergeBlock
|
|
2705
|
-
|
|
|
2762
|
+
| _ => {
|
|
2706
2763
|
// Cap mergeBlock at target
|
|
2707
2764
|
let mergeBlock = switch p.mergeBlock {
|
|
2708
2765
|
| Some(mergeBlock) if mergeBlock > targetBlockNumber => Some(targetBlockNumber)
|
|
2709
2766
|
| other => other
|
|
2710
2767
|
}
|
|
2711
2768
|
|
|
2712
|
-
// Drop addresses pruned from the
|
|
2713
|
-
let
|
|
2714
|
-
|
|
2715
|
-
let keptAddresses =
|
|
2716
|
-
addresses->Array.filter(address =>
|
|
2717
|
-
indexingAddresses->IndexingAddresses.get(address->Address.toString)->Option.isSome
|
|
2718
|
-
)
|
|
2719
|
-
if keptAddresses->Array.length > 0 {
|
|
2720
|
-
rollbackedAddressesByContractName->Dict.set(contractName, keptAddresses)
|
|
2721
|
-
}
|
|
2722
|
-
})
|
|
2769
|
+
// Drop addresses pruned from the store
|
|
2770
|
+
let rollbackedAddresses =
|
|
2771
|
+
p.addresses->AddressSet.filterByRegistrationBlock(targetBlockNumber)
|
|
2723
2772
|
|
|
2724
|
-
if !(
|
|
2773
|
+
if !(rollbackedAddresses->AddressSet.isEmpty) {
|
|
2725
2774
|
let id = nextKeptIdRef.contents->Int.toString
|
|
2726
2775
|
nextKeptIdRef := nextKeptIdRef.contents + 1
|
|
2727
2776
|
keptPartitions
|
|
2728
2777
|
->Array.push({
|
|
2729
|
-
...p,
|
|
2778
|
+
...p->withAddresses(rollbackedAddresses),
|
|
2730
2779
|
id,
|
|
2731
|
-
addressesByContractName: rollbackedAddressesByContractName,
|
|
2732
2780
|
mutPendingQueries: rollbackPendingQueries(p.mutPendingQueries, ~targetBlockNumber),
|
|
2733
2781
|
mergeBlock,
|
|
2734
2782
|
})
|
|
@@ -2739,8 +2787,9 @@ let rollback = (fetchState: t, ~indexingAddresses: IndexingAddresses.t, ~targetB
|
|
|
2739
2787
|
}
|
|
2740
2788
|
|
|
2741
2789
|
// Step 3: Recreate partitions from deleted partition addresses
|
|
2742
|
-
let optimizedPartitions =
|
|
2743
|
-
~
|
|
2790
|
+
let optimizedPartitions = createPartitions(
|
|
2791
|
+
~registeringSetsByContract,
|
|
2792
|
+
~addressStore,
|
|
2744
2793
|
~dynamicContracts=fetchState.optimizedPartitions.dynamicContracts,
|
|
2745
2794
|
~clientFilteredContracts=fetchState.optimizedPartitions.clientFilteredContracts,
|
|
2746
2795
|
~normalSelection=fetchState.normalSelection,
|
|
@@ -2748,6 +2797,7 @@ let rollback = (fetchState: t, ~indexingAddresses: IndexingAddresses.t, ~targetB
|
|
|
2748
2797
|
~nextPartitionIndex=nextKeptIdRef.contents,
|
|
2749
2798
|
~existingPartitions=keptPartitions,
|
|
2750
2799
|
~progressBlockNumber=targetBlockNumber,
|
|
2800
|
+
~knownHeight=fetchState.knownHeight,
|
|
2751
2801
|
)
|
|
2752
2802
|
|
|
2753
2803
|
// Step 4: Update state
|