envio 3.9.0 → 3.10.0-subgraph
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/evm.schema.json +26 -0
- package/fuel.schema.json +26 -0
- package/index.d.ts +41 -8
- package/package.json +6 -7
- package/src/Batch.res +9 -0
- package/src/ChainState.res +35 -39
- package/src/ChainState.res.mjs +14 -11
- package/src/ChainState.resi +15 -7
- package/src/Config.res +61 -44
- package/src/Config.res.mjs +56 -19
- package/src/Core.res +19 -0
- package/src/Core.res.mjs +11 -0
- package/src/CrossChainState.res +15 -23
- package/src/CrossChainState.res.mjs +10 -18
- package/src/CrossChainState.resi +4 -1
- package/src/Ecosystem.res +7 -2
- package/src/Envio.res +6 -4
- package/src/EventConfigBuilder.res +169 -26
- package/src/EventConfigBuilder.res.mjs +124 -15
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +8 -6
- package/src/HandlerLoader.res +20 -8
- package/src/HandlerLoader.res.mjs +11 -2
- package/src/HandlerRegister.res +21 -4
- package/src/HandlerRegister.res.mjs +24 -5
- package/src/InMemoryStore.res +9 -7
- package/src/InMemoryStore.res.mjs +3 -3
- package/src/IndexerState.res +43 -6
- package/src/IndexerState.res.mjs +36 -7
- package/src/IndexerState.resi +4 -4
- package/src/Internal.res +16 -17
- package/src/Metrics.res +72 -6
- package/src/Metrics.res.mjs +30 -2
- package/src/Persistence.res +36 -30
- package/src/Persistence.res.mjs +1 -20
- package/src/PgStorage.res +104 -56
- package/src/PgStorage.res.mjs +57 -35
- package/src/PruneStaleHistory.res +85 -52
- package/src/PruneStaleHistory.res.mjs +63 -37
- package/src/Rollback.res +68 -45
- package/src/Rollback.res.mjs +38 -19
- package/src/RollbackCommit.res +7 -9
- package/src/RollbackCommit.res.mjs +5 -6
- package/src/SafeCheckpointTracking.res +11 -11
- package/src/SafeCheckpointTracking.res.mjs +7 -6
- package/src/SimulateItems.res +33 -18
- package/src/SimulateItems.res.mjs +26 -22
- package/src/Sink.res +38 -26
- package/src/Sink.res.mjs +21 -16
- package/src/TestIndexer.res +5 -5
- package/src/TestIndexer.res.mjs +1 -1
- package/src/UserContext.res +358 -51
- package/src/UserContext.res.mjs +271 -35
- package/src/Utils.res +170 -0
- package/src/Utils.res.mjs +153 -0
- package/src/Writing.res +2 -2
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +366 -698
- package/src/bindings/ClickHouse.res.mjs +371 -421
- package/src/bindings/ClickHouseSink.res +337 -0
- package/src/bindings/ClickHouseSink.res.mjs +246 -0
- package/src/bindings/EventSource.res +8 -2
- package/src/bindings/NodeJs.res +6 -0
- package/src/bindings/Vitest.res +23 -0
- package/src/bindings/Vitest.res.mjs +3 -0
- package/src/bindings/WebSocket.res +9 -10
- package/src/db/CheckpointBounds.res +53 -0
- package/src/db/CheckpointBounds.res.mjs +44 -0
- package/src/db/EntityFilter.res +36 -17
- package/src/db/EntityFilter.res.mjs +25 -14
- package/src/db/EntityHistory.res +47 -22
- package/src/db/EntityHistory.res.mjs +19 -12
- package/src/db/InternalTable.res +84 -35
- package/src/db/InternalTable.res.mjs +56 -23
- package/src/db/RollbackFloors.res +55 -0
- package/src/db/RollbackFloors.res.mjs +92 -0
- package/src/db/Table.res +22 -4
- package/src/db/Table.res.mjs +30 -9
- package/src/sources/Evm.res +2 -0
- package/src/sources/Evm.res.mjs +2 -0
- package/src/sources/EvmHyperSyncSource.res +3 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcWs.res +124 -0
- package/src/sources/EvmRpcWs.res.mjs +117 -0
- package/src/sources/Fuel.res +2 -0
- package/src/sources/Fuel.res.mjs +2 -0
- package/src/sources/FuelHyperSyncSource.res +1 -1
- package/src/sources/FuelHyperSyncSource.res.mjs +2 -1
- package/src/sources/HeightFeed.res +568 -0
- package/src/sources/HeightFeed.res.mjs +452 -0
- package/src/sources/HeightStream.res +257 -0
- package/src/sources/HeightStream.res.mjs +194 -0
- package/src/sources/HyperSync.res +5 -0
- package/src/sources/HyperSync.res.mjs +3 -0
- package/src/sources/HyperSync.resi +4 -0
- package/src/sources/HyperSyncSSE.res +49 -0
- package/src/sources/HyperSyncSSE.res.mjs +79 -0
- package/src/sources/RpcSource.res +1 -1
- package/src/sources/RpcSource.res.mjs +2 -2
- package/src/sources/Source.res +54 -1
- package/src/sources/Source.res.mjs +25 -0
- package/src/sources/SourceManager.res +220 -227
- package/src/sources/SourceManager.res.mjs +139 -164
- package/src/sources/SourceManager.resi +8 -0
- package/src/sources/Svm.res +2 -0
- package/src/sources/Svm.res.mjs +2 -0
- package/src/sources/SvmHyperSyncClient.res +70 -127
- package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
- package/src/sources/SvmHyperSyncSource.res +18 -15
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/src/subgraph/blocks.ts +176 -0
- package/src/subgraph/calls.ts +217 -0
- package/src/subgraph/conformance.ts +102 -0
- package/src/subgraph/division.ts +133 -0
- package/src/subgraph/errors.ts +90 -0
- package/src/subgraph/graph-ts-types/VERSION +2 -0
- package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
- package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
- package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
- package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
- package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
- package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
- package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
- package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
- package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
- package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
- package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
- package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
- package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
- package/src/subgraph/graph-ts-types/index.d.ts +102 -0
- package/src/subgraph/graph-ts.ts +1948 -0
- package/src/subgraph/hosts.ts +148 -0
- package/src/subgraph/runtime.ts +863 -0
- package/src/subgraph/scope.ts +66 -0
- package/svm.schema.json +6 -81
- package/src/MemoryStorage.res +0 -729
- package/src/MemoryStorage.res.mjs +0 -586
- package/src/sources/HyperSyncHeightStream.res +0 -129
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
- package/src/sources/RpcWebSocketHeightStream.res +0 -175
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -180
package/src/Config.res.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
|
17
17
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
18
18
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
19
19
|
import * as ContractMapping from "./ContractMapping.res.mjs";
|
|
20
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
20
21
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
21
22
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
22
23
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
@@ -73,12 +74,6 @@ let publicConfigChainSchema = S$RescriptSchema.schema(s => ({
|
|
|
73
74
|
|
|
74
75
|
let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
|
|
75
76
|
discriminator: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
76
|
-
discriminatorByteLen: s.m(S$RescriptSchema.int),
|
|
77
|
-
accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
|
|
78
|
-
position: s.m(S$RescriptSchema.int),
|
|
79
|
-
values: s.m(S$RescriptSchema.array(S$RescriptSchema.string))
|
|
80
|
-
})))))),
|
|
81
|
-
isInner: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
|
|
82
77
|
accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.string))),
|
|
83
78
|
args: s.m(S$RescriptSchema.option(S$RescriptSchema.json(false)))
|
|
84
79
|
}));
|
|
@@ -226,6 +221,12 @@ function getFieldTypeAndSchema(prop, enumConfigsByName) {
|
|
|
226
221
|
S$RescriptSchema.bool
|
|
227
222
|
];
|
|
228
223
|
break;
|
|
224
|
+
case "bytes" :
|
|
225
|
+
match = [
|
|
226
|
+
"Bytea",
|
|
227
|
+
Utils.Schema.bytes
|
|
228
|
+
];
|
|
229
|
+
break;
|
|
229
230
|
case "date" :
|
|
230
231
|
match = [
|
|
231
232
|
"Date",
|
|
@@ -277,10 +278,20 @@ function getFieldTypeAndSchema(prop, enumConfigsByName) {
|
|
|
277
278
|
match = Stdlib_JsError.throwWithMessage("Unknown field type in entity config: " + typ);
|
|
278
279
|
}
|
|
279
280
|
let baseSchema = match[1];
|
|
280
|
-
let
|
|
281
|
-
let fieldSchema
|
|
281
|
+
let fieldType = match[0];
|
|
282
|
+
let fieldSchema;
|
|
283
|
+
let exit = 0;
|
|
284
|
+
if (typeof fieldType !== "object" && fieldType === "Bytea" && isArray) {
|
|
285
|
+
fieldSchema = Utils.Schema.bytesArray;
|
|
286
|
+
} else {
|
|
287
|
+
exit = 1;
|
|
288
|
+
}
|
|
289
|
+
if (exit === 1) {
|
|
290
|
+
fieldSchema = isArray ? S$RescriptSchema.array(baseSchema) : baseSchema;
|
|
291
|
+
}
|
|
292
|
+
let fieldSchema$1 = isNullable ? Utils.Schema.nullTolerant(fieldSchema) : fieldSchema;
|
|
282
293
|
return [
|
|
283
|
-
|
|
294
|
+
fieldType,
|
|
284
295
|
fieldSchema$1,
|
|
285
296
|
isNullable,
|
|
286
297
|
isArray,
|
|
@@ -390,7 +401,8 @@ let publicConfigSchema = S$RescriptSchema.schema(s => ({
|
|
|
390
401
|
fuel: s.m(S$RescriptSchema.option(publicConfigEcosystemSchema)),
|
|
391
402
|
svm: s.m(S$RescriptSchema.option(publicConfigEcosystemSchema)),
|
|
392
403
|
enums: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(S$RescriptSchema.array(S$RescriptSchema.string)))),
|
|
393
|
-
entities: s.m(S$RescriptSchema.option(S$RescriptSchema.array(entityJsonSchema)))
|
|
404
|
+
entities: s.m(S$RescriptSchema.option(S$RescriptSchema.array(entityJsonSchema))),
|
|
405
|
+
subgraph: s.m(S$RescriptSchema.option(S$RescriptSchema.json(false)))
|
|
394
406
|
}));
|
|
395
407
|
|
|
396
408
|
function contractMappingOf(chainConfigs) {
|
|
@@ -527,11 +539,7 @@ function fromPublic(publicConfigJson) {
|
|
|
527
539
|
) : addresses[0];
|
|
528
540
|
let s = eventItem.svm;
|
|
529
541
|
let svm = s !== undefined ? Primitive_option.valFromOption(s) : Stdlib_JsError.throwWithMessage(`SVM instruction ` + contractName + `.` + eventName + ` is missing the "svm" descriptor in internal config`);
|
|
530
|
-
|
|
531
|
-
position: af.position,
|
|
532
|
-
values: af.values
|
|
533
|
-
})));
|
|
534
|
-
return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, accountFilters, svm.isInner, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes);
|
|
542
|
+
return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes);
|
|
535
543
|
}
|
|
536
544
|
});
|
|
537
545
|
} else {
|
|
@@ -722,10 +730,19 @@ function fromPublic(publicConfigJson) {
|
|
|
722
730
|
isDev: Stdlib_Option.getOr(publicConfig.isDev, false),
|
|
723
731
|
userEntitiesByName: userEntitiesByName,
|
|
724
732
|
userEntities: userEntities,
|
|
725
|
-
allEnums: allEnums
|
|
733
|
+
allEnums: allEnums,
|
|
734
|
+
subgraph: publicConfig.subgraph
|
|
726
735
|
};
|
|
727
736
|
}
|
|
728
737
|
|
|
738
|
+
function isIsolatedMultichain(config) {
|
|
739
|
+
if (ChainMap.keys(config.chainMap).length > 1) {
|
|
740
|
+
return config.userEntities.every(entityConfig => !entityConfig.crossChain);
|
|
741
|
+
} else {
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
729
746
|
function normalizeUserAddress(config, address) {
|
|
730
747
|
let match = config.ecosystem.name;
|
|
731
748
|
switch (match) {
|
|
@@ -789,11 +806,15 @@ function getEventConfig(config, contractName, eventName, chainId) {
|
|
|
789
806
|
});
|
|
790
807
|
}
|
|
791
808
|
|
|
792
|
-
function shouldSaveHistory(config, isInReorgThreshold) {
|
|
809
|
+
function shouldSaveHistory(config, isInReorgThreshold, chainId) {
|
|
793
810
|
if (config.shouldSaveFullHistory) {
|
|
794
811
|
return true;
|
|
795
|
-
} else if (config.shouldRollbackOnReorg) {
|
|
796
|
-
|
|
812
|
+
} else if (config.shouldRollbackOnReorg && isInReorgThreshold) {
|
|
813
|
+
if (chainId !== undefined && isIsolatedMultichain(config)) {
|
|
814
|
+
return ChainMap.get(config.chainMap, Primitive_option.valFromOption(chainId)).maxReorgDepth > 0;
|
|
815
|
+
} else {
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
797
818
|
} else {
|
|
798
819
|
return false;
|
|
799
820
|
}
|
|
@@ -867,6 +888,7 @@ function stripSensitiveData(json) {
|
|
|
867
888
|
stripChains(cloned["evm"]);
|
|
868
889
|
stripChains(cloned["fuel"]);
|
|
869
890
|
stripChains(cloned["svm"]);
|
|
891
|
+
Utils.Dict.deleteInPlace(cloned, "subgraph");
|
|
870
892
|
}
|
|
871
893
|
return cloned;
|
|
872
894
|
}
|
|
@@ -1031,6 +1053,19 @@ function throwIfIncompatible(changedPaths, resetCommand, runCommand, hasClickhou
|
|
|
1031
1053
|
Stdlib_JsError.throwWithMessage(`The following config changes are incompatible with the existing indexer data:\n\n` + bullets + `\n\nPick one:\n 1. ` + padTo(option1, col) + `# resume indexing where it left off\n 2. ` + padTo(resetCommand, col) + `# delete all indexed data and start over` + option3);
|
|
1032
1054
|
}
|
|
1033
1055
|
|
|
1056
|
+
function throwIfResumeIncompatible(storedEnvioInfo, storedContractMapping, envioInfo, contractMapping, resetCommand, runCommand) {
|
|
1057
|
+
let changedPaths = storedEnvioInfo !== undefined ? diffPaths(storedEnvioInfo, envioInfo) : ["storage was initialized by an older envio version"];
|
|
1058
|
+
let changedPaths$1 = ContractMapping.isEqual(storedContractMapping, contractMapping) ? changedPaths : changedPaths.concat(["contracts"]);
|
|
1059
|
+
let hasClickhouse;
|
|
1060
|
+
if (typeof envioInfo === "object" && envioInfo !== null && !Array.isArray(envioInfo)) {
|
|
1061
|
+
let match = envioInfo["storage"];
|
|
1062
|
+
hasClickhouse = typeof match === "object" && match !== null && !Array.isArray(match) ? Primitive_object.equal(match["clickhouse"], true) : false;
|
|
1063
|
+
} else {
|
|
1064
|
+
hasClickhouse = false;
|
|
1065
|
+
}
|
|
1066
|
+
throwIfIncompatible(changedPaths$1, resetCommand, runCommand, hasClickhouse);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1034
1069
|
function load() {
|
|
1035
1070
|
let c = cached.contents;
|
|
1036
1071
|
if (c !== undefined) {
|
|
@@ -1075,6 +1110,7 @@ export {
|
|
|
1075
1110
|
publicConfigSchema,
|
|
1076
1111
|
contractMappingOf,
|
|
1077
1112
|
fromPublic,
|
|
1113
|
+
isIsolatedMultichain,
|
|
1078
1114
|
normalizeUserAddress,
|
|
1079
1115
|
normalizeSimulateAddress,
|
|
1080
1116
|
getEventConfig,
|
|
@@ -1087,6 +1123,7 @@ export {
|
|
|
1087
1123
|
canonicalJson,
|
|
1088
1124
|
diffPaths,
|
|
1089
1125
|
throwIfIncompatible,
|
|
1126
|
+
throwIfResumeIncompatible,
|
|
1090
1127
|
load,
|
|
1091
1128
|
getPgUserEntities,
|
|
1092
1129
|
}
|
package/src/Core.res
CHANGED
|
@@ -10,6 +10,7 @@ type svmHyperSyncClientCtor
|
|
|
10
10
|
type fuelHyperSyncClientCtor
|
|
11
11
|
type transactionStoreCtor
|
|
12
12
|
type blockStoreCtor
|
|
13
|
+
type clickHouseSinkCtor
|
|
13
14
|
type addressStoreCtor
|
|
14
15
|
// Test-only: a local HyperSync server, bound by MockHyperSyncServer in envio-tests.
|
|
15
16
|
type mockHyperSyncServerCtor
|
|
@@ -26,9 +27,18 @@ type fromUserApiResult = {
|
|
|
26
27
|
indexerCode: Null.t<string>,
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
type fromSubgraphOptions = {
|
|
31
|
+
name?: string,
|
|
32
|
+
env?: dict<string>,
|
|
33
|
+
files?: dict<string>,
|
|
34
|
+
root?: string,
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
type addon = {
|
|
30
38
|
getConfigJson: (~configPath: Null.t<string>, ~directory: Null.t<string>) => string,
|
|
39
|
+
fromSubgraph: (string, string, fromSubgraphOptions) => fromUserApiResult,
|
|
31
40
|
encodeIndexedTopic: (~abiType: string, ~value: unknown) => EvmTypes.Hex.t,
|
|
41
|
+
isSvmPubkey: (~value: string) => bool,
|
|
32
42
|
fromUserApi: (string, fromUserApiOptions) => fromUserApiResult,
|
|
33
43
|
runCli: (~args: array<string>, ~envioPackageDir: Null.t<string>) => promise<Null.t<string>>,
|
|
34
44
|
@as("EvmHyperSyncClient")
|
|
@@ -45,6 +55,8 @@ type addon = {
|
|
|
45
55
|
blockStore: blockStoreCtor,
|
|
46
56
|
@as("AddressStore")
|
|
47
57
|
addressStore: addressStoreCtor,
|
|
58
|
+
@as("ClickHouseSink")
|
|
59
|
+
clickHouseSink: clickHouseSinkCtor,
|
|
48
60
|
@as("MockHyperSyncServer")
|
|
49
61
|
mockHyperSyncServer: mockHyperSyncServerCtor,
|
|
50
62
|
encodeAddresses: (~ecosystem: string, ~addresses: array<Address.t>) => array<NodeJs.Buffer.t>,
|
|
@@ -257,6 +269,13 @@ let fromUserApi = (~schema=?, ~env=?, ~files=?, ~withIndexerTypes=false, yaml) =
|
|
|
257
269
|
)
|
|
258
270
|
}
|
|
259
271
|
|
|
272
|
+
/// Parses a subgraph project without touching the filesystem: subgraph.yaml and
|
|
273
|
+
/// its schema inline, ABI bodies through `files`.
|
|
274
|
+
let fromSubgraph = (~name=?, ~env=?, ~files=?, ~root=?, ~manifest, ~schema) => {
|
|
275
|
+
let addon = getAddon()
|
|
276
|
+
addon.fromSubgraph(manifest, schema, {?name, ?env, ?files, ?root})
|
|
277
|
+
}
|
|
278
|
+
|
|
260
279
|
let runCli = args => {
|
|
261
280
|
let addon = getAddon()
|
|
262
281
|
addon.runCli(~args, ~envioPackageDir=Null.make(envioPackageDir))
|
package/src/Core.res.mjs
CHANGED
|
@@ -195,6 +195,16 @@ function fromUserApi(schema, env, files, withIndexerTypesOpt, yaml) {
|
|
|
195
195
|
});
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
function fromSubgraph(name, env, files, root, manifest, schema) {
|
|
199
|
+
let addon = getAddon();
|
|
200
|
+
return addon.fromSubgraph(manifest, schema, {
|
|
201
|
+
name: name,
|
|
202
|
+
env: env,
|
|
203
|
+
files: files,
|
|
204
|
+
root: root
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
198
208
|
function runCli(args) {
|
|
199
209
|
let addon = getAddon();
|
|
200
210
|
return addon.runCli(args, envioPackageDir);
|
|
@@ -212,6 +222,7 @@ export {
|
|
|
212
222
|
getAddon,
|
|
213
223
|
getConfigJson,
|
|
214
224
|
fromUserApi,
|
|
225
|
+
fromSubgraph,
|
|
215
226
|
runCli,
|
|
216
227
|
}
|
|
217
228
|
/* envioPackageDir Not a pure module */
|
package/src/CrossChainState.res
CHANGED
|
@@ -72,31 +72,23 @@ let totalReadyCount = (crossChainState: t) => {
|
|
|
72
72
|
let nextItemIsNone = (crossChainState: t): bool =>
|
|
73
73
|
!(crossChainState.chainStates->Dict.valuesToArray->Array.some(ChainState.hasReadyItem))
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
// Each chain's safe checkpoint: the last one a reorg on that chain can no longer
|
|
76
|
+
// reach, or None while it has nothing safe yet. A chain that can't be rolled
|
|
77
|
+
// back (maxReorgDepth = 0) tracks none, and everything it has committed is safe.
|
|
78
|
+
let getSafeCheckpointIdByChain = (crossChainState: t, ~committedCheckpointId) =>
|
|
79
|
+
crossChainState.chainIds->Array.map(chainId => {
|
|
80
|
+
let cs = crossChainState->getChainState(chainId)
|
|
81
|
+
(
|
|
82
|
+
chainId,
|
|
83
|
+
switch cs->ChainState.safeCheckpointTracking {
|
|
84
|
+
| None => Some(committedCheckpointId)
|
|
85
|
+
| Some(tracking) =>
|
|
86
|
+
tracking->SafeCheckpointTracking.getSafeCheckpointId(
|
|
85
87
|
~sourceBlockNumber=cs->ChainState.knownHeight,
|
|
86
88
|
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
| _ => ()
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
switch result.contents {
|
|
96
|
-
| Some(id) if id > 0n => Some(id)
|
|
97
|
-
| _ => None // No safe checkpoint found
|
|
98
|
-
}
|
|
99
|
-
}
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
})
|
|
100
92
|
|
|
101
93
|
// --- Cross-chain transitions. ---
|
|
102
94
|
|
|
@@ -65,23 +65,15 @@ function nextItemIsNone(crossChainState) {
|
|
|
65
65
|
return !Object.values(crossChainState.chainStates).some(ChainState.hasReadyItem);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
let
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
result = safeCheckpointId;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
let id = result;
|
|
82
|
-
if (id !== undefined && id > 0n) {
|
|
83
|
-
return id;
|
|
84
|
-
}
|
|
68
|
+
function getSafeCheckpointIdByChain(crossChainState, committedCheckpointId) {
|
|
69
|
+
return crossChainState.chainIds.map(chainId => {
|
|
70
|
+
let cs = crossChainState.chainStates[chainId];
|
|
71
|
+
let tracking = ChainState.safeCheckpointTracking(cs);
|
|
72
|
+
return [
|
|
73
|
+
chainId,
|
|
74
|
+
tracking !== undefined ? SafeCheckpointTracking.getSafeCheckpointId(tracking, ChainState.knownHeight(cs)) : committedCheckpointId
|
|
75
|
+
];
|
|
76
|
+
});
|
|
85
77
|
}
|
|
86
78
|
|
|
87
79
|
function createBatch(crossChainState, processedCheckpointId, batchSizeTarget, isRollback) {
|
|
@@ -261,7 +253,7 @@ export {
|
|
|
261
253
|
isInReorgThreshold,
|
|
262
254
|
targetBufferSize,
|
|
263
255
|
nextItemIsNone,
|
|
264
|
-
|
|
256
|
+
getSafeCheckpointIdByChain,
|
|
265
257
|
createBatch,
|
|
266
258
|
enterReorgThreshold,
|
|
267
259
|
applyBatchProgress,
|
package/src/CrossChainState.resi
CHANGED
|
@@ -21,7 +21,10 @@ let targetBufferSize: t => int
|
|
|
21
21
|
|
|
22
22
|
// Derived (pure).
|
|
23
23
|
let nextItemIsNone: t => bool
|
|
24
|
-
let
|
|
24
|
+
let getSafeCheckpointIdByChain: (
|
|
25
|
+
t,
|
|
26
|
+
~committedCheckpointId: Internal.checkpointId,
|
|
27
|
+
) => array<(ChainId.t, option<Internal.checkpointId>)>
|
|
25
28
|
|
|
26
29
|
// Cross-chain transitions.
|
|
27
30
|
let createBatch: (
|
package/src/Ecosystem.res
CHANGED
|
@@ -10,6 +10,11 @@ type t = {
|
|
|
10
10
|
for SVM. Centralised here so adding a new ecosystem only requires a
|
|
11
11
|
new ecosystem record, not another switch in `Main.res`. */
|
|
12
12
|
onBlockMethodName: string,
|
|
13
|
+
/** What the ecosystem calls a contract and one of its events, for
|
|
14
|
+
registration errors that are shared across ecosystems — "contract" /
|
|
15
|
+
"event" on EVM and Fuel, "program" / "instruction" on SVM. */
|
|
16
|
+
contractNoun: string,
|
|
17
|
+
eventNoun: string,
|
|
13
18
|
/** Schema that unwraps the ecosystem-specific outer wrapper around the
|
|
14
19
|
user's `where`-returned filter (`block.number` on EVM, `block.height`
|
|
15
20
|
on Fuel, `slot` on SVM) and surfaces the raw inner `{_gte?, _lte?,
|
|
@@ -23,8 +28,8 @@ type t = {
|
|
|
23
28
|
`option<unknown>`. Separate from `onBlockFilterSchema` because event
|
|
24
29
|
block filters support only `_gte` (→ per-event `startBlock`) — `_lte`
|
|
25
30
|
and `_every` are rejected by the inner `eventBlockRangeSchema` in
|
|
26
|
-
`LogSelection.res`. SVM
|
|
27
|
-
schema always surfaces `None`. */
|
|
31
|
+
`LogSelection.res`. SVM parses its own `where` (including the
|
|
32
|
+
`block.slot` chunk), so its schema always surfaces `None`. */
|
|
28
33
|
onEventBlockFilterSchema: S.t<option<unknown>>,
|
|
29
34
|
/** Base logger injected at construction. Used to build per-item child
|
|
30
35
|
loggers (see `getItemLogger`). */
|
package/src/Envio.res
CHANGED
|
@@ -83,10 +83,12 @@ type svmInstruction = {
|
|
|
83
83
|
instructionName: string,
|
|
84
84
|
discriminator: string,
|
|
85
85
|
programId?: SvmTypes.Pubkey.t,
|
|
86
|
-
data?:
|
|
86
|
+
data?: Uint8Array.t,
|
|
87
87
|
path?: array<int>,
|
|
88
88
|
isInner?: bool,
|
|
89
|
-
args
|
|
89
|
+
// Decoded Borsh args; wide integers (u64/u128/i64/i128) are bigint, so the
|
|
90
|
+
// tree is not valid JSON.
|
|
91
|
+
args?: unknown,
|
|
90
92
|
accounts?: dict<svmInstructionAccount>,
|
|
91
93
|
accountArguments?: array<SvmTypes.Pubkey.t>,
|
|
92
94
|
transaction?: svmTransaction,
|
|
@@ -283,9 +285,9 @@ type svmSimulateItem = {
|
|
|
283
285
|
slot?: int,
|
|
284
286
|
path?: array<int>,
|
|
285
287
|
programId?: string,
|
|
286
|
-
data?:
|
|
288
|
+
data?: Uint8Array.t,
|
|
287
289
|
isInner?: bool,
|
|
288
|
-
args?:
|
|
290
|
+
args?: unknown,
|
|
289
291
|
accounts?: dict<{address: string}>,
|
|
290
292
|
accountArguments?: array<string>,
|
|
291
293
|
logs?: array<{kind?: string, message?: string}>,
|
|
@@ -317,9 +317,6 @@ let svmSelectionKinds = ["instruction", "transaction", "accountActivity", "block
|
|
|
317
317
|
// typo, and an unrecognised key would read as an empty selection — silently
|
|
318
318
|
// dropping every field `config.yaml` selected. Rejected here so the option is
|
|
319
319
|
// held to the same shape whether or not the project type-checks it.
|
|
320
|
-
let quotedJoin = (names: array<string>) =>
|
|
321
|
-
names->Array.map(name => `"${name}"`)->Array.joinUnsafe(", ")
|
|
322
|
-
|
|
323
320
|
let validateFieldsShapeOrThrow = (
|
|
324
321
|
fields: unknown,
|
|
325
322
|
~registration: string,
|
|
@@ -337,7 +334,7 @@ let validateFieldsShapeOrThrow = (
|
|
|
337
334
|
->Array.forEach(key =>
|
|
338
335
|
if !(validKeys->Array.includes(key)) {
|
|
339
336
|
JsError.throwWithMessage(
|
|
340
|
-
`Invalid "${key}" key in the fields option of ${registration}. Valid keys: ${quotedJoin(
|
|
337
|
+
`Invalid "${key}" key in the fields option of ${registration}. Valid keys: ${Utils.Array.quotedJoin(
|
|
341
338
|
validKeys,
|
|
342
339
|
)}.`,
|
|
343
340
|
)
|
|
@@ -373,7 +370,7 @@ let parseFieldsOrThrow = (
|
|
|
373
370
|
fields->Array.forEach(name => {
|
|
374
371
|
if !(valid->Utils.Set.has(name)) {
|
|
375
372
|
JsError.throwWithMessage(
|
|
376
|
-
`Invalid "${name}" field in the fields.${kind} option of ${registration}. Valid ${kind} fields: ${quotedJoin(
|
|
373
|
+
`Invalid "${name}" field in the fields.${kind} option of ${registration}. Valid ${kind} fields: ${Utils.Array.quotedJoin(
|
|
377
374
|
valid->Utils.Set.toArray,
|
|
378
375
|
)}.`,
|
|
379
376
|
)
|
|
@@ -577,7 +574,7 @@ let resolveSvmInlineFieldSelection = (
|
|
|
577
574
|
~contractName: string,
|
|
578
575
|
~eventName: string,
|
|
579
576
|
): Internal.fieldSelection => {
|
|
580
|
-
let registration = `the "${eventName}"
|
|
577
|
+
let registration = `the "${eventName}" instruction on program "${contractName}"`
|
|
581
578
|
validateFieldsShapeOrThrow(
|
|
582
579
|
fields,
|
|
583
580
|
~registration,
|
|
@@ -638,9 +635,6 @@ let buildSvmInstructionEventConfig = (
|
|
|
638
635
|
~instructionName: string,
|
|
639
636
|
~programId: SvmTypes.Pubkey.t,
|
|
640
637
|
~discriminator: option<string>,
|
|
641
|
-
~discriminatorByteLen: int,
|
|
642
|
-
~accountFilters: array<Internal.svmAccountFilterGroup>,
|
|
643
|
-
~isInner: option<bool>,
|
|
644
638
|
~accounts: array<string>=[],
|
|
645
639
|
~args: JSON.t=JSON.Null,
|
|
646
640
|
~definedTypes: JSON.t=JSON.Null,
|
|
@@ -667,38 +661,187 @@ let buildSvmInstructionEventConfig = (
|
|
|
667
661
|
simulateParamsSchema: paramsSchema,
|
|
668
662
|
programId,
|
|
669
663
|
discriminator,
|
|
670
|
-
discriminatorByteLen,
|
|
671
664
|
fieldSelection,
|
|
672
|
-
accountFilters,
|
|
673
|
-
isInner,
|
|
674
665
|
accounts,
|
|
675
666
|
args,
|
|
676
667
|
definedTypes,
|
|
677
668
|
}
|
|
678
669
|
}
|
|
679
670
|
|
|
680
|
-
//
|
|
681
|
-
|
|
671
|
+
// ============== SVM `where` ==============
|
|
672
|
+
|
|
673
|
+
type parsedSvmWhere = {
|
|
674
|
+
// Disjunctive normal form: outer array is OR of AND-groups. Empty means the
|
|
675
|
+
// registration accepts any accounts.
|
|
676
|
+
accountFilters: array<Internal.svmAccountFilterGroup>,
|
|
677
|
+
isInner: option<bool>,
|
|
678
|
+
startBlock: option<int>,
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
let validSvmWhereKeys = ["accounts", "isInner", "block"]
|
|
682
|
+
|
|
683
|
+
// `{slot: {_gte?}}`. Both levels are strict: the inner `eventBlockRangeSchema`
|
|
684
|
+
// rejects the `_lte` / `_every` that only `onSlot` supports, the outer rejects
|
|
685
|
+
// a wrapper key other than `slot`.
|
|
686
|
+
type svmBlockFilter = {slot?: LogSelection.eventBlockRange}
|
|
687
|
+
let svmBlockFilterSchema: S.t<svmBlockFilter> = S.object(s => {
|
|
688
|
+
slot: ?s.field("slot", S.option(LogSelection.eventBlockRangeSchema)),
|
|
689
|
+
})->S.strict
|
|
690
|
+
|
|
691
|
+
// The source query narrows accounts through `a0..a9`, so only an instruction's
|
|
692
|
+
// leading account slots can be filtered.
|
|
693
|
+
let filterableAccountCount = 10
|
|
694
|
+
|
|
695
|
+
// Resolve the `where` option of an `onInstruction` registration. Account names
|
|
696
|
+
// are resolved against the instruction's declared accounts here, so the
|
|
697
|
+
// registration carries positions and nothing downstream needs the names.
|
|
698
|
+
let resolveSvmWhereOrThrow = (
|
|
699
|
+
where: JSON.t,
|
|
700
|
+
~contractName: string,
|
|
701
|
+
~eventName: string,
|
|
702
|
+
~accountNames: array<string>,
|
|
703
|
+
): parsedSvmWhere => {
|
|
704
|
+
let invalid = message =>
|
|
705
|
+
JsError.throwWithMessage(
|
|
706
|
+
`Invalid where configuration for the "${eventName}" instruction on program "${contractName}". ${message}`,
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
let obj = switch where {
|
|
710
|
+
| Object(obj) => obj
|
|
711
|
+
| _ => invalid("Expected an object.")
|
|
712
|
+
}
|
|
713
|
+
obj->Utils.Dict.forEachWithKey((_, key) =>
|
|
714
|
+
if !(validSvmWhereKeys->Array.includes(key)) {
|
|
715
|
+
invalid(`Unknown field "${key}". Valid fields: ${Utils.Array.quotedJoin(validSvmWhereKeys)}.`)
|
|
716
|
+
}
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
let isInner = switch obj->Dict.get("isInner") {
|
|
720
|
+
| None => None
|
|
721
|
+
| Some(Boolean(isInner)) => Some(isInner)
|
|
722
|
+
| Some(_) => invalid(`The "isInner" filter must be a boolean.`)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
let parseGroup = (group: dict<JSON.t>): Internal.svmAccountFilterGroup =>
|
|
726
|
+
group
|
|
727
|
+
->Dict.toArray
|
|
728
|
+
->Array.map(((name, value)) => {
|
|
729
|
+
let position = switch accountNames->Array.indexOf(name) {
|
|
730
|
+
| -1 if accountNames->Utils.Array.isEmpty =>
|
|
731
|
+
invalid(
|
|
732
|
+
"The instruction has no named accounts to filter on. Add `accounts` and `args` to it in config.yaml, or attach an IDL.",
|
|
733
|
+
)
|
|
734
|
+
| -1 =>
|
|
735
|
+
invalid(
|
|
736
|
+
`The instruction has no account named "${name}" to filter on. Named accounts: ${Utils.Array.quotedJoin(
|
|
737
|
+
accountNames,
|
|
738
|
+
)}.`,
|
|
739
|
+
)
|
|
740
|
+
| position if position >= filterableAccountCount =>
|
|
741
|
+
invalid(
|
|
742
|
+
`Account "${name}" is at position ${position->Int.toString}, and only the first ${filterableAccountCount->Int.toString} accounts of an instruction can be filtered.`,
|
|
743
|
+
)
|
|
744
|
+
| position => position
|
|
745
|
+
}
|
|
746
|
+
let values = value->normalizeOrThrow
|
|
747
|
+
if values->Utils.Array.isEmpty {
|
|
748
|
+
invalid(`The "${name}" filter must list at least one pubkey.`)
|
|
749
|
+
}
|
|
750
|
+
{
|
|
751
|
+
Internal.position,
|
|
752
|
+
values: values->Array.map(value =>
|
|
753
|
+
switch value {
|
|
754
|
+
| JSON.String(pubkey) if Core.getAddon().isSvmPubkey(~value=pubkey) =>
|
|
755
|
+
pubkey->SvmTypes.Pubkey.fromStringUnsafe
|
|
756
|
+
| JSON.String(pubkey) =>
|
|
757
|
+
invalid(`The "${name}" filter value "${pubkey}" is not a base58 SVM pubkey.`)
|
|
758
|
+
| _ => invalid(`The "${name}" filter must list base58 pubkeys as strings.`)
|
|
759
|
+
}
|
|
760
|
+
),
|
|
761
|
+
}
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
// An empty AND-group matches every instruction, so it makes the whole OR
|
|
765
|
+
// vacuous. Normalized away here so the source builds one unfiltered
|
|
766
|
+
// selection instead of a match-all selection beside the narrower ones.
|
|
767
|
+
let groups = switch obj->Dict.get("accounts") {
|
|
768
|
+
| None => []
|
|
769
|
+
| Some(Object(group)) => [parseGroup(group)]
|
|
770
|
+
| Some(Array(entries)) =>
|
|
771
|
+
entries->Array.map(entry =>
|
|
772
|
+
switch entry {
|
|
773
|
+
| Object(group) => parseGroup(group)
|
|
774
|
+
| _ => invalid(`Each entry in "accounts" must be an object.`)
|
|
775
|
+
}
|
|
776
|
+
)
|
|
777
|
+
| Some(_) => invalid(`Expected "accounts" to be an object or an array of objects.`)
|
|
778
|
+
}
|
|
779
|
+
let accountFilters = groups->Array.some(Utils.Array.isEmpty) ? [] : groups
|
|
780
|
+
|
|
781
|
+
let startBlock = switch obj->Dict.get("block") {
|
|
782
|
+
| None => None
|
|
783
|
+
| Some(block) =>
|
|
784
|
+
let filter = try block->S.parseOrThrow(svmBlockFilterSchema) catch {
|
|
785
|
+
| S.Raised(exn) =>
|
|
786
|
+
invalid(
|
|
787
|
+
`The "block" filter is invalid: ${exn
|
|
788
|
+
->Utils.prettifyExn
|
|
789
|
+
->(
|
|
790
|
+
Utils.magic: exn => string
|
|
791
|
+
)}. Only \`_gte\` is supported on instruction filters — use \`indexer.onSlot\` for \`_lte\` or \`_every\`.`,
|
|
792
|
+
)
|
|
793
|
+
}
|
|
794
|
+
switch filter.slot {
|
|
795
|
+
| Some({_gte}) => _gte
|
|
796
|
+
| None => None
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
{accountFilters, isInner, startBlock}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Enrich an SVM definition into a registration: the handler binding plus the
|
|
804
|
+
// `where`-derived account/isInner filters and startBlock override.
|
|
682
805
|
let buildSvmOnEventRegistration = (
|
|
683
806
|
~eventConfig: Internal.svmInstructionEventConfig,
|
|
684
807
|
~isWildcard: bool,
|
|
685
808
|
~handler: option<Internal.handler>,
|
|
686
809
|
~contractRegister: option<Internal.contractRegister>,
|
|
810
|
+
~where: option<JSON.t>,
|
|
687
811
|
~fieldSelection: option<Internal.fieldSelection>=?,
|
|
688
812
|
~startBlock: option<int>=?,
|
|
689
813
|
): Internal.svmOnEventRegistration => {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
814
|
+
let resolvedWhere = switch where {
|
|
815
|
+
| None => {accountFilters: [], isInner: None, startBlock: None}
|
|
816
|
+
| Some(where) =>
|
|
817
|
+
where->resolveSvmWhereOrThrow(
|
|
818
|
+
~contractName=eventConfig.contractName,
|
|
819
|
+
~eventName=eventConfig.name,
|
|
820
|
+
~accountNames=eventConfig.accounts,
|
|
821
|
+
)
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
{
|
|
825
|
+
index: -1,
|
|
826
|
+
eventConfig: (eventConfig :> Internal.eventConfig),
|
|
827
|
+
handler,
|
|
828
|
+
contractRegister,
|
|
829
|
+
isWildcard,
|
|
830
|
+
filterByAddresses: false,
|
|
831
|
+
dependsOnAddresses: Internal.dependsOnAddresses(~isWildcard, ~filterByAddresses=false),
|
|
832
|
+
// `where.block.slot._gte` overrides the program-level startBlock when
|
|
833
|
+
// present, mirroring EVM's `where.block.number._gte`.
|
|
834
|
+
startBlock: switch resolvedWhere.startBlock {
|
|
835
|
+
| Some(_) as sb => sb
|
|
836
|
+
| None => startBlock
|
|
837
|
+
},
|
|
838
|
+
fieldSelection: switch fieldSelection {
|
|
839
|
+
| Some(fieldSelection) => fieldSelection
|
|
840
|
+
| None => eventConfig.fieldSelection
|
|
841
|
+
},
|
|
842
|
+
accountFilters: resolvedWhere.accountFilters,
|
|
843
|
+
isInner: resolvedWhere.isInner,
|
|
844
|
+
}
|
|
702
845
|
}
|
|
703
846
|
|
|
704
847
|
// ============== Build Fuel event config ==============
|