Skip to content
Closed
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
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ on:
permissions:
id-token: write
attestations: write
contents: write

jobs:
build-and-release:
Expand Down
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ sha2 = "0.10.8"
arboard = { version = "3.4.0", default-features = false, features = [
"windows-sys",
] }
enum_dispatch = "0.3.13"
ambassador = "0.4.1"
directories = "5.0"

rusqlite = { version = "0.32.1", features = ["functions"]}
serde_yaml = "0.9.34+deprecated"
image = { version = "0.25.2", default-features = false, features = ["png"] }
bitflags = "2.6.0"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
rust-embed = "8.5.0"
#zmq = "0.10"
zmq = "0.10"
zeroize = "1.8.1"
4 changes: 3 additions & 1 deletion dash_core_configs/mainnet.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ rpcport=9998
rpcallowip=127.0.0.1/32
rpcuser=dashrpc
rpcpassword=password
server=1
server=1
zmqpubrawtxlocksig=tcp://0.0.0.0:23708
zmqpubrawchainlock=tcp://0.0.0.0:23708
4 changes: 3 additions & 1 deletion dash_core_configs/testnet.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ rpcport=19998
rpcallowip=127.0.0.1/32
rpcuser=dashrpc
rpcpassword=password
server=1
server=1
zmqpubrawtxlocksig=tcp://0.0.0.0:23709
zmqpubrawchainlock=tcp://0.0.0.0:23709
Binary file added icons/wallet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/withdraws.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
170 changes: 157 additions & 13 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
use crate::app_dir::{
app_user_data_file_path, copy_env_file_if_not_exists,
create_app_user_data_directory_if_not_exists,
};
use crate::backend_task::core::CoreItem;
use crate::backend_task::{BackendTask, BackendTaskSuccessResult};
use crate::components::core_zmq_listener::{CoreZMQListener, ZMQMessage};
use crate::context::AppContext;
use crate::database::Database;
use crate::logging::initialize_logger;
use crate::platform::{BackendTask, BackendTaskSuccessResult};
use crate::ui::document_query_screen::DocumentQueryScreen;
use crate::ui::dpns_contested_names_screen::DPNSContestedNamesScreen;
use crate::ui::dpns_contested_names_screen::{DPNSContestedNamesScreen, DPNSSubscreen};
use crate::ui::identities::identities_screen::IdentitiesScreen;
use crate::ui::network_chooser_screen::NetworkChooserScreen;
use crate::ui::transition_visualizer_screen::TransitionVisualizerScreen;
use crate::ui::wallet::wallets_screen::WalletsBalancesScreen;
use crate::ui::withdraws_status_screen::WithdrawsStatusScreen;
use crate::ui::{MessageType, RootScreenType, Screen, ScreenLike, ScreenType};
use dash_sdk::dpp::dashcore::Network;
use derive_more::From;
use eframe::{egui, App};
use std::collections::BTreeMap;
use std::ops::BitOrAssign;
use std::sync::Arc;
use std::sync::{mpsc, Arc};
use std::time::Instant;
use std::vec;
use tokio::sync::mpsc;
use tokio::sync::mpsc as tokiompsc;

