Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 33 additions & 72 deletions psyche-book/src/enduser/quickstart-compute-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Before starting, ensure you have:

- [ ] Linux operating system (Ubuntu recommended)
- [ ] NVIDIA GPU with sufficient VRAM for the model being trained
- [ ] The `run-manager` binary
- [ ] The `run-manager` binary, made executable if needed (`chmod +x ./run-manager`)
- [ ] Run ID from the run administrator

---
Expand Down Expand Up @@ -75,50 +75,23 @@ You should see the same GPU information as running `nvidia-smi` directly.

---

## Step 4: Install Solana CLI and Create Wallet

### Install Solana CLI

```bash
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
```

After installation, add Solana to your PATH by adding this line to your `~/.bashrc` or `~/.zshrc`:

```bash
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
```

Then reload your shell:

```bash
source ~/.bashrc # or source ~/.zshrc
```

Verify the installation:

```bash
solana --version
```

For more details, see the [Solana installation docs](https://solana.com/docs/intro/installation).

### Generate a Keypair
## Step 4: Create Wallet

Create a new Solana keypair for your node:

```bash
solana-keygen new --outfile ~/.config/solana/psyche-node.json
./run-manager solana new-key ~/.config/solana/psyche-node.json
```

You'll be prompted to set an optional passphrase. The keypair file will be created at the specified path.
The keypair file will be created at the specified path.

**Important:** Back up this keypair file securely. If you lose it, you lose access to any rewards earned.

Get your public key (you'll need this):
This command will also output the public key for the keypair (you'll need this).
If you need to access the public key again later, you can run:

```bash
solana-keygen pubkey ~/.config/solana/psyche-node.json
./run-manager solana pubkey ~/.config/solana/psyche-node.json
```

---
Expand All @@ -133,33 +106,7 @@ NousNet runs are permissioned. To join, you need the run administrator to author

---

## Step 6: Fund Your Wallet (Devnet)

Your wallet needs SOL to pay for transaction fees when communicating with the Solana blockchain.

First, configure Solana CLI to use devnet:

```bash
solana config set --url https://api.devnet.solana.com
```

Then request an airdrop from the devnet faucet:

```bash
solana airdrop 2 ~/.config/solana/psyche-node.json
```

Verify your balance:

```bash
solana balance ~/.config/solana/psyche-node.json
```

> **Note:** If the airdrop fails due to rate limiting, wait a few minutes and try again, or use the [Solana Faucet web interface](https://faucet.solana.com/).

---

## Step 7: Create the Environment File
## Step 6: Create the Environment File

Create a `.env` file with your configuration. This file tells the run-manager how to connect and authenticate.

Expand Down Expand Up @@ -209,14 +156,28 @@ MICRO_BATCH_SIZE=4

---

## Step 8: Run the Manager
## Step 7: Fund Your Wallet (Devnet)

Make the binary executable if needed:
Your wallet needs SOL to pay for transaction fees when communicating with the Solana blockchain.

You can request an airdrop from the devnet faucet to your node's wallet:

```bash
./run-manager solana airdrop 2 --env-file ~/.config/psyche/run.env
```

Verify your balance:

```bash
chmod +x ./run-manager
./run-manager solana balance --env-file ~/.config/psyche/run.env
```

> **Note:** If the airdrop fails due to rate limiting, wait a few minutes and try again, or use the [Solana Faucet web interface](https://faucet.solana.com/).

---

## Step 8: Join the network via the Run Manager

Open and enter a tmux window:

```bash
Expand Down Expand Up @@ -401,11 +362,11 @@ The `AUTHORIZER` should be your master key's public key (the one authorized by t

## Quick Reference

| Command | Purpose |
| ------------------------------------------------------------- | --------------------------- |
| `nvidia-smi` | Verify GPU and drivers |
| `docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi` | Verify GPU access in Docker |
| `solana-keygen pubkey ~/.config/solana/psyche-node.json` | Get your public key |
| `solana balance ~/.config/solana/psyche-node.json` | Check wallet balance |
| `./run-manager --env-file ~/.config/psyche/run.env` | Start providing compute |
| `Ctrl+C` | Stop the client gracefully |
| Command | Purpose |
| ------------------------------------------------------------------ | --------------------------- |
| `nvidia-smi` | Verify GPU and drivers |
| `docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi` | Verify GPU access in Docker |
| `./run-manager solana pubkey ~/.config/solana/psyche-node.json` | Get your public key |
| `./run-manager solana balance --env-file ~/.config/psyche/run.env` | Check wallet balance |
| `./run-manager --env-file ~/.config/psyche/run.env` | Start providing compute |
| `Ctrl+C` | Stop the client gracefully |
1 change: 1 addition & 0 deletions tools/rust-tools/run-manager/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ pub mod authorization;
pub mod can_join;
pub mod run;
pub mod treasury;
pub mod wallet;

pub use command::Command;
167 changes: 167 additions & 0 deletions tools/rust-tools/run-manager/src/commands/wallet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use anchor_client::solana_sdk::{
native_token::{lamports_to_sol, sol_to_lamports},
signature::{EncodableKey, Keypair, Signer},
};
use anyhow::{Context, Result, bail};
use clap::Subcommand;
use solana_client::rpc_client::RpcClient;
use std::path::PathBuf;

#[derive(Subcommand, Debug)]
pub enum WalletCommands {
/// Display the pubkey from a keypair file
Pubkey {
/// Filepath to a keypair
keypair_path: PathBuf,
},
/// Generate new keypair from a random seed phrase and write to a file
New {
/// Filepath to write the new keypair to
output_path: PathBuf,
},
/// Request SOL from a faucet (won't work on mainnet)
Airdrop {
/// Amount of SOL to request
amount: f64,
/// Path to .env file containing RPC and WALLET_FILE
#[arg(long, required = true)]
env_file: PathBuf,
},
/// Get the balance of the wallet
Balance {
/// Path to .env file containing RPC and WALLET_FILE
#[arg(long, required = true)]
env_file: PathBuf,
},
}

pub fn execute(command: WalletCommands) -> Result<()> {
match command {
WalletCommands::Pubkey { keypair_path } => {
let keypair = Keypair::read_from_file(&keypair_path).map_err(|e| {
anyhow::anyhow!("Failed to read keypair from {keypair_path:?}: {e}")
})?;
println!("{}", keypair.pubkey());
Ok(())
}
WalletCommands::New { output_path } => {
// this uses OsRng, which is a cryptographically secure RNG,
// so it's safe.
let keypair = Keypair::new();

if output_path.exists() {
bail!("keypair output path {output_path:?} exists, refusing to overwrite it.")
}

keypair
.write_to_file(&output_path)
.map_err(|e| anyhow::anyhow!("Failed to write keypair to {output_path:?}: {e}"))?;

println!("Wrote keypair to {output_path:?}");
println!("pubkey: {}", keypair.pubkey());
Ok(())
}
WalletCommands::Airdrop { amount, env_file } => airdrop(amount, env_file),
WalletCommands::Balance { env_file } => check_balance(env_file),
}
}

/// load an env file and extract the RPC URL and keypair
fn load_env_and_keypair(env_file: &PathBuf) -> Result<(String, Keypair)> {
crate::load_and_apply_env_file(env_file)?;

let rpc_url = crate::get_env_var("RPC")?;

let wallet_file = crate::get_env_var("WALLET_PRIVATE_KEY_PATH")?;

// expand tilde in wallet path. we could use a crate like `dirs` to get the home dir more reliably, etc,
// but this will do for now.
let wallet_path = if wallet_file.starts_with("~") {
let home = std::env::var("HOME").map_err(|_| {
anyhow::anyhow!("wallet path contains ~, but HOME environment variable isn't set")
})?;
PathBuf::from(wallet_file.replacen("~", &home, 1))
} else {
PathBuf::from(wallet_file)
};

let keypair = Keypair::read_from_file(&wallet_path)
.map_err(|e| anyhow::anyhow!("Failed to read keypair from {wallet_path:?}: {e}"))?;

Ok((rpc_url, keypair))
}

fn airdrop(amount: f64, env_file: PathBuf) -> Result<()> {
let (rpc_url, keypair) = load_env_and_keypair(&env_file)?;

let pubkey = keypair.pubkey();

let rpc_client = RpcClient::new(&rpc_url);

let lamports = sol_to_lamports(amount);

println!("Requesting airdrop of {amount} SOL to {pubkey} via RPC {rpc_url}");

let pre_balance = rpc_client
.get_balance(&pubkey)
.context("Failed to get balance before airdrop")?;

let signature = rpc_client
.request_airdrop(&pubkey, lamports)
.context("Failed to request airdrop")?;

println!("Airdrop requested. Waiting for confirmation..: {signature}");

let mut confirmed = false;
for _ in 0..30 {
print!(".");
std::thread::sleep(std::time::Duration::from_secs(1));
if let Ok(status) = rpc_client.get_signature_statuses(&[signature]) {
if let Some(status) = &status.value[0] {
if status.err.is_none() {
confirmed = true;
break;
}
}
}
}
println!();

if !confirmed {
println!("Warning: Airdrop confirmation timed out");
println!("Run `solana confirm {signature}` to check status");
return Ok(());
}

let post_balance = rpc_client
.get_balance(&pubkey)
.context("Failed to get balance post-airdrop")?;

if post_balance < pre_balance + lamports {
bail!("Balance unchanged. Run `solana confirm -v {signature}` to debug");
} else {
let balance_sol = lamports_to_sol(post_balance);
println!("New balance: {balance_sol} SOL");
}

Ok(())
}

fn check_balance(env_file: PathBuf) -> Result<()> {
let (rpc_url, keypair) = load_env_and_keypair(&env_file)?;

let pubkey = keypair.pubkey();

let rpc_client = RpcClient::new(rpc_url);

let balance = rpc_client
.get_balance(&pubkey)
.context("Failed to get balance")?;

let balance_sol = lamports_to_sol(balance);

println!("pubkey {pubkey} has sol balance");
println!("{balance_sol} SOL");

Ok(())
}
8 changes: 8 additions & 0 deletions tools/rust-tools/run-manager/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use commands::run::{
CommandSetFutureEpochRates, CommandSetPaused, CommandTick, CommandUpdateConfig,
};
use commands::treasury::{CommandTreasurerClaimRewards, CommandTreasurerTopUpRewards};
use commands::wallet;

const VERSION: &str = env!("CARGO_PKG_VERSION");
const GIT_HASH: &str = env!("GIT_HASH");
Expand Down Expand Up @@ -214,6 +215,12 @@ enum Commands {
params: CommandCanJoin,
},

// Solana key management
Solana {
#[command(subcommand)]
command: wallet::WalletCommands,
},

// Docs generation
#[clap(hide = true)]
PrintAllHelp {
Expand Down Expand Up @@ -370,6 +377,7 @@ async fn async_main() -> Result<()> {
Commands::CanJoin { cluster, params } => {
params.execute(create_backend_readonly(cluster)?).await
}
Commands::Solana { command } => wallet::execute(command),
Commands::PrintAllHelp { markdown } => {
assert!(markdown);
clap_markdown::print_help_markdown::<CliArgs>();
Expand Down
Loading