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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
name = "dash-evo-tool"
version = "0.8.3"
version = "0.8.4"
license = "MIT"
edition = "2021"
default-run = "dash-evo-tool"
rust-version = "1.81"
rust-version = "1.85"
build = "build.rs"

[build]
Expand Down
47 changes: 44 additions & 3 deletions src/app_dir.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
use directories::ProjectDirs;
use directories::{ProjectDirs, UserDirs};
use std::fs;
use std::path::PathBuf;
use dash_sdk::dpp::dashcore::Network;

const QUALIFIER: &str = ""; // Typically empty on macOS and Linux
const ORGANIZATION: &str = "";
const APPLICATION: &str = "Dash-Evo-Tool";

pub fn app_user_data_dir_path() -> Result<PathBuf, std::io::Error> {
let proj_dirs = ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION).ok_or_else(|| {
const CORE_APPLICATION: &str = "DashCore";

fn user_data_dir_path(app: &str) -> Result<PathBuf, std::io::Error> {
let proj_dirs = ProjectDirs::from(QUALIFIER, ORGANIZATION, app).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
"Failed to determine project directories",
)
})?;
Ok(proj_dirs.config_dir().to_path_buf())
}

pub fn app_user_data_dir_path() -> Result<PathBuf, std::io::Error> {
user_data_dir_path(APPLICATION)
}

pub fn core_user_data_dir_path() -> Result<PathBuf, std::io::Error> {
#[cfg(target_os = "linux")]
{
UserDirs::new()
.and_then(|dirs| dirs.home_dir().to_owned().into())
.map(|home_dir| home_dir.join(".dashcore"))
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
"Failed to determine user home directory",
)
})
}

#[cfg(not(target_os = "linux"))]
{
user_data_dir_path(CORE_APPLICATION)
}
}

pub fn core_cookie_path(network: Network, devnet_name: &Option<String>) -> Result<PathBuf, std::io::Error> {
core_user_data_dir_path().map(|path| {
let network_dir = match network {
Network::Dash => "",
Network::Testnet => "testnet3",
Network::Devnet => devnet_name.as_deref().unwrap_or(""),
Network::Regtest => "regtest",
_ => unimplemented!(),
};
path.join(network_dir).join(".cookie")
})
}

