sbv2-solana/javascript/solana.js/scripts/localnet.ts

323 lines
7.5 KiB
TypeScript

#!/usr/bin/env tsx
import { clusterApiUrl, Connection, Keypair, PublicKey } from "@solana/web3.js";
import { sleep } from "@switchboard-xyz/common";
import { exec, execSync, spawn } from "child_process";
import fsSync from "fs";
import fs from "fs/promises";
import _ from "lodash";
import os from "os";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const getProgramDataAddress = (programId) => {
return PublicKey.findProgramAddressSync(
[programId.toBytes()],
new PublicKey("BPFLoaderUpgradeab1e11111111111111111111111")
)[0];
};
const getIdlAddress = (programId) => {
const base = PublicKey.findProgramAddressSync([], programId)[0];
return PublicKey.createWithSeed(base, "anchor:idl", new PublicKey(programId));
};
const SWITCHBOARD_PROGRAM_ID = new PublicKey(
"SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
);
const SWITCHBOARD_ATTESTATION_PROGRAM_ID = new PublicKey(
"SBAPyGPyvYEXTiTEfVrktmpvm3Bae3VoZmjYZ6694Ha"
);
const jsSdkRoot = path.join(__dirname, "..");
const solanaSdkRoot = path.join(jsSdkRoot, "..", "..");
const devSwitchboard = path.join(
solanaSdkRoot,
"..",
"switchboard-core",
"switchboard_v2"
);
const defaultPubkeyPath = path.join(
os.homedir(),
".config",
"solana",
"id.json"
);
function killPort(port: number) {
if (port <= 0 || port > 65536) {
throw new Error(`Invalid port number`);
}
execSync(`lsof -t -i :${port} | xargs kill -9 || exit 0`, {
encoding: "utf-8",
});
}
async function main() {
// if dev, clone the local program version
const isDev = process.argv.slice(2).includes("--dev");
if (isDev) {
console.log(`Using local switchboard programs`);
}
const isMainnet = process.argv.slice(2).includes("--mainnet");
const shouldBuild = process.argv.slice(2).includes("--build");
try {
killPort(8899);
killPort(8900);
} catch (error) {
console.error(`Failed to kill port 8899`);
console.error(error);
}
if (!fsSync.existsSync(defaultPubkeyPath)) {
await fs.writeFile(defaultPubkeyPath, `[${Keypair.generate().secretKey}]`);
}
const payerPubkey = Keypair.fromSecretKey(
new Uint8Array(JSON.parse(await fs.readFile(defaultPubkeyPath, "utf-8")))
).publicKey;
await fs.mkdir(".anchor/test-ledger", { recursive: true });
let rpcUrl = clusterApiUrl(isMainnet ? "mainnet-beta" : "devnet");
if (isMainnet && process.env.SOLANA_MAINNET_RPC_URL) {
rpcUrl = process.env.SOLANA_MAINNET_RPC_URL;
} else if (!isMainnet && process.env.SOLANA_DEVNET_RPC_URL) {
rpcUrl = process.env.SOLANA_DEVNET_RPC_URL;
}
if (shouldBuild && isDev) {
console.log(`rebuilding anchor programs ...`);
execSync(`anchor build`, { cwd: devSwitchboard });
}
if (isDev) {
spawn(
"solana-test-validator",
[
"-q",
"-r",
"--mint",
payerPubkey.toBase58(),
"--ledger",
".anchor/test-ledger",
// '--url',
// rpcUrl,
// ...cloneAccounts,
],
{ cwd: jsSdkRoot, stdio: "pipe" }
);
await awaitValidator();
await Promise.all([
programDeploy(
devSwitchboard,
defaultPubkeyPath,
"switchboard_v2",
"SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
),
programDeploy(
devSwitchboard,
defaultPubkeyPath,
"switchboard_attestation_program",
"SBAPyGPyvYEXTiTEfVrktmpvm3Bae3VoZmjYZ6694Ha"
),
]);
} else {
const cloneAccounts = [
SWITCHBOARD_PROGRAM_ID,
getProgramDataAddress(SWITCHBOARD_PROGRAM_ID),
await getIdlAddress(SWITCHBOARD_PROGRAM_ID),
SWITCHBOARD_ATTESTATION_PROGRAM_ID,
getProgramDataAddress(SWITCHBOARD_ATTESTATION_PROGRAM_ID),
await getIdlAddress(SWITCHBOARD_ATTESTATION_PROGRAM_ID),
]
.map((a) => `--clone ${a.toBase58()}`)
.join(" ")
.split(" ");
spawn(
"solana-test-validator",
[
"-q",
"-r",
"--mint",
payerPubkey.toBase58(),
"--ledger",
".anchor/test-ledger",
"--url",
rpcUrl,
...cloneAccounts,
],
{ cwd: jsSdkRoot }
);
await awaitValidator();
}
console.log(`\n\nLocal solana validator started ... `);
}
main()
.then()
.catch((error) => {
console.error(error);
process.exit(1);
});
async function awaitValidator(timeout = 60) {
const connection = new Connection("http://127.0.0.1:8899");
let myError;
let numRetries = timeout * 2;
while (numRetries) {
try {
const id = await connection.getBlockHeight();
if (id) {
return;
}
} catch (error) {
myError = error;
// console.error(error);
}
--numRetries;
await sleep(500);
}
throw new Error(
`Failed to start Solana local validator in ${timeout} seconds${
myError ? ": " + myError : undefined
}`
);
}
async function programDeploy(
switchboardDir,
defaultPubkeyPath,
programName,
programId
) {
const sbProgramPath = path.join(
switchboardDir,
"target",
"deploy",
`${programName}.so`
);
if (!fsSync.existsSync(sbProgramPath)) {
throw new Error(
`Failed to find BPF program ${programName}.so in ${switchboardDir}`
);
}
const programKeypairPath = path.join(
switchboardDir,
"target",
"deploy",
`${programName}-keypair.json`
);
if (!fsSync.existsSync(programKeypairPath)) {
throw new Error(
`Failed to find program keypair for ${programName} in ${switchboardDir}`
);
}
const programKeypair = Keypair.fromSecretKey(
new Uint8Array(JSON.parse(await fs.readFile(programKeypairPath, "utf-8")))
);
if (programKeypair.publicKey.toBase58() !== programId) {
throw new Error(
`Program ID mismatch for program ${programName}, expected ${programId}, received ${programKeypair.publicKey.toBase58()}`
);
}
const idlPath = path.join(
switchboardDir,
"target",
"idl",
`${programName}.json`
);
if (!fsSync.existsSync(idlPath)) {
throw new Error(
`Failed to find IDL ${programName}.json in ${switchboardDir}`
);
}
console.log(`Starting program deploy for ${programName} ...`);
await runCommandAsync(
`solana ${[
"program",
"deploy",
"-u",
"l",
"-k",
"~/.config/solana/id.json",
"--program-id",
programKeypairPath,
"--upgrade-authority",
defaultPubkeyPath,
sbProgramPath,
].join(" ")}`,
{
cwd: switchboardDir,
encoding: "utf8",
stdio: "pipe",
shell: "/bin/zsh",
}
);
console.log(`Starting IDL deploy for ${programName} ...`);
await runCommandAsync(
`anchor ${[
"idl",
"init",
"--provider.cluster",
"localnet",
"--provider.wallet",
defaultPubkeyPath,
"-f",
idlPath,
programId,
].join(" ")}`,
{
cwd: switchboardDir,
encoding: "utf8",
stdio: "pipe",
shell: "/bin/zsh",
}
);
}
async function runCommandAsync(command, options) {
return new Promise((resolve, reject) => {
const cmd = spawn(command, options);
cmd.stdout.on("data", (data) => {
console.log(data.toString());
});
cmd.stderr.on("data", (data) => {
console.error(data.toString());
});
cmd.on("error", (error) => {
reject(error);
});
cmd.on("close", (code) => {
if (code !== 0) {
reject(new Error(`Command exited with code ${code}`));
} else {
resolve(undefined);
}
});
});
}