1
0

start-validator.mjs 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. #!/usr/bin/env zx
  2. import { spawn } from 'node:child_process';
  3. import fs from 'node:fs';
  4. import 'zx/globals';
  5. import {
  6. getCargo,
  7. getExternalProgramAddresses,
  8. getExternalProgramOutputDir,
  9. getProgramFolders,
  10. } from './utils.mjs';
  11. // Options and arguments.
  12. const restart = argv['restart'];
  13. // Keep the validator running when not using the restart flag.
  14. const isValidatorRunning = (await $`lsof -t -i:8899`.quiet().exitCode) === 0;
  15. if (!restart && isValidatorRunning) {
  16. echo(chalk.yellow('Local validator is already running.'));
  17. process.exit();
  18. }
  19. // Initial message.
  20. const verb = isValidatorRunning ? 'Restarting' : 'Starting';
  21. const programs = [...getPrograms(), ...getExternalPrograms()];
  22. const programPluralized = programs.length === 1 ? 'program' : 'programs';
  23. echo(
  24. `${verb} local validator with ${programs.length} custom ${programPluralized}...`
  25. );
  26. // Kill the validator if it's already running.
  27. if (isValidatorRunning) {
  28. await $`pkill -f solana-test-validator`.quiet();
  29. await sleep(1000);
  30. }
  31. // Global validator arguments.
  32. const args = [/* Reset ledger */ '-r'];
  33. // Load programs.
  34. programs.forEach(({ programId, deployPath }) => {
  35. args.push(/* Load BPF program */ '--bpf-program', programId, deployPath);
  36. });
  37. // Start the validator in detached mode.
  38. const cliLogs = path.join(os.tmpdir(), 'validator-cli.log');
  39. fs.writeFileSync(cliLogs, '', () => {});
  40. const out = fs.openSync(cliLogs, 'a');
  41. const err = fs.openSync(cliLogs, 'a');
  42. const validator = spawn('solana-test-validator', args, {
  43. detached: true,
  44. stdio: ['ignore', out, err],
  45. });
  46. validator.unref();
  47. // Wait for the validator to stabilize.
  48. const waitForValidator = spinner(
  49. 'Waiting for local validator to stabilize...',
  50. () =>
  51. new Promise((resolve, reject) => {
  52. setInterval(() => {
  53. const logs = fs.readFileSync(cliLogs, 'utf8');
  54. if (validator.exitCode !== null) {
  55. reject(logs);
  56. } else if (logs.includes('Confirmed Slot: 1')) {
  57. resolve();
  58. }
  59. }, 1000);
  60. })
  61. );
  62. try {
  63. await waitForValidator;
  64. echo(chalk.green('Local validator is up and running!'));
  65. } catch (error) {
  66. echo(error);
  67. echo(chalk.red('Could not start local validator.'));
  68. } finally {
  69. fs.rmSync(cliLogs);
  70. process.exit();
  71. }
  72. function getPrograms() {
  73. const binaryDir = path.join(__dirname, '..', 'target', 'deploy');
  74. return getProgramFolders().map((folder) => {
  75. const cargo = getCargo(folder);
  76. const name = cargo.package.name.replace(/-/g, '_');
  77. return {
  78. programId: cargo.package.metadata.solana['program-id'],
  79. deployPath: path.join(binaryDir, `${name}.so`),
  80. };
  81. });
  82. }
  83. function getExternalPrograms() {
  84. const binaryDir = getExternalProgramOutputDir();
  85. return getExternalProgramAddresses().map((address) => ({
  86. programId: address,
  87. deployPath: path.join(binaryDir, `${address}.so`),
  88. }));
  89. }