envio 3.5.0-alpha.2 → 3.5.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/Batch.res +8 -8
- package/src/Batch.res.mjs +4 -3
- package/src/BatchProcessing.res +24 -0
- package/src/BatchProcessing.res.mjs +12 -0
- package/src/ChainFetching.res +20 -19
- package/src/ChainFetching.res.mjs +14 -14
- package/src/ChainId.res +58 -0
- package/src/ChainId.res.mjs +49 -0
- package/src/ChainId.resi +27 -0
- package/src/ChainMap.res +12 -26
- package/src/ChainMap.res.mjs +5 -41
- package/src/ChainMap.resi +5 -20
- package/src/ChainMetadata.res +1 -1
- package/src/ChainMetadata.res.mjs +3 -2
- package/src/ChainState.res +45 -24
- package/src/ChainState.res.mjs +40 -16
- package/src/ChainState.resi +3 -1
- package/src/Config.res +43 -26
- package/src/Config.res.mjs +19 -16
- package/src/ContractRegisterContext.res +1 -1
- package/src/ContractRegisterContext.res.mjs +1 -1
- package/src/Core.res +1 -0
- package/src/CrossChainState.res +90 -22
- package/src/CrossChainState.res.mjs +55 -16
- package/src/CrossChainState.resi +5 -1
- package/src/Env.res +0 -3
- package/src/Env.res.mjs +0 -3
- package/src/EventConfigBuilder.res +1 -1
- package/src/EventProcessing.res +4 -4
- package/src/EventProcessing.res.mjs +5 -5
- package/src/FetchState.res +489 -153
- package/src/FetchState.res.mjs +399 -129
- package/src/FinalizeBackfill.res +61 -0
- package/src/FinalizeBackfill.res.mjs +44 -0
- package/src/HandlerRegister.res +8 -8
- package/src/HandlerRegister.res.mjs +5 -4
- package/src/HandlerRegister.resi +1 -1
- package/src/InMemoryStore.res +4 -0
- package/src/InMemoryStore.res.mjs +1 -1
- package/src/InMemoryTable.res +15 -15
- package/src/InMemoryTable.res.mjs +14 -14
- package/src/IndexerLoop.res +9 -2
- package/src/IndexerLoop.res.mjs +8 -1
- package/src/IndexerState.res +52 -23
- package/src/IndexerState.res.mjs +52 -14
- package/src/IndexerState.resi +11 -6
- package/src/Internal.res +19 -17
- package/src/Internal.res.mjs +18 -9
- package/src/LoadLayer.res +10 -2
- package/src/LoadLayer.res.mjs +3 -1
- package/src/LogSelection.res +2 -2
- package/src/Main.res +7 -11
- package/src/Main.res.mjs +4 -6
- package/src/Metrics.res +6 -6
- package/src/Metrics.res.mjs +4 -3
- package/src/Persistence.res +23 -4
- package/src/PgStorage.res +447 -100
- package/src/PgStorage.res.mjs +285 -99
- package/src/RawEvent.res +1 -2
- package/src/RawEvent.res.mjs +1 -1
- package/src/Rollback.res +18 -20
- package/src/Rollback.res.mjs +9 -9
- package/src/RollbackCommit.res +2 -2
- package/src/RollbackCommit.res.mjs +3 -3
- package/src/SafeCheckpointTracking.res +2 -2
- package/src/SimulateDeadInputTracker.res +17 -21
- package/src/SimulateDeadInputTracker.res.mjs +9 -8
- package/src/SimulateItems.res +8 -10
- package/src/SimulateItems.res.mjs +9 -10
- package/src/Sink.res +2 -2
- package/src/Sink.res.mjs +3 -2
- package/src/TestIndexer.res +14 -14
- package/src/TestIndexer.res.mjs +16 -10
- package/src/UserContext.res +2 -2
- package/src/UserContext.res.mjs +2 -1
- package/src/Utils.res +0 -4
- package/src/Utils.res.mjs +1 -6
- package/src/bindings/ClickHouse.res +20 -2
- package/src/bindings/ClickHouse.res.mjs +25 -14
- package/src/db/EntityFilter.res +0 -4
- package/src/db/EntityFilter.res.mjs +1 -8
- package/src/db/EntityHistory.res +1 -1
- package/src/db/IndexCatalog.res +248 -0
- package/src/db/IndexCatalog.res.mjs +222 -0
- package/src/db/IndexDefinition.res +131 -0
- package/src/db/IndexDefinition.res.mjs +138 -0
- package/src/db/IndexManager.res +145 -0
- package/src/db/IndexManager.res.mjs +130 -0
- package/src/db/InternalTable.res +54 -23
- package/src/db/InternalTable.res.mjs +38 -23
- package/src/db/Table.res +24 -14
- package/src/db/Table.res.mjs +21 -16
- package/src/sources/AddressSet.res +5 -4
- package/src/sources/AddressStore.res +25 -14
- package/src/sources/AddressStore.res.mjs +10 -12
- package/src/sources/Evm.res +2 -2
- package/src/sources/Evm.res.mjs +1 -1
- package/src/sources/EvmChain.res +3 -3
- package/src/sources/EvmChain.res.mjs +3 -3
- package/src/sources/EvmHyperSyncSource.res +7 -7
- package/src/sources/EvmHyperSyncSource.res.mjs +6 -6
- package/src/sources/Fuel.res +2 -2
- package/src/sources/Fuel.res.mjs +1 -1
- package/src/sources/FuelHyperSyncClient.res +3 -0
- package/src/sources/FuelHyperSyncClient.res.mjs +1 -0
- package/src/sources/FuelHyperSyncSource.res +5 -7
- package/src/sources/FuelHyperSyncSource.res.mjs +5 -5
- package/src/sources/HyperSync.resi +2 -2
- package/src/sources/HyperSyncClient.res +5 -0
- package/src/sources/HyperSyncClient.res.mjs +1 -0
- package/src/sources/RpcSource.res +13 -14
- package/src/sources/RpcSource.res.mjs +13 -12
- package/src/sources/SimulateSource.res +42 -15
- package/src/sources/SimulateSource.res.mjs +27 -9
- package/src/sources/Source.res +1 -5
- package/src/sources/SourceManager.res +12 -12
- package/src/sources/SourceManager.res.mjs +11 -10
- package/src/sources/SourceManager.resi +2 -2
- package/src/sources/Svm.res +4 -5
- package/src/sources/Svm.res.mjs +5 -4
- package/src/sources/SvmHyperSyncClient.res +3 -0
- package/src/sources/SvmHyperSyncClient.res.mjs +1 -0
- package/src/sources/SvmHyperSyncSource.res +4 -4
- package/src/sources/SvmHyperSyncSource.res.mjs +3 -3
- package/src/tui/Tui.res +1 -1
- package/src/tui/Tui.res.mjs +2 -1
- package/src/tui/components/CustomHooks.res +5 -4
- package/src/tui/components/CustomHooks.res.mjs +4 -2
package/src/FetchState.res
CHANGED
|
@@ -19,6 +19,42 @@ type selection = {
|
|
|
19
19
|
// gates each item against the chain's address store instead of the
|
|
20
20
|
// partition's set. Absent for normal partitions.
|
|
21
21
|
clientFilteredContracts?: array<string>,
|
|
22
|
+
// The earliest block any of these registrations can produce an item at.
|
|
23
|
+
// Derived once here, by `makeSelection`, because `getNextQuery` reads it for
|
|
24
|
+
// every partition on every tick and a chain can hold hundreds of them.
|
|
25
|
+
//
|
|
26
|
+
// Absent when a registration is unrestricted and so can fire from the chain
|
|
27
|
+
// start — which is also the only case where nothing can be skipped, so absent
|
|
28
|
+
// and "block 0" are the same answer here. Build selections through
|
|
29
|
+
// `makeSelection` rather than as literals, or this goes stale.
|
|
30
|
+
startBlock?: int,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The earliest block any of the registrations can produce an item at, or `None`
|
|
34
|
+
// when one of them is unrestricted. Nothing below it is worth querying.
|
|
35
|
+
let deriveSelectionStartBlock = (onEventRegistrations: array<Internal.onEventRegistration>) => {
|
|
36
|
+
let earliest = ref(None)
|
|
37
|
+
let idx = ref(0)
|
|
38
|
+
let unrestricted = ref(onEventRegistrations->Utils.Array.isEmpty)
|
|
39
|
+
while !unrestricted.contents && idx.contents < onEventRegistrations->Array.length {
|
|
40
|
+
switch (onEventRegistrations->Array.getUnsafe(idx.contents)).startBlock {
|
|
41
|
+
| None => unrestricted := true
|
|
42
|
+
| Some(startBlock) =>
|
|
43
|
+
switch earliest.contents {
|
|
44
|
+
| Some(current) if current <= startBlock => ()
|
|
45
|
+
| _ => earliest := Some(startBlock)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
idx := idx.contents + 1
|
|
49
|
+
}
|
|
50
|
+
unrestricted.contents ? None : earliest.contents
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let makeSelection = (~onEventRegistrations, ~dependsOnAddresses, ~clientFilteredContracts=?) => {
|
|
54
|
+
onEventRegistrations,
|
|
55
|
+
dependsOnAddresses,
|
|
56
|
+
?clientFilteredContracts,
|
|
57
|
+
startBlock: ?deriveSelectionStartBlock(onEventRegistrations),
|
|
22
58
|
}
|
|
23
59
|
|
|
24
60
|
type pendingQuery = {
|
|
@@ -100,6 +136,43 @@ type query = {
|
|
|
100
136
|
|
|
101
137
|
let withAddresses = (p: partition, addresses: AddressSet.t) => {...p, addresses}
|
|
102
138
|
|
|
139
|
+
// The selection a query over [fromBlock, toBlock] actually needs: a registration
|
|
140
|
+
// whose own start block sits past the range can't produce an item there, so
|
|
141
|
+
// leaving it out keeps the source from asking the server for its logs at all.
|
|
142
|
+
// The address gate can't express this — it's contract-wide, so an unrestricted
|
|
143
|
+
// sibling holds it open from the chain start for every registration alike.
|
|
144
|
+
//
|
|
145
|
+
// Returns the selection as-is when nothing is dropped (the common case), and
|
|
146
|
+
// for an open-ended query, which may reach any block and so can exclude nothing.
|
|
147
|
+
// Also when every registration would be dropped: `selectionStartBlock` keeps a
|
|
148
|
+
// partition's cursor at or above its earliest start block, so a query below all
|
|
149
|
+
// of them shouldn't exist — and an empty selection is one no source can build.
|
|
150
|
+
let narrowSelectionToRange = (selection: selection, ~toBlock) =>
|
|
151
|
+
switch toBlock {
|
|
152
|
+
| None => selection
|
|
153
|
+
| Some(toBlock) =>
|
|
154
|
+
let inRange = selection.onEventRegistrations->Array.filter(reg =>
|
|
155
|
+
switch reg.startBlock {
|
|
156
|
+
| Some(startBlock) => startBlock <= toBlock
|
|
157
|
+
| None => true
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
switch inRange->Array.length {
|
|
161
|
+
// Every registration starts above the range. Narrowing to nothing would
|
|
162
|
+
// build a selection with no log filters, which each source reads as
|
|
163
|
+
// "select everything" — the opposite of the intent. The partition is
|
|
164
|
+
// queried unnarrowed instead and the routers drop what hasn't started.
|
|
165
|
+
| 0 => selection
|
|
166
|
+
| length if length === selection.onEventRegistrations->Array.length => selection
|
|
167
|
+
| _ =>
|
|
168
|
+
makeSelection(
|
|
169
|
+
~onEventRegistrations=inRange,
|
|
170
|
+
~dependsOnAddresses=selection.dependsOnAddresses,
|
|
171
|
+
~clientFilteredContracts=?selection.clientFilteredContracts,
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
103
176
|
// itemsEst for a query over [fromBlock, toBlock] at the given event density
|
|
104
177
|
// (items/block). toBlock None is the open-ended tail, capped at
|
|
105
178
|
// chainTargetBlock — the soft per-tick horizon the owning chain wants to reach
|
|
@@ -127,6 +200,14 @@ let getMinHistoryRange = (p: partition) => {
|
|
|
127
200
|
// source-capacity history, including when an itemsTarget cap truncates a response.
|
|
128
201
|
let getTrustedDensity = (p: partition) => p.eventDensity
|
|
129
202
|
|
|
203
|
+
// A response is still owed for this partition, so it owns range nothing else
|
|
204
|
+
// accounts for: removing it would let the frontier advance over blocks the
|
|
205
|
+
// response can still deliver items from, and orphan the response itself.
|
|
206
|
+
// Narrower than a non-empty queue on purpose — a settled query parked behind a
|
|
207
|
+
// gap is data already in hand, and waiting on it would wedge a partition that
|
|
208
|
+
// no longer queries.
|
|
209
|
+
let isFetching = (p: partition) => p.mutPendingQueries->Array.some(pq => pq.fetchedBlock === None)
|
|
210
|
+
|
|
130
211
|
let getMinQueryRange = (partitions: array<partition>) => {
|
|
131
212
|
let min = ref(0)
|
|
132
213
|
for i in 0 to partitions->Array.length - 1 {
|
|
@@ -270,6 +351,47 @@ module OptimizedPartitions = {
|
|
|
270
351
|
let ascSortFn = (a, b) =>
|
|
271
352
|
Int.compare(a.latestFetchedBlock.blockNumber, b.latestFetchedBlock.blockNumber)
|
|
272
353
|
|
|
354
|
+
// Contracts a standing address-free partition already fetches client-side.
|
|
355
|
+
// Addresses registered for them after that partition passed get a normal
|
|
356
|
+
// address-bound partition which dies once it catches up (see make), instead
|
|
357
|
+
// of being folded into the address-free one.
|
|
358
|
+
let anchoredContracts = (partitions: array<partition>) => {
|
|
359
|
+
let set = Utils.Set.make()
|
|
360
|
+
partitions->Array.forEach(p =>
|
|
361
|
+
switch (p.mergeBlock, p.selection.clientFilteredContracts) {
|
|
362
|
+
| (None, Some(contractNames)) =>
|
|
363
|
+
contractNames->Array.forEach(name => set->Utils.Set.add(name)->ignore)
|
|
364
|
+
| _ => ()
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
set
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// The furthest block an address-free partition's already-dispatched queries
|
|
371
|
+
// can deliver without the addresses registered from now on. A settled query
|
|
372
|
+
// claims only what it actually fetched — the rest of its range is re-queried
|
|
373
|
+
// later, by then against the updated address store — while an in-flight
|
|
374
|
+
// bounded query claims its whole toBlock. An in-flight unbounded query has no
|
|
375
|
+
// ceiling at all, so nothing is safe to stop a catch-up partition at yet.
|
|
376
|
+
let getAnchorSafeBlock = (p: partition) => {
|
|
377
|
+
let safeRef = ref(Some(p.latestFetchedBlock.blockNumber))
|
|
378
|
+
p.mutPendingQueries->Array.forEach(pq =>
|
|
379
|
+
switch (safeRef.contents, pq) {
|
|
380
|
+
| (None, _) => ()
|
|
381
|
+
| (Some(safe), {fetchedBlock: Some({blockNumber})}) =>
|
|
382
|
+
if blockNumber > safe {
|
|
383
|
+
safeRef := Some(blockNumber)
|
|
384
|
+
}
|
|
385
|
+
| (Some(safe), {toBlock: Some(toBlock)}) =>
|
|
386
|
+
if toBlock > safe {
|
|
387
|
+
safeRef := Some(toBlock)
|
|
388
|
+
}
|
|
389
|
+
| (Some(_), _) => safeRef := None
|
|
390
|
+
}
|
|
391
|
+
)
|
|
392
|
+
safeRef.contents
|
|
393
|
+
}
|
|
394
|
+
|
|
273
395
|
/**
|
|
274
396
|
* Optimizes partitions by finding opportunities to merge partitions that
|
|
275
397
|
* are behind other partitions with same/superset of contract names.
|
|
@@ -288,19 +410,50 @@ module OptimizedPartitions = {
|
|
|
288
410
|
let mergingPartitions = Dict.make()
|
|
289
411
|
let nextPartitionIndexRef = ref(nextPartitionIndex)
|
|
290
412
|
|
|
413
|
+
// Where a catch-up partition for a client-filtered contract may stop, keyed
|
|
414
|
+
// by contract name. A contract absent from the dict while present in
|
|
415
|
+
// anchoredContractsSet has an unbounded query in flight on its address-free
|
|
416
|
+
// partition — its catch-up can't be bounded yet, and a later call will bound it.
|
|
417
|
+
let anchorSafeBlocks = Dict.make()
|
|
418
|
+
let anchoredContractsSet = Utils.Set.make()
|
|
419
|
+
if clientFilteredContracts->Utils.Set.size > 0 {
|
|
420
|
+
partitions->Array.forEach(p =>
|
|
421
|
+
switch (p.mergeBlock, p.selection.clientFilteredContracts) {
|
|
422
|
+
| (None, Some(contractNames)) =>
|
|
423
|
+
let safeBlock = p->getAnchorSafeBlock
|
|
424
|
+
contractNames->Array.forEach(name => {
|
|
425
|
+
anchoredContractsSet->Utils.Set.add(name)->ignore
|
|
426
|
+
switch safeBlock {
|
|
427
|
+
| Some(safeBlock) => anchorSafeBlocks->Dict.set(name, safeBlock)
|
|
428
|
+
| None => ()
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
| _ => ()
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
291
436
|
for idx in 0 to partitions->Array.length - 1 {
|
|
292
437
|
let p = partitions->Array.getUnsafe(idx)
|
|
293
438
|
switch p {
|
|
439
|
+
// For now don't merge partitions with mergeBlock,
|
|
440
|
+
// assuming they are already merged,
|
|
441
|
+
// TODO: Although there might be cases with too far away mergeBlock,
|
|
442
|
+
// which is worth merging.
|
|
443
|
+
// A partition already at or past its merge block is done — normally
|
|
444
|
+
// handleQueryResponse removes it when the response lands, but a rollback
|
|
445
|
+
// can cap the merge block at the rolled-back frontier, leaving no
|
|
446
|
+
// response to ever remove it on. A retired partition still awaiting a
|
|
447
|
+
// response stays until it lands.
|
|
448
|
+
| {mergeBlock: Some(mergeBlock)} =>
|
|
449
|
+
if p.latestFetchedBlock.blockNumber < mergeBlock || p->isFetching {
|
|
450
|
+
newPartitions->Array.push(p)->ignore
|
|
451
|
+
}
|
|
294
452
|
// Since it's not a dynamic contract partition,
|
|
295
453
|
// there's no need for merge logic
|
|
296
454
|
| {dynamicContract: None}
|
|
297
455
|
| // Wildcard doesn't need merging
|
|
298
|
-
{selection: {dependsOnAddresses: false}}
|
|
299
|
-
| // For now don't merge partitions with mergeBlock,
|
|
300
|
-
// assuming they are already merged,
|
|
301
|
-
// TODO: Although there might be cases with too far away mergeBlock,
|
|
302
|
-
// which is worth merging
|
|
303
|
-
{mergeBlock: Some(_)} =>
|
|
456
|
+
{selection: {dependsOnAddresses: false}} =>
|
|
304
457
|
newPartitions->Array.push(p)->ignore
|
|
305
458
|
| {dynamicContract: Some(contractName)} =>
|
|
306
459
|
let pAddressesCount = p.addresses->AddressSet.countFor(contractName)
|
|
@@ -398,14 +551,46 @@ module OptimizedPartitions = {
|
|
|
398
551
|
newPartitions->Array.push(currentPRef.contents)->ignore
|
|
399
552
|
}
|
|
400
553
|
|
|
554
|
+
// A dynamic partition for a contract the address-free partition already
|
|
555
|
+
// fetches is a catch-up for addresses registered after that partition
|
|
556
|
+
// passed them. It merges by disappearing once it reaches the last block
|
|
557
|
+
// that partition's dispatched queries could have missed them on — its
|
|
558
|
+
// addresses are in the chain's store, so there is nothing to hand over.
|
|
559
|
+
let finalPartitions = if anchoredContractsSet->Utils.Set.size === 0 {
|
|
560
|
+
newPartitions
|
|
561
|
+
} else {
|
|
562
|
+
let anchored = []
|
|
563
|
+
newPartitions->Array.forEach(p =>
|
|
564
|
+
switch p {
|
|
565
|
+
| {dynamicContract: Some(contractName), mergeBlock: None}
|
|
566
|
+
if anchoredContractsSet->Utils.Set.has(contractName) =>
|
|
567
|
+
switch anchorSafeBlocks->Utils.Dict.dangerouslyGetNonOption(contractName) {
|
|
568
|
+
| None => anchored->Array.push(p)->ignore
|
|
569
|
+
| Some(anchorSafeBlock) =>
|
|
570
|
+
if p.latestFetchedBlock.blockNumber < anchorSafeBlock {
|
|
571
|
+
anchored->Array.push({...p, mergeBlock: Some(anchorSafeBlock)})->ignore
|
|
572
|
+
} else if p->isFetching {
|
|
573
|
+
// Caught up, but a response is still owed: keep it until that
|
|
574
|
+
// lands rather than dropping the range it owns.
|
|
575
|
+
anchored
|
|
576
|
+
->Array.push({...p, mergeBlock: Some(p.latestFetchedBlock.blockNumber)})
|
|
577
|
+
->ignore
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
| _ => anchored->Array.push(p)->ignore
|
|
581
|
+
}
|
|
582
|
+
)
|
|
583
|
+
anchored
|
|
584
|
+
}
|
|
585
|
+
|
|
401
586
|
// Sort partitions by latestFetchedBlock ascending
|
|
402
|
-
let _ =
|
|
587
|
+
let _ = finalPartitions->Array.sort(ascSortFn)
|
|
403
588
|
|
|
404
|
-
let partitionsCount =
|
|
589
|
+
let partitionsCount = finalPartitions->Array.length
|
|
405
590
|
let idsInAscOrder = Utils.Array.jsArrayCreate(partitionsCount)
|
|
406
591
|
let entities = Dict.make()
|
|
407
592
|
for idx in 0 to partitionsCount - 1 {
|
|
408
|
-
let p =
|
|
593
|
+
let p = finalPartitions->Array.getUnsafe(idx)
|
|
409
594
|
idsInAscOrder->Array.setUnsafe(idx, p.id)
|
|
410
595
|
entities->Dict.set(p.id, p)
|
|
411
596
|
}
|
|
@@ -468,6 +653,10 @@ module OptimizedPartitions = {
|
|
|
468
653
|
}
|
|
469
654
|
}
|
|
470
655
|
|
|
656
|
+
// Every path that stops a partition from fetching keeps it until its last
|
|
657
|
+
// response lands, so a response always has a partition to advance. A rollback
|
|
658
|
+
// does delete partitions mid-run, but it bumps the indexer epoch and
|
|
659
|
+
// ChainFetching drops older responses before they reach here.
|
|
471
660
|
let rec handleQueryResponse = (
|
|
472
661
|
optimizedPartitions: t,
|
|
473
662
|
~query,
|
|
@@ -475,21 +664,13 @@ module OptimizedPartitions = {
|
|
|
475
664
|
~itemsCount,
|
|
476
665
|
~latestFetchedBlock: blockNumberAndTimestamp,
|
|
477
666
|
) =>
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
optimizedPartitions->handleQueryResponseForPartition(
|
|
486
|
-
~p,
|
|
487
|
-
~query,
|
|
488
|
-
~knownHeight,
|
|
489
|
-
~itemsCount,
|
|
490
|
-
~latestFetchedBlock,
|
|
491
|
-
)
|
|
492
|
-
}
|
|
667
|
+
optimizedPartitions->handleQueryResponseForPartition(
|
|
668
|
+
~p=optimizedPartitions->getOrThrow(~partitionId=query.partitionId),
|
|
669
|
+
~query,
|
|
670
|
+
~knownHeight,
|
|
671
|
+
~itemsCount,
|
|
672
|
+
~latestFetchedBlock,
|
|
673
|
+
)
|
|
493
674
|
|
|
494
675
|
and handleQueryResponseForPartition = (
|
|
495
676
|
optimizedPartitions: t,
|
|
@@ -562,11 +743,15 @@ module OptimizedPartitions = {
|
|
|
562
743
|
~initialLatestFetchedBlock=p.latestFetchedBlock,
|
|
563
744
|
)
|
|
564
745
|
|
|
565
|
-
// Check if partition reached its mergeBlock and should be removed
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
746
|
+
// Check if partition reached its mergeBlock and should be removed. A
|
|
747
|
+
// retired partition can hold several queries at once, so it goes only when
|
|
748
|
+
// the last of them has landed.
|
|
749
|
+
let partitionReachedMergeBlock =
|
|
750
|
+
switch p.mergeBlock {
|
|
751
|
+
| Some(mergeBlock) => updatedLatestFetchedBlock.blockNumber >= mergeBlock
|
|
752
|
+
| None => false
|
|
753
|
+
} &&
|
|
754
|
+
!(p->isFetching)
|
|
570
755
|
|
|
571
756
|
if partitionReachedMergeBlock {
|
|
572
757
|
mutEntities->Utils.Dict.deleteInPlace(p.id)
|
|
@@ -648,7 +833,7 @@ type t = {
|
|
|
648
833
|
endBlock: option<int>,
|
|
649
834
|
normalSelection: selection,
|
|
650
835
|
// Not used for logic - only metadata
|
|
651
|
-
chainId:
|
|
836
|
+
chainId: ChainId.t,
|
|
652
837
|
// The block number of the latest block which was added to the queue
|
|
653
838
|
// by the onBlock configs
|
|
654
839
|
// Need a separate pointer for this
|
|
@@ -951,18 +1136,24 @@ let addClientFilteredContract = (
|
|
|
951
1136
|
~chainId,
|
|
952
1137
|
~addressCount,
|
|
953
1138
|
~threshold,
|
|
1139
|
+
// A resumed fetch state re-derives the switch from the persisted addresses,
|
|
1140
|
+
// but it already happened - and was logged - in the run that crossed the
|
|
1141
|
+
// threshold.
|
|
1142
|
+
~shouldLog=true,
|
|
954
1143
|
) => {
|
|
955
1144
|
clientFilteredContracts->Utils.Set.add(contractName)->ignore
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1145
|
+
if shouldLog {
|
|
1146
|
+
Logging.createChild(
|
|
1147
|
+
~params={
|
|
1148
|
+
"chainId": chainId,
|
|
1149
|
+
"contractName": contractName,
|
|
1150
|
+
"addressCount": addressCount,
|
|
1151
|
+
"threshold": threshold,
|
|
1152
|
+
},
|
|
1153
|
+
)->Logging.childTrace(
|
|
1154
|
+
"Switching contract to client-side address filtering: registered address count crossed the server-side threshold.",
|
|
1155
|
+
)
|
|
1156
|
+
}
|
|
966
1157
|
}
|
|
967
1158
|
|
|
968
1159
|
// The block a partition will have fetched to once everything on its pending
|
|
@@ -987,9 +1178,10 @@ let claimedFetchedBlock = (p: partition, ~knownHeight) =>
|
|
|
987
1178
|
// fetching without tearing down established state:
|
|
988
1179
|
// - The standing address-free partition (no mergeBlock) keeps its id, frontier,
|
|
989
1180
|
// 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
|
|
991
|
-
//
|
|
992
|
-
//
|
|
1181
|
+
// must be added does its selection change — under a fresh id, so responses
|
|
1182
|
+
// built from the old selection can't advance the frontier past ranges the new
|
|
1183
|
+
// contract wasn't fetched for. The old generation is retired beside it,
|
|
1184
|
+
// keeping the queue those responses belong to.
|
|
993
1185
|
// - Partitions absorbed below the standing partition's claimed block
|
|
994
1186
|
// (single-contract dynamic partitions and config partitions all of whose
|
|
995
1187
|
// contracts are client-filtered, plus any prior backfill) become one bounded
|
|
@@ -998,6 +1190,10 @@ let claimedFetchedBlock = (p: partition, ~knownHeight) =>
|
|
|
998
1190
|
// it on arrival. The overlap it re-delivers is deduped by mergeIntoBuffer, and
|
|
999
1191
|
// the re-fetch doubles as history for freshly registered addresses: events
|
|
1000
1192
|
// dropped before the address was registered now pass the address gate.
|
|
1193
|
+
// - A dynamic partition for a contract the standing partition already covers is
|
|
1194
|
+
// left alone: it is a catch-up for addresses registered after that partition
|
|
1195
|
+
// passed them, and OptimizedPartitions.make bounds it against the standing
|
|
1196
|
+
// partition's own claims instead.
|
|
1001
1197
|
// - A partition mixing client-filtered and server-side contracts stays,
|
|
1002
1198
|
// stripped of the client-filtered contracts' addresses — the address-free
|
|
1003
1199
|
// partition covers those logs, so fetching them server-side too would only
|
|
@@ -1014,6 +1210,10 @@ let collapseClientFilteredContracts = (
|
|
|
1014
1210
|
~normalSelection: selection,
|
|
1015
1211
|
~nextPartitionIndexRef: ref<int>,
|
|
1016
1212
|
~addressStore: AddressStore.t,
|
|
1213
|
+
// Frontiers of the addresses that were never given a server-side partition
|
|
1214
|
+
// because their contract is already client-filtered. They stand in for the
|
|
1215
|
+
// partitions that would otherwise have been created only to be absorbed here.
|
|
1216
|
+
~clientFilteredFrontiers: array<blockNumberAndTimestamp>=[],
|
|
1017
1217
|
~knownHeight: int,
|
|
1018
1218
|
) => {
|
|
1019
1219
|
if clientFilteredContracts->Utils.Set.size === 0 {
|
|
@@ -1024,6 +1224,22 @@ let collapseClientFilteredContracts = (
|
|
|
1024
1224
|
let backfills = []
|
|
1025
1225
|
let absorbedPartitions = []
|
|
1026
1226
|
let strippedFrontiers = []
|
|
1227
|
+
let anchoredContracts = partitions->OptimizedPartitions.anchoredContracts
|
|
1228
|
+
|
|
1229
|
+
// Retire a partition in place of removing it: same id and selection, same
|
|
1230
|
+
// pending queue, capped at its own frontier so it issues nothing more. It
|
|
1231
|
+
// holds the frontier down and owns its in-flight response until that lands,
|
|
1232
|
+
// then handleQueryResponse drops it.
|
|
1233
|
+
let retire = (p: partition) => {
|
|
1234
|
+
...p,
|
|
1235
|
+
mergeBlock: Some(p.latestFetchedBlock.blockNumber),
|
|
1236
|
+
}
|
|
1237
|
+
let absorb = p => {
|
|
1238
|
+
absorbedPartitions->Array.push(p)->ignore
|
|
1239
|
+
if p->isFetching {
|
|
1240
|
+
kept->Array.push(p->retire)->ignore
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1027
1243
|
|
|
1028
1244
|
partitions->Array.forEach(p =>
|
|
1029
1245
|
switch p {
|
|
@@ -1032,15 +1248,33 @@ let collapseClientFilteredContracts = (
|
|
|
1032
1248
|
standingRef := Some(p)
|
|
1033
1249
|
| {selection: {dependsOnAddresses: false}, mergeBlock: Some(_)} =>
|
|
1034
1250
|
backfills->Array.push(p)->ignore
|
|
1035
|
-
|
|
1251
|
+
|
|
1252
|
+
// The backfill this call builds covers its remaining range from the
|
|
1253
|
+
// same min frontier, so it is superseded — but not before the response
|
|
1254
|
+
// it is waiting on lands.
|
|
1255
|
+
if p->isFetching {
|
|
1256
|
+
kept->Array.push(p->retire)->ignore
|
|
1257
|
+
}
|
|
1258
|
+
| {selection: {dependsOnAddresses: false}} => p->absorb
|
|
1036
1259
|
| _ =>
|
|
1037
1260
|
let contractNames = p.addresses->AddressSet.contractNames
|
|
1038
1261
|
let serverSideNames =
|
|
1039
1262
|
contractNames->Array.filter(c => !(clientFilteredContracts->Utils.Set.has(c)))
|
|
1040
1263
|
if serverSideNames->Array.length === contractNames->Array.length {
|
|
1041
1264
|
kept->Array.push(p)->ignore
|
|
1265
|
+
} else if (
|
|
1266
|
+
switch p.dynamicContract {
|
|
1267
|
+
| Some(contractName) => anchoredContracts->Utils.Set.has(contractName)
|
|
1268
|
+
| None => false
|
|
1269
|
+
}
|
|
1270
|
+
) {
|
|
1271
|
+
// A catch-up partition for addresses registered after the address-free
|
|
1272
|
+
// partition already covered their contract. It is the correctness
|
|
1273
|
+
// barrier for those addresses until it catches up — absorbing it would
|
|
1274
|
+
// hand that job back to a guessed ceiling.
|
|
1275
|
+
kept->Array.push(p)->ignore
|
|
1042
1276
|
} else if serverSideNames->Utils.Array.isEmpty {
|
|
1043
|
-
|
|
1277
|
+
p->absorb
|
|
1044
1278
|
} else {
|
|
1045
1279
|
strippedFrontiers->Array.push(p.latestFetchedBlock)->ignore
|
|
1046
1280
|
kept
|
|
@@ -1060,6 +1294,7 @@ let collapseClientFilteredContracts = (
|
|
|
1060
1294
|
if (
|
|
1061
1295
|
absorbedPartitions->Utils.Array.isEmpty &&
|
|
1062
1296
|
strippedFrontiers->Utils.Array.isEmpty &&
|
|
1297
|
+
clientFilteredFrontiers->Utils.Array.isEmpty &&
|
|
1063
1298
|
!selectionChanged
|
|
1064
1299
|
) {
|
|
1065
1300
|
// Nothing to fold in and no newly-switched contract: leave the standing
|
|
@@ -1075,6 +1310,7 @@ let collapseClientFilteredContracts = (
|
|
|
1075
1310
|
absorbedPartitions->Array.forEach(p => considerFrontier(p.latestFetchedBlock))
|
|
1076
1311
|
backfills->Array.forEach(p => considerFrontier(p.latestFetchedBlock))
|
|
1077
1312
|
strippedFrontiers->Array.forEach(considerFrontier)
|
|
1313
|
+
clientFilteredFrontiers->Array.forEach(considerFrontier)
|
|
1078
1314
|
|
|
1079
1315
|
let regByIndex = Dict.make()
|
|
1080
1316
|
let addRegs = (regs: array<Internal.onEventRegistration>) =>
|
|
@@ -1093,11 +1329,11 @@ let collapseClientFilteredContracts = (
|
|
|
1093
1329
|
clientFilteredContracts->Utils.Set.has(reg.eventConfig.contractName)
|
|
1094
1330
|
),
|
|
1095
1331
|
)
|
|
1096
|
-
let newSelection =
|
|
1097
|
-
dependsOnAddresses
|
|
1098
|
-
onEventRegistrations
|
|
1099
|
-
clientFilteredContracts
|
|
1100
|
-
|
|
1332
|
+
let newSelection = makeSelection(
|
|
1333
|
+
~dependsOnAddresses=false,
|
|
1334
|
+
~onEventRegistrations=regByIndex->Dict.valuesToArray,
|
|
1335
|
+
~clientFilteredContracts=clientFilteredContracts->Utils.Set.toArray,
|
|
1336
|
+
)
|
|
1101
1337
|
|
|
1102
1338
|
switch standingRef.contents {
|
|
1103
1339
|
| None =>
|
|
@@ -1131,6 +1367,15 @@ let collapseClientFilteredContracts = (
|
|
|
1131
1367
|
let standingOut = if selectionChanged {
|
|
1132
1368
|
let id = nextPartitionIndexRef.contents->Int.toString
|
|
1133
1369
|
nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
|
|
1370
|
+
|
|
1371
|
+
// A response built from the old selection must not advance the new
|
|
1372
|
+
// one's frontier over ranges the newly-switched contract wasn't
|
|
1373
|
+
// fetched for, so the continuing partition takes a fresh id and an
|
|
1374
|
+
// empty queue. The old generation is retired beside it rather than
|
|
1375
|
+
// dropped: it keeps the queue those responses belong to.
|
|
1376
|
+
if standing->isFetching {
|
|
1377
|
+
kept->Array.push(standing->retire)->ignore
|
|
1378
|
+
}
|
|
1134
1379
|
{...standing, id, selection: newSelection, mutPendingQueries: []}
|
|
1135
1380
|
} else {
|
|
1136
1381
|
standing
|
|
@@ -1172,6 +1417,65 @@ let collapseClientFilteredContracts = (
|
|
|
1172
1417
|
}
|
|
1173
1418
|
}
|
|
1174
1419
|
|
|
1420
|
+
let warnAddressRegistration = (
|
|
1421
|
+
~chainId: ChainId.t,
|
|
1422
|
+
~contractAddress: Address.t,
|
|
1423
|
+
~params,
|
|
1424
|
+
message,
|
|
1425
|
+
) =>
|
|
1426
|
+
Logging.createChild(
|
|
1427
|
+
~params={
|
|
1428
|
+
"chainId": chainId,
|
|
1429
|
+
"contractAddress": contractAddress->Address.toString,
|
|
1430
|
+
"details": params,
|
|
1431
|
+
},
|
|
1432
|
+
)->Logging.childWarn(message)
|
|
1433
|
+
|
|
1434
|
+
// A rejected registration is simply absent from every partition, so without a
|
|
1435
|
+
// warning the user sees a contract that never indexes and nothing saying why.
|
|
1436
|
+
// Shared by config-time registration in `make` and by dynamic registration.
|
|
1437
|
+
let warnRejectedRegistration = (
|
|
1438
|
+
verdict: AddressStore.verdict,
|
|
1439
|
+
~chainId: ChainId.t,
|
|
1440
|
+
~contractAddress: Address.t,
|
|
1441
|
+
~contractName: string,
|
|
1442
|
+
) =>
|
|
1443
|
+
switch verdict {
|
|
1444
|
+
| Conflict({existingContractName}) =>
|
|
1445
|
+
warnAddressRegistration(
|
|
1446
|
+
~chainId,
|
|
1447
|
+
~contractAddress,
|
|
1448
|
+
~params={
|
|
1449
|
+
"existingContractType": existingContractName,
|
|
1450
|
+
"newContractType": contractName,
|
|
1451
|
+
},
|
|
1452
|
+
`Skipping contract registration: Contract address is already registered for one contract and cannot be registered for another contract.`,
|
|
1453
|
+
)
|
|
1454
|
+
| Duplicate({effectiveStartBlock, existingEffectiveStartBlock}) =>
|
|
1455
|
+
// FIXME: Instead of filtering out duplicates, we should check the block
|
|
1456
|
+
// number first. If a new registration has an earlier block number we
|
|
1457
|
+
// should register it for the missing block range.
|
|
1458
|
+
if existingEffectiveStartBlock > effectiveStartBlock {
|
|
1459
|
+
warnAddressRegistration(
|
|
1460
|
+
~chainId,
|
|
1461
|
+
~contractAddress,
|
|
1462
|
+
~params={
|
|
1463
|
+
"existingBlockNumber": existingEffectiveStartBlock,
|
|
1464
|
+
"newBlockNumber": effectiveStartBlock,
|
|
1465
|
+
},
|
|
1466
|
+
`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.`,
|
|
1467
|
+
)
|
|
1468
|
+
}
|
|
1469
|
+
| Invalid =>
|
|
1470
|
+
warnAddressRegistration(
|
|
1471
|
+
~chainId,
|
|
1472
|
+
~contractAddress,
|
|
1473
|
+
~params={"contractName": contractName},
|
|
1474
|
+
`Skipping contract registration: Not a valid address for this chain's ecosystem.`,
|
|
1475
|
+
)
|
|
1476
|
+
| Added(_) | NoEvents(_) => ()
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1175
1479
|
/**
|
|
1176
1480
|
Creates partitions from indexing addresses with two phases:
|
|
1177
1481
|
Phase 1: Create per-contract-name partitions (smart grouping by startBlock)
|
|
@@ -1199,68 +1503,96 @@ OptimizedPartitions.t => {
|
|
|
1199
1503
|
// ── Phase 1: Create per-contract-name partitions ──
|
|
1200
1504
|
let dynamicPartitions = []
|
|
1201
1505
|
let nonDynamicPartitions = []
|
|
1506
|
+
let clientFilteredFrontiers = []
|
|
1507
|
+
|
|
1508
|
+
// Contracts an address-free partition already fetches. Their new addresses
|
|
1509
|
+
// need a partition of their own: the address-free partition passed the blocks
|
|
1510
|
+
// they were registered at without them in the store.
|
|
1511
|
+
let anchoredContracts = existingPartitions->OptimizedPartitions.anchoredContracts
|
|
1202
1512
|
|
|
1203
1513
|
let contractNames = registeringSetsByContract->Dict.keysToArray
|
|
1204
1514
|
for cIdx in 0 to contractNames->Array.length - 1 {
|
|
1205
1515
|
let contractName = contractNames->Array.getUnsafe(cIdx)
|
|
1206
1516
|
let contractSet = registeringSetsByContract->Dict.getUnsafe(contractName)
|
|
1207
1517
|
|
|
1208
|
-
let
|
|
1518
|
+
let isAnchored = anchoredContracts->Utils.Set.has(contractName)
|
|
1519
|
+
// A catch-up partition must go through the dynamic merge logic — that's
|
|
1520
|
+
// what bounds it against its anchor and removes it once it catches up.
|
|
1521
|
+
let isDynamic = dynamicContracts->Utils.Set.has(contractName) || isAnchored
|
|
1209
1522
|
let partitions = isDynamic ? dynamicPartitions : nonDynamicPartitions
|
|
1210
1523
|
|
|
1211
1524
|
// A set is ordered by effectiveStartBlock, so its start-block groups are
|
|
1212
1525
|
// ascending and each group's addresses are a contiguous slice.
|
|
1213
1526
|
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
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
1527
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
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,
|
|
1528
|
+
if clientFilteredContracts->Utils.Set.has(contractName) && !isAnchored {
|
|
1529
|
+
// The contract is switching to client-side filtering in this very call, so
|
|
1530
|
+
// the collapse below folds everything it has into the address-free
|
|
1531
|
+
// partition anyway. Chunking these addresses by maxAddrInPartition would
|
|
1532
|
+
// only build partitions for it to absorb - hundreds of them for a contract
|
|
1533
|
+
// big enough to be client-filtered in the first place. All the collapse
|
|
1534
|
+
// needs is the earliest block the addresses aren't covered from, which is
|
|
1535
|
+
// the first group's since groups are ascending.
|
|
1536
|
+
switch groups->Array.get(0) {
|
|
1537
|
+
| Some({startBlock}) =>
|
|
1538
|
+
clientFilteredFrontiers->Array.push({
|
|
1539
|
+
blockNumber: Pervasives.max(startBlock - 1, progressBlockNumber),
|
|
1540
|
+
blockTimestamp: 0,
|
|
1256
1541
|
})
|
|
1257
|
-
|
|
1258
|
-
chunkOffsetRef := chunkOffsetRef.contents + take
|
|
1259
|
-
remainingRef := remainingRef.contents - take
|
|
1542
|
+
| None => ()
|
|
1260
1543
|
}
|
|
1544
|
+
} else {
|
|
1545
|
+
let offsetRef = ref(0)
|
|
1546
|
+
let groupIdx = ref(0)
|
|
1547
|
+
while groupIdx.contents < groups->Array.length {
|
|
1548
|
+
let startBlock = (groups->Array.getUnsafe(groupIdx.contents)).startBlock
|
|
1549
|
+
// Addresses with different start blocks within range share a partition;
|
|
1550
|
+
// events before each address's effectiveStartBlock are dropped by the
|
|
1551
|
+
// source's address gate.
|
|
1552
|
+
let countRef = ref(0)
|
|
1553
|
+
let nextIdx = ref(groupIdx.contents)
|
|
1554
|
+
let joining = ref(true)
|
|
1555
|
+
while joining.contents && nextIdx.contents < groups->Array.length {
|
|
1556
|
+
let group = groups->Array.getUnsafe(nextIdx.contents)
|
|
1557
|
+
if group.startBlock - startBlock < OptimizedPartitions.tooFarBlockRange {
|
|
1558
|
+
countRef := countRef.contents + group.count
|
|
1559
|
+
nextIdx := nextIdx.contents + 1
|
|
1560
|
+
} else {
|
|
1561
|
+
joining := false
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1261
1564
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1565
|
+
let latestFetchedBlock = {
|
|
1566
|
+
blockNumber: Pervasives.max(startBlock - 1, progressBlockNumber),
|
|
1567
|
+
blockTimestamp: 0,
|
|
1568
|
+
}
|
|
1569
|
+
let remainingRef = ref(countRef.contents)
|
|
1570
|
+
let chunkOffsetRef = ref(offsetRef.contents)
|
|
1571
|
+
while remainingRef.contents > 0 {
|
|
1572
|
+
let take = Pervasives.min(remainingRef.contents, maxAddrInPartition)
|
|
1573
|
+
let pAddresses =
|
|
1574
|
+
contractSet->AddressSet.slice(~offset=chunkOffsetRef.contents, ~limit=Some(take))
|
|
1575
|
+
partitions->Array.push({
|
|
1576
|
+
id: nextPartitionIndexRef.contents->Int.toString,
|
|
1577
|
+
latestFetchedBlock,
|
|
1578
|
+
selection: normalSelection,
|
|
1579
|
+
dynamicContract: isDynamic ? Some(contractName) : None,
|
|
1580
|
+
addresses: pAddresses,
|
|
1581
|
+
mergeBlock: None,
|
|
1582
|
+
mutPendingQueries: [],
|
|
1583
|
+
sourceRangeCapacity: 0,
|
|
1584
|
+
prevSourceRangeCapacity: 0,
|
|
1585
|
+
eventDensity: None,
|
|
1586
|
+
latestSourceRangeCapacityUpdateBlock: 0,
|
|
1587
|
+
})
|
|
1588
|
+
nextPartitionIndexRef := nextPartitionIndexRef.contents + 1
|
|
1589
|
+
chunkOffsetRef := chunkOffsetRef.contents + take
|
|
1590
|
+
remainingRef := remainingRef.contents - take
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
offsetRef := offsetRef.contents + countRef.contents
|
|
1594
|
+
groupIdx := nextIdx.contents
|
|
1595
|
+
}
|
|
1264
1596
|
}
|
|
1265
1597
|
}
|
|
1266
1598
|
|
|
@@ -1324,6 +1656,7 @@ OptimizedPartitions.t => {
|
|
|
1324
1656
|
~normalSelection,
|
|
1325
1657
|
~nextPartitionIndexRef,
|
|
1326
1658
|
~addressStore,
|
|
1659
|
+
~clientFilteredFrontiers,
|
|
1327
1660
|
~knownHeight,
|
|
1328
1661
|
)
|
|
1329
1662
|
OptimizedPartitions.make(
|
|
@@ -1389,21 +1722,12 @@ let registerDynamicContracts = (
|
|
|
1389
1722
|
// single batch conflict the same way as across batches.
|
|
1390
1723
|
let verdicts = addressStore->AddressStore.registerBatch(registrations)
|
|
1391
1724
|
|
|
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
1725
|
let registeringContractNames = []
|
|
1402
1726
|
let hasNoEventsUpdatesRef = ref(false)
|
|
1403
1727
|
for idx in 0 to verdicts->Array.length - 1 {
|
|
1404
1728
|
let registration = registrations->Array.getUnsafe(idx)
|
|
1405
|
-
let
|
|
1406
|
-
let keep = switch
|
|
1729
|
+
let verdict = verdicts->Array.getUnsafe(idx)
|
|
1730
|
+
let keep = switch verdict {
|
|
1407
1731
|
| Added(_) =>
|
|
1408
1732
|
if !(registeringContractNames->Array.includes(registration.contractName)) {
|
|
1409
1733
|
registeringContractNames->Array.push(registration.contractName)->ignore
|
|
@@ -1414,42 +1738,18 @@ let registerDynamicContracts = (
|
|
|
1414
1738
|
// Persist the address to the db so a future config change that adds
|
|
1415
1739
|
// events for this contract can pick it up on restart, but skip partition
|
|
1416
1740
|
// registration since there's nothing to fetch.
|
|
1417
|
-
|
|
1418
|
-
~
|
|
1741
|
+
warnAddressRegistration(
|
|
1742
|
+
~chainId=fetchState.chainId,
|
|
1743
|
+
~contractAddress=registration.address,
|
|
1419
1744
|
~params={"contractName": registration.contractName},
|
|
1420
1745
|
`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
1746
|
)
|
|
1422
1747
|
true
|
|
1423
|
-
| Conflict(
|
|
1424
|
-
|
|
1425
|
-
~
|
|
1426
|
-
~
|
|
1427
|
-
|
|
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
|
-
)
|
|
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.`,
|
|
1748
|
+
| Conflict(_) | Duplicate(_) | Invalid =>
|
|
1749
|
+
verdict->warnRejectedRegistration(
|
|
1750
|
+
~chainId=fetchState.chainId,
|
|
1751
|
+
~contractAddress=registration.address,
|
|
1752
|
+
~contractName=registration.contractName,
|
|
1453
1753
|
)
|
|
1454
1754
|
false
|
|
1455
1755
|
}
|
|
@@ -1610,7 +1910,7 @@ let registerDynamicContracts = (
|
|
|
1610
1910
|
|
|
1611
1911
|
/*
|
|
1612
1912
|
Updates fetchState with a response for a given query.
|
|
1613
|
-
|
|
1913
|
+
Throws if the partition with given query cannot be found (unexpected)
|
|
1614
1914
|
|
|
1615
1915
|
newItems are ordered earliest to latest (as they are returned from the worker)
|
|
1616
1916
|
*/
|
|
@@ -1884,11 +2184,27 @@ let walkPartitionPending = (
|
|
|
1884
2184
|
pqIdx := pqIdx.contents + 1
|
|
1885
2185
|
}
|
|
1886
2186
|
|
|
2187
|
+
// Nothing in this partition's selection can match below its earliest start
|
|
2188
|
+
// block, so forward work skips straight to it instead of scanning up to it and
|
|
2189
|
+
// discarding whole pages. Only the cursor moves — `latestFetchedBlock` still
|
|
2190
|
+
// advances solely on a response, so no block is ever reported fetched that
|
|
2191
|
+
// wasn't. Bounded by the head and the query end block: past either, the
|
|
2192
|
+
// partition would offer no candidate at all, so it queries as before rather
|
|
2193
|
+
// than going quiet until the chain reaches its start block.
|
|
2194
|
+
let cursor = switch p.selection.startBlock {
|
|
2195
|
+
| Some(startBlock) if startBlock > cursor.contents =>
|
|
2196
|
+
switch Utils.Math.minOptInt(Some(headBlockNumber), queryEndBlock) {
|
|
2197
|
+
| Some(reachable) if startBlock <= reachable => startBlock
|
|
2198
|
+
| _ => cursor.contents
|
|
2199
|
+
}
|
|
2200
|
+
| _ => cursor.contents
|
|
2201
|
+
}
|
|
2202
|
+
|
|
1887
2203
|
canContinue.contents
|
|
1888
2204
|
? Some({
|
|
1889
2205
|
partitionId,
|
|
1890
2206
|
p,
|
|
1891
|
-
cursor
|
|
2207
|
+
cursor,
|
|
1892
2208
|
chunksUsedThisCall: chunksUsedThisCall.contents,
|
|
1893
2209
|
inFlightCount,
|
|
1894
2210
|
queryEndBlock,
|
|
@@ -2335,7 +2651,7 @@ let make = (
|
|
|
2335
2651
|
~addressStore: AddressStore.t,
|
|
2336
2652
|
~addresses: array<Internal.indexingAddress>,
|
|
2337
2653
|
~maxAddrInPartition,
|
|
2338
|
-
~chainId,
|
|
2654
|
+
~chainId: ChainId.t,
|
|
2339
2655
|
~maxOnBlockBufferSize,
|
|
2340
2656
|
~knownHeight,
|
|
2341
2657
|
~progressBlockNumber=startBlock - 1,
|
|
@@ -2343,6 +2659,7 @@ let make = (
|
|
|
2343
2659
|
~blockLag=0,
|
|
2344
2660
|
~firstEventBlock=None,
|
|
2345
2661
|
~clientFilterAddressThreshold=None,
|
|
2662
|
+
~isResumed=false,
|
|
2346
2663
|
): t => {
|
|
2347
2664
|
let latestFetchedBlock = {
|
|
2348
2665
|
blockTimestamp: 0,
|
|
@@ -2368,10 +2685,10 @@ let make = (
|
|
|
2368
2685
|
partitions->Array.push({
|
|
2369
2686
|
id: partitions->Array.length->Int.toString,
|
|
2370
2687
|
latestFetchedBlock,
|
|
2371
|
-
selection:
|
|
2372
|
-
dependsOnAddresses
|
|
2373
|
-
onEventRegistrations
|
|
2374
|
-
|
|
2688
|
+
selection: makeSelection(
|
|
2689
|
+
~dependsOnAddresses=false,
|
|
2690
|
+
~onEventRegistrations=notDependingOnAddresses,
|
|
2691
|
+
),
|
|
2375
2692
|
addresses: addressStore->AddressStore.emptySet,
|
|
2376
2693
|
mergeBlock: None,
|
|
2377
2694
|
dynamicContract: None,
|
|
@@ -2383,10 +2700,10 @@ let make = (
|
|
|
2383
2700
|
})
|
|
2384
2701
|
}
|
|
2385
2702
|
|
|
2386
|
-
let normalSelection =
|
|
2387
|
-
dependsOnAddresses
|
|
2388
|
-
onEventRegistrations
|
|
2389
|
-
|
|
2703
|
+
let normalSelection = makeSelection(
|
|
2704
|
+
~dependsOnAddresses=true,
|
|
2705
|
+
~onEventRegistrations=normalRegistrations,
|
|
2706
|
+
)
|
|
2390
2707
|
|
|
2391
2708
|
// Every address the chain indexes goes into the store — including ones whose
|
|
2392
2709
|
// contract has no address-dependent events, so a later registration of the
|
|
@@ -2399,7 +2716,17 @@ let make = (
|
|
|
2399
2716
|
registrationBlock: contract.registrationBlock,
|
|
2400
2717
|
}),
|
|
2401
2718
|
)
|
|
2402
|
-
|
|
2719
|
+
// Verdicts are in the batch's order, so they line up with `addresses`. A
|
|
2720
|
+
// config address the store rejects is dropped exactly like a dynamic one, and
|
|
2721
|
+
// needs the same warning — restored dynamic addresses come through here too.
|
|
2722
|
+
->Array.forEachWithIndex((verdict, idx) => {
|
|
2723
|
+
let contract = addresses->Array.getUnsafe(idx)
|
|
2724
|
+
verdict->warnRejectedRegistration(
|
|
2725
|
+
~chainId,
|
|
2726
|
+
~contractAddress=contract.address,
|
|
2727
|
+
~contractName=contract.contractName,
|
|
2728
|
+
)
|
|
2729
|
+
})
|
|
2403
2730
|
|
|
2404
2731
|
let dynamicContracts = Utils.Set.make()
|
|
2405
2732
|
let clientFilteredContracts = Utils.Set.make()
|
|
@@ -2438,6 +2765,7 @@ let make = (
|
|
|
2438
2765
|
~chainId,
|
|
2439
2766
|
~addressCount,
|
|
2440
2767
|
~threshold,
|
|
2768
|
+
~shouldLog=!isResumed,
|
|
2441
2769
|
)
|
|
2442
2770
|
}
|
|
2443
2771
|
})
|
|
@@ -2462,7 +2790,7 @@ let make = (
|
|
|
2462
2790
|
onBlockRegistrations->Utils.Array.isEmpty
|
|
2463
2791
|
) {
|
|
2464
2792
|
JsError.throwWithMessage(
|
|
2465
|
-
`Invalid configuration: Nothing to fetch on chain ${chainId->
|
|
2793
|
+
`Invalid configuration: Nothing to fetch on chain ${chainId->ChainId.toString}. ` ++
|
|
2466
2794
|
`addresses=${addresses->Array.length->Int.toString}, ` ++
|
|
2467
2795
|
`onEventRegistrations=${onEventRegistrations->Array.length->Int.toString}, ` ++
|
|
2468
2796
|
`normalRegistrations=${normalRegistrations
|
|
@@ -2586,6 +2914,14 @@ let rollback = (fetchState: t, ~addressStore: AddressStore.t, ~targetBlockNumber
|
|
|
2586
2914
|
latestFetchedBlock: p.latestFetchedBlock.blockNumber > targetBlockNumber
|
|
2587
2915
|
? {blockNumber: targetBlockNumber, blockTimestamp: 0}
|
|
2588
2916
|
: p.latestFetchedBlock,
|
|
2917
|
+
// Everything above the target is refetched by whichever partition this
|
|
2918
|
+
// one was catching up to, so there is nothing left to catch up on past
|
|
2919
|
+
// it. createPartitions drops the partition outright when the capped
|
|
2920
|
+
// block is already reached.
|
|
2921
|
+
mergeBlock: switch p.mergeBlock {
|
|
2922
|
+
| Some(mergeBlock) if mergeBlock > targetBlockNumber => Some(targetBlockNumber)
|
|
2923
|
+
| other => other
|
|
2924
|
+
},
|
|
2589
2925
|
mutPendingQueries: rollbackPendingQueries(p.mutPendingQueries, ~targetBlockNumber),
|
|
2590
2926
|
})
|
|
2591
2927
|
->ignore
|