envio 3.10.0 → 3.11.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/evm.schema.json +21 -4
- package/fuel.schema.json +21 -4
- package/index.d.ts +7 -6
- package/package.json +6 -6
- package/src/ChainState.res +9 -73
- package/src/ChainState.res.mjs +6 -58
- package/src/Config.res +73 -18
- package/src/Config.res.mjs +69 -16
- package/src/EventConfigBuilder.res +14 -10
- package/src/EventConfigBuilder.res.mjs +6 -4
- package/src/HandlerRegister.res +12 -11
- package/src/HandlerRegister.res.mjs +6 -5
- package/src/Internal.res +20 -4
- package/src/Internal.res.mjs +9 -0
- package/src/Main.res +8 -1
- package/src/Main.res.mjs +3 -3
- package/src/Persistence.res +11 -1
- package/src/Persistence.res.mjs +6 -2
- package/src/PgStorage.res +1 -1
- package/src/PgStorage.res.mjs +1 -1
- package/src/SimulateItems.res +13 -6
- package/src/SimulateItems.res.mjs +7 -6
- package/src/TestIndexer.res +6 -5
- package/src/TestIndexer.res.mjs +5 -4
- package/src/db/InternalTable.res +1 -1
- package/src/db/InternalTable.res.mjs +2 -1
- package/src/sources/ChainSources.res +72 -0
- package/src/sources/ChainSources.res.mjs +55 -0
- package/src/sources/EvmHyperSyncSource.res +10 -22
- package/src/sources/EvmHyperSyncSource.res.mjs +9 -41
- package/src/sources/FuelHyperSyncSource.res +10 -18
- package/src/sources/FuelHyperSyncSource.res.mjs +8 -38
- package/src/sources/HyperSync.res +31 -0
- package/src/sources/HyperSync.res.mjs +31 -0
- package/src/sources/HyperSync.resi +16 -0
- package/src/sources/StartBlockResolver.res +173 -0
- package/src/sources/StartBlockResolver.res.mjs +134 -0
- package/src/sources/Svm.res +0 -51
- package/src/sources/Svm.res.mjs +0 -46
- package/src/sources/SvmHyperSyncSource.res +20 -9
- package/src/sources/SvmHyperSyncSource.res.mjs +34 -12
- package/svm.schema.json +80 -66
package/src/Config.res.mjs
CHANGED
|
@@ -60,26 +60,72 @@ let chainContractSchema = S$RescriptSchema.schema(s => ({
|
|
|
60
60
|
startBlock: s.m(S$RescriptSchema.option(S$RescriptSchema.int))
|
|
61
61
|
}));
|
|
62
62
|
|
|
63
|
+
function startBlockOrThrow(chain) {
|
|
64
|
+
let startBlock = chain.startBlock;
|
|
65
|
+
if (startBlock === "latest") {
|
|
66
|
+
return Stdlib_JsError.throwWithMessage(`Chain ` + ChainId.toString(chain.id) + `: the "latest" start block was read before it was resolved. This is a bug in envio - please report it.`);
|
|
67
|
+
} else {
|
|
68
|
+
return startBlock;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function startBlockOrZero(chain) {
|
|
73
|
+
let startBlock = chain.startBlock;
|
|
74
|
+
if (startBlock === "latest") {
|
|
75
|
+
return 0;
|
|
76
|
+
} else {
|
|
77
|
+
return startBlock;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let startBlockSchema = S$RescriptSchema.union([
|
|
82
|
+
S$RescriptSchema.shape(S$RescriptSchema.int, n => (n)),
|
|
83
|
+
S$RescriptSchema.shape(S$RescriptSchema.literal("latest"), param => "latest")
|
|
84
|
+
]);
|
|
85
|
+
|
|
63
86
|
let publicConfigChainSchema = S$RescriptSchema.schema(s => ({
|
|
64
87
|
id: s.m(ChainId.schema),
|
|
65
|
-
startBlock: s.m(
|
|
88
|
+
startBlock: s.m(startBlockSchema),
|
|
66
89
|
endBlock: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
|
|
67
90
|
maxReorgDepth: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
|
|
68
91
|
blockLag: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
|
|
69
92
|
hypersync: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
70
93
|
rpcs: s.m(S$RescriptSchema.option(S$RescriptSchema.array(rpcConfigSchema))),
|
|
71
|
-
rpc: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
72
94
|
contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(chainContractSchema)))
|
|
73
95
|
}));
|
|
74
96
|
|
|
97
|
+
let svmAccountSlotSchema = S$RescriptSchema.schema(s => ({
|
|
98
|
+
name: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
99
|
+
optional: s.m(S$RescriptSchema.option(S$RescriptSchema.bool))
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
function svmAccountSlotFromItem(slot) {
|
|
103
|
+
let match = slot.name;
|
|
104
|
+
let match$1 = slot.optional;
|
|
105
|
+
if (match !== undefined) {
|
|
106
|
+
if (match$1 !== undefined && match$1) {
|
|
107
|
+
return {
|
|
108
|
+
TAG: "Optional",
|
|
109
|
+
_0: match
|
|
110
|
+
};
|
|
111
|
+
} else {
|
|
112
|
+
return {
|
|
113
|
+
TAG: "Required",
|
|
114
|
+
_0: match
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
return "Unnamed";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
75
122
|
let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
|
|
76
123
|
discriminator: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
77
|
-
accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(
|
|
124
|
+
accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(svmAccountSlotSchema))),
|
|
78
125
|
args: s.m(S$RescriptSchema.option(S$RescriptSchema.json(false)))
|
|
79
126
|
}));
|
|
80
127
|
|
|
81
128
|
let svmAbiSchema = S$RescriptSchema.schema(s => ({
|
|
82
|
-
programId: s.m(S$RescriptSchema.string),
|
|
83
129
|
definedTypes: s.m(S$RescriptSchema.json(false)),
|
|
84
130
|
source: s.m(S$RescriptSchema.string)
|
|
85
131
|
}));
|
|
@@ -538,7 +584,7 @@ function fromPublic(publicConfigJson) {
|
|
|
538
584
|
) : addresses[0];
|
|
539
585
|
let s = eventItem.svm;
|
|
540
586
|
let svm = s !== undefined ? Primitive_option.valFromOption(s) : Stdlib_JsError.throwWithMessage(`SVM instruction ` + contractName + `.` + eventName + ` is missing the "svm" descriptor in internal config`);
|
|
541
|
-
return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes);
|
|
587
|
+
return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, Stdlib_Option.getOr(svm.accounts, []).map(svmAccountSlotFromItem), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes);
|
|
542
588
|
}
|
|
543
589
|
});
|
|
544
590
|
} else {
|
|
@@ -572,7 +618,15 @@ function fromPublic(publicConfigJson) {
|
|
|
572
618
|
let publicChainConfig = publicChainsConfig[chainName];
|
|
573
619
|
let chainId = publicChainConfig.id;
|
|
574
620
|
let chainContracts = Stdlib_Option.getOr(publicChainConfig.contracts, {});
|
|
575
|
-
let contracts = Object.entries(contractDataByName).
|
|
621
|
+
let contracts = Object.entries(contractDataByName).filter(param => {
|
|
622
|
+
switch (ecosystemName) {
|
|
623
|
+
case "evm" :
|
|
624
|
+
case "fuel" :
|
|
625
|
+
return true;
|
|
626
|
+
case "svm" :
|
|
627
|
+
return Stdlib_Option.isSome(chainContracts[param[0]]);
|
|
628
|
+
}
|
|
629
|
+
}).map(param => {
|
|
576
630
|
let contractData = param[1];
|
|
577
631
|
let capitalizedName = param[0];
|
|
578
632
|
let chainContract = chainContracts[capitalizedName];
|
|
@@ -644,15 +698,10 @@ function fromPublic(publicConfigJson) {
|
|
|
644
698
|
break;
|
|
645
699
|
case "svm" :
|
|
646
700
|
let hypersync$1 = publicChainConfig.hypersync;
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
sourceConfig = {
|
|
652
|
-
TAG: "SvmSourceConfig",
|
|
653
|
-
hypersync: hypersync$1,
|
|
654
|
-
rpc: rpc
|
|
655
|
-
};
|
|
701
|
+
sourceConfig = hypersync$1 !== undefined ? ({
|
|
702
|
+
TAG: "SvmSourceConfig",
|
|
703
|
+
hypersync: hypersync$1
|
|
704
|
+
}) : Stdlib_JsError.throwWithMessage(`Chain ` + chainName + ` is missing hypersync endpoint in config`);
|
|
656
705
|
break;
|
|
657
706
|
}
|
|
658
707
|
let tmp;
|
|
@@ -877,7 +926,6 @@ function stripSensitiveData(json) {
|
|
|
877
926
|
return;
|
|
878
927
|
}
|
|
879
928
|
Utils.Dict.deleteInPlace(chainJson, "rpcs");
|
|
880
|
-
Utils.Dict.deleteInPlace(chainJson, "rpc");
|
|
881
929
|
Utils.Dict.deleteInPlace(chainJson, "hypersync");
|
|
882
930
|
});
|
|
883
931
|
};
|
|
@@ -1083,7 +1131,12 @@ export {
|
|
|
1083
1131
|
rpcSourceForSchema,
|
|
1084
1132
|
rpcConfigSchema,
|
|
1085
1133
|
chainContractSchema,
|
|
1134
|
+
startBlockOrThrow,
|
|
1135
|
+
startBlockOrZero,
|
|
1136
|
+
startBlockSchema,
|
|
1086
1137
|
publicConfigChainSchema,
|
|
1138
|
+
svmAccountSlotSchema,
|
|
1139
|
+
svmAccountSlotFromItem,
|
|
1087
1140
|
svmEventDescriptorSchema,
|
|
1088
1141
|
svmAbiSchema,
|
|
1089
1142
|
contractEventItemSchema,
|
|
@@ -635,7 +635,7 @@ let buildSvmInstructionEventConfig = (
|
|
|
635
635
|
~instructionName: string,
|
|
636
636
|
~programId: SvmTypes.Pubkey.t,
|
|
637
637
|
~discriminator: option<string>,
|
|
638
|
-
~accounts: array<
|
|
638
|
+
~accounts: array<Internal.svmAccountSlot>=[],
|
|
639
639
|
~args: JSON.t=JSON.Null,
|
|
640
640
|
~definedTypes: JSON.t=JSON.Null,
|
|
641
641
|
): Internal.svmInstructionEventConfig => {
|
|
@@ -699,7 +699,7 @@ let resolveSvmWhereOrThrow = (
|
|
|
699
699
|
where: JSON.t,
|
|
700
700
|
~contractName: string,
|
|
701
701
|
~eventName: string,
|
|
702
|
-
~
|
|
702
|
+
~accounts: array<Internal.svmAccountSlot>,
|
|
703
703
|
): parsedSvmWhere => {
|
|
704
704
|
let invalid = message =>
|
|
705
705
|
JsError.throwWithMessage(
|
|
@@ -722,26 +722,30 @@ let resolveSvmWhereOrThrow = (
|
|
|
722
722
|
| Some(_) => invalid(`The "isInner" filter must be a boolean.`)
|
|
723
723
|
}
|
|
724
724
|
|
|
725
|
+
let namedAccounts = accounts->Array.filterMap(Internal.svmAccountSlotName)
|
|
726
|
+
|
|
725
727
|
let parseGroup = (group: dict<JSON.t>): Internal.svmAccountFilterGroup =>
|
|
726
728
|
group
|
|
727
729
|
->Dict.toArray
|
|
728
730
|
->Array.map(((name, value)) => {
|
|
729
|
-
let position = switch
|
|
730
|
-
|
|
731
|
+
let position = switch accounts->Array.findIndexOpt(slot =>
|
|
732
|
+
slot->Internal.svmAccountSlotName == Some(name)
|
|
733
|
+
) {
|
|
734
|
+
| None if namedAccounts->Utils.Array.isEmpty =>
|
|
731
735
|
invalid(
|
|
732
|
-
"The instruction has no named accounts to filter on. Add `accounts`
|
|
736
|
+
"The instruction has no named accounts to filter on. Add `accounts` to it in config.yaml, or attach an IDL.",
|
|
733
737
|
)
|
|
734
|
-
|
|
|
738
|
+
| None =>
|
|
735
739
|
invalid(
|
|
736
740
|
`The instruction has no account named "${name}" to filter on. Named accounts: ${Utils.Array.quotedJoin(
|
|
737
|
-
|
|
741
|
+
namedAccounts,
|
|
738
742
|
)}.`,
|
|
739
743
|
)
|
|
740
|
-
| position if position >= filterableAccountCount =>
|
|
744
|
+
| Some(position) if position >= filterableAccountCount =>
|
|
741
745
|
invalid(
|
|
742
746
|
`Account "${name}" is at position ${position->Int.toString}, and only the first ${filterableAccountCount->Int.toString} accounts of an instruction can be filtered.`,
|
|
743
747
|
)
|
|
744
|
-
| position => position
|
|
748
|
+
| Some(position) => position
|
|
745
749
|
}
|
|
746
750
|
let values = value->normalizeOrThrow
|
|
747
751
|
if values->Utils.Array.isEmpty {
|
|
@@ -817,7 +821,7 @@ let buildSvmOnEventRegistration = (
|
|
|
817
821
|
where->resolveSvmWhereOrThrow(
|
|
818
822
|
~contractName=eventConfig.contractName,
|
|
819
823
|
~eventName=eventConfig.name,
|
|
820
|
-
~
|
|
824
|
+
~accounts=eventConfig.accounts,
|
|
821
825
|
)
|
|
822
826
|
}
|
|
823
827
|
|
|
@@ -12,6 +12,7 @@ import * as LogSelection from "./LogSelection.res.mjs";
|
|
|
12
12
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
13
13
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
14
14
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
15
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
15
16
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
16
17
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
17
18
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
@@ -464,7 +465,7 @@ let svmBlockFilterSchema = S$RescriptSchema.strict(S$RescriptSchema.object(s =>
|
|
|
464
465
|
slot: s.f("slot", S$RescriptSchema.option(LogSelection.eventBlockRangeSchema))
|
|
465
466
|
})));
|
|
466
467
|
|
|
467
|
-
function resolveSvmWhereOrThrow(where, contractName, eventName,
|
|
468
|
+
function resolveSvmWhereOrThrow(where, contractName, eventName, accounts) {
|
|
468
469
|
let invalid = message => Stdlib_JsError.throwWithMessage(`Invalid where configuration for the "` + eventName + `" instruction on program "` + contractName + `". ` + message);
|
|
469
470
|
let obj;
|
|
470
471
|
obj = typeof where === "object" && where !== null && !Array.isArray(where) ? where : invalid("Expected an object.");
|
|
@@ -477,13 +478,14 @@ function resolveSvmWhereOrThrow(where, contractName, eventName, accountNames) {
|
|
|
477
478
|
let isInner = match !== undefined ? (
|
|
478
479
|
typeof match === "boolean" ? match : invalid(`The "isInner" filter must be a boolean.`)
|
|
479
480
|
) : undefined;
|
|
481
|
+
let namedAccounts = Stdlib_Array.filterMap(accounts, Internal.svmAccountSlotName);
|
|
480
482
|
let parseGroup = group => Object.entries(group).map(param => {
|
|
481
483
|
let name = param[0];
|
|
482
|
-
let position =
|
|
483
|
-
let position$1 = position !==
|
|
484
|
+
let position = Stdlib_Array.findIndexOpt(accounts, slot => Primitive_object.equal(Internal.svmAccountSlotName(slot), name));
|
|
485
|
+
let position$1 = position !== undefined ? (
|
|
484
486
|
position >= 10 ? invalid(`Account "` + name + `" is at position ` + position.toString() + `, and only the first ` + (10).toString() + ` accounts of an instruction can be filtered.`) : position
|
|
485
487
|
) : (
|
|
486
|
-
Utils.$$Array.isEmpty(
|
|
488
|
+
Utils.$$Array.isEmpty(namedAccounts) ? invalid("The instruction has no named accounts to filter on. Add `accounts` to it in config.yaml, or attach an IDL.") : invalid(`The instruction has no account named "` + name + `" to filter on. Named accounts: ` + Utils.$$Array.quotedJoin(namedAccounts) + `.`)
|
|
487
489
|
);
|
|
488
490
|
let values = normalizeOrThrow(param[1]);
|
|
489
491
|
if (Utils.$$Array.isEmpty(values)) {
|
package/src/HandlerRegister.res
CHANGED
|
@@ -312,13 +312,7 @@ let addOnEventRegistration = (
|
|
|
312
312
|
),
|
|
313
313
|
)
|
|
314
314
|
| Svm =>
|
|
315
|
-
Some(
|
|
316
|
-
EventConfigBuilder.resolveSvmInlineFieldSelection(
|
|
317
|
-
fields,
|
|
318
|
-
~contractName,
|
|
319
|
-
~eventName,
|
|
320
|
-
),
|
|
321
|
-
)
|
|
315
|
+
Some(EventConfigBuilder.resolveSvmInlineFieldSelection(fields, ~contractName, ~eventName))
|
|
322
316
|
| Fuel =>
|
|
323
317
|
JsError.throwWithMessage(
|
|
324
318
|
`The fields option of the "${eventName}" event registration on contract "${contractName}" is not supported on Fuel. Select the fields in your config instead.`,
|
|
@@ -340,13 +334,16 @@ let addOnEventRegistration = (
|
|
|
340
334
|
| (Svm, Some(fieldSelection)) if fieldSelection.instructionFields->Utils.Set.has("args") =>
|
|
341
335
|
let svmEventConfig =
|
|
342
336
|
eventConfig->(Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig)
|
|
337
|
+
// An empty layout is still a layout: it decodes to `{}` and filters
|
|
338
|
+
// out the calls that carry a payload. Only an absent one has nothing
|
|
339
|
+
// to decode.
|
|
343
340
|
let declaresArgs = switch svmEventConfig.args {
|
|
344
|
-
| JSON.Array(
|
|
341
|
+
| JSON.Array(_) => true
|
|
345
342
|
| _ => false
|
|
346
343
|
}
|
|
347
344
|
if !declaresArgs {
|
|
348
345
|
JsError.throwWithMessage(
|
|
349
|
-
`Invalid "args" field in the fields.instruction option of the "${eventName}" instruction on program "${contractName}". The instruction
|
|
346
|
+
`Invalid "args" field in the fields.instruction option of the "${eventName}" instruction on program "${contractName}". The instruction attaches no args layout in config.yaml, so there is nothing to decode. Remove "args" from the selection, or give the instruction an \`args\` layout — \`args: []\` if it takes none.`,
|
|
350
347
|
)
|
|
351
348
|
}
|
|
352
349
|
| _ => ()
|
|
@@ -800,9 +797,13 @@ let registerOnBlock = (
|
|
|
800
797
|
|
|
801
798
|
if shouldRegister {
|
|
802
799
|
matchedAny := true
|
|
803
|
-
|
|
800
|
+
// Off the same object the predicate above was handed, not off
|
|
801
|
+
// `chainConfig`: that one still says whatever config.yaml said, and for
|
|
802
|
+
// `start_block: latest` the resolved head only lives in persisted state.
|
|
803
|
+
let chainStartBlock = (chainObj->(Utils.magic: unknown => {"startBlock": int}))["startBlock"]
|
|
804
|
+
if range._gte->Option.getOr(chainStartBlock) < chainStartBlock {
|
|
804
805
|
JsError.throwWithMessage(
|
|
805
|
-
`The start block for onBlock handler "${name}" is less than the chain start block (${
|
|
806
|
+
`The start block for onBlock handler "${name}" is less than the chain start block (${chainStartBlock->Int.toString}). This is not supported yet.`,
|
|
806
807
|
)
|
|
807
808
|
}
|
|
808
809
|
switch chainConfig.endBlock {
|
|
@@ -238,11 +238,11 @@ function addOnEventRegistration(registration, contractName, eventName, handler,
|
|
|
238
238
|
break;
|
|
239
239
|
case "svm" :
|
|
240
240
|
if (fieldSelection !== undefined && fieldSelection.instructionFields.has("args")) {
|
|
241
|
-
let
|
|
241
|
+
let match$1 = eventConfig.args;
|
|
242
242
|
let declaresArgs;
|
|
243
|
-
declaresArgs = Array.isArray(
|
|
243
|
+
declaresArgs = Array.isArray(match$1);
|
|
244
244
|
if (!declaresArgs) {
|
|
245
|
-
Stdlib_JsError.throwWithMessage(`Invalid "args" field in the fields.instruction option of the "` + eventName + `" instruction on program "` + contractName + `". The instruction
|
|
245
|
+
Stdlib_JsError.throwWithMessage(`Invalid "args" field in the fields.instruction option of the "` + eventName + `" instruction on program "` + contractName + `". The instruction attaches no args layout in config.yaml, so there is nothing to decode. Remove "args" from the selection, or give the instruction an \`args\` layout — \`args: []\` if it takes none.`);
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
break;
|
|
@@ -492,8 +492,9 @@ function registerOnBlock(name, where, handler, getChainsObject) {
|
|
|
492
492
|
}
|
|
493
493
|
let range = match[1];
|
|
494
494
|
matchedAny.contents = true;
|
|
495
|
-
|
|
496
|
-
|
|
495
|
+
let chainStartBlock = chainObj.startBlock;
|
|
496
|
+
if (Stdlib_Option.getOr(range._gte, chainStartBlock) < chainStartBlock) {
|
|
497
|
+
Stdlib_JsError.throwWithMessage(`The start block for onBlock handler "` + name + `" is less than the chain start block (` + chainStartBlock.toString() + `). This is not supported yet.`);
|
|
497
498
|
}
|
|
498
499
|
let chainEndBlock = chainConfig.endBlock;
|
|
499
500
|
if (chainEndBlock !== undefined && Stdlib_Option.getOr(range._lte, chainEndBlock) > chainEndBlock) {
|
package/src/Internal.res
CHANGED
|
@@ -567,17 +567,33 @@ type svmAccountFilter = {
|
|
|
567
567
|
/** AND-group: every entry must match the same instruction. */
|
|
568
568
|
type svmAccountFilterGroup = array<svmAccountFilter>
|
|
569
569
|
|
|
570
|
+
/** One positional account slot of an instruction. An `Optional` slot is absent
|
|
571
|
+
from the payload when the call carries no such slot, or fills it with the id of
|
|
572
|
+
the program being invoked. `Unnamed` holds a position and surfaces nothing. */
|
|
573
|
+
type svmAccountSlot =
|
|
574
|
+
| Unnamed
|
|
575
|
+
| Required(string)
|
|
576
|
+
| Optional(string)
|
|
577
|
+
|
|
578
|
+
let svmAccountSlotName = slot =>
|
|
579
|
+
switch slot {
|
|
580
|
+
| Unnamed => None
|
|
581
|
+
| Required(name) | Optional(name) => Some(name)
|
|
582
|
+
}
|
|
583
|
+
|
|
570
584
|
type svmInstructionEventConfig = {
|
|
571
585
|
...eventConfig,
|
|
572
586
|
/** Base58 Solana program id this instruction belongs to. */
|
|
573
587
|
programId: SvmTypes.Pubkey.t,
|
|
574
588
|
/** Hex-encoded discriminator. `None` matches every instruction in the program. */
|
|
575
589
|
discriminator: option<string>,
|
|
576
|
-
/** Positional account
|
|
577
|
-
|
|
578
|
-
accounts: array<
|
|
590
|
+
/** Positional account slots in declared order. `[]` means no schema is
|
|
591
|
+
attached for this instruction. */
|
|
592
|
+
accounts: array<svmAccountSlot>,
|
|
579
593
|
/** Borsh args layout as `Vec<ArgDef>` JSON (see `human_config::svm::ArgDef`
|
|
580
|
-
on the Rust side). `JSON.Null`
|
|
594
|
+
on the Rust side). `JSON.Null` attaches no decoder, so every matched call is
|
|
595
|
+
delivered with its payload raw. An array attaches one, `[]` included: a call
|
|
596
|
+
whose data the layout rejects is skipped. */
|
|
581
597
|
args: JSON.t,
|
|
582
598
|
/** Program-level nominal-type registry (`BTreeMap<String, ArgType>` JSON).
|
|
583
599
|
Duplicated on every event of the same program — the runtime dedups by
|
package/src/Internal.res.mjs
CHANGED
|
@@ -174,6 +174,14 @@ function dependsOnAddresses(isWildcard, filterByAddresses) {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
function svmAccountSlotName(slot) {
|
|
178
|
+
if (typeof slot !== "object") {
|
|
179
|
+
return;
|
|
180
|
+
} else {
|
|
181
|
+
return slot._0;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
177
185
|
function getItemChainId(item) {
|
|
178
186
|
if (item.kind === 0) {
|
|
179
187
|
return item.chainId;
|
|
@@ -334,6 +342,7 @@ export {
|
|
|
334
342
|
unionFields,
|
|
335
343
|
unionFieldSelection,
|
|
336
344
|
dependsOnAddresses,
|
|
345
|
+
svmAccountSlotName,
|
|
337
346
|
getItemChainId,
|
|
338
347
|
fuelSupplyParamsSchema,
|
|
339
348
|
fuelTransferParamsSchema,
|
package/src/Main.res
CHANGED
|
@@ -126,7 +126,9 @@ let buildChainsObject = (~config: Config.t) => {
|
|
|
126
126
|
get: () => {
|
|
127
127
|
switch getInitialChainState(~chainId=chainConfig.id) {
|
|
128
128
|
| Some(chainState) => chainState.startBlock
|
|
129
|
-
|
|
129
|
+
// Only before persistence is ready, which in a real run is before any
|
|
130
|
+
// handler module has loaded. The test indexer sits here for good.
|
|
131
|
+
| None => chainConfig->Config.startBlockOrZero
|
|
130
132
|
}
|
|
131
133
|
},
|
|
132
134
|
},
|
|
@@ -589,6 +591,10 @@ let migrate = async (~reset) => {
|
|
|
589
591
|
~envioInfo=getEnvioInfo(),
|
|
590
592
|
~resetCommand="envio local db-migrate setup",
|
|
591
593
|
~runCommand=None,
|
|
594
|
+
~lowercaseAddresses=config.lowercaseAddresses,
|
|
595
|
+
// A migration command runs once and exits, with nobody watching it recover:
|
|
596
|
+
// an unreachable chain should say so now rather than hold the command open.
|
|
597
|
+
~startBlockRetry=StartBlockResolver.Once,
|
|
592
598
|
)
|
|
593
599
|
await persistence.storage.close()
|
|
594
600
|
}
|
|
@@ -641,6 +647,7 @@ let start = async (
|
|
|
641
647
|
~envioInfo=getEnvioInfo(),
|
|
642
648
|
~resetCommand=isDevelopmentMode ? "envio dev -r" : "envio start -r",
|
|
643
649
|
~runCommand=Some(isDevelopmentMode ? "envio dev" : "envio start"),
|
|
650
|
+
~lowercaseAddresses=config.lowercaseAddresses,
|
|
644
651
|
)
|
|
645
652
|
|
|
646
653
|
// Loads user handler files, which register handler/contractRegister/where
|
package/src/Main.res.mjs
CHANGED
|
@@ -130,7 +130,7 @@ function buildChainsObject(config) {
|
|
|
130
130
|
if (chainState !== undefined) {
|
|
131
131
|
return chainState.startBlock;
|
|
132
132
|
} else {
|
|
133
|
-
return chainConfig
|
|
133
|
+
return Config.startBlockOrZero(chainConfig);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
}), "endBlock", {
|
|
@@ -467,7 +467,7 @@ function getEnvioInfo() {
|
|
|
467
467
|
async function migrate(reset) {
|
|
468
468
|
let config = Config.load();
|
|
469
469
|
let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
|
|
470
|
-
await Persistence.init(persistence, ChainMap.values(config.chainMap), config.contractMapping, Config.stripSensitiveData(Config.getPublicConfigJson()), "envio local db-migrate setup", undefined, reset);
|
|
470
|
+
await Persistence.init(persistence, ChainMap.values(config.chainMap), config.contractMapping, Config.stripSensitiveData(Config.getPublicConfigJson()), "envio local db-migrate setup", undefined, reset, config.lowercaseAddresses, "Once");
|
|
471
471
|
return await persistence.storage.close();
|
|
472
472
|
}
|
|
473
473
|
|
|
@@ -494,7 +494,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
494
494
|
let isDevelopmentMode = !isTest && config.isDev;
|
|
495
495
|
let persistence$1 = persistence !== undefined ? persistence : PgStorage.makePersistenceFromConfig(config, undefined);
|
|
496
496
|
EnvioGlobal.value.persistence = Primitive_option.some(persistence$1);
|
|
497
|
-
await Persistence.init(persistence$1, ChainMap.values(config.chainMap), config.contractMapping, Config.stripSensitiveData(Config.getPublicConfigJson()), isDevelopmentMode ? "envio dev -r" : "envio start -r", isDevelopmentMode ? "envio dev" : "envio start", reset);
|
|
497
|
+
await Persistence.init(persistence$1, ChainMap.values(config.chainMap), config.contractMapping, Config.stripSensitiveData(Config.getPublicConfigJson()), isDevelopmentMode ? "envio dev -r" : "envio start -r", isDevelopmentMode ? "envio dev" : "envio start", reset, config.lowercaseAddresses, undefined);
|
|
498
498
|
let registrationsByChainId = await HandlerLoader.registerAllHandlers(config);
|
|
499
499
|
let config$1;
|
|
500
500
|
if (isTest) {
|
package/src/Persistence.res
CHANGED
|
@@ -235,12 +235,14 @@ let make = (
|
|
|
235
235
|
let init = {
|
|
236
236
|
async (
|
|
237
237
|
persistence,
|
|
238
|
-
~chainConfigs
|
|
238
|
+
~chainConfigs: array<Config.chain>,
|
|
239
239
|
~contractMapping,
|
|
240
240
|
~envioInfo,
|
|
241
241
|
~resetCommand,
|
|
242
242
|
~runCommand,
|
|
243
243
|
~reset=false,
|
|
244
|
+
~lowercaseAddresses=false,
|
|
245
|
+
~startBlockRetry=StartBlockResolver.UntilItAnswers,
|
|
244
246
|
) => {
|
|
245
247
|
try {
|
|
246
248
|
let shouldRun = switch persistence.storageStatus {
|
|
@@ -259,6 +261,14 @@ let init = {
|
|
|
259
261
|
persistence.storageStatus = Initializing(promise)
|
|
260
262
|
if reset || !(await persistence.storage.isInitialized()) {
|
|
261
263
|
Logging.info(`Initializing the indexer storage...`)
|
|
264
|
+
// Only runs once per schema (this branch is the "first deploy or
|
|
265
|
+
// reset" gate), which is exactly when a `latest` start block must be
|
|
266
|
+
// resolved: every later resume reads `envio_chains.start_block` back
|
|
267
|
+
// verbatim instead of re-running this.
|
|
268
|
+
let chainConfigs = await chainConfigs->StartBlockResolver.resolveAllOrThrow(
|
|
269
|
+
~lowercaseAddresses,
|
|
270
|
+
~retry=startBlockRetry,
|
|
271
|
+
)
|
|
262
272
|
let initialState = await persistence.storage.initialize(
|
|
263
273
|
~entities=persistence.allEntities,
|
|
264
274
|
~enums=persistence.allEnums,
|
package/src/Persistence.res.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import * as Logging from "./Logging.res.mjs";
|
|
|
5
5
|
import * as EntityHistory from "./db/EntityHistory.res.mjs";
|
|
6
6
|
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
7
7
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
8
|
+
import * as StartBlockResolver from "./sources/StartBlockResolver.res.mjs";
|
|
8
9
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
9
10
|
|
|
10
11
|
let StorageError = /* @__PURE__ */Primitive_exceptions.create("Persistence.StorageError");
|
|
@@ -20,8 +21,10 @@ function make(userEntities, allEnums, storage) {
|
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
async function init(persistence, chainConfigs, contractMapping, envioInfo, resetCommand, runCommand, resetOpt) {
|
|
24
|
+
async function init(persistence, chainConfigs, contractMapping, envioInfo, resetCommand, runCommand, resetOpt, lowercaseAddressesOpt, startBlockRetryOpt) {
|
|
24
25
|
let reset = resetOpt !== undefined ? resetOpt : false;
|
|
26
|
+
let lowercaseAddresses = lowercaseAddressesOpt !== undefined ? lowercaseAddressesOpt : false;
|
|
27
|
+
let startBlockRetry = startBlockRetryOpt !== undefined ? startBlockRetryOpt : "UntilItAnswers";
|
|
25
28
|
try {
|
|
26
29
|
let promise = persistence.storageStatus;
|
|
27
30
|
let shouldRun;
|
|
@@ -48,7 +51,8 @@ async function init(persistence, chainConfigs, contractMapping, envioInfo, reset
|
|
|
48
51
|
};
|
|
49
52
|
if (reset || !await persistence.storage.isInitialized()) {
|
|
50
53
|
Logging.info(`Initializing the indexer storage...`);
|
|
51
|
-
let
|
|
54
|
+
let chainConfigs$1 = await StartBlockResolver.resolveAllOrThrow(chainConfigs, lowercaseAddresses, startBlockRetry, undefined, undefined);
|
|
55
|
+
let initialState = await persistence.storage.initialize(chainConfigs$1, persistence.allEntities, persistence.allEnums, contractMapping, envioInfo);
|
|
52
56
|
Logging.info(`The indexer storage is ready. Starting indexing!`);
|
|
53
57
|
persistence.storageStatus = {
|
|
54
58
|
TAG: "Ready",
|
package/src/PgStorage.res
CHANGED
|
@@ -1917,7 +1917,7 @@ let make = (
|
|
|
1917
1917
|
idx,
|
|
1918
1918
|
): Persistence.initialChainState => {
|
|
1919
1919
|
id: chainConfig.id,
|
|
1920
|
-
startBlock: chainConfig.
|
|
1920
|
+
startBlock: chainConfig->Config.startBlockOrThrow,
|
|
1921
1921
|
endBlock: chainConfig.endBlock,
|
|
1922
1922
|
maxReorgDepth: chainConfig.maxReorgDepth,
|
|
1923
1923
|
progressBlockNumber: -1,
|
package/src/PgStorage.res.mjs
CHANGED
|
@@ -1161,7 +1161,7 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
1161
1161
|
cache: cache,
|
|
1162
1162
|
chains: chainConfigs.map((chainConfig, idx) => ({
|
|
1163
1163
|
id: chainConfig.id,
|
|
1164
|
-
startBlock: chainConfig
|
|
1164
|
+
startBlock: Config.startBlockOrThrow(chainConfig),
|
|
1165
1165
|
endBlock: chainConfig.endBlock,
|
|
1166
1166
|
maxReorgDepth: chainConfig.maxReorgDepth,
|
|
1167
1167
|
progressBlockNumber: -1,
|
package/src/SimulateItems.res
CHANGED
|
@@ -299,7 +299,7 @@ let parse = (
|
|
|
299
299
|
~onEventRegistrations: array<Internal.onEventRegistration>,
|
|
300
300
|
): parseResult => {
|
|
301
301
|
let chainId = chainConfig.id
|
|
302
|
-
let startBlock = chainConfig.
|
|
302
|
+
let startBlock = chainConfig->Config.startBlockOrZero
|
|
303
303
|
let currentBlock = ref(startBlock)
|
|
304
304
|
let currentLogIndex = ref(0)
|
|
305
305
|
|
|
@@ -358,15 +358,22 @@ let parse = (
|
|
|
358
358
|
let path = item.path->Option.getOr([0])
|
|
359
359
|
let programId =
|
|
360
360
|
item.programId->Option.getOr(svmEventConfig.programId->SvmTypes.Pubkey.toString)
|
|
361
|
+
// `accountArguments` is the list the call carries, so it stands as
|
|
362
|
+
// written: a short one leaves the later slots absent, the way a
|
|
363
|
+
// truncated instruction reads from chain. Named accounts speak about
|
|
364
|
+
// some slots instead, so they go back onto their declared positions and
|
|
365
|
+
// a slot the item leaves out carries the program id — chain's own
|
|
366
|
+
// stand-in for an absent account, which reads back as absent for an
|
|
367
|
+
// optional slot and as the program for a required one.
|
|
361
368
|
let accountArguments = switch item.accountArguments {
|
|
362
369
|
| Some(args) => args
|
|
363
370
|
| None =>
|
|
364
371
|
switch item.accounts {
|
|
365
372
|
| Some(named) =>
|
|
366
|
-
svmEventConfig.accounts->Array.map(
|
|
367
|
-
switch
|
|
368
|
-
|
|
|
369
|
-
|
|
|
373
|
+
svmEventConfig.accounts->Array.map(slot =>
|
|
374
|
+
switch slot->Internal.svmAccountSlotName {
|
|
375
|
+
| None => programId
|
|
376
|
+
| Some(name) => named->Dict.get(name)->Option.mapOr(programId, ({address}) => address)
|
|
370
377
|
}
|
|
371
378
|
)
|
|
372
379
|
| None => []
|
|
@@ -695,7 +702,7 @@ let patchConfig = (
|
|
|
695
702
|
let endBlock: int = raw["endBlock"]->(Utils.magic: 'a => int)
|
|
696
703
|
// Parse with the process's startBlock so items default into the range
|
|
697
704
|
// the source will be queried over; the source now filters by range.
|
|
698
|
-
let chainConfig = {...chainConfig, startBlock, endBlock}
|
|
705
|
+
let chainConfig = {...chainConfig, startBlock: Config.Block(startBlock), endBlock}
|
|
699
706
|
let {items, transactionStore, blockStore} = parse(
|
|
700
707
|
~simulateItems,
|
|
701
708
|
~config,
|
|
@@ -5,6 +5,7 @@ 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 Internal from "./Internal.res.mjs";
|
|
8
9
|
import * as BlockStore from "./sources/BlockStore.res.mjs";
|
|
9
10
|
import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
|
|
10
11
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
@@ -184,7 +185,7 @@ function liveRegistrationsFor(config, chainId, eventConfig) {
|
|
|
184
185
|
|
|
185
186
|
function parse(simulateItems, config, chainConfig, onEventRegistrations) {
|
|
186
187
|
let chainId = chainConfig.id;
|
|
187
|
-
let startBlock = chainConfig
|
|
188
|
+
let startBlock = Config.startBlockOrZero(chainConfig);
|
|
188
189
|
let currentBlock = {
|
|
189
190
|
contents: startBlock
|
|
190
191
|
};
|
|
@@ -236,12 +237,12 @@ function parse(simulateItems, config, chainConfig, onEventRegistrations) {
|
|
|
236
237
|
accountArguments = args;
|
|
237
238
|
} else {
|
|
238
239
|
let named = rawJson.accounts;
|
|
239
|
-
accountArguments = named !== undefined ? eventConfig.accounts.map(
|
|
240
|
-
let
|
|
241
|
-
if (
|
|
242
|
-
return
|
|
240
|
+
accountArguments = named !== undefined ? eventConfig.accounts.map(slot => {
|
|
241
|
+
let name = Internal.svmAccountSlotName(slot);
|
|
242
|
+
if (name !== undefined) {
|
|
243
|
+
return Stdlib_Option.mapOr(named[name], programId, param => param.address);
|
|
243
244
|
} else {
|
|
244
|
-
return
|
|
245
|
+
return programId;
|
|
245
246
|
}
|
|
246
247
|
}) : [];
|
|
247
248
|
}
|
package/src/TestIndexer.res
CHANGED
|
@@ -355,13 +355,14 @@ let parseBlockRange = (
|
|
|
355
355
|
JsError.throwWithMessage(`Chain ${chainIdStr} is not configured in config.yaml`)
|
|
356
356
|
}
|
|
357
357
|
let configChain = config.chainMap->ChainMap.get(chain)
|
|
358
|
+
let configStartBlock = configChain->Config.startBlockOrZero
|
|
358
359
|
|
|
359
360
|
let startBlock = switch rawChainConfig.startBlock {
|
|
360
361
|
| Some(sb) => sb
|
|
361
362
|
| None =>
|
|
362
363
|
switch progressBlock {
|
|
363
364
|
| Some(prevEndBlock) => prevEndBlock + 1
|
|
364
|
-
| None =>
|
|
365
|
+
| None => configStartBlock
|
|
365
366
|
}
|
|
366
367
|
}
|
|
367
368
|
|
|
@@ -378,10 +379,10 @@ let parseBlockRange = (
|
|
|
378
379
|
| None => None // auto-exit mode: will fetch first block with events and exit
|
|
379
380
|
}
|
|
380
381
|
|
|
381
|
-
if startBlock <
|
|
382
|
+
if startBlock < configStartBlock {
|
|
382
383
|
JsError.throwWithMessage(
|
|
383
|
-
`Invalid block range for chain ${chainIdStr}: startBlock (${startBlock->Int.toString}) is less than config.startBlock (${
|
|
384
|
-
`Either use startBlock >= ${
|
|
384
|
+
`Invalid block range for chain ${chainIdStr}: startBlock (${startBlock->Int.toString}) is less than config.startBlock (${configStartBlock->Int.toString}). ` ++
|
|
385
|
+
`Either use startBlock >= ${configStartBlock->Int.toString} or create a new test indexer with createTestIndexer().`,
|
|
385
386
|
)
|
|
386
387
|
}
|
|
387
388
|
|
|
@@ -754,7 +755,7 @@ let createTestIndexer = (): t<'processConfig> => {
|
|
|
754
755
|
->Utils.Object.definePropertyWithValue("id", {enumerable: true, value: chainConfig.id})
|
|
755
756
|
->Utils.Object.definePropertyWithValue(
|
|
756
757
|
"startBlock",
|
|
757
|
-
{enumerable: true, value: chainConfig.
|
|
758
|
+
{enumerable: true, value: chainConfig->Config.startBlockOrZero},
|
|
758
759
|
)
|
|
759
760
|
->Utils.Object.definePropertyWithValue(
|
|
760
761
|
"endBlock",
|