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
104 changes: 61 additions & 43 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use std::env;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;

use anyhow::Result;
use clap::{Parser, Subcommand};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::signature::Keypair;
use tape_network::store::TapeStore;
use std::env;
use std::str::FromStr;
use std::path::PathBuf;
use std::sync::Arc;

use crate::keypair::{get_keypair_path, get_payer};
use crate::keypair::{get_keypair_path, load_keypair};
use tape_network::store::TapeStore;

#[derive(Parser)]
#[command(
Expand All @@ -26,23 +27,26 @@ pub struct Cli {
pub keypair_path: Option<PathBuf>,

#[arg(
short = 'u',
long = "cluster",
default_value = "l",
short = 'u',
long = "cluster",
default_value = "l",
global = true,
help = "Cluster to use: l (localnet), m (mainnet), d (devnet), t (testnet),\n or a custom RPC URL"
)]
pub cluster: Cluster,

#[arg(short = 'v', long = "verbose", help = "Print verbose output", global = true)]
#[arg(
short = 'v',
long = "verbose",
help = "Print verbose output",
global = true
)]
pub verbose: bool,
}

#[derive(Subcommand)]
pub enum Commands {

// Tape Commands

Read {
#[arg(help = "Tape account to read")]
tape: String,
Expand All @@ -65,13 +69,15 @@ pub enum Commands {
#[arg(short = 'r', long = "remote", conflicts_with_all = ["filename", "message"])]
remote: Option<String>,

#[arg(short = 'n', long = "tape-name", help = "Custom name for the tape (defaults to timestamp)")]
#[arg(
short = 'n',
long = "tape-name",
help = "Custom name for the tape (defaults to timestamp)"
)]
tape_name: Option<String>,
},


// Miner Commands

#[command(hide = true)]
Register {
#[arg(help = "The name of the miner you're registering")]
Expand All @@ -87,9 +93,12 @@ pub enum Commands {
},

// Node Commands

Archive {
#[arg(help = "Starting slot to archive from, defaults to the latest slot", short = 's', long = "starting-slot")]
#[arg(
help = "Starting slot to archive from, defaults to the latest slot",
short = 's',
long = "starting-slot"
)]
starting_slot: Option<u64>,

#[arg(help = "Trusted peer to connect to", short = 'p', long = "peer")]
Expand All @@ -102,7 +111,12 @@ pub enum Commands {
#[arg(help = "Miner account public key", conflicts_with = "name")]
pubkey: Option<String>,

#[arg(help = "Name of the miner you're mining with", conflicts_with = "pubkey", short = 'n', long = "name")]
#[arg(
help = "Name of the miner you're mining with",
conflicts_with = "pubkey",
short = 'n',
long = "name"
)]
name: Option<String>,
},
Web {
Expand All @@ -111,7 +125,6 @@ pub enum Commands {
},

// Admin Commands

#[command(hide = true)]
Init {},

Expand All @@ -121,15 +134,12 @@ pub enum Commands {
},

// Store Management Commands

#[command(subcommand)]
Snapshot(SnapshotCommands),

// Info Commands

#[command(subcommand)]
Info(InfoCommands),

}

#[derive(Subcommand)]
Expand All @@ -142,7 +152,9 @@ pub enum SnapshotCommands {
},

Create {
#[arg(help = "Output path for the snapshot file (defaults to a timestamped file in current directory)")]
#[arg(
help = "Output path for the snapshot file (defaults to a timestamped file in current directory)"
)]
output: Option<String>,
},

Expand All @@ -158,7 +170,11 @@ pub enum SnapshotCommands {
#[arg(short = 'o', long = "output", help = "Output file")]
output: Option<String>,

#[arg(short = 'r', long = "raw", help = "Output raw segments instead of decoded tape")]
#[arg(
short = 'r',
long = "raw",
help = "Output raw segments instead of decoded tape"
)]
raw: bool,
},

Expand All @@ -185,7 +201,12 @@ pub enum InfoCommands {
#[arg(help = "Miner account public key", conflicts_with = "name")]
pubkey: Option<String>,

#[arg(help = "Name of the miner you're mining with", conflicts_with = "pubkey", short = 'n', long = "name")]
#[arg(
help = "Name of the miner you're mining with",
conflicts_with = "pubkey",
short = 'n',
long = "name"
)]
name: Option<String>,
},

