@lightprotocol/zk-compression-cli 0.27.1-alpha.9 → 0.28.0-beta.10
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/account_compression.so +0 -0
- package/bin/forester +0 -0
- package/bin/forester.toml +15 -0
- package/bin/light_compressed_token.so +0 -0
- package/bin/light_registry.so +0 -0
- package/bin/light_system_program_pinocchio.so +0 -0
- package/bin/test_batched_cpi_context_7Hp52chxaew8bW1ApR4fck2bh6Y8qA1pu3qwH6N9zaLj.json +1 -0
- package/dist/commands/create-token-pool/index.js +1 -1
- package/dist/commands/test-validator/index.d.ts +10 -0
- package/dist/commands/test-validator/index.js +66 -9
- package/dist/utils/constants.d.ts +4 -4
- package/dist/utils/constants.js +7 -6
- package/dist/utils/downloadProverBinary.js +3 -4
- package/dist/utils/initTestEnv.d.ts +43 -4
- package/dist/utils/initTestEnv.js +279 -55
- package/dist/utils/photonVersion.generated.d.ts +3 -0
- package/dist/utils/photonVersion.generated.js +7 -0
- package/dist/utils/process.d.ts +1 -1
- package/dist/utils/process.js +26 -11
- package/dist/utils/processForester.d.ts +23 -0
- package/dist/utils/processForester.js +88 -0
- package/dist/utils/processPhotonIndexer.d.ts +1 -1
- package/dist/utils/processPhotonIndexer.js +7 -11
- package/dist/utils/processProverServer.js +1 -0
- package/dist/utils/proverVersion.generated.d.ts +1 -0
- package/dist/utils/proverVersion.generated.js +5 -0
- package/oclif.manifest.json +204 -156
- package/package.json +7 -43
|
@@ -7,17 +7,22 @@ exports.initTestEnvIfNeeded = initTestEnvIfNeeded;
|
|
|
7
7
|
exports.programsDirPath = programsDirPath;
|
|
8
8
|
exports.programFilePath = programFilePath;
|
|
9
9
|
exports.getSolanaArgs = getSolanaArgs;
|
|
10
|
+
exports.getSurfpoolArgs = getSurfpoolArgs;
|
|
10
11
|
exports.startTestValidator = startTestValidator;
|
|
11
12
|
exports.killTestValidator = killTestValidator;
|
|
12
13
|
const tslib_1 = require("tslib");
|
|
13
14
|
const utils_1 = require("./utils");
|
|
14
15
|
const constants_1 = require("./constants");
|
|
16
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
15
17
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
18
|
+
const os_1 = tslib_1.__importDefault(require("os"));
|
|
16
19
|
const psp_utils_1 = require("../psp-utils");
|
|
17
20
|
const process_1 = require("./process");
|
|
18
21
|
const processProverServer_1 = require("./processProverServer");
|
|
19
22
|
const processPhotonIndexer_1 = require("./processPhotonIndexer");
|
|
23
|
+
const processForester_1 = require("./processForester");
|
|
20
24
|
const web3_js_1 = require("@solana/web3.js");
|
|
25
|
+
const child_process_1 = require("child_process");
|
|
21
26
|
exports.SYSTEM_PROGRAMS = [
|
|
22
27
|
{
|
|
23
28
|
id: "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV",
|
|
@@ -79,6 +84,7 @@ async function getProgramOwnedAccounts(programId, rpcUrl) {
|
|
|
79
84
|
}
|
|
80
85
|
async function stopTestEnv(options) {
|
|
81
86
|
const processesToKill = [
|
|
87
|
+
{ name: "forester", condition: options.forester ?? false, killFunction: processForester_1.killForester },
|
|
82
88
|
{ name: "photon", condition: options.indexer, killFunction: processPhotonIndexer_1.killIndexer },
|
|
83
89
|
{ name: "prover", condition: options.prover, killFunction: processProverServer_1.killProver },
|
|
84
90
|
{
|
|
@@ -103,23 +109,62 @@ async function stopTestEnv(options) {
|
|
|
103
109
|
await Promise.all(killPromises);
|
|
104
110
|
console.log("All specified processes and validator stopped.");
|
|
105
111
|
}
|
|
106
|
-
async function initTestEnv({ additionalPrograms, skipSystemAccounts, indexer = true, prover = true, rpcPort = 8899, indexerPort = 8784, proverPort = 3001, gossipHost = "127.0.0.1", checkPhotonVersion = true, photonDatabaseUrl, limitLedgerSize, geyserConfig, validatorArgs, cloneNetwork, verbose, skipReset, }) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
112
|
+
async function initTestEnv({ additionalPrograms, upgradeablePrograms, skipSystemAccounts, indexer = true, prover = true, forester = false, rpcPort = 8899, indexerPort = 8784, proverPort = 3001, foresterPort = 8080, gossipHost = "127.0.0.1", checkPhotonVersion = true, photonDatabaseUrl, limitLedgerSize, geyserConfig, validatorArgs, cloneNetwork, verbose, skipReset, useSurfpool, lightPdaPrograms, additionalAccountDirs, }) {
|
|
113
|
+
if (useSurfpool) {
|
|
114
|
+
// For surfpool we can await startTestValidator because spawnBinary returns
|
|
115
|
+
// immediately (surfpool starts in ~30ms). For solana-test-validator we must
|
|
116
|
+
// NOT await because the validator is a long-running process.
|
|
117
|
+
await startTestValidator({
|
|
118
|
+
additionalPrograms,
|
|
119
|
+
upgradeablePrograms,
|
|
120
|
+
skipSystemAccounts,
|
|
121
|
+
limitLedgerSize,
|
|
122
|
+
rpcPort,
|
|
123
|
+
gossipHost,
|
|
124
|
+
validatorArgs,
|
|
125
|
+
geyserConfig,
|
|
126
|
+
cloneNetwork,
|
|
127
|
+
verbose,
|
|
128
|
+
skipReset,
|
|
129
|
+
useSurfpool,
|
|
130
|
+
additionalAccountDirs,
|
|
131
|
+
});
|
|
132
|
+
// Surfpool only supports JSON-RPC POST, not GET /health.
|
|
133
|
+
await (0, process_1.confirmRpcReadiness)(`http://127.0.0.1:${rpcPort}`);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// We cannot await this promise directly because it will hang the process
|
|
137
|
+
startTestValidator({
|
|
138
|
+
additionalPrograms,
|
|
139
|
+
upgradeablePrograms,
|
|
140
|
+
skipSystemAccounts,
|
|
141
|
+
limitLedgerSize,
|
|
142
|
+
rpcPort,
|
|
143
|
+
gossipHost,
|
|
144
|
+
validatorArgs,
|
|
145
|
+
geyserConfig,
|
|
146
|
+
cloneNetwork,
|
|
147
|
+
verbose,
|
|
148
|
+
skipReset,
|
|
149
|
+
useSurfpool,
|
|
150
|
+
additionalAccountDirs,
|
|
151
|
+
});
|
|
152
|
+
await (0, process_1.waitForServers)([{ port: rpcPort, path: "/health" }]);
|
|
153
|
+
await (0, process_1.confirmServerStability)(`http://127.0.0.1:${rpcPort}/health`);
|
|
154
|
+
await (0, process_1.confirmRpcReadiness)(`http://127.0.0.1:${rpcPort}`);
|
|
155
|
+
}
|
|
156
|
+
if (prover) {
|
|
157
|
+
const config = (0, utils_1.getConfig)();
|
|
158
|
+
config.proverUrl = `http://127.0.0.1:${proverPort}`;
|
|
159
|
+
(0, utils_1.setConfig)(config);
|
|
160
|
+
try {
|
|
161
|
+
await (0, processProverServer_1.startProver)(proverPort);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.error("Failed to start prover:", error);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
123
168
|
if (indexer) {
|
|
124
169
|
const config = (0, utils_1.getConfig)();
|
|
125
170
|
config.indexerUrl = `http://127.0.0.1:${indexerPort}`;
|
|
@@ -127,19 +172,54 @@ async function initTestEnv({ additionalPrograms, skipSystemAccounts, indexer = t
|
|
|
127
172
|
const proverUrlForIndexer = prover
|
|
128
173
|
? `http://127.0.0.1:${proverPort}`
|
|
129
174
|
: undefined;
|
|
130
|
-
|
|
175
|
+
// Surfpool's first available block may not be slot 0.
|
|
176
|
+
// Query the RPC so Photon starts from the correct slot.
|
|
177
|
+
let startSlot;
|
|
178
|
+
if (useSurfpool) {
|
|
179
|
+
const conn = new web3_js_1.Connection(`http://127.0.0.1:${rpcPort}`);
|
|
180
|
+
startSlot = await conn.getFirstAvailableBlock();
|
|
181
|
+
}
|
|
182
|
+
await (0, processPhotonIndexer_1.startIndexer)(`http://127.0.0.1:${rpcPort}`, indexerPort, checkPhotonVersion, photonDatabaseUrl, proverUrlForIndexer, startSlot);
|
|
131
183
|
}
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
184
|
+
if (forester) {
|
|
185
|
+
if (!indexer || !prover) {
|
|
186
|
+
throw new Error("Forester requires both indexer and prover to be running");
|
|
187
|
+
}
|
|
136
188
|
try {
|
|
137
|
-
|
|
138
|
-
await (0,
|
|
189
|
+
const payer = (0, processForester_1.getPayerForForester)();
|
|
190
|
+
await (0, processForester_1.startForester)({
|
|
191
|
+
rpcUrl: `http://127.0.0.1:${rpcPort}`,
|
|
192
|
+
wsRpcUrl: `ws://127.0.0.1:${rpcPort + 1}`,
|
|
193
|
+
indexerUrl: `http://127.0.0.1:${indexerPort}`,
|
|
194
|
+
proverUrl: `http://127.0.0.1:${proverPort}`,
|
|
195
|
+
payer,
|
|
196
|
+
foresterPort,
|
|
197
|
+
lightPdaPrograms,
|
|
198
|
+
});
|
|
139
199
|
}
|
|
140
200
|
catch (error) {
|
|
141
|
-
console.error("Failed to start
|
|
142
|
-
|
|
201
|
+
console.error("Failed to start forester:", error);
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (forester) {
|
|
206
|
+
if (!indexer || !prover) {
|
|
207
|
+
throw new Error("Forester requires both indexer and prover to be running");
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const payer = (0, processForester_1.getPayerForForester)();
|
|
211
|
+
await (0, processForester_1.startForester)({
|
|
212
|
+
rpcUrl: `http://127.0.0.1:${rpcPort}`,
|
|
213
|
+
wsRpcUrl: `ws://127.0.0.1:${rpcPort + 1}`,
|
|
214
|
+
indexerUrl: `http://127.0.0.1:${indexerPort}`,
|
|
215
|
+
proverUrl: `http://127.0.0.1:${proverPort}`,
|
|
216
|
+
payer,
|
|
217
|
+
foresterPort,
|
|
218
|
+
lightPdaPrograms,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
console.error("Failed to start forester:", error);
|
|
143
223
|
throw error;
|
|
144
224
|
}
|
|
145
225
|
}
|
|
@@ -194,12 +274,12 @@ function programFilePath(programName) {
|
|
|
194
274
|
}
|
|
195
275
|
return path_1.default.resolve(__dirname, path_1.default.join(constants_1.BASE_PATH, programName));
|
|
196
276
|
}
|
|
197
|
-
async function getSolanaArgs({ additionalPrograms, skipSystemAccounts, limitLedgerSize, rpcPort, gossipHost, downloadBinaries = true, cloneNetwork, verbose = false, skipReset = false, }) {
|
|
277
|
+
async function getSolanaArgs({ additionalPrograms, upgradeablePrograms, skipSystemAccounts, limitLedgerSize, rpcPort, gossipHost, downloadBinaries = true, cloneNetwork, verbose = false, skipReset = false, }) {
|
|
198
278
|
const dirPath = programsDirPath();
|
|
199
279
|
const solanaArgs = [
|
|
200
280
|
`--limit-ledger-size=${limitLedgerSize}`,
|
|
201
281
|
`--rpc-port=${rpcPort}`,
|
|
202
|
-
`--
|
|
282
|
+
`--bind-address=${gossipHost}`,
|
|
203
283
|
"--quiet",
|
|
204
284
|
];
|
|
205
285
|
if (!skipReset) {
|
|
@@ -257,6 +337,12 @@ async function getSolanaArgs({ additionalPrograms, skipSystemAccounts, limitLedg
|
|
|
257
337
|
solanaArgs.push("--bpf-program", program.address, program.path);
|
|
258
338
|
}
|
|
259
339
|
}
|
|
340
|
+
// Add upgradeable programs (with upgrade authority)
|
|
341
|
+
if (upgradeablePrograms) {
|
|
342
|
+
for (const program of upgradeablePrograms) {
|
|
343
|
+
solanaArgs.push("--upgradeable-program", program.address, program.path, program.upgradeAuthority);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
260
346
|
// Load local system accounts only if not cloning from network
|
|
261
347
|
if (!skipSystemAccounts && !cloneNetwork) {
|
|
262
348
|
const accountsRelPath = "../../accounts";
|
|
@@ -265,34 +351,172 @@ async function getSolanaArgs({ additionalPrograms, skipSystemAccounts, limitLedg
|
|
|
265
351
|
}
|
|
266
352
|
return solanaArgs;
|
|
267
353
|
}
|
|
268
|
-
async function
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
354
|
+
async function getSurfpoolArgs({ additionalPrograms, upgradeablePrograms, skipSystemAccounts, rpcPort, gossipHost, downloadBinaries = true, additionalAccountDirs, }) {
|
|
355
|
+
const dirPath = programsDirPath();
|
|
356
|
+
const args = ["start", "--offline", "--no-tui", "--no-deploy", "--no-studio"];
|
|
357
|
+
args.push("--port", String(rpcPort));
|
|
358
|
+
args.push("--host", String(gossipHost));
|
|
359
|
+
// Load system programs
|
|
360
|
+
for (const program of exports.SYSTEM_PROGRAMS) {
|
|
361
|
+
const localFilePath = programFilePath(program.name);
|
|
362
|
+
if (program.name === "spl_noop.so" || downloadBinaries) {
|
|
363
|
+
await (0, psp_utils_1.downloadBinIfNotExists)({
|
|
364
|
+
localFilePath,
|
|
365
|
+
dirPath,
|
|
366
|
+
owner: "Lightprotocol",
|
|
367
|
+
repoName: "light-protocol",
|
|
368
|
+
remoteFileName: program.name,
|
|
369
|
+
tag: program.tag,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
args.push("--bpf-program", program.id, localFilePath);
|
|
373
|
+
}
|
|
374
|
+
// Load additional programs
|
|
375
|
+
if (additionalPrograms) {
|
|
376
|
+
for (const program of additionalPrograms) {
|
|
377
|
+
args.push("--bpf-program", program.address, program.path);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Load upgradeable programs with full BPF upgradeable loader account layout
|
|
381
|
+
if (upgradeablePrograms) {
|
|
382
|
+
for (const program of upgradeablePrograms) {
|
|
383
|
+
args.push("--upgradeable-program", program.address, program.path, program.upgradeAuthority);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// Load system accounts
|
|
387
|
+
if (!skipSystemAccounts) {
|
|
388
|
+
const accountsRelPath = "../../accounts";
|
|
389
|
+
const accountsPath = path_1.default.resolve(__dirname, accountsRelPath);
|
|
390
|
+
args.push("--account-dir", accountsPath);
|
|
391
|
+
}
|
|
392
|
+
// Load additional account directories
|
|
393
|
+
if (additionalAccountDirs) {
|
|
394
|
+
for (const accountDir of additionalAccountDirs) {
|
|
395
|
+
args.push("--account-dir", path_1.default.resolve(accountDir));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return args;
|
|
399
|
+
}
|
|
400
|
+
function getSurfpoolAssetName() {
|
|
401
|
+
const platform = process.platform; // "darwin" | "linux"
|
|
402
|
+
const arch = process.arch; // "arm64" | "x64"
|
|
403
|
+
return `surfpool-${platform}-${arch}.tar.gz`;
|
|
404
|
+
}
|
|
405
|
+
function getSurfpoolBinDir() {
|
|
406
|
+
return path_1.default.join(os_1.default.homedir(), ".config", "light", "bin");
|
|
407
|
+
}
|
|
408
|
+
function getSurfpoolBinaryPath() {
|
|
409
|
+
return path_1.default.join(getSurfpoolBinDir(), "surfpool");
|
|
410
|
+
}
|
|
411
|
+
function getInstalledSurfpoolVersion() {
|
|
412
|
+
const binaryPath = getSurfpoolBinaryPath();
|
|
413
|
+
if (!fs_1.default.existsSync(binaryPath)) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const output = (0, child_process_1.execSync)(`"${binaryPath}" --version`, {
|
|
418
|
+
encoding: "utf-8",
|
|
419
|
+
timeout: 5000,
|
|
420
|
+
}).trim();
|
|
421
|
+
const match = output.match(/(\d+\.\d+\.\d+)/);
|
|
422
|
+
return match ? match[1] : null;
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async function downloadSurfpoolBinary() {
|
|
429
|
+
const binPath = getSurfpoolBinaryPath();
|
|
430
|
+
const dirPath = getSurfpoolBinDir();
|
|
431
|
+
const assetName = getSurfpoolAssetName();
|
|
432
|
+
if (!fs_1.default.existsSync(dirPath)) {
|
|
433
|
+
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
434
|
+
}
|
|
435
|
+
// Remove existing binary so downloadBinIfNotExists actually downloads
|
|
436
|
+
if (fs_1.default.existsSync(binPath)) {
|
|
437
|
+
fs_1.default.unlinkSync(binPath);
|
|
438
|
+
}
|
|
439
|
+
await (0, psp_utils_1.downloadBinIfNotExists)({
|
|
440
|
+
localFilePath: binPath,
|
|
441
|
+
dirPath,
|
|
442
|
+
owner: "Lightprotocol",
|
|
443
|
+
repoName: "surfpool",
|
|
444
|
+
remoteFileName: assetName,
|
|
445
|
+
tag: constants_1.SURFPOOL_RELEASE_TAG,
|
|
294
446
|
});
|
|
295
447
|
}
|
|
296
|
-
async function
|
|
448
|
+
async function ensureSurfpoolBinary() {
|
|
449
|
+
const binPath = getSurfpoolBinaryPath();
|
|
450
|
+
const installedVersion = getInstalledSurfpoolVersion();
|
|
451
|
+
if (installedVersion === constants_1.SURFPOOL_VERSION) {
|
|
452
|
+
return binPath;
|
|
453
|
+
}
|
|
454
|
+
if (installedVersion) {
|
|
455
|
+
console.log(`Surfpool version mismatch. Expected: ${constants_1.SURFPOOL_VERSION}, Found: ${installedVersion}. Downloading correct version...`);
|
|
456
|
+
}
|
|
457
|
+
else if (fs_1.default.existsSync(binPath)) {
|
|
458
|
+
console.log("Surfpool binary found but version could not be determined. Downloading latest version...");
|
|
459
|
+
}
|
|
460
|
+
await downloadSurfpoolBinary();
|
|
461
|
+
return binPath;
|
|
462
|
+
}
|
|
463
|
+
async function startTestValidator({ additionalPrograms, upgradeablePrograms, skipSystemAccounts, limitLedgerSize, rpcPort, gossipHost, validatorArgs, geyserConfig, cloneNetwork, verbose, skipReset, useSurfpool, additionalAccountDirs, }) {
|
|
464
|
+
if (useSurfpool) {
|
|
465
|
+
const command = await ensureSurfpoolBinary();
|
|
466
|
+
const surfpoolArgs = await getSurfpoolArgs({
|
|
467
|
+
additionalPrograms,
|
|
468
|
+
upgradeablePrograms,
|
|
469
|
+
skipSystemAccounts,
|
|
470
|
+
rpcPort,
|
|
471
|
+
gossipHost,
|
|
472
|
+
additionalAccountDirs,
|
|
473
|
+
});
|
|
474
|
+
await killTestValidator(rpcPort);
|
|
475
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
476
|
+
console.log("Starting surfpool...");
|
|
477
|
+
(0, process_1.spawnBinary)(command, surfpoolArgs, process.env);
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
const command = "solana-test-validator";
|
|
481
|
+
const solanaArgs = await getSolanaArgs({
|
|
482
|
+
additionalPrograms,
|
|
483
|
+
upgradeablePrograms,
|
|
484
|
+
skipSystemAccounts,
|
|
485
|
+
limitLedgerSize,
|
|
486
|
+
rpcPort,
|
|
487
|
+
gossipHost,
|
|
488
|
+
cloneNetwork,
|
|
489
|
+
verbose,
|
|
490
|
+
skipReset,
|
|
491
|
+
});
|
|
492
|
+
await killTestValidator(rpcPort);
|
|
493
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
494
|
+
// Add geyser config if provided
|
|
495
|
+
if (geyserConfig) {
|
|
496
|
+
solanaArgs.push("--geyser-plugin-config", geyserConfig);
|
|
497
|
+
}
|
|
498
|
+
// Add custom validator args last
|
|
499
|
+
if (validatorArgs) {
|
|
500
|
+
solanaArgs.push(...validatorArgs.split(" "));
|
|
501
|
+
}
|
|
502
|
+
console.log("Starting test validator...");
|
|
503
|
+
// Use spawnBinary instead of executeCommand to properly detach the process.
|
|
504
|
+
// This ensures the validator survives when the CLI exits (executeCommand uses
|
|
505
|
+
// piped stdio which causes SIGPIPE when parent exits).
|
|
506
|
+
// Pass process.env directly to maintain same env behavior as before.
|
|
507
|
+
(0, process_1.spawnBinary)(command, solanaArgs, process.env);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async function killTestValidator(rpcPort = 8899) {
|
|
297
511
|
await (0, process_1.killProcess)("solana-test-validator");
|
|
512
|
+
await (0, process_1.killProcess)("surfpool");
|
|
513
|
+
// Fallback: kill anything still listening on the RPC port.
|
|
514
|
+
// find-process name matching can miss processes depending on platform
|
|
515
|
+
// and how the binary path appears in the process table.
|
|
516
|
+
try {
|
|
517
|
+
await (0, process_1.killProcessByPort)(rpcPort);
|
|
518
|
+
}
|
|
519
|
+
catch {
|
|
520
|
+
// No process listening on the port, nothing to do.
|
|
521
|
+
}
|
|
298
522
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PHOTON_GIT_REPO = exports.PHOTON_GIT_COMMIT = exports.PHOTON_VERSION = void 0;
|
|
4
|
+
// Auto-generated from external/photon submodule - do not edit manually
|
|
5
|
+
exports.PHOTON_VERSION = "0.51.2";
|
|
6
|
+
exports.PHOTON_GIT_COMMIT = "7a649f9c45a138ef47b090445163abe84775145c";
|
|
7
|
+
exports.PHOTON_GIT_REPO = "https://github.com/lightprotocol/photon.git";
|
package/dist/utils/process.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare function executeCommand({ command, args, additionalPath, logFile,
|
|
|
27
27
|
* @example const output = await execute("ls -alh");
|
|
28
28
|
*/
|
|
29
29
|
export declare function execute(command: string): Promise<string>;
|
|
30
|
-
export declare function spawnBinary(command: string, args?: string[]): import("child_process").ChildProcess;
|
|
30
|
+
export declare function spawnBinary(command: string, args?: string[], env?: NodeJS.ProcessEnv): import("child_process").ChildProcess;
|
|
31
31
|
export declare function waitForServers(servers: {
|
|
32
32
|
port: number;
|
|
33
33
|
path: string;
|
package/dist/utils/process.js
CHANGED
|
@@ -55,19 +55,17 @@ async function logProverFileContents() {
|
|
|
55
55
|
}
|
|
56
56
|
async function killProcess(processName) {
|
|
57
57
|
const processList = await (0, find_process_1.default)("name", processName);
|
|
58
|
-
const targetProcesses = processList.filter((proc) => proc.
|
|
58
|
+
const targetProcesses = processList.filter((proc) => proc.pid !== process.pid &&
|
|
59
|
+
proc.pid !== process.ppid &&
|
|
60
|
+
(proc.name.includes(processName) || proc.cmd.includes(processName)));
|
|
59
61
|
for (const proc of targetProcesses) {
|
|
60
62
|
try {
|
|
61
63
|
process.kill(proc.pid, "SIGKILL");
|
|
62
64
|
}
|
|
63
65
|
catch (error) {
|
|
64
|
-
|
|
66
|
+
// Process may have already exited between find and kill.
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
|
-
const remainingProcesses = await (0, find_process_1.default)("name", processName);
|
|
68
|
-
if (remainingProcesses.length > 0) {
|
|
69
|
-
console.warn(`Warning: ${remainingProcesses.length} processes still running after kill attempt`);
|
|
70
|
-
}
|
|
71
69
|
}
|
|
72
70
|
async function killProcessByPort(port) {
|
|
73
71
|
if (port < 0) {
|
|
@@ -165,7 +163,7 @@ async function execute(command) {
|
|
|
165
163
|
throw err;
|
|
166
164
|
}
|
|
167
165
|
}
|
|
168
|
-
function spawnBinary(command, args = []) {
|
|
166
|
+
function spawnBinary(command, args = [], env) {
|
|
169
167
|
const logDir = "test-ledger";
|
|
170
168
|
const binaryName = path_1.default.basename(command);
|
|
171
169
|
const dir = path_1.default.join(process.cwd(), logDir);
|
|
@@ -180,16 +178,33 @@ function spawnBinary(command, args = []) {
|
|
|
180
178
|
stdio: ["ignore", out, err],
|
|
181
179
|
shell: false,
|
|
182
180
|
detached: true,
|
|
183
|
-
env: {
|
|
181
|
+
env: env ?? {
|
|
184
182
|
...process.env,
|
|
185
183
|
RUST_LOG: process.env.RUST_LOG || "debug",
|
|
186
184
|
},
|
|
187
185
|
});
|
|
186
|
+
// Close parent's copy of the file descriptors so the child owns them
|
|
187
|
+
// exclusively and node's event loop isn't held open.
|
|
188
|
+
fs_1.default.closeSync(out);
|
|
189
|
+
fs_1.default.closeSync(err);
|
|
190
|
+
// Allow node to exit without waiting for the detached child.
|
|
191
|
+
spawnedProcess.unref();
|
|
188
192
|
spawnedProcess.on("close", async (code) => {
|
|
189
193
|
console.log(`${binaryName} process exited with code ${code}`);
|
|
190
|
-
if (code !== 0
|
|
191
|
-
console.error(
|
|
192
|
-
|
|
194
|
+
if (code !== 0) {
|
|
195
|
+
console.error(`${binaryName} process failed with exit code ${code}`);
|
|
196
|
+
try {
|
|
197
|
+
const contents = fs_1.default.readFileSync(logPath, "utf8");
|
|
198
|
+
console.error(`--- ${binaryName}.log ---`);
|
|
199
|
+
console.error(contents);
|
|
200
|
+
console.error(`--- End of ${binaryName}.log ---`);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// log file may not exist yet
|
|
204
|
+
}
|
|
205
|
+
if (binaryName.includes("prover")) {
|
|
206
|
+
await logProverFileContents();
|
|
207
|
+
}
|
|
193
208
|
}
|
|
194
209
|
});
|
|
195
210
|
return spawnedProcess;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ForesterConfig {
|
|
2
|
+
rpcUrl: string;
|
|
3
|
+
wsRpcUrl: string;
|
|
4
|
+
indexerUrl: string;
|
|
5
|
+
proverUrl: string;
|
|
6
|
+
payer: string;
|
|
7
|
+
foresterPort: number;
|
|
8
|
+
lightPdaPrograms?: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Starts the forester service for auto-compression of compressible accounts.
|
|
12
|
+
*
|
|
13
|
+
* @param config - Forester configuration
|
|
14
|
+
*/
|
|
15
|
+
export declare function startForester(config: ForesterConfig): Promise<undefined>;
|
|
16
|
+
export declare function killForester(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Gets the payer keypair as a JSON array string for forester.
|
|
19
|
+
* Reads from ~/.config/solana/id.json or SOLANA_PAYER environment variable.
|
|
20
|
+
*
|
|
21
|
+
* @returns JSON array string of the keypair bytes
|
|
22
|
+
*/
|
|
23
|
+
export declare function getPayerForForester(): string;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startForester = startForester;
|
|
4
|
+
exports.killForester = killForester;
|
|
5
|
+
exports.getPayerForForester = getPayerForForester;
|
|
6
|
+
const tslib_1 = require("tslib");
|
|
7
|
+
const which_1 = tslib_1.__importDefault(require("which"));
|
|
8
|
+
const process_1 = require("./process");
|
|
9
|
+
const constants_1 = require("./constants");
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const util = tslib_1.__importStar(require("node:util"));
|
|
12
|
+
const node_process_1 = require("node:process");
|
|
13
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
14
|
+
const path = tslib_1.__importStar(require("path"));
|
|
15
|
+
const execAsync = util.promisify(node_child_process_1.exec);
|
|
16
|
+
async function isForesterInstalled() {
|
|
17
|
+
try {
|
|
18
|
+
const resolvedOrNull = which_1.default.sync("forester", { nothrow: true });
|
|
19
|
+
return resolvedOrNull !== null;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function getForesterInstallMessage() {
|
|
26
|
+
return `\nForester not found. Please install it by running: "cargo install --git https://github.com/Lightprotocol/light-protocol forester --locked --force"`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Starts the forester service for auto-compression of compressible accounts.
|
|
30
|
+
*
|
|
31
|
+
* @param config - Forester configuration
|
|
32
|
+
*/
|
|
33
|
+
async function startForester(config) {
|
|
34
|
+
await killForester();
|
|
35
|
+
if (!(await isForesterInstalled())) {
|
|
36
|
+
console.log(getForesterInstallMessage());
|
|
37
|
+
return (0, node_process_1.exit)(1);
|
|
38
|
+
}
|
|
39
|
+
console.log("Starting forester...");
|
|
40
|
+
const args = [
|
|
41
|
+
"start",
|
|
42
|
+
"--rpc-url",
|
|
43
|
+
config.rpcUrl,
|
|
44
|
+
"--ws-rpc-url",
|
|
45
|
+
config.wsRpcUrl,
|
|
46
|
+
"--indexer-url",
|
|
47
|
+
config.indexerUrl,
|
|
48
|
+
"--prover-url",
|
|
49
|
+
config.proverUrl,
|
|
50
|
+
"--payer",
|
|
51
|
+
config.payer,
|
|
52
|
+
"--api-server-port",
|
|
53
|
+
config.foresterPort.toString(),
|
|
54
|
+
"--enable-compressible",
|
|
55
|
+
];
|
|
56
|
+
// Add light PDA programs if specified
|
|
57
|
+
if (config.lightPdaPrograms && config.lightPdaPrograms.length > 0) {
|
|
58
|
+
for (const program of config.lightPdaPrograms) {
|
|
59
|
+
args.push("--light-pda-program", program);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
(0, process_1.spawnBinary)(constants_1.FORESTER_PROCESS_NAME, args);
|
|
63
|
+
await (0, process_1.waitForServers)([{ port: config.foresterPort, path: "/health" }]);
|
|
64
|
+
console.log("Forester started successfully!");
|
|
65
|
+
}
|
|
66
|
+
async function killForester() {
|
|
67
|
+
await (0, process_1.killProcess)(constants_1.FORESTER_PROCESS_NAME);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Gets the payer keypair as a JSON array string for forester.
|
|
71
|
+
* Reads from ~/.config/solana/id.json or SOLANA_PAYER environment variable.
|
|
72
|
+
*
|
|
73
|
+
* @returns JSON array string of the keypair bytes
|
|
74
|
+
*/
|
|
75
|
+
function getPayerForForester() {
|
|
76
|
+
// Check for SOLANA_PAYER environment variable first
|
|
77
|
+
if (process.env.SOLANA_PAYER) {
|
|
78
|
+
return process.env.SOLANA_PAYER;
|
|
79
|
+
}
|
|
80
|
+
// Default to standard Solana keypair location
|
|
81
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
82
|
+
const keypairPath = path.join(homeDir, ".config", "solana", "id.json");
|
|
83
|
+
if (fs.existsSync(keypairPath)) {
|
|
84
|
+
const keypairData = fs.readFileSync(keypairPath, "utf-8");
|
|
85
|
+
return keypairData.trim();
|
|
86
|
+
}
|
|
87
|
+
throw new Error("No payer keypair found. Set SOLANA_PAYER environment variable or create ~/.config/solana/id.json");
|
|
88
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function startIndexer(rpcUrl: string, indexerPort: number, checkPhotonVersion?: boolean, photonDatabaseUrl?: string, proverUrl?: string): Promise<undefined>;
|
|
1
|
+
export declare function startIndexer(rpcUrl: string, indexerPort: number, checkPhotonVersion?: boolean, photonDatabaseUrl?: string, proverUrl?: string, startSlot?: number): Promise<undefined>;
|
|
2
2
|
export declare function killIndexer(): Promise<void>;
|
|
@@ -6,6 +6,7 @@ const tslib_1 = require("tslib");
|
|
|
6
6
|
const which_1 = tslib_1.__importDefault(require("which"));
|
|
7
7
|
const process_1 = require("./process");
|
|
8
8
|
const constants_1 = require("./constants");
|
|
9
|
+
const photonVersion_generated_1 = require("./photonVersion.generated");
|
|
9
10
|
const node_child_process_1 = require("node:child_process");
|
|
10
11
|
const util = tslib_1.__importStar(require("node:util"));
|
|
11
12
|
const node_process_1 = require("node:process");
|
|
@@ -22,21 +23,13 @@ async function isExpectedPhotonVersion(requiredVersion) {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
function getPhotonInstallMessage() {
|
|
25
|
-
|
|
26
|
-
return `\nPhoton indexer ${constants_1.PHOTON_VERSION} (commit ${constants_1.PHOTON_GIT_COMMIT}) not found. Please install it by running: "cargo install --git ${constants_1.PHOTON_GIT_REPO} --rev ${constants_1.PHOTON_GIT_COMMIT} --locked --force"`;
|
|
27
|
-
}
|
|
28
|
-
else if (constants_1.USE_PHOTON_FROM_GIT) {
|
|
29
|
-
return `\nPhoton indexer ${constants_1.PHOTON_VERSION} not found. Please install it by running: "cargo install --git ${constants_1.PHOTON_GIT_REPO} --locked --force"`;
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
return `\nPhoton indexer ${constants_1.PHOTON_VERSION} not found. Please install it by running: "cargo install photon-indexer --version ${constants_1.PHOTON_VERSION} --locked --force"`;
|
|
33
|
-
}
|
|
26
|
+
return `\nPhoton indexer ${photonVersion_generated_1.PHOTON_VERSION} (commit ${photonVersion_generated_1.PHOTON_GIT_COMMIT}) not found. Please install it by running: "cargo install --git ${photonVersion_generated_1.PHOTON_GIT_REPO} --rev ${photonVersion_generated_1.PHOTON_GIT_COMMIT} --locked --force"`;
|
|
34
27
|
}
|
|
35
|
-
async function startIndexer(rpcUrl, indexerPort, checkPhotonVersion = true, photonDatabaseUrl, proverUrl) {
|
|
28
|
+
async function startIndexer(rpcUrl, indexerPort, checkPhotonVersion = true, photonDatabaseUrl, proverUrl, startSlot) {
|
|
36
29
|
await killIndexer();
|
|
37
30
|
const resolvedOrNull = which_1.default.sync("photon", { nothrow: true });
|
|
38
31
|
if (resolvedOrNull === null ||
|
|
39
|
-
(checkPhotonVersion && !(await isExpectedPhotonVersion(
|
|
32
|
+
(checkPhotonVersion && !(await isExpectedPhotonVersion(photonVersion_generated_1.PHOTON_VERSION)))) {
|
|
40
33
|
console.log(getPhotonInstallMessage());
|
|
41
34
|
return (0, node_process_1.exit)(1);
|
|
42
35
|
}
|
|
@@ -54,6 +47,9 @@ async function startIndexer(rpcUrl, indexerPort, checkPhotonVersion = true, phot
|
|
|
54
47
|
if (proverUrl) {
|
|
55
48
|
args.push("--prover-url", proverUrl);
|
|
56
49
|
}
|
|
50
|
+
if (startSlot !== undefined) {
|
|
51
|
+
args.push("--start-slot", startSlot.toString());
|
|
52
|
+
}
|
|
57
53
|
(0, process_1.spawnBinary)(constants_1.INDEXER_PROCESS_NAME, args);
|
|
58
54
|
await (0, process_1.waitForServers)([{ port: indexerPort, path: "/getIndexerHealth" }]);
|
|
59
55
|
console.log("Indexer started successfully!");
|
|
@@ -84,6 +84,7 @@ async function startProver(proverPort, redisUrl) {
|
|
|
84
84
|
}
|
|
85
85
|
(0, process_1.spawnBinary)(getProverPathByArch(), args);
|
|
86
86
|
await (0, process_1.waitForServers)([{ port: proverPort, path: "/" }]);
|
|
87
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
87
88
|
console.log(`Prover started successfully!`);
|
|
88
89
|
}
|
|
89
90
|
function getProverNameByArch() {
|