start-validator.mjs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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. getExternalAccountAddresses,
  8. getExternalProgramAddresses,
  9. getExternalProgramOutputDir,
  10. getProgramFolders,
  11. } from './utils.mjs';
  12. // Check Solana version.
  13. await $`pnpm solana:check`;
  14. // Options and arguments.
  15. const restart = argv['restart'];
  16. // Keep the validator running when not using the restart flag.
  17. const isValidatorRunning = (await $`lsof -t -i:8899`.quiet().exitCode) === 0;
  18. if (!restart && isValidatorRunning) {
  19. echo(chalk.yellow('Local validator is already running.'));
  20. process.exit();
  21. }
  22. // Dump external programs and accounts.
  23. await $`pnpm programs:dump`;
  24. // Initial message.
  25. const verb = isValidatorRunning ? 'Restarting' : 'Starting';
  26. // Get programs and accounts.
  27. const programs = [...getPrograms(), ...getExternalPrograms(), ...getFixturePrograms()];
  28. const programPluralized = programs.length === 1 ? 'program' : 'programs';
  29. const accounts = [...getExternalAccounts()];
  30. const accountsPluralized = accounts.length === 1 ? 'account' : 'accounts';
  31. echo(
  32. `${verb} local validator with ${programs.length} custom ${programPluralized}` +
  33. (accounts.length > 0
  34. ? ` and ${accounts.length} external ${accountsPluralized}...`
  35. : `...`)
  36. );
  37. // Kill the validator if it's already running.
  38. if (isValidatorRunning) {
  39. await $`pkill -f solana-test-validator`.quiet();
  40. await sleep(1000);
  41. }
  42. // Global validator arguments.
  43. const args = [/* Reset ledger */ '-r'];
  44. // Load programs.
  45. programs.forEach(({ programId, deployPath }) => {
  46. args.push(/* Load BPF program */ '--bpf-program', programId, deployPath);
  47. });
  48. // Load accounts.
  49. accounts.forEach(({ account, deployPath }) => {
  50. args.push(/* Load account */ '--account', account, deployPath);
  51. });
  52. // Start the validator in detached mode.
  53. const cliLogs = path.join(os.tmpdir(), 'validator-cli.log');
  54. fs.writeFileSync(cliLogs, '', () => {});
  55. const out = fs.openSync(cliLogs, 'a');
  56. const err = fs.openSync(cliLogs, 'a');
  57. const validator = spawn('solana-test-validator', args, {
  58. detached: true,
  59. stdio: ['ignore', out, err],
  60. });
  61. validator.unref();
  62. // Wait for the validator to stabilize.
  63. const waitForValidator = spinner(
  64. 'Waiting for local validator to stabilize...',
  65. () =>
  66. new Promise((resolve, reject) => {
  67. setInterval(() => {
  68. const logs = fs.readFileSync(cliLogs, 'utf8');
  69. if (validator.exitCode !== null) {
  70. reject(logs);
  71. } else if (logs.includes('Confirmed Slot: 1')) {
  72. resolve();
  73. }
  74. }, 1000);
  75. })
  76. );
  77. try {
  78. await waitForValidator;
  79. echo(chalk.green('Local validator is up and running!'));
  80. } catch (error) {
  81. echo(error);
  82. echo(chalk.red('Could not start local validator.'));
  83. } finally {
  84. fs.rmSync(cliLogs);
  85. process.exit();
  86. }
  87. function getPrograms() {
  88. const binaryDir = path.join(__dirname, '..', 'target', 'deploy');
  89. return getProgramFolders().map((folder) => {
  90. const cargo = getCargo(folder);
  91. const name = cargo.package.name.replace(/-/g, '_');
  92. return {
  93. programId: cargo.package.metadata.solana['program-id'],
  94. deployPath: path.join(binaryDir, `${name}.so`),
  95. };
  96. });
  97. }
  98. function getExternalPrograms() {
  99. const binaryDir = getExternalProgramOutputDir();
  100. return getExternalProgramAddresses().map((address) => ({
  101. programId: address,
  102. deployPath: path.join(binaryDir, `${address}.so`),
  103. }));
  104. }
  105. function getFixturePrograms() {
  106. const binaryDir = path.join(__dirname, '..', 'clients', 'rust-legacy', 'tests', 'fixtures');
  107. return [{
  108. programId: 'TokenHookExampLe8smaVNrxTBezWTRbEwxwb1Zykrb',
  109. deployPath: path.join(binaryDir, 'spl_transfer_hook_example_no_default_features.so'),
  110. }];
  111. }
  112. function getExternalAccounts() {
  113. const binaryDir = getExternalProgramOutputDir();
  114. return getExternalAccountAddresses().map((address) => ({
  115. account: address,
  116. deployPath: path.join(binaryDir, `${address}.json`),
  117. }));
  118. }