Expand Down Expand Up @@ -235,32 +256,31 @@ impl FromStr for Cluster {
pub struct Context {
pub rpc: Arc<RpcClient>,
pub keypair_path: PathBuf,
pub payer: Keypair
pub payer: Keypair,
}

impl Context{
pub fn try_build(cli:&Cli) -> Result<Self> {
impl Context {
pub fn try_build(cli: &Cli) -> Result<Self> {
let rpc_url = cli.cluster.rpc_url();
let rpc = Arc::new(
RpcClient::new_with_commitment(rpc_url.clone(),
CommitmentConfig::finalized())
);
let rpc = Arc::new(RpcClient::new_with_commitment(
rpc_url.clone(),
CommitmentConfig::finalized(),
));
let keypair_path = get_keypair_path(cli.keypair_path.clone());
let payer = get_payer(keypair_path.clone())?;
let payer = load_keypair(keypair_path.clone())?;

Ok(Self {
rpc,
keypair_path,
payer
rpc,
keypair_path,
payer,
})

}

pub fn keyapir_path(&self) -> &PathBuf{
pub fn keyapir_path(&self) -> &PathBuf {
&self.keypair_path
}

pub fn rpc(&self) -> &Arc<RpcClient>{
pub fn rpc(&self) -> &Arc<RpcClient> {
&self.rpc
}

Expand All @@ -280,9 +300,7 @@ impl Context{
Ok(tape_network::store::read_only()?)
}


pub fn payer(&self) -> &Keypair{
pub fn payer(&self) -> &Keypair {
&self.payer
}

}
72 changes: 38 additions & 34 deletions cli/src/keypair.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
use solana_sdk::signature::Keypair;
use std::path::PathBuf;
use anyhow::{Result, anyhow};
use std::fs;
use std::path::{Path, PathBuf};

pub fn create_keypair(path: &PathBuf) -> Result<Keypair> {
use anyhow::{anyhow, Result};
use solana_sdk::{signature::Keypair, signer::EncodableKey};

/// Returns the keypair path. If `keypair_path` is `None`, defaults to `~/.config/solana/id.json`.
pub fn get_keypair_path<F: AsRef<Path>>(keypair_path: Option<F>) -> PathBuf {
keypair_path
.map(|p| p.as_ref().to_path_buf())
.unwrap_or_else(|| {
dirs::home_dir()
.expect("Could not find home directory")
.join(".config/solana/id.json")
})
}

/// Generates a new random `Keypair` and writes it to the given file path.
fn generate_keypair<F: AsRef<Path>>(path: F) -> Result<Keypair> {
let keypair = Keypair::new();
let bytes = keypair.to_bytes().to_vec();
let json = serde_json::to_string(&bytes)
.map_err(|e| anyhow!("Failed to serialize keypair to JSON: {}", e))?;
fs::write(path, json)
.map_err(|e| anyhow!("Failed to write keypair file {}: {}", path.display(), e))?;
let _ = keypair.write_to_file(&path).map_err(|e| {
anyhow!(
"Failed to write new keypair to {}: {}",
path.as_ref().display(),
e
)
})?;

Ok(keypair)
}

pub fn load_keypair(path: &PathBuf) -> Result<Keypair> {
let data = fs::read_to_string(path)
.map_err(|e| anyhow!("Failed to read keypair file {}: {}", path.display(), e))?;
let bytes: Vec<u8> = serde_json::from_str(&data)
.map_err(|e| anyhow!("Failed to parse keypair JSON: {}", e))?;
Keypair::from_bytes(&bytes)
.map_err(|e| anyhow!("Failed to create keypair from bytes: {}", e))
}
/// Loads a `Keypair` from the given path.
///
/// - If the file does **not exist**, a new keypair is generated and written to the path.
/// - If the file exists but is **malformed** or **unreadable**, returns a detailed error.
pub fn load_keypair<F: AsRef<Path>>(path: F) -> Result<Keypair> {
let path = path.as_ref();

/// Loads the keypair from a specified path or the default Solana keypair location.
pub fn get_keypair_path(keypair_path: Option<PathBuf>) -> PathBuf {
keypair_path.unwrap_or_else(|| {
dirs::home_dir()
.expect("Could not find home directory")
.join(".config/solana/id.json")
})
}
if path.exists() {
// Try to read keypair from file
return Keypair::read_from_file(path)
.map_err(|e| anyhow!("Failed to read keypair from {}: {}", path.display(), e));
}

pub fn get_payer(keypair_path: PathBuf) -> Result<Keypair> {
let payer = match load_keypair(&keypair_path) {
Ok(payer) => payer,
Err(_) => {
create_keypair(&keypair_path)?
}
};
Ok(payer)
// File does not exist — generate and save new keypair
generate_keypair(path)
}