From f1116832b1a703ffb77520c102eec3554be16676 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Mon, 1 Sep 2025 16:46:47 +0530 Subject: [PATCH 01/12] 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 5cce799..ea4a171 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 f099a9c..8fe3486 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 c99a83e..31eb501 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 f9c5bb8..94971fa 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 6861c99..88b4559 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 0000000..547a1c6 --- /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 2e39f90..2414682 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 0000000..16799c8 --- /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 bab3512f89ac7472d51b6ceb81fc07e5c23452ad Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 21 Aug 2025 21:05:17 +0530 Subject: [PATCH 02/12] 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 547a1c6..26d1c0b 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 ba74f59f253e948f13dff198e5609b6e82be7106 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Mon, 1 Sep 2025 23:49:48 +0530 Subject: [PATCH 03/12] 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 26d1c0b..bbed766 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 e661c8e63ddba47378ba5f7001250570dccdee11 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Tue, 2 Sep 2025 23:42:44 +0530 Subject: [PATCH 04/12] 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 88b4559..1713dc1 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 bbed766..16a9e77 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 2414682..1c8fde9 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 c56187829707c1f1d0a6a8bf956220f1dd1840a3 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Tue, 2 Sep 2025 23:50:38 +0530 Subject: [PATCH 05/12] 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 1713dc1..7a8270e 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 1c8fde9..1abf3f2 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 596886a16c421ff4d21bbd31fb4562d7af2fc24c Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Fri, 5 Sep 2025 14:11:32 +0530 Subject: [PATCH 06/12] 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 16a9e77..862a1f4 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 d5177bb46ab3b1f8604ea56bbe25334712f2792c Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Wed, 10 Sep 2025 00:58:20 +0530 Subject: [PATCH 07/12] 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 7a8270e..6be7ac6 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 862a1f4..6fb15da 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 16799c8..779add3 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 374e13e371797f6dabc3c63f12427a9945c915b9 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 11 Sep 2025 11:21:05 +0530 Subject: [PATCH 08/12] 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 6be7ac6..430301b 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 b0a3e7b..ed34b84 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 bf32c1f0517681e9597fac98d031cdef2768fac1 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 11 Sep 2025 11:24:36 +0530 Subject: [PATCH 09/12] 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 430301b..4955850 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 6fb15da..46b0506 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 779add3..ff03e85 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 50bfabf69410ef4f48ef180b540d7fe4adb2e544 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 11 Sep 2025 11:28:25 +0530 Subject: [PATCH 10/12] 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 46b0506..8ae3ad6 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 4fa2a9492f85d0246d6badc1be64a3c3a4c57307 Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 11 Sep 2025 20:58:33 +0530 Subject: [PATCH 11/12] 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 4955850..4fab05d 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 8ae3ad6..041f971 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 ed34b84..ab8d968 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 1abf3f2..64ea971 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 02d7048cf4bc4d96e662786154fea25abe709fef Mon Sep 17 00:00:00 2001 From: 4rjunc Date: Thu, 11 Sep 2025 21:25:06 +0530 Subject: [PATCH 12/12] 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 4fab05d..ef0a45e 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 041f971..8fb0abb 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 0031858..945ca81 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) }