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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Security

- **Dependency advisory GHSA-4w2j-m93h-cj5j cleared**: the `quinn-proto` entry in
the lock file moves from 0.11.14 to 0.11.15, which fixes a remote
memory-exhaustion issue in out-of-order stream reassembly. The crate is an
inert optional entry that no build of this app actually links, so this is
dependency hygiene rather than a fix for reachable behavior.

- **Dependency advisory GHSA-7gcf-g7xr-8hxj still open** (`serde_with` below
3.21.0, a panic when serializing empty key-value map entries): it cannot be
resolved in this repository. `serde_with` 2.x is required by
`dashcore-rpc-json`, which arrives through pinned revisions of
`dashpay/platform` and `dashpay/rust-dashcore`; both still declare
`serde_with = "2.1.0"` at their current development heads as of 2026-07-27.
Comment on lines +19 to +22

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the dependency provenance precise and reproducible.

The supplied upstream manifest shows serde_with = "2.1.0" declared directly by dashcore-rpc-json; it does not establish that both dashpay/platform and dashpay/rust-dashcore directly declare the dependency. Distinguish the direct pin owner from the repository that pins that revision, and prefer recording exact revisions over “current development heads” for an auditable changelog. (github.com)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 19 - 22, Update the changelog dependency
provenance statement to identify dashcore-rpc-json as the direct owner of the
serde_with = "2.1.0" declaration, and describe dashpay/platform and
dashpay/rust-dashcore only as repositories supplying pinned revisions. Replace
“current development heads” with the exact relevant revision(s), including the
referenced rust-dashcore commit, so the record is reproducible.

Source: MCP tools

Allowing 3.x needs an upstream change in `dashpay/rust-dashcore` first. A TODO
in `Cargo.toml` marks the re-check.

### Added

- **Automatic Platform node refresh during upgrades**: migrating a pre-1.0
Expand Down Expand Up @@ -84,7 +101,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
the send-fee estimate from picking up the network's current rate. A
temporary workaround is in place while the underlying issue is fixed
upstream; the send-fee estimate will keep using its last known rate until
that lands.
that lands. The check only accepts a protocol version the connected network
actually confirms: when the network cannot be reached, shielded operations
stay unavailable and the app keeps retrying, instead of assuming the version
the app was built with.

