envio 3.5.0 → 3.5.1-svm-alpha.1
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/ChainFetching.res +11 -21
- package/src/ChainFetching.res.mjs +12 -20
- package/src/ChainState.res +29 -4
- package/src/ChainState.res.mjs +24 -3
- package/src/ChainState.resi +7 -1
- package/src/Config.res +2 -1
- package/src/FetchState.res +25 -68
- package/src/FetchState.res.mjs +12 -59
- package/src/InMemoryStore.res +47 -35
- package/src/InMemoryStore.res.mjs +32 -31
- package/src/Internal.res +0 -7
- package/src/bindings/Vitest.res +0 -3
- package/src/bindings/Vitest.res.mjs +2 -12
- package/src/sources/AddressStore.res +77 -26
- package/src/sources/AddressStore.res.mjs +21 -10
- package/src/sources/SvmHyperSyncClient.res +8 -7
- package/svm.schema.json +10 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.1-svm-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
6
6
|
"bin": "./bin.mjs",
|
|
@@ -69,10 +69,10 @@
|
|
|
69
69
|
"tsx": "4.21.0"
|
|
70
70
|
},
|
|
71
71
|
"optionalDependencies": {
|
|
72
|
-
"envio-linux-x64": "3.5.
|
|
73
|
-
"envio-linux-x64-musl": "3.5.
|
|
74
|
-
"envio-linux-arm64": "3.5.
|
|
75
|
-
"envio-darwin-x64": "3.5.
|
|
76
|
-
"envio-darwin-arm64": "3.5.
|
|
72
|
+
"envio-linux-x64": "3.5.1-svm-alpha.1",
|
|
73
|
+
"envio-linux-x64-musl": "3.5.1-svm-alpha.1",
|
|
74
|
+
"envio-linux-arm64": "3.5.1-svm-alpha.1",
|
|
75
|
+
"envio-darwin-x64": "3.5.1-svm-alpha.1",
|
|
76
|
+
"envio-darwin-arm64": "3.5.1-svm-alpha.1"
|
|
77
77
|
}
|
|
78
78
|
}
|
package/src/ChainFetching.res
CHANGED
|
@@ -24,25 +24,15 @@ let runContractRegistersOrThrow = async (
|
|
|
24
24
|
~ecosystem=config.ecosystem.name,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
-
let
|
|
27
|
+
let registrations: array<AddressStore.registration> = []
|
|
28
28
|
|
|
29
29
|
let onRegister = (~item: Internal.item, ~contractAddress, ~contractName) => {
|
|
30
30
|
let eventItem = item->Internal.castUnsafeEventItem
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
let dc: Internal.indexingAddress = {
|
|
31
|
+
registrations->Array.push({
|
|
34
32
|
address: contractAddress,
|
|
35
33
|
contractName,
|
|
36
|
-
registrationBlock: blockNumber,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
switch item->Internal.getItemDcs {
|
|
40
|
-
| None => {
|
|
41
|
-
item->Internal.setItemDcs([dc])
|
|
42
|
-
itemsWithDcs->Array.push(item)
|
|
43
|
-
}
|
|
44
|
-
| Some(dcs) => dcs->Array.push(dc)
|
|
45
|
-
}
|
|
34
|
+
registrationBlock: eventItem.blockNumber,
|
|
35
|
+
})
|
|
46
36
|
}
|
|
47
37
|
|
|
48
38
|
let promises = []
|
|
@@ -101,7 +91,7 @@ let runContractRegistersOrThrow = async (
|
|
|
101
91
|
let _ = await Promise.all(promises)
|
|
102
92
|
}
|
|
103
93
|
|
|
104
|
-
|
|
94
|
+
registrations
|
|
105
95
|
}
|
|
106
96
|
|
|
107
97
|
let rec onQueryResponse = async (
|
|
@@ -226,13 +216,13 @@ let rec onQueryResponse = async (
|
|
|
226
216
|
|
|
227
217
|
// Re-check staleness: contract registration is async, so the chain state
|
|
228
218
|
// may have rolled back by the time we apply the fetched items.
|
|
229
|
-
let proceed = (~
|
|
219
|
+
let proceed = (~newRegistrations) =>
|
|
230
220
|
if !(state->IndexerState.isStale(~stateId)) {
|
|
231
221
|
applyQueryResponse(
|
|
232
222
|
state,
|
|
233
223
|
~chainId,
|
|
234
224
|
~newItems,
|
|
235
|
-
~
|
|
225
|
+
~newRegistrations,
|
|
236
226
|
~knownHeight,
|
|
237
227
|
~latestFetchedBlock={
|
|
238
228
|
FetchState.blockNumber: latestFetchedBlockNumber,
|
|
@@ -248,7 +238,7 @@ let rec onQueryResponse = async (
|
|
|
248
238
|
}
|
|
249
239
|
|
|
250
240
|
switch itemsWithContractRegister {
|
|
251
|
-
| [] => proceed(~
|
|
241
|
+
| [] => proceed(~newRegistrations=[])
|
|
252
242
|
| _ =>
|
|
253
243
|
switch await runContractRegistersOrThrow(
|
|
254
244
|
~itemsWithContractRegister,
|
|
@@ -257,7 +247,7 @@ let rec onQueryResponse = async (
|
|
|
257
247
|
~blockStore,
|
|
258
248
|
) {
|
|
259
249
|
| exception exn => IndexerState.errorExit(state, exn->ErrorHandling.make)
|
|
260
|
-
|
|
|
250
|
+
| newRegistrations => proceed(~newRegistrations)
|
|
261
251
|
}
|
|
262
252
|
}
|
|
263
253
|
}
|
|
@@ -267,7 +257,7 @@ and applyQueryResponse = (
|
|
|
267
257
|
state: IndexerState.t,
|
|
268
258
|
~chainId,
|
|
269
259
|
~newItems,
|
|
270
|
-
~
|
|
260
|
+
~newRegistrations,
|
|
271
261
|
~knownHeight,
|
|
272
262
|
~latestFetchedBlock,
|
|
273
263
|
~query,
|
|
@@ -281,7 +271,7 @@ and applyQueryResponse = (
|
|
|
281
271
|
~query,
|
|
282
272
|
~latestFetchedBlock,
|
|
283
273
|
~newItems,
|
|
284
|
-
~
|
|
274
|
+
~newRegistrations,
|
|
285
275
|
~knownHeight,
|
|
286
276
|
~transactionStore,
|
|
287
277
|
~blockStore,
|
|
@@ -18,21 +18,13 @@ import * as ContractRegisterContext from "./ContractRegisterContext.res.mjs";
|
|
|
18
18
|
|
|
19
19
|
async function runContractRegistersOrThrow(itemsWithContractRegister, config, transactionStore, blockStore) {
|
|
20
20
|
await ChainState.materializePageItems(itemsWithContractRegister, transactionStore, blockStore, config.ecosystem.name);
|
|
21
|
-
let
|
|
21
|
+
let registrations = [];
|
|
22
22
|
let onRegister = (item, contractAddress, contractName) => {
|
|
23
|
-
|
|
24
|
-
let dc = {
|
|
23
|
+
registrations.push({
|
|
25
24
|
address: contractAddress,
|
|
26
25
|
contractName: contractName,
|
|
27
|
-
registrationBlock:
|
|
28
|
-
};
|
|
29
|
-
let dcs = item.dcs;
|
|
30
|
-
if (dcs !== undefined) {
|
|
31
|
-
dcs.push(dc);
|
|
32
|
-
} else {
|
|
33
|
-
item.dcs = [dc];
|
|
34
|
-
itemsWithDcs.push(item);
|
|
35
|
-
}
|
|
26
|
+
registrationBlock: item.blockNumber
|
|
27
|
+
});
|
|
36
28
|
};
|
|
37
29
|
let promises = [];
|
|
38
30
|
for (let idx = 0, idx_finish = itemsWithContractRegister.length; idx < idx_finish; ++idx) {
|
|
@@ -68,13 +60,13 @@ async function runContractRegistersOrThrow(itemsWithContractRegister, config, tr
|
|
|
68
60
|
if (Utils.$$Array.notEmpty(promises)) {
|
|
69
61
|
await Promise.all(promises);
|
|
70
62
|
}
|
|
71
|
-
return
|
|
63
|
+
return registrations;
|
|
72
64
|
}
|
|
73
65
|
|
|
74
|
-
function applyQueryResponse(state, chainId, newItems,
|
|
66
|
+
function applyQueryResponse(state, chainId, newItems, newRegistrations, knownHeight, latestFetchedBlock, query, transactionStore, blockStore) {
|
|
75
67
|
let chainState = IndexerState.getChainState(state, chainId);
|
|
76
68
|
let wasFetchingAtHead = ChainState.isFetchingAtHead(chainState);
|
|
77
|
-
ChainState.handleQueryResult(chainState, query, newItems,
|
|
69
|
+
ChainState.handleQueryResult(chainState, query, newItems, newRegistrations, latestFetchedBlock, knownHeight, transactionStore, blockStore);
|
|
78
70
|
if (IndexerState.exitAfterFirstEventBlock(state) && newItems.length !== 0) {
|
|
79
71
|
ChainState.setEndBlockToFirstEvent(chainState, newItems[0].blockNumber);
|
|
80
72
|
}
|
|
@@ -152,9 +144,9 @@ async function onQueryResponse(state, param, stateId, scheduleFetch, schedulePro
|
|
|
152
144
|
itemsWithContractRegister.push(item);
|
|
153
145
|
}
|
|
154
146
|
}
|
|
155
|
-
let proceed =
|
|
147
|
+
let proceed = newRegistrations => {
|
|
156
148
|
if (!IndexerState.isStale(state, stateId)) {
|
|
157
|
-
applyQueryResponse(state, chainId, parsedQueueItems,
|
|
149
|
+
applyQueryResponse(state, chainId, parsedQueueItems, newRegistrations, knownHeight, {
|
|
158
150
|
blockNumber: latestFetchedBlockNumber,
|
|
159
151
|
blockTimestamp: latestFetchedBlockTimestamp
|
|
160
152
|
}, query, transactionStore, blockStore);
|
|
@@ -166,14 +158,14 @@ async function onQueryResponse(state, param, stateId, scheduleFetch, schedulePro
|
|
|
166
158
|
if (itemsWithContractRegister.length === 0) {
|
|
167
159
|
return proceed([]);
|
|
168
160
|
}
|
|
169
|
-
let
|
|
161
|
+
let newRegistrations;
|
|
170
162
|
try {
|
|
171
|
-
|
|
163
|
+
newRegistrations = await runContractRegistersOrThrow(itemsWithContractRegister, IndexerState.config(state), transactionStore, blockStore);
|
|
172
164
|
} catch (raw_exn) {
|
|
173
165
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
174
166
|
return IndexerState.errorExit(state, ErrorHandling.make(exn, undefined, undefined));
|
|
175
167
|
}
|
|
176
|
-
return proceed(
|
|
168
|
+
return proceed(newRegistrations);
|
|
177
169
|
}
|
|
178
170
|
|
|
179
171
|
function finishWaitingForNewBlock(state, chainId, knownHeight, stateId, scheduleFetch, scheduleProcessing) {
|
package/src/ChainState.res
CHANGED
|
@@ -130,6 +130,19 @@ let make = (
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// Every chain's contracts, not just one chain's: `context.chain.X.add` accepts
|
|
134
|
+
// any config contract, whichever chain declared it, so every chain's address
|
|
135
|
+
// store has to hold the whole set.
|
|
136
|
+
let configContractNames = (config: Config.t) => {
|
|
137
|
+
let names = Utils.Set.make()
|
|
138
|
+
config.chainMap
|
|
139
|
+
->ChainMap.values
|
|
140
|
+
->Array.forEach(chain =>
|
|
141
|
+
chain.contracts->Array.forEach(contract => names->Utils.Set.add(contract.name)->ignore)
|
|
142
|
+
)
|
|
143
|
+
names->Utils.Set.toArray
|
|
144
|
+
}
|
|
145
|
+
|
|
133
146
|
let makeInternal = (
|
|
134
147
|
~chainConfig: Config.chain,
|
|
135
148
|
~indexingAddresses: array<Internal.indexingAddress>,
|
|
@@ -174,7 +187,10 @@ let makeInternal = (
|
|
|
174
187
|
let addressStore = AddressStore.make(
|
|
175
188
|
~ecosystem=config.ecosystem.name,
|
|
176
189
|
~shouldChecksum=!lowercaseAddresses,
|
|
177
|
-
~contracts=AddressStore.contractsOf(
|
|
190
|
+
~contracts=AddressStore.contractsOf(
|
|
191
|
+
~onEventRegistrations,
|
|
192
|
+
~configContractNames=config->configContractNames,
|
|
193
|
+
),
|
|
178
194
|
)
|
|
179
195
|
|
|
180
196
|
let fetchState = FetchState.make(
|
|
@@ -430,6 +446,15 @@ let setRollbackTargetBlock = (cs: t, ~blockNumber) => cs.rollbackTargetBlock = S
|
|
|
430
446
|
let knownHeight = (cs: t) => cs.fetchState.knownHeight
|
|
431
447
|
let contractAddresses = (cs: t, ~contractName) =>
|
|
432
448
|
cs.addressStore->AddressStore.contractAddresses(contractName)
|
|
449
|
+
|
|
450
|
+
// Hands over the dynamically registered addresses the database hasn't seen yet,
|
|
451
|
+
// up to the block the caller is about to commit, each paired with the checkpoint
|
|
452
|
+
// that owns its row. Destructive: the caller must persist them or take the
|
|
453
|
+
// indexer down. Throws with nothing consumed when a registration's block has no
|
|
454
|
+
// checkpoint in the batch.
|
|
455
|
+
let drainAddressesForWrite = (cs: t, ~toBlockInclusive, ~checkpointBlockNumbers) =>
|
|
456
|
+
cs.addressStore->AddressStore.drainForWrite(toBlockInclusive, checkpointBlockNumbers)
|
|
457
|
+
let hasAddressesToWrite = (cs: t) => cs.addressStore->AddressStore.pendingCount > 0
|
|
433
458
|
let bufferSize = (cs: t) => cs.fetchState->FetchState.bufferSize
|
|
434
459
|
let bufferReadyCount = (cs: t) => cs.fetchState->FetchState.bufferReadyCount
|
|
435
460
|
let getProgressPercentage = (cs: t) => cs.fetchState->FetchState.getProgressPercentage
|
|
@@ -841,7 +866,7 @@ let handleQueryResult = (
|
|
|
841
866
|
cs: t,
|
|
842
867
|
~query: FetchState.query,
|
|
843
868
|
~newItems,
|
|
844
|
-
~
|
|
869
|
+
~newRegistrations,
|
|
845
870
|
~latestFetchedBlock: FetchState.blockNumberAndTimestamp,
|
|
846
871
|
~knownHeight,
|
|
847
872
|
~transactionStore as txPage: option<TransactionStore.t>,
|
|
@@ -858,7 +883,7 @@ let handleQueryResult = (
|
|
|
858
883
|
| None => ()
|
|
859
884
|
}
|
|
860
885
|
|
|
861
|
-
let fs = switch
|
|
886
|
+
let fs = switch newRegistrations {
|
|
862
887
|
| [] => cs.fetchState
|
|
863
888
|
| _ =>
|
|
864
889
|
cs.fetchState->FetchState.registerDynamicContracts(
|
|
@@ -868,7 +893,7 @@ let handleQueryResult = (
|
|
|
868
893
|
// catch-up range — and an unbounded query can reach past the height that
|
|
869
894
|
// was known when it went out.
|
|
870
895
|
~claimCeiling=Pervasives.max(knownHeight, latestFetchedBlock.blockNumber),
|
|
871
|
-
|
|
896
|
+
newRegistrations,
|
|
872
897
|
)
|
|
873
898
|
}
|
|
874
899
|
|
package/src/ChainState.res.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import * as Batch from "./Batch.res.mjs";
|
|
|
6
6
|
import * as Utils from "./Utils.res.mjs";
|
|
7
7
|
import * as ChainId from "./ChainId.res.mjs";
|
|
8
8
|
import * as Logging from "./Logging.res.mjs";
|
|
9
|
+
import * as ChainMap from "./ChainMap.res.mjs";
|
|
9
10
|
import * as EvmChain from "./sources/EvmChain.res.mjs";
|
|
10
11
|
import * as FieldMask from "./sources/FieldMask.res.mjs";
|
|
11
12
|
import * as BlockStore from "./sources/BlockStore.res.mjs";
|
|
@@ -90,6 +91,16 @@ function make(chainConfig, fetchState, onEventRegistrationsOpt, addressStore, so
|
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
function configContractNames(config) {
|
|
95
|
+
let names = new Set();
|
|
96
|
+
ChainMap.values(config.chainMap).forEach(chain => {
|
|
97
|
+
chain.contracts.forEach(contract => {
|
|
98
|
+
names.add(contract.name);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
return Array.from(names);
|
|
102
|
+
}
|
|
103
|
+
|
|
93
104
|
function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firstEventBlockOpt, progressBlockNumber, config, registrationsByChainId, logger, timestampCaughtUpToHeadOrEndblock, numEventsProcessed, isInReorgThreshold, isRealtime, reorgCheckpoints, maxReorgDepth, knownHeightOpt, isResumedOpt, reducedPollingInterval) {
|
|
94
105
|
let firstEventBlock = firstEventBlockOpt !== undefined ? Primitive_option.valFromOption(firstEventBlockOpt) : undefined;
|
|
95
106
|
let knownHeight = knownHeightOpt !== undefined ? knownHeightOpt : 0;
|
|
@@ -106,7 +117,7 @@ function makeInternal(chainConfig, indexingAddresses, startBlock, endBlock, firs
|
|
|
106
117
|
}
|
|
107
118
|
});
|
|
108
119
|
let lowercaseAddresses = config.lowercaseAddresses;
|
|
109
|
-
let addressStore = AddressStore.make(config.ecosystem.name, !lowercaseAddresses, AddressStore.contractsOf(onEventRegistrations));
|
|
120
|
+
let addressStore = AddressStore.make(config.ecosystem.name, !lowercaseAddresses, AddressStore.contractsOf(onEventRegistrations, configContractNames(config)));
|
|
110
121
|
let match$1 = config.ecosystem.name;
|
|
111
122
|
let tmp;
|
|
112
123
|
switch (match$1) {
|
|
@@ -259,6 +270,14 @@ function contractAddresses(cs, contractName) {
|
|
|
259
270
|
return cs.addressStore.contractAddresses(contractName);
|
|
260
271
|
}
|
|
261
272
|
|
|
273
|
+
function drainAddressesForWrite(cs, toBlockInclusive, checkpointBlockNumbers) {
|
|
274
|
+
return cs.addressStore.drainForWrite(toBlockInclusive, checkpointBlockNumbers);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function hasAddressesToWrite(cs) {
|
|
278
|
+
return cs.addressStore.pendingCount() > 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
262
281
|
function bufferSize(cs) {
|
|
263
282
|
return FetchState.bufferSize(cs.fetchState);
|
|
264
283
|
}
|
|
@@ -565,14 +584,14 @@ async function materializePageItems(items, transactionStore, blockStore, ecosyst
|
|
|
565
584
|
]);
|
|
566
585
|
}
|
|
567
586
|
|
|
568
|
-
function handleQueryResult(cs, query, newItems,
|
|
587
|
+
function handleQueryResult(cs, query, newItems, newRegistrations, latestFetchedBlock, knownHeight, txPage, blockPage) {
|
|
569
588
|
if (txPage !== undefined) {
|
|
570
589
|
cs.transactionStore.merge(Primitive_option.valFromOption(txPage));
|
|
571
590
|
}
|
|
572
591
|
if (blockPage !== undefined) {
|
|
573
592
|
cs.blockStore.merge(Primitive_option.valFromOption(blockPage));
|
|
574
593
|
}
|
|
575
|
-
let fs =
|
|
594
|
+
let fs = newRegistrations.length !== 0 ? FetchState.registerDynamicContracts(cs.fetchState, cs.addressStore, Primitive_int.max(knownHeight, latestFetchedBlock.blockNumber), newRegistrations) : cs.fetchState;
|
|
576
595
|
cs.fetchState = FetchState.updateKnownHeight(FetchState.handleQueryResult(fs, query, latestFetchedBlock, newItems), knownHeight);
|
|
577
596
|
cs.pendingBudget = Primitive_float.max(0, cs.pendingBudget - query.itemsEst);
|
|
578
597
|
}
|
|
@@ -837,6 +856,8 @@ export {
|
|
|
837
856
|
setRollbackTargetBlock,
|
|
838
857
|
knownHeight,
|
|
839
858
|
contractAddresses,
|
|
859
|
+
drainAddressesForWrite,
|
|
860
|
+
hasAddressesToWrite,
|
|
840
861
|
bufferSize,
|
|
841
862
|
bufferReadyCount,
|
|
842
863
|
getProgressPercentage,
|
package/src/ChainState.resi
CHANGED
|
@@ -69,6 +69,12 @@ let setRollbackTargetBlock: (t, ~blockNumber: int) => unit
|
|
|
69
69
|
// Fetch-frontier reads.
|
|
70
70
|
let knownHeight: t => int
|
|
71
71
|
let contractAddresses: (t, ~contractName: string) => array<Address.t>
|
|
72
|
+
let drainAddressesForWrite: (
|
|
73
|
+
t,
|
|
74
|
+
~toBlockInclusive: int,
|
|
75
|
+
~checkpointBlockNumbers: array<int>,
|
|
76
|
+
) => array<AddressStore.drainedAddress>
|
|
77
|
+
let hasAddressesToWrite: t => bool
|
|
72
78
|
let bufferSize: t => int
|
|
73
79
|
let bufferReadyCount: t => int
|
|
74
80
|
let getProgressPercentage: t => float
|
|
@@ -110,7 +116,7 @@ let handleQueryResult: (
|
|
|
110
116
|
t,
|
|
111
117
|
~query: FetchState.query,
|
|
112
118
|
~newItems: array<Internal.item>,
|
|
113
|
-
~
|
|
119
|
+
~newRegistrations: array<AddressStore.registration>,
|
|
114
120
|
~latestFetchedBlock: FetchState.blockNumberAndTimestamp,
|
|
115
121
|
~knownHeight: int,
|
|
116
122
|
~transactionStore: option<TransactionStore.t>,
|
package/src/Config.res
CHANGED
|
@@ -114,7 +114,8 @@ module EnvioAddresses = {
|
|
|
114
114
|
id: string,
|
|
115
115
|
@as("chain_id") chainId: ChainId.t,
|
|
116
116
|
@as("registration_block") registrationBlock: int,
|
|
117
|
-
//
|
|
117
|
+
// Vestigial: always written as -1 and never read back. The column stays so
|
|
118
|
+
// an existing schema doesn't need a migration.
|
|
118
119
|
@as("registration_log_index") registrationLogIndex: int,
|
|
119
120
|
@as("contract_name") contractName: string,
|
|
120
121
|
}
|
package/src/FetchState.res
CHANGED
|
@@ -1473,7 +1473,7 @@ let warnRejectedRegistration = (
|
|
|
1473
1473
|
~params={"contractName": contractName},
|
|
1474
1474
|
`Skipping contract registration: Not a valid address for this chain's ecosystem.`,
|
|
1475
1475
|
)
|
|
1476
|
-
| Added(_)
|
|
1476
|
+
| Added(_) => ()
|
|
1477
1477
|
}
|
|
1478
1478
|
|
|
1479
1479
|
/**
|
|
@@ -1678,9 +1678,9 @@ let registerDynamicContracts = (
|
|
|
1678
1678
|
// height in here. Defaults to what the fetch state knows, which is all
|
|
1679
1679
|
// there is to go on when no response is being applied.
|
|
1680
1680
|
~claimCeiling=fetchState.knownHeight,
|
|
1681
|
-
//
|
|
1682
|
-
//
|
|
1683
|
-
|
|
1681
|
+
// Registrations collected from contractRegister calls, in the order the
|
|
1682
|
+
// handlers made them. May contain duplicates, which the store filters out.
|
|
1683
|
+
registrations: array<AddressStore.registration>,
|
|
1684
1684
|
) => {
|
|
1685
1685
|
if fetchState.normalSelection.onEventRegistrations->Utils.Array.isEmpty {
|
|
1686
1686
|
// Can the normalSelection be empty?
|
|
@@ -1689,91 +1689,42 @@ let registerDynamicContracts = (
|
|
|
1689
1689
|
)
|
|
1690
1690
|
}
|
|
1691
1691
|
|
|
1692
|
-
// Flatten every item's dcs into one batch, remembering where each came from
|
|
1693
|
-
// so the rejected ones can be spliced back out of their item (which is what
|
|
1694
|
-
// keeps them from being persisted twice).
|
|
1695
|
-
let registrations: array<AddressStore.registration> = []
|
|
1696
|
-
let sourceDcs: array<Internal.dcs> = []
|
|
1697
|
-
let sourceIndexes: array<int> = []
|
|
1698
|
-
for itemIdx in 0 to items->Array.length - 1 {
|
|
1699
|
-
switch items->Array.getUnsafe(itemIdx)->Internal.getItemDcs {
|
|
1700
|
-
| None => ()
|
|
1701
|
-
| Some(dcs) =>
|
|
1702
|
-
for dcIdx in 0 to dcs->Array.length - 1 {
|
|
1703
|
-
let dc = dcs->Array.getUnsafe(dcIdx)
|
|
1704
|
-
registrations
|
|
1705
|
-
->Array.push({
|
|
1706
|
-
address: dc.address,
|
|
1707
|
-
contractName: dc.contractName,
|
|
1708
|
-
registrationBlock: dc.registrationBlock,
|
|
1709
|
-
})
|
|
1710
|
-
->ignore
|
|
1711
|
-
sourceDcs->Array.push(dcs)->ignore
|
|
1712
|
-
sourceIndexes->Array.push(dcIdx)->ignore
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
1692
|
// Ids are handed out in registration order, so a cursor taken now selects
|
|
1718
1693
|
// exactly what this batch adds.
|
|
1719
1694
|
let idCursor = addressStore->AddressStore.nextId
|
|
1720
1695
|
// The store resolves each address against both what it already holds and the
|
|
1721
1696
|
// batch's own earlier entries, so two contracts claiming one address inside a
|
|
1722
|
-
// single batch conflict the same way as across batches.
|
|
1697
|
+
// single batch conflict the same way as across batches. It also decides which
|
|
1698
|
+
// additions this chain fetches for, since it's what holds the contract list.
|
|
1723
1699
|
let verdicts = addressStore->AddressStore.registerBatch(registrations)
|
|
1724
1700
|
|
|
1725
1701
|
let registeringContractNames = []
|
|
1726
|
-
let hasNoEventsUpdatesRef = ref(false)
|
|
1727
1702
|
for idx in 0 to verdicts->Array.length - 1 {
|
|
1728
1703
|
let registration = registrations->Array.getUnsafe(idx)
|
|
1729
1704
|
let verdict = verdicts->Array.getUnsafe(idx)
|
|
1730
|
-
|
|
1731
|
-
| Added(
|
|
1705
|
+
switch verdict {
|
|
1706
|
+
| Added({fetchable: true}) =>
|
|
1732
1707
|
if !(registeringContractNames->Array.includes(registration.contractName)) {
|
|
1733
1708
|
registeringContractNames->Array.push(registration.contractName)->ignore
|
|
1734
1709
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
// events for this contract can pick it up on restart, but skip partition
|
|
1740
|
-
// registration since there's nothing to fetch.
|
|
1741
|
-
warnAddressRegistration(
|
|
1742
|
-
~chainId=fetchState.chainId,
|
|
1743
|
-
~contractAddress=registration.address,
|
|
1744
|
-
~params={"contractName": registration.contractName},
|
|
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.`,
|
|
1746
|
-
)
|
|
1747
|
-
true
|
|
1710
|
+
// Nothing on this chain is fetched by address for the contract, so there's
|
|
1711
|
+
// no partition to build. The address is still stored and persisted, so a
|
|
1712
|
+
// config that later adds address-dependent events picks it up on restart.
|
|
1713
|
+
| Added({fetchable: false}) => ()
|
|
1748
1714
|
| Conflict(_) | Duplicate(_) | Invalid =>
|
|
1749
1715
|
verdict->warnRejectedRegistration(
|
|
1750
1716
|
~chainId=fetchState.chainId,
|
|
1751
1717
|
~contractAddress=registration.address,
|
|
1752
1718
|
~contractName=registration.contractName,
|
|
1753
1719
|
)
|
|
1754
|
-
false
|
|
1755
|
-
}
|
|
1756
|
-
if !keep {
|
|
1757
|
-
// Mark for removal; the splice happens below, back to front, so earlier
|
|
1758
|
-
// indexes stay valid.
|
|
1759
|
-
sourceIndexes->Array.setUnsafe(idx, -1 - sourceIndexes->Array.getUnsafe(idx))
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
for idx in verdicts->Array.length - 1 downto 0 {
|
|
1763
|
-
let marked = sourceIndexes->Array.getUnsafe(idx)
|
|
1764
|
-
if marked < 0 {
|
|
1765
|
-
let _ =
|
|
1766
|
-
sourceDcs->Array.getUnsafe(idx)->Array.splice(~start=-1 - marked, ~remove=1, ~insert=[])
|
|
1767
1720
|
}
|
|
1768
1721
|
}
|
|
1769
1722
|
|
|
1770
|
-
switch
|
|
1771
|
-
//
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
| ([], true) => fetchState
|
|
1776
|
-
| (_, _) => {
|
|
1723
|
+
switch registeringContractNames {
|
|
1724
|
+
// Nothing to fetch: everything was rejected, or registered for contracts
|
|
1725
|
+
// this chain has no address-dependent events for.
|
|
1726
|
+
| [] => fetchState
|
|
1727
|
+
| _ => {
|
|
1777
1728
|
let newPartitions = []
|
|
1778
1729
|
let dynamicContractsRef = ref(fetchState.optimizedPartitions.dynamicContracts)
|
|
1779
1730
|
let mutExistingPartitions = fetchState.optimizedPartitions.entities->Dict.valuesToArray
|
|
@@ -1835,7 +1786,11 @@ let registerDynamicContracts = (
|
|
|
1835
1786
|
dynamicContract: Some(contractName),
|
|
1836
1787
|
addresses: splitAddresses,
|
|
1837
1788
|
mergeBlock: None,
|
|
1838
|
-
|
|
1789
|
+
// Must be a fresh array: sharing p's (empty) queue by
|
|
1790
|
+
// reference lets both split halves push and consume in
|
|
1791
|
+
// one array, so one half can consume the other's
|
|
1792
|
+
// fetched range and skip it for its own addresses.
|
|
1793
|
+
mutPendingQueries: [],
|
|
1839
1794
|
sourceRangeCapacity: p.sourceRangeCapacity,
|
|
1840
1795
|
prevSourceRangeCapacity: p.prevSourceRangeCapacity,
|
|
1841
1796
|
eventDensity: p.eventDensity,
|
|
@@ -2709,7 +2664,9 @@ let make = (
|
|
|
2709
2664
|
// contract has no address-dependent events, so a later registration of the
|
|
2710
2665
|
// same address still conflicts and the address is still persisted.
|
|
2711
2666
|
addressStore
|
|
2712
|
-
|
|
2667
|
+
// These come from the config or from a resume, so they're already stored and
|
|
2668
|
+
// must never be drained back into a write.
|
|
2669
|
+
->AddressStore.seedBatch(
|
|
2713
2670
|
addresses->Array.map((contract): AddressStore.registration => {
|
|
2714
2671
|
address: contract.address,
|
|
2715
2672
|
contractName: contract.contractName,
|
package/src/FetchState.res.mjs
CHANGED
|
@@ -1086,7 +1086,6 @@ function warnRejectedRegistration(verdict, chainId, contractAddress, contractNam
|
|
|
1086
1086
|
}
|
|
1087
1087
|
switch (verdict.TAG) {
|
|
1088
1088
|
case "Added" :
|
|
1089
|
-
case "NoEvents" :
|
|
1090
1089
|
return;
|
|
1091
1090
|
case "Duplicate" :
|
|
1092
1091
|
let existingEffectiveStartBlock = verdict.existingEffectiveStartBlock;
|
|
@@ -1225,71 +1224,25 @@ function createPartitions(registeringSetsByContract, addressStore, dynamicContra
|
|
|
1225
1224
|
return make(allPartitions, maxAddrInPartition, nextPartitionIndexRef.contents, dynamicContracts, clientFilteredContracts);
|
|
1226
1225
|
}
|
|
1227
1226
|
|
|
1228
|
-
function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt,
|
|
1227
|
+
function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt, registrations) {
|
|
1229
1228
|
let claimCeiling = claimCeilingOpt !== undefined ? claimCeilingOpt : fetchState.knownHeight;
|
|
1230
1229
|
if (Utils.$$Array.isEmpty(fetchState.normalSelection.onEventRegistrations)) {
|
|
1231
1230
|
Stdlib_JsError.throwWithMessage("Invalid configuration. No events to fetch for the dynamic contract registration.");
|
|
1232
1231
|
}
|
|
1233
|
-
let registrations = [];
|
|
1234
|
-
let sourceDcs = [];
|
|
1235
|
-
let sourceIndexes = [];
|
|
1236
|
-
for (let itemIdx = 0, itemIdx_finish = items.length; itemIdx < itemIdx_finish; ++itemIdx) {
|
|
1237
|
-
let dcs = items[itemIdx].dcs;
|
|
1238
|
-
if (dcs !== undefined) {
|
|
1239
|
-
for (let dcIdx = 0, dcIdx_finish = dcs.length; dcIdx < dcIdx_finish; ++dcIdx) {
|
|
1240
|
-
let dc = dcs[dcIdx];
|
|
1241
|
-
registrations.push({
|
|
1242
|
-
address: dc.address,
|
|
1243
|
-
contractName: dc.contractName,
|
|
1244
|
-
registrationBlock: dc.registrationBlock
|
|
1245
|
-
});
|
|
1246
|
-
sourceDcs.push(dcs);
|
|
1247
|
-
sourceIndexes.push(dcIdx);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
1232
|
let idCursor = addressStore.nextId();
|
|
1252
1233
|
let verdicts = AddressStore.registerBatch(addressStore, registrations);
|
|
1253
1234
|
let registeringContractNames = [];
|
|
1254
|
-
let hasNoEventsUpdatesRef = false;
|
|
1255
1235
|
for (let idx = 0, idx_finish = verdicts.length; idx < idx_finish; ++idx) {
|
|
1256
1236
|
let registration = registrations[idx];
|
|
1257
1237
|
let verdict = verdicts[idx];
|
|
1258
|
-
let keep;
|
|
1259
1238
|
let exit = 0;
|
|
1260
|
-
if (typeof verdict !== "object") {
|
|
1239
|
+
if (typeof verdict !== "object" || verdict.TAG !== "Added") {
|
|
1261
1240
|
exit = 1;
|
|
1262
|
-
} else {
|
|
1263
|
-
|
|
1264
|
-
case "Added" :
|
|
1265
|
-
if (!registeringContractNames.includes(registration.contractName)) {
|
|
1266
|
-
registeringContractNames.push(registration.contractName);
|
|
1267
|
-
}
|
|
1268
|
-
keep = true;
|
|
1269
|
-
break;
|
|
1270
|
-
case "NoEvents" :
|
|
1271
|
-
hasNoEventsUpdatesRef = true;
|
|
1272
|
-
warnAddressRegistration(fetchState.chainId, registration.address, {
|
|
1273
|
-
contractName: registration.contractName
|
|
1274
|
-
}, `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.`);
|
|
1275
|
-
keep = true;
|
|
1276
|
-
break;
|
|
1277
|
-
default:
|
|
1278
|
-
exit = 1;
|
|
1279
|
-
}
|
|
1241
|
+
} else if (verdict.fetchable && !registeringContractNames.includes(registration.contractName)) {
|
|
1242
|
+
registeringContractNames.push(registration.contractName);
|
|
1280
1243
|
}
|
|
1281
1244
|
if (exit === 1) {
|
|
1282
1245
|
warnRejectedRegistration(verdict, fetchState.chainId, registration.address, registration.contractName);
|
|
1283
|
-
keep = false;
|
|
1284
|
-
}
|
|
1285
|
-
if (!keep) {
|
|
1286
|
-
sourceIndexes[idx] = -1 - sourceIndexes[idx] | 0;
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
for (let idx$1 = verdicts.length - 1 | 0; idx$1 >= 0; --idx$1) {
|
|
1290
|
-
let marked = sourceIndexes[idx$1];
|
|
1291
|
-
if (marked < 0) {
|
|
1292
|
-
sourceDcs[idx$1].splice(-1 - marked | 0, 1);
|
|
1293
1246
|
}
|
|
1294
1247
|
}
|
|
1295
1248
|
if (registeringContractNames.length === 0) {
|
|
@@ -1298,12 +1251,12 @@ function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt, ite
|
|
|
1298
1251
|
let newPartitions = [];
|
|
1299
1252
|
let dynamicContractsRef = fetchState.optimizedPartitions.dynamicContracts;
|
|
1300
1253
|
let mutExistingPartitions = Object.values(fetchState.optimizedPartitions.entities);
|
|
1301
|
-
for (let idx$
|
|
1302
|
-
let contractName = registeringContractNames[idx$
|
|
1254
|
+
for (let idx$1 = 0, idx_finish$1 = registeringContractNames.length; idx$1 < idx_finish$1; ++idx$1) {
|
|
1255
|
+
let contractName = registeringContractNames[idx$1];
|
|
1303
1256
|
if (!dynamicContractsRef.has(contractName)) {
|
|
1304
1257
|
dynamicContractsRef = Utils.$$Set.immutableAdd(dynamicContractsRef, contractName);
|
|
1305
|
-
for (let idx$
|
|
1306
|
-
let p = mutExistingPartitions[idx$
|
|
1258
|
+
for (let idx$2 = 0, idx_finish$2 = mutExistingPartitions.length; idx$2 < idx_finish$2; ++idx$2) {
|
|
1259
|
+
let p = mutExistingPartitions[idx$2];
|
|
1307
1260
|
let match = p.addresses.countFor(contractName);
|
|
1308
1261
|
if (match !== 0 && p.selection.dependsOnAddresses && p.mergeBlock === undefined) {
|
|
1309
1262
|
let allPartitionContractNames = p.addresses.contractNames();
|
|
@@ -1312,7 +1265,7 @@ function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt, ite
|
|
|
1312
1265
|
if (!isFetching) {
|
|
1313
1266
|
let newPartitionId = (fetchState.optimizedPartitions.nextPartitionIndex + newPartitions.length | 0).toString();
|
|
1314
1267
|
let restNames = allPartitionContractNames.filter(name => name !== contractName);
|
|
1315
|
-
mutExistingPartitions[idx$
|
|
1268
|
+
mutExistingPartitions[idx$2] = withAddresses(p, p.addresses.filterByContracts(restNames));
|
|
1316
1269
|
let splitAddresses = p.addresses.filterByContracts([contractName]);
|
|
1317
1270
|
newPartitions.push({
|
|
1318
1271
|
id: newPartitionId,
|
|
@@ -1321,7 +1274,7 @@ function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt, ite
|
|
|
1321
1274
|
addresses: splitAddresses,
|
|
1322
1275
|
mergeBlock: undefined,
|
|
1323
1276
|
dynamicContract: contractName,
|
|
1324
|
-
mutPendingQueries:
|
|
1277
|
+
mutPendingQueries: [],
|
|
1325
1278
|
sourceRangeCapacity: p.sourceRangeCapacity,
|
|
1326
1279
|
prevSourceRangeCapacity: p.prevSourceRangeCapacity,
|
|
1327
1280
|
eventDensity: p.eventDensity,
|
|
@@ -1329,7 +1282,7 @@ function registerDynamicContracts(fetchState, addressStore, claimCeilingOpt, ite
|
|
|
1329
1282
|
});
|
|
1330
1283
|
}
|
|
1331
1284
|
} else {
|
|
1332
|
-
mutExistingPartitions[idx$
|
|
1285
|
+
mutExistingPartitions[idx$2] = {
|
|
1333
1286
|
id: p.id,
|
|
1334
1287
|
latestFetchedBlock: p.latestFetchedBlock,
|
|
1335
1288
|
selection: p.selection,
|
|
@@ -1786,7 +1739,7 @@ function make$1(startBlock, endBlock, onEventRegistrations, addressStore, addres
|
|
|
1786
1739
|
});
|
|
1787
1740
|
}
|
|
1788
1741
|
let normalSelection = makeSelection(normalRegistrations, true, undefined);
|
|
1789
|
-
AddressStore.
|
|
1742
|
+
AddressStore.seedBatch(addressStore, addresses.map(contract => ({
|
|
1790
1743
|
address: contract.address,
|
|
1791
1744
|
contractName: contract.contractName,
|
|
1792
1745
|
registrationBlock: contract.registrationBlock
|
package/src/InMemoryStore.res
CHANGED
|
@@ -157,46 +157,58 @@ let prepareRollbackDiff = async (
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
// Stages the addresses registered by this batch's events for the write. They're
|
|
161
|
+
// drained from each chain's address store rather than carried on the items that
|
|
162
|
+
// registered them: the store is where they already live, and it knows which
|
|
163
|
+
// ones the database hasn't seen yet.
|
|
160
164
|
let setBatchDcs = (state: IndexerState.t, ~batch: Batch.t) => {
|
|
161
165
|
let inMemTable = state->getInMemTable(~entityConfig=InternalTable.EnvioAddresses.entityConfig)
|
|
162
166
|
let committedCheckpointId = state->IndexerState.committedCheckpointId
|
|
163
167
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
let entity: InternalTable.EnvioAddresses.t = {
|
|
181
|
-
id: InternalTable.EnvioAddresses.makeId(~chainId, ~address=dc.address),
|
|
182
|
-
chainId,
|
|
183
|
-
contractName: dc.contractName,
|
|
184
|
-
registrationBlock: eventItem.blockNumber,
|
|
185
|
-
registrationLogIndex: eventItem.logIndex,
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
inMemTable->InMemoryTable.Entity.set(
|
|
189
|
-
~committedCheckpointId,
|
|
190
|
-
Set({
|
|
191
|
-
entityId: entity.id->EntityId.unsafeOfString,
|
|
192
|
-
checkpointId,
|
|
193
|
-
entity: entity->InternalTable.EnvioAddresses.castToInternal,
|
|
194
|
-
}),
|
|
195
|
-
)
|
|
168
|
+
batch.progressedChainsById->Utils.Dict.forEach(progressedChain => {
|
|
169
|
+
let chainId = progressedChain.fetchState.chainId
|
|
170
|
+
let chainState = state->IndexerState.getChainState(~chainId)
|
|
171
|
+
|
|
172
|
+
// Most batches register nothing, so the checkpoints are only collected for a
|
|
173
|
+
// chain that has something waiting.
|
|
174
|
+
if chainState->ChainState.hasAddressesToWrite {
|
|
175
|
+
// The store pairs each drained registration with a checkpoint of its own
|
|
176
|
+
// chain, so only this chain's checkpoints go in — and the index it returns
|
|
177
|
+
// points back into these two parallel arrays.
|
|
178
|
+
let checkpointIds = []
|
|
179
|
+
let checkpointBlockNumbers = []
|
|
180
|
+
for idx in 0 to batch.checkpointIds->Array.length - 1 {
|
|
181
|
+
if batch.checkpointChainIds->Array.getUnsafe(idx) === chainId {
|
|
182
|
+
checkpointIds->Array.push(batch.checkpointIds->Array.getUnsafe(idx))
|
|
183
|
+
checkpointBlockNumbers->Array.push(batch.checkpointBlockNumbers->Array.getUnsafe(idx))
|
|
196
184
|
}
|
|
197
185
|
}
|
|
198
|
-
}
|
|
199
186
|
|
|
200
|
-
|
|
201
|
-
|
|
187
|
+
chainState
|
|
188
|
+
->ChainState.drainAddressesForWrite(
|
|
189
|
+
~toBlockInclusive=progressedChain.progressBlockNumber,
|
|
190
|
+
~checkpointBlockNumbers,
|
|
191
|
+
)
|
|
192
|
+
->Array.forEach(dc => {
|
|
193
|
+
let entity: InternalTable.EnvioAddresses.t = {
|
|
194
|
+
id: InternalTable.EnvioAddresses.makeId(~chainId, ~address=dc.address),
|
|
195
|
+
chainId,
|
|
196
|
+
contractName: dc.contractName,
|
|
197
|
+
registrationBlock: dc.registrationBlock,
|
|
198
|
+
// Only ever written, never read back. Kept on the table so the column
|
|
199
|
+
// doesn't need a migration.
|
|
200
|
+
registrationLogIndex: -1,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
inMemTable->InMemoryTable.Entity.set(
|
|
204
|
+
~committedCheckpointId,
|
|
205
|
+
Set({
|
|
206
|
+
entityId: entity.id->EntityId.unsafeOfString,
|
|
207
|
+
checkpointId: checkpointIds->Array.getUnsafe(dc.checkpointIdx),
|
|
208
|
+
entity: entity->InternalTable.EnvioAddresses.castToInternal,
|
|
209
|
+
}),
|
|
210
|
+
)
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
})
|
|
202
214
|
}
|
|
@@ -4,6 +4,7 @@ import * as Table from "./db/Table.res.mjs";
|
|
|
4
4
|
import * as Utils from "./Utils.res.mjs";
|
|
5
5
|
import * as Config from "./Config.res.mjs";
|
|
6
6
|
import * as Internal from "./Internal.res.mjs";
|
|
7
|
+
import * as ChainState from "./ChainState.res.mjs";
|
|
7
8
|
import * as EffectState from "./EffectState.res.mjs";
|
|
8
9
|
import * as IndexerState from "./IndexerState.res.mjs";
|
|
9
10
|
import * as InMemoryTable from "./InMemoryTable.res.mjs";
|
|
@@ -117,39 +118,39 @@ async function prepareRollbackDiff(state, rollbackTargetCheckpointId, rollbackDi
|
|
|
117
118
|
function setBatchDcs(state, batch) {
|
|
118
119
|
let inMemTable = getInMemTable(state, Config.EnvioAddresses.entityConfig);
|
|
119
120
|
let committedCheckpointId = IndexerState.committedCheckpointId(state);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
let entity_registration_block = item.blockNumber;
|
|
133
|
-
let entity_registration_log_index = item.logIndex;
|
|
134
|
-
let entity_contract_name = dc.contractName;
|
|
135
|
-
let entity = {
|
|
136
|
-
id: entity_id,
|
|
137
|
-
chain_id: chainId,
|
|
138
|
-
registration_block: entity_registration_block,
|
|
139
|
-
registration_log_index: entity_registration_log_index,
|
|
140
|
-
contract_name: entity_contract_name
|
|
141
|
-
};
|
|
142
|
-
InMemoryTable.Entity.set(inMemTable, committedCheckpointId, {
|
|
143
|
-
type: "SET",
|
|
144
|
-
entityId: entity_id,
|
|
145
|
-
entity: entity,
|
|
146
|
-
checkpointId: checkpointId
|
|
147
|
-
});
|
|
148
|
-
}
|
|
121
|
+
Utils.Dict.forEach(batch.progressedChainsById, progressedChain => {
|
|
122
|
+
let chainId = progressedChain.fetchState.chainId;
|
|
123
|
+
let chainState = IndexerState.getChainState(state, chainId);
|
|
124
|
+
if (!ChainState.hasAddressesToWrite(chainState)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
let checkpointIds = [];
|
|
128
|
+
let checkpointBlockNumbers = [];
|
|
129
|
+
for (let idx = 0, idx_finish = batch.checkpointIds.length; idx < idx_finish; ++idx) {
|
|
130
|
+
if (batch.checkpointChainIds[idx] === chainId) {
|
|
131
|
+
checkpointIds.push(batch.checkpointIds[idx]);
|
|
132
|
+
checkpointBlockNumbers.push(batch.checkpointBlockNumbers[idx]);
|
|
149
133
|
}
|
|
150
134
|
}
|
|
151
|
-
|
|
152
|
-
|
|
135
|
+
ChainState.drainAddressesForWrite(chainState, progressedChain.progressBlockNumber, checkpointBlockNumbers).forEach(dc => {
|
|
136
|
+
let entity_id = Config.EnvioAddresses.makeId(chainId, dc.address);
|
|
137
|
+
let entity_registration_block = dc.registrationBlock;
|
|
138
|
+
let entity_contract_name = dc.contractName;
|
|
139
|
+
let entity = {
|
|
140
|
+
id: entity_id,
|
|
141
|
+
chain_id: chainId,
|
|
142
|
+
registration_block: entity_registration_block,
|
|
143
|
+
registration_log_index: -1,
|
|
144
|
+
contract_name: entity_contract_name
|
|
145
|
+
};
|
|
146
|
+
InMemoryTable.Entity.set(inMemTable, committedCheckpointId, {
|
|
147
|
+
type: "SET",
|
|
148
|
+
entityId: entity_id,
|
|
149
|
+
entity: entity,
|
|
150
|
+
checkpointId: checkpointIds[dc.checkpointIdx]
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
export {
|
package/src/Internal.res
CHANGED
|
@@ -616,8 +616,6 @@ type indexingAddress = {
|
|
|
616
616
|
registrationBlock: int,
|
|
617
617
|
}
|
|
618
618
|
|
|
619
|
-
type dcs = array<indexingAddress>
|
|
620
|
-
|
|
621
619
|
// Duplicate the type from item to keep item properly unboxed. Runtime event
|
|
622
620
|
// items carry the registration their source already resolved from the
|
|
623
621
|
// ChainState-owned registration array.
|
|
@@ -698,11 +696,6 @@ let getItemChainId = item =>
|
|
|
698
696
|
| Block({onBlockRegistration: {chainId}}) => chainId
|
|
699
697
|
}
|
|
700
698
|
|
|
701
|
-
@get
|
|
702
|
-
external getItemDcs: item => option<dcs> = "dcs"
|
|
703
|
-
@set
|
|
704
|
-
external setItemDcs: (item, dcs) => unit = "dcs"
|
|
705
|
-
|
|
706
699
|
type eventOptions<'where> = {
|
|
707
700
|
wildcard?: bool,
|
|
708
701
|
where?: 'where,
|
package/src/bindings/Vitest.res
CHANGED
|
@@ -119,9 +119,6 @@ module Async = {
|
|
|
119
119
|
@module("vitest") @scope("it")
|
|
120
120
|
external it_fails: (string, testContext => promise<unit>) => unit = "fails"
|
|
121
121
|
|
|
122
|
-
let isClaudeCloud: bool = %raw(`process.env.CLAUDE_CODE_CONTAINER_ID != null`)
|
|
123
|
-
let itSkipInClaudeCloud = (name, fn) => it_skipIf(isClaudeCloud)(name, fn)
|
|
124
|
-
|
|
125
122
|
@module("vitest")
|
|
126
123
|
external test: (string, testContext => promise<unit>) => unit = "test"
|
|
127
124
|
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as Vitest from "vitest";
|
|
4
3
|
|
|
5
|
-
let
|
|
6
|
-
|
|
7
|
-
function itSkipInClaudeCloud(name, fn) {
|
|
8
|
-
Vitest.it.skipIf(isClaudeCloud)(name, fn);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
let Async = {
|
|
12
|
-
isClaudeCloud: isClaudeCloud,
|
|
13
|
-
itSkipInClaudeCloud: itSkipInClaudeCloud
|
|
14
|
-
};
|
|
4
|
+
let Async = {};
|
|
15
5
|
|
|
16
6
|
let messageOfThrown = (function (fn) {
|
|
17
7
|
try {
|
|
@@ -31,4 +21,4 @@ export {
|
|
|
31
21
|
messageOfThrown,
|
|
32
22
|
toThrowErrorEqual,
|
|
33
23
|
}
|
|
34
|
-
/*
|
|
24
|
+
/* No side effect */
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
// (or address-valued param) isn't registered at the item's block.
|
|
8
8
|
type t
|
|
9
9
|
|
|
10
|
-
// A contract
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
10
|
+
// A contract an address may be registered for. Every config contract of every
|
|
11
|
+
// chain, since `context.chain.<Contract>.add` validates against that whole set
|
|
12
|
+
// — a name outside it makes the store throw. `dependsOnAddresses` is whether
|
|
13
|
+
// this chain fetches for the contract by address, which is what makes the store
|
|
14
|
+
// able to answer `fetchable` on a verdict.
|
|
15
|
+
type contract = {name: string, startBlock: option<int>, dependsOnAddresses: bool}
|
|
14
16
|
|
|
15
17
|
type registration = {
|
|
16
18
|
address: Address.t,
|
|
@@ -20,9 +22,10 @@ type registration = {
|
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
type verdict =
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
|
|
25
|
+
// `fetchable` is false when nothing on the chain is fetched by address for the
|
|
26
|
+
// contract: the address is still stored and persisted, so a config that later
|
|
27
|
+
// adds events picks it up on restart, but no partition is built from it.
|
|
28
|
+
| Added({effectiveStartBlock: int, fetchable: bool})
|
|
26
29
|
// Already registered for the same contract.
|
|
27
30
|
| Duplicate({effectiveStartBlock: int, existingEffectiveStartBlock: int})
|
|
28
31
|
// Already registered for a different contract.
|
|
@@ -32,11 +35,22 @@ type verdict =
|
|
|
32
35
|
|
|
33
36
|
type rawVerdict = {
|
|
34
37
|
kind: string,
|
|
38
|
+
fetchable: bool,
|
|
35
39
|
effectiveStartBlock: int,
|
|
36
40
|
existingContractName: Null.t<string>,
|
|
37
41
|
existingEffectiveStartBlock: Null.t<int>,
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
// A registration the store handed over for persistence, paired with the
|
|
45
|
+
// checkpoint that owns its row. `checkpointIdx` indexes the block numbers
|
|
46
|
+
// passed to `drainForWrite` — the ids stay on the JS side.
|
|
47
|
+
type drainedAddress = {
|
|
48
|
+
address: Address.t,
|
|
49
|
+
contractName: string,
|
|
50
|
+
registrationBlock: int,
|
|
51
|
+
checkpointIdx: int,
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
type makeSetOptions = {
|
|
41
55
|
// Only addresses whose id is at or above this. Ids are handed out in
|
|
42
56
|
// registration order, so a cursor read before a batch (`nextId`) selects
|
|
@@ -66,22 +80,37 @@ let make = (~ecosystem: Ecosystem.name, ~shouldChecksum: bool, ~contracts: array
|
|
|
66
80
|
}
|
|
67
81
|
}
|
|
68
82
|
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// `Some`: one registration without a start block makes the whole
|
|
72
|
-
// address gate open from the chain's start, and the per-registration
|
|
73
|
-
// block still holds back the registrations that declared one.
|
|
74
|
-
|
|
83
|
+
// Every contract an address may be registered for, with the earliest block any
|
|
84
|
+
// of this chain's events for it may fire at. `None` means unrestricted, so it
|
|
85
|
+
// wins over any `Some`: one registration without a start block makes the whole
|
|
86
|
+
// contract's address gate open from the chain's start, and the per-registration
|
|
87
|
+
// start block still holds back the registrations that declared one.
|
|
88
|
+
//
|
|
89
|
+
// A contract whose events are all wildcard is fetched without consulting
|
|
90
|
+
// addresses, so registering one changes nothing about what's queried — it
|
|
91
|
+
// carries `dependsOnAddresses: false` just like a contract named only by
|
|
92
|
+
// `configContractNames`. Either way the addresses are stored and persisted,
|
|
93
|
+
// never fetched.
|
|
94
|
+
let contractsOf = (
|
|
95
|
+
~onEventRegistrations: array<Internal.onEventRegistration>,
|
|
96
|
+
~configContractNames: array<string>,
|
|
97
|
+
): array<contract> => {
|
|
75
98
|
// Only ever holds a declared start block, so a missing key is unambiguous —
|
|
76
99
|
// storing `None` in a dict would be indistinguishable from "not seen yet".
|
|
77
100
|
let startBlocks: dict<int> = Dict.make()
|
|
78
101
|
let unrestricted = Utils.Set.make()
|
|
102
|
+
let addressDependent = Utils.Set.make()
|
|
79
103
|
let names = []
|
|
80
|
-
|
|
81
|
-
let name = reg.eventConfig.contractName
|
|
104
|
+
let addName = name =>
|
|
82
105
|
if !(names->Array.includes(name)) {
|
|
83
106
|
names->Array.push(name)->ignore
|
|
84
107
|
}
|
|
108
|
+
onEventRegistrations->Array.forEach(reg => {
|
|
109
|
+
let name = reg.eventConfig.contractName
|
|
110
|
+
addName(name)
|
|
111
|
+
if reg.dependsOnAddresses {
|
|
112
|
+
addressDependent->Utils.Set.add(name)->ignore
|
|
113
|
+
}
|
|
85
114
|
switch reg.startBlock {
|
|
86
115
|
| None => unrestricted->Utils.Set.add(name)->ignore
|
|
87
116
|
| Some(startBlock) =>
|
|
@@ -94,11 +123,13 @@ let contractsOf = (~onEventRegistrations: array<Internal.onEventRegistration>):
|
|
|
94
123
|
)
|
|
95
124
|
}
|
|
96
125
|
})
|
|
126
|
+
configContractNames->Array.forEach(addName)
|
|
97
127
|
names->Array.map(name => {
|
|
98
128
|
name,
|
|
99
129
|
startBlock: unrestricted->Utils.Set.has(name)
|
|
100
130
|
? None
|
|
101
131
|
: startBlocks->Utils.Dict.dangerouslyGetNonOption(name),
|
|
132
|
+
dependsOnAddresses: addressDependent->Utils.Set.has(name),
|
|
102
133
|
})
|
|
103
134
|
}
|
|
104
135
|
|
|
@@ -112,10 +143,24 @@ let contractsOf = (~onEventRegistrations: array<Internal.onEventRegistration>):
|
|
|
112
143
|
// come from that chain's one store — ids are store-scoped, so sets from
|
|
113
144
|
// different stores can't be merged.
|
|
114
145
|
@send external makeSetOf: (t, array<Address.t>) => AddressSet.t = "makeSetOf"
|
|
115
|
-
@send external registerBatchRaw: (t, array<registration>) => array<rawVerdict> = "registerBatch"
|
|
116
|
-
@send external makeSetRaw: (t, string, makeSetOptions) => AddressSet.t = "makeSet"
|
|
117
146
|
@send
|
|
118
|
-
external
|
|
147
|
+
external registerBatchRaw: (t, array<registration>) => array<rawVerdict> = "registerBatch"
|
|
148
|
+
@send external seedBatchRaw: (t, array<registration>) => array<rawVerdict> = "seedBatch"
|
|
149
|
+
|
|
150
|
+
// Drains the registrations awaiting persistence at or below the given block —
|
|
151
|
+
// what the batch being written covers — pairing each with the checkpoint at its
|
|
152
|
+
// registration block. Later registrations stay pending. Throws, with the queue
|
|
153
|
+
// untouched, when a drained registration's block has no checkpoint in the batch.
|
|
154
|
+
@send
|
|
155
|
+
external drainForWrite: (t, int, array<int>) => array<drainedAddress> = "drainForWrite"
|
|
156
|
+
|
|
157
|
+
// How many registrations await persistence — lets a caller skip the work of
|
|
158
|
+
// assembling what `drainForWrite` needs.
|
|
159
|
+
@send external pendingCount: t => int = "pendingCount"
|
|
160
|
+
|
|
161
|
+
// The registrations still awaiting persistence. For assertions.
|
|
162
|
+
@send external pendingEntries: t => array<Internal.indexingContract> = "pendingEntries"
|
|
163
|
+
@send external makeSetRaw: (t, string, makeSetOptions) => AddressSet.t = "makeSet"
|
|
119
164
|
@send external contractCount: (t, string) => int = "contractCount"
|
|
120
165
|
@send external size: t => int = "size"
|
|
121
166
|
|
|
@@ -132,12 +177,9 @@ external startBlockGroups: (t, string) => array<AddressSet.startBlockGroup> = "s
|
|
|
132
177
|
|
|
133
178
|
@send external contractAddresses: (t, string) => array<Address.t> = "contractAddresses"
|
|
134
179
|
|
|
135
|
-
@send external entries: t => array<Internal.indexingContract> = "entries"
|
|
136
|
-
|
|
137
180
|
let toVerdict = (raw: rawVerdict): verdict =>
|
|
138
181
|
switch raw.kind {
|
|
139
|
-
| "added" => Added({effectiveStartBlock: raw.effectiveStartBlock})
|
|
140
|
-
| "noEvents" => NoEvents({effectiveStartBlock: raw.effectiveStartBlock})
|
|
182
|
+
| "added" => Added({effectiveStartBlock: raw.effectiveStartBlock, fetchable: raw.fetchable})
|
|
141
183
|
| "duplicate" =>
|
|
142
184
|
Duplicate({
|
|
143
185
|
effectiveStartBlock: raw.effectiveStartBlock,
|
|
@@ -148,13 +190,22 @@ let toVerdict = (raw: rawVerdict): verdict =>
|
|
|
148
190
|
| kind => JsError.throwWithMessage(`Unexpected address registration verdict "${kind}"`)
|
|
149
191
|
}
|
|
150
192
|
|
|
151
|
-
// Registers
|
|
152
|
-
// batch's own earlier entries — so two contracts claiming one
|
|
153
|
-
// single batch conflict just as they would across batches.
|
|
154
|
-
// in the batch's order.
|
|
193
|
+
// Registers dynamic registrations, resolving each address against both the
|
|
194
|
+
// store and the batch's own earlier entries — so two contracts claiming one
|
|
195
|
+
// address inside a single batch conflict just as they would across batches.
|
|
196
|
+
// Verdicts come back in the batch's order. What it adds is pending persistence
|
|
197
|
+
// until a batch write drains it.
|
|
198
|
+
//
|
|
199
|
+
// An unknown contract name throws with none of the batch applied.
|
|
155
200
|
let registerBatch = (store: t, registrations: array<registration>): array<verdict> =>
|
|
156
201
|
store->registerBatchRaw(registrations)->Array.map(toVerdict)
|
|
157
202
|
|
|
203
|
+
// `registerBatch` for addresses the database already holds — config addresses
|
|
204
|
+
// and the dynamic ones a resume restores. Nothing is marked pending, so nothing
|
|
205
|
+
// is ever written back.
|
|
206
|
+
let seedBatch = (store: t, registrations: array<registration>): array<verdict> =>
|
|
207
|
+
store->seedBatchRaw(registrations)->Array.map(toVerdict)
|
|
208
|
+
|
|
158
209
|
let makeSet = (store: t, ~contractName, ~options={}: makeSetOptions) =>
|
|
159
210
|
store->makeSetRaw(contractName, options)
|
|
160
211
|
|
|
@@ -17,14 +17,22 @@ function make(ecosystem, shouldChecksum, contracts) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function contractsOf(onEventRegistrations) {
|
|
20
|
+
function contractsOf(onEventRegistrations, configContractNames) {
|
|
21
21
|
let startBlocks = {};
|
|
22
22
|
let unrestricted = new Set();
|
|
23
|
+
let addressDependent = new Set();
|
|
23
24
|
let names = [];
|
|
24
|
-
|
|
25
|
-
let name = reg.eventConfig.contractName;
|
|
25
|
+
let addName = name => {
|
|
26
26
|
if (!names.includes(name)) {
|
|
27
27
|
names.push(name);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
onEventRegistrations.forEach(reg => {
|
|
32
|
+
let name = reg.eventConfig.contractName;
|
|
33
|
+
addName(name);
|
|
34
|
+
if (reg.dependsOnAddresses) {
|
|
35
|
+
addressDependent.add(name);
|
|
28
36
|
}
|
|
29
37
|
let startBlock = reg.startBlock;
|
|
30
38
|
if (startBlock !== undefined) {
|
|
@@ -34,9 +42,11 @@ function contractsOf(onEventRegistrations) {
|
|
|
34
42
|
}
|
|
35
43
|
unrestricted.add(name);
|
|
36
44
|
});
|
|
45
|
+
configContractNames.forEach(addName);
|
|
37
46
|
return names.map(name => ({
|
|
38
47
|
name: name,
|
|
39
|
-
startBlock: unrestricted.has(name) ? undefined : startBlocks[name]
|
|
48
|
+
startBlock: unrestricted.has(name) ? undefined : startBlocks[name],
|
|
49
|
+
dependsOnAddresses: addressDependent.has(name)
|
|
40
50
|
}));
|
|
41
51
|
}
|
|
42
52
|
|
|
@@ -46,7 +56,8 @@ function toVerdict(raw) {
|
|
|
46
56
|
case "added" :
|
|
47
57
|
return {
|
|
48
58
|
TAG: "Added",
|
|
49
|
-
effectiveStartBlock: raw.effectiveStartBlock
|
|
59
|
+
effectiveStartBlock: raw.effectiveStartBlock,
|
|
60
|
+
fetchable: raw.fetchable
|
|
50
61
|
};
|
|
51
62
|
case "conflict" :
|
|
52
63
|
return {
|
|
@@ -61,11 +72,6 @@ function toVerdict(raw) {
|
|
|
61
72
|
};
|
|
62
73
|
case "invalid" :
|
|
63
74
|
return "Invalid";
|
|
64
|
-
case "noEvents" :
|
|
65
|
-
return {
|
|
66
|
-
TAG: "NoEvents",
|
|
67
|
-
effectiveStartBlock: raw.effectiveStartBlock
|
|
68
|
-
};
|
|
69
75
|
default:
|
|
70
76
|
return Stdlib_JsError.throwWithMessage(`Unexpected address registration verdict "` + kind + `"`);
|
|
71
77
|
}
|
|
@@ -75,6 +81,10 @@ function registerBatch(store, registrations) {
|
|
|
75
81
|
return store.registerBatch(registrations).map(toVerdict);
|
|
76
82
|
}
|
|
77
83
|
|
|
84
|
+
function seedBatch(store, registrations) {
|
|
85
|
+
return store.seedBatch(registrations).map(toVerdict);
|
|
86
|
+
}
|
|
87
|
+
|
|
78
88
|
function makeSet(store, contractName, optionsOpt) {
|
|
79
89
|
let options = optionsOpt !== undefined ? optionsOpt : ({});
|
|
80
90
|
return store.makeSet(contractName, options);
|
|
@@ -89,6 +99,7 @@ export {
|
|
|
89
99
|
contractsOf,
|
|
90
100
|
toVerdict,
|
|
91
101
|
registerBatch,
|
|
102
|
+
seedBatch,
|
|
92
103
|
makeSet,
|
|
93
104
|
get,
|
|
94
105
|
}
|
|
@@ -110,13 +110,13 @@ module QueryTypes = {
|
|
|
110
110
|
|
|
111
111
|
type fieldSelection = {block?: array<blockField>, transaction?: array<transactionField>}
|
|
112
112
|
|
|
113
|
-
/** Filter for selecting
|
|
114
|
-
instruction must match at least one value in every non-empty field.
|
|
113
|
+
/** Filter for selecting instruction calls. All non-empty fields are AND-ed:
|
|
114
|
+
an instruction must match at least one value in every non-empty field.
|
|
115
115
|
|
|
116
116
|
Discriminator filters (d1..d8) take hex-encoded byte prefixes ("0x" optional).
|
|
117
117
|
Account filters (a0..a9) take base58 pubkey strings. */
|
|
118
118
|
type instructionSelection = {
|
|
119
|
-
|
|
119
|
+
executingAccount?: array<string>,
|
|
120
120
|
d1?: array<string>,
|
|
121
121
|
d2?: array<string>,
|
|
122
122
|
d4?: array<string>,
|
|
@@ -129,7 +129,7 @@ module QueryTypes = {
|
|
|
129
129
|
type query = {
|
|
130
130
|
fromSlot: int,
|
|
131
131
|
toSlot?: int,
|
|
132
|
-
|
|
132
|
+
instructionCalls?: array<instructionSelection>,
|
|
133
133
|
includeAllBlocks?: bool,
|
|
134
134
|
fields?: fieldSelection,
|
|
135
135
|
maxNumBlocks?: int,
|
|
@@ -159,15 +159,16 @@ module ResponseTypes = {
|
|
|
159
159
|
slot: int,
|
|
160
160
|
transactionIndex: int,
|
|
161
161
|
instructionAddress: array<int>,
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
executingAccount: string,
|
|
163
|
+
accountArguments: array<string>,
|
|
164
164
|
data: string,
|
|
165
165
|
d1?: string,
|
|
166
166
|
d2?: string,
|
|
167
167
|
d4?: string,
|
|
168
168
|
d8?: string,
|
|
169
169
|
isInner: bool,
|
|
170
|
-
isCommitted
|
|
170
|
+
// True when the parent transaction succeeded (renamed from isCommitted).
|
|
171
|
+
txSuccess: bool,
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
type queryResponseData = {
|
package/svm.schema.json
CHANGED
|
@@ -264,8 +264,15 @@
|
|
|
264
264
|
"type": "object",
|
|
265
265
|
"properties": {
|
|
266
266
|
"hypersync_config": {
|
|
267
|
-
"description": "HyperSync Config for fetching historical instructions on this chain.",
|
|
268
|
-
"
|
|
267
|
+
"description": "HyperSync Config for fetching historical instructions on this chain. Defaults to the public Solana mainnet HyperSync endpoint.",
|
|
268
|
+
"anyOf": [
|
|
269
|
+
{
|
|
270
|
+
"$ref": "#/$defs/HypersyncConfig"
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"type": "null"
|
|
274
|
+
}
|
|
275
|
+
]
|
|
269
276
|
},
|
|
270
277
|
"programs": {
|
|
271
278
|
"description": "Solana programs to index on this chain.",
|
|
@@ -277,7 +284,6 @@
|
|
|
277
284
|
},
|
|
278
285
|
"additionalProperties": false,
|
|
279
286
|
"required": [
|
|
280
|
-
"hypersync_config",
|
|
281
287
|
"programs"
|
|
282
288
|
]
|
|
283
289
|
},
|
|
@@ -285,7 +291,7 @@
|
|
|
285
291
|
"type": "object",
|
|
286
292
|
"properties": {
|
|
287
293
|
"url": {
|
|
288
|
-
"description": "URL of the HyperSync endpoint
|
|
294
|
+
"description": "URL of the HyperSync endpoint. Defaults to the public Solana mainnet HyperSync endpoint when the hypersync_config block is omitted.",
|
|
289
295
|
"type": "string"
|
|
290
296
|
}
|
|
291
297
|
},
|