#[derive(Debug, From)]
pub enum TaskResult {
Expand All @@ -41,8 +49,11 @@ pub struct AppState {
pub chosen_network: Network,
pub mainnet_app_context: Arc<AppContext>,
pub testnet_app_context: Option<Arc<AppContext>>,
pub task_result_sender: mpsc::Sender<TaskResult>, // Channel sender for sending task results
pub task_result_receiver: mpsc::Receiver<TaskResult>, // Channel receiver for receiving task results
pub mainnet_core_zmq_listener: CoreZMQListener,
pub testnet_core_zmq_listener: CoreZMQListener,
pub core_message_receiver: mpsc::Receiver<(ZMQMessage, Network)>,
pub task_result_sender: tokiompsc::Sender<TaskResult>, // Channel sender for sending task results
pub task_result_receiver: tokiompsc::Receiver<TaskResult>, // Channel receiver for receiving task results
last_repaint: Instant, // Track the last time we requested a repaint
}

Expand Down Expand Up @@ -98,8 +109,12 @@ impl BitOrAssign for AppAction {
}
impl AppState {
pub fn new() -> Self {
create_app_user_data_directory_if_not_exists()
.expect("Failed to create app user_data directory");
copy_env_file_if_not_exists();
initialize_logger();
let db = Arc::new(Database::new("identities.db").unwrap());
let db_file_path = app_user_data_file_path("data.db").expect("should create db file path");
let db = Arc::new(Database::new(db_file_path).unwrap());
db.initialize().unwrap();

let settings = db.get_settings().expect("expected to get settings");
Expand All @@ -116,16 +131,24 @@ impl AppState {
let testnet_app_context = AppContext::new(Network::Testnet, db.clone());

let mut identities_screen = IdentitiesScreen::new(&mainnet_app_context);
let mut dpns_contested_names_screen = DPNSContestedNamesScreen::new(&mainnet_app_context);
let mut dpns_active_contests_screen =
DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Active);
let mut dpns_past_contests_screen =
DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Past);
let mut dpns_my_usernames_screen =
DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Owned);
let mut transition_visualizer_screen =
TransitionVisualizerScreen::new(&mainnet_app_context);
let mut document_query_screen = DocumentQueryScreen::new(&mainnet_app_context);
let mut withdraws_status_screen = WithdrawsStatusScreen::new(&mainnet_app_context);
let mut network_chooser_screen = NetworkChooserScreen::new(
&mainnet_app_context,
testnet_app_context.as_ref(),
Network::Dash,
);

let mut wallets_balances_screen = WalletsBalancesScreen::new(&mainnet_app_context);

let mut selected_main_screen = RootScreenType::RootScreenIdentities;

let mut chosen_network = Network::Dash;
Expand All @@ -136,28 +159,65 @@ impl AppState {
if network == Network::Testnet && testnet_app_context.is_some() {
let testnet_app_context = testnet_app_context.as_ref().unwrap();
identities_screen = IdentitiesScreen::new(testnet_app_context);
dpns_contested_names_screen = DPNSContestedNamesScreen::new(testnet_app_context);
dpns_active_contests_screen =
DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Active);
dpns_past_contests_screen =
DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Past);
dpns_my_usernames_screen =
DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Owned);
transition_visualizer_screen = TransitionVisualizerScreen::new(testnet_app_context);
document_query_screen = DocumentQueryScreen::new(testnet_app_context);
wallets_balances_screen = WalletsBalancesScreen::new(testnet_app_context);
withdraws_status_screen = WithdrawsStatusScreen::new(testnet_app_context);
}
network_chooser_screen.current_network = chosen_network;
}

// // Create a channel with a buffer size of 32 (adjust as needed)
let (task_result_sender, task_result_receiver) = mpsc::channel(256);
let (task_result_sender, task_result_receiver) = tokiompsc::channel(256);

// Initialize the last repaint time to the current instant
let last_repaint = Instant::now();

// Create a channel for communication with the InstantSendListener
let (core_message_sender, core_message_receiver) = mpsc::channel();

// Pass the sender to the listener when creating it
let mainnet_core_zmq_listener = CoreZMQListener::spawn_listener(
Network::Dash,
"tcp://127.0.0.1:23708",
core_message_sender.clone(), // Clone the sender for each listener
)
.expect("Failed to create mainnet InstantSend listener");

let testnet_core_zmq_listener = CoreZMQListener::spawn_listener(
Network::Testnet,
"tcp://127.0.0.1:23709",
core_message_sender, // Use the original sender or create a new one if needed
)
.expect("Failed to create testnet InstantSend listener");

