envio 3.7.0-svm-alpha.1 → 3.8.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/index.d.ts +331 -142
- package/package.json +6 -6
- package/src/ChainState.res +9 -1
- package/src/ChainState.res.mjs +6 -1
- package/src/Config.res +19 -10
- package/src/Config.res.mjs +17 -7
- package/src/Core.res +4 -0
- package/src/Envio.res +54 -76
- package/src/EventConfigBuilder.res +159 -30
- package/src/EventConfigBuilder.res.mjs +120 -24
- package/src/HandlerRegister.res +20 -11
- package/src/HandlerRegister.res.mjs +22 -5
- package/src/Hasura.res +30 -22
- package/src/Hasura.res.mjs +11 -5
- package/src/InMemoryStore.res +1 -6
- package/src/InMemoryStore.res.mjs +1 -3
- package/src/Internal.res +33 -13
- package/src/Internal.res.mjs +12 -16
- package/src/Main.res +2 -5
- package/src/MemoryStorage.res +40 -8
- package/src/MemoryStorage.res.mjs +38 -16
- package/src/Persistence.res +4 -2
- package/src/PgStorage.res +6 -1
- package/src/PgStorage.res.mjs +1 -1
- package/src/SimulateItems.res +261 -9
- package/src/SimulateItems.res.mjs +218 -47
- package/src/TestIndexer.res +26 -10
- package/src/TestIndexer.res.mjs +34 -12
- package/src/bindings/ClickHouse.res +49 -17
- package/src/bindings/ClickHouse.res.mjs +35 -14
- package/src/db/InternalTable.res +11 -0
- package/src/db/InternalTable.res.mjs +7 -0
- package/src/sources/EvmHyperSyncSource.res +8 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
- package/src/sources/SimulateSource.res +8 -4
- package/src/sources/SimulateSource.res.mjs +7 -3
- package/src/sources/Svm.res +34 -7
- package/src/sources/Svm.res.mjs +32 -4
- package/src/sources/SvmHyperSyncClient.res +10 -12
- package/src/sources/SvmHyperSyncClient.res.mjs +3 -2
- package/src/sources/SvmHyperSyncSource.res +96 -35
- package/src/sources/SvmHyperSyncSource.res.mjs +78 -38
- package/src/sources/TransactionStore.res +38 -0
- package/src/sources/TransactionStore.res.mjs +5 -0
- package/svm.schema.json +27 -78
|
@@ -5,11 +5,14 @@ import * as Config from "./Config.res.mjs";
|
|
|
5
5
|
import * as Address from "./Address.res.mjs";
|
|
6
6
|
import * as ChainId from "./ChainId.res.mjs";
|
|
7
7
|
import * as ChainMap from "./ChainMap.res.mjs";
|
|
8
|
+
import * as BlockStore from "./sources/BlockStore.res.mjs";
|
|
8
9
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
9
10
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
10
11
|
import * as HandlerRegister from "./HandlerRegister.res.mjs";
|
|
11
12
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
12
13
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
14
|
+
import * as TransactionStore from "./sources/TransactionStore.res.mjs";
|
|
15
|
+
import * as SvmHyperSyncSource from "./sources/SvmHyperSyncSource.res.mjs";
|
|
13
16
|
|
|
14
17
|
let evmSimulateBlockSchema = S$RescriptSchema.schema(s => ({
|
|
15
18
|
number: s.m(S$RescriptSchema.Option.getOr(S$RescriptSchema.$$null(S$RescriptSchema.int), 0)),
|
|
@@ -168,6 +171,16 @@ function deriveSrcAddress(providedSrcAddress, eventConfig, chainConfig, config)
|
|
|
168
171
|
}
|
|
169
172
|
}
|
|
170
173
|
|
|
174
|
+
function liveRegistrationsFor(config, chainId, eventConfig) {
|
|
175
|
+
return HandlerRegister.getSimulateOnEventRegistrations(config, chainId, eventConfig).filter(reg => {
|
|
176
|
+
if (Stdlib_Option.isSome(reg.handler) || Stdlib_Option.isSome(reg.contractRegister)) {
|
|
177
|
+
return !HandlerRegister.isDroppedByWhere(config, reg);
|
|
178
|
+
} else {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
171
184
|
function parse(simulateItems, config, chainConfig, onEventRegistrations) {
|
|
172
185
|
let chainId = chainConfig.id;
|
|
173
186
|
let startBlock = chainConfig.startBlock;
|
|
@@ -178,21 +191,161 @@ function parse(simulateItems, config, chainConfig, onEventRegistrations) {
|
|
|
178
191
|
contents: 0
|
|
179
192
|
};
|
|
180
193
|
let items = [];
|
|
194
|
+
let svmTxs = [];
|
|
195
|
+
let svmActivities = [];
|
|
196
|
+
let svmBlocks = [];
|
|
181
197
|
let seenCoordinates = {};
|
|
182
198
|
simulateItems.forEach((rawJson, itemIndex) => {
|
|
183
|
-
let match =
|
|
184
|
-
let match$1 = rawJson.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
199
|
+
let match = config.ecosystem.name;
|
|
200
|
+
let match$1 = rawJson.program;
|
|
201
|
+
let match$2 = rawJson.instruction;
|
|
202
|
+
switch (match) {
|
|
203
|
+
case "evm" :
|
|
204
|
+
case "fuel" :
|
|
205
|
+
break;
|
|
206
|
+
case "svm" :
|
|
207
|
+
if (match$1 === undefined) {
|
|
208
|
+
return Stdlib_JsError.throwWithMessage(`simulate: Invalid item. Each item must have "program" and "instruction" fields.`);
|
|
209
|
+
}
|
|
210
|
+
if (match$2 === undefined) {
|
|
211
|
+
return Stdlib_JsError.throwWithMessage(`simulate: Invalid item. Each item must have "program" and "instruction" fields.`);
|
|
212
|
+
}
|
|
213
|
+
let ec = findEventConfig(config, match$1, match$2);
|
|
214
|
+
let eventConfig = ec !== undefined ? ec : Stdlib_JsError.throwWithMessage(`simulate: Instruction "` + match$2 + `" not found on program "` + match$1 + `". Check that the program and instruction names match your config.yaml.`);
|
|
215
|
+
let blockJson = rawJson.block;
|
|
216
|
+
let s = rawJson.slot;
|
|
217
|
+
let slot;
|
|
218
|
+
if (s !== undefined) {
|
|
219
|
+
slot = s;
|
|
220
|
+
} else if (blockJson == null) {
|
|
221
|
+
slot = currentBlock.contents;
|
|
222
|
+
} else {
|
|
223
|
+
let v = blockJson["slot"];
|
|
224
|
+
slot = v !== undefined ? Stdlib_Option.getOr((v == null) ? undefined : Primitive_option.some(v), currentBlock.contents) : currentBlock.contents;
|
|
225
|
+
}
|
|
226
|
+
currentBlock.contents = slot;
|
|
227
|
+
let tx = rawJson.transaction;
|
|
228
|
+
let transaction = tx !== undefined ? Primitive_option.valFromOption(tx) : ({});
|
|
229
|
+
let transactionIndex = Stdlib_Option.getOr(transaction.transactionIndex, 0);
|
|
230
|
+
let path = Stdlib_Option.getOr(rawJson.path, [0]);
|
|
231
|
+
let programId = Stdlib_Option.getOr(rawJson.programId, eventConfig.programId);
|
|
232
|
+
let args = rawJson.accountArguments;
|
|
233
|
+
let accountArguments;
|
|
234
|
+
if (args !== undefined) {
|
|
235
|
+
accountArguments = args;
|
|
236
|
+
} else {
|
|
237
|
+
let named = rawJson.accounts;
|
|
238
|
+
accountArguments = named !== undefined ? eventConfig.accounts.map(name => {
|
|
239
|
+
let match = named[name];
|
|
240
|
+
if (match !== undefined) {
|
|
241
|
+
return match.address;
|
|
242
|
+
} else {
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
245
|
+
}) : [];
|
|
246
|
+
}
|
|
247
|
+
let data = Stdlib_Option.getOr(rawJson.data, Stdlib_Option.getOr(eventConfig.discriminator, "0x"));
|
|
248
|
+
let decoded = Stdlib_Option.map(rawJson.args, args => ({
|
|
249
|
+
name: match$2,
|
|
250
|
+
argsJson: JSON.stringify(args),
|
|
251
|
+
accountsJson: "{}",
|
|
252
|
+
extraAccounts: []
|
|
253
|
+
}));
|
|
254
|
+
let logs = Stdlib_Option.map(rawJson.logs, logs => logs.map(log => ({
|
|
255
|
+
kind: log.kind,
|
|
256
|
+
message: log.message
|
|
257
|
+
})));
|
|
258
|
+
let liveRegistrations = liveRegistrationsFor(config, chainId, eventConfig);
|
|
259
|
+
if (Utils.$$Array.isEmpty(liveRegistrations)) {
|
|
260
|
+
Stdlib_JsError.throwWithMessage(`simulate: no handler runs for instruction "` + match$2 + `" on program "` + match$1 + `". Register a handler with indexer.onInstruction before simulating it.`);
|
|
261
|
+
}
|
|
262
|
+
svmTxs.push({
|
|
263
|
+
slot: slot,
|
|
264
|
+
transactionIndex: transactionIndex,
|
|
265
|
+
signature: transaction.signature,
|
|
266
|
+
allSignatures: transaction.allSignatures,
|
|
267
|
+
feePayer: transaction.feePayer,
|
|
268
|
+
success: transaction.success,
|
|
269
|
+
err: transaction.err,
|
|
270
|
+
fee: transaction.fee,
|
|
271
|
+
computeUnitsConsumed: transaction.computeUnitsConsumed,
|
|
272
|
+
accountKeys: transaction.accountKeys,
|
|
273
|
+
recentBlockhash: transaction.recentBlockhash,
|
|
274
|
+
version: transaction.version
|
|
275
|
+
});
|
|
276
|
+
let rows = transaction.accountActivities;
|
|
277
|
+
if (rows !== undefined) {
|
|
278
|
+
rows.forEach(activity => {
|
|
279
|
+
svmActivities.push({
|
|
280
|
+
slot: slot,
|
|
281
|
+
transactionIndex: transactionIndex,
|
|
282
|
+
account: activity.address,
|
|
283
|
+
accountIndex: activity.transactionAccountIndex,
|
|
284
|
+
isSigner: activity.isSigner,
|
|
285
|
+
isWritable: activity.isWritable,
|
|
286
|
+
preBalance: Stdlib_Option.flatMap(activity.lamports, l => l.pre),
|
|
287
|
+
postBalance: Stdlib_Option.flatMap(activity.lamports, l => l.post),
|
|
288
|
+
mint: Stdlib_Option.flatMap(activity.token, t => t.mint),
|
|
289
|
+
owner: Stdlib_Option.flatMap(activity.token, t => t.owner),
|
|
290
|
+
decimals: Stdlib_Option.flatMap(activity.token, t => t.decimals),
|
|
291
|
+
preAmount: Stdlib_Option.flatMap(activity.token, t => t.preAmount),
|
|
292
|
+
postAmount: Stdlib_Option.flatMap(activity.token, t => t.postAmount)
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
let block = rawJson.block;
|
|
297
|
+
let blockTime = block !== undefined ? block.time : undefined;
|
|
298
|
+
let block$1 = rawJson.block;
|
|
299
|
+
let blockHash = block$1 !== undefined ? block$1.hash : undefined;
|
|
300
|
+
svmBlocks.push({
|
|
301
|
+
blockNumber: slot,
|
|
302
|
+
blockHash: blockHash,
|
|
303
|
+
blockTimestamp: blockTime
|
|
304
|
+
});
|
|
305
|
+
liveRegistrations.forEach(reg => {
|
|
306
|
+
let onEventRegistrationIndex = onEventRegistrations.length;
|
|
307
|
+
let newrecord = {...reg};
|
|
308
|
+
newrecord.index = onEventRegistrationIndex;
|
|
309
|
+
onEventRegistrations.push(newrecord);
|
|
310
|
+
let payload = SvmHyperSyncSource.toSvmInstruction({
|
|
311
|
+
onEventRegistrationIndex: onEventRegistrationIndex,
|
|
312
|
+
slot: slot,
|
|
313
|
+
transactionIndex: transactionIndex,
|
|
314
|
+
path: path,
|
|
315
|
+
programId: programId,
|
|
316
|
+
accounts: accountArguments,
|
|
317
|
+
data: data,
|
|
318
|
+
isInner: Stdlib_Option.getOr(rawJson.isInner, false),
|
|
319
|
+
decoded: decoded,
|
|
320
|
+
logs: logs
|
|
321
|
+
}, match$1, match$2, eventConfig, newrecord.fieldSelection);
|
|
322
|
+
payload["srcAddress"] = programId;
|
|
323
|
+
items.push({
|
|
324
|
+
kind: 0,
|
|
325
|
+
onEventRegistration: newrecord,
|
|
326
|
+
chainId: chainId,
|
|
327
|
+
blockNumber: slot,
|
|
328
|
+
logIndex: transactionIndex,
|
|
329
|
+
orderPath: path,
|
|
330
|
+
transactionIndex: transactionIndex,
|
|
331
|
+
payload: payload
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
return;
|
|
190
335
|
}
|
|
191
|
-
let
|
|
192
|
-
let
|
|
336
|
+
let match$3 = rawJson.contract;
|
|
337
|
+
let match$4 = rawJson.event;
|
|
338
|
+
let match$5 = match$3 !== undefined && match$4 !== undefined ? [
|
|
339
|
+
match$3,
|
|
340
|
+
match$4
|
|
341
|
+
] : Stdlib_JsError.throwWithMessage(`simulate: Invalid item. Each item must have "contract" and "event" fields.`);
|
|
342
|
+
let eventName = match$5[1];
|
|
343
|
+
let contractName = match$5[0];
|
|
344
|
+
let ec$1 = findEventConfig(config, contractName, eventName);
|
|
345
|
+
let eventConfig$1 = ec$1 !== undefined ? ec$1 : Stdlib_JsError.throwWithMessage(`simulate: Event "` + eventName + `" not found on contract "` + contractName + `". Check that the contract and event names match your config.yaml.`);
|
|
193
346
|
let json = rawJson.params;
|
|
194
347
|
let paramsJson = json !== undefined ? json : ({});
|
|
195
|
-
let params = S$RescriptSchema.convertOrThrow(paramsJson, eventConfig.simulateParamsSchema);
|
|
348
|
+
let params = S$RescriptSchema.convertOrThrow(paramsJson, eventConfig$1.simulateParamsSchema);
|
|
196
349
|
let li = rawJson.logIndex;
|
|
197
350
|
let logIndex;
|
|
198
351
|
if (li !== undefined) {
|
|
@@ -205,45 +358,45 @@ function parse(simulateItems, config, chainConfig, onEventRegistrations) {
|
|
|
205
358
|
currentLogIndex.contents = li$1 + 1 | 0;
|
|
206
359
|
logIndex = li$1;
|
|
207
360
|
}
|
|
208
|
-
let srcAddress = deriveSrcAddress(rawJson.srcAddress, eventConfig, chainConfig, config);
|
|
209
|
-
let blockJson = rawJson.block;
|
|
210
|
-
let blockJson$
|
|
361
|
+
let srcAddress = deriveSrcAddress(rawJson.srcAddress, eventConfig$1, chainConfig, config);
|
|
362
|
+
let blockJson$1 = rawJson.block;
|
|
363
|
+
let blockJson$2 = (blockJson$1 == null) ? undefined : Primitive_option.some(blockJson$1);
|
|
211
364
|
let transactionJson = rawJson.transaction;
|
|
212
365
|
let transactionJson$1 = (transactionJson == null) ? undefined : Primitive_option.some(transactionJson);
|
|
213
|
-
let match$
|
|
214
|
-
let match$
|
|
215
|
-
switch (match$
|
|
366
|
+
let match$6 = config.ecosystem.name;
|
|
367
|
+
let match$7;
|
|
368
|
+
switch (match$6) {
|
|
216
369
|
case "evm" :
|
|
217
|
-
let block = parseEvmSimulateBlock(currentBlock.contents, blockJson$
|
|
218
|
-
match$
|
|
219
|
-
block,
|
|
220
|
-
block.number
|
|
370
|
+
let block$2 = parseEvmSimulateBlock(currentBlock.contents, blockJson$2);
|
|
371
|
+
match$7 = [
|
|
372
|
+
block$2,
|
|
373
|
+
block$2.number
|
|
221
374
|
];
|
|
222
375
|
break;
|
|
223
376
|
case "fuel" :
|
|
224
|
-
let block$
|
|
225
|
-
match$
|
|
226
|
-
block$
|
|
227
|
-
block$
|
|
377
|
+
let block$3 = parseFuelSimulateBlock(currentBlock.contents, blockJson$2);
|
|
378
|
+
match$7 = [
|
|
379
|
+
block$3,
|
|
380
|
+
block$3.height
|
|
228
381
|
];
|
|
229
382
|
break;
|
|
230
383
|
case "svm" :
|
|
231
|
-
match$
|
|
384
|
+
match$7 = Stdlib_JsError.throwWithMessage("simulate is not supported for SVM ecosystem");
|
|
232
385
|
break;
|
|
233
386
|
}
|
|
234
|
-
let blockNumber = match$
|
|
235
|
-
let block$
|
|
236
|
-
let match$
|
|
237
|
-
let transaction;
|
|
238
|
-
switch (match$
|
|
387
|
+
let blockNumber = match$7[1];
|
|
388
|
+
let block$4 = match$7[0];
|
|
389
|
+
let match$8 = config.ecosystem.name;
|
|
390
|
+
let transaction$1;
|
|
391
|
+
switch (match$8) {
|
|
239
392
|
case "evm" :
|
|
240
|
-
transaction = parseEvmSimulateTransaction(transactionJson$1);
|
|
393
|
+
transaction$1 = parseEvmSimulateTransaction(transactionJson$1);
|
|
241
394
|
break;
|
|
242
395
|
case "fuel" :
|
|
243
|
-
transaction = parseFuelSimulateTransaction(transactionJson$1);
|
|
396
|
+
transaction$1 = parseFuelSimulateTransaction(transactionJson$1);
|
|
244
397
|
break;
|
|
245
398
|
case "svm" :
|
|
246
|
-
transaction = Stdlib_JsError.throwWithMessage("simulate is not supported for SVM ecosystem");
|
|
399
|
+
transaction$1 = Stdlib_JsError.throwWithMessage("simulate is not supported for SVM ecosystem");
|
|
247
400
|
break;
|
|
248
401
|
}
|
|
249
402
|
currentBlock.contents = blockNumber;
|
|
@@ -254,20 +407,20 @@ function parse(simulateItems, config, chainConfig, onEventRegistrations) {
|
|
|
254
407
|
} else {
|
|
255
408
|
seenCoordinates[coordinate] = itemIndex;
|
|
256
409
|
}
|
|
257
|
-
let liveRegistrations = HandlerRegister.getSimulateOnEventRegistrations(config, chainId, eventConfig).filter(reg => {
|
|
410
|
+
let liveRegistrations$1 = HandlerRegister.getSimulateOnEventRegistrations(config, chainId, eventConfig$1).filter(reg => {
|
|
258
411
|
if (Stdlib_Option.isSome(reg.handler) || Stdlib_Option.isSome(reg.contractRegister)) {
|
|
259
412
|
return !HandlerRegister.isDroppedByWhere(config, reg);
|
|
260
413
|
} else {
|
|
261
414
|
return false;
|
|
262
415
|
}
|
|
263
416
|
});
|
|
264
|
-
if (Utils.$$Array.isEmpty(liveRegistrations)) {
|
|
265
|
-
let match$
|
|
266
|
-
Stdlib_JsError.throwWithMessage(`simulate: no handler runs for event "` +
|
|
267
|
-
match$
|
|
417
|
+
if (Utils.$$Array.isEmpty(liveRegistrations$1)) {
|
|
418
|
+
let match$9 = ChainMap.values(config.chainMap).length;
|
|
419
|
+
Stdlib_JsError.throwWithMessage(`simulate: no handler runs for event "` + eventName + `" on contract "` + contractName + `"` + (
|
|
420
|
+
match$9 !== 1 ? ` on chain ` + ChainId.toString(chainId) : ""
|
|
268
421
|
) + `. Register a handler with indexer.onEvent (and check any \`where\` filter isn't excluding this chain) before simulating it.`);
|
|
269
422
|
}
|
|
270
|
-
liveRegistrations.forEach(reg => {
|
|
423
|
+
liveRegistrations$1.forEach(reg => {
|
|
271
424
|
let onEventRegistrationIndex = onEventRegistrations.length;
|
|
272
425
|
let newrecord = {...reg};
|
|
273
426
|
newrecord.index = onEventRegistrationIndex;
|
|
@@ -280,19 +433,34 @@ function parse(simulateItems, config, chainConfig, onEventRegistrations) {
|
|
|
280
433
|
logIndex: logIndex,
|
|
281
434
|
transactionIndex: 0,
|
|
282
435
|
payload: {
|
|
283
|
-
contractName: eventConfig.contractName,
|
|
284
|
-
eventName: eventConfig.name,
|
|
436
|
+
contractName: eventConfig$1.contractName,
|
|
437
|
+
eventName: eventConfig$1.name,
|
|
285
438
|
params: params,
|
|
286
439
|
chainId: chainId,
|
|
287
440
|
srcAddress: srcAddress,
|
|
288
441
|
logIndex: logIndex,
|
|
289
|
-
transaction: Primitive_option.some(transaction),
|
|
290
|
-
block: Primitive_option.some(block$
|
|
442
|
+
transaction: Primitive_option.some(transaction$1),
|
|
443
|
+
block: Primitive_option.some(block$4)
|
|
291
444
|
}
|
|
292
445
|
});
|
|
293
446
|
});
|
|
294
447
|
});
|
|
295
|
-
|
|
448
|
+
let match = config.ecosystem.name;
|
|
449
|
+
switch (match) {
|
|
450
|
+
case "evm" :
|
|
451
|
+
case "fuel" :
|
|
452
|
+
return {
|
|
453
|
+
items: items,
|
|
454
|
+
transactionStore: undefined,
|
|
455
|
+
blockStore: undefined
|
|
456
|
+
};
|
|
457
|
+
case "svm" :
|
|
458
|
+
return {
|
|
459
|
+
items: items,
|
|
460
|
+
transactionStore: Primitive_option.some(TransactionStore.fromSvmJs(svmTxs, svmActivities)),
|
|
461
|
+
blockStore: Primitive_option.some(BlockStore.fromJs(svmBlocks, "svm", false))
|
|
462
|
+
};
|
|
463
|
+
}
|
|
296
464
|
}
|
|
297
465
|
|
|
298
466
|
function patchConfig(config, processConfig, registrationsByChainId) {
|
|
@@ -329,12 +497,14 @@ function patchConfig(config, processConfig, registrationsByChainId) {
|
|
|
329
497
|
let newrecord = {...chainConfig};
|
|
330
498
|
newrecord.endBlock = endBlock;
|
|
331
499
|
newrecord.startBlock = startBlock;
|
|
332
|
-
let
|
|
500
|
+
let match = parse(simulateRaw, config, newrecord, chainRegistrations.onEventRegistrations);
|
|
333
501
|
let newrecord$1 = {...newrecord};
|
|
334
502
|
newrecord$1.sourceConfig = {
|
|
335
503
|
TAG: "SimulateSourceConfig",
|
|
336
|
-
items: items,
|
|
337
|
-
endBlock: endBlock
|
|
504
|
+
items: match.items,
|
|
505
|
+
endBlock: endBlock,
|
|
506
|
+
transactionStore: match.transactionStore,
|
|
507
|
+
blockStore: match.blockStore
|
|
338
508
|
};
|
|
339
509
|
return newrecord$1;
|
|
340
510
|
});
|
|
@@ -356,6 +526,7 @@ export {
|
|
|
356
526
|
dummySrcAddress,
|
|
357
527
|
firstContractAddress,
|
|
358
528
|
deriveSrcAddress,
|
|
529
|
+
liveRegistrationsFor,
|
|
359
530
|
parse,
|
|
360
531
|
patchConfig,
|
|
361
532
|
}
|
package/src/TestIndexer.res
CHANGED
|
@@ -10,6 +10,12 @@ type fuelChainConfig = {
|
|
|
10
10
|
simulate?: array<Envio.fuelSimulateItem>,
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
type svmChainConfig = {
|
|
14
|
+
startBlock?: int,
|
|
15
|
+
endBlock?: int,
|
|
16
|
+
simulate?: array<Envio.svmSimulateItem>,
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
// Internal type used for block range validation and state management
|
|
14
20
|
type chainConfig = {
|
|
15
21
|
startBlock: int,
|
|
@@ -314,22 +320,32 @@ let getSimulateEndBlock = (
|
|
|
314
320
|
~startBlock: int,
|
|
315
321
|
): int => {
|
|
316
322
|
let maxBlock = ref(startBlock)
|
|
323
|
+
let blockNumberKey = switch config.ecosystem.name {
|
|
324
|
+
| Svm => "slot"
|
|
325
|
+
| _ => config.ecosystem.blockNumberName
|
|
326
|
+
}
|
|
327
|
+
let bump = (n: option<int>) =>
|
|
328
|
+
switch n {
|
|
329
|
+
| Some(v) if v > maxBlock.contents => maxBlock := v
|
|
330
|
+
| _ => ()
|
|
331
|
+
}
|
|
332
|
+
let getInt = (d: dict<JSON.t>, key) =>
|
|
333
|
+
d
|
|
334
|
+
->Dict.get(key)
|
|
335
|
+
->Option.flatMap(v => v->(Utils.magic: JSON.t => Nullable.t<int>)->Nullable.toOption)
|
|
317
336
|
simulateItems->Array.forEach(rawJson => {
|
|
337
|
+
let itemDict = rawJson->(Utils.magic: JSON.t => dict<JSON.t>)
|
|
338
|
+
// SVM items carry the slot at the top level (`block.slot` is the override).
|
|
339
|
+
switch config.ecosystem.name {
|
|
340
|
+
| Svm => itemDict->getInt("slot")->bump
|
|
341
|
+
| _ => ()
|
|
342
|
+
}
|
|
318
343
|
let blockJson: option<JSON.t> =
|
|
319
344
|
(rawJson->(Utils.magic: JSON.t => {..}))["block"]
|
|
320
345
|
->(Utils.magic: 'a => Nullable.t<JSON.t>)
|
|
321
346
|
->Nullable.toOption
|
|
322
347
|
switch blockJson {
|
|
323
|
-
| Some(bj) =>
|
|
324
|
-
let blockDict = bj->(Utils.magic: JSON.t => dict<JSON.t>)
|
|
325
|
-
let n: option<int> =
|
|
326
|
-
blockDict
|
|
327
|
-
->Dict.get(config.ecosystem.blockNumberName)
|
|
328
|
-
->Option.flatMap(v => v->(Utils.magic: JSON.t => Nullable.t<int>)->Nullable.toOption)
|
|
329
|
-
switch n {
|
|
330
|
-
| Some(v) if v > maxBlock.contents => maxBlock := v
|
|
331
|
-
| _ => ()
|
|
332
|
-
}
|
|
348
|
+
| Some(bj) => bj->(Utils.magic: JSON.t => dict<JSON.t>)->getInt(blockNumberKey)->bump
|
|
333
349
|
| None => ()
|
|
334
350
|
}
|
|
335
351
|
})
|
package/src/TestIndexer.res.mjs
CHANGED
|
@@ -230,22 +230,44 @@ function getSimulateEndBlock(simulateItems, config, startBlock) {
|
|
|
230
230
|
let maxBlock = {
|
|
231
231
|
contents: startBlock
|
|
232
232
|
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
233
|
+
let match = config.ecosystem.name;
|
|
234
|
+
let blockNumberKey;
|
|
235
|
+
switch (match) {
|
|
236
|
+
case "evm" :
|
|
237
|
+
case "fuel" :
|
|
238
|
+
blockNumberKey = config.ecosystem.blockNumberName;
|
|
239
|
+
break;
|
|
240
|
+
case "svm" :
|
|
241
|
+
blockNumberKey = "slot";
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
let bump = n => {
|
|
245
245
|
if (n !== undefined && n > maxBlock.contents) {
|
|
246
246
|
maxBlock.contents = n;
|
|
247
247
|
return;
|
|
248
248
|
}
|
|
249
|
+
};
|
|
250
|
+
let getInt = (d, key) => Stdlib_Option.flatMap(d[key], v => {
|
|
251
|
+
if (v == null) {
|
|
252
|
+
return;
|
|
253
|
+
} else {
|
|
254
|
+
return Primitive_option.some(v);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
simulateItems.forEach(rawJson => {
|
|
258
|
+
let match = config.ecosystem.name;
|
|
259
|
+
switch (match) {
|
|
260
|
+
case "evm" :
|
|
261
|
+
case "fuel" :
|
|
262
|
+
break;
|
|
263
|
+
case "svm" :
|
|
264
|
+
bump(getInt(rawJson, "slot"));
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
let blockJson = rawJson.block;
|
|
268
|
+
if (!(blockJson == null)) {
|
|
269
|
+
return bump(getInt(blockJson, blockNumberKey));
|
|
270
|
+
}
|
|
249
271
|
});
|
|
250
272
|
return maxBlock.contents;
|
|
251
273
|
}
|
|
@@ -14,8 +14,13 @@ type execParams = {query: string}
|
|
|
14
14
|
@module("@clickhouse/client")
|
|
15
15
|
external createClient: clientConfig => client = "createClient"
|
|
16
16
|
|
|
17
|
+
// `command`, not `exec`: exec hands its response stream to the caller and holds
|
|
18
|
+
// the socket until it is consumed, which nothing here does. The pool is 10
|
|
19
|
+
// sockets wide, so a schema whose DDL needs more than that — initialize issues
|
|
20
|
+
// 2N+3 statements for N entities — stalled every statement past the tenth until
|
|
21
|
+
// the 30s request timeout freed one. command destroys the stream for us.
|
|
17
22
|
@send
|
|
18
|
-
external
|
|
23
|
+
external command: (client, execParams) => promise<unit> = "command"
|
|
19
24
|
|
|
20
25
|
@send
|
|
21
26
|
external close: client => promise<unit> = "close"
|
|
@@ -352,6 +357,17 @@ let setUpdatesOrThrow = async (
|
|
|
352
357
|
// The '{cluster}' macro resolves to each node's configured cluster name.
|
|
353
358
|
let onClusterClause = (~onCluster: bool) => onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
354
359
|
|
|
360
|
+
// ReplicatedMergeTree drops an insert whose block hash is already in Keeper, and
|
|
361
|
+
// mutations don't clear those hashes. Crash recovery trims the history tail past
|
|
362
|
+
// the committed Postgres checkpoint with ALTER ... DELETE and then replays it, so
|
|
363
|
+
// an identical replayed block would be discarded while still reporting success —
|
|
364
|
+
// a permanent gap that nothing surfaces. Trim-then-replay is what makes recovery
|
|
365
|
+
// correct here, and the duplicates dedup would have caught are already collapsed
|
|
366
|
+
// by the entity view's `LIMIT 1 BY`. Plain MergeTree goes by
|
|
367
|
+
// non_replicated_deduplication_window (0 by default), so it needs no clause.
|
|
368
|
+
let replicatedTableSettingsClause = (~replicated: bool) =>
|
|
369
|
+
replicated ? "\nSETTINGS replicated_deduplication_window = 0" : ""
|
|
370
|
+
|
|
355
371
|
// Strip both engine arguments `(...)` and a trailing `SETTINGS ...` clause to
|
|
356
372
|
// get the bare engine name, e.g. `Replicated('/p','{shard}','{replica}') SETTINGS x=1`
|
|
357
373
|
// and `Replicated SETTINGS x=1` both yield `Replicated`.
|
|
@@ -390,9 +406,9 @@ let makeCreateHistoryTableQuery = (
|
|
|
390
406
|
}
|
|
391
407
|
})
|
|
392
408
|
|
|
393
|
-
let (partitionBy, orderBy, ttl) = switch entityConfig.storage.clickhouseOptions {
|
|
394
|
-
| Some(options) => (options.partitionBy, options.orderBy, options.ttl)
|
|
395
|
-
| None => (None, None, None)
|
|
409
|
+
let (partitionBy, orderBy, ttl, skippingIndexes) = switch entityConfig.storage.clickhouseOptions {
|
|
410
|
+
| Some(options) => (options.partitionBy, options.orderBy, options.ttl, options.skippingIndexes)
|
|
411
|
+
| None => (None, None, None, None)
|
|
396
412
|
}
|
|
397
413
|
|
|
398
414
|
// Schema field name -> ClickHouse column name, so @storage(clickhouse: {...})
|
|
@@ -456,6 +472,22 @@ let makeCreateHistoryTableQuery = (
|
|
|
456
472
|
| None => ""
|
|
457
473
|
}
|
|
458
474
|
|
|
475
|
+
// Data skipping indexes live inside the column list, after the last column.
|
|
476
|
+
// GRANULARITY is omitted when unset, leaving ClickHouse's default of 1.
|
|
477
|
+
let skippingIndexDefinitions = switch skippingIndexes {
|
|
478
|
+
| Some(skippingIndexes) =>
|
|
479
|
+
skippingIndexes
|
|
480
|
+
->Array.map(index => {
|
|
481
|
+
let granularityClause = switch index.granularity {
|
|
482
|
+
| Some(granularity) => ` GRANULARITY ${granularity->Int.toString}`
|
|
483
|
+
| None => ""
|
|
484
|
+
}
|
|
485
|
+
`,\n INDEX \`${index.name}\` ${index.expr->resolveExpressionColumns} TYPE ${index.type_}${granularityClause}`
|
|
486
|
+
})
|
|
487
|
+
->Array.joinUnsafe("")
|
|
488
|
+
| None => ""
|
|
489
|
+
}
|
|
490
|
+
|
|
459
491
|
`CREATE TABLE IF NOT EXISTS ${database}.\`${EntityHistory.historyTableName(
|
|
460
492
|
~entityName=entityConfig.name,
|
|
461
493
|
~entityIndex=entityConfig.index,
|
|
@@ -470,10 +502,10 @@ let makeCreateHistoryTableQuery = (
|
|
|
470
502
|
~fieldType=Enum({config: EntityHistory.RowAction.config->Table.fromGenericEnumConfig}),
|
|
471
503
|
~isNullable=false,
|
|
472
504
|
~isArray=false,
|
|
473
|
-
)}
|
|
505
|
+
)}${skippingIndexDefinitions}
|
|
474
506
|
)
|
|
475
507
|
ENGINE = ${tableEngine}${partitionByClause}
|
|
476
|
-
ORDER BY (${orderByColumns})${ttlClause}`
|
|
508
|
+
ORDER BY (${orderByColumns})${ttlClause}${replicatedTableSettingsClause(~replicated)}`
|
|
477
509
|
}
|
|
478
510
|
|
|
479
511
|
// Generate CREATE TABLE query for checkpoints
|
|
@@ -517,7 +549,7 @@ let makeCreateCheckpointsTableQuery = (
|
|
|
517
549
|
)}
|
|
518
550
|
)
|
|
519
551
|
ENGINE = ${tableEngine}
|
|
520
|
-
ORDER BY (${idField})`
|
|
552
|
+
ORDER BY (${idField})${replicatedTableSettingsClause(~replicated)}`
|
|
521
553
|
}
|
|
522
554
|
|
|
523
555
|
// Generate CREATE VIEW query for entity current state
|
|
@@ -628,21 +660,21 @@ let initialize = async (
|
|
|
628
660
|
// CLUSTER removes the database from every node — the engine's own log
|
|
629
661
|
// can't replicate the drop of the database it lives in — and SYNC waits
|
|
630
662
|
// for the drop to finish before the CREATE below.
|
|
631
|
-
await client->
|
|
663
|
+
await client->command({
|
|
632
664
|
query: `DROP DATABASE IF EXISTS ${database} ON CLUSTER '{cluster}' SYNC`,
|
|
633
665
|
})
|
|
634
666
|
} else {
|
|
635
|
-
await client->
|
|
667
|
+
await client->command({
|
|
636
668
|
query: `TRUNCATE DATABASE IF EXISTS ${database}${onClusterClause(~onCluster=ddlOnCluster)}`,
|
|
637
669
|
})
|
|
638
670
|
}
|
|
639
|
-
await client->
|
|
671
|
+
await client->command({
|
|
640
672
|
query: `CREATE DATABASE IF NOT EXISTS ${database}${databaseOnClusterClause}${databaseEngineClause}`,
|
|
641
673
|
})
|
|
642
674
|
|
|
643
675
|
await Promise.all(
|
|
644
676
|
entities->Array.map(entityConfig =>
|
|
645
|
-
client->
|
|
677
|
+
client->command({
|
|
646
678
|
query: makeCreateHistoryTableQuery(
|
|
647
679
|
~entityConfig,
|
|
648
680
|
~database,
|
|
@@ -653,7 +685,7 @@ let initialize = async (
|
|
|
653
685
|
})
|
|
654
686
|
),
|
|
655
687
|
)->Utils.Promise.ignoreValue
|
|
656
|
-
await client->
|
|
688
|
+
await client->command({
|
|
657
689
|
query: makeCreateCheckpointsTableQuery(
|
|
658
690
|
~database,
|
|
659
691
|
~replicated,
|
|
@@ -670,14 +702,14 @@ let initialize = async (
|
|
|
670
702
|
// caught up before creating the views. ON CLUSTER must precede the
|
|
671
703
|
// database name in this command's grammar.
|
|
672
704
|
if hasReplicatedDatabaseEngine {
|
|
673
|
-
await client->
|
|
705
|
+
await client->command({
|
|
674
706
|
query: `SYSTEM SYNC DATABASE REPLICA ON CLUSTER '{cluster}' ${database}`,
|
|
675
707
|
})
|
|
676
708
|
}
|
|
677
709
|
|
|
678
710
|
await Promise.all(
|
|
679
711
|
entities->Array.map(entityConfig =>
|
|
680
|
-
client->
|
|
712
|
+
client->command({
|
|
681
713
|
query: makeCreateViewQuery(~entityConfig, ~database, ~onCluster=ddlOnCluster),
|
|
682
714
|
})
|
|
683
715
|
),
|
|
@@ -697,7 +729,7 @@ let resume = async (client, ~database: string, ~checkpointId: Internal.checkpoin
|
|
|
697
729
|
try {
|
|
698
730
|
// Try to use the database - will throw if it doesn't exist
|
|
699
731
|
try {
|
|
700
|
-
await client->
|
|
732
|
+
await client->command({query: `USE ${database}`})
|
|
701
733
|
} catch {
|
|
702
734
|
| exn =>
|
|
703
735
|
Logging.errorWithExn(
|
|
@@ -717,14 +749,14 @@ let resume = async (client, ~database: string, ~checkpointId: Internal.checkpoin
|
|
|
717
749
|
await Promise.all(
|
|
718
750
|
tables->Array.map(table => {
|
|
719
751
|
let tableName = table["name"]
|
|
720
|
-
client->
|
|
752
|
+
client->command({
|
|
721
753
|
query: `ALTER TABLE ${database}.\`${tableName}\` DELETE WHERE \`${EntityHistory.checkpointIdFieldName}\` > ${checkpointId->BigInt.toString}`,
|
|
722
754
|
})
|
|
723
755
|
}),
|
|
724
756
|
)->Utils.Promise.ignoreValue
|
|
725
757
|
|
|
726
758
|
// Delete stale checkpoints
|
|
727
|
-
await client->
|
|
759
|
+
await client->command({
|
|
728
760
|
query: `DELETE FROM ${database}.\`${InternalTable.Checkpoints.table.tableName}\` WHERE \`${Table.idFieldName}\` > ${checkpointId->BigInt.toString}`,
|
|
729
761
|
})
|
|
730
762
|
} catch {
|