envio 3.5.0-alpha.3 → 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 +17 -7
- package/src/BatchProcessing.res.mjs +8 -4
- 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 +35 -20
- package/src/ChainState.res.mjs +34 -11
- package/src/ChainState.resi +2 -0
- package/src/Config.res +41 -24
- package/src/Config.res.mjs +17 -14
- package/src/ContractRegisterContext.res +1 -1
- package/src/ContractRegisterContext.res.mjs +1 -1
- package/src/Core.res +1 -0
- package/src/CrossChainState.res +61 -16
- package/src/CrossChainState.res.mjs +37 -13
- package/src/CrossChainState.resi +3 -2
- 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 +261 -89
- package/src/FetchState.res.mjs +187 -63
- package/src/FinalizeBackfill.res +33 -6
- package/src/FinalizeBackfill.res.mjs +19 -1
- 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/IndexerLoop.res +9 -2
- package/src/IndexerLoop.res.mjs +8 -1
- package/src/IndexerState.res +41 -24
- package/src/IndexerState.res.mjs +36 -17
- package/src/IndexerState.resi +9 -7
- package/src/Internal.res +19 -17
- package/src/Internal.res.mjs +18 -9
- package/src/LoadLayer.res +1 -1
- package/src/LoadLayer.res.mjs +2 -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 +15 -7
- package/src/PgStorage.res +284 -258
- package/src/PgStorage.res.mjs +181 -208
- 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 +11 -14
- package/src/TestIndexer.res.mjs +14 -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/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 +50 -26
- package/src/db/InternalTable.res.mjs +32 -24
- package/src/db/Table.res +11 -1
- package/src/db/Table.res.mjs +9 -4
- 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/db/IndexRegistry.res +0 -219
- package/src/db/IndexRegistry.res.mjs +0 -195
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 {
|
|
@@ -362,9 +443,10 @@ module OptimizedPartitions = {
|
|
|
362
443
|
// A partition already at or past its merge block is done — normally
|
|
363
444
|
// handleQueryResponse removes it when the response lands, but a rollback
|
|
364
445
|
// can cap the merge block at the rolled-back frontier, leaving no
|
|
365
|
-
// response to ever remove it on.
|
|
446
|
+
// response to ever remove it on. A retired partition still awaiting a
|
|
447
|
+
// response stays until it lands.
|
|
366
448
|
| {mergeBlock: Some(mergeBlock)} =>
|
|
367
|
-
if p.latestFetchedBlock.blockNumber < mergeBlock {
|
|
449
|
+
if p.latestFetchedBlock.blockNumber < mergeBlock || p->isFetching {
|
|
368
450
|
newPartitions->Array.push(p)->ignore
|
|
369
451
|
}
|
|
370
452
|
// Since it's not a dynamic contract partition,
|
|
@@ -487,6 +569,12 @@ module OptimizedPartitions = {
|
|
|
487
569
|
| Some(anchorSafeBlock) =>
|
|
488
570
|
if p.latestFetchedBlock.blockNumber < anchorSafeBlock {
|
|
489
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
|
|
490
578
|
}
|
|
491
579
|
}
|
|
492
580
|
| _ => anchored->Array.push(p)->ignore
|
|
@@ -565,6 +653,10 @@ module OptimizedPartitions = {
|
|
|
565
653
|
}
|
|
566
654
|
}
|
|
567
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.
|
|
568
660
|
let rec handleQueryResponse = (
|
|
569
661
|
optimizedPartitions: t,
|
|
570
662
|
~query,
|
|
@@ -572,21 +664,13 @@ module OptimizedPartitions = {
|
|
|
572
664
|
~itemsCount,
|
|
573
665
|
~latestFetchedBlock: blockNumberAndTimestamp,
|
|
574
666
|
) =>
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
optimizedPartitions->handleQueryResponseForPartition(
|
|
583
|
-
~p,
|
|
584
|
-
~query,
|
|
585
|
-
~knownHeight,
|
|
586
|
-
~itemsCount,
|
|
587
|
-
~latestFetchedBlock,
|
|
588
|
-
)
|
|
589
|
-
}
|
|
667
|
+
optimizedPartitions->handleQueryResponseForPartition(
|
|
668
|
+
~p=optimizedPartitions->getOrThrow(~partitionId=query.partitionId),
|
|
669
|
+
~query,
|
|
670
|
+
~knownHeight,
|
|
671
|
+
~itemsCount,
|
|
672
|
+
~latestFetchedBlock,
|
|
673
|
+
)
|
|
590
674
|
|
|
591
675
|
and handleQueryResponseForPartition = (
|
|
592
676
|
optimizedPartitions: t,
|
|
@@ -659,11 +743,15 @@ module OptimizedPartitions = {
|
|
|
659
743
|
~initialLatestFetchedBlock=p.latestFetchedBlock,
|
|
660
744
|
)
|
|
661
745
|
|
|
662
|
-
// Check if partition reached its mergeBlock and should be removed
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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)
|
|
667
755
|
|
|
668
756
|
if partitionReachedMergeBlock {
|
|
669
757
|
mutEntities->Utils.Dict.deleteInPlace(p.id)
|
|
@@ -745,7 +833,7 @@ type t = {
|
|
|
745
833
|
endBlock: option<int>,
|
|
746
834
|
normalSelection: selection,
|
|
747
835
|
// Not used for logic - only metadata
|
|
748
|
-
chainId:
|
|
836
|
+
chainId: ChainId.t,
|
|
749
837
|
// The block number of the latest block which was added to the queue
|
|
750
838
|
// by the onBlock configs
|
|
751
839
|
// Need a separate pointer for this
|
|
@@ -1090,9 +1178,10 @@ let claimedFetchedBlock = (p: partition, ~knownHeight) =>
|
|
|
1090
1178
|
// fetching without tearing down established state:
|
|
1091
1179
|
// - The standing address-free partition (no mergeBlock) keeps its id, frontier,
|
|
1092
1180
|
// in-flight queries and learned density. Only when a newly-switched contract
|
|
1093
|
-
// must be added does its selection change — under a fresh id, so
|
|
1094
|
-
//
|
|
1095
|
-
//
|
|
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.
|
|
1096
1185
|
// - Partitions absorbed below the standing partition's claimed block
|
|
1097
1186
|
// (single-contract dynamic partitions and config partitions all of whose
|
|
1098
1187
|
// contracts are client-filtered, plus any prior backfill) become one bounded
|
|
@@ -1137,6 +1226,21 @@ let collapseClientFilteredContracts = (
|
|
|
1137
1226
|
let strippedFrontiers = []
|
|
1138
1227
|
let anchoredContracts = partitions->OptimizedPartitions.anchoredContracts
|
|
1139
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
|
+
}
|
|
1243
|
+
|
|
1140
1244
|
partitions->Array.forEach(p =>
|
|
1141
1245
|
switch p {
|
|
1142
1246
|
| {selection: {dependsOnAddresses: false}, mergeBlock: None}
|
|
@@ -1144,7 +1248,14 @@ let collapseClientFilteredContracts = (
|
|
|
1144
1248
|
standingRef := Some(p)
|
|
1145
1249
|
| {selection: {dependsOnAddresses: false}, mergeBlock: Some(_)} =>
|
|
1146
1250
|
backfills->Array.push(p)->ignore
|
|
1147
|
-
|
|
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
|
|
1148
1259
|
| _ =>
|
|
1149
1260
|
let contractNames = p.addresses->AddressSet.contractNames
|
|
1150
1261
|
let serverSideNames =
|
|
@@ -1163,7 +1274,7 @@ let collapseClientFilteredContracts = (
|
|
|
1163
1274
|
// hand that job back to a guessed ceiling.
|
|
1164
1275
|
kept->Array.push(p)->ignore
|
|
1165
1276
|
} else if serverSideNames->Utils.Array.isEmpty {
|
|
1166
|
-
|
|
1277
|
+
p->absorb
|
|
1167
1278
|
} else {
|
|
1168
1279
|
strippedFrontiers->Array.push(p.latestFetchedBlock)->ignore
|
|
1169
1280
|
kept
|
|
@@ -1218,11 +1329,11 @@ let collapseClientFilteredContracts = (
|
|
|
1218
1329
|
clientFilteredContracts->Utils.Set.has(reg.eventConfig.contractName)
|
|
1219
1330
|
),
|
|
1220
1331
|
)
|
|
1221
|
-
let newSelection =
|
|
1222
|
-
dependsOnAddresses
|
|
1223
|
-
onEventRegistrations
|
|
1224
|
-
clientFilteredContracts
|
|
1225
|
-
|
|
1332
|
+
let newSelection = makeSelection(
|
|
1333
|
+
~dependsOnAddresses=false,
|
|
1334
|
+
~onEventRegistrations=regByIndex->Dict.valuesToArray,
|
|
1335
|
+
~clientFilteredContracts=clientFilteredContracts->Utils.Set.toArray,
|
|
1336
|
+
)
|
|
1226
1337
|
|
|
1227
1338
|
switch standingRef.contents {
|
|
1228
1339
|
| None =>
|
|
@@ -1256,6 +1367,15 @@ let collapseClientFilteredContracts = (
|
|
|
1256
1367
|
let standingOut = if selectionChanged {
|
|
1257
1368
|
let id = nextPartitionIndexRef.contents->Int.toString
|
|
1258
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
|
+
}
|
|
1259
1379
|
{...standing, id, selection: newSelection, mutPendingQueries: []}
|
|
1260
1380
|
} else {
|
|
1261
1381
|
standing
|
|
@@ -1297,6 +1417,65 @@ let collapseClientFilteredContracts = (
|
|
|
1297
1417
|
}
|
|
1298
1418
|
}
|
|
1299
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
|
+
|
|
1300
1479
|
/**
|
|
1301
1480
|
Creates partitions from indexing addresses with two phases:
|
|
1302
1481
|
Phase 1: Create per-contract-name partitions (smart grouping by startBlock)
|
|
@@ -1543,21 +1722,12 @@ let registerDynamicContracts = (
|
|
|
1543
1722
|
// single batch conflict the same way as across batches.
|
|
1544
1723
|
let verdicts = addressStore->AddressStore.registerBatch(registrations)
|
|
1545
1724
|
|
|
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
1725
|
let registeringContractNames = []
|
|
1556
1726
|
let hasNoEventsUpdatesRef = ref(false)
|
|
1557
1727
|
for idx in 0 to verdicts->Array.length - 1 {
|
|
1558
1728
|
let registration = registrations->Array.getUnsafe(idx)
|
|
1559
|
-
let
|
|
1560
|
-
let keep = switch
|
|
1729
|
+
let verdict = verdicts->Array.getUnsafe(idx)
|
|
1730
|
+
let keep = switch verdict {
|
|
1561
1731
|
| Added(_) =>
|
|
1562
1732
|
if !(registeringContractNames->Array.includes(registration.contractName)) {
|
|
1563
1733
|
registeringContractNames->Array.push(registration.contractName)->ignore
|
|
@@ -1568,42 +1738,18 @@ let registerDynamicContracts = (
|
|
|
1568
1738
|
// Persist the address to the db so a future config change that adds
|
|
1569
1739
|
// events for this contract can pick it up on restart, but skip partition
|
|
1570
1740
|
// registration since there's nothing to fetch.
|
|
1571
|
-
|
|
1572
|
-
~
|
|
1741
|
+
warnAddressRegistration(
|
|
1742
|
+
~chainId=fetchState.chainId,
|
|
1743
|
+
~contractAddress=registration.address,
|
|
1573
1744
|
~params={"contractName": registration.contractName},
|
|
1574
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.`,
|
|
1575
1746
|
)
|
|
1576
1747
|
true
|
|
1577
|
-
| Conflict(
|
|
1578
|
-
|
|
1579
|
-
~
|
|
1580
|
-
~
|
|
1581
|
-
|
|
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.`,
|
|
1748
|
+
| Conflict(_) | Duplicate(_) | Invalid =>
|
|
1749
|
+
verdict->warnRejectedRegistration(
|
|
1750
|
+
~chainId=fetchState.chainId,
|
|
1751
|
+
~contractAddress=registration.address,
|
|
1752
|
+
~contractName=registration.contractName,
|
|
1607
1753
|
)
|
|
1608
1754
|
false
|
|
1609
1755
|
}
|
|
@@ -1764,7 +1910,7 @@ let registerDynamicContracts = (
|
|
|
1764
1910
|
|
|
1765
1911
|
/*
|
|
1766
1912
|
Updates fetchState with a response for a given query.
|
|
1767
|
-
|
|
1913
|
+
Throws if the partition with given query cannot be found (unexpected)
|
|
1768
1914
|
|
|
1769
1915
|
newItems are ordered earliest to latest (as they are returned from the worker)
|
|
1770
1916
|
*/
|
|
@@ -2038,11 +2184,27 @@ let walkPartitionPending = (
|
|
|
2038
2184
|
pqIdx := pqIdx.contents + 1
|
|
2039
2185
|
}
|
|
2040
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
|
+
|
|
2041
2203
|
canContinue.contents
|
|
2042
2204
|
? Some({
|
|
2043
2205
|
partitionId,
|
|
2044
2206
|
p,
|
|
2045
|
-
cursor
|
|
2207
|
+
cursor,
|
|
2046
2208
|
chunksUsedThisCall: chunksUsedThisCall.contents,
|
|
2047
2209
|
inFlightCount,
|
|
2048
2210
|
queryEndBlock,
|
|
@@ -2489,7 +2651,7 @@ let make = (
|
|
|
2489
2651
|
~addressStore: AddressStore.t,
|
|
2490
2652
|
~addresses: array<Internal.indexingAddress>,
|
|
2491
2653
|
~maxAddrInPartition,
|
|
2492
|
-
~chainId,
|
|
2654
|
+
~chainId: ChainId.t,
|
|
2493
2655
|
~maxOnBlockBufferSize,
|
|
2494
2656
|
~knownHeight,
|
|
2495
2657
|
~progressBlockNumber=startBlock - 1,
|
|
@@ -2523,10 +2685,10 @@ let make = (
|
|
|
2523
2685
|
partitions->Array.push({
|
|
2524
2686
|
id: partitions->Array.length->Int.toString,
|
|
2525
2687
|
latestFetchedBlock,
|
|
2526
|
-
selection:
|
|
2527
|
-
dependsOnAddresses
|
|
2528
|
-
onEventRegistrations
|
|
2529
|
-
|
|
2688
|
+
selection: makeSelection(
|
|
2689
|
+
~dependsOnAddresses=false,
|
|
2690
|
+
~onEventRegistrations=notDependingOnAddresses,
|
|
2691
|
+
),
|
|
2530
2692
|
addresses: addressStore->AddressStore.emptySet,
|
|
2531
2693
|
mergeBlock: None,
|
|
2532
2694
|
dynamicContract: None,
|
|
@@ -2538,10 +2700,10 @@ let make = (
|
|
|
2538
2700
|
})
|
|
2539
2701
|
}
|
|
2540
2702
|
|
|
2541
|
-
let normalSelection =
|
|
2542
|
-
dependsOnAddresses
|
|
2543
|
-
onEventRegistrations
|
|
2544
|
-
|
|
2703
|
+
let normalSelection = makeSelection(
|
|
2704
|
+
~dependsOnAddresses=true,
|
|
2705
|
+
~onEventRegistrations=normalRegistrations,
|
|
2706
|
+
)
|
|
2545
2707
|
|
|
2546
2708
|
// Every address the chain indexes goes into the store — including ones whose
|
|
2547
2709
|
// contract has no address-dependent events, so a later registration of the
|
|
@@ -2554,7 +2716,17 @@ let make = (
|
|
|
2554
2716
|
registrationBlock: contract.registrationBlock,
|
|
2555
2717
|
}),
|
|
2556
2718
|
)
|
|
2557
|
-
|
|
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
|
+
})
|
|
2558
2730
|
|
|
2559
2731
|
let dynamicContracts = Utils.Set.make()
|
|
2560
2732
|
let clientFilteredContracts = Utils.Set.make()
|
|
@@ -2618,7 +2790,7 @@ let make = (
|
|
|
2618
2790
|
onBlockRegistrations->Utils.Array.isEmpty
|
|
2619
2791
|
) {
|
|
2620
2792
|
JsError.throwWithMessage(
|
|
2621
|
-
`Invalid configuration: Nothing to fetch on chain ${chainId->
|
|
2793
|
+
`Invalid configuration: Nothing to fetch on chain ${chainId->ChainId.toString}. ` ++
|
|
2622
2794
|
`addresses=${addresses->Array.length->Int.toString}, ` ++
|
|
2623
2795
|
`onEventRegistrations=${onEventRegistrations->Array.length->Int.toString}, ` ++
|
|
2624
2796
|
`normalRegistrations=${normalRegistrations
|