envio 3.13.0-rc.0 → 3.14.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/bin.mjs +10 -1
- package/index.d.ts +11 -0
- package/package.json +8 -20
- package/rescript.json +1 -4
- package/src/BatchProcessing.res +0 -2
- package/src/ChainState.res +39 -25
- package/src/ChainState.res.mjs +26 -23
- package/src/ChainState.resi +1 -2
- package/src/Config.res +69 -100
- package/src/Config.res.mjs +50 -63
- package/src/Core.res +46 -6
- package/src/Core.res.mjs +41 -4
- package/src/CrossChainState.res +13 -6
- package/src/CrossChainState.res.mjs +17 -4
- package/src/EventConfigBuilder.res +4 -10
- package/src/EventConfigBuilder.res.mjs +1 -3
- package/src/ExitOnCaughtUp.res +5 -1
- package/src/ExitOnCaughtUp.res.mjs +8 -3
- package/src/FinalizeBackfill.res +16 -10
- package/src/FinalizeBackfill.res.mjs +16 -5
- package/src/HandlerLoader.res +74 -32
- package/src/HandlerLoader.res.mjs +69 -23
- package/src/HandlerTypeCheck.mjs +14 -0
- package/src/HandlerTypeCheckWorker.mjs +68 -0
- package/src/Internal.res +1 -1
- package/src/Main.res +17 -12
- package/src/Main.res.mjs +9 -3
- package/src/NodeVersion.mjs +13 -0
- package/src/Persistence.res +79 -38
- package/src/Persistence.res.mjs +41 -10
- package/src/PgStorage.res +62 -34
- package/src/PgStorage.res.mjs +65 -31
- package/src/ResumePlan.res +172 -0
- package/src/ResumePlan.res.mjs +122 -0
- package/src/Supervisor.res +8 -8
- package/src/Supervisor.res.mjs +6 -1
- package/src/TestIndexer.res +19 -6
- package/src/TestIndexer.res.mjs +5 -4
- package/src/TsModuleHooks.mjs +121 -0
- package/src/Utils.res +0 -8
- package/src/Worker.res +8 -0
- package/src/Worker.res.mjs +11 -0
- package/src/bindings/NodeJs.res +8 -7
- package/src/bindings/NodeJs.res.mjs +0 -3
- package/src/bindings/Pino.res +2 -2
- package/src/db/InternalTable.res +52 -2
- package/src/db/InternalTable.res.mjs +32 -3
- package/src/sources/FuelHyperSync.res +2 -0
- package/src/sources/FuelHyperSync.res.mjs +1 -0
- package/src/sources/FuelHyperSync.resi +1 -0
- package/src/sources/FuelHyperSyncClient.res +25 -8
- package/src/sources/FuelHyperSyncClient.res.mjs +8 -2
- package/src/sources/FuelHyperSyncSource.res +24 -22
- package/src/sources/FuelHyperSyncSource.res.mjs +18 -13
- package/src/tui/InitApi.res +85 -0
- package/src/tui/{components/CustomHooks.res.mjs → InitApi.res.mjs} +11 -64
- package/src/tui/Tui.res +142 -249
- package/src/tui/Tui.res.mjs +123 -332
- package/src/Js.shim.ts +0 -11
- package/src/bindings/DateFns.res +0 -74
- package/src/bindings/DateFns.res.mjs +0 -22
- package/src/sources/FuelSDK.res +0 -38
- package/src/sources/FuelSDK.res.mjs +0 -27
- package/src/sources/vendored-fuel-abi-coder.js +0 -1792
- package/src/tui/bindings/Ink.res +0 -369
- package/src/tui/bindings/Ink.res.mjs +0 -66
- package/src/tui/bindings/Style.res +0 -123
- package/src/tui/bindings/Style.res.mjs +0 -2
- package/src/tui/components/BufferedProgressBar.res +0 -40
- package/src/tui/components/BufferedProgressBar.res.mjs +0 -57
- package/src/tui/components/CustomHooks.res +0 -122
- package/src/tui/components/Messages.res +0 -40
- package/src/tui/components/Messages.res.mjs +0 -71
- package/src/tui/components/SyncETA.res +0 -179
- package/src/tui/components/SyncETA.res.mjs +0 -249
- package/src/tui/components/TuiData.res +0 -98
- package/src/tui/components/TuiData.res.mjs +0 -73
package/bin.mjs
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { unsupportedNodeMessage } from "./src/NodeVersion.mjs";
|
|
3
|
+
|
|
4
|
+
// Checked before the rest loads, which an older Node may not even parse.
|
|
5
|
+
const unsupported = unsupportedNodeMessage();
|
|
6
|
+
if (unsupported !== undefined) {
|
|
7
|
+
console.error(unsupported);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { run } = await import("./src/Bin.res.mjs");
|
|
3
12
|
await run(process.argv.slice(2));
|
package/index.d.ts
CHANGED
|
@@ -12,6 +12,17 @@ declare const bigintSchema: Sury.Schema<bigint>;
|
|
|
12
12
|
/** Ethereum address — a 20-byte hex string prefixed with `0x`. */
|
|
13
13
|
export type Address = `0x${string}`;
|
|
14
14
|
|
|
15
|
+
/** A value a `Json` entity field can hold. A `bigint` isn't JSON — store its
|
|
16
|
+
* decimal string with `.toString()`. Shadows the global `JSON` interface only
|
|
17
|
+
* where imported; the `JSON` object itself is unaffected. */
|
|
18
|
+
export type JSON =
|
|
19
|
+
| string
|
|
20
|
+
| number
|
|
21
|
+
| boolean
|
|
22
|
+
| null
|
|
23
|
+
| readonly JSON[]
|
|
24
|
+
| { readonly [key: string]: JSON };
|
|
25
|
+
|
|
15
26
|
/** Structured logger bound to an event or handler context. Messages are
|
|
16
27
|
* displayed in the console and the Envio Hosted Service. */
|
|
17
28
|
export type Logger = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
6
6
|
"bin": "./bin.mjs",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dapp"
|
|
28
28
|
],
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=22.
|
|
30
|
+
"node": ">=22.15.0"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"bin.mjs",
|
|
@@ -42,11 +42,6 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@elastic/ecs-pino-format": "1.4.0",
|
|
45
|
-
"@fuel-ts/crypto": "0.96.1",
|
|
46
|
-
"@fuel-ts/errors": "0.96.1",
|
|
47
|
-
"@fuel-ts/hasher": "0.96.1",
|
|
48
|
-
"@fuel-ts/math": "0.96.1",
|
|
49
|
-
"@fuel-ts/utils": "0.96.1",
|
|
50
45
|
"bignumber.js": "9.3.1",
|
|
51
46
|
"eventsource": "4.1.0",
|
|
52
47
|
"express": "4.19.2",
|
|
@@ -56,20 +51,13 @@
|
|
|
56
51
|
"rescript-schema": "9.5.1",
|
|
57
52
|
"viem": "2.54.0",
|
|
58
53
|
"dotenv": "16.4.5",
|
|
59
|
-
"
|
|
60
|
-
"@rescript/react": "0.14.1",
|
|
61
|
-
"react": "19.2.5",
|
|
62
|
-
"ink": "6.8.0",
|
|
63
|
-
"ink-big-text": "2.0.0",
|
|
64
|
-
"ink-spinner": "5.0.0",
|
|
65
|
-
"postgres": "3.4.8",
|
|
66
|
-
"tsx": "4.21.0"
|
|
54
|
+
"postgres": "3.4.8"
|
|
67
55
|
},
|
|
68
56
|
"optionalDependencies": {
|
|
69
|
-
"envio-linux-x64": "3.
|
|
70
|
-
"envio-linux-x64-musl": "3.
|
|
71
|
-
"envio-linux-arm64": "3.
|
|
72
|
-
"envio-darwin-x64": "3.
|
|
73
|
-
"envio-darwin-arm64": "3.
|
|
57
|
+
"envio-linux-x64": "3.14.0",
|
|
58
|
+
"envio-linux-x64-musl": "3.14.0",
|
|
59
|
+
"envio-linux-arm64": "3.14.0",
|
|
60
|
+
"envio-darwin-x64": "3.14.0",
|
|
61
|
+
"envio-darwin-arm64": "3.14.0"
|
|
74
62
|
}
|
|
75
63
|
}
|
package/rescript.json
CHANGED
|
@@ -12,10 +12,7 @@
|
|
|
12
12
|
"module": "esmodule",
|
|
13
13
|
"in-source": true
|
|
14
14
|
},
|
|
15
|
-
"
|
|
16
|
-
"version": 4
|
|
17
|
-
},
|
|
18
|
-
"dependencies": ["rescript-schema", "@rescript/react"],
|
|
15
|
+
"dependencies": ["rescript-schema"],
|
|
19
16
|
"compiler-flags": ["-open RescriptSchema"],
|
|
20
17
|
"warnings": {
|
|
21
18
|
"error": "+a"
|
package/src/BatchProcessing.res
CHANGED
|
@@ -159,8 +159,6 @@ and processNextBatch = async (state: IndexerState.t, ~scheduleFetch): unit => {
|
|
|
159
159
|
// Can safely reset rollback state, since overwrite is not possible.
|
|
160
160
|
state->IndexerState.clearRollback
|
|
161
161
|
state->IndexerState.applyBatchProgress(~batch)
|
|
162
|
-
// Before the finalize below, so a chain says where it finished ahead of
|
|
163
|
-
// the process saying what it does about that.
|
|
164
162
|
state->IndexerState.reportFinished
|
|
165
163
|
|
|
166
164
|
// Backfilling → FinalizingIndexes → Ready. Awaiting here holds the
|
package/src/ChainState.res
CHANGED
|
@@ -426,7 +426,6 @@ let getLatestValidScannedBlock = (cs: t, ~blockStore: BlockStore.t, ~blockNumber
|
|
|
426
426
|
->BlockStore.latestValidBlockFromStore(blockStore, blockNumbers)
|
|
427
427
|
->Null.toOption
|
|
428
428
|
let safeCheckpointTracking = (cs: t) => cs.safeCheckpointTracking
|
|
429
|
-
let isProgressAtHead = (cs: t) => cs.isProgressAtHead
|
|
430
429
|
let committedProgressBlockNumber = (cs: t) => cs.committedProgressBlockNumber
|
|
431
430
|
let committedProgressBlockTime = (cs: t) => cs.committedProgressBlockTime
|
|
432
431
|
let numEventsProcessed = (cs: t) => cs.numEventsProcessed
|
|
@@ -660,30 +659,6 @@ let hasProcessedToEndblock = (cs: t) => {
|
|
|
660
659
|
}
|
|
661
660
|
}
|
|
662
661
|
|
|
663
|
-
// Where this chain has finished indexing, the first time it gets there.
|
|
664
|
-
// `EndBlock` is terminal: the chain indexed everything it was configured to.
|
|
665
|
-
// `Backfill` is the rest of the history, up to the point where blocks can
|
|
666
|
-
// still be reorged, which is as far as a chain indexes before the indexer
|
|
667
|
-
// crosses into them.
|
|
668
|
-
type finished = EndBlock(int) | Backfill(int)
|
|
669
|
-
|
|
670
|
-
let takeFinished = (cs: t) =>
|
|
671
|
-
if cs.reportedFinished {
|
|
672
|
-
None
|
|
673
|
-
} else {
|
|
674
|
-
switch (cs.fetchState.endBlock, cs->hasProcessedToEndblock, cs.isProgressAtHead) {
|
|
675
|
-
| (Some(endBlock), true, _) => {
|
|
676
|
-
cs.reportedFinished = true
|
|
677
|
-
Some(EndBlock(endBlock))
|
|
678
|
-
}
|
|
679
|
-
| (_, _, true) => {
|
|
680
|
-
cs.reportedFinished = true
|
|
681
|
-
Some(Backfill(cs.committedProgressBlockNumber))
|
|
682
|
-
}
|
|
683
|
-
| _ => None
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
662
|
// Caught up as judged by persisted values alone: progress reached the endBlock,
|
|
688
663
|
// or the head the previous run had already observed (less the lag that holds the
|
|
689
664
|
// tip back). Unlike `isFetchingAtHead` this doesn't move when a fresh height
|
|
@@ -708,6 +683,45 @@ let isDurablyCaughtUp = (cs: t) => {
|
|
|
708
683
|
atEndBlock || atHead
|
|
709
684
|
}
|
|
710
685
|
|
|
686
|
+
// This chain has indexed everything there was to index: it reached its end
|
|
687
|
+
// block, or its progress reached the head. The single reading of that, for
|
|
688
|
+
// everything that turns on it — what the chain reports, and whether the whole
|
|
689
|
+
// process may finalize.
|
|
690
|
+
//
|
|
691
|
+
// Both spellings, because neither covers the other: `isProgressAtHead` latches
|
|
692
|
+
// the moment a batch's progress met the head known when it was created, which a
|
|
693
|
+
// head that has since moved on would read as behind; `isDurablyCaughtUp` judges
|
|
694
|
+
// the head as it stands, which is all a run resumed at the head has — it has no
|
|
695
|
+
// batch to latch anything.
|
|
696
|
+
let hasCaughtUp = (cs: t) => cs.isProgressAtHead || cs->isDurablyCaughtUp
|
|
697
|
+
|
|
698
|
+
// Where this chain has finished indexing, the first time it gets there.
|
|
699
|
+
// `EndBlock` is terminal: the chain indexed everything it was configured to.
|
|
700
|
+
// `Backfill` is the rest of the history, up to the point where blocks can
|
|
701
|
+
// still be reorged, which is as far as a chain indexes before the indexer
|
|
702
|
+
// crosses into them.
|
|
703
|
+
type finished = EndBlock(int) | Backfill(int)
|
|
704
|
+
|
|
705
|
+
let takeFinished = (cs: t) =>
|
|
706
|
+
if cs.reportedFinished {
|
|
707
|
+
None
|
|
708
|
+
} else {
|
|
709
|
+
switch (cs.fetchState.endBlock, cs->hasProcessedToEndblock, cs->hasCaughtUp) {
|
|
710
|
+
| (Some(endBlock), true, _) => {
|
|
711
|
+
cs.reportedFinished = true
|
|
712
|
+
Some(EndBlock(endBlock))
|
|
713
|
+
}
|
|
714
|
+
| (_, _, true) => {
|
|
715
|
+
cs.reportedFinished = true
|
|
716
|
+
// Finishing a backfill happens in a run, and a chain resumed with its
|
|
717
|
+
// `ready_at` already committed did none in this one. Being at the end
|
|
718
|
+
// block, above, stays true across runs and is said on every one.
|
|
719
|
+
cs->isReady ? None : Some(Backfill(cs.committedProgressBlockNumber))
|
|
720
|
+
}
|
|
721
|
+
| _ => None
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
711
725
|
let getHighestBlockBelowThreshold = (cs: t): int => {
|
|
712
726
|
let highestBlockBelowThreshold = cs.fetchState.knownHeight - cs.maxReorgDepth
|
|
713
727
|
highestBlockBelowThreshold < 0 ? 0 : highestBlockBelowThreshold
|
package/src/ChainState.res.mjs
CHANGED
|
@@ -220,10 +220,6 @@ function safeCheckpointTracking(cs) {
|
|
|
220
220
|
return cs.safeCheckpointTracking;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
function isProgressAtHead(cs) {
|
|
224
|
-
return cs.isProgressAtHead;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
223
|
function committedProgressBlockNumber(cs) {
|
|
228
224
|
return cs.committedProgressBlockNumber;
|
|
229
225
|
}
|
|
@@ -421,13 +417,29 @@ function hasProcessedToEndblock(cs) {
|
|
|
421
417
|
}
|
|
422
418
|
}
|
|
423
419
|
|
|
420
|
+
function hasCaughtUp(cs) {
|
|
421
|
+
if (cs.isProgressAtHead) {
|
|
422
|
+
return true;
|
|
423
|
+
} else {
|
|
424
|
+
let fetchState = cs.fetchState;
|
|
425
|
+
let committedProgressBlockNumber = cs.committedProgressBlockNumber;
|
|
426
|
+
let atEndBlock = Stdlib_Option.mapOr(fetchState.endBlock, false, endBlock => committedProgressBlockNumber >= endBlock);
|
|
427
|
+
let atHead = fetchState.knownHeight > 0 && committedProgressBlockNumber >= Primitive_int.max(0, fetchState.knownHeight - cs.chainConfig.blockLag | 0);
|
|
428
|
+
if (atEndBlock) {
|
|
429
|
+
return true;
|
|
430
|
+
} else {
|
|
431
|
+
return atHead;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
424
436
|
function takeFinished(cs) {
|
|
425
437
|
if (cs.reportedFinished) {
|
|
426
438
|
return;
|
|
427
439
|
}
|
|
428
440
|
let match = cs.fetchState.endBlock;
|
|
429
441
|
let match$1 = hasProcessedToEndblock(cs);
|
|
430
|
-
let match$2 = cs
|
|
442
|
+
let match$2 = hasCaughtUp(cs);
|
|
431
443
|
if (match !== undefined && match$1) {
|
|
432
444
|
cs.reportedFinished = true;
|
|
433
445
|
return {
|
|
@@ -437,22 +449,14 @@ function takeFinished(cs) {
|
|
|
437
449
|
}
|
|
438
450
|
if (match$2) {
|
|
439
451
|
cs.reportedFinished = true;
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
let fetchState = cs.fetchState;
|
|
449
|
-
let committedProgressBlockNumber = cs.committedProgressBlockNumber;
|
|
450
|
-
let atEndBlock = Stdlib_Option.mapOr(fetchState.endBlock, false, endBlock => committedProgressBlockNumber >= endBlock);
|
|
451
|
-
let atHead = fetchState.knownHeight > 0 && committedProgressBlockNumber >= Primitive_int.max(0, fetchState.knownHeight - cs.chainConfig.blockLag | 0);
|
|
452
|
-
if (atEndBlock) {
|
|
453
|
-
return true;
|
|
454
|
-
} else {
|
|
455
|
-
return atHead;
|
|
452
|
+
if (cs.timestampCaughtUpToHeadOrEndblock !== undefined) {
|
|
453
|
+
return;
|
|
454
|
+
} else {
|
|
455
|
+
return {
|
|
456
|
+
TAG: "Backfill",
|
|
457
|
+
_0: cs.committedProgressBlockNumber
|
|
458
|
+
};
|
|
459
|
+
}
|
|
456
460
|
}
|
|
457
461
|
}
|
|
458
462
|
|
|
@@ -946,7 +950,6 @@ export {
|
|
|
946
950
|
getReorgThresholdBlockNumbersBelow,
|
|
947
951
|
getLatestValidScannedBlock,
|
|
948
952
|
safeCheckpointTracking,
|
|
949
|
-
isProgressAtHead,
|
|
950
953
|
committedProgressBlockNumber,
|
|
951
954
|
committedProgressBlockTime,
|
|
952
955
|
numEventsProcessed,
|
|
@@ -980,7 +983,7 @@ export {
|
|
|
980
983
|
reorgThresholdLiftsCeiling,
|
|
981
984
|
reorgThresholdEntryMessage,
|
|
982
985
|
hasProcessedToEndblock,
|
|
983
|
-
|
|
986
|
+
hasCaughtUp,
|
|
984
987
|
getHighestBlockBelowThreshold,
|
|
985
988
|
isActivelyIndexing,
|
|
986
989
|
isReady,
|
package/src/ChainState.resi
CHANGED
|
@@ -70,7 +70,6 @@ let getLatestValidScannedBlock: (
|
|
|
70
70
|
~blockNumbers: array<int>,
|
|
71
71
|
) => option<int>
|
|
72
72
|
let safeCheckpointTracking: t => option<SafeCheckpointTracking.t>
|
|
73
|
-
let isProgressAtHead: t => bool
|
|
74
73
|
let committedProgressBlockNumber: t => int
|
|
75
74
|
let committedProgressBlockTime: t => option<int>
|
|
76
75
|
let numEventsProcessed: t => float
|
|
@@ -132,7 +131,7 @@ let takeFinished: t => option<finished>
|
|
|
132
131
|
let reorgThresholdLiftsCeiling: t => bool
|
|
133
132
|
let reorgThresholdEntryMessage: string
|
|
134
133
|
let hasProcessedToEndblock: t => bool
|
|
135
|
-
let
|
|
134
|
+
let hasCaughtUp: t => bool
|
|
136
135
|
let getHighestBlockBelowThreshold: t => int
|
|
137
136
|
let isActivelyIndexing: t => bool
|
|
138
137
|
let isReady: t => bool
|
package/src/Config.res
CHANGED
|
@@ -136,6 +136,9 @@ type t = {
|
|
|
136
136
|
reorgThresholdReadyTolerance: int,
|
|
137
137
|
lowercaseAddresses: bool,
|
|
138
138
|
isDev: bool,
|
|
139
|
+
// The operator's `-d` / `--config` flags, repeated in every command printed
|
|
140
|
+
// for them to run next. Empty when they gave neither.
|
|
141
|
+
projectFlags: string,
|
|
139
142
|
// An `envio start --chain` process: drives a subset of the schema's chains
|
|
140
143
|
// while sibling processes drive the rest, so it only touches what its own
|
|
141
144
|
// chains own — their partitions' indexes, their `ready_at`, their resume.
|
|
@@ -147,6 +150,7 @@ type t = {
|
|
|
147
150
|
// the schema alone: a cross-chain entity has rows any chain's reorg can
|
|
148
151
|
// reach, so its checkpoints have to be comparable across chains.
|
|
149
152
|
checkpointSequence: CheckpointSequence.t,
|
|
153
|
+
envioInfo: JSON.t,
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
type rpcSourceFor = | @as("sync") Sync | @as("fallback") Fallback | @as("realtime") Realtime
|
|
@@ -595,6 +599,7 @@ let publicConfigSchema = S.schema(s =>
|
|
|
595
599
|
"handlers": s.matches(S.option(S.string)),
|
|
596
600
|
"isDev": s.matches(S.option(S.bool)),
|
|
597
601
|
"isolatedChains": s.matches(S.option(S.array(ChainId.schema))),
|
|
602
|
+
"projectFlags": s.matches(S.option(S.string)),
|
|
598
603
|
"fullBatchSize": s.matches(S.option(S.int)),
|
|
599
604
|
"rollbackOnReorg": s.matches(S.option(S.bool)),
|
|
600
605
|
"saveFullHistory": s.matches(S.option(S.bool)),
|
|
@@ -666,6 +671,41 @@ let isolate = (config: t, ~chainIds: array<ChainId.t>) => {
|
|
|
666
671
|
}
|
|
667
672
|
}
|
|
668
673
|
|
|
674
|
+
// What the command decided rather than the project's files: `envio dev` vs
|
|
675
|
+
// `envio start`, which chains this process drives, and how it found the
|
|
676
|
+
// project.
|
|
677
|
+
let commandFields = ["isDev", "isolatedChains", "projectFlags"]
|
|
678
|
+
|
|
679
|
+
let withProjectFlags = (command, ~config: t) =>
|
|
680
|
+
switch config.projectFlags {
|
|
681
|
+
| "" => command
|
|
682
|
+
| flags => `${command} ${flags}`
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
let ecosystemFields = ["evm", "fuel", "svm"]
|
|
686
|
+
|
|
687
|
+
// What `envio_info` records: the public config without the command's fields
|
|
688
|
+
// or its chains. A chain is recorded by its own `envio_chains` row and its
|
|
689
|
+
// config's `envio_addresses` rows instead, so adding one never rewrites this
|
|
690
|
+
// record. The rest of a chain's config — its sources, its block lag, a
|
|
691
|
+
// contract's start block — is read from config.yaml on every start.
|
|
692
|
+
let toEnvioInfo = (publicConfigJson: JSON.t): JSON.t => {
|
|
693
|
+
let envioInfo = publicConfigJson->JSON.stringify->JSON.parseOrThrow
|
|
694
|
+
switch envioInfo {
|
|
695
|
+
| Object(obj) => {
|
|
696
|
+
commandFields->Array.forEach(field => obj->Utils.Dict.deleteInPlace(field))
|
|
697
|
+
ecosystemFields->Array.forEach(ecosystem =>
|
|
698
|
+
switch obj->Dict.get(ecosystem) {
|
|
699
|
+
| Some(Object(ecosystemDict)) => ecosystemDict->Utils.Dict.deleteInPlace("chains")
|
|
700
|
+
| _ => ()
|
|
701
|
+
}
|
|
702
|
+
)
|
|
703
|
+
}
|
|
704
|
+
| _ => ()
|
|
705
|
+
}
|
|
706
|
+
envioInfo
|
|
707
|
+
}
|
|
708
|
+
|
|
669
709
|
let fromPublic = (publicConfigJson: JSON.t) => {
|
|
670
710
|
let maxAddrInPartition = Env.maxAddrInPartition
|
|
671
711
|
// Parse public config
|
|
@@ -1136,11 +1176,13 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
1136
1176
|
reorgThresholdReadyTolerance: 100,
|
|
1137
1177
|
lowercaseAddresses,
|
|
1138
1178
|
isDev: publicConfig["isDev"]->Option.getOr(false),
|
|
1179
|
+
projectFlags: publicConfig["projectFlags"]->Option.getOr(""),
|
|
1139
1180
|
isolated: false,
|
|
1140
1181
|
userEntitiesByName,
|
|
1141
1182
|
userEntities,
|
|
1142
1183
|
allEnums,
|
|
1143
1184
|
checkpointSequence: CheckpointSequence.fromEntities(userEntities),
|
|
1185
|
+
envioInfo: publicConfigJson->toEnvioInfo,
|
|
1144
1186
|
}
|
|
1145
1187
|
|
|
1146
1188
|
switch publicConfig["isolatedChains"] {
|
|
@@ -1255,49 +1297,6 @@ let getPublicConfigJson = () =>
|
|
|
1255
1297
|
| None => Core.getConfigJson()->JSON.parseOrThrow
|
|
1256
1298
|
}
|
|
1257
1299
|
|
|
1258
|
-
// Drops source URLs from each chain so RPC/hypersync edits don't trigger
|
|
1259
|
-
// the resume-time compat check (and don't end up in `envio_info`). Also
|
|
1260
|
-
// drops `isDev`, which toggles between `envio dev` and `envio start` and
|
|
1261
|
-
// has no bearing on schema/indexing compatibility.
|
|
1262
|
-
let stripSensitiveData = (json: JSON.t): JSON.t => {
|
|
1263
|
-
let cloned = json->JSON.stringify->JSON.parseOrThrow
|
|
1264
|
-
let stripChains = (ecosystem: option<JSON.t>) =>
|
|
1265
|
-
switch ecosystem {
|
|
1266
|
-
| Some(Object(ecosystemDict)) =>
|
|
1267
|
-
switch ecosystemDict->Dict.get("chains") {
|
|
1268
|
-
| Some(Object(chains)) =>
|
|
1269
|
-
chains
|
|
1270
|
-
->Dict.valuesToArray
|
|
1271
|
-
->Array.forEach(chainJson =>
|
|
1272
|
-
switch chainJson {
|
|
1273
|
-
| Object(chain) => {
|
|
1274
|
-
chain->Utils.Dict.deleteInPlace("rpcs")
|
|
1275
|
-
chain->Utils.Dict.deleteInPlace("hypersync")
|
|
1276
|
-
}
|
|
1277
|
-
| _ => ()
|
|
1278
|
-
}
|
|
1279
|
-
)
|
|
1280
|
-
| _ => ()
|
|
1281
|
-
}
|
|
1282
|
-
| _ => ()
|
|
1283
|
-
}
|
|
1284
|
-
switch cloned {
|
|
1285
|
-
| Object(obj) => {
|
|
1286
|
-
obj->Utils.Dict.deleteInPlace("isDev")
|
|
1287
|
-
obj->Utils.Dict.deleteInPlace("isolatedChains")
|
|
1288
|
-
stripChains(obj->Dict.get("evm"))
|
|
1289
|
-
stripChains(obj->Dict.get("fuel"))
|
|
1290
|
-
stripChains(obj->Dict.get("svm"))
|
|
1291
|
-
}
|
|
1292
|
-
| _ => ()
|
|
1293
|
-
}
|
|
1294
|
-
cloned
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
// What the storage layer records as the config this schema was built from,
|
|
1298
|
-
// and checks a resuming run against.
|
|
1299
|
-
let envioInfo = () => getPublicConfigJson()->stripSensitiveData
|
|
1300
|
-
|
|
1301
1300
|
// Postgres jsonb doesn't preserve key order, so canonicalize with sorted
|
|
1302
1301
|
// keys before string-comparing.
|
|
1303
1302
|
let rec canonicalJson = (json: JSON.t): JSON.t =>
|
|
@@ -1317,9 +1316,10 @@ let rec canonicalJson = (json: JSON.t): JSON.t =>
|
|
|
1317
1316
|
// Returns dotted leaf paths (`a.b[i].c`) where `stored` differs from
|
|
1318
1317
|
// `current`, restricted to the highest-priority top-level tier with any
|
|
1319
1318
|
// diff. Tiers in order: version → name → storage → ecosystem
|
|
1320
|
-
// (evm/fuel/svm
|
|
1321
|
-
// containing a diff is the only one
|
|
1322
|
-
// so a single noisy section doesn't bury
|
|
1319
|
+
// (evm/fuel/svm, and the chains a resume puts beside them) → entities →
|
|
1320
|
+
// other top-level keys. The first tier containing a diff is the only one
|
|
1321
|
+
// rendered; lower tiers are silenced so a single noisy section doesn't bury
|
|
1322
|
+
// the actionable change.
|
|
1323
1323
|
let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
|
|
1324
1324
|
let canonEq = (a: JSON.t, b: JSON.t) =>
|
|
1325
1325
|
JSON.stringify(canonicalJson(a)) === JSON.stringify(canonicalJson(b))
|
|
@@ -1387,7 +1387,7 @@ let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
|
|
|
1387
1387
|
["chainIdMode"],
|
|
1388
1388
|
["name"],
|
|
1389
1389
|
["storage"],
|
|
1390
|
-
["evm", "fuel", "svm"],
|
|
1390
|
+
["evm", "fuel", "svm", "chains"],
|
|
1391
1391
|
["entities"],
|
|
1392
1392
|
]
|
|
1393
1393
|
let firstHit = tiers->Array.reduce(None, (acc, tier) =>
|
|
@@ -1417,65 +1417,34 @@ let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
|
|
|
1417
1417
|
acc
|
|
1418
1418
|
}
|
|
1419
1419
|
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1422
|
-
//
|
|
1423
|
-
//
|
|
1424
|
-
//
|
|
1425
|
-
//
|
|
1426
|
-
//
|
|
1427
|
-
let
|
|
1420
|
+
// The `incompatible config` error listing each path in `changedPaths`, plus
|
|
1421
|
+
// the remediation options. `~resetCommand` is rendered as-is for option 2
|
|
1422
|
+
// (the wipe-and-redo). `~runCommand` controls option 3 (parallel indexer
|
|
1423
|
+
// recipe): when `None`, option 3 is omitted — the migrate flow uses this
|
|
1424
|
+
// because running a second indexer doesn't apply. `~hasClickhouse` adds the
|
|
1425
|
+
// extra env line so users running both Postgres and Clickhouse get a complete
|
|
1426
|
+
// override.
|
|
1427
|
+
let incompatibleMessage = (
|
|
1428
1428
|
changedPaths: array<string>,
|
|
1429
1429
|
~resetCommand: string,
|
|
1430
1430
|
~runCommand: option<string>,
|
|
1431
1431
|
~hasClickhouse: bool,
|
|
1432
1432
|
) => {
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
`\n 3. Run a second indexer alongside this one — keep both datasets:\n ENVIO_PG_SCHEMA=<new_schema> \\\n${clickhouseLine} ENVIO_INDEXER_PORT=<new_port> \\\n ${cmd}`
|
|
1443
|
-
}
|
|
1444
|
-
JsError.throwWithMessage(
|
|
1445
|
-
`The following config changes are incompatible with the existing indexer data:\n\n${bullets}\n\nPick one:\n 1. ${option1->padTo(
|
|
1446
|
-
col,
|
|
1447
|
-
)}# resume indexing where it left off\n 2. ${resetCommand->padTo(
|
|
1448
|
-
col,
|
|
1449
|
-
)}# delete all indexed data and start over${option3}`,
|
|
1450
|
-
)
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
let throwIfResumeIncompatible = (
|
|
1455
|
-
~storedEnvioInfo: option<JSON.t>,
|
|
1456
|
-
~storedContractMapping: ContractMapping.t,
|
|
1457
|
-
~envioInfo: JSON.t,
|
|
1458
|
-
~contractMapping: ContractMapping.t,
|
|
1459
|
-
~resetCommand: string,
|
|
1460
|
-
~runCommand: option<string>,
|
|
1461
|
-
) => {
|
|
1462
|
-
let changedPaths = switch storedEnvioInfo {
|
|
1463
|
-
| None => ["storage was initialized by an older envio version"]
|
|
1464
|
-
| Some(stored) => diffPaths(~stored, ~current=envioInfo)
|
|
1465
|
-
}
|
|
1466
|
-
let changedPaths =
|
|
1467
|
-
storedContractMapping->ContractMapping.isEqual(contractMapping)
|
|
1468
|
-
? changedPaths
|
|
1469
|
-
: changedPaths->Array.concat(["contracts"])
|
|
1470
|
-
let hasClickhouse = switch envioInfo {
|
|
1471
|
-
| Object(d) =>
|
|
1472
|
-
switch d->Dict.get("storage") {
|
|
1473
|
-
| Some(Object(s)) => s->Dict.get("clickhouse") == Some(Boolean(true))
|
|
1474
|
-
| _ => false
|
|
1475
|
-
}
|
|
1476
|
-
| _ => false
|
|
1433
|
+
let bullets = changedPaths->Array.map(p => ` - ${p}`)->Array.joinUnsafe("\n")
|
|
1434
|
+
let option1 = "Revert the changes above"
|
|
1435
|
+
let padTo = (s, col) => s ++ " "->String.repeat(Math.Int.max(col - String.length(s), 1))
|
|
1436
|
+
let col = Math.Int.max(String.length(option1), String.length(resetCommand)) + 2
|
|
1437
|
+
let option3 = switch runCommand {
|
|
1438
|
+
| None => ""
|
|
1439
|
+
| Some(cmd) =>
|
|
1440
|
+
let clickhouseLine = hasClickhouse ? " ENVIO_CLICKHOUSE_DATABASE=<new_db> \\\n" : ""
|
|
1441
|
+
`\n 3. Run a second indexer alongside this one — keep both datasets:\n ENVIO_PG_SCHEMA=<new_schema> \\\n${clickhouseLine} ENVIO_INDEXER_PORT=<new_port> \\\n ${cmd}`
|
|
1477
1442
|
}
|
|
1478
|
-
|
|
1443
|
+
`The following config changes are incompatible with the existing indexer data:\n\n${bullets}\n\nPick one:\n 1. ${option1->padTo(
|
|
1444
|
+
col,
|
|
1445
|
+
)}# resume indexing where it left off\n 2. ${resetCommand->padTo(
|
|
1446
|
+
col,
|
|
1447
|
+
)}# delete all indexed data and start over${option3}`
|
|
1479
1448
|
}
|
|
1480
1449
|
|
|
1481
1450
|
// The returned value is a pure function of the JSON: it holds only event
|