From 2dc62a58ce3fd94ef2d6c493f13e46eadaef0840 Mon Sep 17 00:00:00 2001 From: Pasta Lil Claw Date: Mon, 16 Feb 2026 19:55:23 -0600 Subject: [PATCH 1/3] fix: compute relative timestamps from actual data (#581) * fix: compute relative timestamps from actual data Replace hardcoded display strings like "Received: 1 day ago" with real relative timestamps computed from document createdAt/updatedAt fields using chrono::Utc::now(). Fixes dashpay#579 * style: fix import ordering per cargo fmt * refactor: extract format_relative_time to shared dashpay module Deduplicate the identical format_relative_time function that existed in both contact_requests.rs and send_payment.rs. Move it to the dashpay mod.rs as a pub(crate) function and import from both files. --------- Co-authored-by: PastaClaw --- src/ui/dashpay/contact_requests.rs | 12 ++++++++++-- src/ui/dashpay/mod.rs | 28 ++++++++++++++++++++++++++++ src/ui/dashpay/send_payment.rs | 17 ++++++++++++----- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/ui/dashpay/contact_requests.rs b/src/ui/dashpay/contact_requests.rs index 5c9358917..b62dcda60 100644 --- a/src/ui/dashpay/contact_requests.rs +++ b/src/ui/dashpay/contact_requests.rs @@ -21,6 +21,8 @@ use egui::{Frame, Margin, RichText, ScrollArea, Ui}; use std::collections::{BTreeMap, HashSet}; use std::sync::{Arc, RwLock}; +use super::format_relative_time; + #[derive(Debug, Clone)] pub struct ContactRequest { pub request_id: Identifier, @@ -593,8 +595,11 @@ impl ContactRequests { } // Timestamp + let time_text = format_relative_time(request.timestamp) + .map(|t| format!("Received: {}", t)) + .unwrap_or_else(|| "Received: unknown".to_string()); ui.label( - RichText::new("Received: 1 day ago").small().color(DashColors::text_secondary(dark_mode)), + RichText::new(time_text).small().color(DashColors::text_secondary(dark_mode)), ); }); @@ -772,7 +777,10 @@ impl ContactRequests { // Status ui.label(RichText::new("Status: Pending").small().color(DashColors::text_secondary(dark_mode))); - ui.label(RichText::new("Sent: 2 days ago").small().color(DashColors::text_secondary(dark_mode))); + let sent_time_text = format_relative_time(request.timestamp) + .map(|t| format!("Sent: {}", t)) + .unwrap_or_else(|| "Sent: unknown".to_string()); + ui.label(RichText::new(sent_time_text).small().color(DashColors::text_secondary(dark_mode))); }); ui.with_layout( diff --git a/src/ui/dashpay/mod.rs b/src/ui/dashpay/mod.rs index 63ac34847..fa094ef62 100644 --- a/src/ui/dashpay/mod.rs +++ b/src/ui/dashpay/mod.rs @@ -18,6 +18,34 @@ pub use profile_search::ProfileSearchScreen; use crate::app::AppAction; use crate::context::AppContext; use crate::ui::ScreenType; +use chrono::{LocalResult, TimeZone, Utc}; +use chrono_humanize::HumanTime; + +/// Format a Unix timestamp (seconds or milliseconds) as a human-readable +/// relative time string (e.g. "3 hours ago", "just now"). +pub(crate) fn format_relative_time(timestamp: u64) -> Option { + if timestamp == 0 { + return None; + } + // Distinguish seconds vs milliseconds: timestamps after year ~2001 in millis + // exceed 1_000_000_000_000, while second-based timestamps won't until year 33658. + let dt_result = if timestamp > 1_000_000_000_000 { + Utc.timestamp_millis_opt(timestamp as i64) + } else { + Utc.timestamp_opt(timestamp as i64, 0) + }; + match dt_result { + LocalResult::Single(dt) => { + let human = HumanTime::from(dt).to_string(); + if human.contains("seconds") { + Some("just now".to_string()) + } else { + Some(human) + } + } + _ => None, + } +} use egui::{Frame, Margin, RichText, Ui}; use std::sync::Arc; diff --git a/src/ui/dashpay/send_payment.rs b/src/ui/dashpay/send_payment.rs index 620dea493..ceba08405 100644 --- a/src/ui/dashpay/send_payment.rs +++ b/src/ui/dashpay/send_payment.rs @@ -26,6 +26,8 @@ use dash_sdk::platform::Identifier; use egui::{Frame, Margin, RichText, ScrollArea, TextEdit, Ui}; use std::sync::{Arc, RwLock}; +use super::format_relative_time; + const PAYMENT_GUIDELINES_INFO_TEXT: &str = "Payment Guidelines:\n\n\ Payments to contacts use encrypted payment channels.\n\n\ Only you and the recipient can see payment details.\n\n\ @@ -767,11 +769,16 @@ impl PaymentHistory { ); // Timestamp - ui.label( - RichText::new("• 2 days ago") - .small() - .color(DashColors::text_secondary(dark_mode)), - ); + let payment_time_text = format_relative_time(payment.timestamp) + .map(|t| format!("• {}", t)) + .unwrap_or_default(); + if !payment_time_text.is_empty() { + ui.label( + RichText::new(payment_time_text) + .small() + .color(DashColors::text_secondary(dark_mode)), + ); + } }); }); }); From 55398f09854b484b96844e6185355b5d83ebacd4 Mon Sep 17 00:00:00 2001 From: lklimek <842586+lklimek@users.noreply.github.com> Date: Tue, 17 Feb 2026 02:57:40 +0100 Subject: [PATCH 2/3] fix: update platform for DIP-18 HRP and improve SPV sync progress (#575) * fix: update dashpay/platform to d6f4eb9 for DIP-18 HRP fix The previous platform revision used incorrect bech32m HRP prefixes (evo/tevo) for Platform addresses. The updated commit uses the correct DIP-0018 prefixes (dash/tdash). Co-Authored-By: Claude Opus 4.6 * refactor(spv): use dash-spv SyncProgress API directly Replace the intermediate SyncProgress/DetailedSyncProgress/SyncStage translation layer with direct use of dash_spv::sync::SyncProgress. This eliminates ~120 lines of bridge code in spawn_progress_watcher() and determine_sync_stage(), and lets the UI query per-manager progress (headers, filter_headers, filters, masternodes, blocks) via the upstream API. Co-Authored-By: Claude Opus 4.6 * fix(spv): improve sync progress accuracy and robustness - Log headers() error before discarding for easier debugging - Restore download-window optimization for headers progress on checkpoint-resumed syncs (progress starts near 0% not 83%) - Replace catch-all _ => 0.0 with explicit SyncState variant matches so new variants produce compile errors - Fix filters progress to use current_height/target_height instead of downloaded/target_height (session count vs absolute height mismatch) - Restore peer count in sync status text - Add "Querying peer heights" label and diffs_processed to masternode status for more informative sync messages Co-Authored-By: Claude Opus 4.6 * fix(spv): use height-based blocks progress to prevent bar from jumping The blocks progress bar was bouncing backward because it used processed/requested session counters whose denominator grows as filters discover more matching blocks. Switch to last_processed block height relative to headers target_height, which only increases. Display "current / target" heights instead of percentage on the blocks bar. Co-Authored-By: Claude Opus 4.6 * fix(spv): improve progress bar resilience across resume and network switch - Add windowed progress tracking for filters (consistent with headers/filter_headers) - Reset blocks_stage_start on Error state so recovery gets a fresh window - Track spv_progress_network to detect network changes and rebuild progress state from the new network's sync_progress instead of resetting to zero - Eliminate redundant clone in progress watcher (move instead of clone twice) - Consolidate all progress state reset logic into rebuild_spv_progress_state() Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- Cargo.lock | 293 +++++++++-------- Cargo.toml | 13 +- src/backend_task/core/mod.rs | 9 +- src/spv/manager.rs | 153 +-------- src/ui/network_chooser_screen.rs | 537 ++++++++++++++++++------------- 5 files changed, 490 insertions(+), 515 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12226b7f7..dfe01d188 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,7 +581,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -642,7 +642,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -732,9 +732,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" dependencies = [ "cc", "cmake", @@ -855,7 +855,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.114", + "syn 2.0.115", "which 4.4.2", ] @@ -1127,7 +1127,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1213,9 +1213,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -1364,9 +1364,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" dependencies = [ "clap_builder", "clap_derive", @@ -1374,9 +1374,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" dependencies = [ "anstream", "anstyle", @@ -1393,14 +1393,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "clipboard-win" @@ -1711,13 +1711,13 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] name = "dapi-grpc" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "dash-platform-macros", "futures-core", @@ -1768,7 +1768,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1779,13 +1779,13 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] name = "dash-context-provider" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "dpp", "drive", @@ -1862,7 +1862,7 @@ dependencies = [ [[package]] name = "dash-network" version = "0.42.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=12ba186fd24a85fcb4736f0868b393f8a5b58c46#12ba186fd24a85fcb4736f0868b393f8a5b58c46" +source = "git+https://www.github.com/dashpay/rust-dashcore?branch=v0.42-dev#ab4aef885e7ddf8c7fc40aaabf7d29d6eeb7c2e1" dependencies = [ "bincode 2.0.1", "bincode_derive", @@ -1872,18 +1872,18 @@ dependencies = [ [[package]] name = "dash-platform-macros" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "heck", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] name = "dash-sdk" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "arc-swap", "async-trait", @@ -1917,7 +1917,7 @@ dependencies = [ [[package]] name = "dash-spv" version = "0.42.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=12ba186fd24a85fcb4736f0868b393f8a5b58c46#12ba186fd24a85fcb4736f0868b393f8a5b58c46" +source = "git+https://www.github.com/dashpay/rust-dashcore?branch=v0.42-dev#ab4aef885e7ddf8c7fc40aaabf7d29d6eeb7c2e1" dependencies = [ "anyhow", "async-trait", @@ -1950,7 +1950,7 @@ dependencies = [ [[package]] name = "dashcore" version = "0.42.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=12ba186fd24a85fcb4736f0868b393f8a5b58c46#12ba186fd24a85fcb4736f0868b393f8a5b58c46" +source = "git+https://www.github.com/dashpay/rust-dashcore?branch=v0.42-dev#ab4aef885e7ddf8c7fc40aaabf7d29d6eeb7c2e1" dependencies = [ "anyhow", "base64-compat", @@ -1976,12 +1976,12 @@ dependencies = [ [[package]] name = "dashcore-private" version = "0.42.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=12ba186fd24a85fcb4736f0868b393f8a5b58c46#12ba186fd24a85fcb4736f0868b393f8a5b58c46" +source = "git+https://www.github.com/dashpay/rust-dashcore?branch=v0.42-dev#ab4aef885e7ddf8c7fc40aaabf7d29d6eeb7c2e1" [[package]] name = "dashcore-rpc" version = "0.42.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=12ba186fd24a85fcb4736f0868b393f8a5b58c46#12ba186fd24a85fcb4736f0868b393f8a5b58c46" +source = "git+https://www.github.com/dashpay/rust-dashcore?branch=v0.42-dev#ab4aef885e7ddf8c7fc40aaabf7d29d6eeb7c2e1" dependencies = [ "dashcore-rpc-json", "hex", @@ -1994,7 +1994,7 @@ dependencies = [ [[package]] name = "dashcore-rpc-json" version = "0.42.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=12ba186fd24a85fcb4736f0868b393f8a5b58c46#12ba186fd24a85fcb4736f0868b393f8a5b58c46" +source = "git+https://www.github.com/dashpay/rust-dashcore?branch=v0.42-dev#ab4aef885e7ddf8c7fc40aaabf7d29d6eeb7c2e1" dependencies = [ "bincode 2.0.1", "dashcore", @@ -2009,7 +2009,7 @@ dependencies = [ [[package]] name = "dashcore_hashes" version = "0.42.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=12ba186fd24a85fcb4736f0868b393f8a5b58c46#12ba186fd24a85fcb4736f0868b393f8a5b58c46" +source = "git+https://www.github.com/dashpay/rust-dashcore?branch=v0.42-dev#ab4aef885e7ddf8c7fc40aaabf7d29d6eeb7c2e1" dependencies = [ "bincode 2.0.1", "dashcore-private", @@ -2033,8 +2033,8 @@ dependencies = [ [[package]] name = "dashpay-contract" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "platform-value", "platform-version", @@ -2044,8 +2044,8 @@ dependencies = [ [[package]] name = "data-contracts" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "dashpay-contract", "dpns-contract", @@ -2085,9 +2085,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" dependencies = [ "powerfmt", "serde_core", @@ -2122,7 +2122,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -2132,7 +2132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -2161,7 +2161,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "unicode-xid", ] @@ -2175,7 +2175,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -2256,7 +2256,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -2297,8 +2297,8 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" [[package]] name = "dpns-contract" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "platform-value", "platform-version", @@ -2308,8 +2308,8 @@ dependencies = [ [[package]] name = "dpp" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "anyhow", "async-trait", @@ -2356,8 +2356,8 @@ dependencies = [ [[package]] name = "drive" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "bincode 2.0.1", "byteorder", @@ -2381,8 +2381,8 @@ dependencies = [ [[package]] name = "drive-proof-verifier" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "bincode 2.0.1", "dapi-grpc", @@ -2707,7 +2707,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -2727,7 +2727,7 @@ checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -2747,7 +2747,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -2768,7 +2768,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -2779,14 +2779,14 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" dependencies = [ "log", "regex", @@ -2800,9 +2800,9 @@ checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ "anstream", "anstyle", @@ -2949,7 +2949,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -2963,8 +2963,8 @@ dependencies = [ [[package]] name = "feature-flags-contract" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "platform-value", "platform-version", @@ -3086,7 +3086,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3197,7 +3197,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4288,9 +4288,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" +checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" dependencies = [ "jiff-static", "log", @@ -4301,13 +4301,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" +checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4385,7 +4385,7 @@ dependencies = [ [[package]] name = "key-wallet" version = "0.42.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=12ba186fd24a85fcb4736f0868b393f8a5b58c46#12ba186fd24a85fcb4736f0868b393f8a5b58c46" +source = "git+https://www.github.com/dashpay/rust-dashcore?branch=v0.42-dev#ab4aef885e7ddf8c7fc40aaabf7d29d6eeb7c2e1" dependencies = [ "async-trait", "base58ck", @@ -4412,7 +4412,7 @@ dependencies = [ [[package]] name = "key-wallet-manager" version = "0.42.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=12ba186fd24a85fcb4736f0868b393f8a5b58c46#12ba186fd24a85fcb4736f0868b393f8a5b58c46" +source = "git+https://www.github.com/dashpay/rust-dashcore?branch=v0.42-dev#ab4aef885e7ddf8c7fc40aaabf7d29d6eeb7c2e1" dependencies = [ "async-trait", "bincode 2.0.1", @@ -4427,8 +4427,8 @@ dependencies = [ [[package]] name = "keyword-search-contract" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "platform-value", "platform-version", @@ -4538,7 +4538,7 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.7.0", + "redox_syscall 0.7.1", ] [[package]] @@ -4619,8 +4619,8 @@ dependencies = [ [[package]] name = "masternode-reward-shares-contract" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "platform-value", "platform-version", @@ -4838,9 +4838,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "6cdede44f9a69cab2899a2049e2c3bd49bf911a157f6a3353d4a91c61abbce44" dependencies = [ "libc", "log", @@ -4980,7 +4980,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -5075,7 +5075,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -5404,7 +5404,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -5602,7 +5602,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "unicase", ] @@ -5639,7 +5639,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -5683,8 +5683,8 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "platform-serialization" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "bincode 2.0.1", "platform-version", @@ -5692,19 +5692,19 @@ dependencies = [ [[package]] name = "platform-serialization-derive" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "virtue 0.0.17", ] [[package]] name = "platform-value" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "base64 0.22.1", "bincode 2.0.1", @@ -5723,24 +5723,23 @@ dependencies = [ [[package]] name = "platform-version" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "bincode 2.0.1", "grovedb-version", - "once_cell", "thiserror 2.0.18", "versioned-feature-core 1.0.0 (git+https://github.com/dashpay/versioned-feature-core)", ] [[package]] name = "platform-versioning" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -5853,7 +5852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -5917,7 +5916,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.114", + "syn 2.0.115", "tempfile", ] @@ -5931,7 +5930,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -6203,9 +6202,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" dependencies = [ "bitflags 2.10.0", ] @@ -6444,8 +6443,8 @@ dependencies = [ [[package]] name = "rs-dapi-client" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "backon", "chrono", @@ -6524,7 +6523,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.114", + "syn 2.0.115", "walkdir", ] @@ -6858,7 +6857,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -6883,7 +6882,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -6932,7 +6931,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -7250,7 +7249,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -7291,9 +7290,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -7317,7 +7316,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -7467,7 +7466,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -7478,7 +7477,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -7597,8 +7596,8 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "token-history-contract" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "platform-value", "platform-version", @@ -7631,7 +7630,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -7757,9 +7756,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.7+spec-1.1.0" +version = "1.0.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" dependencies = [ "winnow 0.7.14", ] @@ -7804,7 +7803,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -7829,7 +7828,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.114", + "syn 2.0.115", "tempfile", "tonic-build", ] @@ -7940,7 +7939,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -8345,8 +8344,8 @@ dependencies = [ [[package]] name = "wallet-utils-contract" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "platform-value", "platform-version", @@ -8433,7 +8432,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "wasm-bindgen-shared", ] @@ -9024,7 +9023,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -9035,7 +9034,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -9046,7 +9045,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -9057,7 +9056,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -9611,7 +9610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -9692,7 +9691,7 @@ dependencies = [ "heck", "indexmap 2.13.0", "prettyplease", - "syn 2.0.114", + "syn 2.0.115", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -9708,7 +9707,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -9752,8 +9751,8 @@ dependencies = [ [[package]] name = "withdrawals-contract" -version = "3.0.0" -source = "git+https://github.com/dashpay/platform?rev=060515987a#060515987a9d54bb3046bc8deca821f449649856" +version = "3.0.1" +source = "git+https://github.com/dashpay/platform?rev=d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7#d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7" dependencies = [ "num_enum 0.5.11", "platform-value", @@ -9867,7 +9866,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "synstructure", ] @@ -9924,7 +9923,7 @@ checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "zbus-lockstep", "zbus_xml", "zvariant", @@ -9939,7 +9938,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "zbus_names", "zvariant", "zvariant_utils", @@ -9985,7 +9984,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -10005,7 +10004,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "synstructure", ] @@ -10027,7 +10026,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -10097,7 +10096,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -10122,9 +10121,9 @@ checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" [[package]] name = "zmij" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zmq" @@ -10214,7 +10213,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "zvariant_utils", ] @@ -10227,7 +10226,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.114", + "syn 2.0.115", "winnow 0.7.14", ] diff --git a/Cargo.toml b/Cargo.toml index 983e8fd80..89326e918 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ qrcode = "0.14.1" nix = { version = "0.31.1", features = ["signal"] } eframe = { version = "0.33.3", features = ["persistence"] } base64 = "0.22.1" -dash-sdk = { git = "https://github.com/dashpay/platform", rev = "060515987a", features = [ +dash-sdk = { git = "https://github.com/dashpay/platform", rev = "d6f4eb9ac9feafaa914f06e1b78eb66beceef3b7", features = [ "core_key_wallet", "core_key_wallet_manager", "core_bincode", @@ -90,5 +90,16 @@ egui_kittest = { version = "0.33.3", features = ["eframe"] } [build-dependencies] winres = "0.1" +[patch."https://github.com/dashpay/rust-dashcore"] +dash-network = { git = "https://www.github.com/dashpay/rust-dashcore", branch = "v0.42-dev" } +dash-spv = { git = "https://www.github.com/dashpay/rust-dashcore", branch = "v0.42-dev" } +dashcore = { git = "https://www.github.com/dashpay/rust-dashcore", branch = "v0.42-dev" } +dashcore-private = { git = "https://www.github.com/dashpay/rust-dashcore", branch = "v0.42-dev" } +dashcore-rpc = { git = "https://www.github.com/dashpay/rust-dashcore", branch = "v0.42-dev" } +dashcore-rpc-json = { git = "https://www.github.com/dashpay/rust-dashcore", branch = "v0.42-dev" } +dashcore_hashes = { git = "https://www.github.com/dashpay/rust-dashcore", branch = "v0.42-dev" } +key-wallet = { git = "https://www.github.com/dashpay/rust-dashcore", branch = "v0.42-dev" } +key-wallet-manager = { git = "https://www.github.com/dashpay/rust-dashcore", branch = "v0.42-dev" } + [lints.clippy] uninlined_format_args = "allow" diff --git a/src/backend_task/core/mod.rs b/src/backend_task/core/mod.rs index 543a1614a..398d0a719 100644 --- a/src/backend_task/core/mod.rs +++ b/src/backend_task/core/mod.rs @@ -489,7 +489,14 @@ impl AppContext { .spv_manager() .status() .sync_progress - .map(|p| p.header_height) + .and_then(|p| { + p.headers() + .inspect_err(|e| { + tracing::debug!("SPV headers progress unavailable: {e}"); + }) + .ok() + .map(|h| h.current_height()) + }) .ok_or("Cannot build transaction: SPV sync height is not yet known")?; let total_amount: u64 = recipients.iter().map(|(_, amt)| *amt).sum(); let mut scale_factor = 1.0f64; diff --git a/src/spv/manager.rs b/src/spv/manager.rs index da4ae699e..09b153881 100644 --- a/src/spv/manager.rs +++ b/src/spv/manager.rs @@ -8,9 +8,8 @@ use dash_sdk::dash_spv::network::NetworkEvent; use dash_sdk::dash_spv::network::PeerNetworkManager; use dash_sdk::dash_spv::storage::DiskStorageManager; use dash_sdk::dash_spv::sync::SyncEvent; -use dash_sdk::dash_spv::sync::SyncProgress as WatchSyncProgress; -use dash_sdk::dash_spv::sync::SyncState; -use dash_sdk::dash_spv::types::{DetailedSyncProgress, SyncProgress, SyncStage, ValidationMode}; +use dash_sdk::dash_spv::sync::SyncProgress as SpvSyncProgress; +use dash_sdk::dash_spv::types::ValidationMode; use dash_sdk::dash_spv::{ClientConfig, DashSpvClient, Hash, LLMQType, QuorumHash}; use dash_sdk::dpp::dashcore::{Address, InstantLock, Network, Transaction, Txid}; use dash_sdk::dpp::key_wallet::bip32::{DerivationPath, ExtendedPrivKey}; @@ -115,8 +114,7 @@ impl From for SpvStatus { #[derive(Debug, Clone, Default)] pub struct SpvStatusSnapshot { pub status: SpvStatus, - pub sync_progress: Option, - pub detailed_progress: Option, + pub sync_progress: Option, pub last_error: Option, pub started_at: Option, pub last_updated: Option, @@ -156,8 +154,7 @@ pub struct SpvManager { status: Arc>, last_error: Arc>>, started_at: Arc>>, - sync_progress_state: Arc>>, - detailed_progress_state: Arc>>, + sync_progress_state: Arc>>, progress_updated_at: Arc>>, // mapping DET wallet seed_hash -> SPV wallet identifier (if created) det_wallets: Arc>>, @@ -247,14 +244,14 @@ impl SpvManager { Ok(()) } - fn read_sync_progress(&self) -> SpvResult> { + fn read_sync_progress(&self) -> SpvResult> { self.sync_progress_state .read() .map(|g| g.clone()) .map_err(|_| SpvError::LockPoisoned("sync_progress".into())) } - fn write_sync_progress(&self, value: Option) -> SpvResult<()> { + fn write_sync_progress(&self, value: Option) -> SpvResult<()> { let mut guard = self .sync_progress_state .write() @@ -263,22 +260,6 @@ impl SpvManager { Ok(()) } - fn read_detailed_progress(&self) -> SpvResult> { - self.detailed_progress_state - .read() - .map(|g| g.clone()) - .map_err(|_| SpvError::LockPoisoned("detailed_progress".into())) - } - - fn write_detailed_progress(&self, value: Option) -> SpvResult<()> { - let mut guard = self - .detailed_progress_state - .write() - .map_err(|_| SpvError::LockPoisoned("detailed_progress".into()))?; - *guard = value; - Ok(()) - } - fn read_progress_updated_at(&self) -> SpvResult> { self.progress_updated_at .read() @@ -321,7 +302,6 @@ impl SpvManager { last_error: Arc::new(RwLock::new(None)), started_at: Arc::new(RwLock::new(None)), sync_progress_state: Arc::new(RwLock::new(None)), - detailed_progress_state: Arc::new(RwLock::new(None)), progress_updated_at: Arc::new(RwLock::new(None)), det_wallets: Arc::new(RwLock::new(std::collections::BTreeMap::new())), reconcile_tx: Mutex::new(None), @@ -354,7 +334,6 @@ impl SpvManager { let last_error = self.read_last_error().unwrap_or(None); let started_at = self.read_started_at().unwrap_or(None); let sync_progress = self.read_sync_progress().unwrap_or(None); - let detailed_progress = self.read_detailed_progress().unwrap_or(None); let last_updated = self .read_progress_updated_at() .unwrap_or(None) @@ -364,7 +343,6 @@ impl SpvManager { SpvStatusSnapshot { status, sync_progress, - detailed_progress, last_error, started_at, last_updated, @@ -379,7 +357,6 @@ impl SpvManager { let last_error = self.read_last_error().unwrap_or(None); let started_at = self.read_started_at().unwrap_or(None); let sync_progress = self.read_sync_progress().unwrap_or(None); - let detailed_progress = self.read_detailed_progress().unwrap_or(None); let last_updated = self .read_progress_updated_at() .unwrap_or(None) @@ -389,7 +366,6 @@ impl SpvManager { SpvStatusSnapshot { status, sync_progress, - detailed_progress, last_error, started_at, last_updated, @@ -415,8 +391,6 @@ impl SpvManager { self.write_started_at(Some(SystemTime::now())) .map_err(|e| e.to_string())?; self.write_sync_progress(None).map_err(|e| e.to_string())?; - self.write_detailed_progress(None) - .map_err(|e| e.to_string())?; self.write_progress_updated_at(None) .map_err(|e| e.to_string())?; @@ -606,8 +580,6 @@ impl SpvManager { } self.write_sync_progress(None).map_err(|e| e.to_string())?; - self.write_detailed_progress(None) - .map_err(|e| e.to_string())?; self.write_progress_updated_at(None) .map_err(|e| e.to_string())?; self.write_started_at(None).map_err(|e| e.to_string())?; @@ -1062,11 +1034,10 @@ impl SpvManager { fn spawn_progress_watcher( &self, - mut progress_rx: tokio::sync::watch::Receiver, + mut progress_rx: tokio::sync::watch::Receiver, ) { let status = Arc::clone(&self.status); let sync_progress_state = Arc::clone(&self.sync_progress_state); - let detailed_progress_state = Arc::clone(&self.detailed_progress_state); let progress_updated_at = Arc::clone(&self.progress_updated_at); let cancel = self.subtasks.cancellation_token.clone(); @@ -1078,59 +1049,12 @@ impl SpvManager { if result.is_err() { break; // Channel closed } - let watch_progress = progress_rx.borrow(); - - // Extract all available heights from WatchSyncProgress - let header_height = watch_progress - .headers() - .map(|h| h.current_height()) - .unwrap_or(0); - let masternode_height = watch_progress - .masternodes() - .map(|m| m.current_height()) - .unwrap_or(0); - let filter_header_height = watch_progress - .filter_headers() - .map(|fh| fh.current_height()) - .unwrap_or(0); - - let sync_progress = SyncProgress { - header_height, - masternode_height, - filter_header_height, - ..Default::default() - }; - - // Build detailed progress with sync stage information - let peer_best_height = watch_progress - .headers() - .map(|h| h.target_height()) - .unwrap_or(0); - let sync_stage = Self::determine_sync_stage(&watch_progress); - let detailed = DetailedSyncProgress { - sync_progress: sync_progress.clone(), - peer_best_height, - percentage: if peer_best_height > 0 { - (header_height as f64 / peer_best_height as f64 * 100.0).min(100.0) - } else { - 0.0 - }, - headers_per_second: 0.0, - bytes_per_second: 0, - estimated_time_remaining: None, - sync_stage, - total_headers_processed: 0, - total_bytes_downloaded: 0, - sync_start_time: SystemTime::now(), - last_update_time: SystemTime::now(), - }; + let watch_progress = progress_rx.borrow().clone(); + let is_synced = watch_progress.is_synced(); // Update sync progress state if let Ok(mut stored_sync) = sync_progress_state.write() { - *stored_sync = Some(sync_progress); - } - if let Ok(mut stored_detailed) = detailed_progress_state.write() { - *stored_detailed = Some(detailed); + *stored_sync = Some(watch_progress); } if let Ok(mut updated_at) = progress_updated_at.write() { *updated_at = Some(SystemTime::now()); @@ -1138,7 +1062,7 @@ impl SpvManager { // Update status based on progress if let Ok(mut status_guard) = status.write() { - if watch_progress.is_synced() { + if is_synced { *status_guard = SpvStatus::Running; } else if !matches!(*status_guard, SpvStatus::Stopping | SpvStatus::Stopped | SpvStatus::Error) { *status_guard = SpvStatus::Syncing; @@ -1151,61 +1075,6 @@ impl SpvManager { }); } - /// Map the parallel WatchSyncProgress managers into a single UI-facing SyncStage. - /// - /// The parallel sync system has independent managers for headers, masternodes, - /// filter-headers, filters, and blocks. We map to a single stage by checking - /// which manager is actively syncing, preferring later pipeline stages. - fn determine_sync_stage(watch: &WatchSyncProgress) -> SyncStage { - // Check stages from latest to earliest in the pipeline - if let Ok(blocks) = watch.blocks() - && blocks.state() == SyncState::Syncing - { - return SyncStage::DownloadingBlocks { - pending: blocks.requested().saturating_sub(blocks.processed()) as usize, - }; - } - if let Ok(filters) = watch.filters() - && filters.state() == SyncState::Syncing - { - return SyncStage::DownloadingFilters { - completed: filters.downloaded(), - total: filters - .target_height() - .saturating_sub(filters.current_height()), - }; - } - if let Ok(fh) = watch.filter_headers() - && fh.state() == SyncState::Syncing - { - return SyncStage::DownloadingFilterHeaders { - current: fh.current_height(), - target: fh.target_height(), - }; - } - if let Ok(mn) = watch.masternodes() - && mn.state() == SyncState::Syncing - { - return SyncStage::ValidatingHeaders { - batch_size: mn.diffs_processed() as usize, - }; - } - if let Ok(headers) = watch.headers() - && headers.state() == SyncState::Syncing - { - return SyncStage::DownloadingHeaders { - start: 0, - end: headers.target_height(), - }; - } - - if watch.is_synced() { - SyncStage::Complete - } else { - SyncStage::Connecting - } - } - fn spawn_sync_event_handler(&self, mut sync_rx: tokio::sync::broadcast::Receiver) { let reconcile_tx = self.reconcile_tx.lock().ok().and_then(|g| g.clone()); let finality_tx = self.finality_tx.lock().ok().and_then(|g| g.clone()); diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 81bc2e695..da3e07fd7 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -16,7 +16,7 @@ use crate::ui::components::top_panel::add_top_panel; use crate::ui::theme::{DashColors, Shape, ThemeMode}; use crate::ui::{RootScreenType, ScreenLike}; use crate::utils::path::format_path_for_display; -use dash_sdk::dash_spv::types::{DetailedSyncProgress, SyncStage}; +use dash_sdk::dash_spv::sync::{SyncProgress as SpvSyncProgress, SyncState}; use dash_sdk::dpp::dashcore::Network; use dash_sdk::dpp::identity::TimestampMillis; use eframe::egui::{self, Color32, Context, Frame, Margin, RichText, Ui}; @@ -53,7 +53,12 @@ pub struct NetworkChooserScreen { theme_preference: ThemeMode, should_reset_collapsing_states: bool, backend_modes: HashMap, + spv_progress_network: Option, + headers_stage_start: Option, filter_headers_stage_start: Option, + filters_stage_start: Option, + blocks_stage_start: Option, + blocks_target_height: u32, spv_clear_dialog: Option, spv_clear_message: Option, db_clear_dialog: Option, @@ -147,7 +152,12 @@ impl NetworkChooserScreen { theme_preference, should_reset_collapsing_states: true, // Start with collapsed state backend_modes, + spv_progress_network: None, + headers_stage_start: None, filter_headers_stage_start: None, + filters_stage_start: None, + blocks_stage_start: None, + blocks_target_height: 0, spv_clear_dialog: None, spv_clear_message: None, db_clear_dialog: None, @@ -1307,28 +1317,112 @@ impl NetworkChooserScreen { app_action } + /// Rebuild all SPV progress tracking state from the current snapshot. + /// Called once when the active network changes so that stale values from + /// one network don't leak into another, while preserving already-synced + /// progress from the new network's SPV manager. + fn rebuild_spv_progress_state(&mut self, snapshot: &SpvStatusSnapshot) { + self.headers_stage_start = None; + self.filter_headers_stage_start = None; + self.filters_stage_start = None; + self.blocks_stage_start = None; + self.blocks_target_height = 0; + + // Seed from the new network's sync_progress so bars don't jump to 0. + if let Some(progress) = &snapshot.sync_progress { + if let Ok(headers) = progress.headers() { + self.blocks_target_height = self.blocks_target_height.max(headers.target_height()); + } + if let Ok(blocks) = progress.blocks() { + self.blocks_target_height = self.blocks_target_height.max(blocks.last_processed()); + if blocks.state() == SyncState::Syncing { + self.blocks_stage_start = Some(blocks.last_processed()); + } + } + } + } + fn render_spv_sync_progress(&mut self, ui: &mut Ui, snapshot: &SpvStatusSnapshot) { - if let Some(detailed) = &snapshot.detailed_progress { - match detailed.sync_stage { - SyncStage::DownloadingFilterHeaders { current, target } => { + // Rebuild progress state when the network changes. + if self.spv_progress_network != Some(self.current_network) { + self.rebuild_spv_progress_state(snapshot); + self.spv_progress_network = Some(self.current_network); + } + + if let Some(progress) = &snapshot.sync_progress { + // Track headers download window start for checkpoint-aware progress + if let Ok(headers) = progress.headers() { + if headers.state() == SyncState::Syncing { + let current = headers.current_height(); + let target = headers.target_height(); + let baseline = current.min(target); + if let Some(existing) = self.headers_stage_start { + self.headers_stage_start = Some(existing.min(target)); + } else { + self.headers_stage_start = Some(baseline); + } + } else { + self.headers_stage_start = None; + } + } else { + self.headers_stage_start = None; + } + + // Track filter headers download window start + if let Ok(fh) = progress.filter_headers() { + if fh.state() == SyncState::Syncing { + let current = fh.current_height(); + let target = fh.target_height(); let baseline = current.min(target); if let Some(existing) = self.filter_headers_stage_start { self.filter_headers_stage_start = Some(existing.min(target)); } else { self.filter_headers_stage_start = Some(baseline); } - } - _ => { + } else { self.filter_headers_stage_start = None; } + } else { + self.filter_headers_stage_start = None; + } + + // Track filters download window start + if let Ok(filters) = progress.filters() { + if filters.state() == SyncState::Syncing { + let current = filters.current_height(); + let target = filters.target_height(); + let baseline = current.min(target); + if let Some(existing) = self.filters_stage_start { + self.filters_stage_start = Some(existing.min(target)); + } else { + self.filters_stage_start = Some(baseline); + } + } else { + self.filters_stage_start = None; + } + } else { + self.filters_stage_start = None; + } + + // Capture target height from headers and blocks (only increases). + if let Ok(headers) = progress.headers() { + self.blocks_target_height = self.blocks_target_height.max(headers.target_height()); + } + if let Ok(blocks) = progress.blocks() { + // last_processed is a lower bound for chain height + self.blocks_target_height = self.blocks_target_height.max(blocks.last_processed()); + + if blocks.state() == SyncState::Syncing && self.blocks_stage_start.is_none() { + self.blocks_stage_start = Some(blocks.last_processed()); + } + if matches!(blocks.state(), SyncState::Synced | SyncState::Error) { + self.blocks_stage_start = None; + } } - } else { - self.filter_headers_stage_start = None; } let dark_mode = ui.ctx().style().visuals.dark_mode; - // Raw sync status display egui::Frame::new() .fill(DashColors::glass_white(dark_mode)) .corner_radius(Shape::RADIUS_SM) @@ -1342,12 +1436,10 @@ impl NetworkChooserScreen { ui.add_space(8.0); - // Display sync information in a grid egui::Grid::new("spv_sync_info") .num_columns(2) .spacing([16.0, 4.0]) .show(ui, |ui| { - // Show current status detail if let Some(detail) = self.spv_status_detail(snapshot) { ui.label( egui::RichText::new("Status:") @@ -1357,9 +1449,7 @@ impl NetworkChooserScreen { ui.end_row(); } - // Prefer detailed header progress when available - if snapshot.detailed_progress.is_some() { - // Add separator between status and progress bars + if snapshot.sync_progress.is_some() { ui.separator(); ui.separator(); ui.end_row(); @@ -1373,7 +1463,7 @@ impl NetworkChooserScreen { ui.add(egui::ProgressBar::new(headers_progress).show_percentage()); ui.end_row(); - // Validating headers progress (formerly masternode lists) + // Masternode Lists progress ui.label( egui::RichText::new("Masternode Lists:") .color(DashColors::text_secondary(dark_mode)), @@ -1404,71 +1494,25 @@ impl NetworkChooserScreen { ui.add(egui::ProgressBar::new(filters_progress).show_percentage()); ui.end_row(); - // Blocks progress bar + // Blocks progress ui.label( egui::RichText::new("Blocks:") .color(DashColors::text_secondary(dark_mode)), ); let blocks_progress = self.calculate_blocks_progress(snapshot); - ui.add(egui::ProgressBar::new(blocks_progress).show_percentage()); - ui.end_row(); - } else if let Some(ev) = &snapshot.sync_progress { - // Event-driven progress (updates most frequently) - ui.label( - egui::RichText::new("Synced:") - .color(DashColors::text_secondary(dark_mode)), - ); - ui.label(format!("Headers height: {}", ev.header_height)); - ui.end_row(); - - // Add separator between stats and progress bars - ui.separator(); - ui.separator(); - ui.end_row(); - - // Progress bars for different components - let headers_progress = self.calculate_headers_progress(snapshot); - ui.label( - egui::RichText::new("Headers:") - .color(DashColors::text_secondary(dark_mode)), - ); - ui.add(egui::ProgressBar::new(headers_progress).show_percentage()); - ui.end_row(); - - let validating_progress = - self.calculate_validating_headers_progress(snapshot); - ui.label( - egui::RichText::new("Masternode Lists:") - .color(DashColors::text_secondary(dark_mode)), - ); - ui.add(egui::ProgressBar::new(validating_progress).show_percentage()); - ui.end_row(); - - let filter_headers_progress = - self.calculate_filter_headers_progress(snapshot); - ui.label( - egui::RichText::new("Filter Headers:") - .color(DashColors::text_secondary(dark_mode)), - ); - ui.add( - egui::ProgressBar::new(filter_headers_progress).show_percentage(), - ); - ui.end_row(); - - let filters_progress = self.calculate_filters_progress(snapshot); - ui.label( - egui::RichText::new("Filters:") - .color(DashColors::text_secondary(dark_mode)), - ); - ui.add(egui::ProgressBar::new(filters_progress).show_percentage()); - ui.end_row(); - - let blocks_progress = self.calculate_blocks_progress(snapshot); - ui.label( - egui::RichText::new("Blocks:") - .color(DashColors::text_secondary(dark_mode)), - ); - ui.add(egui::ProgressBar::new(blocks_progress).show_percentage()); + let blocks_text = snapshot + .sync_progress + .as_ref() + .and_then(|p| p.blocks().ok()) + .map(|b| { + format!( + "{} / {}", + b.last_processed(), + self.blocks_target_height + ) + }) + .unwrap_or_default(); + ui.add(egui::ProgressBar::new(blocks_progress).text(blocks_text)); ui.end_row(); } }); @@ -1630,94 +1674,118 @@ impl NetworkChooserScreen { } fn calculate_headers_progress(&self, snapshot: &SpvStatusSnapshot) -> f32 { - if let Some(detailed) = &snapshot.detailed_progress { - match &detailed.sync_stage { - SyncStage::DownloadingHeaders { start, end } => { - // Respect restored checkpoints: show progress relative to the download window. - if end > start { - let window = (end - start) as f32; - let current = detailed.sync_progress.header_height; - let clamped = current.clamp(*start, *end) - start; - (clamped as f32 / window).clamp(0.0, 1.0) + let Some(progress) = &snapshot.sync_progress else { + return 0.0; + }; + let Ok(headers) = progress.headers() else { + return 0.0; + }; + match headers.state() { + SyncState::Syncing => { + let target = headers.target_height(); + if target == 0 { + return 0.0; + } + // Use download window to show progress relative to remaining work, + // so checkpoint-resumed syncs start near 0% rather than jumping ahead. + let start = self + .headers_stage_start + .unwrap_or(headers.current_height()) + .min(target); + let span = target.saturating_sub(start); + if span == 0 { + if headers.current_height() >= target { + 1.0 } else { 0.0 } + } else { + let done = headers.current_height().saturating_sub(start); + (done as f32 / span as f32).clamp(0.0, 1.0) } - SyncStage::ValidatingHeaders { .. } - | SyncStage::StoringHeaders { .. } - | SyncStage::DownloadingFilterHeaders { .. } - | SyncStage::DownloadingFilters { .. } - | SyncStage::DownloadingBlocks { .. } - | SyncStage::Complete => 1.0, - SyncStage::Failed(_) => 0.0, - _ => 0.0, } - } else if let Some(progress) = &snapshot.sync_progress { - if progress.header_height == 0 { - 0.0 - } else { - // Without detailed context fall back to comparing against masternode progress - (progress.masternode_height as f32 / progress.header_height as f32).clamp(0.0, 1.0) - } - } else { - 0.0 + SyncState::Synced => 1.0, + SyncState::Initializing + | SyncState::WaitingForConnections + | SyncState::WaitForEvents + | SyncState::Error => 0.0, } } fn calculate_filter_headers_progress(&self, snapshot: &SpvStatusSnapshot) -> f32 { - if let Some(detailed) = &snapshot.detailed_progress { - if detailed.peer_best_height == 0 { - return 0.0; - } - match &detailed.sync_stage { - SyncStage::DownloadingFilterHeaders { current, target } => { - let current = *current; - let target = *target; - if target == 0 { - return 0.0; - } - - let start = self - .filter_headers_stage_start - .unwrap_or(current) - .min(target); - let span = target.saturating_sub(start); - if span == 0 { - if current >= target { 1.0 } else { 0.0 } + let Some(progress) = &snapshot.sync_progress else { + return 0.0; + }; + let Ok(fh) = progress.filter_headers() else { + return 0.0; + }; + match fh.state() { + SyncState::Syncing => { + let target = fh.target_height(); + if target == 0 { + return 0.0; + } + let start = self + .filter_headers_stage_start + .unwrap_or(fh.current_height()) + .min(target); + let span = target.saturating_sub(start); + if span == 0 { + if fh.current_height() >= target { + 1.0 } else { - let progress = current.saturating_sub(start); - (progress as f32 / span as f32).clamp(0.0, 1.0) + 0.0 } + } else { + let done = fh.current_height().saturating_sub(start); + (done as f32 / span as f32).clamp(0.0, 1.0) } - SyncStage::DownloadingFilters { .. } - | SyncStage::DownloadingBlocks { .. } - | SyncStage::Complete => (detailed.sync_progress.filter_header_height as f32 - / detailed.peer_best_height as f32) - .clamp(0.0, 1.0), - SyncStage::Failed(_) => 0.0, - _ => 0.0, } - } else { - 0.0 + SyncState::Synced => 1.0, + SyncState::Initializing + | SyncState::WaitingForConnections + | SyncState::WaitForEvents + | SyncState::Error => 0.0, } } fn calculate_filters_progress(&self, snapshot: &SpvStatusSnapshot) -> f32 { - if let Some(detailed) = &snapshot.detailed_progress { - match &detailed.sync_stage { - SyncStage::DownloadingFilters { completed, total } => { - if *total == 0 { - 0.0 + let Some(progress) = &snapshot.sync_progress else { + return 0.0; + }; + let Ok(filters) = progress.filters() else { + return 0.0; + }; + match filters.state() { + SyncState::Syncing => { + let target = filters.target_height(); + if target == 0 { + return 0.0; + } + // Use windowed progress so checkpoint-resumed syncs start near 0%. + // current_height is the storage tip (not downloaded() which is a + // session-level count). + let start = self + .filters_stage_start + .unwrap_or(filters.current_height()) + .min(target); + let span = target.saturating_sub(start); + if span == 0 { + if filters.current_height() >= target { + 1.0 } else { - (*completed as f32 / *total as f32).clamp(0.0, 1.0) + 0.0 } + } else { + let done = filters.current_height().saturating_sub(start); + (done as f32 / span as f32).clamp(0.0, 1.0) } - SyncStage::DownloadingBlocks { .. } | SyncStage::Complete => 1.0, - SyncStage::Failed(_) => 0.0, - _ => 0.0, } - } else { - 0.0 + SyncState::Synced => 1.0, + SyncState::Initializing + | SyncState::WaitingForConnections + | SyncState::WaitForEvents + | SyncState::Error => 0.0, } } @@ -1725,33 +1793,25 @@ impl NetworkChooserScreen { if snapshot.status == SpvStatus::Running { return 1.0; } - - if let Some(detailed) = &snapshot.detailed_progress { - match &detailed.sync_stage { - SyncStage::ValidatingHeaders { .. } | SyncStage::StoringHeaders { .. } => { - if detailed.peer_best_height == 0 { - 0.0 - } else { - let best_height = detailed.peer_best_height as f32; - let validated = detailed.sync_progress.masternode_height as f32; - (validated / best_height).clamp(0.0, 1.0) - } + let Some(progress) = &snapshot.sync_progress else { + return 0.0; + }; + let Ok(mn) = progress.masternodes() else { + return 0.0; + }; + match mn.state() { + SyncState::Syncing => { + let target = mn.target_height(); + if target == 0 { + return 0.0; } - SyncStage::DownloadingFilterHeaders { .. } - | SyncStage::DownloadingFilters { .. } - | SyncStage::DownloadingBlocks { .. } - | SyncStage::Complete => 1.0, - SyncStage::Failed(_) => 0.0, - _ => 0.0, - } - } else if let Some(progress) = &snapshot.sync_progress { - if progress.header_height == 0 { - 0.0 - } else { - (progress.masternode_height as f32 / progress.header_height as f32).clamp(0.0, 1.0) + (mn.current_height() as f32 / target as f32).clamp(0.0, 1.0) } - } else { - 0.0 + SyncState::Synced => 1.0, + SyncState::Initializing + | SyncState::WaitingForConnections + | SyncState::WaitForEvents + | SyncState::Error => 0.0, } } @@ -1759,26 +1819,30 @@ impl NetworkChooserScreen { if snapshot.status == SpvStatus::Running { return 1.0; } - - if let Some(detailed) = &snapshot.detailed_progress { - match &detailed.sync_stage { - SyncStage::DownloadingBlocks { .. } => { - if detailed.peer_best_height == 0 { - 0.0 - } else { - let processed_height = detailed - .sync_progress - .last_synced_filter_height - .unwrap_or(0); - (processed_height as f32 / detailed.peer_best_height as f32).clamp(0.0, 1.0) - } - } - SyncStage::Complete => 1.0, - SyncStage::Failed(_) => 0.0, - _ => 0.0, - } + let Some(progress) = &snapshot.sync_progress else { + return 0.0; + }; + let Ok(blocks) = progress.blocks() else { + return 0.0; + }; + if blocks.state() == SyncState::Synced { + return 1.0; + } + // Use last_processed height relative to the tracked target height. + // Don't branch on SyncState — blocks can transiently leave Syncing + // (e.g. WaitForEvents between batches) while still making progress. + let target = self.blocks_target_height; + if target == 0 { + return 0.0; + } + let current = blocks.last_processed(); + let start = self.blocks_stage_start.unwrap_or(current).min(target); + let span = target.saturating_sub(start); + if span == 0 { + if current >= target { 1.0 } else { 0.0 } } else { - 0.0 + let done = current.saturating_sub(start); + (done as f32 / span as f32).clamp(0.0, 1.0) } } @@ -1805,53 +1869,78 @@ impl NetworkChooserScreen { return Some(err.clone()); } - if let Some(progress) = snapshot.detailed_progress.as_ref() { - return Some(Self::format_detailed_progress(progress)); + if let Some(progress) = snapshot.sync_progress.as_ref() { + return Some(Self::format_sync_progress( + progress, + snapshot.connected_peers, + )); } snapshot.last_error.clone() } - fn format_detailed_progress(progress: &DetailedSyncProgress) -> String { - let mut message = match &progress.sync_stage { - SyncStage::Connecting => "Connecting to peers".to_string(), - SyncStage::QueryingPeerHeight => "Querying peer heights".to_string(), - SyncStage::DownloadingHeaders { .. } => { - format!( - "Headers: {} / {}", - progress.sync_progress.header_height, progress.peer_best_height, - ) - } - SyncStage::ValidatingHeaders { batch_size } => { - format!( - "Masternode lists (batch {batch_size}) | Height {}", - progress.sync_progress.masternode_height - ) - } - SyncStage::StoringHeaders { batch_size } => { - format!( - "Storing headers (batch {batch_size}) | Height {}", - progress.sync_progress.header_height - ) - } - SyncStage::Complete => "Sync complete".to_string(), - SyncStage::Failed(reason) => format!("Failed: {reason}"), - SyncStage::DownloadingFilterHeaders { current, target } => { - format!("Filter headers: {current} / {target}") - } - SyncStage::DownloadingFilters { completed, total } => { - format!("Filters: {completed} / {total}") - } - SyncStage::DownloadingBlocks { pending } => { - format!("Blocks: {pending}") + fn format_sync_progress(progress: &SpvSyncProgress, connected_peers: usize) -> String { + // Check each manager's state to determine what to display, + // preferring later pipeline stages. + let stage_message = if let Ok(blocks) = progress.blocks() + && blocks.state() == SyncState::Syncing + { + format!( + "Blocks: {} requested, {} processed", + blocks.requested(), + blocks.processed() + ) + } else if let Ok(filters) = progress.filters() + && filters.state() == SyncState::Syncing + { + format!( + "Filters: {} / {}", + filters.current_height(), + filters.target_height() + ) + } else if let Ok(fh) = progress.filter_headers() + && fh.state() == SyncState::Syncing + { + format!( + "Filter headers: {} / {}", + fh.current_height(), + fh.target_height() + ) + } else if let Ok(mn) = progress.masternodes() + && mn.state() == SyncState::Syncing + { + format!( + "Masternode lists: {} diffs | Height {} / {}", + mn.diffs_processed(), + mn.current_height(), + mn.target_height() + ) + } else if let Ok(headers) = progress.headers() + && headers.state() == SyncState::Syncing + { + format!( + "Headers: {} / {}", + headers.current_height(), + headers.target_height() + ) + } else if progress.is_synced() { + "Sync complete".to_string() + } else { + match progress.state() { + SyncState::WaitingForConnections => "Connecting to peers".to_string(), + SyncState::WaitForEvents => "Querying peer heights".to_string(), + SyncState::Error => "Sync error".to_string(), + SyncState::Initializing | SyncState::Syncing | SyncState::Synced => { + "Syncing...".to_string() + } } }; - if progress.sync_progress.peer_count > 0 { - message = format!("{message} | Peers: {}", progress.sync_progress.peer_count); + if connected_peers > 0 { + format!("{stage_message} | Peers: {connected_peers}") + } else { + stage_message } - - message } } From 6cc1565609f9cc5b67302d5d4fcc8605ead674f6 Mon Sep 17 00:00:00 2001 From: lklimek <842586+lklimek@users.noreply.github.com> Date: Tue, 17 Feb 2026 02:59:00 +0100 Subject: [PATCH 3/3] build: add git and cargo permissions to Claude Code workflow (#565) * build: add git and cargo permissions to Claude Code workflow Allow Claude to run git fetch/merge/checkout/rebase/push and cargo build/test/clippy/fmt commands. Switch model to opus. Co-Authored-By: Claude Opus 4.6 * fix: use claude_args for model and allowed tools The `model` and `allowed_tools` inputs are not declared in claude-code-action@v1 and are silently ignored. Move them to `claude_args` with --model and --allowedTools flags. Also fix deprecated colon syntax (`:*`) to space syntax (` *`). Co-Authored-By: Claude Opus 4.6 * fix: use single quotes to prevent glob expansion in allowed tools Co-Authored-By: Claude Opus 4.6 * build: sandbox cargo commands and add git permissions for Claude - Add safe-cargo.sh wrapper that strips CI secrets before running cargo - Use --allowedTools for git and safe-cargo, --disallowedTools for raw cargo - Document safe-cargo usage in CLAUDE.md Co-Authored-By: Claude Opus 4.6 * fix: switch safe-cargo.sh from denylist to allowlist approach Use `env -i` (start with empty environment, explicitly pass only what cargo needs) instead of `env -u` (strip known secrets). This is more robust against future secrets being added to the workflow. Co-Authored-By: Claude Opus 4.6 * build: deny Claude from editing CI scripts and workflows Prevent Claude from modifying .github/scripts/ and .github/workflows/ to ensure the safe-cargo wrapper cannot be tampered with. Co-Authored-By: Claude Opus 4.6 * fix: conditionally pass optional env vars in safe-cargo.sh Empty PROTOC="" caused prost build scripts to fail with "protoc not found". Now optional vars (PROTOC, CC, CXX, etc.) are only passed when set and non-empty. Tested: build, test, fmt all pass through the wrapper. Co-Authored-By: Claude Opus 4.6 * rabbit feedback * build: move safe-cargo.sh to scripts/ and allow +nightly fmt Move safe-cargo.sh from .github/scripts/ to top-level scripts/ for better discoverability. Add detailed comment explaining why the wrapper exists (prevent CI secret exfiltration via build scripts). Update all references in claude.yml, CLAUDE.md, and permission settings. Add `+nightly fmt` to allowedTools so Claude can follow CLAUDE.md formatting instructions in CI. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .claude/settings.json | 8 +++++++ .github/workflows/claude.yml | 4 ++++ CLAUDE.md | 11 +++++++++ scripts/safe-cargo.sh | 44 ++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100755 scripts/safe-cargo.sh diff --git a/.claude/settings.json b/.claude/settings.json index b8d0bd970..524891982 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,4 +1,12 @@ { + "permissions": { + "deny": [ + "Edit(scripts/**)", + "Write(scripts/**)", + "Edit(.github/workflows/**)", + "Write(.github/workflows/**)" + ] + }, "hooks": { "SessionStart": [ { diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 9d5239dd1..b0607826b 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -35,3 +35,7 @@ jobs: uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN_LKLIMEK }} + claude_args: | + --model opus + --allowedTools 'Bash(git fetch *),Bash(git merge *),Bash(git checkout *),Bash(git rebase *),Bash(git push *),Bash(scripts/safe-cargo.sh build *),Bash(scripts/safe-cargo.sh test *),Bash(scripts/safe-cargo.sh clippy *),Bash(scripts/safe-cargo.sh +nightly fmt *),Bash(scripts/safe-cargo.sh fmt *)' + --disallowedTools 'Bash(cargo *)' diff --git a/CLAUDE.md b/CLAUDE.md index a1552d670..4d92446c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,6 +35,17 @@ Test locations: Always run `cargo clippy` and `cargo +nightly fmt` when finalizing your work. +## CI: Safe Cargo Wrapper + +In GitHub Actions (Claude Code workflow), use `scripts/safe-cargo.sh` instead of `cargo` directly. This wrapper strips CI secrets from the environment before running cargo, preventing build scripts from accessing credentials. + +```bash +scripts/safe-cargo.sh build --all-features +scripts/safe-cargo.sh test --all-features --workspace +scripts/safe-cargo.sh clippy --all-features --all-targets -- -D warnings +scripts/safe-cargo.sh +nightly fmt --all +``` + ## Architecture Overview **Dash Evo Tool** is a cross-platform GUI application (Rust + egui) for interacting with Dash Evolution. It enables DPNS username registration, contest voting, state transition viewing, wallet management, and identity operations across Mainnet/Testnet/Devnet. diff --git a/scripts/safe-cargo.sh b/scripts/safe-cargo.sh new file mode 100755 index 000000000..b299eaa3f --- /dev/null +++ b/scripts/safe-cargo.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -euo pipefail +# +# safe-cargo.sh — Run cargo without CI secrets leaking to build scripts. +# +# WHY THIS FILE EXISTS +# -------------------- +# Cargo build scripts (build.rs / proc-macros) execute arbitrary code during +# compilation. In CI the runner environment contains secrets such as +# CLAUDE_CODE_OAUTH_TOKEN and GITHUB_TOKEN. A compromised or malicious +# dependency could read those variables and exfiltrate them. +# +# This wrapper uses `env -i` (an allowlist approach) so that cargo and every +# child process it spawns start with only the variables listed below. +# Any new secret added to CI in the future is automatically excluded without +# having to update a denylist. +# +# USAGE (GitHub Actions) +# scripts/safe-cargo.sh build --all-features +# scripts/safe-cargo.sh test --all-features --workspace +# scripts/safe-cargo.sh clippy --all-features --all-targets -- -D warnings +# scripts/safe-cargo.sh +nightly fmt --all +# + +# Build the environment allowlist. Only pass variables that are set +# to avoid empty values confusing tools (e.g. PROTOC="" breaks prost). +ENV_ARGS=( + HOME="$HOME" + PATH="$PATH" + CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}" + RUSTUP_HOME="${RUSTUP_HOME:-$HOME/.rustup}" + TMPDIR="${TMPDIR:-/tmp}" + LANG="${LANG:-C.UTF-8}" + TERM="${TERM:-dumb}" +) + +# Conditionally pass optional variables only if they are set and non-empty. +for var in PROTOC CC CXX PKG_CONFIG_PATH USER SHELL; do + if [ -n "${!var:-}" ]; then + ENV_ARGS+=("$var=${!var}") + fi +done + +exec env -i "${ENV_ARGS[@]}" cargo "$@"