From 55a8780134b6d9abc55d0e70512a661e836e103f Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Mon, 1 Sep 2025 16:46:47 +0530 Subject: [PATCH 01/18] Squash --- .gitignore | 5 +- Cargo.lock | 92 +++++++++- Cargo.toml | 2 + cli/Cargo.toml | 3 + cli/src/cli.rs | 23 +-- cli/src/config.rs | 447 ++++++++++++++++++++++++++++++++++++++++++++++ cli/src/main.rs | 66 ++++++- tape.example.toml | 27 +++ 8 files changed, 641 insertions(+), 24 deletions(-) create mode 100644 cli/src/config.rs create mode 100644 tape.example.toml diff --git a/.gitignore b/.gitignore index 5cce7990..ea4a171e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,7 @@ .DS_Store */**/.DS_Store -.vscode \ No newline at end of file +.vscode + +*/tape.*.toml +tape.*.toml diff --git a/Cargo.lock b/Cargo.lock index f099a9c2..8fe34865 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,13 +1397,33 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + [[package]] name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", ] [[package]] @@ -3399,7 +3419,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -4234,6 +4254,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4326,6 +4355,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs 4.0.0", +] + [[package]] name = "shlex" version = "1.3.0" @@ -6753,7 +6791,7 @@ dependencies = [ "colored", "console", "dialoguer", - "dirs", + "dirs 5.0.1", "env_logger", "indicatif", "log", @@ -6763,13 +6801,16 @@ dependencies = [ "num_enum", "packx", "reqwest 0.12.15", + "serde", "serde_json", + "shellexpand", "solana-client", "solana-sdk", "tape-api", "tape-client", "tape-network", "tokio", + "toml 0.9.5", ] [[package]] @@ -7028,12 +7069,36 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + [[package]] name = "toml_edit" version = "0.22.24" @@ -7041,10 +7106,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 0.6.8", "winnow", ] +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + [[package]] name = "tower" version = "0.5.2" @@ -7794,9 +7874,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index c99a83e2..31eb5013 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ mime = "0.3" mime_guess = "2.0" log = "0.4" env_logger = "0.11" +shellexpand = "2.1.0" # network-specific futures = "0.3" @@ -82,6 +83,7 @@ hyper = { version = "1.6.0", features = ["http1", "server"] } hyper-util = { version = "0.1", features = ["tokio"] } lazy_static = "1.5.0" http-body-util = "0.1.3" +toml = "0.9.5" [patch.crates-io] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index f9c5bb86..94971fab 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -23,6 +23,7 @@ packx.workspace = true anyhow.workspace = true chrono.workspace = true +serde.workspace = true serde_json.workspace = true clap.workspace = true colored.workspace = true @@ -36,6 +37,8 @@ num_enum.workspace = true log.workspace = true env_logger.workspace = true num_cpus.workspace = true +toml.workspace = true +shellexpand.workspace = true mime.workspace = true mime_guess.workspace = true diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 6861c99b..88b45595 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1,7 +1,6 @@ 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; @@ -10,6 +9,7 @@ use std::path::PathBuf; use std::sync::Arc; use crate::keypair::{get_keypair_path, get_payer}; +use crate::config::TapeConfig; #[derive(Parser)] #[command( @@ -22,18 +22,12 @@ pub struct Cli { #[command(subcommand)] pub command: Commands, + #[arg(short = 'c', long = "config", help = "Path to config file (overrides default)", global = true)] + pub config_path: Option, + #[arg(short = 'k', long = "keypair", global = true)] pub keypair_path: Option, - #[arg( - 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)] pub verbose: bool, } @@ -245,13 +239,14 @@ pub struct Context { } impl Context{ - pub fn try_build(cli:&Cli) -> Result { - let rpc_url = cli.cluster.rpc_url(); + pub fn try_build(_cli:&Cli, config: &TapeConfig) -> Result { + let rpc_url = config.solana.rpc_url.to_string(); + let commitment_level = config.solana.commitment.to_commitment_config(); let rpc = Arc::new( RpcClient::new_with_commitment(rpc_url.clone(), - CommitmentConfig::finalized()) + commitment_level) ); - let keypair_path = get_keypair_path(cli.keypair_path.clone()); + let keypair_path = get_keypair_path(Some(PathBuf::from(&*shellexpand::tilde(&config.identity.keypair_path)))); let payer = get_payer(keypair_path.clone())?; Ok(Self { diff --git a/cli/src/config.rs b/cli/src/config.rs new file mode 100644 index 00000000..547a1c61 --- /dev/null +++ b/cli/src/config.rs @@ -0,0 +1,447 @@ +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::{Path, PathBuf}; +use solana_sdk::commitment_config::CommitmentConfig; +use std::fmt; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct TapeConfig { + pub mining_config: MiningConfig, + pub identity: IdentityConfig, + pub solana: SolanaConfig, + pub storage: StorageConfig, + pub logging: LoggingConfig, +} + + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct MiningConfig { + pub num_cores: usize, + pub max_memory_mb: u64, + pub max_poa_threads: u64, + pub max_pow_threads: u64, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PerformanceConfig { + pub num_cores: usize, + pub max_memory_mb: u64, + pub max_poa_threads: u64, + pub max_pow_threads: u64, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct IdentityConfig { + pub keypair_path: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct SolanaConfig { + pub rpc_url: String, + pub ws_url: Option, + pub commitment: CommitmentLevel, + pub priority_fee_lamports: u64, + pub max_transaction_retries: u32, +} + + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct StorageConfig { + pub backend: StorageBackend, + pub rocksdb: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct RocksDbConfig { + pub primary_path: String, + pub secondary_path: Option, + pub cache_size_mb: u64, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum StorageBackend { + RocksDb, + Postgres +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct LoggingConfig { + pub log_level: LogLevel, + pub log_path: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + Error, + Warn, + Info, + Debug, + Trace, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum CommitmentLevel { + Processed, + Confirmed, + Finalized, +} + +impl ToString for CommitmentLevel { + fn to_string(&self) -> String { + match self { + CommitmentLevel::Processed => "processed".to_string(), + CommitmentLevel::Confirmed => "confirmed".to_string(), + CommitmentLevel::Finalized => "finalized".to_string(), + } + } +} + +impl CommitmentLevel { + pub fn to_commitment_config(&self) -> CommitmentConfig { + match self { + CommitmentLevel::Processed => CommitmentConfig::processed(), + CommitmentLevel::Confirmed => CommitmentConfig::confirmed(), + CommitmentLevel::Finalized => CommitmentConfig::finalized(), + } + } +} + + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct StorageConfig { + pub backend: StorageBackend, + pub rocksdb: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct RocksDbConfig { + pub primary_path: String, + pub secondary_path: Option, + pub cache_size_mb: u64, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum StorageBackend { + RocksDb, + Postgres +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct LoggingConfig { + pub log_level: LogLevel, + pub log_path: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + Error, + Warn, + Info, + Debug, + Trace, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum CommitmentLevel { + Processed, + Confirmed, + Finalized, +} + +impl ToString for CommitmentLevel { + fn to_string(&self) -> String { + match self { + CommitmentLevel::Processed => "processed".to_string(), + CommitmentLevel::Confirmed => "confirmed".to_string(), + CommitmentLevel::Finalized => "finalized".to_string(), + } + } +} + +impl CommitmentLevel { + pub fn to_commitment_config(&self) -> CommitmentConfig { + match self { + CommitmentLevel::Processed => CommitmentConfig::processed(), + CommitmentLevel::Confirmed => CommitmentConfig::confirmed(), + CommitmentLevel::Finalized => CommitmentConfig::finalized(), + } + } +} + +impl TapeConfig { + + pub fn load_with_path(config_path: &Option) -> Result { + match config_path { + Some(path) => { + let expanded_path = expand_path(path); + if !expanded_path.exists() { + return Err(TapeConfigError::CustomConfigFileNotFound( + expanded_path.display().to_string() + )); + } + Self::load_from_path(expanded_path) + }, + None => { + let default_path = get_default_config_path()?; + Self::load_from_path(default_path) + } + } + } + + pub fn load_from_path>(path: P) -> Result { + let path = path.as_ref(); + + if !path.exists() { + return Err(TapeConfigError::ConfigFileNotFound); + } + + let contents = fs::read_to_string(path) + .map_err(TapeConfigError::FileReadError)?; + let config: TapeConfig = toml::from_str(&contents) + .map_err(TapeConfigError::ParseError)?; + config.validate()?; + Ok(config) + } + + fn validate(&self) -> Result<(), TapeConfigError> { + // solana rpc and websocket url validation + self.validate_url(&self.solana.rpc_url, "Solana RPC URL", &["http://", "https://"])?; + if let Some(ref ws_url) = self.solana.ws_url { + self.validate_url(ws_url, "Solana WebSocket URL", &["ws://", "wss://"])?; + } + + // keypair validation + let keypair_path = &*shellexpand::tilde(&self.identity.keypair_path); + if !Path::new(&keypair_path).exists() { + return Err(TapeConfigError::KeypairNotFound(keypair_path.to_string())); + } + + Ok(()) + } + + fn validate_url(&self, url: &str, field_name: &str, valid_schemes: &[&str]) -> Result<(), TapeConfigError> { + let has_valid_scheme = valid_schemes.iter().any(|scheme| url.starts_with(scheme)); + + if !has_valid_scheme { + return Err(TapeConfigError::InvalidUrl( + format!("{} must start with one of {:?}, found: '{}'", field_name, valid_schemes, url) + )); + } + + if url.contains(' ') { + return Err(TapeConfigError::InvalidUrl( + format!("{} cannot contain spaces, found: '{}'", field_name, url) + )); + } + + if url.trim().is_empty() { + return Err(TapeConfigError::InvalidUrl( + format!("{} cannot be empty", field_name) + )); + } + + Ok(()) + } + + fn validate_url(&self, url: &str, field_name: &str, valid_schemes: &[&str]) -> Result<(), TapeConfigError> { + let has_valid_scheme = valid_schemes.iter().any(|scheme| url.starts_with(scheme)); + + if !has_valid_scheme { + return Err(TapeConfigError::InvalidUrl( + format!("{} must start with one of {:?}, found: '{}'", field_name, valid_schemes, url) + )); + } + + if url.contains(' ') { + return Err(TapeConfigError::InvalidUrl( + format!("{} cannot contain spaces, found: '{}'", field_name, url) + )); + } + + if url.trim().is_empty() { + return Err(TapeConfigError::InvalidUrl( + format!("{} cannot be empty", field_name) + )); + } + + Ok(()) + } + + /// create default configuration and save to file + pub fn create_default() -> Result { + let config = Self::default(); + let toml_string = toml::to_string_pretty(&config) + .map_err(|e| TapeConfigError::DefaultConfigCreationFailed(format!("Serialization failed: {}", e)))?; + + let home_dir = dirs::home_dir() + .ok_or(TapeConfigError::HomeDirectoryNotFound)?; + let config_path = home_dir.join("tape.devnet.toml"); + + fs::write(config_path, toml_string) + .map_err(|e| TapeConfigError::DefaultConfigCreationFailed(format!("Write failed: {}", e)))?; + + Ok(config) + } +} + +pub fn get_default_config_path() -> Result { + let home_dir = dirs::home_dir() + .ok_or(TapeConfigError::HomeDirectoryNotFound)?; + Ok(home_dir.join("tape.devnet.toml")) +} + + pub fn expand_path>(path: P) -> PathBuf { + let path_str = path.as_ref().to_string_lossy(); + let expanded = shellexpand::tilde(&path_str); + PathBuf::from(expanded.as_ref()) + } + +impl Default for TapeConfig { + fn default() -> Self { + Self { + mining_config: MiningConfig{ + num_cores: num_cpus::get(), + max_memory_mb: 16384, + max_poa_threads: 4, + max_pow_threads: 4 + }, + performance: PerformanceConfig{ + num_cores: num_cpus::get(), + max_memory_mb: 16384, + max_poa_threads: 4, + max_pow_threads: 4 + }, + identity: IdentityConfig { + keypair_path: "~/.config/solana/id.json".to_string(), + }, + solana: SolanaConfig { + rpc_url: "https://api.devnet.solana.com".to_string(), + ws_url: Some("wss://api.devnet.solana.com/".to_string()), + commitment: CommitmentLevel::Confirmed, + priority_fee_lamports: 1000, + max_transaction_retries: 3, + }, + storage: StorageConfig { + backend: StorageBackend::RocksDb, + rocksdb: Some(RocksDbConfig{ + primary_path: "./db_tapestore".to_string(), + secondary_path: Some("./db_tapestore_secondary".to_string()), + cache_size_mb: 512, + }) + }, + logging: LoggingConfig { + log_level: LogLevel::Info, + log_path: Some("./logs/tape.log".to_string()), + }, + storage: StorageConfig { + backend: StorageBackend::RocksDb, + rocksdb: Some(RocksDbConfig{ + primary_path: "./db_tapestore".to_string(), + secondary_path: Some("./db_tapestore_secondary".to_string()), + cache_size_mb: 512, + }) + }, + logging: LoggingConfig { + log_level: LogLevel::Info, + log_path: Some("./logs/tape.log".to_string()), + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_toml_parsing_works_properly() { + let toml_content = r#" +[mining_config] +num_cores = 4 +max_memory_mb = 16384 +max_poa_threads = 4 +max_pow_threads = 4 + +[performance] +num_cores = 4 +max_memory_mb = 16384 +max_poa_threads = 4 +max_pow_threads = 4 + +[identity] +keypair_path = "~/.config/solana/id.json" + +[solana] +rpc_url = "https://api.mainnet-beta.solana.com" +ws_url = "wss://api.mainnet-beta.solana.com/" +commitment = "finalized" +priority_fee_lamports = 2000 +max_transaction_retries = 5 + +[storage] +backend = "rocksdb" + +[storage.rocksdb] +primary_path = "./data/primary" +secondary_path = "./data/secondary" +cache_size_mb = 512 + +[logging] +log_level = "debug" +log_path = "./test.log" +"#; + + let config: TapeConfig = toml::from_str(toml_content).unwrap(); + + + assert_eq!(config.identity.keypair_path, "~/.config/solana/id.json"); + assert_eq!(config.solana.rpc_url, "https://api.mainnet-beta.solana.com"); + assert_eq!(config.solana.ws_url, Some("wss://api.mainnet-beta.solana.com/".to_string())); + assert_eq!(config.solana.commitment, CommitmentLevel::Finalized); + assert_eq!(config.solana.priority_fee_lamports, 2000); + assert_eq!(config.solana.max_transaction_retries, 5); + + assert_eq!(config.storage.backend, StorageBackend::RocksDb); + let rocksdb_config = config.storage.rocksdb.as_ref().unwrap(); + assert_eq!(rocksdb_config.primary_path, "./data/primary"); + assert_eq!(rocksdb_config.secondary_path, Some("./data/secondary".to_string())); + assert_eq!(rocksdb_config.cache_size_mb, 512); + + + assert_eq!(config.logging.log_level, LogLevel::Debug); + assert_eq!(config.logging.log_path, Some("./test.log".to_string())); + } +} + +#[derive(Debug)] +pub enum TapeConfigError { + ConfigFileNotFound, + CustomConfigFileNotFound(String), + InvalidUrl(String), + KeypairNotFound(String), + HomeDirectoryNotFound, + FileReadError(std::io::Error), + ParseError(toml::de::Error), + DefaultConfigCreationFailed(String), +} + +impl fmt::Display for TapeConfigError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TapeConfigError::ConfigFileNotFound => write!(f, "Configuration file not found"), + TapeConfigError::CustomConfigFileNotFound(path) => write!(f, "Configuration file not found at path: {}", path), + TapeConfigError::InvalidUrl(msg) => write!(f, "Invalid URL configuration: {}", msg), + TapeConfigError::KeypairNotFound(path) => write!(f, "Keypair not found at path: {}", path), + TapeConfigError::HomeDirectoryNotFound => write!(f, "Home directory not found"), + TapeConfigError::FileReadError(e) => write!(f, "Failed to read config file: {}", e), + TapeConfigError::ParseError(e) => write!(f, "Failed to parse config file: {}", e), + TapeConfigError::DefaultConfigCreationFailed(msg) => write!(f, "Failed to create default config: {}", msg), + } + } +} + +impl std::error::Error for TapeConfigError {} diff --git a/cli/src/main.rs b/cli/src/main.rs index 2e39f900..2414682a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,9 +3,10 @@ mod keypair; mod log; mod commands; mod utils; +mod config; -use anyhow::{Ok, Result}; +use anyhow::Result; use clap::Parser; use cli::{Cli, Commands}; use commands::{admin, read, write, info, snapshot, network, claim}; @@ -13,6 +14,7 @@ use env_logger::{self, Env}; use tape_network::store::TapeStore; use crate::cli::Context; +use crate::config::{TapeConfig, TapeConfigError}; fn main() -> Result<()>{ @@ -37,9 +39,67 @@ async fn run_tape_cli() -> Result<()> { log::print_title(format!("⊙⊙ TAPEDRIVE {}", env!("CARGO_PKG_VERSION")).as_str()); let cli = Cli::parse(); - - let context = Context::try_build(&cli)?; + let config = match TapeConfig::load_with_path(&cli.config_path) { + Ok(config) => config, + Err(e) => match e { + TapeConfigError::ConfigFileNotFound => { + log::print_info("tape.toml not found, creating default configuration..."); + match TapeConfig::create_default() { + Ok(config) => { + log::print_info("✓ Default configuration created successfully"); + config + }, + Err(creation_error) => { + log::print_error(&format!("{}", creation_error)); + std::process::exit(1); + } + } + }, + + TapeConfigError::CustomConfigFileNotFound(path) => { + // This happens when user explicitly provided a path that doesn't exist + log::print_error(&format!("Custom config file not found: {}", path)); + log::print_info("Please check the path and try again."); + std::process::exit(1); + }, + + TapeConfigError::InvalidUrl(msg) => { + log::print_error(&format!("URL Configuration Error: {}", msg)); + log::print_info("Please fix the URL in your tape.toml file and try again."); + std::process::exit(1); + }, + + TapeConfigError::KeypairNotFound(path) => { + log::print_error(&format!("Keypair not found at path: {}", path)); + log::print_info("Please ensure the keypair file exists at the specified path in tape.toml"); + std::process::exit(1); + }, + + TapeConfigError::FileReadError(io_err) => { + log::print_error(&format!("Could not read config file: {}", io_err)); + std::process::exit(1); + }, + + TapeConfigError::ParseError(parse_err) => { + log::print_error(&format!("Invalid tape.toml format: {}", parse_err)); + log::print_info("Please check your tape.toml file syntax."); + std::process::exit(1); + }, + + TapeConfigError::HomeDirectoryNotFound => { + log::print_error("Could not determine home directory"); + std::process::exit(1); + }, + + TapeConfigError::DefaultConfigCreationFailed(msg) => { + log::print_error(&format!("Failed to create default config: {}", msg)); + std::process::exit(1); + }, + } + }; + + let context = Context::try_build(&cli, &config)?; match cli.command { Commands::Init {} | diff --git a/tape.example.toml b/tape.example.toml new file mode 100644 index 00000000..16799c8b --- /dev/null +++ b/tape.example.toml @@ -0,0 +1,27 @@ +[mining_config] +num_cores = 4 +max_memory_mb = 16384 +max_poa_threads = 4 +max_pow_threads = 4 + +[identity] +keypair_path = "~/.config/solana/id.json" + +[solana] +rpc_url = "https://api.mainnet-beta.solana.com" +ws_url = "wss://api.mainnet-beta.solana.com/" +commitment = "finalized" +priority_fee_lamports = 2000 +max_transaction_retries = 5 + +[storage] +backend = "rocksdb" + +[storage.rocksdb] +primary_path = "./data/primary" +secondary_path = "./data/secondary" +cache_size_mb = 512 + +[logging] +log_level = "debug" +log_path = "./test.log" From 7777cd1ce83384b1a462d25e3248be791762f5f8 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 21 Aug 2025 21:05:17 +0530 Subject: [PATCH 02/18] feat: update fields for TapeCofig > transaction > performance > identity > solana > storage > network > logging feat: add validate + default values for toml.config feat: log_level is enum fix: resolving comments > change the `max_memory_mb` 2gb -> 16 gb > add few comments on what to change fix: remove the `NetworkConfig` feat: add enum for commitment level > add a impl for `CommitmentLevel` fix: fix the keypair loading up from ~ path feat: removed the helper function for tilde, add function used in anchor feat: add abstract StorageConfig feat: add URL validation fix: rename performance -> mining_config > add max_poa_thread > add max_pow_thread feat: better error handling for config::load() - ConfigFileNotFound - InvalidUrl(String) - KeypairNotFound(String) - HomeDirectoryNotFound - FileReadError(std::io::Error) - ParseError(toml::de::Error) - DefaultConfigCreationFailed(String) feat: add `--config` option feat: add error handling for config load > create new config is not found in default path, `--config` is not used > if config file not found in given path -> error will be shown feat: add `--config` option feat: add error handling for config load > create new config is not found in default path, `--config` is not used > if config file not found in given path -> error will be shown --- cli/src/config.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/cli/src/config.rs b/cli/src/config.rs index 547a1c61..26d1c0b7 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -30,6 +30,14 @@ pub struct PerformanceConfig { pub max_pow_threads: u64, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PerformanceConfig { + pub num_cores: usize, + pub max_memory_mb: u64, + pub max_poa_threads: u64, + pub max_pow_threads: u64, +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct IdentityConfig { pub keypair_path: String, @@ -109,6 +117,70 @@ impl CommitmentLevel { } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct StorageConfig { + pub backend: StorageBackend, + pub rocksdb: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct RocksDbConfig { + pub primary_path: String, + pub secondary_path: Option, + pub cache_size_mb: u64, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum StorageBackend { + RocksDb, + Postgres +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct LoggingConfig { + pub log_level: LogLevel, + pub log_path: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + Error, + Warn, + Info, + Debug, + Trace, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum CommitmentLevel { + Processed, + Confirmed, + Finalized, +} + +impl ToString for CommitmentLevel { + fn to_string(&self) -> String { + match self { + CommitmentLevel::Processed => "processed".to_string(), + CommitmentLevel::Confirmed => "confirmed".to_string(), + CommitmentLevel::Finalized => "finalized".to_string(), + } + } +} + +impl CommitmentLevel { + pub fn to_commitment_config(&self) -> CommitmentConfig { + match self { + CommitmentLevel::Processed => CommitmentConfig::processed(), + CommitmentLevel::Confirmed => CommitmentConfig::confirmed(), + CommitmentLevel::Finalized => CommitmentConfig::finalized(), + } + } +} + + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct StorageConfig { pub backend: StorageBackend, @@ -271,6 +343,30 @@ impl TapeConfig { Ok(()) } + fn validate_url(&self, url: &str, field_name: &str, valid_schemes: &[&str]) -> Result<(), TapeConfigError> { + let has_valid_scheme = valid_schemes.iter().any(|scheme| url.starts_with(scheme)); + + if !has_valid_scheme { + return Err(TapeConfigError::InvalidUrl( + format!("{} must start with one of {:?}, found: '{}'", field_name, valid_schemes, url) + )); + } + + if url.contains(' ') { + return Err(TapeConfigError::InvalidUrl( + format!("{} cannot contain spaces, found: '{}'", field_name, url) + )); + } + + if url.trim().is_empty() { + return Err(TapeConfigError::InvalidUrl( + format!("{} cannot be empty", field_name) + )); + } + + Ok(()) + } + /// create default configuration and save to file pub fn create_default() -> Result { let config = Self::default(); @@ -315,6 +411,12 @@ impl Default for TapeConfig { max_poa_threads: 4, max_pow_threads: 4 }, + performance: PerformanceConfig{ + num_cores: num_cpus::get(), + max_memory_mb: 16384, + max_poa_threads: 4, + max_pow_threads: 4 + }, identity: IdentityConfig { keypair_path: "~/.config/solana/id.json".to_string(), }, @@ -349,6 +451,18 @@ impl Default for TapeConfig { log_level: LogLevel::Info, log_path: Some("./logs/tape.log".to_string()), }, + storage: StorageConfig { + backend: StorageBackend::RocksDb, + rocksdb: Some(RocksDbConfig{ + primary_path: "./db_tapestore".to_string(), + secondary_path: Some("./db_tapestore_secondary".to_string()), + cache_size_mb: 512, + }) + }, + logging: LoggingConfig { + log_level: LogLevel::Info, + log_path: Some("./logs/tape.log".to_string()), + }, } } } @@ -372,6 +486,12 @@ max_memory_mb = 16384 max_poa_threads = 4 max_pow_threads = 4 +[performance] +num_cores = 4 +max_memory_mb = 16384 +max_poa_threads = 4 +max_pow_threads = 4 + [identity] keypair_path = "~/.config/solana/id.json" From b2213e823be6622c784e05bf8cd2ceb3f4f5940c Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Mon, 1 Sep 2025 23:49:48 +0530 Subject: [PATCH 03/18] fix: removed duplicate lines --- cli/src/config.rs | 227 ---------------------------------------------- 1 file changed, 227 deletions(-) diff --git a/cli/src/config.rs b/cli/src/config.rs index 26d1c0b7..bbed7668 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -22,22 +22,6 @@ pub struct MiningConfig { pub max_pow_threads: u64, } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct PerformanceConfig { - pub num_cores: usize, - pub max_memory_mb: u64, - pub max_poa_threads: u64, - pub max_pow_threads: u64, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct PerformanceConfig { - pub num_cores: usize, - pub max_memory_mb: u64, - pub max_poa_threads: u64, - pub max_pow_threads: u64, -} - #[derive(Debug, Serialize, Deserialize, Clone)] pub struct IdentityConfig { pub keypair_path: String, @@ -117,133 +101,6 @@ impl CommitmentLevel { } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct StorageConfig { - pub backend: StorageBackend, - pub rocksdb: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct RocksDbConfig { - pub primary_path: String, - pub secondary_path: Option, - pub cache_size_mb: u64, -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub enum StorageBackend { - RocksDb, - Postgres -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct LoggingConfig { - pub log_level: LogLevel, - pub log_path: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum LogLevel { - Error, - Warn, - Info, - Debug, - Trace, -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum CommitmentLevel { - Processed, - Confirmed, - Finalized, -} - -impl ToString for CommitmentLevel { - fn to_string(&self) -> String { - match self { - CommitmentLevel::Processed => "processed".to_string(), - CommitmentLevel::Confirmed => "confirmed".to_string(), - CommitmentLevel::Finalized => "finalized".to_string(), - } - } -} - -impl CommitmentLevel { - pub fn to_commitment_config(&self) -> CommitmentConfig { - match self { - CommitmentLevel::Processed => CommitmentConfig::processed(), - CommitmentLevel::Confirmed => CommitmentConfig::confirmed(), - CommitmentLevel::Finalized => CommitmentConfig::finalized(), - } - } -} - - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct StorageConfig { - pub backend: StorageBackend, - pub rocksdb: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct RocksDbConfig { - pub primary_path: String, - pub secondary_path: Option, - pub cache_size_mb: u64, -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub enum StorageBackend { - RocksDb, - Postgres -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct LoggingConfig { - pub log_level: LogLevel, - pub log_path: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum LogLevel { - Error, - Warn, - Info, - Debug, - Trace, -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum CommitmentLevel { - Processed, - Confirmed, - Finalized, -} - -impl ToString for CommitmentLevel { - fn to_string(&self) -> String { - match self { - CommitmentLevel::Processed => "processed".to_string(), - CommitmentLevel::Confirmed => "confirmed".to_string(), - CommitmentLevel::Finalized => "finalized".to_string(), - } - } -} - -impl CommitmentLevel { - pub fn to_commitment_config(&self) -> CommitmentConfig { - match self { - CommitmentLevel::Processed => CommitmentConfig::processed(), - CommitmentLevel::Confirmed => CommitmentConfig::confirmed(), - CommitmentLevel::Finalized => CommitmentConfig::finalized(), - } - } -} - impl TapeConfig { pub fn load_with_path(config_path: &Option) -> Result { @@ -319,54 +176,6 @@ impl TapeConfig { Ok(()) } - fn validate_url(&self, url: &str, field_name: &str, valid_schemes: &[&str]) -> Result<(), TapeConfigError> { - let has_valid_scheme = valid_schemes.iter().any(|scheme| url.starts_with(scheme)); - - if !has_valid_scheme { - return Err(TapeConfigError::InvalidUrl( - format!("{} must start with one of {:?}, found: '{}'", field_name, valid_schemes, url) - )); - } - - if url.contains(' ') { - return Err(TapeConfigError::InvalidUrl( - format!("{} cannot contain spaces, found: '{}'", field_name, url) - )); - } - - if url.trim().is_empty() { - return Err(TapeConfigError::InvalidUrl( - format!("{} cannot be empty", field_name) - )); - } - - Ok(()) - } - - fn validate_url(&self, url: &str, field_name: &str, valid_schemes: &[&str]) -> Result<(), TapeConfigError> { - let has_valid_scheme = valid_schemes.iter().any(|scheme| url.starts_with(scheme)); - - if !has_valid_scheme { - return Err(TapeConfigError::InvalidUrl( - format!("{} must start with one of {:?}, found: '{}'", field_name, valid_schemes, url) - )); - } - - if url.contains(' ') { - return Err(TapeConfigError::InvalidUrl( - format!("{} cannot contain spaces, found: '{}'", field_name, url) - )); - } - - if url.trim().is_empty() { - return Err(TapeConfigError::InvalidUrl( - format!("{} cannot be empty", field_name) - )); - } - - Ok(()) - } - /// create default configuration and save to file pub fn create_default() -> Result { let config = Self::default(); @@ -405,18 +214,6 @@ impl Default for TapeConfig { max_poa_threads: 4, max_pow_threads: 4 }, - performance: PerformanceConfig{ - num_cores: num_cpus::get(), - max_memory_mb: 16384, - max_poa_threads: 4, - max_pow_threads: 4 - }, - performance: PerformanceConfig{ - num_cores: num_cpus::get(), - max_memory_mb: 16384, - max_poa_threads: 4, - max_pow_threads: 4 - }, identity: IdentityConfig { keypair_path: "~/.config/solana/id.json".to_string(), }, @@ -439,30 +236,6 @@ impl Default for TapeConfig { log_level: LogLevel::Info, log_path: Some("./logs/tape.log".to_string()), }, - storage: StorageConfig { - backend: StorageBackend::RocksDb, - rocksdb: Some(RocksDbConfig{ - primary_path: "./db_tapestore".to_string(), - secondary_path: Some("./db_tapestore_secondary".to_string()), - cache_size_mb: 512, - }) - }, - logging: LoggingConfig { - log_level: LogLevel::Info, - log_path: Some("./logs/tape.log".to_string()), - }, - storage: StorageConfig { - backend: StorageBackend::RocksDb, - rocksdb: Some(RocksDbConfig{ - primary_path: "./db_tapestore".to_string(), - secondary_path: Some("./db_tapestore_secondary".to_string()), - cache_size_mb: 512, - }) - }, - logging: LoggingConfig { - log_level: LogLevel::Info, - log_path: Some("./logs/tape.log".to_string()), - }, } } } From c55525d68d6bea09629c0b9703cb47c51521d815 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Tue, 2 Sep 2025 23:42:44 +0530 Subject: [PATCH 04/18] fix: better names to structs and vars --- cli/src/cli.rs | 2 +- cli/src/config.rs | 8 ++++---- cli/src/main.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 88b45595..1713dc16 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -23,7 +23,7 @@ pub struct Cli { pub command: Commands, #[arg(short = 'c', long = "config", help = "Path to config file (overrides default)", global = true)] - pub config_path: Option, + pub config: Option, #[arg(short = 'k', long = "keypair", global = true)] pub keypair_path: Option, diff --git a/cli/src/config.rs b/cli/src/config.rs index bbed7668..16a9e77e 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -6,7 +6,7 @@ use std::fmt; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TapeConfig { - pub mining_config: MiningConfig, + pub mining: MiningConfig, pub identity: IdentityConfig, pub solana: SolanaConfig, pub storage: StorageConfig, @@ -103,7 +103,7 @@ impl CommitmentLevel { impl TapeConfig { - pub fn load_with_path(config_path: &Option) -> Result { + pub fn load(config_path: &Option) -> Result { match config_path { Some(path) => { let expanded_path = expand_path(path); @@ -208,7 +208,7 @@ pub fn get_default_config_path() -> Result { impl Default for TapeConfig { fn default() -> Self { Self { - mining_config: MiningConfig{ + mining: MiningConfig{ num_cores: num_cpus::get(), max_memory_mb: 16384, max_poa_threads: 4, @@ -247,7 +247,7 @@ mod tests { #[test] fn test_toml_parsing_works_properly() { let toml_content = r#" -[mining_config] +[mining] num_cores = 4 max_memory_mb = 16384 max_poa_threads = 4 diff --git a/cli/src/main.rs b/cli/src/main.rs index 2414682a..1c8fde97 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -40,7 +40,7 @@ async fn run_tape_cli() -> Result<()> { let cli = Cli::parse(); - let config = match TapeConfig::load_with_path(&cli.config_path) { + let config = match TapeConfig::load(&cli.config) { Ok(config) => config, Err(e) => match e { TapeConfigError::ConfigFileNotFound => { From 519ff95953e3250a54d474d7186f9836d50746cb Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Tue, 2 Sep 2025 23:50:38 +0530 Subject: [PATCH 05/18] feat: move config load-up -> Context:try_build --- cli/src/cli.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++-- cli/src/main.rs | 62 +--------------------------------------------- 2 files changed, 65 insertions(+), 63 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 1713dc16..7a8270e4 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -9,7 +9,8 @@ use std::path::PathBuf; use std::sync::Arc; use crate::keypair::{get_keypair_path, get_payer}; -use crate::config::TapeConfig; +use crate::config::{TapeConfig, TapeConfigError}; +use crate::log; #[derive(Parser)] #[command( @@ -239,7 +240,68 @@ pub struct Context { } impl Context{ - pub fn try_build(_cli:&Cli, config: &TapeConfig) -> Result { + pub fn try_build(cli:&Cli) -> Result { + + // loading up configs + let config = match TapeConfig::load(&cli.config) { + Ok(config) => config, + Err(e) => match e { + TapeConfigError::ConfigFileNotFound => { + log::print_info("tape.toml not found, creating default configuration..."); + match TapeConfig::create_default() { + Ok(config) => { + log::print_info("✓ Default configuration created successfully"); + config + }, + Err(creation_error) => { + log::print_error(&format!("{}", creation_error)); + std::process::exit(1); + } + } + }, + + TapeConfigError::CustomConfigFileNotFound(path) => { + // This happens when user explicitly provided a path that doesn't exist + log::print_error(&format!("Custom config file not found: {}", path)); + log::print_info("Please check the path and try again."); + std::process::exit(1); + }, + + TapeConfigError::InvalidUrl(msg) => { + log::print_error(&format!("URL Configuration Error: {}", msg)); + log::print_info("Please fix the URL in your tape.toml file and try again."); + std::process::exit(1); + }, + + TapeConfigError::KeypairNotFound(path) => { + log::print_error(&format!("Keypair not found at path: {}", path)); + log::print_info("Please ensure the keypair file exists at the specified path in tape.toml"); + std::process::exit(1); + }, + + TapeConfigError::FileReadError(io_err) => { + log::print_error(&format!("Could not read config file: {}", io_err)); + std::process::exit(1); + }, + + TapeConfigError::ParseError(parse_err) => { + log::print_error(&format!("Invalid tape.toml format: {}", parse_err)); + log::print_info("Please check your tape.toml file syntax."); + std::process::exit(1); + }, + + TapeConfigError::HomeDirectoryNotFound => { + log::print_error("Could not determine home directory"); + std::process::exit(1); + }, + + TapeConfigError::DefaultConfigCreationFailed(msg) => { + log::print_error(&format!("Failed to create default config: {}", msg)); + std::process::exit(1); + }, + } + }; + let rpc_url = config.solana.rpc_url.to_string(); let commitment_level = config.solana.commitment.to_commitment_config(); let rpc = Arc::new( diff --git a/cli/src/main.rs b/cli/src/main.rs index 1c8fde97..1abf3f22 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -14,7 +14,6 @@ use env_logger::{self, Env}; use tape_network::store::TapeStore; use crate::cli::Context; -use crate::config::{TapeConfig, TapeConfigError}; fn main() -> Result<()>{ @@ -40,66 +39,7 @@ async fn run_tape_cli() -> Result<()> { let cli = Cli::parse(); - let config = match TapeConfig::load(&cli.config) { - Ok(config) => config, - Err(e) => match e { - TapeConfigError::ConfigFileNotFound => { - log::print_info("tape.toml not found, creating default configuration..."); - match TapeConfig::create_default() { - Ok(config) => { - log::print_info("✓ Default configuration created successfully"); - config - }, - Err(creation_error) => { - log::print_error(&format!("{}", creation_error)); - std::process::exit(1); - } - } - }, - - TapeConfigError::CustomConfigFileNotFound(path) => { - // This happens when user explicitly provided a path that doesn't exist - log::print_error(&format!("Custom config file not found: {}", path)); - log::print_info("Please check the path and try again."); - std::process::exit(1); - }, - - TapeConfigError::InvalidUrl(msg) => { - log::print_error(&format!("URL Configuration Error: {}", msg)); - log::print_info("Please fix the URL in your tape.toml file and try again."); - std::process::exit(1); - }, - - TapeConfigError::KeypairNotFound(path) => { - log::print_error(&format!("Keypair not found at path: {}", path)); - log::print_info("Please ensure the keypair file exists at the specified path in tape.toml"); - std::process::exit(1); - }, - - TapeConfigError::FileReadError(io_err) => { - log::print_error(&format!("Could not read config file: {}", io_err)); - std::process::exit(1); - }, - - TapeConfigError::ParseError(parse_err) => { - log::print_error(&format!("Invalid tape.toml format: {}", parse_err)); - log::print_info("Please check your tape.toml file syntax."); - std::process::exit(1); - }, - - TapeConfigError::HomeDirectoryNotFound => { - log::print_error("Could not determine home directory"); - std::process::exit(1); - }, - - TapeConfigError::DefaultConfigCreationFailed(msg) => { - log::print_error(&format!("Failed to create default config: {}", msg)); - std::process::exit(1); - }, - } - }; - - let context = Context::try_build(&cli, &config)?; + let context = Context::try_build(&cli)?; match cli.command { Commands::Init {} | From 4c4ebcc5202691870e5f250f674255de41d656aa Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Fri, 5 Sep 2025 14:11:32 +0530 Subject: [PATCH 06/18] fix: add `miner_name` --- cli/src/config.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/cli/src/config.rs b/cli/src/config.rs index 16a9e77e..862a1f46 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -16,6 +16,7 @@ pub struct TapeConfig { #[derive(Debug, Serialize, Deserialize, Clone)] pub struct MiningConfig { + pub miner_name: String, pub num_cores: usize, pub max_memory_mb: u64, pub max_poa_threads: u64, @@ -209,6 +210,7 @@ impl Default for TapeConfig { fn default() -> Self { Self { mining: MiningConfig{ + miner_name: "tape_miner".to_string(), num_cores: num_cpus::get(), max_memory_mb: 16384, max_poa_threads: 4, @@ -248,18 +250,7 @@ mod tests { fn test_toml_parsing_works_properly() { let toml_content = r#" [mining] -num_cores = 4 -max_memory_mb = 16384 -max_poa_threads = 4 -max_pow_threads = 4 - -[performance] -num_cores = 4 -max_memory_mb = 16384 -max_poa_threads = 4 -max_pow_threads = 4 - -[performance] +miner_name = "tape_miner" num_cores = 4 max_memory_mb = 16384 max_poa_threads = 4 From fa7708f85fabec30d37a263fab4266e9dde39dd5 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Wed, 10 Sep 2025 00:58:20 +0530 Subject: [PATCH 07/18] feat: reduce match block of config loadup --- cli/src/cli.rs | 62 ++--------------------------------------------- cli/src/config.rs | 12 ++++++++- tape.example.toml | 2 +- 3 files changed, 14 insertions(+), 62 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 7a8270e4..6be7ac60 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -9,8 +9,7 @@ use std::path::PathBuf; use std::sync::Arc; use crate::keypair::{get_keypair_path, get_payer}; -use crate::config::{TapeConfig, TapeConfigError}; -use crate::log; +use crate::config::TapeConfig; #[derive(Parser)] #[command( @@ -243,64 +242,7 @@ impl Context{ pub fn try_build(cli:&Cli) -> Result { // loading up configs - let config = match TapeConfig::load(&cli.config) { - Ok(config) => config, - Err(e) => match e { - TapeConfigError::ConfigFileNotFound => { - log::print_info("tape.toml not found, creating default configuration..."); - match TapeConfig::create_default() { - Ok(config) => { - log::print_info("✓ Default configuration created successfully"); - config - }, - Err(creation_error) => { - log::print_error(&format!("{}", creation_error)); - std::process::exit(1); - } - } - }, - - TapeConfigError::CustomConfigFileNotFound(path) => { - // This happens when user explicitly provided a path that doesn't exist - log::print_error(&format!("Custom config file not found: {}", path)); - log::print_info("Please check the path and try again."); - std::process::exit(1); - }, - - TapeConfigError::InvalidUrl(msg) => { - log::print_error(&format!("URL Configuration Error: {}", msg)); - log::print_info("Please fix the URL in your tape.toml file and try again."); - std::process::exit(1); - }, - - TapeConfigError::KeypairNotFound(path) => { - log::print_error(&format!("Keypair not found at path: {}", path)); - log::print_info("Please ensure the keypair file exists at the specified path in tape.toml"); - std::process::exit(1); - }, - - TapeConfigError::FileReadError(io_err) => { - log::print_error(&format!("Could not read config file: {}", io_err)); - std::process::exit(1); - }, - - TapeConfigError::ParseError(parse_err) => { - log::print_error(&format!("Invalid tape.toml format: {}", parse_err)); - log::print_info("Please check your tape.toml file syntax."); - std::process::exit(1); - }, - - TapeConfigError::HomeDirectoryNotFound => { - log::print_error("Could not determine home directory"); - std::process::exit(1); - }, - - TapeConfigError::DefaultConfigCreationFailed(msg) => { - log::print_error(&format!("Failed to create default config: {}", msg)); - std::process::exit(1); - }, - } - }; + let config = TapeConfig::load(&cli.config)?; let rpc_url = config.solana.rpc_url.to_string(); let commitment_level = config.solana.commitment.to_commitment_config(); diff --git a/cli/src/config.rs b/cli/src/config.rs index 862a1f46..6fb15dab 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -3,6 +3,7 @@ use std::fs; use std::path::{Path, PathBuf}; use solana_sdk::commitment_config::CommitmentConfig; use std::fmt; +use crate::log::*; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TapeConfig { @@ -117,7 +118,16 @@ impl TapeConfig { }, None => { let default_path = get_default_config_path()?; - Self::load_from_path(default_path) + match Self::load_from_path(&default_path) { + Ok(config) => Ok(config), + Err(TapeConfigError::ConfigFileNotFound) => { + print_info("tape.toml not found, creating default configuration..."); + let config = Self::create_default()?; + print_info("✓ Default configuration created successfully"); + Ok(config) + }, + Err(e) => Err(e), + } } } } diff --git a/tape.example.toml b/tape.example.toml index 16799c8b..779add3e 100644 --- a/tape.example.toml +++ b/tape.example.toml @@ -1,4 +1,4 @@ -[mining_config] +[mining] num_cores = 4 max_memory_mb = 16384 max_poa_threads = 4 From 9ad1077f4b8ceef0c1ba0f36a3a0ce10679e1a3d Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 11 Sep 2025 11:21:05 +0530 Subject: [PATCH 08/18] imp: change the args in `get_keypair_path` fn --- cli/src/cli.rs | 2 +- cli/src/keypair.rs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 6be7ac60..430301bb 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -250,7 +250,7 @@ impl Context{ RpcClient::new_with_commitment(rpc_url.clone(), commitment_level) ); - let keypair_path = get_keypair_path(Some(PathBuf::from(&*shellexpand::tilde(&config.identity.keypair_path)))); + let keypair_path = get_keypair_path(&config.identity.keypair_path); let payer = get_payer(keypair_path.clone())?; Ok(Self { diff --git a/cli/src/keypair.rs b/cli/src/keypair.rs index b0a3e7b3..ed34b844 100644 --- a/cli/src/keypair.rs +++ b/cli/src/keypair.rs @@ -23,12 +23,14 @@ pub fn load_keypair(path: &PathBuf) -> Result { } /// Loads the keypair from a specified path or the default Solana keypair location. -pub fn get_keypair_path(keypair_path: Option) -> PathBuf { - keypair_path.unwrap_or_else(|| { +pub fn get_keypair_path(keypair_path: &str) -> PathBuf { + if keypair_path.is_empty() { dirs::home_dir() .expect("Could not find home directory") .join(".config/solana/id.json") - }) + } else { + PathBuf::from(&*shellexpand::tilde(keypair_path)) + } } pub fn get_payer(keypair_path: PathBuf) -> Result { From 6f4813b759824e8454387a0e2f6133873a9fc5fd Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 11 Sep 2025 11:24:36 +0530 Subject: [PATCH 09/18] imp: move the `keypair_path` -> `Solana`, remove `Identity` --- cli/src/cli.rs | 2 +- cli/src/config.rs | 18 +++++------------- tape.example.toml | 4 +--- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 430301bb..4955850b 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -250,7 +250,7 @@ impl Context{ RpcClient::new_with_commitment(rpc_url.clone(), commitment_level) ); - let keypair_path = get_keypair_path(&config.identity.keypair_path); + let keypair_path = get_keypair_path(&config.solana.keypair_path); let payer = get_payer(keypair_path.clone())?; Ok(Self { diff --git a/cli/src/config.rs b/cli/src/config.rs index 6fb15dab..46b05060 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -8,7 +8,6 @@ use crate::log::*; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TapeConfig { pub mining: MiningConfig, - pub identity: IdentityConfig, pub solana: SolanaConfig, pub storage: StorageConfig, pub logging: LoggingConfig, @@ -24,13 +23,10 @@ pub struct MiningConfig { pub max_pow_threads: u64, } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct IdentityConfig { - pub keypair_path: String, -} #[derive(Debug, Serialize, Deserialize, Clone)] pub struct SolanaConfig { + pub keypair_path: String, pub rpc_url: String, pub ws_url: Option, pub commitment: CommitmentLevel, @@ -155,7 +151,7 @@ impl TapeConfig { } // keypair validation - let keypair_path = &*shellexpand::tilde(&self.identity.keypair_path); + let keypair_path = &*shellexpand::tilde(&self.solana.keypair_path); if !Path::new(&keypair_path).exists() { return Err(TapeConfigError::KeypairNotFound(keypair_path.to_string())); } @@ -226,10 +222,8 @@ impl Default for TapeConfig { max_poa_threads: 4, max_pow_threads: 4 }, - identity: IdentityConfig { - keypair_path: "~/.config/solana/id.json".to_string(), - }, solana: SolanaConfig { + keypair_path: "~/.config/solana/id.json".to_string(), rpc_url: "https://api.devnet.solana.com".to_string(), ws_url: Some("wss://api.devnet.solana.com/".to_string()), commitment: CommitmentLevel::Confirmed, @@ -266,10 +260,8 @@ max_memory_mb = 16384 max_poa_threads = 4 max_pow_threads = 4 -[identity] -keypair_path = "~/.config/solana/id.json" - [solana] +keypair_path = "~/.config/solana/id.json" rpc_url = "https://api.mainnet-beta.solana.com" ws_url = "wss://api.mainnet-beta.solana.com/" commitment = "finalized" @@ -292,7 +284,7 @@ log_path = "./test.log" let config: TapeConfig = toml::from_str(toml_content).unwrap(); - assert_eq!(config.identity.keypair_path, "~/.config/solana/id.json"); + assert_eq!(config.solana.keypair_path, "~/.config/solana/id.json"); assert_eq!(config.solana.rpc_url, "https://api.mainnet-beta.solana.com"); assert_eq!(config.solana.ws_url, Some("wss://api.mainnet-beta.solana.com/".to_string())); assert_eq!(config.solana.commitment, CommitmentLevel::Finalized); diff --git a/tape.example.toml b/tape.example.toml index 779add3e..ff03e85b 100644 --- a/tape.example.toml +++ b/tape.example.toml @@ -4,10 +4,8 @@ max_memory_mb = 16384 max_poa_threads = 4 max_pow_threads = 4 -[identity] -keypair_path = "~/.config/solana/id.json" - [solana] +keypair_path = "~/.config/solana/id.json" rpc_url = "https://api.mainnet-beta.solana.com" ws_url = "wss://api.mainnet-beta.solana.com/" commitment = "finalized" From 0415068bcf92d71bb7b3e33e6d49ef92a6ab732c Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 11 Sep 2025 11:28:25 +0530 Subject: [PATCH 10/18] imp: logs default config creation path --- cli/src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/config.rs b/cli/src/config.rs index 46b05060..8ae3ad69 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -117,9 +117,9 @@ impl TapeConfig { match Self::load_from_path(&default_path) { Ok(config) => Ok(config), Err(TapeConfigError::ConfigFileNotFound) => { - print_info("tape.toml not found, creating default configuration..."); + print_info("No configuration found, creating default tape.toml..."); let config = Self::create_default()?; - print_info("✓ Default configuration created successfully"); + print_info(&format!("✓ Created default configuration at {:?}", default_path)); Ok(config) }, Err(e) => Err(e), From 6755c57ddc8d000160d1b03bc6eef1bf6a2d38c8 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 11 Sep 2025 20:58:33 +0530 Subject: [PATCH 11/18] feat: add `config` field to `Context` > rm keypair_path > update keypair_path() helper function --- cli/src/cli.rs | 15 ++++++++------- cli/src/config.rs | 12 ++++++++++++ cli/src/keypair.rs | 11 ----------- cli/src/main.rs | 3 +-- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 4955850b..4fab05de 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -8,7 +8,7 @@ use std::str::FromStr; use std::path::PathBuf; use std::sync::Arc; -use crate::keypair::{get_keypair_path, get_payer}; +use crate::keypair::get_payer; use crate::config::TapeConfig; #[derive(Parser)] @@ -233,8 +233,8 @@ impl FromStr for Cluster { } pub struct Context { + pub config: Arc, pub rpc: Arc, - pub keypair_path: PathBuf, pub payer: Keypair } @@ -242,7 +242,7 @@ impl Context{ pub fn try_build(cli:&Cli) -> Result { // loading up configs - let config = TapeConfig::load(&cli.config)?; + let config = Arc::new(TapeConfig::load(&cli.config)?); let rpc_url = config.solana.rpc_url.to_string(); let commitment_level = config.solana.commitment.to_commitment_config(); @@ -250,19 +250,20 @@ impl Context{ RpcClient::new_with_commitment(rpc_url.clone(), commitment_level) ); - let keypair_path = get_keypair_path(&config.solana.keypair_path); + + let keypair_path = config.solana.keypair_path(); let payer = get_payer(keypair_path.clone())?; Ok(Self { + config, rpc, - keypair_path, payer }) } - pub fn keyapir_path(&self) -> &PathBuf{ - &self.keypair_path + pub fn keypair_path(&self) -> PathBuf { + self.config.solana.keypair_path() } pub fn rpc(&self) -> &Arc{ diff --git a/cli/src/config.rs b/cli/src/config.rs index 8ae3ad69..041f9710 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -78,6 +78,18 @@ pub enum CommitmentLevel { Finalized, } +impl SolanaConfig { + pub fn keypair_path(&self) -> PathBuf { + if self.keypair_path.is_empty() { + dirs::home_dir() + .expect("Could not find home directory") + .join(".config/solana/id.json") + } else { + PathBuf::from(&*shellexpand::tilde(&self.keypair_path)) + } + } +} + impl ToString for CommitmentLevel { fn to_string(&self) -> String { match self { diff --git a/cli/src/keypair.rs b/cli/src/keypair.rs index ed34b844..ab8d968c 100644 --- a/cli/src/keypair.rs +++ b/cli/src/keypair.rs @@ -22,17 +22,6 @@ pub fn load_keypair(path: &PathBuf) -> Result { .map_err(|e| anyhow!("Failed to create keypair from bytes: {}", e)) } -/// Loads the keypair from a specified path or the default Solana keypair location. -pub fn get_keypair_path(keypair_path: &str) -> PathBuf { - if keypair_path.is_empty() { - dirs::home_dir() - .expect("Could not find home directory") - .join(".config/solana/id.json") - } else { - PathBuf::from(&*shellexpand::tilde(keypair_path)) - } -} - pub fn get_payer(keypair_path: PathBuf) -> Result { let payer = match load_keypair(&keypair_path) { Ok(payer) => payer, diff --git a/cli/src/main.rs b/cli/src/main.rs index 1abf3f22..64ea971c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -49,8 +49,7 @@ async fn run_tape_cli() -> Result<()> { => { log::print_message(&format!( "Using keypair from {}", - context.keyapir_path().display() - + context.keypair_path().display() )); } _ => {} From b875f87981a58678b0659407424abbf4520e5bca Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 11 Sep 2025 21:25:06 +0530 Subject: [PATCH 12/18] feat: add config for mine A --- cli/src/cli.rs | 4 +++- cli/src/config.rs | 8 +++++--- network/src/store/helpers.rs | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 4fab05de..ef0a45ef 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -275,7 +275,9 @@ impl Context{ } pub fn open_secondary_store_conn_mine(&self) -> Result { - Ok(tape_network::store::secondary_mine()?) + let rocksdb_config = self.config.storage.rocksdb.as_ref() + .ok_or_else(|| anyhow::anyhow!("RocksDB config not found"))?; + Ok(tape_network::store::secondary_mine(&rocksdb_config.primary_path, &rocksdb_config.secondary_path_mine)?) } pub fn open_secondary_store_conn_web(&self) -> Result { diff --git a/cli/src/config.rs b/cli/src/config.rs index 041f9710..8fb0abb9 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -44,7 +44,8 @@ pub struct StorageConfig { #[derive(Debug, Serialize, Deserialize, Clone)] pub struct RocksDbConfig { pub primary_path: String, - pub secondary_path: Option, + pub secondary_path_mine: String, + pub secondary_path_web: String, pub cache_size_mb: u64, } @@ -245,8 +246,9 @@ impl Default for TapeConfig { storage: StorageConfig { backend: StorageBackend::RocksDb, rocksdb: Some(RocksDbConfig{ - primary_path: "./db_tapestore".to_string(), - secondary_path: Some("./db_tapestore_secondary".to_string()), + primary_path: "db_tapestore".to_string(), + secondary_path_mine: "db_tapestore_read_mine".to_string(), + secondary_path_web: "db_tapestore_read_web".to_string(), cache_size_mb: 512, }) }, diff --git a/network/src/store/helpers.rs b/network/src/store/helpers.rs index 0031858b..945ca810 100644 --- a/network/src/store/helpers.rs +++ b/network/src/store/helpers.rs @@ -8,10 +8,10 @@ pub fn primary() -> Result { TapeStore::new(&db_primary) } -pub fn secondary_mine() -> Result { +pub fn secondary_mine(tape_store_primary_db: &str, tape_store_secondary_db_mine: &str) -> Result { let current_dir = env::current_dir().map_err(StoreError::IoError)?; - let db_primary = current_dir.join(TAPE_STORE_PRIMARY_DB); - let db_secondary = current_dir.join(TAPE_STORE_SECONDARY_DB_MINE); + let db_primary = current_dir.join(tape_store_primary_db); + let db_secondary = current_dir.join(tape_store_secondary_db_mine); std::fs::create_dir_all(&db_secondary).map_err(StoreError::IoError)?; TapeStore::new_secondary(&db_primary, &db_secondary) } From 73d51d60e323a6d6926c16eeb91aba31e031fa2f Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Fri, 12 Sep 2025 01:16:24 +0530 Subject: [PATCH 13/18] feat: add config for web --- cli/src/cli.rs | 4 +++- network/src/store/helpers.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index ef0a45ef..7c04f53b 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -281,7 +281,9 @@ impl Context{ } pub fn open_secondary_store_conn_web(&self) -> Result { - Ok(tape_network::store::secondary_web()?) + let rocksdb_config = self.config.storage.rocksdb.as_ref() + .ok_or_else(|| anyhow::anyhow!("RocksDB config not found"))?; + Ok(tape_network::store::secondary_web(&rocksdb_config.primary_path, &rocksdb_config.secondary_path_web)?) } pub fn open_read_only_store_conn(&self) -> Result { diff --git a/network/src/store/helpers.rs b/network/src/store/helpers.rs index 945ca810..5c958e0d 100644 --- a/network/src/store/helpers.rs +++ b/network/src/store/helpers.rs @@ -16,10 +16,10 @@ pub fn secondary_mine(tape_store_primary_db: &str, tape_store_secondary_db_mine: TapeStore::new_secondary(&db_primary, &db_secondary) } -pub fn secondary_web() -> Result { +pub fn secondary_web(tape_store_primary_db: &str, tape_store_secondary_db_web: &str) -> Result { let current_dir = env::current_dir().map_err(StoreError::IoError)?; - let db_primary = current_dir.join(TAPE_STORE_PRIMARY_DB); - let db_secondary = current_dir.join(TAPE_STORE_SECONDARY_DB_WEB); + let db_primary = current_dir.join(tape_store_primary_db); + let db_secondary = current_dir.join(tape_store_secondary_db_web); std::fs::create_dir_all(&db_secondary).map_err(StoreError::IoError)?; TapeStore::new_secondary(&db_primary, &db_secondary) } From 25b548c98dc757039de9adb0934783fc6d14348c Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Fri, 3 Oct 2025 20:04:56 +0530 Subject: [PATCH 14/18] feat: add config for archive --- cli/src/cli.rs | 4 +++- cli/src/main.rs | 4 +++- network/src/store/helpers.rs | 4 ++-- network/src/store/store.rs | 5 ++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 7c04f53b..958c38ab 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -271,7 +271,9 @@ impl Context{ } pub fn open_primary_store_conn(&self) -> Result { - Ok(tape_network::store::primary()?) + let rocksdb_config = self.config.storage.rocksdb.as_ref() + .ok_or_else(|| anyhow::anyhow!("RocksDB config not found"))?; + Ok(tape_network::store::primary(&rocksdb_config.primary_path)?) } pub fn open_secondary_store_conn_mine(&self) -> Result { diff --git a/cli/src/main.rs b/cli/src/main.rs index 64ea971c..73224b7c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -87,7 +87,9 @@ async fn run_tape_cli() -> Result<()> { Commands::Web { .. } | Commands::Archive { .. } | Commands::Mine { .. } => { - TapeStore::try_init_store()?; + let rocksdb_config = context.config.storage.rocksdb.as_ref() + .ok_or_else(|| anyhow::anyhow!("RocksDB config not found"))?; + TapeStore::try_init_store(&rocksdb_config.primary_path)?; network::handle_network_commands(cli, context).await?; } diff --git a/network/src/store/helpers.rs b/network/src/store/helpers.rs index 5c958e0d..9691f4a0 100644 --- a/network/src/store/helpers.rs +++ b/network/src/store/helpers.rs @@ -1,9 +1,9 @@ use super::{TapeStore, StoreError, consts::*}; use std::{env, sync::Arc}; -pub fn primary() -> Result { +pub fn primary(tape_store_primary_db: &str) -> Result { let current_dir = env::current_dir().map_err(StoreError::IoError)?; - let db_primary = current_dir.join(TAPE_STORE_PRIMARY_DB); + let db_primary = current_dir.join(tape_store_primary_db); std::fs::create_dir_all(&db_primary).map_err(StoreError::IoError)?; TapeStore::new(&db_primary) } diff --git a/network/src/store/store.rs b/network/src/store/store.rs index 03f17075..ce0bf07a 100644 --- a/network/src/store/store.rs +++ b/network/src/store/store.rs @@ -25,8 +25,8 @@ impl TapeStore { Ok(Self { db }) } - pub fn try_init_store() -> Result<(), StoreError> { - if let Ok(_store) = super::helpers::primary() { + pub fn try_init_store(tape_store_primary_db: &str) -> Result<(), StoreError> { + if let Ok(_store) = super::helpers::primary(tape_store_primary_db) { log::debug!("Primary store initialized successfully"); } Ok(()) @@ -74,4 +74,3 @@ impl Drop for TapeStore { // RocksDB handles cleanup automatically } } - From cc62dc3651d5ca0bfae698aa9d775045def5f895 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Sat, 11 Oct 2025 12:38:06 +0530 Subject: [PATCH 15/18] feat: add config to snapshot > open_read_only_store_conn --- cli/src/cli.rs | 4 +++- network/src/store/helpers.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 958c38ab..55207530 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -289,7 +289,9 @@ impl Context{ } pub fn open_read_only_store_conn(&self) -> Result { - Ok(tape_network::store::read_only()?) + let rocksdb_config = self.config.storage.rocksdb.as_ref() + .ok_or_else(|| anyhow::anyhow!("RocksDB config not found"))?; + Ok(tape_network::store::read_only(&rocksdb_config.primary_path)?) } diff --git a/network/src/store/helpers.rs b/network/src/store/helpers.rs index 9691f4a0..b24241f7 100644 --- a/network/src/store/helpers.rs +++ b/network/src/store/helpers.rs @@ -1,4 +1,4 @@ -use super::{TapeStore, StoreError, consts::*}; +use super::{TapeStore, StoreError}; use std::{env, sync::Arc}; pub fn primary(tape_store_primary_db: &str) -> Result { @@ -24,9 +24,9 @@ pub fn secondary_web(tape_store_primary_db: &str, tape_store_secondary_db_web: & TapeStore::new_secondary(&db_primary, &db_secondary) } -pub fn read_only() -> Result { +pub fn read_only(tape_store_primary_db: &str,) -> Result { let current_dir = env::current_dir().map_err(StoreError::IoError)?; - let db_primary = current_dir.join(TAPE_STORE_PRIMARY_DB); + let db_primary = current_dir.join(tape_store_primary_db); TapeStore::new_read_only(&db_primary) } From 89fcf0f6e2eea048a9c363d93afcc4483dcea48b Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Tue, 28 Oct 2025 11:30:33 +0530 Subject: [PATCH 16/18] feat: add `max_transaction_retries` --- cli/src/cli.rs | 4 ++++ cli/src/commands/write.rs | 6 ++++-- client/src/tape/write.rs | 9 +++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 55207530..04881db3 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -298,5 +298,9 @@ impl Context{ pub fn payer(&self) -> &Keypair{ &self.payer } + + pub fn max_transaction_retries(&self) -> u32 { + self.config.solana.max_transaction_retries + } } diff --git a/cli/src/commands/write.rs b/cli/src/commands/write.rs index fff7d046..522acbb0 100644 --- a/cli/src/commands/write.rs +++ b/cli/src/commands/write.rs @@ -77,6 +77,7 @@ pub async fn handle_write_command(cli: Cli, context: Context) -> Result<()> { } + let max_transaction_retries = context.max_transaction_retries(); let Context{ rpc, payer, @@ -101,7 +102,7 @@ pub async fn handle_write_command(cli: Cli, context: Context) -> Result<()> { let (tape_address, writer_address, _sig) = create_tape(&rpc, &payer, &tape_name).await?; - write_chunks(&rpc, &payer, tape_address, writer_address, chunks, &pb).await?; + write_chunks(&rpc, &payer, tape_address, writer_address, chunks, &pb, max_transaction_retries).await?; pb.set_message("finalizing tape..."); tokio::time::sleep(Duration::from_secs(32)).await; @@ -173,6 +174,7 @@ async fn write_chunks( writer_address: Pubkey, chunks: Vec>, pb: &ProgressBar, + max_transaction_retries: u32 ) -> Result<()> { pb.set_message(""); pb.set_style( @@ -197,6 +199,7 @@ async fn write_chunks( tape_address, writer_address, &chunk, + max_transaction_retries ) .await?; pb_clone.inc(1); @@ -291,4 +294,3 @@ fn read_from_stdin() -> std::io::Result> { std::io::stdin().read_to_end(&mut buffer)?; Ok(buffer) } - diff --git a/client/src/tape/write.rs b/client/src/tape/write.rs index ee5bc604..64438a1e 100644 --- a/client/src/tape/write.rs +++ b/client/src/tape/write.rs @@ -7,10 +7,7 @@ use solana_sdk::{ }; use tape_api::instruction::tape::build_write_ix; use solana_client::nonblocking::rpc_client::RpcClient; -use crate::{ - consts::*, - utils::*, -}; +use crate::utils::*; pub async fn write_to_tape( client: &Arc, @@ -18,6 +15,7 @@ pub async fn write_to_tape( tape_address: Pubkey, writer_address: Pubkey, data: &[u8], + max_transaction_retries: u32 ) -> Result { let instruction = build_write_ix( @@ -27,7 +25,6 @@ pub async fn write_to_tape( data, ); - let sig = send_with_retry(client, &instruction, signer, MAX_RETRIES).await?; + let sig = send_with_retry(client, &instruction, signer, max_transaction_retries).await?; Ok(sig) } - From 16d0977e6eddebaeba4acff2d5d5f95ece4b32df Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Tue, 28 Oct 2025 12:03:21 +0530 Subject: [PATCH 17/18] feat: add `miner_name` --- cli/src/cli.rs | 9 +++------ cli/src/commands/info.rs | 4 ++-- cli/src/commands/network.rs | 19 ++++++++----------- cli/src/commands/snapshot.rs | 6 +++--- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 04881db3..304e1e6b 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -92,9 +92,6 @@ pub enum Commands { Mine { #[arg(help = "Miner account public key", conflicts_with = "name")] pubkey: Option, - - #[arg(help = "Name of the miner you're mining with", conflicts_with = "pubkey", short = 'n', long = "name")] - name: Option, }, Web { #[arg(help = "Port to run the web RPC service on")] @@ -184,9 +181,6 @@ pub enum InfoCommands { Miner { #[arg(help = "Miner account public key", conflicts_with = "name")] pubkey: Option, - - #[arg(help = "Name of the miner you're mining with", conflicts_with = "pubkey", short = 'n', long = "name")] - name: Option, }, Archive {}, @@ -303,4 +297,7 @@ impl Context{ self.config.solana.max_transaction_retries } + pub fn miner_name_owned(&self) -> String { + self.config.mining.miner_name.clone() + } } diff --git a/cli/src/commands/info.rs b/cli/src/commands/info.rs index 6d69f1ef..c9438269 100644 --- a/cli/src/commands/info.rs +++ b/cli/src/commands/info.rs @@ -80,8 +80,8 @@ pub async fn handle_info_commands(cli: Cli, context: Context) -> Result<()> { log::print_divider(); } - InfoCommands::Miner { pubkey, name } => { - let miner_address = get_or_create_miner(context.rpc(), context.payer(), pubkey, name, false).await?; + InfoCommands::Miner { pubkey } => { + let miner_address = get_or_create_miner(context.rpc(), context.payer(), pubkey,context.miner_name_owned(), false).await?; let (miner, _) = tapedrive::get_miner_account(context.rpc(), &miner_address).await?; log::print_section_header("Miner Account"); log::print_message(&format!("Name: {}", from_name(&miner.name))); diff --git a/cli/src/commands/network.rs b/cli/src/commands/network.rs index 2632609e..da594264 100644 --- a/cli/src/commands/network.rs +++ b/cli/src/commands/network.rs @@ -27,8 +27,8 @@ pub async fn handle_network_commands(cli: Cli, context: Context) -> Result<()> { Commands::Archive { trusted_peer, miner_address } => { handle_archive(context, trusted_peer, miner_address).await?; } - Commands::Mine { pubkey, name } => { - handle_mine(context, pubkey, name).await?; + Commands::Mine { pubkey } => { + handle_mine(context, pubkey).await?; } Commands::Register { name } => { handle_register(context, name).await?; @@ -67,7 +67,7 @@ pub async fn handle_archive( context.rpc(), context.payer(), miner_address, - None, + context.miner_name_owned(), true ).await?; @@ -84,13 +84,12 @@ pub async fn handle_archive( pub async fn handle_mine( context: Context, miner_address: Option, - miner_name: Option ) -> Result<()> { log::print_info("Starting mining service..."); let miner_address = get_or_create_miner( - context.rpc(), context.payer(), miner_address, miner_name, true).await?; + context.rpc(), context.payer(), miner_address, context.miner_name_owned(), true).await?; log::print_message(&format!("Using miner address: {miner_address}")); @@ -129,14 +128,12 @@ pub async fn get_or_create_miner( client: &Arc, payer: &Keypair, pubkey_opt: Option, - name_opt: Option, + name_opt: String, auto_register: bool, ) -> Result { - let (miner_address, name) = match (pubkey_opt, name_opt) { - (Some(_), Some(_)) => bail!("Cannot provide both pubkey and name"), - (Some(p), None) => (Pubkey::from_str(&p)?, None), - (None, Some(n)) => (miner_pda(payer.pubkey(), to_name(&n)).0, Some(n)), - (None, None) => (miner_pda(payer.pubkey(), to_name("default")).0, Some("default".to_string())), + let (miner_address, name) = match pubkey_opt { + Some(p) => (Pubkey::from_str(&p)?, None), + None => (miner_pda(payer.pubkey(), to_name(&name_opt)).0, Some(name_opt)) }; let miner_account = get_miner_account(client, &miner_address).await; diff --git a/cli/src/commands/snapshot.rs b/cli/src/commands/snapshot.rs index b53ce11f..66c1d650 100644 --- a/cli/src/commands/snapshot.rs +++ b/cli/src/commands/snapshot.rs @@ -73,7 +73,7 @@ async fn handle_resync( context.rpc(), context.payer(), miner_address, - None, + context.miner_name_owned(), false ).await?; log::print_message(&format!("Using miner address: {miner_pubkey}")); @@ -125,7 +125,7 @@ async fn handle_get_tape( context.rpc(), context.payer(), miner_address, - None, + context.miner_name_owned(), false ).await?; let miner_bytes = miner_pubkey.to_bytes(); @@ -193,7 +193,7 @@ async fn handle_get_segment( context.rpc(), context.payer(), miner_address, - None, + context.miner_name_owned(), false ).await?; let miner_bytes = miner_pubkey.to_bytes(); From d92a72245c16aef937877b52ab48255420af01e7 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Tue, 28 Oct 2025 12:22:42 +0530 Subject: [PATCH 18/18] cleanup: delete unused code --- cli/src/cli.rs | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 304e1e6b..336584f5 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -4,7 +4,6 @@ use solana_client::nonblocking::rpc_client::RpcClient; 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; @@ -188,43 +187,6 @@ pub enum InfoCommands { Block {}, } -#[derive(Debug, Clone)] -pub enum Cluster { - Localnet, - Mainnet, - Devnet, - Testnet, - Custom(String), -} - -impl Cluster { - pub fn rpc_url(&self) -> String { - match self { - Cluster::Localnet => "http://127.0.0.1:8899".to_string(), - Cluster::Mainnet => "https://api.mainnet-beta.solana.com".to_string(), - Cluster::Devnet => "https://api.devnet.solana.com".to_string(), - Cluster::Testnet => "https://api.testnet.solana.com".to_string(), - Cluster::Custom(url) => url.clone(), - } - } -} - -impl FromStr for Cluster { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "l" => Ok(Cluster::Localnet), - "m" => Ok(Cluster::Mainnet), - "d" => Ok(Cluster::Devnet), - "t" => Ok(Cluster::Testnet), - s if s.starts_with("http://") || s.starts_with("https://") => Ok(Cluster::Custom(s.to_string())), - _ => Err(format!( - "Invalid cluster value: '{s}'. Use l, m, d, t, or a valid RPC URL (http:// or https://)" - )), - } - } -} pub struct Context { pub config: Arc,