### Changed

Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ qrcode = "0.14.1"
nix = { version = "0.31.1", features = ["signal"] }
eframe = { version = "0.35.0", features = ["persistence", "wgpu"] }
base64 = "0.22.1"
# TODO: GHSA-7gcf-g7xr-8hxj (serde_with <3.21.0) is unfixable from here — the 2.x pin lives in
# dashcore-rpc-json (dashpay/rust-dashcore, rpc-json/Cargo.toml). Re-check when these pins move.
dash-sdk = { git = "https://github.com/dashpay/platform", rev = "288a6cae4f9653d6085d2b3d6c7410210a0c95ba", features = [
"core_key_wallet",
"core_key_wallet_manager",
Expand Down
111 changes: 79 additions & 32 deletions src/backend_task/platform_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,23 @@ fn format_extended_epoch_info(
)
}

fn format_unavailable_current_epoch_info(protocol_version: u32) -> String {
format!(
"Current Epoch Information:\n\
• Protocol Version: {protocol_version}\n\
• Epoch details and fee multiplier are temporarily unavailable while \
dashpay/platform#4231 is unresolved.\n\n\
(The fee multiplier cache was not updated.)"
)
/// `protocol_version` is `None` while the connected network has not confirmed one.
fn format_unavailable_current_epoch_info(protocol_version: Option<u32>) -> String {
match protocol_version {
Some(protocol_version) => format!(
"Current Epoch Information:\n\
• Protocol Version: {protocol_version}\n\
• Epoch details and fee multiplier are temporarily unavailable while \
dashpay/platform#4231 is unresolved.\n\n\
(The fee multiplier cache was not updated.)"
),
None => "Current Epoch Information:\n\
• Protocol Version: the connected network has not confirmed one yet.\n\
• Epoch details and fee multiplier are temporarily unavailable while \
dashpay/platform#4231 is unresolved.\n\n\
(The fee multiplier cache was not updated.)"
.to_string(),
Comment on lines +253 to +268

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove internal implementation details from the fallback message.

This user-facing result exposes dashpay/platform#4231 and fee-cache internals. Replace them with a brief actionable message (for example, “Current epoch details are temporarily unavailable. Please try again later.”); keep diagnostics in BannerHandle::with_details.

As per coding guidelines, “User-facing error messages must be calm, brief, jargon-free, actionable” and must not expose technical details.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend_task/platform_info.rs` around lines 253 - 268, Update
format_unavailable_current_epoch_info to remove the issue reference and
fee-cache implementation details from both protocol-version branches. Replace
them with a brief, calm, jargon-free message directing users to try again later,
while leaving technical diagnostics to BannerHandle::with_details.

Source: Coding guidelines

}
}

fn format_current_quorums_info(current_quorums_info: &CurrentQuorumsInfo) -> String {
Expand Down Expand Up @@ -470,23 +479,6 @@ fn withdrawal_status_str(status: WithdrawalStatus) -> &'static str {
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn epoch_workaround_reports_protocol_version_and_stale_fee_cache() {
assert_eq!(
format_unavailable_current_epoch_info(12),
"Current Epoch Information:\n\
• Protocol Version: 12\n\
• Epoch details and fee multiplier are temporarily unavailable while \
dashpay/platform#4231 is unresolved.\n\n\
(The fee multiplier cache was not updated.)"
);
}
}

/// Flatten one withdrawal [`Document`] into a [`WithdrawalRecord`].
fn extract_withdrawal_record(
document: &Document,
Expand Down Expand Up @@ -580,20 +572,23 @@ impl AppContext {
))
}
PlatformInfoTaskRequestType::CurrentEpochInfo => {
if let Err(error) = DataContract::fetch(sdk, self.dpns_contract.id()).await {
tracing::warn!(
// dashpay/platform#4231 breaks `ExtendedEpochInfo::fetch_current`, so the
// network's version is learned from the ratchet a proved DPNS fetch drives.
// Only a successful fetch proves it came from the network, not the local seed.
match DataContract::fetch(sdk, self.dpns_contract.id()).await {
Ok(_) => self.set_platform_protocol_version(sdk.protocol_version_number()),
Err(error) => tracing::warn!(
%error,
"Protocol-version ratchet trigger (DPNS contract fetch) failed; \
keeping the SDK's previous protocol version"
);
the network's protocol version stays unconfirmed"
),
}
let protocol_version = sdk.protocol_version_number();
self.set_platform_protocol_version(protocol_version);

match ExtendedEpochInfo::fetch_current(sdk).await {
Ok(epoch_info) => {
let fee_multiplier = epoch_info.fee_multiplier_permille();
self.set_fee_multiplier_permille(fee_multiplier);
self.set_platform_protocol_version(epoch_info.protocol_version());

let mut formatted =
format_extended_epoch_info(epoch_info, self.network, true);
Expand All @@ -611,9 +606,13 @@ impl AppContext {
"Current epoch fetch is blocked by dashpay/platform#4231; \
keeping the cached fee multiplier"
);
let confirmed = match self.platform_protocol_version() {
0 => None,
version => Some(version),
};
Ok(BackendTaskSuccessResult::PlatformInfo(
PlatformInfoTaskResult::TextResult(
format_unavailable_current_epoch_info(protocol_version),
format_unavailable_current_epoch_info(confirmed),
),
))
}
Expand Down Expand Up @@ -934,3 +933,51 @@ impl AppContext {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn epoch_workaround_reports_protocol_version_and_stale_fee_cache() {
assert_eq!(
format_unavailable_current_epoch_info(Some(12)),
"Current Epoch Information:\n\
• Protocol Version: 12\n\
• Epoch details and fee multiplier are temporarily unavailable while \
dashpay/platform#4231 is unresolved.\n\n\
(The fee multiplier cache was not updated.)"
);
}

#[test]
fn epoch_workaround_never_reports_an_unconfirmed_protocol_version_as_a_number() {
let formatted = format_unavailable_current_epoch_info(None);
assert!(
formatted
.contains("• Protocol Version: the connected network has not confirmed one yet."),
"an unconfirmed version must be named as such, got: {formatted}"
);
}

/// A failed ratchet trigger leaves the SDK reporting its local seed, which is
/// not a network observation: caching it would open the shielded capability
/// gate and defeat the `0` retry sentinel `mcp::resolve` polls.
#[tokio::test]
async fn a_failed_ratchet_trigger_leaves_the_protocol_version_unconfirmed() {
let temp_dir = tempfile::tempdir().expect("tempdir");
let ctx = crate::context::test_support::test_app_context(temp_dir.path());
let sdk = dash_sdk::Sdk::new_mock();
assert_ne!(
sdk.protocol_version_number(),
0,
"precondition: the mock SDK seeds a local version of its own"
);

ctx.run_platform_info_task(PlatformInfoTaskRequestType::CurrentEpochInfo, &sdk)
.await
.expect("the epoch workaround degrades to a text result");

assert_eq!(ctx.platform_protocol_version(), 0);
}
}
Loading