Self {
main_screens: [
(
RootScreenType::RootScreenIdentities,
Screen::IdentitiesScreen(identities_screen),
),
(
RootScreenType::RootScreenDPNSContestedNames,
Screen::DPNSContestedNamesScreen(dpns_contested_names_screen),
RootScreenType::RootScreenDPNSActiveContests,
Screen::DPNSContestedNamesScreen(dpns_active_contests_screen),
),
(
RootScreenType::RootScreenDPNSPastContests,
Screen::DPNSContestedNamesScreen(dpns_past_contests_screen),
),
(
RootScreenType::RootScreenDPNSOwnedNames,
Screen::DPNSContestedNamesScreen(dpns_my_usernames_screen),
),
(
RootScreenType::RootScreenWalletsBalances,
Screen::WalletsBalancesScreen(wallets_balances_screen),
),
(
RootScreenType::RootScreenTransitionVisualizerScreen,
Expand All @@ -167,6 +227,10 @@ impl AppState {
RootScreenType::RootScreenDocumentQuery,
Screen::DocumentQueryScreen(document_query_screen),
),
(
RootScreenType::RootScreenWithdrawsStatus,
Screen::WithdrawsStatusScreen(withdraws_status_screen),
),
(
RootScreenType::RootScreenNetworkChooser,
Screen::NetworkChooserScreen(network_chooser_screen),
Expand All @@ -178,6 +242,9 @@ impl AppState {
chosen_network,
mainnet_app_context,
testnet_app_context,
mainnet_core_zmq_listener,
testnet_core_zmq_listener,
core_message_receiver,
task_result_sender,
task_result_receiver,
last_repaint,
Expand Down Expand Up @@ -254,7 +321,40 @@ impl AppState {
}
}

impl AppState {}
impl AppState {
// /// This function continuously listens for asset locks and updates the wallets accordingly.
// fn start_listening_for_asset_locks(&mut self) {
// let instant_send_receiver = self.instant_send_receiver.clone(); // Clone the receiver
// let mainnet_app_context = self.mainnet_app_context.clone();
// let testnet_app_context = self.testnet_app_context.clone();
//
// // Spawn a new task to listen asynchronously for asset locks
// task::spawn_blocking(move || {
// while let Ok((tx, islock, network)) = instant_send_receiver.recv() {
// let app_context = match network {
// Network::Dash => &mainnet_app_context,
// Network::Testnet => {
// if let Some(context) = testnet_app_context.as_ref() {
// context
// } else {
// // Handle the case when testnet_app_context is None
// eprintln!("No testnet app context available for Testnet");
// continue; // Skip this iteration or handle as needed
// }
// }
// _ => continue,
// };
// // Store the asset lock transaction in the database
// if let Err(e) = app_context.store_asset_lock_in_db(&tx, islock) {
// eprintln!("Failed to store asset lock: {}", e);
// }
//
// // Sleep briefly to avoid busy-waiting
// std::thread::sleep(Duration::from_millis(50));
// }
// });
// }
}

impl App for AppState {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
Expand All @@ -279,6 +379,9 @@ impl App for AppState {
BackendTaskSuccessResult::SuccessfulVotes(_) => {
self.visible_screen_mut().refresh();
}
BackendTaskSuccessResult::WithdrawalStatus(_) => {
self.visible_screen_mut().display_task_result(message);
}
},
TaskResult::Error(message) => {
self.visible_screen_mut()
Expand All @@ -290,6 +393,47 @@ impl App for AppState {
}
}

// **Poll the instant_send_receiver for any new InstantSend messages**
while let Ok((message, network)) = self.core_message_receiver.try_recv() {
let app_context = match network {
Network::Dash => &self.mainnet_app_context,
Network::Testnet => {
if let Some(context) = self.testnet_app_context.as_ref() {
context
} else {
// Handle the case when testnet_app_context is None
eprintln!("No testnet app context available for Testnet");
continue; // Skip this iteration or handle as needed
}
}
_ => continue,
};
match message {
ZMQMessage::ISLockedTransaction(tx, is_lock) => {
// Store the asset lock transaction in the database
match app_context.received_transaction_finality(&tx, Some(is_lock), None) {
Ok(utxos) => {
let core_item =
CoreItem::ReceivedAvailableUTXOTransaction(tx.clone(), utxos);
self.visible_screen_mut()
.display_task_result(core_item.into());
}
Err(e) => {
eprintln!("Failed to store asset lock: {}", e);
}
}
}
ZMQMessage::ChainLockedLockedTransaction(tx, height) => {
if let Err(e) =
app_context.received_transaction_finality(&tx, None, Some(height))
{
eprintln!("Failed to store asset lock: {}", e);
}
}
ZMQMessage::ChainLockedBlock(_) => {}
}
}

// Use a timer to repaint the UI every 0.05 seconds
ctx.request_repaint_after(std::time::Duration::from_millis(50));

Expand Down
60 changes: 60 additions & 0 deletions src/app_dir.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use directories::ProjectDirs;
use std::fs;
use std::path::{Path, PathBuf};

const QUALIFIER: &str = ""; // Typically empty on macOS and Linux
const ORGANIZATION: &str = "";
const APPLICATION: &str = "DashEvoTool";

pub fn app_user_data_dir_path() -> Result<PathBuf, std::io::Error> {
let proj_dirs = ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION).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 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)?;

// Verify directory permissions
let metadata = fs::metadata(&app_data_dir)?;
if !metadata.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Created path is not a directory",
));
}
Ok(())
}
Comment on lines +18 to +31
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

