From 67f8e61a08c4761cc2914880c14672e6613c850d Mon Sep 17 00:00:00 2001 From: Paul DeLucia <69597248+pauldelucia@users.noreply.github.com> Date: Mon, 18 Nov 2024 19:56:57 +0700 Subject: [PATCH 01/18] refactor: remove unneeded CURRENT_DB_VERSION in database init (#71) * remove current version and just use default version * fix --- .../query_dpns_contested_resources.rs | 4 ++-- .../query_dpns_vote_contenders.rs | 4 ++-- src/database/initialization.rs | 19 ++++++++++--------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/backend_task/contested_names/query_dpns_contested_resources.rs b/src/backend_task/contested_names/query_dpns_contested_resources.rs index 83bcead0..4e714787 100644 --- a/src/backend_task/contested_names/query_dpns_contested_resources.rs +++ b/src/backend_task/contested_names/query_dpns_contested_resources.rs @@ -40,7 +40,7 @@ impl AppContext { let (contested_resources) = ContestedResource::fetch_many(&sdk, query.clone()) .await .map_err(|e| { - tracing::error!("error fetching contested resources: {}", e); + tracing::error!("Error fetching contested resources: {}", e); if let dash_sdk::Error::Proof( dash_sdk::ProofVerifierError::GroveDBProofVerificationError { proof_bytes, @@ -89,7 +89,7 @@ impl AppContext { return e; } } - format!("error fetching contested resources: {}", e) + format!("Error fetching contested resources: {}", e) })?; let contested_resources_len = contested_resources.0.len(); diff --git a/src/backend_task/contested_names/query_dpns_vote_contenders.rs b/src/backend_task/contested_names/query_dpns_vote_contenders.rs index 549034fc..21f19427 100644 --- a/src/backend_task/contested_names/query_dpns_vote_contenders.rs +++ b/src/backend_task/contested_names/query_dpns_vote_contenders.rs @@ -48,8 +48,8 @@ impl AppContext { ContenderWithSerializedDocument::fetch_many(&sdk, contenders_query.clone()) .await .map_err(|e| { - tracing::error!("error fetching contested resources: {}", e); - format!("error fetching contested resources: {}", e) + tracing::error!("Error fetching contested resources: {}", e); + format!("Error fetching contested resources: {}", e) })?; self.db .insert_or_update_contenders(name, &contenders, document_type, self) diff --git a/src/database/initialization.rs b/src/database/initialization.rs index 15d047f0..967fdcfe 100644 --- a/src/database/initialization.rs +++ b/src/database/initialization.rs @@ -4,8 +4,7 @@ use rusqlite::{params, Connection}; use std::fs; use std::path::Path; -pub const DEFAULT_DB_VERSION: u16 = 1; -pub const CURRENT_DB_VERSION: u16 = 2; +pub const DEFAULT_DB_VERSION: u16 = 2; pub const DEFAULT_NETWORK: &str = "dash"; impl Database { @@ -15,10 +14,10 @@ impl Database { self.create_tables()?; self.set_default_version()?; } else { - // Perform version check and back up and recreate the database if outdated. + // If outdated, back up and either migrate or recreate the database. if let Some(current_version) = self.is_outdated()? { self.backup_db(db_file_path)?; - if let Err(e) = self.try_perform_migration(current_version, CURRENT_DB_VERSION) { + if let Err(e) = self.try_perform_migration(current_version, DEFAULT_DB_VERSION) { // The migration failed println!("Migration failed: {:?}", e); self.recreate_db(db_file_path)?; @@ -76,7 +75,8 @@ impl Database { } } - /// Checks if the current database version is below the minimum supported version. + /// Checks if the version in the current database settings is below DEFAULT_DB_VERSION. + /// If outdated, returns the version in the current database settings fn is_outdated(&self) -> rusqlite::Result> { let conn = self.conn.lock().unwrap(); let version: u16 = conn @@ -86,14 +86,14 @@ impl Database { |row| row.get(0), ) .unwrap_or(0); // Default to version 0 if there's no version set - if version < CURRENT_DB_VERSION { + if version < DEFAULT_DB_VERSION { Ok(Some(version)) } else { Ok(None) } } - /// Backs up the existing database with a unique timestamped filename, recreates `data.db`, and refreshes the connection. + /// Backs up the existing database with a unique timestamped filename in backups directory. fn backup_db(&self, db_file_path: &Path) -> rusqlite::Result<()> { if db_file_path.exists() { // Create a "backups" folder in the same directory as `data.db` if not exists @@ -112,15 +112,16 @@ impl Database { let backup_filename = format!("data_backup_{}.db", timestamp); let backup_path = backups_dir.join(backup_filename); - // Rename `data.db` to the unique backup file + // Copy `data.db` to the unique backup file fs::copy(db_file_path, &backup_path) .map_err(|e| rusqlite::Error::ToSqlConversionFailure(e.into()))?; println!("Old database backed up to {:?}", backup_path); } + Ok(()) } - /// Backs up the existing database with a unique timestamped filename, recreates `data.db`, and refreshes the connection. + /// Recreates `data.db`, and refreshes the connection. fn recreate_db(&self, db_file_path: &Path) -> rusqlite::Result<()> { // Remove the existing database file if it exists if db_file_path.exists() { From 7a2831717419af44fa874f46c7dd789dad988831 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Tue, 19 Nov 2024 14:38:54 +0200 Subject: [PATCH 02/18] chore: rename ZMQ log (#75) --- src/components/core_zmq_listener.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/core_zmq_listener.rs b/src/components/core_zmq_listener.rs index ff0de263..f260b800 100644 --- a/src/components/core_zmq_listener.rs +++ b/src/components/core_zmq_listener.rs @@ -240,7 +240,7 @@ impl CoreZMQListener { match zmq::SocketEvent::from_raw(event_number) { zmq::SocketEvent::CONNECTED => { if let Some(ref tx) = tx_zmq_status { - println!("ODY Socket connected to {}", endpoint); + println!("ZMQ Socket connected to {}", endpoint); tx.send(ZMQConnectionEvent::Connected) .expect("Failed to send connected event"); } @@ -248,7 +248,7 @@ impl CoreZMQListener { } zmq::SocketEvent::DISCONNECTED => { if let Some(ref tx) = tx_zmq_status { - println!("ODY Socket disconnected from {}", endpoint); + println!("ZMQ Socket disconnected from {}", endpoint); tx.send(ZMQConnectionEvent::Disconnected) .expect("Failed to send connected event"); } From 68cdd20a19b2b0b5099a7a7ce8f92321a21e0f4a Mon Sep 17 00:00:00 2001 From: Paul DeLucia <69597248+pauldelucia@users.noreply.github.com> Date: Wed, 20 Nov 2024 00:27:33 +0700 Subject: [PATCH 03/18] fix: error messages in dpns screen (#72) * retry when appropriate * retry both errors in both places. works on testnet * remove unnecessary check --- .../query_dpns_contested_resources.rs | 56 +++++++++++++------ .../query_dpns_vote_contenders.rs | 50 ++++++++++++++--- 2 files changed, 81 insertions(+), 25 deletions(-) diff --git a/src/backend_task/contested_names/query_dpns_contested_resources.rs b/src/backend_task/contested_names/query_dpns_contested_resources.rs index 4e714787..db8b335f 100644 --- a/src/backend_task/contested_names/query_dpns_contested_resources.rs +++ b/src/backend_task/contested_names/query_dpns_contested_resources.rs @@ -9,6 +9,7 @@ use dash_sdk::platform::FetchMany; use dash_sdk::query_types::ContestedResource; use dash_sdk::Sdk; use std::sync::Arc; +use std::time::Instant; use tokio::sync::{mpsc, OwnedSemaphorePermit, Semaphore}; impl AppContext { @@ -24,22 +25,27 @@ impl AppContext { let Some(contested_index) = document_type.find_contested_index() else { return Err("No contested index on dpns domains".to_string()); }; + const MAX_RETRIES: usize = 3; let mut start_at_value = None; loop { let query = VotePollsByDocumentTypeQuery { contract_id: data_contract.id(), document_type_name: document_type.name().to_string(), index_name: contested_index.name.clone(), - start_at_value, + start_at_value: start_at_value.clone(), start_index_values: vec!["dash".into()], // hardcoded for dpns end_index_values: vec![], limit: Some(100), order_ascending: true, }; - let (contested_resources) = ContestedResource::fetch_many(&sdk, query.clone()) - .await - .map_err(|e| { + // Initialize retry counter + let mut retries = 0; + + let contested_resources = match ContestedResource::fetch_many(&sdk, query.clone()).await + { + Ok(contested_resources) => contested_resources, + Err(e) => { tracing::error!("Error fetching contested resources: {}", e); if let dash_sdk::Error::Proof( dash_sdk::ProofVerifierError::GroveDBProofVerificationError { @@ -55,22 +61,22 @@ impl AppContext { let encoded_query = match bincode::encode_to_vec(&query, bincode::config::standard()) .map_err(|encode_err| { - tracing::error!("error encoding query: {}", encode_err); - format!("error encoding query: {}", encode_err) + tracing::error!("Error encoding query: {}", encode_err); + format!("Error encoding query: {}", encode_err) }) { Ok(encoded_query) => encoded_query, - Err(e) => return e, + Err(e) => return Err(e), }; // Encode the path_query using bincode let verification_path_query_bytes = match bincode::encode_to_vec(&path_query, bincode::config::standard()) .map_err(|encode_err| { - tracing::error!("error encoding path_query: {}", encode_err); - format!("error encoding path_query: {}", encode_err) + tracing::error!("Error encoding path_query: {}", encode_err); + format!("Error encoding path_query: {}", encode_err) }) { Ok(encoded_path_query) => encoded_path_query, - Err(e) => return e, + Err(e) => return Err(e), }; if let Err(e) = self @@ -86,12 +92,30 @@ impl AppContext { }) .map_err(|e| e.to_string()) { - return e; + return Err(e); } } - format!("Error fetching contested resources: {}", e) - })?; - + if e.to_string().contains("try another server") + || e.to_string().contains( + "contract not found when querying from value with contract info", + ) + { + retries += 1; + if retries > MAX_RETRIES { + tracing::error!("Max retries reached for query: {}", e); + return Err(format!( + "Error fetching contested resources after retries: {}", + e + )); + } else { + // Retry + continue; + } + } else { + return Err(format!("Error fetching contested resources: {}", e)); + } + } + }; let contested_resources_len = contested_resources.0.len(); if contested_resources_len == 0 { @@ -146,7 +170,7 @@ impl AppContext { .expect("expected to send refresh"); } Err(e) => { - tracing::error!("error querying dpns end times: {}", e); + tracing::error!("Error querying dpns end times: {}", e); sender .send(TaskResult::Error(e)) .await @@ -184,7 +208,7 @@ impl AppContext { } Err(e) => { tracing::error!( - "error querying dpns vote contenders for {}: {}", + "Error querying dpns vote contenders for {}: {}", name, e ); diff --git a/src/backend_task/contested_names/query_dpns_vote_contenders.rs b/src/backend_task/contested_names/query_dpns_vote_contenders.rs index 21f19427..162b7147 100644 --- a/src/backend_task/contested_names/query_dpns_vote_contenders.rs +++ b/src/backend_task/contested_names/query_dpns_vote_contenders.rs @@ -44,15 +44,47 @@ impl AppContext { result_type: ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally, }; - let contenders = - ContenderWithSerializedDocument::fetch_many(&sdk, contenders_query.clone()) - .await - .map_err(|e| { + // Define retries + const MAX_RETRIES: usize = 3; + let mut retries = 0; + + loop { + match ContenderWithSerializedDocument::fetch_many(&sdk, contenders_query.clone()).await + { + Ok(contenders) => { + // If successful, proceed to insert/update contenders + return self + .db + .insert_or_update_contenders(name, &contenders, document_type, self) + .map_err(|e| e.to_string()); + } + Err(e) => { tracing::error!("Error fetching contested resources: {}", e); - format!("Error fetching contested resources: {}", e) - })?; - self.db - .insert_or_update_contenders(name, &contenders, document_type, self) - .map_err(|e| e.to_string()) + let error_str = e.to_string(); + if error_str.contains("try another server") + || error_str.contains( + "contract not found when querying from value with contract info", + ) + { + retries += 1; + if retries > MAX_RETRIES { + tracing::error!( + "Max retries reached for query_dpns_vote_contenders: {}", + e + ); + return Err(format!( + "Error fetching contested resources after retries: {}", + e + )); + } else { + continue; + } + } else { + // For other errors, return immediately + return Err(format!("Error fetching contested resources: {}", e)); + } + } + } + } } } From d45990daf84e18d7ee3c3f17aa4fb5933d8ad64b Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Wed, 20 Nov 2024 13:46:19 +0700 Subject: [PATCH 04/18] fix: remove duplicate testnet nodes in env and remove unused env in exe dir --- .env.example | 2 +- .github/workflows/release.yml | 1 - .gitignore | 2 -- src/app_dir.rs | 7 ++----- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 1a670ee2..43284649 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ MAINNET_WALLET_PRIVATE_KEY= # Testnet configuration TESTNET_SHOW_IN_UI=true -TESTNET_DAPI_ADDRESSES=https://34.214.48.68:1443,https://35.166.18.166:1443,https://52.12.176.90:1443,https://44.233.44.95:1443,https://52.12.176.90:1443,https://44.233.44.95:1443,https://52.34.144.50:1443,https://44.240.98.102:1443,https://54.201.32.131:1443,https://52.10.229.11:1443,https://52.13.132.146:1443,https://44.228.242.181:1443,https://35.82.197.197:1443,https://52.40.219.41:1443,https://44.239.39.153:1443,https://54.149.33.167:1443,https://35.164.23.245:1443,https://52.33.28.47:1443,https://52.43.86.231:1443,https://52.43.13.92:1443,https://35.163.144.230:1443,https://52.89.154.48:1443,https://52.24.124.162:1443,https://35.85.21.179:1443,https://54.187.14.232:1443,https://54.68.235.201:1443,https://52.13.250.182:1443,https://35.82.49.196:1443,https://44.232.196.6:1443,https://54.189.164.39:1443,https://54.213.204.85:1443 +TESTNET_DAPI_ADDRESSES=https://34.214.48.68:1443,https://35.166.18.166:1443,https://52.12.176.90:1443,https://44.233.44.95:1443,https://52.34.144.50:1443,https://44.240.98.102:1443,https://54.201.32.131:1443,https://52.10.229.11:1443,https://52.13.132.146:1443,https://44.228.242.181:1443,https://35.82.197.197:1443,https://52.40.219.41:1443,https://44.239.39.153:1443,https://54.149.33.167:1443,https://35.164.23.245:1443,https://52.33.28.47:1443,https://52.43.86.231:1443,https://52.43.13.92:1443,https://35.163.144.230:1443,https://52.89.154.48:1443,https://52.24.124.162:1443,https://35.85.21.179:1443,https://54.187.14.232:1443,https://54.68.235.201:1443,https://52.13.250.182:1443,https://35.82.49.196:1443,https://44.232.196.6:1443,https://54.189.164.39:1443,https://54.213.204.85:1443 TESTNET_CORE_HOST=127.0.0.1 TESTNET_CORE_RPC_PORT=19998 TESTNET_CORE_RPC_USER=dashrpc diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 710fd8e5..d0512baf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,7 +68,6 @@ jobs: - name: Setup prerequisites run: | mkdir -p dash-evo-tool/ - cp .env.example dash-evo-tool/.env cp -r dash_core_configs/ dash-evo-tool/dash_core_configs - name: Install Rust toolchain diff --git a/.gitignore b/.gitignore index d2db760d..99cebd92 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,6 @@ debug/ target/ # Dot env file -.env -.env.backups .testnet_nodes.yml # Visual Studo Code configuration diff --git a/src/app_dir.rs b/src/app_dir.rs index 69ff100d..4b7bfa6c 100644 --- a/src/app_dir.rs +++ b/src/app_dir.rs @@ -45,16 +45,13 @@ 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 { + if !env_file_in_app_dir.exists() || !env_file_in_app_dir.is_file() { 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"); + panic!("Failed to create environment variables: failed to find .env.example file in the executable directory"); } } } From 8d452aa1d96c9a88755bef11a76550685ab3a904 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Wed, 20 Nov 2024 13:53:41 +0700 Subject: [PATCH 05/18] fix error message --- src/app_dir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app_dir.rs b/src/app_dir.rs index 4b7bfa6c..67e6867a 100644 --- a/src/app_dir.rs +++ b/src/app_dir.rs @@ -51,7 +51,7 @@ pub fn copy_env_file_if_not_exists() { fs::copy(&env_example_file_in_exe_dir, env_file_in_app_dir) .expect("Failed to copy main net env file"); } else { - panic!("Failed to create environment variables: failed to find .env.example file in the executable directory"); + panic!("Failed to find or create environment variables"); } } } From d6cc46aacca0ad8fc13edda743957db404632372 Mon Sep 17 00:00:00 2001 From: Paul DeLucia <69597248+pauldelucia@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:28:20 +0700 Subject: [PATCH 06/18] feat: dont display seconds in dpns screen last updated column (#78) --- src/ui/dpns_contested_names_screen.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ui/dpns_contested_names_screen.rs b/src/ui/dpns_contested_names_screen.rs index 07a84438..11c4f411 100644 --- a/src/ui/dpns_contested_names_screen.rs +++ b/src/ui/dpns_contested_names_screen.rs @@ -412,10 +412,14 @@ impl DPNSContestedNamesScreen { let relative_time = HumanTime::from(datetime).to_string(); - ui.label(relative_time); + if relative_time.contains("seconds") { + ui.label("now"); + } else { + ui.label(relative_time); + } } else { - // Handle case where the timestamp is invalid - ui.label("Invalid timestamp"); + // Handle case where the timestamp is invalid + ui.label("Invalid timestamp"); } } else { ui.label("Fetching"); @@ -529,7 +533,11 @@ impl DPNSContestedNamesScreen { let relative_time = HumanTime::from(datetime).to_string(); - ui.label(relative_time); + if relative_time.contains("seconds") { + ui.label("now"); + } else { + ui.label(relative_time); + } } else { // Handle case where the timestamp is invalid ui.label("Invalid timestamp"); From f7d6bb21e796742b9d8d164c40a9990b677a2807 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Wed, 20 Nov 2024 16:19:36 +0700 Subject: [PATCH 07/18] fix --- .github/workflows/release.yml | 1 + src/app_dir.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0512baf..710fd8e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,6 +68,7 @@ jobs: - name: Setup prerequisites run: | mkdir -p dash-evo-tool/ + cp .env.example dash-evo-tool/.env cp -r dash_core_configs/ dash-evo-tool/dash_core_configs - name: Install Rust toolchain diff --git a/src/app_dir.rs b/src/app_dir.rs index 67e6867a..1ddf2493 100644 --- a/src/app_dir.rs +++ b/src/app_dir.rs @@ -49,9 +49,10 @@ pub fn copy_env_file_if_not_exists() { 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"); + .expect("Failed to copy env file"); } else { - panic!("Failed to find or create environment variables"); + 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 env file"); } } } From c9cc4e7519b38f3eb4160bd636df3e135d899584 Mon Sep 17 00:00:00 2001 From: Paul DeLucia <69597248+pauldelucia@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:59:08 +0700 Subject: [PATCH 08/18] undo gitignore change --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 99cebd92..d2db760d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ debug/ target/ # Dot env file +.env +.env.backups .testnet_nodes.yml # Visual Studo Code configuration From 287441088add84024ade324db2447f1ff75d45b4 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Thu, 21 Nov 2024 11:44:35 +0200 Subject: [PATCH 09/18] feat: custom dash-qt path and conf (#73) * feat: custom dash core --- Cargo.toml | 1 + src/app.rs | 17 ++++- src/backend_task/core/mod.rs | 7 +- src/backend_task/core/start_dash_qt.rs | 55 +++++++++------- src/components/core_zmq_listener.rs | 2 +- src/context.rs | 12 +++- src/database/initialization.rs | 8 ++- src/database/settings.rs | 54 +++++++++++++++- src/ui/components/top_panel.rs | 20 +++++- src/ui/network_chooser_screen.rs | 88 +++++++++++++++++++++++++- 10 files changed, 228 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e5e7786e..3b371aa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ derive_more = "1.0.0" accesskit = "=0.16.1" egui = { version = "0.29.1" } egui_extras = "0.29.1" +rfd = "0.15.1" qrcode = "0.14.1" eframe = { version = "0.29.1", features = ["persistence"] } strum = { version = "0.26.1", features = ["derive"] } diff --git a/src/app.rs b/src/app.rs index 009bc2f9..d952724a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -123,7 +123,7 @@ impl AppState { let password_info = settings .clone() - .map(|(_, _, password_info)| password_info) + .map(|(_, _, password_info, _, _)| password_info) .flatten(); let mainnet_app_context = @@ -150,10 +150,23 @@ impl AppState { let mut proof_log_screen = ProofLogScreen::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 (custom_dash_qt_path, overwrite_dash_conf) = match settings.clone() { + Some((.., db_custom_dash_qt_path, db_overwrite_dash_qt)) => { + (db_custom_dash_qt_path, db_overwrite_dash_qt) + } + _ => { + // Default values: Use system default path and overwrite conf + (None, true) + } + }; + let mut network_chooser_screen = NetworkChooserScreen::new( &mainnet_app_context, testnet_app_context.as_ref(), Network::Dash, + custom_dash_qt_path, + overwrite_dash_conf, ); let mut wallets_balances_screen = WalletsBalancesScreen::new(&mainnet_app_context); @@ -162,7 +175,7 @@ impl AppState { let mut chosen_network = Network::Dash; - if let Some((network, screen_type, password_info)) = settings { + if let Some((network, screen_type, password_info, _, _)) = settings { selected_main_screen = screen_type; chosen_network = network; if chosen_network == Network::Testnet && testnet_app_context.is_some() { diff --git a/src/backend_task/core/mod.rs b/src/backend_task/core/mod.rs index 8dad4341..a4c9bd36 100644 --- a/src/backend_task/core/mod.rs +++ b/src/backend_task/core/mod.rs @@ -12,13 +12,14 @@ use std::sync::{Arc, RwLock}; pub(crate) enum CoreTask { GetBestChainLock, RefreshWalletInfo(Arc>), - StartDashQT(Network), + StartDashQT(Network, Option, bool), } impl PartialEq for CoreTask { fn eq(&self, other: &Self) -> bool { match (self, other) { (CoreTask::GetBestChainLock, CoreTask::GetBestChainLock) => true, (CoreTask::RefreshWalletInfo(_), CoreTask::RefreshWalletInfo(_)) => true, + (CoreTask::StartDashQT(_, _, _), CoreTask::StartDashQT(_, _, _)) => true, _ => false, } } @@ -44,8 +45,8 @@ impl AppContext { }) .map_err(|e| e.to_string()), CoreTask::RefreshWalletInfo(wallet) => self.refresh_wallet_info(wallet), - CoreTask::StartDashQT(network) => self - .start_dash_qt(network) + CoreTask::StartDashQT(network, custom_dash_qt, overwrite_dash_conf) => self + .start_dash_qt(network, custom_dash_qt, overwrite_dash_conf) .map_err(|e| e.to_string()) .map(|_| BackendTaskSuccessResult::None), } diff --git a/src/backend_task/core/start_dash_qt.rs b/src/backend_task/core/start_dash_qt.rs index 9b4a8a24..49d62626 100644 --- a/src/backend_task/core/start_dash_qt.rs +++ b/src/backend_task/core/start_dash_qt.rs @@ -8,19 +8,26 @@ use std::{env, io}; impl AppContext { /// Function to start Dash QT based on the selected network - pub(super) fn start_dash_qt(&self, network: Network) -> io::Result<()> { - // Determine the path to Dash-Qt based on the operating system - let dash_qt_path: PathBuf = if cfg!(target_os = "macos") { - PathBuf::from("/Applications/Dash-Qt.app/Contents/MacOS/Dash-Qt") - } else if cfg!(target_os = "windows") { - // Retrieve the PROGRAMFILES environment variable and construct the path - let program_files = env::var("PROGRAMFILES") - .map(PathBuf::from) - .map_err(|e| io::Error::new(io::ErrorKind::NotFound, e))?; - - program_files.join("DashCore\\dash-qt.exe") - } else { - PathBuf::from("/usr/local/bin/dash-qt") // Linux path + pub(super) fn start_dash_qt( + &self, + network: Network, + custom_dash_qt: Option, + overwrite_dash_conf: bool, + ) -> io::Result<()> { + let dash_qt_path = match custom_dash_qt { + Some(ref custom_path) => PathBuf::from(custom_path), + None => { + if cfg!(target_os = "macos") { + PathBuf::from("/Applications/Dash-Qt.app/Contents/MacOS/Dash-Qt") + } else if cfg!(target_os = "windows") { + // Retrieve the PROGRAMFILES environment variable or default to "C:\\Program Files" + let program_files = env::var("PROGRAMFILES") + .unwrap_or_else(|_| "C:\\Program Files".to_string()); + PathBuf::from(program_files).join("DashCore\\dash-qt.exe") + } else { + PathBuf::from("/usr/local/bin/dash-qt") // Default Linux path + } + } }; // Ensure the Dash-Qt binary path exists @@ -43,16 +50,20 @@ impl AppContext { } }; - // Construct the full path to the config file - let current_dir = env::current_dir()?; - let config_path = current_dir.join(config_file); + let mut command = Command::new(&dash_qt_path); + command.stdout(Stdio::null()).stderr(Stdio::null()); // Suppress output + + if overwrite_dash_conf { + // Construct the full path to the config file + let current_dir = env::current_dir()?; + let config_path = current_dir.join(config_file); + command.arg(format!("-conf={}", config_path.display())); + } else if network == Network::Testnet { + command.arg("-testnet"); + } - // Start Dash-Qt with the appropriate config - Command::new(&dash_qt_path) - .arg(format!("-conf={}", config_path.display())) - .stdout(Stdio::null()) // Optional: Suppress output - .stderr(Stdio::null()) - .spawn()?; // Spawn the Dash-Qt process + // Spawn the Dash-Qt process + command.spawn()?; Ok(()) } diff --git a/src/components/core_zmq_listener.rs b/src/components/core_zmq_listener.rs index f260b800..40aaf343 100644 --- a/src/components/core_zmq_listener.rs +++ b/src/components/core_zmq_listener.rs @@ -232,7 +232,7 @@ impl CoreZMQListener { .recv(&mut addr_msg, 0) .expect("Failed to receive address message"); - let data = event_msg.as_ref(); + let data: &[u8] = event_msg.as_ref(); // Explicitly annotate the type if data.len() >= 6 { let event_number = u16::from_le_bytes([data[0], data[1]]); let endpoint = addr_msg.as_str().unwrap_or(""); diff --git a/src/context.rs b/src/context.rs index c9ac5474..dcf76b2a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -221,7 +221,17 @@ impl AppContext { } /// Retrieves the current `RootScreenType` from the settings - pub fn get_settings(&self) -> Result)>> { + pub fn get_settings( + &self, + ) -> Result< + Option<( + Network, + RootScreenType, + Option, + Option, + bool, + )>, + > { self.db.get_settings() } diff --git a/src/database/initialization.rs b/src/database/initialization.rs index 967fdcfe..2a828c88 100644 --- a/src/database/initialization.rs +++ b/src/database/initialization.rs @@ -4,7 +4,8 @@ use rusqlite::{params, Connection}; use std::fs; use std::path::Path; -pub const DEFAULT_DB_VERSION: u16 = 2; +pub const DEFAULT_DB_VERSION: u16 = 3; + pub const DEFAULT_NETWORK: &str = "dash"; impl Database { @@ -33,6 +34,9 @@ impl Database { fn apply_version_changes(&self, version: u16) -> rusqlite::Result<()> { match version { + 3 => { + self.add_custom_dash_qt_columns()?; + } 2 => { self.initialize_proof_log_table()?; } @@ -175,6 +179,8 @@ impl Database { main_password_nonce BLOB, network TEXT NOT NULL, start_root_screen INTEGER NOT NULL, + custom_dash_qt_path TEXT, + overwrite_dash_conf INTEGER, database_version INTEGER NOT NULL )", [], diff --git a/src/database/settings.rs b/src/database/settings.rs index edec45cf..56425b95 100644 --- a/src/database/settings.rs +++ b/src/database/settings.rs @@ -3,6 +3,7 @@ use crate::model::password_info::PasswordInfo; use crate::ui::RootScreenType; use dash_sdk::dpp::dashcore::Network; use rusqlite::{params, Result}; +use std::path::PathBuf; use std::str::FromStr; impl Database { @@ -44,6 +45,35 @@ impl Database { Ok(()) } + pub fn update_dash_core_execution_settings( + &self, + custom_dash_path: Option, + overwrite_dash_conf: bool, + ) -> Result<()> { + self.execute( + "UPDATE settings + SET custom_dash_qt_path = ?, + overwrite_dash_conf = ? + WHERE id = 1", + rusqlite::params![custom_dash_path, overwrite_dash_conf], + )?; + + Ok(()) + } + + pub fn add_custom_dash_qt_columns(&self) -> Result<()> { + self.execute( + "ALTER TABLE settings ADD COLUMN custom_dash_qt_path TEXT DEFAULT NULL;", + (), + )?; + self.execute( + "ALTER TABLE settings ADD COLUMN overwrite_dash_conf INTEGER DEFAULT NULL;", + (), + )?; + + Ok(()) + } + /// Updates the database version in the settings table. pub fn update_database_version(&self, new_version: u16) -> Result<()> { // Ensure the database version is updated @@ -58,11 +88,21 @@ impl Database { } /// Retrieves the settings from the database. - pub fn get_settings(&self) -> Result)>> { + pub fn get_settings( + &self, + ) -> Result< + Option<( + Network, + RootScreenType, + Option, + Option, + bool, + )>, + > { // Query the settings row let conn = self.conn.lock().unwrap(); let mut stmt = - conn.prepare("SELECT network, start_root_screen, password_check, main_password_salt, main_password_nonce FROM settings WHERE id = 1")?; + conn.prepare("SELECT network, start_root_screen, password_check, main_password_salt, main_password_nonce, custom_dash_qt_path, overwrite_dash_conf FROM settings WHERE id = 1")?; let result = stmt.query_row([], |row| { let network: String = row.get(0)?; @@ -70,6 +110,8 @@ impl Database { let password_check: Option> = row.get(2)?; let main_password_salt: Option> = row.get(3)?; let main_password_nonce: Option> = row.get(4)?; + let custom_dash_qt_path: Option = row.get(5)?; + let overwrite_dash_conf: Option = row.get(6)?; // Combine the password-related fields if all are present, otherwise set to None let password_data = match (password_check, main_password_salt, main_password_nonce) { @@ -89,7 +131,13 @@ impl Database { let root_screen_type = RootScreenType::from_int(start_root_screen) .ok_or_else(|| rusqlite::Error::InvalidQuery)?; - Ok((parsed_network, root_screen_type, password_data)) + Ok(( + parsed_network, + root_screen_type, + password_data, + custom_dash_qt_path, + overwrite_dash_conf.unwrap_or(true), + )) }); match result { diff --git a/src/ui/components/top_panel.rs b/src/ui/components/top_panel.rs index bf371054..dbc7c6f3 100644 --- a/src/ui/components/top_panel.rs +++ b/src/ui/components/top_panel.rs @@ -92,10 +92,28 @@ fn add_connection_indicator(ui: &mut Ui, app_context: &Arc) -> AppAc }; response.clone().on_hover_text(tooltip_text); + let settings = app_context + .db + .get_settings() + .expect("Failed to db get settings"); + let (custom_dash_qt_path, overwrite_dash_conf) = match settings { + Some((.., db_custom_dash_qt_path, db_overwrite_dash_qt)) => { + (db_custom_dash_qt_path, db_overwrite_dash_qt) + } + _ => { + // Default values: Use system default path and overwrite conf + (None, true) + } + }; + // Handle click to start DashQT if disconnected if response.clicked() && !connected { let network = app_context.network; - action |= AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT(network))); + action |= AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT( + network, + custom_dash_qt_path, + overwrite_dash_conf, + ))); } }); diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 0e99c5c8..98e35a78 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -2,6 +2,7 @@ use crate::app::AppAction; use crate::backend_task::core::{CoreItem, CoreTask}; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; +use crate::model::password_info::PasswordInfo; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::wallet::add_new_wallet_screen::AddNewWalletScreen; @@ -21,6 +22,9 @@ pub struct NetworkChooserScreen { pub testnet_core_status_online: bool, status_checked: bool, pub recheck_time: Option, + custom_dash_qt_path: Option, + custom_dash_qt_error_message: Option, + overwrite_dash_conf: bool, } impl NetworkChooserScreen { @@ -28,6 +32,8 @@ impl NetworkChooserScreen { mainnet_app_context: &Arc, testnet_app_context: Option<&Arc>, current_network: Network, + custom_dash_qt_path: Option, + overwrite_dash_conf: bool, ) -> Self { Self { mainnet_app_context: mainnet_app_context.clone(), @@ -37,6 +43,9 @@ impl NetworkChooserScreen { testnet_core_status_online: false, status_checked: false, recheck_time: None, + custom_dash_qt_path, + custom_dash_qt_error_message: None, + overwrite_dash_conf, } } @@ -82,6 +91,77 @@ impl NetworkChooserScreen { // Render Testnet app_action |= self.render_network_row(ui, Network::Testnet, "Testnet"); }); + egui::CollapsingHeader::new("Show more advanced settings") + .default_open(false) // The grid is hidden by default + .show(ui, |ui| { + egui::Grid::new("advanced_settings") + .show(ui, |ui| { + ui.label("Custom Dash-QT path:"); + + if ui.button("Select file").clicked() { + if let Some(path) = rfd::FileDialog::new().pick_file() { + { + let file_name = path.file_name().and_then(|f| f.to_str()); + if let Some(file_name) = file_name { + self.custom_dash_qt_path = None; + self.custom_dash_qt_error_message = None; + let required_file_name = if cfg!(target_os = "windows") { + String::from("dash-qt.exe") + } else if cfg!(target_os = "macos") { + String::from("dash-qt") + } else { //linux + String::from("dash-qt") + }; + if file_name.ends_with(required_file_name.as_str()) { + self.custom_dash_qt_path = Some(path.display().to_string()); + self.custom_dash_qt_error_message = None; + self.current_app_context().db.update_dash_core_execution_settings(self.custom_dash_qt_path.clone(), self.overwrite_dash_conf).expect("Expected to save db settings"); + } else { + self.custom_dash_qt_error_message = Some(format!("Invalid file: Please select a valid '{}'.", required_file_name)); + } + } + } + } + } + + if let Some(ref file) = self.custom_dash_qt_path { + ui.label(format!("Selected: {}", file)); + } else if let Some(ref error) = self.custom_dash_qt_error_message { + ui.colored_label(egui::Color32::RED, error); + } else { + ui.label(""); + } + if self.custom_dash_qt_path.is_some() || self.custom_dash_qt_error_message.is_some() { + if ui.button("clear").clicked() { + self.custom_dash_qt_path = None; + self.custom_dash_qt_error_message = None; + } + } + ui.end_row(); + + if ui.checkbox(&mut self.overwrite_dash_conf, "Overwrite dash.conf").clicked() { + self.current_app_context().db.update_dash_core_execution_settings(self.custom_dash_qt_path.clone(), self.overwrite_dash_conf).expect("Expected to save db settings"); + } + if !self.overwrite_dash_conf { + ui.end_row(); + if self.current_network == Network::Dash { + ui.colored_label(egui::Color32::ORANGE, "The following lines must be included in the custom Mainnet dash.conf:"); + ui.end_row(); + ui.label("zmqpubrawtxlocksig=tcp://0.0.0.0:23708"); + ui.end_row(); + ui.label("zmqpubrawchainlock=tcp://0.0.0.0:23708"); + } + else { //Testnet + ui.colored_label(egui::Color32::ORANGE, "The following lines must be included in the custom Testnet dash.conf:"); + ui.end_row(); + ui.label("zmqpubrawtxlocksig=tcp://0.0.0.0:23709"); + ui.end_row(); + ui.label("zmqpubrawchainlock=tcp://0.0.0.0:23709"); + } + } + + }); + }); app_action } @@ -157,8 +237,11 @@ impl NetworkChooserScreen { // Add a button to start the network if ui.button("Start").clicked() { - app_action |= - AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT(network))); + app_action |= AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT( + network, + self.custom_dash_qt_path.clone(), + self.overwrite_dash_conf, + ))); // in 5 seconds self.recheck_time = Some( (SystemTime::now() @@ -200,6 +283,7 @@ impl ScreenLike for NetworkChooserScreen { } } fn ui(&mut self, ctx: &Context) -> AppAction { + //let _ = self.current_app_context().db.get_settings(); let mut action = add_top_panel( ctx, self.current_app_context(), From 5c2628d18cd0ddfef304b31ff3afb5369c8f96de Mon Sep 17 00:00:00 2001 From: Paul DeLucia <69597248+pauldelucia@users.noreply.github.com> Date: Fri, 22 Nov 2024 20:04:56 +0700 Subject: [PATCH 10/18] feat: disable refresh button in dpns screen when already refreshing (#83) --- src/backend_task/contested_names/mod.rs | 85 +------------------ .../query_dpns_contested_resources.rs | 38 ++++++--- .../query_dpns_vote_contenders.rs | 2 +- .../contested_names/vote_on_dpns_name.rs | 2 +- src/ui/dpns_contested_names_screen.rs | 27 +++++- 5 files changed, 51 insertions(+), 103 deletions(-) diff --git a/src/backend_task/contested_names/mod.rs b/src/backend_task/contested_names/mod.rs index 29383796..05844899 100644 --- a/src/backend_task/contested_names/mod.rs +++ b/src/backend_task/contested_names/mod.rs @@ -26,7 +26,6 @@ impl AppContext { sdk: &Sdk, sender: mpsc::Sender, ) -> Result { - let sdk = sdk.clone(); match &task { ContestedResourceTask::QueryDPNSContestedResources => self .query_dpns_contested_resources(sdk, sender) @@ -39,89 +38,7 @@ impl AppContext { ContestedResourceTask::VoteOnDPNSName(name, vote_choice, voters) => { self.vote_on_dpns_name(name, *vote_choice, voters, sdk, sender) .await - } // ContestedResourceTask::VoteOnContestedResource(vote_poll, vote_choice) => { - // let mut vote = Vote::default(); - // let identity_private_keys_lock = self.known_identities_private_keys.lock().await; - // let loaded_identity_lock = match self.loaded_identity.lock().await.clone() { - // Some(identity) => identity, - // None => { - // return BackendEvent::TaskCompleted { - // task: Task::Document(task), - // execution_result: Err( - // "No loaded identity for signing vote transaction".to_string(), - // ), - // }; - // } - // }; - // - // let mut signer = SimpleSigner::default(); - // let Identity::V0(identity_v0) = &loaded_identity_lock; - // for (key_id, public_key) in &identity_v0.public_keys { - // let identity_key_tuple = (identity_v0.id, *key_id); - // if let Some(private_key_bytes) = - // identity_private_keys_lock.get(&identity_key_tuple) - // { - // signer - // .private_keys - // .insert(public_key.clone(), private_key_bytes.clone()); - // } - // } - // - // let voting_public_key = match loaded_identity_lock.get_first_public_key_matching( - // Purpose::VOTING, - // HashSet::from(SecurityLevel::full_range()), - // HashSet::from(KeyType::all_key_types()), - // false, - // ) { - // Some(voting_key) => voting_key, - // None => { - // return BackendEvent::TaskCompleted { - // task: Task::Document(task), - // execution_result: Err( - // "No voting key in the loaded identity. Are you sure it's a masternode identity?".to_string() - // ), - // }; - // } - // }; - // - // match vote { - // Vote::ResourceVote(ref mut resource_vote) => match resource_vote { - // ResourceVote::V0(ref mut resource_vote_v0) => { - // resource_vote_v0.vote_poll = vote_poll.clone(); - // resource_vote_v0.resource_vote_choice = *vote_choice; - // let pro_tx_hash = self - // .loaded_identity_pro_tx_hash - // .lock() - // .await - // .expect("Expected a proTxHash in AppState"); - // match vote - // .put_to_platform_and_wait_for_response( - // pro_tx_hash, - // voting_public_key, - // sdk, - // &signer, - // None, - // ) - // .await - // { - // Ok(_) => { - // // TODO: Insert vote result into the database - // BackendEvent::TaskCompleted { - // task: Task::Document(task), - // execution_result: Ok(CompletedTaskPayload::String( - // "Vote cast successfully".to_string(), - // )), - // } - // } - // Err(e) => BackendEvent::TaskCompleted { - // task: Task::Document(task), - // execution_result: Err(e.to_string()), - // }, - // } - // } - // }, - // } - // } + } } } } diff --git a/src/backend_task/contested_names/query_dpns_contested_resources.rs b/src/backend_task/contested_names/query_dpns_contested_resources.rs index db8b335f..3a66b32f 100644 --- a/src/backend_task/contested_names/query_dpns_contested_resources.rs +++ b/src/backend_task/contested_names/query_dpns_contested_resources.rs @@ -1,4 +1,5 @@ use crate::app::TaskResult; +use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; use crate::model::proof_log_item::{ProofLogItem, RequestType}; use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -9,13 +10,12 @@ use dash_sdk::platform::FetchMany; use dash_sdk::query_types::ContestedResource; use dash_sdk::Sdk; use std::sync::Arc; -use std::time::Instant; use tokio::sync::{mpsc, OwnedSemaphorePermit, Semaphore}; impl AppContext { pub(super) async fn query_dpns_contested_resources( self: &Arc, - sdk: Sdk, + sdk: &Sdk, sender: mpsc::Sender, ) -> Result<(), String> { let data_contract = self.dpns_contract.as_ref(); @@ -23,7 +23,9 @@ impl AppContext { .document_type_for_name("domain") .expect("expected document type"); let Some(contested_index) = document_type.find_contested_index() else { - return Err("No contested index on dpns domains".to_string()); + return Err( + "Contested resource query failed: No contested index on dpns domains.".to_string(), + ); }; const MAX_RETRIES: usize = 3; let mut start_at_value = None; @@ -76,7 +78,9 @@ impl AppContext { format!("Error encoding path_query: {}", encode_err) }) { Ok(encoded_path_query) => encoded_path_query, - Err(e) => return Err(e), + Err(e) => { + return Err(format!("Contested resource query failed: {}", e)) + } }; if let Err(e) = self @@ -92,7 +96,7 @@ impl AppContext { }) .map_err(|e| e.to_string()) { - return Err(e); + return Err(format!("Contested resource query failed: {}", e)); } } if e.to_string().contains("try another server") @@ -104,7 +108,7 @@ impl AppContext { if retries > MAX_RETRIES { tracing::error!("Max retries reached for query: {}", e); return Err(format!( - "Error fetching contested resources after retries: {}", + "Contested resource query failed after retries: {}", e )); } else { @@ -112,7 +116,7 @@ impl AppContext { continue; } } else { - return Err(format!("Error fetching contested resources: {}", e)); + return Err(format!("Contested resource query failed: {}", e)); } } }; @@ -139,12 +143,14 @@ impl AppContext { let names_to_be_updated = self .db .insert_name_contests_as_normalized_names(contested_resources_as_strings, &self) - .map_err(|e| e.to_string())?; + .map_err(|e| format!("Contested resource query failed. Failed to insert name contests into database: {}", e.to_string()))?; - sender - .send(TaskResult::Refresh) - .await - .map_err(|e| e.to_string())?; + sender.send(TaskResult::Refresh).await.map_err(|e| { + format!( + "Contested resource query failed. Sender failed to send TaskResult: {}", + e.to_string() + ) + })?; // Create a semaphore with 15 permits let semaphore = Arc::new(Semaphore::new(24)); @@ -196,7 +202,7 @@ impl AppContext { // Perform the query match self_ref - .query_dpns_vote_contenders(&name, sdk, sender.clone()) + .query_dpns_vote_contenders(&name, &sdk, sender.clone()) .await { Ok(_) => { @@ -236,6 +242,12 @@ impl AppContext { start_at_value = Some((Value::Text(last_found_name), false)) } + sender + .send(TaskResult::Success(BackendTaskSuccessResult::Message( + "Finished querying DPNS contested resources".to_string(), + ))) + .await + .map_err(|e| format!("Finished querying DPNS contested resources but sender failed to send TaskResult: {}", e.to_string()))?; Ok(()) } } diff --git a/src/backend_task/contested_names/query_dpns_vote_contenders.rs b/src/backend_task/contested_names/query_dpns_vote_contenders.rs index 162b7147..6d3fb5de 100644 --- a/src/backend_task/contested_names/query_dpns_vote_contenders.rs +++ b/src/backend_task/contested_names/query_dpns_vote_contenders.rs @@ -16,7 +16,7 @@ impl AppContext { pub(super) async fn query_dpns_vote_contenders( &self, name: &String, - sdk: Sdk, + sdk: &Sdk, _sender: mpsc::Sender, ) -> Result<(), String> { let data_contract = self.dpns_contract.as_ref(); diff --git a/src/backend_task/contested_names/vote_on_dpns_name.rs b/src/backend_task/contested_names/vote_on_dpns_name.rs index 7a651466..1685eccd 100644 --- a/src/backend_task/contested_names/vote_on_dpns_name.rs +++ b/src/backend_task/contested_names/vote_on_dpns_name.rs @@ -22,7 +22,7 @@ impl AppContext { name: &String, vote_choice: ResourceVoteChoice, voters: &Vec, - sdk: Sdk, + sdk: &Sdk, _sender: mpsc::Sender, ) -> Result { // Fetch DPNS contract and document type information diff --git a/src/ui/dpns_contested_names_screen.rs b/src/ui/dpns_contested_names_screen.rs index 11c4f411..3221a804 100644 --- a/src/ui/dpns_contested_names_screen.rs +++ b/src/ui/dpns_contested_names_screen.rs @@ -1,4 +1,5 @@ use super::components::dpns_subscreen_chooser_panel::add_dpns_subscreen_chooser_panel; +use super::components::top_panel; use super::{Screen, ScreenType}; use crate::app::{AppAction, DesiredAppAction}; use crate::backend_task::contested_names::ContestedResourceTask; @@ -66,6 +67,7 @@ pub struct DPNSContestedNamesScreen { sort_order: SortOrder, show_vote_popup_info: Option<(String, ContestedResourceTask)>, pub dpns_subscreen: DPNSSubscreen, + refreshing: bool, } impl DPNSContestedNamesScreen { @@ -105,6 +107,7 @@ impl DPNSContestedNamesScreen { sort_order: SortOrder::Ascending, show_vote_popup_info: None, dpns_subscreen, + refreshing: false, } } @@ -794,28 +797,36 @@ impl ScreenLike for DPNSContestedNamesScreen { } fn display_message(&mut self, message: &str, message_type: MessageType) { + if message.contains("Finished querying DPNS contested resources") + || message.contains("Contested resource query failed") + { + self.refreshing = false; + } self.error_message = Some((message.to_string(), message_type, Utc::now())); } fn ui(&mut self, ctx: &Context) -> AppAction { self.check_error_expiration(); - let has_identity_that_can_register = !self.user_identities.is_empty(); - let query = ( + let mut top_panel_refresh_button = ( "Refresh", DesiredAppAction::BackendTask(BackendTask::ContestedResourceTask( ContestedResourceTask::QueryDPNSContestedResources, )), ); + if self.refreshing { + top_panel_refresh_button = ("Refreshing...", DesiredAppAction::None) + } + let has_identity_that_can_register = !self.user_identities.is_empty(); let right_buttons = if has_identity_that_can_register { vec![ ( "Register Name", DesiredAppAction::AddScreenType(ScreenType::RegisterDpnsName), ), - query, + top_panel_refresh_button, ] } else { - vec![query] + vec![top_panel_refresh_button] }; let mut action = add_top_panel( ctx, @@ -918,6 +929,14 @@ impl ScreenLike for DPNSContestedNamesScreen { } }); + if action + == AppAction::BackendTask(BackendTask::ContestedResourceTask( + ContestedResourceTask::QueryDPNSContestedResources, + )) + { + self.refreshing = true; + } + action } } From b23f121d0569c759f09f49db4729454efe149aaa Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Fri, 22 Nov 2024 19:43:31 +0200 Subject: [PATCH 11/18] feat: detect CPU incompatibility (#84) * added: flag cpu generic * removed target cpu generic and excluded avx >1 * feat: check_cpu_compatibility * split invalid instruction cases --- Cargo.toml | 11 +++++++- src/cpu_compatibility.rs | 55 ++++++++++++++++++++++++++++++++++++++++ src/main.rs | 4 +++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/cpu_compatibility.rs diff --git a/Cargo.toml b/Cargo.toml index 3b371aa8..7a517a8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,11 @@ edition = "2021" default-run = "dash-evo-tool" rust-version = "1.81" +[build] +rustflags = [ + "-C", "target-feature=+avx,-avx2,-avx512f,-avx512cd,-avx512er,-avx512pf,-avx512bw,-avx512dq,-avx512vl" +] + [dependencies] bip39 = { version = "2.1.0", features = ["all-languages", "rand"] } derive_more = "1.0.0" @@ -60,4 +65,8 @@ regex = "1.11" zmq = "0.10" [target.'cfg(target_os = "windows")'.dependencies] -zeromq = "0.4.1" \ No newline at end of file +zeromq = "0.4.1" + +[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies] +native-dialog = "0.7.0" +raw-cpuid = "11.2.0" \ No newline at end of file diff --git a/src/cpu_compatibility.rs b/src/cpu_compatibility.rs new file mode 100644 index 00000000..20a73628 --- /dev/null +++ b/src/cpu_compatibility.rs @@ -0,0 +1,55 @@ +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use native_dialog::MessageDialog; + +pub fn check_cpu_compatibility() { + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + use raw_cpuid::CpuId; + + let cpuid = CpuId::new(); + + if let Some(feature_info) = cpuid.get_feature_info() { + if !feature_info.has_avx() { + MessageDialog::new() + .set_type(native_dialog::MessageType::Error) + .set_title("Compatibility Error") + .set_text("Your CPU does not support AVX instructions. Please use a compatible CPU.") + .show_alert() + .unwrap(); + std::process::exit(1); + } + let avx2_supported = cpuid + .get_extended_feature_info() + .map_or(false, |ext_info| ext_info.has_avx2()); + if !avx2_supported { + MessageDialog::new() + .set_type(native_dialog::MessageType::Error) + .set_title("Compatibility Error") + .set_text("Your CPU does not support AVX2 instructions. Please use a compatible CPU.") + .show_alert() + .unwrap(); + std::process::exit(1); + } + let avx512_supported = cpuid + .get_extended_feature_info() + .map_or(false, |ext_info| ext_info.has_avx512f()); // AVX-512 Foundation + if !avx512_supported { + MessageDialog::new() + .set_type(native_dialog::MessageType::Error) + .set_title("Compatibility Error") + .set_text("Your CPU does not support AVX512 instructions. Please use a compatible CPU.") + .show_alert() + .unwrap(); + std::process::exit(1); + } + } else { + MessageDialog::new() + .set_type(native_dialog::MessageType::Error) + .set_title("Compatibility Error") + .set_text("Unable to determine CPU features.") + .show_alert() + .unwrap(); + std::process::exit(1); + } + } +} diff --git a/src/main.rs b/src/main.rs index 7b6b5a6a..75e33a99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use crate::cpu_compatibility::check_cpu_compatibility; + mod app; mod app_dir; mod backend_task; @@ -5,6 +7,7 @@ mod components; mod config; mod context; mod context_provider; +mod cpu_compatibility; mod database; mod logging; mod model; @@ -12,6 +15,7 @@ mod sdk_wrapper; mod ui; fn main() -> eframe::Result<()> { + check_cpu_compatibility(); // Initialize the Tokio runtime let runtime = tokio::runtime::Builder::new_multi_thread() .worker_threads(40) From f6ad81fe50446859c766969cfe47e56e67a28e05 Mon Sep 17 00:00:00 2001 From: Paul DeLucia <69597248+pauldelucia@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:00:00 +0700 Subject: [PATCH 12/18] feat: retry vote poll end times query and insert proof logs (#81) * feat: retry vote poll end times query * fix logging * add proof log to vote contenders query --- .../query_dpns_vote_contenders.rs | 57 +++++++- .../contested_names/query_ending_times.rs | 136 ++++++++++++++---- 2 files changed, 160 insertions(+), 33 deletions(-) diff --git a/src/backend_task/contested_names/query_dpns_vote_contenders.rs b/src/backend_task/contested_names/query_dpns_vote_contenders.rs index 6d3fb5de..082793ba 100644 --- a/src/backend_task/contested_names/query_dpns_vote_contenders.rs +++ b/src/backend_task/contested_names/query_dpns_vote_contenders.rs @@ -1,5 +1,6 @@ use crate::app::TaskResult; use crate::context::AppContext; +use crate::model::proof_log_item::{ProofLogItem, RequestType}; use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; use dash_sdk::dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dash_sdk::dpp::platform_value::Value; @@ -59,7 +60,57 @@ impl AppContext { .map_err(|e| e.to_string()); } Err(e) => { - tracing::error!("Error fetching contested resources: {}", e); + tracing::error!("Error fetching vote contenders: {}", e); + if let dash_sdk::Error::Proof( + dash_sdk::ProofVerifierError::GroveDBProofVerificationError { + proof_bytes, + path_query, + height, + time_ms, + error, + }, + ) = &e + { + // Encode the query using bincode + let encoded_query = match bincode::encode_to_vec( + &contenders_query, + bincode::config::standard(), + ) + .map_err(|encode_err| { + tracing::error!("Error encoding query: {}", encode_err); + format!("Error encoding query: {}", encode_err) + }) { + Ok(encoded_query) => encoded_query, + Err(e) => return Err(e), + }; + + // Encode the path_query using bincode + let verification_path_query_bytes = + match bincode::encode_to_vec(&path_query, bincode::config::standard()) + .map_err(|encode_err| { + tracing::error!("Error encoding path_query: {}", encode_err); + format!("Error encoding path_query: {}", encode_err) + }) { + Ok(encoded_path_query) => encoded_path_query, + Err(e) => return Err(e), + }; + + if let Err(e) = self + .db + .insert_proof_log_item(ProofLogItem { + request_type: RequestType::GetContestedResourceIdentityVotes, + request_bytes: encoded_query, + verification_path_query_bytes, + height: *height, + time_ms: *time_ms, + proof_bytes: proof_bytes.clone(), + error: Some(error.clone()), + }) + .map_err(|e| e.to_string()) + { + return Err(e); + } + } let error_str = e.to_string(); if error_str.contains("try another server") || error_str.contains( @@ -73,7 +124,7 @@ impl AppContext { e ); return Err(format!( - "Error fetching contested resources after retries: {}", + "Error fetching vote contenders after retries: {}", e )); } else { @@ -81,7 +132,7 @@ impl AppContext { } } else { // For other errors, return immediately - return Err(format!("Error fetching contested resources: {}", e)); + return Err(format!("Error fetching vote contenders: {}", e)); } } } diff --git a/src/backend_task/contested_names/query_ending_times.rs b/src/backend_task/contested_names/query_ending_times.rs index 2e78ff38..6c927bac 100644 --- a/src/backend_task/contested_names/query_ending_times.rs +++ b/src/backend_task/contested_names/query_ending_times.rs @@ -1,4 +1,5 @@ use crate::context::AppContext; +use crate::model::proof_log_item::{ProofLogItem, RequestType}; use chrono::{DateTime, Duration, Utc}; use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; use dash_sdk::dpp::voting::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePoll; @@ -32,43 +33,118 @@ impl AppContext { order_ascending: true, }; + const MAX_RETRIES: usize = 3; + let mut retries = 0; + let mut contests_end_times = BTreeMap::new(); - for (timestamp, vote_poll) in VotePoll::fetch_many(&sdk, end_time_query) - .await - .map_err(|e| format!("error querying vote poll end times: {}", e))? - { - let contests = vote_poll.into_iter().filter_map(|vote_poll| { - let VotePoll::ContestedDocumentResourceVotePoll( - ContestedDocumentResourceVotePoll { - contract_id, - document_type_name, - index_name, - index_values, - }, - ) = vote_poll; - if contract_id != self.dpns_contract.id() { - return None; - } - if document_type_name != "domain" { - return None; - } - if index_name != "parentNameAndLabel" { - return None; + loop { + match VotePoll::fetch_many(&sdk, end_time_query.clone()).await { + Ok(vote_polls) => { + for (timestamp, vote_poll_list) in vote_polls { + let contests = vote_poll_list.into_iter().filter_map(|vote_poll| { + let VotePoll::ContestedDocumentResourceVotePoll( + ContestedDocumentResourceVotePoll { + contract_id, + document_type_name, + index_name, + index_values, + }, + ) = vote_poll; + if contract_id != self.dpns_contract.id() { + return None; + } + if document_type_name != "domain" { + return None; + } + if index_name != "parentNameAndLabel" { + return None; + } + if index_values.len() != 2 { + return None; + } + index_values + .get(1) + .and_then(|a| a.to_str().ok().map(|a| (a.to_string(), timestamp))) + }); + contests_end_times.extend(contests); + } + break; } - if index_values.len() != 2 { - return None; - } - index_values - .get(1) - .and_then(|a| a.to_str().ok().map(|a| (a.to_string(), timestamp))) - }); + Err(e) => { + tracing::error!("Error fetching vote polls: {}", e); + if let dash_sdk::Error::Proof( + dash_sdk::ProofVerifierError::GroveDBProofVerificationError { + proof_bytes, + path_query, + height, + time_ms, + error, + }, + ) = &e + { + // Encode the query using bincode + let encoded_query = match bincode::encode_to_vec( + &end_time_query, + bincode::config::standard(), + ) + .map_err(|encode_err| { + tracing::error!("Error encoding query: {}", encode_err); + format!("Error encoding query: {}", encode_err) + }) { + Ok(encoded_query) => encoded_query, + Err(e) => return Err(e), + }; + + // Encode the path_query using bincode + let verification_path_query_bytes = + match bincode::encode_to_vec(&path_query, bincode::config::standard()) + .map_err(|encode_err| { + tracing::error!("Error encoding path_query: {}", encode_err); + format!("Error encoding path_query: {}", encode_err) + }) { + Ok(encoded_path_query) => encoded_path_query, + Err(e) => return Err(e), + }; - contests_end_times.extend(contests); + if let Err(e) = self + .db + .insert_proof_log_item(ProofLogItem { + request_type: RequestType::GetVotePollsByEndDate, + request_bytes: encoded_query, + verification_path_query_bytes, + height: *height, + time_ms: *time_ms, + proof_bytes: proof_bytes.clone(), + error: Some(error.clone()), + }) + .map_err(|e| e.to_string()) + { + return Err(e); + } + } + if e.to_string().contains("try another server") + || e.to_string().contains( + "contract not found when querying from value with contract info", + ) + { + retries += 1; + if retries > MAX_RETRIES { + tracing::error!("Max retries reached for query: {}", e); + return Err(format!("Error fetching vote polls after retries: {}", e)); + } else { + // Retry + continue; + } + } else { + return Err(format!("Error fetching vote polls: {}", e)); + } + } + } } self.db .update_ending_time(contests_end_times, self) - .map_err(|e| format!("error updating ending time: {}", e)) + .map_err(|e| format!("Error updating ending time: {}", e)) } } From 06bc890d394b8fc9581e5b959ada6d0a6970d720 Mon Sep 17 00:00:00 2001 From: Paul DeLucia <69597248+pauldelucia@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:40:30 +0700 Subject: [PATCH 13/18] feat: make proof logs screen default tool screen (#90) --- src/ui/components/left_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/left_panel.rs b/src/ui/components/left_panel.rs index 346faa96..8887d845 100644 --- a/src/ui/components/left_panel.rs +++ b/src/ui/components/left_panel.rs @@ -54,7 +54,7 @@ pub fn add_left_panel( ("W", RootScreenType::RootScreenWalletsBalances, "wallet.png"), ( "T", - RootScreenType::RootScreenToolsTransitionVisualizerScreen, + RootScreenType::RootScreenToolsProofLogScreen, "tools.png", ), ( From 77759bee6cbdb2c716d1c1d916541a5a7a9bdf5a Mon Sep 17 00:00:00 2001 From: Paul DeLucia <69597248+pauldelucia@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:26:24 +0700 Subject: [PATCH 14/18] fix: network chooser screen statuses (#89) * fix: show offline status when core disconnects on network chooser screen * set recheck time to none on screen load * clean * always check both network statuses * rename * handle missing configs * fix * refactor * fix usages of bitwise or operator * add back unused core task --- src/backend_task/core/mod.rs | 86 +++++++++++++++++++++++++++++ src/ui/network_chooser_screen.rs | 95 +++++++++++++++----------------- 2 files changed, 130 insertions(+), 51 deletions(-) diff --git a/src/backend_task/core/mod.rs b/src/backend_task/core/mod.rs index a4c9bd36..faa3c1e5 100644 --- a/src/backend_task/core/mod.rs +++ b/src/backend_task/core/mod.rs @@ -2,15 +2,18 @@ mod refresh_wallet_info; mod start_dash_qt; use crate::backend_task::BackendTaskSuccessResult; +use crate::config::Config; use crate::context::AppContext; use crate::model::wallet::Wallet; use dash_sdk::dashcore_rpc::RpcApi; +use dash_sdk::dashcore_rpc::{Auth, Client}; use dash_sdk::dpp::dashcore::{Address, ChainLock, Network, OutPoint, Transaction, TxOut}; use std::sync::{Arc, RwLock}; #[derive(Debug, Clone)] pub(crate) enum CoreTask { GetBestChainLock, + GetBestChainLocks, RefreshWalletInfo(Arc>), StartDashQT(Network, Option, bool), } @@ -18,6 +21,7 @@ impl PartialEq for CoreTask { fn eq(&self, other: &Self) -> bool { match (self, other) { (CoreTask::GetBestChainLock, CoreTask::GetBestChainLock) => true, + (CoreTask::GetBestChainLocks, CoreTask::GetBestChainLocks) => true, (CoreTask::RefreshWalletInfo(_), CoreTask::RefreshWalletInfo(_)) => true, (CoreTask::StartDashQT(_, _, _), CoreTask::StartDashQT(_, _, _)) => true, _ => false, @@ -29,6 +33,7 @@ impl PartialEq for CoreTask { pub(crate) enum CoreItem { ReceivedAvailableUTXOTransaction(Transaction, Vec<(OutPoint, TxOut, Address)>), ChainLock(ChainLock, Network), + ChainLocks(Option, Option), // Mainnet, Testnet } impl AppContext { @@ -44,6 +49,87 @@ impl AppContext { )) }) .map_err(|e| e.to_string()), + CoreTask::GetBestChainLocks => { + 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); + + // 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()) + }; + + // Handle results + match (mainnet_result, testnet_result) { + (Ok(mainnet_chainlock), Ok(testnet_chainlock)) => { + Ok(BackendTaskSuccessResult::CoreItem(CoreItem::ChainLocks( + Some(mainnet_chainlock), + Some(testnet_chainlock), + ))) + } + (Ok(mainnet_chainlock), Err(_)) => Ok(BackendTaskSuccessResult::CoreItem( + CoreItem::ChainLocks(Some(mainnet_chainlock), None), + )), + (Err(_), Ok(testnet_chainlock)) => Ok(BackendTaskSuccessResult::CoreItem( + CoreItem::ChainLocks(None, Some(testnet_chainlock)), + )), + (Err(_), Err(_)) => { + Err("Failed to get best chain lock for both mainnet and testnet" + .to_string()) + } + } + } CoreTask::RefreshWalletInfo(wallet) => self.refresh_wallet_info(wallet), CoreTask::StartDashQT(network, custom_dash_qt, overwrite_dash_conf) => self .start_dash_qt(network, custom_dash_qt, overwrite_dash_conf) diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 98e35a78..8ce7f470 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -2,12 +2,10 @@ use crate::app::AppAction; use crate::backend_task::core::{CoreItem, CoreTask}; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; -use crate::model::password_info::PasswordInfo; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::wallet::add_new_wallet_screen::AddNewWalletScreen; use crate::ui::{RootScreenType, Screen, ScreenLike}; -use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::dashcore::Network; use dash_sdk::dpp::identity::TimestampMillis; use eframe::egui::{self, Color32, Context, Ui}; @@ -20,7 +18,6 @@ pub struct NetworkChooserScreen { pub current_network: Network, pub mainnet_core_status_online: bool, pub testnet_core_status_online: bool, - status_checked: bool, pub recheck_time: Option, custom_dash_qt_path: Option, custom_dash_qt_error_message: Option, @@ -41,7 +38,6 @@ impl NetworkChooserScreen { current_network, mainnet_core_status_online: false, testnet_core_status_online: false, - status_checked: false, recheck_time: None, custom_dash_qt_path, custom_dash_qt_error_message: None, @@ -63,11 +59,6 @@ impl NetworkChooserScreen { self.context_for_network(self.current_network) } - /// Function to check the status of Dash Core for a given network - async fn check_core_status(app_context: &Arc) -> bool { - app_context.core_client.get_best_chain_lock().is_ok() - } - /// Render the network selection table fn render_network_table(&mut self, ui: &mut Ui) -> AppAction { let mut app_action = AppAction::None; @@ -85,10 +76,10 @@ impl NetworkChooserScreen { ui.label("Start"); ui.end_row(); - // Render Mainnet + // Render Mainnet Row app_action |= self.render_network_row(ui, Network::Dash, "Mainnet"); - // Render Testnet + // Render Testnet Row app_action |= self.render_network_row(ui, Network::Testnet, "Testnet"); }); egui::CollapsingHeader::new("Show more advanced settings") @@ -170,23 +161,11 @@ impl NetworkChooserScreen { let mut app_action = AppAction::None; ui.label(name); - // Simulate checking network status + // Check network status let is_working = self.check_network_status(network); let status_color = if is_working { Color32::from_rgb(0, 255, 0) // Green if working } else { - if let Some(recheck_time) = self.recheck_time { - let current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards"); - let current_time_ms = current_time.as_millis() as u64; - if current_time_ms >= recheck_time { - app_action |= - AppAction::BackendTask(BackendTask::CoreTask(CoreTask::GetBestChainLock)); - self.recheck_time = - Some((current_time + Duration::from_secs(5)).as_millis() as u64); - } - } Color32::from_rgb(255, 0, 0) // Red if not working }; @@ -216,7 +195,7 @@ impl NetworkChooserScreen { } else { &self.testnet_app_context.as_ref().unwrap() }; - app_action |= + app_action = AppAction::AddScreen(Screen::AddNewWalletScreen(AddNewWalletScreen::new(context))); } @@ -225,7 +204,7 @@ impl NetworkChooserScreen { if ui.checkbox(&mut is_selected, "Select").clicked() && is_selected { self.current_network = network; app_action = AppAction::SwitchNetwork(network); - // in 1 second + // Recheck in 1 second self.recheck_time = Some( (SystemTime::now() .duration_since(UNIX_EPOCH) @@ -237,26 +216,18 @@ impl NetworkChooserScreen { // Add a button to start the network if ui.button("Start").clicked() { - app_action |= AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT( + app_action = AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT( network, self.custom_dash_qt_path.clone(), self.overwrite_dash_conf, ))); - // in 5 seconds - self.recheck_time = Some( - (SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - + Duration::from_secs(5)) - .as_millis() as u64, - ); } ui.end_row(); app_action } - /// Simulate a function to check if the network is working + /// Check if the network is working fn check_network_status(&self, network: Network) -> bool { match network { Network::Dash => self.mainnet_core_status_online, @@ -267,23 +238,33 @@ impl NetworkChooserScreen { } impl ScreenLike for NetworkChooserScreen { + fn display_message(&mut self, message: &str, _message_type: super::MessageType) { + if message.contains("Failed to get best chain lock for both mainnet and testnet") { + self.mainnet_core_status_online = false; + self.testnet_core_status_online = false; + } + } + fn display_task_result(&mut self, backend_task_success_result: BackendTaskSuccessResult) { - if let BackendTaskSuccessResult::CoreItem(CoreItem::ChainLock(_, network)) = - backend_task_success_result - { - match network { - Network::Dash => { - self.mainnet_core_status_online = true; + match backend_task_success_result { + BackendTaskSuccessResult::CoreItem(CoreItem::ChainLocks( + mainnet_chainlock, + testnet_chainlock, + )) => { + match mainnet_chainlock { + Some(_) => self.mainnet_core_status_online = true, + None => self.mainnet_core_status_online = false, } - Network::Testnet => { - self.testnet_core_status_online = true; + match testnet_chainlock { + Some(_) => self.testnet_core_status_online = true, + None => self.testnet_core_status_online = false, } - _ => {} } + _ => {} } } + fn ui(&mut self, ctx: &Context) -> AppAction { - //let _ = self.current_app_context().db.get_settings(); let mut action = add_top_panel( ctx, self.current_app_context(), @@ -291,11 +272,6 @@ impl ScreenLike for NetworkChooserScreen { vec![], ); - if !self.status_checked { - self.status_checked = true; - action |= AppAction::BackendTask(BackendTask::CoreTask(CoreTask::GetBestChainLock)); - } - action |= add_left_panel( ctx, self.current_app_context(), @@ -306,6 +282,23 @@ impl ScreenLike for NetworkChooserScreen { action |= self.render_network_table(ui); }); + // Recheck both network status every 3 seconds + let recheck_time = Duration::from_secs(3); + if action == AppAction::None { + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + if let Some(time) = self.recheck_time { + if current_time.as_millis() as u64 >= time { + action = + AppAction::BackendTask(BackendTask::CoreTask(CoreTask::GetBestChainLocks)); + self.recheck_time = Some((current_time + recheck_time).as_millis() as u64); + } + } else { + self.recheck_time = Some((current_time + recheck_time).as_millis() as u64); + } + } + action } } From 679340a76b07d91352f9b2ecadc147a6115be65d Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Mon, 25 Nov 2024 18:10:22 +0300 Subject: [PATCH 15/18] feat: fund with Wallet Balance (#92) --- src/backend_task/core/refresh_wallet_info.rs | 2 +- .../identity/register_identity.rs | 3 + src/cpu_compatibility.rs | 8 ++- src/model/wallet/asset_lock_transaction.rs | 64 ++++++++++++------ src/model/wallet/mod.rs | 6 +- src/model/wallet/utxos.rs | 67 ++++++++++++++----- .../by_using_unused_balance.rs | 4 +- .../identities/add_new_identity_screen/mod.rs | 31 +++++---- .../withdraw_from_identity_screen.rs | 2 +- src/ui/wallet/wallets_screen/mod.rs | 36 +++++----- 10 files changed, 149 insertions(+), 74 deletions(-) diff --git a/src/backend_task/core/refresh_wallet_info.rs b/src/backend_task/core/refresh_wallet_info.rs index bc1fa020..9b6428c7 100644 --- a/src/backend_task/core/refresh_wallet_info.rs +++ b/src/backend_task/core/refresh_wallet_info.rs @@ -30,7 +30,7 @@ impl AppContext { // Step 2: Iterate over each address and update balances for address in &addresses { // Fetch balance for the address from Dash Core - match self.core_client.get_received_by_address(address, Some(0)) { + match self.core_client.get_received_by_address(address, None) { Ok(new_balance) => { // Update the wallet's address_balances and database { diff --git a/src/backend_task/identity/register_identity.rs b/src/backend_task/identity/register_identity.rs index ff9f4050..c460b833 100644 --- a/src/backend_task/identity/register_identity.rs +++ b/src/backend_task/identity/register_identity.rs @@ -177,6 +177,7 @@ impl AppContext { match wallet.asset_lock_transaction( sdk.network, amount, + true, identity_index, Some(self), ) { @@ -188,6 +189,7 @@ impl AppContext { wallet.asset_lock_transaction( sdk.network, amount, + true, identity_index, Some(self), )? @@ -409,6 +411,7 @@ impl AppContext { wallet .unused_asset_locks .retain(|(tx, _, _, _, _)| tx.txid() != tx_id); + wallet.identities.insert(wallet_identity_index, identity); } self.db diff --git a/src/cpu_compatibility.rs b/src/cpu_compatibility.rs index 20a73628..f9078d06 100644 --- a/src/cpu_compatibility.rs +++ b/src/cpu_compatibility.rs @@ -13,7 +13,9 @@ pub fn check_cpu_compatibility() { MessageDialog::new() .set_type(native_dialog::MessageType::Error) .set_title("Compatibility Error") - .set_text("Your CPU does not support AVX instructions. Please use a compatible CPU.") + .set_text( + "Your CPU does not support AVX instructions. Please use a compatible CPU.", + ) .show_alert() .unwrap(); std::process::exit(1); @@ -25,7 +27,9 @@ pub fn check_cpu_compatibility() { MessageDialog::new() .set_type(native_dialog::MessageType::Error) .set_title("Compatibility Error") - .set_text("Your CPU does not support AVX2 instructions. Please use a compatible CPU.") + .set_text( + "Your CPU does not support AVX2 instructions. Please use a compatible CPU.", + ) .show_alert() .unwrap(); std::process::exit(1); diff --git a/src/model/wallet/asset_lock_transaction.rs b/src/model/wallet/asset_lock_transaction.rs index 4b9d18d0..769e8b86 100644 --- a/src/model/wallet/asset_lock_transaction.rs +++ b/src/model/wallet/asset_lock_transaction.rs @@ -16,13 +16,14 @@ impl Wallet { &mut self, network: Network, amount: u64, + allow_take_fee_from_amount: bool, identity_index: u32, register_addresses: Option<&AppContext>, ) -> Result< ( Transaction, PrivateKey, - Address, + Option
, BTreeMap, ), String, @@ -37,53 +38,72 @@ impl Wallet { let one_time_key_hash = asset_lock_public_key.pubkey_hash(); let fee = 3_000; - let (utxos, change) = self - .take_unspent_utxos_for(amount + fee) + + let (utxos, change_option) = self + .take_unspent_utxos_for(amount, fee, allow_take_fee_from_amount) .ok_or("take_unspent_utxos_for() returned None".to_string())?; - let change_address = self.change_address(network, register_addresses)?; + let actual_amount = if change_option.is_none() && allow_take_fee_from_amount { + // The amount has been adjusted by taking the fee from the amount + // Calculate the adjusted amount based on the total value of the UTXOs minus the fee + let total_input_value: u64 = utxos.iter().map(|(_, (tx_out, _))| tx_out.value).sum(); + total_input_value - fee + } else { + amount + }; let payload_output = TxOut { - value: amount, + value: actual_amount, script_pubkey: ScriptBuf::new_p2pkh(&one_time_key_hash), }; let burn_output = TxOut { - value: amount, + value: actual_amount, script_pubkey: ScriptBuf::new_op_return(&[]), }; - if change < fee { - return Err("Change < Fee in asset_lock_transaction()".to_string()); - } - let change_output = TxOut { - value: change - fee, - script_pubkey: change_address.script_pubkey(), + + let (change_output, change_address) = if let Some(change) = change_option { + let change_address = self.change_address(network, register_addresses)?; + ( + Some(TxOut { + value: change, + script_pubkey: change_address.script_pubkey(), + }), + Some(change_address), + ) + } else { + (None, None) }; + let payload = AssetLockPayload { version: 1, credit_outputs: vec![payload_output], }; - // we need to get all inputs from utxos to add them to the transaction - + // Collect inputs from UTXOs let inputs = utxos .iter() - .map(|(utxo, _)| { - let mut tx_in = TxIn::default(); - tx_in.previous_output = utxo.clone(); - tx_in + .map(|(utxo, _)| TxIn { + previous_output: utxo.clone(), + ..Default::default() }) .collect(); - let sighash_u32 = 1u32; - - let mut tx: Transaction = Transaction { + let mut tx = Transaction { version: 3, lock_time: 0, input: inputs, - output: vec![burn_output, change_output], + output: { + let mut outputs = vec![burn_output]; + if let Some(change_output) = change_output { + outputs.push(change_output); + } + outputs + }, special_transaction_payload: Some(TransactionPayload::AssetLockPayloadType(payload)), }; + let sighash_u32 = 1u32; + let cache = SighashCache::new(&tx); // Next, collect the sighashes for each input since that's what we need from the diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index 199c6eb7..d642115d 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -242,7 +242,11 @@ impl Wallet { } pub fn max_balance(&self) -> u64 { - self.address_balances.values().sum::() + self.utxos + .values() + .map(|outpoints_to_tx_out| outpoints_to_tx_out.values().map(|tx_out| tx_out.value)) + .flatten() + .sum::() } fn seed_bytes(&self) -> Result<&[u8; 64], String> { diff --git a/src/model/wallet/utxos.rs b/src/model/wallet/utxos.rs index 35e67b6b..1ee13ccf 100644 --- a/src/model/wallet/utxos.rs +++ b/src/model/wallet/utxos.rs @@ -8,11 +8,13 @@ impl Wallet { pub fn take_unspent_utxos_for( &mut self, amount: u64, - ) -> Option<(BTreeMap, u64)> { + fee: u64, + allow_take_fee_from_amount: bool, + ) -> Option<(BTreeMap, Option)> { // Ensure UTXOs exist let utxos = &mut self.utxos; - let mut required: i64 = amount as i64; + let mut required: i64 = (amount + fee) as i64; let mut taken_utxos = BTreeMap::new(); let mut utxos_to_remove = Vec::new(); @@ -31,23 +33,58 @@ impl Wallet { } } - // If not enough UTXOs were found, return None + // If not enough UTXOs were found, try to adjust if allowed if required > 0 { - return None; - } - - // Remove the collected UTXOs from the wallet's UTXO map - for (address, outpoint) in utxos_to_remove { - if let Some(outpoints) = utxos.get_mut(&address) { - outpoints.remove(&outpoint); - if outpoints.is_empty() { - utxos.remove(&address); + if allow_take_fee_from_amount { + let total_collected = (amount + fee) as i64 - required; + if total_collected >= amount as i64 { + // We have enough to cover the amount, but not the fee + // So we can reduce the amount by the missing fee + let missing_fee = required; // required > 0 + let adjusted_amount = amount as i64 - missing_fee; + if adjusted_amount <= 0 { + // Cannot adjust amount to cover missing fee + return None; + } + // Remove UTXOs from wallet + for (address, outpoint) in utxos_to_remove { + if let Some(outpoints) = utxos.get_mut(&address) { + outpoints.remove(&outpoint); + if outpoints.is_empty() { + utxos.remove(&address); + } + } + } + // Return collected UTXOs and None for change + Some((taken_utxos, None)) + } else { + // Not enough to cover amount even after adjusting + return None; } + } else { + // Not enough UTXOs and not allowed to take fee from amount + return None; } - } + } else { + // Remove the collected UTXOs from the wallet's UTXO map + for (address, outpoint) in utxos_to_remove { + if let Some(outpoints) = utxos.get_mut(&address) { + outpoints.remove(&outpoint); + if outpoints.is_empty() { + utxos.remove(&address); + } + } + } + // Calculate change amount + let total_input = (amount + fee) as i64 - required; // total input collected + let change = total_input as u64 - amount - fee; - // Return the collected UTXOs and the remaining amount (which should be zero or positive) - Some((taken_utxos, required.abs() as u64)) + // If change is zero, return None + let change_option = if change > 0 { Some(change) } else { None }; + + // Return the collected UTXOs and the change amount + Some((taken_utxos, change_option)) + } } pub fn reload_utxos( diff --git a/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs b/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs index 4ded5f9b..3d67aea6 100644 --- a/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs +++ b/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs @@ -30,13 +30,15 @@ impl AddNewIdentityScreen { self.show_wallet_balance(ui); - step_number += 1; + ui.add_space(10.0); ui.heading(format!( "{}. How much of your wallet balance would you like to transfer?", step_number )); + step_number += 1; + self.render_funding_amount_input(ui); // Extract the step from the RwLock to minimize borrow scope diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index 3eff547c..e99943dc 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -517,19 +517,24 @@ impl AddNewIdentityScreen { *step = AddNewIdentityWalletFundedScreenStep::ReadyToCreate; } } - // if has_balance { - // if ui - // .selectable_value( - // &mut *funding_method, - // FundingMethod::UseWalletBalance, - // "Use Wallet Balance", - // ) - // .changed() - // { - // let mut step = self.step.write().unwrap(); // Write lock on step - // *step = AddNewIdentityWalletFundedScreenStep::ReadyToCreate; - // } - // } + if has_balance { + if ui + .selectable_value( + &mut *funding_method, + FundingMethod::UseWalletBalance, + "Use Wallet Balance", + ) + .changed() + { + if let Some(wallet) = &self.selected_wallet { + let wallet = wallet.read().unwrap(); // Read lock on the wallet + let max_amount = wallet.max_balance(); + self.funding_amount = format!("{:.4}", max_amount as f64 * 1e-8); + } + let mut step = self.step.write().unwrap(); // Write lock on step + *step = AddNewIdentityWalletFundedScreenStep::ReadyToCreate; + } + } if ui .selectable_value( &mut *funding_method, diff --git a/src/ui/identities/withdraw_from_identity_screen.rs b/src/ui/identities/withdraw_from_identity_screen.rs index f51738ef..b2e38954 100644 --- a/src/ui/identities/withdraw_from_identity_screen.rs +++ b/src/ui/identities/withdraw_from_identity_screen.rs @@ -105,7 +105,7 @@ impl WithdrawalScreen { ui.text_edit_singleline(&mut self.withdrawal_amount); if ui.button("Max").clicked() { - let expected_max_amount = self.max_amount.saturating_sub(5000000) as f64 * 1e-8; + let expected_max_amount = self.max_amount.saturating_sub(5000000) as f64 * 1e-11; // Use flooring and format the result with 4 decimal places let floored_amount = (expected_max_amount * 10_000.0).floor() / 10_000.0; diff --git a/src/ui/wallet/wallets_screen/mod.rs b/src/ui/wallet/wallets_screen/mod.rs index 94a267b6..16d05b1b 100644 --- a/src/ui/wallet/wallets_screen/mod.rs +++ b/src/ui/wallet/wallets_screen/mod.rs @@ -392,7 +392,7 @@ impl WalletsBalancesScreen { .resizable(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::auto()) // Address - // .column(Column::initial(100.0)) // Balance + .column(Column::initial(100.0)) // Balance .column(Column::initial(60.0)) // UTXOs .column(Column::initial(150.0)) // Total Received .column(Column::initial(100.0)) // Type @@ -412,19 +412,19 @@ impl WalletsBalancesScreen { self.toggle_sort(SortColumn::Address); } }); - // header.col(|ui| { - // let label = if self.sort_column == SortColumn::Balance { - // match self.sort_order { - // SortOrder::Ascending => "Balance (DASH) ^", - // SortOrder::Descending => "Balance (DASH) v", - // } - // } else { - // "Balance (DASH)" - // }; - // if ui.button(label).clicked() { - // self.toggle_sort(SortColumn::Balance); - // } - // }); + header.col(|ui| { + let label = if self.sort_column == SortColumn::Balance { + match self.sort_order { + SortOrder::Ascending => "Total Received (DASH) ^", + SortOrder::Descending => "Total Received (DASH) v", + } + } else { + "Total Received (DASH)" + }; + if ui.button(label).clicked() { + self.toggle_sort(SortColumn::Balance); + } + }); header.col(|ui| { let label = if self.sort_column == SortColumn::UTXOs { match self.sort_order { @@ -497,10 +497,10 @@ impl WalletsBalancesScreen { row.col(|ui| { ui.label(data.address.to_string()); }); - // row.col(|ui| { - // let dash_balance = data.balance as f64 * 1e-8; - // ui.label(format!("{:.8}", dash_balance)); - // }); + row.col(|ui| { + let dash_balance = data.balance as f64 * 1e-8; + ui.label(format!("{:.8}", dash_balance)); + }); row.col(|ui| { ui.label(format!("{}", data.utxo_count)); }); From 9b36bf95cac4de82497da4586b8e57e151c801d1 Mon Sep 17 00:00:00 2001 From: Paul DeLucia <69597248+pauldelucia@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:18:15 +0700 Subject: [PATCH 16/18] fix: mn voting keys were not always displaying (#95) --- src/ui/identities/identities_screen.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index 046cb760..587dfc05 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -416,6 +416,8 @@ impl IdentitiesScreen { } } } + } else { + more_keys_available = true; } // If there are more keys, show "View More" button From 62d13d15dbd0cec1cc4e3bd73fdbd70d94ed400e Mon Sep 17 00:00:00 2001 From: Paul DeLucia <69597248+pauldelucia@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:25:10 +0700 Subject: [PATCH 17/18] fix: limit 100 in dpns end time query (#91) * fix: limit 100 in dpns end time query * fix: we were repeatedly querying dpns end times and contenders --- .../query_dpns_contested_resources.rs | 158 +++++++++--------- .../contested_names/query_ending_times.rs | 60 +++---- 2 files changed, 110 insertions(+), 108 deletions(-) diff --git a/src/backend_task/contested_names/query_dpns_contested_resources.rs b/src/backend_task/contested_names/query_dpns_contested_resources.rs index 3a66b32f..a1659aad 100644 --- a/src/backend_task/contested_names/query_dpns_contested_resources.rs +++ b/src/backend_task/contested_names/query_dpns_contested_resources.rs @@ -29,6 +29,7 @@ impl AppContext { }; const MAX_RETRIES: usize = 3; let mut start_at_value = None; + let mut names_to_be_updated = Vec::new(); loop { let query = VotePollsByDocumentTypeQuery { contract_id: data_contract.id(), @@ -140,11 +141,13 @@ impl AppContext { let last_found_name = contested_resources_as_strings.last().unwrap().clone(); - let names_to_be_updated = self + let new_names_to_be_updated = self .db .insert_name_contests_as_normalized_names(contested_resources_as_strings, &self) .map_err(|e| format!("Contested resource query failed. Failed to insert name contests into database: {}", e.to_string()))?; + names_to_be_updated.extend(new_names_to_be_updated); + sender.send(TaskResult::Refresh).await.map_err(|e| { format!( "Contested resource query failed. Sender failed to send TaskResult: {}", @@ -152,94 +155,91 @@ impl AppContext { ) })?; - // Create a semaphore with 15 permits - let semaphore = Arc::new(Semaphore::new(24)); + if contested_resources_len < 100 { + break; + } + start_at_value = Some((Value::Text(last_found_name), false)) + } - let mut handles = Vec::new(); + // Create a semaphore with 15 permits + let semaphore = Arc::new(Semaphore::new(24)); - let handle = { - let semaphore = semaphore.clone(); - let sdk = sdk.clone(); - let sender = sender.clone(); - let self_ref = self.clone(); + let mut handles = Vec::new(); - tokio::spawn(async move { - // Acquire a permit from the semaphore - let _permit: OwnedSemaphorePermit = semaphore.acquire_owned().await.unwrap(); + let handle = { + let semaphore = semaphore.clone(); + let sdk = sdk.clone(); + let sender = sender.clone(); + let self_ref = self.clone(); - match self_ref.query_dpns_ending_times(sdk, sender.clone()).await { - Ok(_) => { - // Send a refresh message if the query succeeded - sender - .send(TaskResult::Refresh) - .await - .expect("expected to send refresh"); - } - Err(e) => { - tracing::error!("Error querying dpns end times: {}", e); - sender - .send(TaskResult::Error(e)) - .await - .expect("expected to send error"); - } - } - }) - }; + tokio::spawn(async move { + // Acquire a permit from the semaphore + let _permit: OwnedSemaphorePermit = semaphore.acquire_owned().await.unwrap(); - handles.push(handle); + match self_ref.query_dpns_ending_times(sdk, sender.clone()).await { + Ok(_) => { + // Send a refresh message if the query succeeded + sender + .send(TaskResult::Refresh) + .await + .expect("expected to send refresh"); + } + Err(e) => { + tracing::error!("Error querying dpns end times: {}", e); + sender + .send(TaskResult::Error(e)) + .await + .expect("expected to send error"); + } + } + }) + }; - for name in names_to_be_updated { - // Clone the semaphore, sdk, and sender for each task - let semaphore = semaphore.clone(); - let sdk = sdk.clone(); - let sender = sender.clone(); - let self_ref = self.clone(); // Assuming self is cloneable - - // Spawn each task with a permit from the semaphore - let handle = tokio::spawn(async move { - // Acquire a permit from the semaphore - let _permit: OwnedSemaphorePermit = semaphore.acquire_owned().await.unwrap(); - - // Perform the query - match self_ref - .query_dpns_vote_contenders(&name, &sdk, sender.clone()) - .await - { - Ok(_) => { - // Send a refresh message if the query succeeded - sender - .send(TaskResult::Refresh) - .await - .expect("expected to send refresh"); - } - Err(e) => { - tracing::error!( - "Error querying dpns vote contenders for {}: {}", - name, - e - ); - sender - .send(TaskResult::Error(e)) - .await - .expect("expected to send error"); - } + handles.push(handle); + + for name in names_to_be_updated { + // Clone the semaphore, sdk, and sender for each task + let semaphore = semaphore.clone(); + let sdk = sdk.clone(); + let sender = sender.clone(); + let self_ref = self.clone(); // Assuming self is cloneable + + // Spawn each task with a permit from the semaphore + let handle = tokio::spawn(async move { + // Acquire a permit from the semaphore + let _permit: OwnedSemaphorePermit = semaphore.acquire_owned().await.unwrap(); + + // Perform the query + match self_ref + .query_dpns_vote_contenders(&name, &sdk, sender.clone()) + .await + { + Ok(_) => { + // Send a refresh message if the query succeeded + sender + .send(TaskResult::Refresh) + .await + .expect("expected to send refresh"); + } + Err(e) => { + tracing::error!("Error querying dpns vote contenders for {}: {}", name, e); + sender + .send(TaskResult::Error(e)) + .await + .expect("expected to send error"); } - }); + } + }); - // Collect all task handles - handles.push(handle); - } + // Collect all task handles + handles.push(handle); + } - // Await all tasks - for handle in handles { - if let Err(e) = handle.await { - tracing::error!("Task failed: {:?}", e); - } + // Await all tasks + for handle in handles { + if let Err(e) = handle.await { + tracing::error!("Task failed: {:?}", e); } - if contested_resources_len < 100 { - break; - } - start_at_value = Some((Value::Text(last_found_name), false)) } sender diff --git a/src/backend_task/contested_names/query_ending_times.rs b/src/backend_task/contested_names/query_ending_times.rs index 6c927bac..82638093 100644 --- a/src/backend_task/contested_names/query_ending_times.rs +++ b/src/backend_task/contested_names/query_ending_times.rs @@ -1,7 +1,6 @@ use crate::context::AppContext; use crate::model::proof_log_item::{ProofLogItem, RequestType}; use chrono::{DateTime, Duration, Utc}; -use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; use dash_sdk::dpp::voting::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePoll; use dash_sdk::dpp::voting::vote_polls::VotePoll; use dash_sdk::drive::query::VotePollsByEndDateDriveQuery; @@ -22,54 +21,44 @@ impl AppContext { let now: DateTime = Utc::now(); let start_time_dt = now - Duration::weeks(2); let end_time_dt = now + Duration::weeks(2); - let start_time = Some((start_time_dt.timestamp_millis() as u64, true)); + let mut start_time = Some((start_time_dt.timestamp_millis() as u64, true)); let end_time = Some((end_time_dt.timestamp_millis() as u64, true)); - let end_time_query = VotePollsByEndDateDriveQuery { - start_time, - end_time, - limit: None, - offset: None, - order_ascending: true, - }; + let mut contests_end_times = BTreeMap::new(); const MAX_RETRIES: usize = 3; let mut retries = 0; - let mut contests_end_times = BTreeMap::new(); - loop { - match VotePoll::fetch_many(&sdk, end_time_query.clone()).await { + let end_time_query = VotePollsByEndDateDriveQuery { + start_time, + end_time, + limit: Some(100), + offset: None, + order_ascending: true, + }; + + let new_end_times = match VotePoll::fetch_many(&sdk, end_time_query.clone()).await { Ok(vote_polls) => { + let mut end_times = BTreeMap::new(); for (timestamp, vote_poll_list) in vote_polls { let contests = vote_poll_list.into_iter().filter_map(|vote_poll| { let VotePoll::ContestedDocumentResourceVotePoll( ContestedDocumentResourceVotePoll { - contract_id, - document_type_name, - index_name, + contract_id: _, + document_type_name: _, + index_name: _, index_values, }, ) = vote_poll; - if contract_id != self.dpns_contract.id() { - return None; - } - if document_type_name != "domain" { - return None; - } - if index_name != "parentNameAndLabel" { - return None; - } - if index_values.len() != 2 { - return None; - } + index_values .get(1) .and_then(|a| a.to_str().ok().map(|a| (a.to_string(), timestamp))) }); - contests_end_times.extend(contests); + end_times.extend(contests); } - break; + end_times } Err(e) => { tracing::error!("Error fetching vote polls: {}", e); @@ -140,6 +129,19 @@ impl AppContext { return Err(format!("Error fetching vote polls: {}", e)); } } + }; + + contests_end_times.extend(new_end_times.clone()); + + if new_end_times.len() == 0 { + break; + } + + let last_found_ending_time = new_end_times.values().max(); + if let Some(last_found_ending_time) = last_found_ending_time { + start_time = Some((*last_found_ending_time, false)); + } else { + break; } } From 07ffa359e573670a19ded5a4096ae25fccda5627 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Tue, 26 Nov 2024 14:27:37 +0200 Subject: [PATCH 18/18] feat: simulate heartbeat for windows zmq (#93) --- src/components/core_zmq_listener.rs | 33 +++++++++++++++++++++++------ src/ui/components/top_panel.rs | 5 +---- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/components/core_zmq_listener.rs b/src/components/core_zmq_listener.rs index 40aaf343..353cb291 100644 --- a/src/components/core_zmq_listener.rs +++ b/src/components/core_zmq_listener.rs @@ -21,6 +21,8 @@ use futures::StreamExt; #[cfg(target_os = "windows")] use tokio::runtime::Runtime; #[cfg(target_os = "windows")] +use tokio::time::timeout; +#[cfg(target_os = "windows")] use zeromq::{Socket, SocketRecv, SubSocket}; pub struct CoreZMQListener { @@ -312,11 +314,10 @@ impl CoreZMQListener { .expect("Failed to subscribe to rawchainlock"); println!("Subscribed to ZMQ at {}", endpoint); - while !should_stop_clone.load(Ordering::SeqCst) { - // Receive messages - match socket.recv().await { - Ok(msg) => { + match timeout(Duration::from_secs(30), socket.recv()).await { + Ok(Ok(msg)) => { + // Process the message // Access frames using msg.get(n) if let Some(topic_frame) = msg.get(0) { let topic = String::from_utf8_lossy(topic_frame).to_string(); @@ -330,6 +331,11 @@ impl CoreZMQListener { let mut cursor = Cursor::new(data_bytes); match Block::consensus_decode(&mut cursor) { Ok(block) => { + if let Some(ref tx) = tx_zmq_status { + // ZMQ refresh socket connected status + tx.send(ZMQConnectionEvent::Connected) + .expect("Failed to send connected event"); + } if let Err(e) = sender_clone.send(( ZMQMessage::ChainLockedBlock(block), network, @@ -356,6 +362,11 @@ impl CoreZMQListener { match InstantLock::consensus_decode(&mut cursor) { Ok(islock) => { + if let Some(ref tx) = tx_zmq_status { + // ZMQ refresh socket connected status + tx.send(ZMQConnectionEvent::Connected) + .expect("Failed to send connected event"); + } if let Err(e) = sender_clone.send(( ZMQMessage::ISLockedTransaction( tx, islock, @@ -390,12 +401,20 @@ impl CoreZMQListener { } } } - } - Err(e) => { + }, + Ok(Err(e)) => { + // Handle recv error eprintln!("Error receiving message: {}", e); // Sleep briefly before retrying tokio::time::sleep(Duration::from_millis(100)).await; - } + }, + Err(_) => { + // Timeout occurred, handle disconnection + if let Some(ref tx) = tx_zmq_status { + tx.send(ZMQConnectionEvent::Disconnected) + .expect("Failed to send connected event"); + } + }, } } diff --git a/src/ui/components/top_panel.rs b/src/ui/components/top_panel.rs index dbc7c6f3..a159e6cc 100644 --- a/src/ui/components/top_panel.rs +++ b/src/ui/components/top_panel.rs @@ -145,10 +145,7 @@ pub fn add_top_panel( .exact_height(50.0) .show(ctx, |ui| { egui::menu::bar(ui, |ui| { - #[cfg(not(target_os = "windows"))] - { - action |= add_connection_indicator(ui, app_context); - } + action |= add_connection_indicator(ui, app_context); // Left-aligned content with location view action |= add_location_view(ui, location);