@constellation-network/node-pilot 0.0.7 → 0.0.9
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/README.md +26 -18
- package/bin/dev.js +1 -6
- package/bin/run.js +1 -1
- package/dist/base-command.d.ts +9 -0
- package/dist/base-command.js +20 -0
- package/dist/checks/check-dependencies.d.ts +1 -0
- package/dist/checks/check-dependencies.js +44 -0
- package/dist/checks/check-hardware.js +3 -3
- package/dist/checks/check-initial-setup.js +6 -4
- package/dist/checks/check-layers.js +7 -7
- package/dist/checks/check-network.d.ts +3 -0
- package/dist/checks/check-network.js +92 -9
- package/dist/checks/check-node-ctl.d.ts +5 -0
- package/dist/checks/check-node-ctl.js +88 -0
- package/dist/checks/check-project.js +33 -13
- package/dist/checks/check-wallet.d.ts +3 -0
- package/dist/checks/check-wallet.js +37 -0
- package/dist/clm.d.ts +1 -0
- package/dist/clm.js +4 -1
- package/dist/commands/config/get.d.ts +6 -0
- package/dist/commands/config/get.js +57 -11
- package/dist/commands/config/set.d.ts +0 -1
- package/dist/commands/config/set.js +13 -11
- package/dist/commands/config.js +17 -22
- package/dist/commands/info.js +3 -2
- package/dist/commands/logs.d.ts +2 -1
- package/dist/commands/logs.js +12 -6
- package/dist/commands/restart.d.ts +5 -2
- package/dist/commands/restart.js +25 -5
- package/dist/commands/shutdown.js +3 -3
- package/dist/commands/status.js +8 -0
- package/dist/commands/test.d.ts +1 -0
- package/dist/commands/test.js +35 -5
- package/dist/config-store.d.ts +50 -34
- package/dist/config-store.js +80 -32
- package/dist/helpers/config-helper.js +2 -2
- package/dist/helpers/env-templates.d.ts +4 -3
- package/dist/helpers/env-templates.js +28 -20
- package/dist/helpers/key-file-helper.d.ts +2 -0
- package/dist/helpers/key-file-helper.js +52 -17
- package/dist/helpers/project-helper.d.ts +3 -2
- package/dist/helpers/project-helper.js +86 -35
- package/dist/helpers/prompt-helper.d.ts +0 -2
- package/dist/helpers/prompt-helper.js +15 -66
- package/dist/services/archiver-service.d.ts +17 -0
- package/dist/services/archiver-service.js +104 -0
- package/dist/services/cluster-service.d.ts +10 -5
- package/dist/services/cluster-service.js +46 -36
- package/dist/services/docker-service.d.ts +9 -0
- package/dist/services/docker-service.js +53 -0
- package/dist/services/fastforward-service.js +12 -6
- package/dist/services/get-random-node.d.ts +2 -0
- package/dist/services/get-random-node.js +16 -0
- package/dist/{helpers/github-helper.d.ts → services/github-service.d.ts} +1 -1
- package/dist/{helpers/github-helper.js → services/github-service.js} +1 -1
- package/dist/services/node-service.d.ts +4 -0
- package/dist/services/node-service.js +29 -14
- package/dist/services/shell-service.d.ts +3 -1
- package/dist/services/shell-service.js +32 -6
- package/dist/test.d.ts +1 -0
- package/dist/test.js +50 -0
- package/dist/types.d.ts +42 -3
- package/install-dependencies.sh +98 -0
- package/oclif.manifest.json +31 -4
- package/package.json +12 -8
- package/projects/hypergraph/Dockerfile +27 -18
- package/projects/hypergraph/docker-compose.yml +14 -12
- package/projects/hypergraph/networks/integrationnet/gl0.env +4 -0
- package/projects/hypergraph/networks/integrationnet/gl1.env +4 -0
- package/projects/hypergraph/networks/integrationnet/network.env +8 -0
- package/projects/hypergraph/networks/integrationnet/source-nodes.env +9 -0
- package/projects/hypergraph/networks/mainnet/gl0.env +4 -0
- package/projects/hypergraph/networks/mainnet/gl1.env +4 -0
- package/projects/hypergraph/networks/mainnet/network.env +8 -0
- package/projects/hypergraph/networks/mainnet/source-nodes.env +9 -0
- package/projects/hypergraph/networks/testnet/gl0.env +5 -0
- package/projects/hypergraph/networks/testnet/gl1.env +4 -0
- package/projects/hypergraph/networks/testnet/network.env +8 -0
- package/projects/hypergraph/networks/testnet/source-nodes.env +9 -0
- package/projects/hypergraph/scripts/check-version.sh +31 -0
- package/projects/hypergraph/scripts/docker-build.sh +12 -1
- package/projects/hypergraph/scripts/install-dependencies.sh +3 -3
- package/projects/hypergraph/scripts/install.sh +34 -31
- package/projects/hypergraph/seedlist +268 -0
- package/scripts/autoheal.sh +8 -0
- package/scripts/services/io.constellationnetwork.nodepilot.Updater.plist +16 -0
- package/scripts/services/node-pilot-autoheal.service +14 -0
- package/scripts/services/node-pilot-updater-hypergraph.service +15 -0
- package/scripts/updater.sh +13 -0
- package/dist/helpers/docker-helper.d.ts +0 -7
- package/dist/helpers/docker-helper.js +0 -37
- package/projects/hypergraph/layers/gl1.env +0 -3
- package/projects/hypergraph/networks/integrationnet.env +0 -9
- package/projects/hypergraph/networks/mainnet.env +0 -8
- package/projects/hypergraph/networks/testnet.env +0 -9
- package/projects/scripts/docker-cleanup.sh +0 -64
@@ -1,51 +1,50 @@
|
|
1
|
+
import { input, select } from "@inquirer/prompts";
|
2
|
+
import chalk from "chalk";
|
1
3
|
import fs from "node:fs";
|
2
4
|
import path from "node:path";
|
3
5
|
import { fileURLToPath } from 'node:url';
|
4
6
|
import { clm } from "../clm.js";
|
5
7
|
import { configStore } from "../config-store.js";
|
8
|
+
import { githubService } from "../services/github-service.js";
|
6
9
|
import { configHelper } from "./config-helper.js";
|
7
10
|
import { getLayerEnvFileContent } from "./env-templates.js";
|
8
11
|
export const projectHelper = {
|
9
12
|
async generateLayerEnvFiles(layers) {
|
10
13
|
const { layersToRun, projectDir } = configStore.getProjectInfo();
|
11
|
-
const { type } = configStore.getNetworkInfo();
|
12
|
-
const
|
14
|
+
const { type: network } = configStore.getNetworkInfo();
|
15
|
+
const envInfo = configStore.getEnvInfo();
|
16
|
+
const envNetworkInfo = configStore.getEnvNetworkInfo(network);
|
13
17
|
layers = layers || layersToRun;
|
14
|
-
for (const
|
15
|
-
const filePath = path.join(projectDir, `${
|
16
|
-
const
|
17
|
-
const fileContents = getLayerEnvFileContent(
|
18
|
+
for (const layer of layers) {
|
19
|
+
const filePath = path.join(projectDir, `${layer}.env`);
|
20
|
+
const envLayerInfo = configStore.getEnvLayerInfo(network, layer);
|
21
|
+
const fileContents = getLayerEnvFileContent(layer, { ...envInfo, ...envNetworkInfo, ...envLayerInfo });
|
18
22
|
clm.debug(`Writing layer env file: ${filePath}`);
|
19
23
|
fs.writeFileSync(filePath, fileContents);
|
20
24
|
}
|
21
25
|
},
|
22
|
-
|
23
|
-
const { projectDir } = configStore.getProjectInfo();
|
24
|
-
const possibleLayers = ['gl0', 'gl1', 'ml0', 'cl1', 'dl1'];
|
25
|
-
for (const n of possibleLayers) {
|
26
|
-
const filePath = path.join(projectDir, 'layers', `${n}.env`);
|
27
|
-
if (fs.existsSync(filePath)) {
|
28
|
-
configStore.setEnvLayerInfo(n, configHelper.parseEnvFile(filePath));
|
29
|
-
}
|
30
|
-
}
|
31
|
-
},
|
32
|
-
async importNetworkEnvFiles() {
|
26
|
+
importEnvFiles() {
|
33
27
|
const { projectDir } = configStore.getProjectInfo();
|
34
28
|
const possibleNetworks = ['mainnet', 'testnet', 'integrationnet'];
|
29
|
+
const possibleLayers = ['gl0', 'gl1', 'ml0', 'cl1', 'dl1'];
|
35
30
|
const supportedTypes = [];
|
36
|
-
const
|
37
|
-
|
38
|
-
const filePath = path.join(projectDir, 'networks', `${n}.env`);
|
31
|
+
for (const network of possibleNetworks) {
|
32
|
+
const filePath = path.join(projectDir, 'networks', network, 'network.env');
|
39
33
|
if (fs.existsSync(filePath)) {
|
40
|
-
supportedTypes.push(
|
41
|
-
|
34
|
+
supportedTypes.push(network);
|
35
|
+
configStore.setEnvNetworkInfo(network, configHelper.parseEnvFile(filePath));
|
36
|
+
}
|
37
|
+
for (const layer of possibleLayers) {
|
38
|
+
const filePath = path.join(projectDir, 'networks', network, `${layer}.env`);
|
39
|
+
if (fs.existsSync(filePath)) {
|
40
|
+
configStore.setEnvLayerInfo(network, layer, configHelper.parseEnvFile(filePath));
|
41
|
+
}
|
42
42
|
}
|
43
43
|
}
|
44
44
|
if (supportedTypes.length === 0) {
|
45
45
|
clm.error('No supported networks found in the project folder.');
|
46
46
|
}
|
47
47
|
configStore.setNetworkInfo({ supportedTypes });
|
48
|
-
configStore.setNetworkEnvInfo(networkEnvInfo);
|
49
48
|
// eslint-disable-next-line no-warning-comments
|
50
49
|
// TODO: verify all required env variables are present
|
51
50
|
},
|
@@ -54,10 +53,7 @@ export const projectHelper = {
|
|
54
53
|
if (!fs.existsSync(projectFolder)) {
|
55
54
|
clm.error(`Project folder not found: ${projectFolder}`);
|
56
55
|
}
|
57
|
-
await
|
58
|
-
const { projectDir } = configStore.getProjectInfo();
|
59
|
-
clm.debug(`Installing project from ${projectFolder} to ${projectDir}`);
|
60
|
-
fs.cpSync(projectFolder, projectDir, { recursive: true });
|
56
|
+
await this.installProject(name, projectFolder);
|
61
57
|
},
|
62
58
|
// curl -s https://api.github.com/repos/Constellation-Labs/pacaswap-metagraph/releases/latest | jq -r '.assets[] | select(.name | contains("node-pilot"))'
|
63
59
|
// use .tag_name for the release version
|
@@ -72,14 +68,69 @@ export const projectHelper = {
|
|
72
68
|
fs.mkdirSync(path.join(gl0DataDir, 'incremental_snapshot'), { recursive: true });
|
73
69
|
fs.mkdirSync(path.join(gl0DataDir, 'snapshot_info'));
|
74
70
|
fs.mkdirSync(path.join(gl0DataDir, 'tmp'));
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
71
|
+
this.importEnvFiles();
|
72
|
+
},
|
73
|
+
async installProject(name, projectFolder) {
|
74
|
+
if (!configStore.hasProjects()) {
|
75
|
+
// On first install, copy scripts
|
76
|
+
const scriptsFolder = path.resolve(path.dirname(fileURLToPath(import.meta.url)), `../../scripts`);
|
77
|
+
const projectDir = path.join(configStore.getAppDir(), 'scripts');
|
78
|
+
clm.debug(`Installing node pilot scripts from ${scriptsFolder} to ${projectDir}`);
|
79
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
80
|
+
fs.cpSync(scriptsFolder, projectDir, { recursive: true });
|
81
|
+
}
|
82
|
+
await configStore.applyNewProjectStore(name);
|
83
|
+
const { projectDir } = configStore.getProjectInfo();
|
84
|
+
clm.debug(`Installing project from ${projectFolder} to ${projectDir}`);
|
85
|
+
fs.cpSync(projectFolder, projectDir, { recursive: true });
|
86
|
+
},
|
87
|
+
async selectProject() {
|
88
|
+
// prompt user to install hypergraph or metagraph
|
89
|
+
const networkType = await select({
|
90
|
+
choices: [
|
91
|
+
{ name: 'Hypergraph (Global layer)', value: 'hypergraph' },
|
92
|
+
{ disabled: true, name: 'Metagraph (Dor, Pacaswap, and more)', value: 'metagraph' },
|
93
|
+
],
|
94
|
+
message: 'Select network:'
|
81
95
|
});
|
82
|
-
|
83
|
-
|
96
|
+
if (networkType === 'hypergraph') {
|
97
|
+
await this.installHypergraph();
|
98
|
+
}
|
99
|
+
else if (networkType === 'metagraph') {
|
100
|
+
const project = await select({
|
101
|
+
choices: [
|
102
|
+
{ name: 'Dor', value: 'dor' },
|
103
|
+
{ name: 'Custom (Enter repo)', value: 'custom' },
|
104
|
+
],
|
105
|
+
message: 'Select metagraph:'
|
106
|
+
});
|
107
|
+
const ghRepoRegex = /^https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9](?:-?[A-Za-z0-9]){0,38})\/([A-Za-z0-9._-]+)(?:\.git)?\/?$/;
|
108
|
+
if (project === 'dor') {
|
109
|
+
await this.installEmbedded('dor');
|
110
|
+
}
|
111
|
+
else if (project === 'custom') {
|
112
|
+
let repo = await input({
|
113
|
+
message: `Enter Github repository URL:`,
|
114
|
+
validate(value) {
|
115
|
+
const m = value.trim().match(ghRepoRegex);
|
116
|
+
if (m)
|
117
|
+
return true;
|
118
|
+
return 'Please enter a valid Github repo URL (e.g., https://github.com/user/repo or .git)';
|
119
|
+
}
|
120
|
+
});
|
121
|
+
if (repo.endsWith('.git'))
|
122
|
+
repo = repo.slice(0, -4);
|
123
|
+
const m = repo.trim().match(ghRepoRegex);
|
124
|
+
const userRepo = `${m[1]}/${m[2]}`; // owner/repo
|
125
|
+
clm.preStep(`Installing from Github repository: ${chalk.cyan(userRepo)}`);
|
126
|
+
if (await githubService.hasAssetInRelease('node-pilot', userRepo)) {
|
127
|
+
await this.installFromGithub(userRepo);
|
128
|
+
}
|
129
|
+
else {
|
130
|
+
clm.warn(`The repository ${repo} does not contain a release asset with the name "node-pilot"`);
|
131
|
+
await this.selectProject();
|
132
|
+
}
|
133
|
+
}
|
134
|
+
}
|
84
135
|
}
|
85
136
|
};
|
@@ -1,8 +1,6 @@
|
|
1
1
|
export declare const promptHelper: {
|
2
|
-
configureAutoRestart(): Promise<void>;
|
3
2
|
configureJavaMemoryArguments(): Promise<void>;
|
4
3
|
doYouWishToContinue(defaultAnswer?: "n" | "y"): Promise<void>;
|
5
4
|
selectLayers(): Promise<void>;
|
6
5
|
selectNetwork(): Promise<void>;
|
7
|
-
selectProject(): Promise<void>;
|
8
6
|
};
|
@@ -2,25 +2,17 @@ import { checkbox, input, number, select } from "@inquirer/prompts";
|
|
2
2
|
import chalk from "chalk";
|
3
3
|
import { clm } from "../clm.js";
|
4
4
|
import { configStore } from "../config-store.js";
|
5
|
-
import { githubHelper } from "./github-helper.js";
|
6
|
-
import { projectHelper } from "./project-helper.js";
|
7
5
|
export const promptHelper = {
|
8
|
-
async configureAutoRestart() {
|
9
|
-
const answer = await input({
|
10
|
-
default: 'y',
|
11
|
-
message: 'Do you want to enable auto-restart? (y/n): '
|
12
|
-
});
|
13
|
-
configStore.setProjectInfo({ autoRestart: answer === 'y' });
|
14
|
-
},
|
15
6
|
async configureJavaMemoryArguments() {
|
16
7
|
const { memory } = configStore.getSystemInfo();
|
17
8
|
const { layersToRun, name } = configStore.getProjectInfo();
|
9
|
+
const { type: currentNetwork } = configStore.getNetworkInfo();
|
18
10
|
const xmx = Number(memory);
|
19
11
|
if (xmx === 8 && layersToRun.length > 1) {
|
20
12
|
clm.warn('Minimum 8GB memory detected. Only a single layer will be allowed to run');
|
21
13
|
await promptHelper.doYouWishToContinue();
|
22
14
|
configStore.setProjectInfo({ layersToRun: [layersToRun[0]] });
|
23
|
-
configStore.setEnvLayerInfo(layersToRun[0], { CL_DOCKER_JAVA_OPTS: '-Xms1024M -Xmx7G -Xss256K' });
|
15
|
+
configStore.setEnvLayerInfo(currentNetwork, layersToRun[0], { CL_DOCKER_JAVA_OPTS: '-Xms1024M -Xmx7G -Xss256K' });
|
24
16
|
}
|
25
17
|
else if (name === 'hypergraph') {
|
26
18
|
// prompt to use all detected memory
|
@@ -33,11 +25,16 @@ export const promptHelper = {
|
|
33
25
|
answer--;
|
34
26
|
const subLayerMem = layersToRun.length > 1 ? Math.floor(answer / 3) : 0;
|
35
27
|
const mainLayerMem = answer - subLayerMem;
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
clm.postStep
|
40
|
-
|
28
|
+
const { supportedTypes } = configStore.getNetworkInfo();
|
29
|
+
for (const type of supportedTypes) {
|
30
|
+
const network = type.toUpperCase();
|
31
|
+
const logMethod = type === currentNetwork ? clm.postStep : clm.debug;
|
32
|
+
logMethod(`${network}:: ${layersToRun[0]} memory allocation: ${mainLayerMem}GB`);
|
33
|
+
configStore.setEnvLayerInfo(type, layersToRun[0], { CL_DOCKER_JAVA_OPTS: `-Xms1024M -Xmx${mainLayerMem}G -Xss256K` });
|
34
|
+
if (subLayerMem) {
|
35
|
+
logMethod(`${network}:: ${layersToRun[1]} memory allocation: ${subLayerMem}GB`);
|
36
|
+
configStore.setEnvLayerInfo(type, layersToRun[1], { CL_DOCKER_JAVA_OPTS: `-Xms1024M -Xmx${subLayerMem}G -Xss256K` });
|
37
|
+
}
|
41
38
|
}
|
42
39
|
}
|
43
40
|
},
|
@@ -69,7 +66,7 @@ export const promptHelper = {
|
|
69
66
|
}
|
70
67
|
if (supportedTypes.length === 1) {
|
71
68
|
configStore.setNetworkInfo({ type: supportedTypes[0], version: "latest" });
|
72
|
-
configStore.
|
69
|
+
// configStore.setEnvNetworkInfo(configStore.getNetworkEnvInfo(supportedTypes[0]));
|
73
70
|
return;
|
74
71
|
}
|
75
72
|
const networkType = await select({
|
@@ -81,55 +78,7 @@ export const promptHelper = {
|
|
81
78
|
message: 'Select network type:'
|
82
79
|
});
|
83
80
|
configStore.setNetworkInfo({ type: networkType, version: "latest" });
|
84
|
-
configStore.
|
85
|
-
|
86
|
-
async selectProject() {
|
87
|
-
// prompt user to install hypergraph or metagraph
|
88
|
-
const networkType = await select({
|
89
|
-
choices: [
|
90
|
-
{ name: 'Hypergraph (Global layer)', value: 'hypergraph' },
|
91
|
-
{ disabled: true, name: 'Metagraph (Dor, Pacaswap, and more)', value: 'metagraph' },
|
92
|
-
],
|
93
|
-
message: 'Select network:'
|
94
|
-
});
|
95
|
-
if (networkType === 'hypergraph') {
|
96
|
-
await projectHelper.installHypergraph();
|
97
|
-
}
|
98
|
-
else if (networkType === 'metagraph') {
|
99
|
-
const project = await select({
|
100
|
-
choices: [
|
101
|
-
{ name: 'Dor', value: 'dor' },
|
102
|
-
{ name: 'Custom (Enter repo)', value: 'custom' },
|
103
|
-
],
|
104
|
-
message: 'Select metagraph:'
|
105
|
-
});
|
106
|
-
const ghRepoRegex = /^https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9](?:-?[A-Za-z0-9]){0,38})\/([A-Za-z0-9._-]+)(?:\.git)?\/?$/;
|
107
|
-
if (project === 'dor') {
|
108
|
-
await projectHelper.installEmbedded('dor');
|
109
|
-
}
|
110
|
-
else if (project === 'custom') {
|
111
|
-
let repo = await input({
|
112
|
-
message: `Enter Github repository URL:`,
|
113
|
-
validate(value) {
|
114
|
-
const m = value.trim().match(ghRepoRegex);
|
115
|
-
if (m)
|
116
|
-
return true;
|
117
|
-
return 'Please enter a valid Github repo URL (e.g., https://github.com/user/repo or .git)';
|
118
|
-
}
|
119
|
-
});
|
120
|
-
if (repo.endsWith('.git'))
|
121
|
-
repo = repo.slice(0, -4);
|
122
|
-
const m = repo.trim().match(ghRepoRegex);
|
123
|
-
const userRepo = `${m[1]}/${m[2]}`; // owner/repo
|
124
|
-
clm.preStep(`Installing from Github repository: ${chalk.cyan(userRepo)}`);
|
125
|
-
if (await githubHelper.hasAssetInRelease('node-pilot', userRepo)) {
|
126
|
-
await projectHelper.installFromGithub(userRepo);
|
127
|
-
}
|
128
|
-
else {
|
129
|
-
clm.warn(`The repository ${repo} does not contain a release asset with the name "node-pilot"`);
|
130
|
-
await this.selectProject();
|
131
|
-
}
|
132
|
-
}
|
133
|
-
}
|
81
|
+
configStore.setProjectFlag('duplicateNodeIdChecked', false);
|
82
|
+
configStore.setProjectFlag('seedListChecked', false);
|
134
83
|
}
|
135
84
|
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
export declare const archiverService: {
|
2
|
+
checkLogsForMissingSnapshots(): Promise<void>;
|
3
|
+
getArchiveSnapshotInfo(): Promise<{
|
4
|
+
clusterOrdinal: number;
|
5
|
+
distance: number;
|
6
|
+
endOrdinal: number;
|
7
|
+
startOrdinal: number;
|
8
|
+
total: number;
|
9
|
+
url: string;
|
10
|
+
}>;
|
11
|
+
getDownloadedSnapshotRange(): {
|
12
|
+
chunkOrdinal: number;
|
13
|
+
endOrdinal: number;
|
14
|
+
startOrdinal: number;
|
15
|
+
};
|
16
|
+
syncToLatestSnapshot(): Promise<void>;
|
17
|
+
};
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import chalk from "chalk";
|
2
|
+
import fs from "node:fs";
|
3
|
+
import path from "node:path";
|
4
|
+
import { clm } from "../clm.js";
|
5
|
+
import { configStore } from "../config-store.js";
|
6
|
+
import { clusterService } from "./cluster-service.js";
|
7
|
+
import { FastforwardService } from "./fastforward-service.js";
|
8
|
+
import { shellService } from "./shell-service.js";
|
9
|
+
// http://5.161.243.241:7777/hash.txt
|
10
|
+
const remoteIndexMap = {
|
11
|
+
integrationnet: "http://5.161.243.241:7777",
|
12
|
+
mainnet: "http://128.140.33.142:7777",
|
13
|
+
testnet: "http://65.108.87.84:7777"
|
14
|
+
};
|
15
|
+
export const archiverService = {
|
16
|
+
async checkLogsForMissingSnapshots() {
|
17
|
+
const { projectDir } = configStore.getProjectInfo();
|
18
|
+
const dataDir = path.join(projectDir, 'app-data', 'gl0-logs', 'app.log');
|
19
|
+
const result = await shellService.runCommandWithOutput(`grep -i 'Global snapshot not found for ordinal' ${dataDir}`).catch(() => '');
|
20
|
+
let oldestOrdinal = Number.MAX_SAFE_INTEGER;
|
21
|
+
for (const line of result.split('\n')) {
|
22
|
+
const number = line.match(/\d+/);
|
23
|
+
if (number) {
|
24
|
+
oldestOrdinal = Math.min(oldestOrdinal, Number(number[0]));
|
25
|
+
}
|
26
|
+
}
|
27
|
+
console.log(`Oldest ordinal: ${oldestOrdinal}`);
|
28
|
+
},
|
29
|
+
async getArchiveSnapshotInfo() {
|
30
|
+
const { type } = configStore.getNetworkInfo();
|
31
|
+
const clusterOrdinal = await clusterService.getSourceNodeLatestOrdinal('gl0');
|
32
|
+
return fetch(remoteIndexMap[type] + '/hash.txt')
|
33
|
+
.then(res => res.text())
|
34
|
+
.then(txt => {
|
35
|
+
const lines = txt.trim().split('\n');
|
36
|
+
const lastLine = lines.at(-1);
|
37
|
+
const filename = lastLine.split(' ')[1];
|
38
|
+
const parseName = filename.split('.')[0].split('-');
|
39
|
+
const startOrdinal = Number(parseName.at(1)?.slice(1));
|
40
|
+
const endOrdinal = parseName.length < 4 ? startOrdinal + 20_000 - 1 : Number(parseName.at(3)?.slice(1));
|
41
|
+
const distance = clusterOrdinal - endOrdinal;
|
42
|
+
const total = endOrdinal - startOrdinal + 1;
|
43
|
+
clm.debug(`Cluster Ordinal: ${chalk.yellow(clusterOrdinal)}, Archive End Ordinal: ${chalk.yellow(endOrdinal)}, Total Archive Snapshots: ${chalk.yellow(total)}, Distance: ${chalk.yellow(distance)}`);
|
44
|
+
return { clusterOrdinal, distance, endOrdinal, startOrdinal, total, url: remoteIndexMap[type] + '/' + filename };
|
45
|
+
});
|
46
|
+
},
|
47
|
+
getDownloadedSnapshotRange() {
|
48
|
+
const { projectDir } = configStore.getProjectInfo();
|
49
|
+
const dataDir = path.join(projectDir, 'app-data', 'gl0-data', 'incremental_snapshot', 'ordinal');
|
50
|
+
const result = { chunkOrdinal: 0, endOrdinal: 0, startOrdinal: 0 };
|
51
|
+
if (!fs.existsSync(dataDir)) {
|
52
|
+
return result;
|
53
|
+
}
|
54
|
+
// get last filename in directory
|
55
|
+
let files = fs.readdirSync(dataDir);
|
56
|
+
if (files.length === 0)
|
57
|
+
return result;
|
58
|
+
const latestChunk = files.sort().at(-1);
|
59
|
+
if (!latestChunk)
|
60
|
+
return result;
|
61
|
+
result.chunkOrdinal = Number(latestChunk);
|
62
|
+
files = fs.readdirSync(path.join(dataDir, latestChunk));
|
63
|
+
if (files.length === 0)
|
64
|
+
return result;
|
65
|
+
const filesSorted = files.sort();
|
66
|
+
const firstFile = filesSorted.at(0);
|
67
|
+
const lastFile = filesSorted.at(-1);
|
68
|
+
result.startOrdinal = Number(firstFile);
|
69
|
+
result.endOrdinal = Number(lastFile);
|
70
|
+
return result;
|
71
|
+
},
|
72
|
+
async syncToLatestSnapshot() {
|
73
|
+
const { clusterOrdinal, distance: archiveDistance, endOrdinal: remoteArchiveEndOrdinal, startOrdinal: remoteArchiveStartOrdinal, total, url } = await this.getArchiveSnapshotInfo();
|
74
|
+
if (archiveDistance > 1000) {
|
75
|
+
clm.preStep('Archive is far behind cluster. Initiating fast forward...');
|
76
|
+
await FastforwardService.synctoLatestSnapshot();
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
const { endOrdinal: localEndOrdinal, startOrdinal: localStartOrdinal } = this.getDownloadedSnapshotRange();
|
80
|
+
const localDistanceFromCluster = clusterOrdinal - localEndOrdinal;
|
81
|
+
// if archive can improve local's snapshot range
|
82
|
+
const needToSync = remoteArchiveStartOrdinal < localStartOrdinal || remoteArchiveEndOrdinal > localEndOrdinal;
|
83
|
+
if (!needToSync) {
|
84
|
+
clm.step(`Already near latest ordinal. Skipping sync. Distance: ${localDistanceFromCluster}`);
|
85
|
+
return;
|
86
|
+
}
|
87
|
+
const requiredOldestOrdinal = clusterOrdinal - 10_000;
|
88
|
+
const archiveStartOrdinalMeetsOldestRequirement = remoteArchiveStartOrdinal <= requiredOldestOrdinal;
|
89
|
+
if (!archiveStartOrdinalMeetsOldestRequirement) {
|
90
|
+
clm.preStep('Archive is not in the optimal range, but proceeding with available data.');
|
91
|
+
}
|
92
|
+
const { projectDir } = configStore.getProjectInfo();
|
93
|
+
const dataDir = path.join(projectDir, 'app-data', 'gl0-data');
|
94
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
95
|
+
clm.preStep(`Downloading latest snapshot archive ${chalk.yellow(remoteArchiveStartOrdinal)}-${chalk.yellow(remoteArchiveEndOrdinal)}; distance from cluster: ${chalk.yellow(archiveDistance)}\nCurrent oldest local ordinal: ${chalk.yellow(localStartOrdinal)}, Latest cluster ordinal: ${chalk.yellow(clusterOrdinal)}`);
|
96
|
+
// await shellService.runCommand(`curl -L ${url} -o ${dataDir}/snapshot.tar.gz`);
|
97
|
+
await shellService.runCommand(`wget --progress=bar:force -O ${dataDir}/snapshot.tar.gz ${url}`);
|
98
|
+
clm.preStep(`Extracting snapshot...`);
|
99
|
+
await shellService.runCommand(`tar -xf ${dataDir}/snapshot.tar.gz -C ${dataDir}`);
|
100
|
+
await shellService.runCommand(`rm ${dataDir}/snapshot.tar.gz`);
|
101
|
+
clm.postStep(`Total snapshots downloaded: ${chalk.yellow(total)}, Synced to ordinal: ${chalk.yellow(remoteArchiveEndOrdinal)}, Cluster Ordinal: ${chalk.yellow(clusterOrdinal)}, Distance from cluster: ${chalk.yellow(archiveDistance)}`);
|
102
|
+
clm.postStep(`Snapshot downloaded and extracted successfully.`);
|
103
|
+
}
|
104
|
+
};
|
@@ -1,9 +1,14 @@
|
|
1
|
-
import { ClusterInfo, NodeInfo } from "../types.js";
|
1
|
+
import { ClusterConsensusInfo, ClusterInfo, NodeInfo, TessellationLayer } from "../types.js";
|
2
2
|
export declare const clusterService: {
|
3
3
|
fastForwardSnapshot(): Promise<void>;
|
4
|
-
getClusterInfo(): Promise<ClusterInfo[]>;
|
5
|
-
|
6
|
-
|
4
|
+
getClusterInfo(layer?: TessellationLayer): Promise<ClusterInfo[]>;
|
5
|
+
getClusterNodeInfo(layer?: TessellationLayer): Promise<NodeInfo>;
|
6
|
+
getLatestConsensusInfo(layer?: TessellationLayer): Promise<ClusterConsensusInfo>;
|
7
|
+
getLayer0(): "gl0" | "ml0";
|
7
8
|
getReleaseVersion(): Promise<string>;
|
8
|
-
getSourceNodeInfo(): Promise<NodeInfo>;
|
9
|
+
getSourceNodeInfo(layer: TessellationLayer): Promise<NodeInfo>;
|
10
|
+
getSourceNodeLatestOrdinal(layer: TessellationLayer): Promise<number>;
|
11
|
+
getSourceNodeOrdinalHash(layer: TessellationLayer, ordinal: number): Promise<string>;
|
12
|
+
makeClusterRequest(path: string, layer?: TessellationLayer): Promise<any>;
|
13
|
+
makeSourceNodeRequest(path: string, layer: TessellationLayer): Promise<any>;
|
9
14
|
};
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { input } from "@inquirer/prompts";
|
2
|
+
import { clm } from "../clm.js";
|
2
3
|
import { configStore } from "../config-store.js";
|
3
4
|
import { FastforwardService } from "./fastforward-service.js";
|
4
5
|
export const clusterService = {
|
@@ -21,47 +22,56 @@ export const clusterService = {
|
|
21
22
|
configStore.setProjectInfo({ fastForward: true });
|
22
23
|
}
|
23
24
|
await FastforwardService.synctoLatestSnapshot();
|
25
|
+
// await archiverService.syncToLatestSnapshot()
|
26
|
+
// .catch(() => {
|
27
|
+
// clm.warn(`Failed to download latest snapshots using Starchiver. Using fast forward to latest snapshot.`);
|
28
|
+
// clusterService.fastForwardSnapshot();
|
29
|
+
// })
|
24
30
|
},
|
25
|
-
async getClusterInfo() {
|
26
|
-
|
27
|
-
return fetch(`https://l0-lb-${type}.constellationnetwork.io/cluster/info`)
|
28
|
-
.then(res => {
|
29
|
-
if (res.ok)
|
30
|
-
return res.json();
|
31
|
-
throw new Error(`Failed`);
|
32
|
-
})
|
33
|
-
.catch(() => []);
|
31
|
+
async getClusterInfo(layer) {
|
32
|
+
return this.makeClusterRequest('cluster/info', layer);
|
34
33
|
},
|
35
|
-
async
|
36
|
-
|
37
|
-
return fetch(`https://l0-lb-${type}.constellationnetwork.io/global-snapshots/latest`)
|
38
|
-
.then(res => {
|
39
|
-
if (res.ok)
|
40
|
-
return res.json().then(i => (i?.value?.ordinal || 0));
|
41
|
-
return 0;
|
42
|
-
})
|
43
|
-
.catch(() => 0);
|
34
|
+
async getClusterNodeInfo(layer) {
|
35
|
+
return this.makeClusterRequest('node/info', layer);
|
44
36
|
},
|
45
|
-
async
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
return res.json();
|
51
|
-
throw new Error(`Failed`);
|
52
|
-
})
|
53
|
-
.catch(() => ({ state: "Unavailable" }));
|
37
|
+
async getLatestConsensusInfo(layer) {
|
38
|
+
return this.makeClusterRequest('consensus/latest/peers', layer);
|
39
|
+
},
|
40
|
+
getLayer0() {
|
41
|
+
return configStore.getProjectInfo().layersToRun.includes('gl0') ? 'gl0' : 'ml0';
|
54
42
|
},
|
55
43
|
async getReleaseVersion() {
|
56
|
-
return this.
|
44
|
+
return this.getClusterNodeInfo().then(i => i.version);
|
45
|
+
},
|
46
|
+
async getSourceNodeInfo(layer) {
|
47
|
+
return this.makeSourceNodeRequest('node/info', layer);
|
48
|
+
},
|
49
|
+
async getSourceNodeLatestOrdinal(layer) {
|
50
|
+
return this.makeSourceNodeRequest('global-snapshots/latest', layer).then(i => i.value.ordinal);
|
57
51
|
},
|
58
|
-
async
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
52
|
+
async getSourceNodeOrdinalHash(layer, ordinal) {
|
53
|
+
return this.makeSourceNodeRequest(`global-snapshots/${ordinal}/hash`, layer);
|
54
|
+
},
|
55
|
+
async makeClusterRequest(path, layer) {
|
56
|
+
layer = layer || this.getLayer0();
|
57
|
+
const { type } = configStore.getNetworkInfo();
|
58
|
+
const envLayerInfo = configStore.getEnvLayerInfo(type, layer);
|
59
|
+
if (envLayerInfo.CL_LB) {
|
60
|
+
return fetch(`${envLayerInfo.CL_LB}/${path}`)
|
61
|
+
.then(res => res.json())
|
62
|
+
.catch(() => {
|
63
|
+
clm.debug(`Failed to get node info from ${envLayerInfo.CL_LB}. Attempting source node...`);
|
64
|
+
return this.makeSourceNodeRequest(path, layer);
|
65
|
+
});
|
66
|
+
}
|
67
|
+
return this.makeSourceNodeRequest(path, layer);
|
68
|
+
},
|
69
|
+
async makeSourceNodeRequest(path, layer) {
|
70
|
+
const { type } = configStore.getNetworkInfo();
|
71
|
+
const { CL_PUBLIC_HTTP_PORT } = configStore.getEnvLayerInfo(type, layer);
|
72
|
+
const { CL_L0_PEER_HTTP_HOST } = configStore.getEnvNetworkInfo(type);
|
73
|
+
clm.debug(`http://${CL_L0_PEER_HTTP_HOST}:${CL_PUBLIC_HTTP_PORT}/${path}`);
|
74
|
+
return fetch(`http://${CL_L0_PEER_HTTP_HOST}:${CL_PUBLIC_HTTP_PORT}/${path}`)
|
75
|
+
.then(res => res.json());
|
66
76
|
}
|
67
77
|
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { TessellationLayer } from "../types.js";
|
2
|
+
export declare const dockerService: {
|
3
|
+
dockerBuild(): Promise<void>;
|
4
|
+
dockerDown(): Promise<void>;
|
5
|
+
dockerRestart(layer: TessellationLayer): Promise<void>;
|
6
|
+
dockerStartLayers(layers: TessellationLayer[]): Promise<void>;
|
7
|
+
dockerUp(): Promise<void>;
|
8
|
+
isRunning(): Promise<boolean>;
|
9
|
+
};
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import ora from "ora";
|
2
|
+
import { clm } from "../clm.js";
|
3
|
+
import { configStore } from "../config-store.js";
|
4
|
+
import { projectHelper } from "../helpers/project-helper.js";
|
5
|
+
import { shellService } from "./shell-service.js";
|
6
|
+
export const dockerService = {
|
7
|
+
async dockerBuild() {
|
8
|
+
if (shellService.existsProjectScript('scripts/docker-build.sh')) {
|
9
|
+
const silent = !process.env.DEBUG;
|
10
|
+
const spinner = ora('Building the node container...');
|
11
|
+
if (silent) {
|
12
|
+
spinner.start();
|
13
|
+
spinner.color = 'green';
|
14
|
+
}
|
15
|
+
else {
|
16
|
+
clm.preStep('Building the node container...');
|
17
|
+
}
|
18
|
+
await shellService.runProjectCommand('bash scripts/docker-build.sh', undefined, silent);
|
19
|
+
if (silent) {
|
20
|
+
spinner.stop();
|
21
|
+
}
|
22
|
+
clm.postStep('Node container built.');
|
23
|
+
}
|
24
|
+
},
|
25
|
+
async dockerDown() {
|
26
|
+
await run('down');
|
27
|
+
},
|
28
|
+
async dockerRestart(layer) {
|
29
|
+
await run('restart', [layer]);
|
30
|
+
},
|
31
|
+
async dockerStartLayers(layers) {
|
32
|
+
await run('up -d', layers);
|
33
|
+
},
|
34
|
+
async dockerUp() {
|
35
|
+
// If docker is already running, stop it
|
36
|
+
if (await this.isRunning()) {
|
37
|
+
await this.dockerDown();
|
38
|
+
}
|
39
|
+
await projectHelper.generateLayerEnvFiles();
|
40
|
+
// const userId = await shellService.runCommandWithOutput('echo "$(id -u):$(id -g)"')
|
41
|
+
// console.log('Setting DOCKER_USER_ID to', userId);
|
42
|
+
// configStore.setDockerEnvInfo({ DOCKER_USER_ID: userId });
|
43
|
+
await run('up -d');
|
44
|
+
},
|
45
|
+
async isRunning() {
|
46
|
+
return shellService.runCommand('docker ps | grep entrypoint.sh', undefined, true).then(Boolean).catch(() => false);
|
47
|
+
}
|
48
|
+
};
|
49
|
+
function run(command, layers) {
|
50
|
+
const { layersToRun } = configStore.getProjectInfo();
|
51
|
+
const args = (layers || layersToRun).map(l => `--profile ${l}`).join(' ');
|
52
|
+
return shellService.runProjectCommand(`docker compose ${args} ${command}`, configStore.getDockerEnvInfo());
|
53
|
+
}
|
@@ -19,9 +19,9 @@ export class FastforwardService {
|
|
19
19
|
this.dataDir = path.join(projectDir, 'app-data', 'gl0-data'); // gl0
|
20
20
|
fs.mkdirSync(this.tmpDir, { recursive: true });
|
21
21
|
fs.mkdirSync(this.dataDir, { recursive: true });
|
22
|
-
const env = configStore.
|
22
|
+
const env = configStore.getEnvNetworkInfo(type);
|
23
23
|
// this.lbUrl = `https://l0-lb-${this.network}.constellationnetwork.io`;
|
24
|
-
this.lbUrl = `http://${env.
|
24
|
+
this.lbUrl = `http://${env.CL_L0_PEER_HTTP_HOST}:${env.CL_L0_PEER_HTTP_PORT}`;
|
25
25
|
}
|
26
26
|
static async synctoLatestSnapshot() {
|
27
27
|
const ffs = new FastforwardService();
|
@@ -38,17 +38,23 @@ export class FastforwardService {
|
|
38
38
|
fs.writeFileSync(path.join(snapshotInfoDir, ordinal.toString()), compressedSnapshotInfo);
|
39
39
|
fs.writeFileSync(path.join(this.tmpDir, ordinal.toString() + '.c'), compressedSnapshotIncremental);
|
40
40
|
await this.saveSnapshotFiles(ordinal.toString(), hash);
|
41
|
-
clm.postStep(
|
41
|
+
clm.postStep(`Fastforward to snapshot "${ordinal}" completed.`);
|
42
42
|
}
|
43
43
|
async fetchLatestSnapshot() {
|
44
44
|
const url = `${this.lbUrl}/global-snapshots/latest/combined`;
|
45
45
|
clm.debug('Fetching latest snapshot ordinal from: ' + chalk.cyan(url));
|
46
|
-
return fetch(url)
|
46
|
+
return fetch(url)
|
47
|
+
.then(res => {
|
48
|
+
if (!res.ok) {
|
49
|
+
throw new Error(`Failed to fetch latest snapshot ordinal: ${res.statusText}`);
|
50
|
+
}
|
51
|
+
return res.json();
|
52
|
+
})
|
47
53
|
.then(data => {
|
48
54
|
const { ordinal } = data[0].value;
|
49
55
|
clm.debug('fetchLatestSnapshot - ' + chalk.cyan(ordinal));
|
50
56
|
return [ordinal, data[0], data[1]];
|
51
|
-
})
|
57
|
+
});
|
52
58
|
}
|
53
59
|
async fetchSnapshot(ordinal) {
|
54
60
|
const url = `${this.lbUrl}/global-snapshots/${ordinal}`;
|
@@ -57,7 +63,7 @@ export class FastforwardService {
|
|
57
63
|
}
|
58
64
|
async fetchSnapshotHash(ordinal) {
|
59
65
|
const url = `${this.lbUrl}/global-snapshots/${ordinal}/hash`;
|
60
|
-
const hash = await fetch(url).then(res => res.
|
66
|
+
const hash = await fetch(url).then(res => res.json());
|
61
67
|
clm.debug('fetchLatestSnapshotHash: ' + chalk.cyan(hash));
|
62
68
|
return hash;
|
63
69
|
}
|