envio 3.7.0 → 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/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 +33 -6
- package/src/bindings/ClickHouse.res.mjs +25 -4
- package/src/db/InternalTable.res +11 -0
- package/src/db/InternalTable.res.mjs +7 -0
- package/src/sources/EvmHyperSyncSource.res +3 -2
- 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 +91 -34
- package/src/sources/SvmHyperSyncSource.res.mjs +77 -37
- 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
|
}
|
|
@@ -357,6 +357,17 @@ let setUpdatesOrThrow = async (
|
|
|
357
357
|
// The '{cluster}' macro resolves to each node's configured cluster name.
|
|
358
358
|
let onClusterClause = (~onCluster: bool) => onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
359
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
|
+
|
|
360
371
|
// Strip both engine arguments `(...)` and a trailing `SETTINGS ...` clause to
|
|
361
372
|
// get the bare engine name, e.g. `Replicated('/p','{shard}','{replica}') SETTINGS x=1`
|
|
362
373
|
// and `Replicated SETTINGS x=1` both yield `Replicated`.
|
|
@@ -395,9 +406,9 @@ let makeCreateHistoryTableQuery = (
|
|
|
395
406
|
}
|
|
396
407
|
})
|
|
397
408
|
|
|
398
|
-
let (partitionBy, orderBy, ttl) = switch entityConfig.storage.clickhouseOptions {
|
|
399
|
-
| Some(options) => (options.partitionBy, options.orderBy, options.ttl)
|
|
400
|
-
| 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)
|
|
401
412
|
}
|
|
402
413
|
|
|
403
414
|
// Schema field name -> ClickHouse column name, so @storage(clickhouse: {...})
|
|
@@ -461,6 +472,22 @@ let makeCreateHistoryTableQuery = (
|
|
|
461
472
|
| None => ""
|
|
462
473
|
}
|
|
463
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
|
+
|
|
464
491
|
`CREATE TABLE IF NOT EXISTS ${database}.\`${EntityHistory.historyTableName(
|
|
465
492
|
~entityName=entityConfig.name,
|
|
466
493
|
~entityIndex=entityConfig.index,
|
|
@@ -475,10 +502,10 @@ let makeCreateHistoryTableQuery = (
|
|
|
475
502
|
~fieldType=Enum({config: EntityHistory.RowAction.config->Table.fromGenericEnumConfig}),
|
|
476
503
|
~isNullable=false,
|
|
477
504
|
~isArray=false,
|
|
478
|
-
)}
|
|
505
|
+
)}${skippingIndexDefinitions}
|
|
479
506
|
)
|
|
480
507
|
ENGINE = ${tableEngine}${partitionByClause}
|
|
481
|
-
ORDER BY (${orderByColumns})${ttlClause}`
|
|
508
|
+
ORDER BY (${orderByColumns})${ttlClause}${replicatedTableSettingsClause(~replicated)}`
|
|
482
509
|
}
|
|
483
510
|
|
|
484
511
|
// Generate CREATE TABLE query for checkpoints
|
|
@@ -522,7 +549,7 @@ let makeCreateCheckpointsTableQuery = (
|
|
|
522
549
|
)}
|
|
523
550
|
)
|
|
524
551
|
ENGINE = ${tableEngine}
|
|
525
|
-
ORDER BY (${idField})`
|
|
552
|
+
ORDER BY (${idField})${replicatedTableSettingsClause(~replicated)}`
|
|
526
553
|
}
|
|
527
554
|
|
|
528
555
|
// Generate CREATE VIEW query for entity current state
|
|
@@ -273,6 +273,14 @@ function onClusterClause(onCluster) {
|
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
function replicatedTableSettingsClause(replicated) {
|
|
277
|
+
if (replicated) {
|
|
278
|
+
return "\nSETTINGS replicated_deduplication_window = 0";
|
|
279
|
+
} else {
|
|
280
|
+
return "";
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
276
284
|
function databaseEngineName(engineSpec) {
|
|
277
285
|
return engineSpec.trim().split("(")[0].split(" ")[0].trim();
|
|
278
286
|
}
|
|
@@ -295,12 +303,15 @@ function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt, onCl
|
|
|
295
303
|
let match = options !== undefined ? [
|
|
296
304
|
options.partitionBy,
|
|
297
305
|
options.orderBy,
|
|
298
|
-
options.ttl
|
|
306
|
+
options.ttl,
|
|
307
|
+
options.skippingIndexes
|
|
299
308
|
] : [
|
|
309
|
+
undefined,
|
|
300
310
|
undefined,
|
|
301
311
|
undefined,
|
|
302
312
|
undefined
|
|
303
313
|
];
|
|
314
|
+
let skippingIndexes = match[3];
|
|
304
315
|
let ttl = match[2];
|
|
305
316
|
let orderBy = match[1];
|
|
306
317
|
let partitionBy = match[0];
|
|
@@ -336,6 +347,11 @@ function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt, onCl
|
|
|
336
347
|
});
|
|
337
348
|
let partitionByClause = partitionBy !== undefined ? `\nPARTITION BY ` + resolveExpressionColumns(partitionBy) : "";
|
|
338
349
|
let ttlClause = ttl !== undefined ? `\nTTL ` + resolveExpressionColumns(ttl) : "";
|
|
350
|
+
let skippingIndexDefinitions = skippingIndexes !== undefined ? skippingIndexes.map(index => {
|
|
351
|
+
let granularity = index.granularity;
|
|
352
|
+
let granularityClause = granularity !== undefined ? ` GRANULARITY ` + granularity.toString() : "";
|
|
353
|
+
return `,\n INDEX \`` + index.name + `\` ` + resolveExpressionColumns(index.expr) + ` TYPE ` + index.type + granularityClause;
|
|
354
|
+
}).join("") : "";
|
|
339
355
|
return `CREATE TABLE IF NOT EXISTS ` + database + `.\`` + EntityHistory.historyTableName(entityConfig.name, entityConfig.index) + `\`` + (
|
|
340
356
|
onCluster ? ` ON CLUSTER '{cluster}'` : ""
|
|
341
357
|
) + ` (
|
|
@@ -344,10 +360,12 @@ function makeCreateHistoryTableQuery(entityConfig, database, replicatedOpt, onCl
|
|
|
344
360
|
\`` + EntityHistory.changeFieldName + `\` ` + getClickHouseFieldType({
|
|
345
361
|
type: "Enum",
|
|
346
362
|
config: EntityHistory.RowAction.config
|
|
347
|
-
}, false, false, undefined) + `
|
|
363
|
+
}, false, false, undefined) + skippingIndexDefinitions + `
|
|
348
364
|
)
|
|
349
365
|
ENGINE = ` + tableEngine + partitionByClause + `
|
|
350
|
-
ORDER BY (` + orderByColumns + `)` + ttlClause
|
|
366
|
+
ORDER BY (` + orderByColumns + `)` + ttlClause + (
|
|
367
|
+
replicated ? "\nSETTINGS replicated_deduplication_window = 0" : ""
|
|
368
|
+
);
|
|
351
369
|
}
|
|
352
370
|
|
|
353
371
|
function makeCreateCheckpointsTableQuery(database, replicatedOpt, onClusterOpt, chainIdModeOpt) {
|
|
@@ -365,7 +383,9 @@ function makeCreateCheckpointsTableQuery(database, replicatedOpt, onClusterOpt,
|
|
|
365
383
|
\`` + "events_processed" + `\` ` + getClickHouseFieldType("UInt64", false, false, undefined) + `
|
|
366
384
|
)
|
|
367
385
|
ENGINE = ` + tableEngine + `
|
|
368
|
-
ORDER BY (` + "id" + `)
|
|
386
|
+
ORDER BY (` + "id" + `)` + (
|
|
387
|
+
replicated ? "\nSETTINGS replicated_deduplication_window = 0" : ""
|
|
388
|
+
);
|
|
369
389
|
}
|
|
370
390
|
|
|
371
391
|
function makeCreateViewQuery(entityConfig, database, onClusterOpt) {
|
|
@@ -505,6 +525,7 @@ export {
|
|
|
505
525
|
setCheckpointsOrThrow,
|
|
506
526
|
setUpdatesOrThrow,
|
|
507
527
|
onClusterClause,
|
|
528
|
+
replicatedTableSettingsClause,
|
|
508
529
|
databaseEngineName,
|
|
509
530
|
makeCreateHistoryTableQuery,
|
|
510
531
|
makeCreateCheckpointsTableQuery,
|