pub fn create_app_user_data_directory_if_not_exists() -> Result<(), std::io::Error> {
let app_data_dir = app_user_data_dir_path()?;
fs::create_dir_all(&app_data_dir)?;
Expand Down
110 changes: 54 additions & 56 deletions src/backend_task/core/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
mod refresh_wallet_info;
mod start_dash_qt;

use crate::app_dir::{core_cookie_path, core_user_data_dir_path};
use crate::backend_task::BackendTaskSuccessResult;
use crate::config::Config;
use crate::config::{Config, NetworkConfig};
use crate::context::AppContext;
use crate::model::wallet::Wallet;
use dash_sdk::dashcore_rpc::RpcApi;
Expand Down Expand Up @@ -53,62 +54,15 @@ impl AppContext {
tracing::info!("Getting best chain locks for testnet and mainnet");

// Load configs
let config = match Config::load() {
Ok(config) => config,
Err(e) => {
return Err(format!("Failed to load config: {}", e));
}
};
let maybe_mainnet_config = config.config_for_network(Network::Dash);
let maybe_testnet_config = config.config_for_network(Network::Testnet);
let config = Config::load().map_err(|e| format!("Failed to load config: {}", e))?;

// Get mainnet best chainlock
let mainnet_result = if let Some(mainnet_config) = maybe_mainnet_config {
let mainnet_addr = format!(
"http://{}:{}",
mainnet_config.core_host, mainnet_config.core_rpc_port
);
let mainnet_client = Client::new(
&mainnet_addr,
Auth::UserPass(
mainnet_config.core_rpc_user.to_string(),
mainnet_config.core_rpc_password.to_string(),
),
)
.map_err(|_| "Failed to create mainnet client".to_string())?;
mainnet_client.get_best_chain_lock().map_err(|e| {
format!(
"Failed to get best chain lock for mainnet: {}",
e.to_string()
)
})
} else {
Err("Mainnet config not found".to_string())
};

// Get testnet best chainlock
let testnet_result = if let Some(testnet_config) = maybe_testnet_config {
let testnet_addr = format!(
"http://{}:{}",
testnet_config.core_host, testnet_config.core_rpc_port
);
let testnet_client = Client::new(
&testnet_addr,
Auth::UserPass(
testnet_config.core_rpc_user.to_string(),
testnet_config.core_rpc_password.to_string(),
),
)
.map_err(|_| "Failed to create testnet client".to_string())?;
testnet_client.get_best_chain_lock().map_err(|e| {
format!(
"Failed to get best chain lock for testnet: {}",
e.to_string()
)
})
} else {
Err("Testnet config not found".to_string())
};
// Get chain locks
let mainnet_result =
Self::get_best_chain_lock(config.config_for_network(Network::Dash), Network::Dash);
let testnet_result = Self::get_best_chain_lock(
config.config_for_network(Network::Testnet),
Network::Testnet,
);

// Handle results
match (mainnet_result, testnet_result) {
Expand Down Expand Up @@ -139,4 +93,48 @@ impl AppContext {
.map(|_| BackendTaskSuccessResult::None),
}
}

fn get_best_chain_lock(
config: &Option<NetworkConfig>,
network: Network,
) -> Result<ChainLock, String> {
if let Some(network_config) = config {
let addr = format!(
"http://{}:{}",
network_config.core_host, network_config.core_rpc_port
);

let cookie_path = core_cookie_path(network, &network_config.devnet_name)
.map_err(|e| format!("Failed to get core cookie path: {}", e))?;

// Try cookie authentication first
let client = match Client::new(&addr, Auth::CookieFile(cookie_path.clone())) {
Ok(client) => Ok(client),
Err(_) => {
tracing::info!(
"Failed to authenticate using .cookie file at {:?}, falling back to user/pass",
cookie_path
);
Client::new(
&addr,
Auth::UserPass(
network_config.core_rpc_user.to_string(),
network_config.core_rpc_password.to_string(),
),
)
}
}
.map_err(|_| format!("Failed to create {} client", network.to_string()))?;

client.get_best_chain_lock().map_err(|e| {
format!(
"Failed to get best chain lock for {}: {}",
network.to_string(),
e.to_string()
)
})
} else {
Err(format!("{} config not found", network.to_string()))
}
}
}
31 changes: 22 additions & 9 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::app_dir::{core_cookie_path, core_user_data_dir_path};
use crate::backend_task::contested_names::ScheduledDPNSVote;
use crate::components::core_zmq_listener::ZMQConnectionEvent;
use crate::config::{Config, NetworkConfig};
Expand Down Expand Up @@ -70,7 +71,7 @@ impl AppContext {

// we create provider, but we need to set app context to it later, as we have a circular dependency
let provider =
Provider::new(db.clone(), &network_config).expect("Failed to initialize SDK");
Provider::new(db.clone(), network, &network_config).expect("Failed to initialize SDK");

let sdk = initialize_sdk(&network_config, network, provider.clone());

Expand All @@ -86,14 +87,26 @@ impl AppContext {
"http://{}:{}",
network_config.core_host, network_config.core_rpc_port
);
let core_client = Client::new(
&addr,
Auth::UserPass(
network_config.core_rpc_user.to_string(),
network_config.core_rpc_password.to_string(),
),
)
.ok()?;
let cookie_path = core_cookie_path(network, &network_config.devnet_name).expect("expected to get cookie path");

// Try cookie authentication first
let core_client = match Client::new(&addr, Auth::CookieFile(cookie_path.clone())) {
Ok(client) => Ok(client),
Err(_) => {
// If cookie auth fails, try user/password authentication
tracing::info!(
"Failed to authenticate using .cookie file at {:?}, falling back to user/pass",
cookie_path,
);
Client::new(
&addr,
Auth::UserPass(
network_config.core_rpc_user.to_string(),
network_config.core_rpc_password.to_string(),
),
)
}
}.expect("Failed to create CoreClient");

let wallets: BTreeMap<_, _> = db
.get_wallets(&network)
Expand Down
35 changes: 26 additions & 9 deletions src/context_provider.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use crate::config::NetworkConfig;
use crate::app_dir::{core_cookie_path, core_user_data_dir_path};
use crate::config::{Config, NetworkConfig};
use crate::context::AppContext;
use crate::database::Database;
use dash_sdk::core::LowLevelDashCoreClient as CoreClient;
use dash_sdk::dpp::dashcore::Network;
use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters;
use dash_sdk::error::ContextProviderError;
use dash_sdk::platform::ContextProvider;
use dash_sdk::platform::DataContract;
use rusqlite::Result;
use std::io::BufRead;
use std::sync::{Arc, Mutex};

pub(crate) struct Provider {
Expand All @@ -19,14 +22,28 @@ impl Provider {
/// Create new ContextProvider.
///
/// Note that you have to bind it to app context using [Provider::set_app_context()].
pub fn new(db: Arc<Database>, config: &NetworkConfig) -> Result<Self, String> {
let core_client = CoreClient::new(
&config.core_host,
config.core_rpc_port,
&config.core_rpc_user,
&config.core_rpc_password,
)
.map_err(|e| e.to_string())?;
pub fn new(db: Arc<Database>, network: Network, config: &NetworkConfig) -> Result<Self, String> {
let cookie_path = core_cookie_path(network, &config.devnet_name)
.expect("Failed to get core cookie path");

// Read the cookie from disk
let cookie = std::fs::read_to_string(cookie_path);
let (user, pass) = if let Ok(cookie) = cookie {
// split the cookie at ":", first part is user (__cookie__), second part is password
let cookie_parts: Vec<&str> = cookie.split(':').collect();
let user = cookie_parts[0];
let password = cookie_parts[1];
(user.to_string(), password.to_string())
} else {
// Fall back to the pre-set user / pass if needed
(
config.core_rpc_user.clone(),
config.core_rpc_password.clone(),
)
Comment on lines +25 to +42

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Validate cookie file content and handle potential errors.
The code inline-panics with .unwrap() for the user data directory, which may cause a crash if retrieving the home directory fails. Also, splitting on : might panic if there's no delimiter or the file is empty. Consider gracefully handling these errors:

Here is an example diff making error handling more robust:

- let cookie_path = core_user_data_dir_path().unwrap().join(".cookie");
+ let cookie_path = match core_user_data_dir_path() {
+     Ok(dir) => dir.join(".cookie"),
+     Err(e) => return Err(format!("Failed to get user data directory: {}", e)),
+ };

...

- let user = cookie_parts[0];
- let password = cookie_parts[1];
- (user.to_string(), password.to_string())
+ if cookie_parts.len() < 2 {
+     return Err("Invalid cookie format in .cookie file; expected 'user:password'".into());
+ }
+ (cookie_parts[0].to_string(), cookie_parts[1].to_string())

Committable suggestion skipped: line range outside the PR's diff.

};

let core_client = CoreClient::new(&config.core_host, config.core_rpc_port, &user, &pass)
.map_err(|e| e.to_string())?;

Ok(Self {
db,
Expand Down