Consider adding permission checks for Unix systems.

While the function correctly creates and verifies the directory, it might be worth adding permission checks on Unix systems to ensure the directory is readable and writable by the current user.

     if !metadata.is_dir() {
         return Err(std::io::Error::new(
             std::io::ErrorKind::Other,
             "Created path is not a directory",
         ));
     }
+    #[cfg(unix)]
+    {
+        use std::os::unix::fs::PermissionsExt;
+        let perms = metadata.permissions();
+        if perms.mode() & 0o600 != 0o600 {
+            return Err(std::io::Error::new(
+                std::io::ErrorKind::PermissionDenied,
+                "Insufficient directory permissions",
+            ));
+        }
+    }
     Ok(())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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)?;
// Verify directory permissions
let metadata = fs::metadata(&app_data_dir)?;
if !metadata.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Created path is not a directory",
));
}
Ok(())
}
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)?;
// Verify directory permissions
let metadata = fs::metadata(&app_data_dir)?;
if !metadata.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Created path is not a directory",
));
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = metadata.permissions();
if perms.mode() & 0o600 != 0o600 {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"Insufficient directory permissions",
));
}
}
Ok(())
}


pub fn app_user_data_file_path(filename: &str) -> Result<PathBuf, std::io::Error> {
if filename.is_empty() || filename.contains(std::path::is_separator) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Invalid filename",
));
}
let app_data_dir = app_user_data_dir_path()?;
Ok(app_data_dir.join(filename))
}
Comment on lines +33 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

Enhance filename validation.

While the function checks for path separators, consider adding validation for:

  1. Maximum filename length
  2. Special characters that might cause issues
  3. Reserved filenames on Windows (e.g., CON, PRN, etc.)
+const MAX_FILENAME_LENGTH: usize = 255;
+
 pub fn app_user_data_file_path(filename: &str) -> Result<PathBuf, std::io::Error> {
-    if filename.is_empty() || filename.contains(std::path::is_separator) {
+    if filename.is_empty() 
+        || filename.contains(std::path::is_separator)
+        || filename.len() > MAX_FILENAME_LENGTH
+        || filename.chars().any(|c| c.is_control())
+        || cfg!(windows) && is_windows_reserved_filename(filename)
+    {
         return Err(std::io::Error::new(
             std::io::ErrorKind::InvalidInput,
-            "Invalid filename",
+            "Invalid filename: must not be empty, contain separators, exceed length limit, or be a reserved name",
         ));
     }

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


pub fn copy_env_file_if_not_exists() {
let app_data_dir =
app_user_data_dir_path().expect("Failed to determine application data directory");
let env_file_in_app_dir = app_data_dir.join(".env".to_string());
if env_file_in_app_dir.exists() && env_file_in_app_dir.is_file() {
} else {
let env_example_file_in_exe_dir = PathBuf::from(".env.example");
if env_example_file_in_exe_dir.exists() && env_example_file_in_exe_dir.is_file() {
fs::copy(&env_example_file_in_exe_dir, env_file_in_app_dir)
.expect("Failed to copy main net env file");
} else {
let env_file_in_exe_dir = PathBuf::from(".env");
fs::copy(&env_file_in_exe_dir, env_file_in_app_dir)
.expect("Failed to copy main net env file");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ mod query_ending_times;
mod vote_on_dpns_name;

use crate::app::TaskResult;
use crate::backend_task::BackendTaskSuccessResult;
use crate::context::AppContext;
use crate::model::qualified_identity::QualifiedIdentity;
use crate::platform::BackendTaskSuccessResult;
use dash_sdk::dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice;
use dash_sdk::Sdk;
use std::sync::Arc;
Expand Down
Loading