feat(masternodes): add Masternodes tab, retargeted onto platform-wallet rewrite#876
Merged
lklimek merged 43 commits intoJul 10, 2026
Merged
Conversation
The Withdraw screen constructor pre-selected a key via the on-chain lookup `identity.get_first_public_key_matching(TRANSFER, ...)`, which is unfiltered by local private-key presence. On loaded masternode/evonode identities where only the Owner key was supplied, this picked a "ghost" Transfer key with no local private material, so the withdrawal failed at signing with a raw, unhelpful protocol error. - model: add `QualifiedIdentity::default_withdrawal_key()` — sourced from `available_withdrawal_keys()` (private-key-backed only), Transfer preferred with Owner fallback, `None` when nothing is signable. - ui: constructor now pre-selects via `default_withdrawal_key()`; the developer-mode on-chain escape hatch is preserved. When no usable key exists the existing empty-state guides the user to add one. - error: add `TaskError::NoWithdrawalSigningKey` (typed `#[source]`, plain-language actionable Display) mapping the SDK `DesiredKeyWithTypePurposeSecurityLevelMissing` protocol error as a defense-in-depth backstop instead of leaking a raw string. - tests: 4 model cases (ghost key rejected, private-backed selected, owner fallback, transfer preferred) + 2 error-mapping/Display cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Identity keys (imported/loaded, including masternode voting/owner/payout) are no longer categorically in the deferred keyless tier: they enter unprotected at load time but can be sealed to Tier-2 per-identity via IdentityTask::ProtectIdentityKeys (Key Info screen "Add password protection"). Clarify that the keyless residual is only no-password secrets and keys the user has not opted to protect. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduce ui/state/global_nav.rs: PageNavSpec (page-aware segment-1 + per-page pill composition) and IdentityPillScope (AppGlobalUser vs PageScopedObject). The PageScopedObject variant carries its own selection and never writes AppContext::selected_identity_id — the structural FR-6 boundary the global switcher (A2) and the Masternodes page (B7) build on. Pure state, renders nothing (module-placement discriminator -> ui/state). Satisfies TC-NAV-13, TC-NAV-14, TC-NAV-15; foundation for TC-NAV-12, TC-FR6-07. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…her (A2) Add ui/components/global_nav_switcher.rs: a page-aware switcher driven by PageNavSpec, rendering segment-1 (page label + link) plus composable wallet / identity-or-object pills. GlobalNavEffect generalizes the hub's BreadcrumbEffect and adds SelectPageObject for the page-scoped pill — kept distinct from SelectIdentity so a page-scoped selection never writes the app-global identity (FR-6 boundary at the effect level). Reuses BreadcrumbPill / IdentityPill / BreadcrumbPillMode verbatim (no new pill widget). breadcrumb_switcher.rs becomes a thin hub-facing shim that builds the hub spec, delegates to the generalized render, and maps the effect back (self-nav to the hub root -> OpenPicker), keeping hub behavior unchanged — verified by the existing identity_hub_switcher kittests. Satisfies TC-NAV-01, TC-NAV-02, TC-NAV-03, TC-NAV-13, TC-NAV-16. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…plier (A3) Add the shell glue in top_panel.rs: apply_global_nav_effect (the shared successor to the hub's apply_breadcrumb_effect — silent app-scoped wallet/identity writes, no forced navigation) and add_top_panel_with_global_nav (one-call render-plus-apply), with subdued_everyday_spec / subdued_wallet_only_spec Phase-A rollout helpers. Wire the switcher onto four non-Hub root screens with Subdued (unwired) specs + TODO markers: Identities, DPNS, DashPay (everyday: wallet + identity pills) and Wallets (wallet-only composition, TC-NAV-15). The Hub keeps its existing interactive pills via the breadcrumb shim (regression — full kittest suite green). Document (comment + test, PROJ-010) that set_selected_hd_wallet reconciles the app-global identity as a side effect on non-Hub pages, and that combined with B1's resolution-layer filter it must never reconcile onto an MN/Evonode. Deferred (documented): tokens/tools screens carry in-header sub-navigation that needs the FR-GLOBAL-NAV-5 content-panel back-row migration before their plain breadcrumb can be swapped for the global switcher — a follow-up, not a mechanical swap. Satisfies TC-NAV-06, TC-NAV-15, TC-NAV-16; enables TC-NAV-12/17. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FR-8: add IdentityInputToLoad.encryption_password: Option<Secret>. When
Some, load_identity validates the password up front (fast fail) then, after
insert migrates the keyless keys into the vault, seals them Tier-2 through
the existing per-identity protect envelope (protect_identity_keys →
put_secret_protected via the secret_seam chokepoint) — no new crypto, no
second persistence path. When None, the keyless Tier-1 path is unchanged.
Relocate validate_protection_password from protect_identity_keys.rs into
model/identity_key_protection.rs (PROJ-006, DET validation-placement rule);
the seal path and load path both call the model validator.
MCP masternode_identity_load passes encryption_password: None (PROJ-007 —
GUI-only this iteration, requirements §2.3) with a TODO for headless
password parity.
Add typed TaskError variants DuplicateProTxHash { identity_id } and
MalformedProTxHash { input } for later duplicate/malformed rejection (B1/B4),
avoiding string parsing.
Tests: model validator (relocated); an offline-wired-AppContext test proving
a load-time password seals a masternode's voting (V-target), owner and
identity (M-target) keys Tier-2 and round-trips under the password — the
exact call load_identity makes (TC-FR8-01/02/10). Load-form UI is B4;
end-to-end load routing is covered by the network backend-e2e suite.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…omment Self-review: replace a transient review-finding ID in the apply_global_nav_effect reconciliation note with the durable FR reference. Comment-only; no behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…or (B1) FR-6 (R1, release-blocking): keep masternode/evonode identities out of every everyday-user surface by filtering at the resolution layer, not the display call sites. - resolve_selected_identity(): candidate set filtered to IdentityType::User before resolving, so neither keep-if-loaded nor the first-loaded fallback can ever resolve a masternode — even when a masternode is the only/first loaded identity (TC-NAV-12b). - set_selected_hd_wallet(): the wallet-switch identity reconciliation resolves only over the wallet's User identities. - restore_selected_identity_from_kv(): one-time sanitization — a masternode persisted as selected_identity_id in a prior session is cleared on load; a User selection is kept (TC-NAV-12c). In-memory only (non-destructive). - Display sources switched to the established User-only accessor load_local_user_identities(): the global switcher's identity pill + dropdown and the Identity Hub landing/picker now list User identities only, so the wallet-less "no wallet on this device" group can no longer surface an MN/Evonode (TC-NAV-17). Masternodes stay in the legacy Identities table (unfiltered accessor untouched — locked decision #2). New context accessor load_local_masternode_identities() (hydrated MN/Evonode) — the Masternodes-page card list + page-scoped pill source (B3/B7). Tests: offline-wired-AppContext unit tests (accessors, resolution filter incl. lone-masternode fallback, stale-MN sanitization) + a Hub kittest asserting a seeded Masternode+Evonode never appear on the hub while remaining in the masternode accessor. TC-FR6-01…06, TC-NAV-12b/12c, TC-NAV-17. Deferred to consuming tasks (documented): FR-7 refresh (TC-FR7-02/03) composes existing RefreshIdentity + contested-names refresh at the card Refresh button (B3); the per-node open-contest card read accessor lands in B3 where the card consumes it and it is testable against the rendered status line. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add RootScreenType::RootScreenMasternodes (stable int 28, round-trip test), ScreenType::Masternodes, Screen::MasternodesScreen, create_screen and all ScreenLike dispatch arms; register the always-present root screen in app.rs (gated at runtime by Expert Mode, not a Cargo feature, so the screen exists to switch into when the gate is on). Nav: a "Masternodes" left-panel entry gated FeatureGate::DeveloperMode, positioned directly below the identity cluster (locked decision #3), independent of the identity-hub feature. Distinct glyph voting.png (TODO: dedicated node/server icon). The existing per-entry gate skip hides the nav item and route when Expert Mode is off. Live de-gating (§10.11): active_root_screen_mut falls the active tab back to Identities (always registered) if Expert Mode flips off while Masternodes is selected, so the gated screen is never shown without its gate. MasternodesScreen is a scaffold (global-nav header + left rail + island placeholder); the empty state + card grid land in B3, the page-scoped masternode pill in B7. Network-switch already calls change_context on main_screens; the sub-screen reset (§10.10) applies once B4/B5 push sub-screens (noted for B8). Tests: RootScreenType round-trip (int 28 stable); kittest — nav absent Expert-off / present Expert-on, and de-gating falls back to Identities. TC-FR1-01…07, TC-EDGE-05/06. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render the Masternodes root screen content on top of the B2 scaffold: - Empty state (FR-2): canonical §7 copy — heading, body, "Load a masternode" primary CTA, and the ProTxHash reassurance line. - Card grid (FR-3): responsive minmax(260,1fr) grid reusing the identity picker's visual language via a new `MasternodeCard` (monogram + `draw_type_badge`, both now `pub(crate)`). Body adds masternode rows the picker lacks: voter readiness, compact `V O P` key status (glyph, not colour-only — NFR-6), DPNS status line, and the IdentityStatus dot+label. - DPNS status precedence (§10.1): open-contest count first, then a pending scheduled vote, then "No open contests", via a display-layer `AppContext::masternode_contest_summary` read (no new backend concept). - Key presence: `QualifiedIdentity::masternode_key_presence` maps Voting/Owner/Payout to voter-identity / OWNER / TRANSFER keys. - Top-right Refresh toolbar button (FR-7) reloads the cached node list. - Whole card is a single accessible click target (`WidgetInfo::labeled`, NFR-6); selection/load intents are captured for B4/B5a/B7 wiring. Traceability: TC-FR2-01…07, TC-FR3-01…15, TC-FR7-01, TC-NFR6-01/03. Unit tests cover heading/sub-line, DPNS precedence, key tokens, badge, and the 8 V/O/P combinations; kittest covers empty-state copy and the grid. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the MN/Evonode-only load flow (FR-4), carved out of the generic add-existing-identity path: - `ui/masternodes/load_form.rs`: ProTxHash (required), Masternode/Evonode segmented toggle (default Masternode, no User option), optional alias, V/O/P key inputs (reused hold-to-reveal `PasswordInput`), optional at-load encryption password (drives B0's seal), always-visible Warning-tone key-storage note, and a Load button gated on a non-empty ProTxHash with the §7 disabled tooltip. Switching node type clears all fields (§10.6). No auto-derive affordance — masternode keys are never wallet-derived (US-6 retired, §Locked-#4). - `model/masternode_input.rs`: `is_valid_pro_tx_hash` shape validator (hex or Base58) for inline on-blur validation; the backend load task remains the authoritative existence/duplicate check. - Masternodes screen gains a List/Load view enum; the empty-state CTA and a `+ Load` toolbar button open the form; submit dispatches `IdentityTask::LoadIdentity`; cancel/submit return to the list with a fresh form on reopen. - `add_existing_identity_screen`: remove Masternode/Evonode from the Advanced-Options Identity-Type dropdown (User-only remains) — no competing entry point (§10.2 / TC-FR4-22, FR-6). Traceability: TC-FR4-01…22 (render/logic; live-network accept/duplicate/ error-banner paths land in B8), TC-EDGE-01/02. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the node detail view (FR-5), reusing existing screens rather than reimplementing: - Fixed section order Header → Actions → Keys → DPNS → Remove (TC-FR5-01, the human-requested Actions-above-Keys correction), pinned by a unit test. - Header: conditional alias, shortened ProTxHash + copy-full-value, type badge (shared `draw_type_badge`), IdentityStatus dot + label. - Actions row (FR-9): Withdraw / Top up / Transfer push the existing WithdrawalScreen / TopUpIdentity / TransferScreen scoped to the node's QualifiedIdentity (both node types). Evonode-only `Claim token rewards ›` cross-link (FR-11), absent for a plain masternode. - Keys section (FR-10): V/O/P presence, copyable voter-identity id, at-rest protection tier (vault-scheme probe), Add-protection offered only Tier-1, `Manage keys ›` into the existing key screen. - DPNS section: collapsible, open-contest count in the header (voting table lands in B5b). - Remove: danger ConfirmationDialog; deletes the node and its voter identity. - `‹ All masternodes` back row + detail Refresh (FR-7/TC-FR7-04); card click opens the detail via a List/Load/Detail view enum. Deviations (documented): the Evonode claim cross-link routes to the Tokens area — precise ClaimTokensScreen token-scoping is deferred to B8 where the evonode reward-token context is resolvable. Add-protection routes into the reused key screen (which hosts the password-entry seal flow) rather than duplicating the form. Traceability: TC-FR5-01…05/07, TC-FR7-04, TC-FR9-01/02, TC-FR11-01/02. Live-network credit/claim routing and TC-FR8-07 land in B8. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the FR-12 dev convenience to the masternode load form: - New `testnet_fixture` module owns the `.testnet_nodes.yml` structs + loader. The loader returns None for BOTH a missing and a malformed file — a malformed file is logged at debug and treated as absent (TC-FR12-04, a deliberate divergence from the legacy screen which banners the parse error). - Fill-Random button + hint render only when Expert Mode is on, the network is Testnet, and the fixture is present — never shown-but-disabled (TC-FR12-01…06). The `dev_mode` gate is a defense-in-depth re-check at the call site (TC-FR12-09 decision: added on future-proofing grounds — a plaintext-key dev tool stays inside the Expert-Mode envelope). - Button label follows the node-type toggle (TC-FR12-01/02). - Autofill pulls from the correct list per type (TC-FR12-07/08): Masternode → `masternodes` (Voting + Owner only; the fixture has no payout key, PROJ-003), Evonode → `hp_masternodes` (all three keys). The node-type toggle still clears autofilled fields (§10.6). - The fixture loads once when the form opens (Testnet only), not per frame. Traceability: TC-FR12-01…09. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Populate the detail view's collapsible DPNS section (FR-5): - Collapsed by default; header shows the open-contest count (`DPNS name contests to vote on (N)`, TC-DPNS-01/02). - Voter present + open contests: per-contest Abstain / Lock / Vote-for-candidate choices with the candidate list scoped to that contest's contestants; a `Cast votes` button dispatches the existing `ContestedResourceTask::VoteOnDPNSNames` backend inline (locked decision #1 — not a deep-link). TC-DPNS-03/04/05. - Voter present, zero open contests: exact §7 empty copy (TC-DPNS-08). - Missing voter identity: the actionable §7 message (never the raw NoVotingIdentity error) plus an `Add voting key` action that opens a scoped, in-place voter-key prompt with the node context pre-bound — distinct from FR-4's load form, no ProTxHash re-entry (TC-DPNS-09/10/11, §10.8). Save re-loads this node with just the voting key to update its voter identity. - Detail Refresh now re-reads both the contest summary and the open-contest list. Active/open contests only — scheduled/past history stays on the DPNS Scheduled Votes screen (§10.7). Traceability: TC-DPNS-01…11. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the Masternodes page into the global-nav switcher with a page-scoped masternode pill whose selection lives on the page and is NEVER written to `AppContext::selected_identity_id` — the structural FR-6 boundary in code, complementing B1's resolution-layer filter. - New `ui/state/masternodes_view.rs` builds the page's `PageNavSpec`: page-aware `Masternodes` segment-1, an interactive wallet pill (funds Top up — FR-9), and a page-scoped identity pill via `IdentityPillScope::PageScopedObject`. Empty → subdued `(no masternode yet)` placeholder; ≥1 node → interactive dropdown with `Choose a masternode` placeholder; the pill reflects the node in detail and resets to the placeholder on `‹ All masternodes` (§10.4). - New `top_panel::add_top_panel_with_global_nav_capturing` surfaces the `SelectPageObject` pick to the caller (applying all other effects as usual) without ever routing it into the app-global identity selection. - The Masternodes screen builds the spec each frame from its node list + current view and opens the picked node's detail — two-way with the card grid. TC-NAV-12 / TC-FR6-07 (release-blocking): a masternode selected on the page never becomes, or resolves as, the app-global identity — verified across Identities and the Identity Hub with no User identity loaded. Traceability: TC-NAV-01/03/04/05/07, TC-NAV-12, TC-FR5-06, TC-FR6-07. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the Remove-flow integration kittest (TC-US4-01/02/06/07): the detail danger button opens a confirmation carrying the `Remove masternode` verb, and confirming deletes only the target node — its card disappears while other nodes survive (isolation). Also sets the confirmation's confirm verb to `Remove masternode` (§7 / TC-US4-02), the one small production touch the test surfaced. Deferred to the network/backend-e2e pass (out of kittest reach without live DAPI or heavy vault fixtures, and flagged as such in the plan): TC-FR8-07 (detail reflecting a load-time Tier-2-sealed node — needs the real password-at-load seal path), TC-US4-05 voter-row deletion assertion (needs a seeded voter-identity row), and the live-network credit/vote/claim dispatch paths behind FR-9/FR-11/DPNS Cast-votes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ad story Adds MN-001..MN-009 (load, card grid, vote, remove, Hub filter, at-load encryption, credit actions, key management, evonode token-reward cross-link) and UX-003 (global wallet/identity switcher) per the completed Masternodes feature. Flips IDN-003 to superseded — its generic-screen masternode load path was removed when the dedicated tab shipped.
Lands the human-accepted requirements, UX spec, test-case spec, and dev plan under docs/ai-design/, following the existing 2026-04-23-identity-hub-impl numbered-file convention. Resolves the ~100+ FR-/TC-/US-/§-ID references already scattered through the feature's code comments and tests, which pointed at an uncommitted /data/artifacts scratch copy. Internal cross-file references (requirements.md, ux-spec.md, etc.) are updated to the new numbered filenames.
…tcher Shortens the four ui/masternodes/*.rs module doc comments to the internal-tier length cap (DOC-003) — they weren't published API, so the 5-10 line public-rustdoc relaxation didn't apply. Adds GlobalNavSwitcher and its top_panel entry point to ui/components/README.md's catalog (DOC-004), so the next screen needing a page-aware switcher finds it instead of reimplementing one.
…5/006) Root-cause storage fix. insert_local_qualified_identity is INSERT OR REPLACE, so a load with no guard silently clobbers an already-stored identity and its keys. Thread an IdentityLoadMode through IdentityInputToLoad so each entry point declares intent: - RejectIfExists: the masternode load form rejects a duplicate ProTxHash with TaskError::DuplicateProTxHash before any network fetch (QA-006). - MergeIntoExisting: the scoped Add-voting-key prompt merges the new key into the stored identity, preserving Owner/Payout it did not resupply (QA-005), via merge_existing_keys_into. - Overwrite: legacy User re-load and headless flows unchanged (default). Adds get_local_qualified_identity accessor backing the existence check and merge read. Failing-first TDD: a unit test proving Owner/Payout keys survive a voting-key-only merge, and an offline test proving a duplicate ProTxHash is rejected and the first node is left untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QA-007: the detail Keys section pushed the static read-only KeysScreen. Render a per-key 'Manage keys' list and route the Add-protection CTA to KeyInfoScreen (interactive view/sign/seal per key), mirroring identities_screen. QA-001: MasternodesScreen had no change_context override, so a network switch left an open load form or cross-network detail view actionable. Add an explicit change_context arm that resets to the List view and reloads from the now-active network. QA-003: both Refresh buttons only re-read the local cache. Wire them to dispatch IdentityTask::RefreshIdentity (per loaded node on the list, the open node on detail) plus a QueryDPNSContests re-query, alongside the optimistic local re-read. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- QA-002: remove the legacy Fill-Random HPMN/Masternode bypass from the now User-only add-existing-identity screen (it set identity_type to Evonode/Masternode directly, defeating the User-only restriction). - QA-004: the evonode 'Claim token rewards' CTA pushes a real scoped ClaimTokensScreen when the node holds exactly one token, falling back to My Tokens when the target is ambiguous — no more bare SetMainScreen. - QA-008: refresh the open detail view after its own backend task, not just the card list. - Diziet-F3: render the missing-voter 'Add voting key' CTA above, outside the collapsed DPNS section, so it is visible without expanding. - QA-009: surface a MessageBanner when node removal fails instead of a silent tracing::warn. - SEC-001: log the testnet-fixture parse error by position only, never its Display text (which echoes a private key). - SEC-002: parse fixture key fields as Secret (redacted/zeroized). - Strip stale PROJ-003/PROJ-004 markers and all ephemeral QA-xxx review IDs from source comments (kept only in commit messages). - TODOs for the deferred mixed-protection-tier CTA and the is_valid_pro_tx_hash/decode_identity_id duplication. - Offline tests: TC-FR8-07 (protection_tier reflects a Tier-2 seal) and TC-US4-05 (Remove deletes the associated voter identity). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rs (SEC-005) DashPay screens (send_payment, contacts_list, profile_screen, contact_requests, qr_scanner, qr_code_generator, add_contact_screen, profile_search) built their IdentitySelector and constructor seed from the unfiltered load_local_qualified_identities() chained with .syncing_global(...). IdentitySelector::sync_to_global() writes the picked id straight to AppContext::selected_identity_id — a separate path from B1's resolve_selected_identity()/restore filters — so a user could select a masternode/evonode as the app-global operate-as identity from inside DashPay, bypassing the FR-6/R1 boundary B1 established. DashPay operates on User identities only, so every identity list in these screens is sourced from load_local_user_identities() (the same swap B1 made for the global-nav switcher and Identity Hub). This filters the masternode out of both the selector write-path and the constructor seed. Adds a kittest (masternode_never_selectable_in_dashpay_screens) exercising the FR-6 boundary through five DashPay screens — the existing FR-6 kittest only covered Identities/Identity Hub, which is how this slipped through. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- QA-012: gate re-submission while a node-load is in flight. Add a load_in_flight flag on MasternodesScreen, set on Submit dispatch and cleared on the task result or a new display_task_error override; the '+ Load' toolbar button and empty-state CTA show a spinner + disabled 'Loading…' while set, so a rapid double-submit of a brand-new ProTxHash cannot race two loads past the pre-fetch existence check. - Extend masternode_never_selectable_in_dashpay_screens to QRScanner, QRCodeGenerator (both seed selected_identity in new()) and assert ProfileSearchScreen's User-filtered data source excludes the masternode — FR-6 coverage now spans all 8 DashPay screens. - Add manage_keys_button_opens_key_info_screen: clicks a per-key 'Voting key ›' button and asserts a KeyInfoScreen is pushed with its 'Key Information' heading (execution-level proof of the QA-007 fix). - Add refresh_from_network unit test: one RefreshIdentity per loaded node plus a trailing QueryDPNSContests, None when empty (QA-003). - Fix two doc-comment lines mangled by the earlier review-ID strip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers the user-facing outcomes of the completed Masternodes feature: the new Expert-Mode-gated tab (card list, detail view, load-time key encryption, inline DPNS voting, credit actions, Evonode token-reward claiming) replacing the old generic load path for masternode/evonode identities, the resulting Identity Hub / Identities picker filter, and the wallet/identity switcher now present on every root screen instead of just the Identity Hub.
…-gated The whole model::masternode_input module was gated behind load form (ui/masternodes/load_form.rs) imports is_valid_pro_tx_hash from it unconditionally. So a plain 'cargo build --bin dash-evo-tool' (default features only — no mcp/cli, the documented quick-start build) failed with E0432 unresolved import. Every gate this feature ran used --all-features, which always pulls mcp+cli and masked it. The module can't be blanket-ungated: its parse/decode helpers return McpToolError (from the feature-gated mcp module). Fix ungates the module and the pure is_valid_pro_tx_hash validator (the only thing the GUI needs), and gates precisely the McpToolError-coupled items — KeyMode, parse_node_type, parse_key_mode, require_at_least_one_signing_key, decode_identity_id, their imports, and their tests — behind mcp/cli. The pure validator's tests move to an always-compiled module so they run in the default build too. Verified: 'cargo build --bin dash-evo-tool' (no flags) compiles; default- feature clippy clean; both default and --all-features test paths pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CHANGELOG and the components README claimed the global wallet/identity switcher was on "every screen". It ships Phase-A: rendered on Identities, DashPay, DPNS, Wallets, Identity Hub, and Masternodes, interactive only on the Hub and Masternodes (the other four render subdued, read-only pills), and absent from every other root screen (Contracts, Tokens, Tools, Network Chooser, Withdraws, ...). Names the actual screens and notes the rest as a tracked follow-up instead of implying full rollout.
…roTxHash error F-001 (MUST-FIX): "Add voting key" on a password-protected (Tier-2) node no longer trips the insert's fail-closed guard. load_identity now verifies the node's object password UP FRONT (before the network fetch, mirroring add_key_to_identity's verify-before-broadcast order) and seals the merged plaintext keys Tier-2 via seal_merged_plaintext_keys just before the at-rest insert. Two regression tests: a scripted-prompt success path proving the new key flips InVault and reads back Protected, and a headless NullSecretPrompt path proving the merge fails closed with SecretPromptUnavailable before fetch. F-002: the list screen's load_in_flight gate is cleared only on the load's own LoadedIdentity result variant (not any routed result), with a refresh_on_arrival backstop so a tab switch mid-load can never strand "+ Load" at "Loading…". F-005: a malformed identity-id input now surfaces MalformedProTxHash for masternode/evonode loads (where the field IS a ProTxHash) and keeps IdentifierParsingError for User loads. Regression test added. F-006: masternodes/evonodes legitimately have no HD wallet, so the "saving identity without wallet" warning is gated to User identities; nodes log at debug instead. F-004: correct the MCP masternode_identity_load comment — Overwrite is a destructive full-replace of stored keys, not a merge/refresh; TODO for a future load-mode param. F-007: strip ephemeral review-ID prefixes (SEC-*, Diziet-*, Smythe) from masternode-scope source comments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…readcrumb Live-walkthrough fixes on real testnet data. Fix 1 — load form back link: the load form now renders the same `‹ All masternodes` back link as the detail view (wireframe C shows it on both), at the top of the form, returning to the card list. New kittest `load_form_back_link_returns_to_list` covers it; the existing `load_form_opens_from_cta_and_cancels` gets a taller headless window so the bottom Cancel button stays reachable now that the back row is present. Fix 2 — remove the masternode object/identity pill from the Masternodes breadcrumb. Masternode/evonode identities are never wallet-linked (wallet_info is always None — locked decision #4), so pairing a wallet pill with an object pill implied a wallet↔masternode relationship that does not exist. The breadcrumb now carries only segment-1 + the interactive wallet pill; node selection is driven entirely by card-click → detail and the back link. The Masternodes page switches to add_top_panel_with_global_nav (non-capturing), matching every other non-object page. The masternodes_page_nav_spec builder drops its items/selected params. This does NOT touch the FR-6 boundary, which is enforced structurally at the resolution layer (B1) independent of any pill. The release-blocking FR-6 boundary tests (TC-NAV-12 / TC-FR6-07) remain unchanged and green. The PageScopedObject / SelectPageObject / add_top_panel_with_global_nav_capturing machinery is retained as the documented, tested boundary pattern for future page-scoped-object features (the global_nav_switcher tests still exercise it); only the Masternodes page's use of it is removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hain
Loading a masternode/evonode by ProTxHash trusted the load-form Masternode/
Evonode toggle as ground truth with no cross-check. A regular masternode loaded
with "Evonode" selected was silently accepted, mis-badged "Evonode", and shown
the Evonode-only "Claim token rewards" action.
The load task (authoritative layer) now cross-checks the selected type against
the node's actual on-chain registration. A masternode's Platform identity id is
its ProTxHash, so the node is looked up via Core RPC `protx info`; the `type`
field ("Regular"/"Evo") is classified and compared. On a confirmed mismatch the
load is rejected with a typed, actionable `TaskError::NodeTypeMismatch` naming
both the selected and actual types. When the on-chain type cannot be determined
(Core RPC unreachable — e.g. an SPV-only setup with no Core node) the load
proceeds unverified, so this adds no regression for those users.
Layering per CLAUDE.md: the pure classification (`classify_protx_node_type`)
and rejection decision (`node_type_conflict`) live in `model/masternode_input`
and are exhaustively unit-tested (the reported Evonode-on-regular case
included); the backend task owns the network lookup and enforcement.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to the node-type cross-check: when Core RPC is unreachable (the common case for SPV-only users) the node type cannot be verified, and silently proceeding with an unverified badge reproduced the original UX bug downgraded from "wrong" to "unverified". The load task now distinguishes the two success outcomes via a new `BackendTaskSuccessResult::LoadedIdentityTypeUnverified` variant, and the Masternodes screen surfaces a visible warning banner (not just a log line) telling the user the badge reflects their selection and to reload later to confirm. The MCP masternode-load tool reports the same distinction via a new `node_type_verified` output field. Regression tests: the pure reject decision (`node_type_conflict`) and the `NodeTypeMismatch` user message cover the hard-reject path; a kittest drives the unverified-load result into the live screen and asserts the warning banner is surfaced to the UI, not merely logged. Confirmed with team-lead: hard-reject on a confirmed mismatch; a visible (not log-only) warning on the unverified path. The upstream platform-wallet SPV masternode-list passthrough (for verifying node type without Core RPC) is tracked as a separate follow-up against the platform repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reverts c5167787 and 755eee87. Product decision: trust the user's
Masternode/Evonode toggle as-is, with no on-chain node-type verification.
Rationale: the only non-type-gated actions (Withdraw/Top up/Transfer) are
unaffected by a mis-toggle; the sole type-gated action ("Claim token rewards")
degrades to a clean no-op/failed Platform state transition, not a fund-safety
issue — so the toggle working as the user set it is correct behavior, not a
defect. Dropping verification also removes the dependency on Core RPC (being
deleted in the platform-wallet migration) and on fetching the operator identity
(extra scope), leaving the load path simpler and migration-proof.
Removes: TaskError::NodeTypeMismatch, the onchain_node_type Core RPC cross-check,
the classify_protx_node_type/node_type_conflict model helpers, the
LoadedIdentityTypeUnverified result variant + UI warning banner, the MCP
node_type_verified output field, and all associated tests. Fixes #1 (load-form
back link) and #2 (breadcrumb pill removal) in 5db23f72 are untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebasing the Masternodes tab onto the platform-wallet backend rewrite (PR #860) surfaced three call sites where the rewrite reshaped an API the masternode-tab code depended on: - `AppContext::identity_kv()` was renamed to `det_kv()`; the load-path existence check (`get_local_qualified_identity`) now calls the new name. - `ContestState::state_is_votable()` was dead-code-removed by the rewrite, but `ContestedName::is_open_for_voter` (Masternodes card DPNS status) relies on it — restored as a live, un-gated method. - The rewrite dropped the `identity-hub` Cargo feature and renders the Identity Hub nav entry unconditionally; the left-panel builder no longer gates that entry behind the removed `#[cfg(feature = "identity-hub")]`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merge the finished withdraw-key-selection fix (fix/withdraw-key-selection) onto the retargeted Masternodes tab. Both branches share PR #860's head as base; the overlapping files (model/qualified_identity, backend_task/error) change additively. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> # Conflicts: # src/model/qualified_identity/mod.rs
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…key fix WithdrawalScreen::new() pre-selecting via default_withdrawal_key() was only unit-tested at the QualifiedIdentity model layer. Add tests/kittest/withdraw_screen.rs to verify the fix at the actual screen layer: ghost-key identities (on-chain-only TRANSFER key) render the no-keys empty state instead of a form, private-key-backed TRANSFER/OWNER keys are pre-selected AND rendered correctly in the key-selection ComboBox (via accesskit value, not label). Also locks in a genuine regression the fix newly exposes: WithdrawalScreen::new() leaks a raw "No key provided when getting selected wallet" error banner for any identity with no locally-signable withdrawal key.
WithdrawalScreen::new() called get_selected_wallet with selected_key=None after default_withdrawal_key() correctly began returning None for identities with no locally-signable withdrawal key. With app_context=None that hits the "No key provided" String Err path, which .or_show_error() posted verbatim into a user-facing MessageBanner — violating the plain-language error policy. Guard the call on selected_key being Some, so the raw-string Err branch is structurally unreachable here instead of avoided by luck. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live QA of the Masternodes tab confirmed MasternodesScreen::reset_for_network_change() cleanly resets the List view and clears stale banners/form data on a network switch, with no existing story documenting this PR's fix. Add MN-010. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WithdrawalScreen::new() now guards get_selected_wallet on selected_key being Some (2edbc18), so the raw "No key provided..." banner leak for ghost-key-only identities is fixed. Rename ghost_key_construction_leaks_raw_error_banner -> ghost_key_construction_does_not_leak_raw_error_banner and invert the assertion; also verify the no-keys empty state still renders correctly for the same identity, so the fix didn't trade the leak for a broken empty state.
…nner leak Adds tests/kittest/withdraw_screen.rs covering the default_withdrawal_key fix at the actual screen layer (ghost-key, happy-path, owner-fallback, ComboBox-level assertion), and fixes a real regression the key-selection fix exposed: WithdrawalScreen::new() called get_selected_wallet unconditionally, surfacing a raw internal error string in the user-facing MessageBanner whenever no locally-signable key exists. Verified independently by a second agent, both directions of the fix observed under test execution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rmcp 1.7's StreamableHttpClientTransportConfig::auth_header takes the
raw token and lets reqwest's bearer_auth prepend "Bearer " itself. The
det-cli client passed format!("Bearer {token}"), so the wire header
became "Authorization: Bearer Bearer <token>". The server middleware
strips one "Bearer " and compares the remaining "Bearer <token>" against
the configured key — never equal — so headless HTTP mode returned 401 on
every request. The auth path had no end-to-end coverage, which is why it
shipped broken.
Pass the raw token and add tests/mcp_http_auth.rs pinning the server's
wire contract: raw token accepted, a double-"Bearer" prefix rejected,
missing credentials rejected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… error Two live-QA bugs on the Masternodes tab (PR #876). Bug 1 — Expert Mode didn't reveal the Masternodes nav without a restart. `developer_mode` was an independent per-network `AppContext` AtomicBool, kept in sync only by a best-effort loop in the Settings checkbox handler over the contexts that happened to exist at click time. AppState keeps one context per network (only the active one at startup; others created lazily on switch), so the left-nav `FeatureGate::DeveloperMode` gate could read a stale value on a different context than the toggle mutated — the nav entry stayed hidden until a restart re-read the persisted flag into the single fresh context. Promote the flag to one shared `Arc<AtomicBool>`, created once in `AppState::new` and injected into every `AppContext::new` (startup + `SwitchNetwork` via `developer_mode_handle()`), so all per-network contexts observe one flag. Drop the fragile sync loop; request a repaint on toggle since enabling Expert Mode disables animations (which stops continuous repaints). Bug 2 — loading a masternode by a valid-but-unregistered ProTxHash showed the generic "Identity not found — check the ID or name" copy, wrong for a ProTxHash load form. Add a dedicated `TaskError::MasternodeNotFound` variant and return it from the masternode/evonode load path instead of `IdentityNotFound`. Regression tests: `developer_mode_is_shared_across_network_contexts` (RED before the shared-flag fix), `masternode_not_found_message_is_node_specific`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Folds in the CLI/MCP headless-auth fix found while live-testing the masternode withdrawal path over det-cli on testnet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Folds in the two live-QA-found bugs from the masternode tab flow test: Expert Mode not propagating to already-created per-network contexts, and a misleading "Identity not found" error for unregistered ProTxHash. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4343d03
into
docs/platform-wallet-migration-design
5 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why this PR exists
v1.0-dev, which turned out to be stale relative to the in-flight platform-wallet backend rewrite (feat: platform-wallet backend rewrite (spec + implementation) #860, this PR's base). It has been retargeted onto that rewrite's head and includes the adaptations that required.What was done
RootScreenTyperelocation intomodel/settings.rs, theset_ctx!/delegate_to_screen!macro dispatch, the rewrite's model-pureMasternodeInputError(dropped the old MCP-coupled/feature-gated version),verify_key_inputrelocation, and a few renamed methods (identity_kv→det_kv, restoredContestState::state_is_votable).QualifiedIdentity::default_withdrawal_key()now sources the pre-selected signing key from private-key-backed keys only (Transfer-preferred, Owner-fallback), instead of the prior unfiltered on-chain key lookup that could pick a key with no local private material. Added screen-level kittest coverage (tests/kittest/withdraw_screen.rs) and fixed a raw-error-banner leak in the ghost-key/no-signable-key path.det-cliheadless HTTP auth bug (double-Bearer-prefixing) found while live-testing the masternode withdrawal path over the CLI against a running headless daemon.AtomicBoolrather than a shared flag, so toggling it left every other network's already-created context stale (nav entry wouldn't appear without a restart) — promoted to a singleArc<AtomicBool>shared across all contexts. And masternode/evonode identity load on an unregistered ProTxHash returned the genericIdentityNotFounderror — added a dedicatedTaskError::MasternodeNotFoundwith an actionable, node-specific message.Testing
cargo build(default + all features) — cleancargo +nightly fmt --all— cleancargo clippy --all-features --all-targets -- -D warnings— cleancargo test --all-features --workspace— 1455 tests passing (unit, kittest UI integration, doctests)det-climasternode identity load + withdrawal (owner-key mode) broadcast and settled on Platform, confirmed viaplatform-withdrawals-get(statuspooled).broadcast_and_waitcompletion in its log (not just the success banner). A second withdrawal to an arbitrary destination address correctly failed with a clean, non-technical error banner — Platform's own consensus rule that an OWNER-key signature can only pay the registered payout address, not a bug.det-clistandalone smoke tests for masternode-related MCP tools — passBreaking changes
None expected — additive feature plus bug fixes; no public API removed.
Checklist
tests/kittest/masternode_tab.rs,tests/kittest/withdraw_screen.rs,tests/mcp_http_auth.rs)cargo fmt/cargo clippycleanCHANGELOG.mdupdateddocs/user-stories.mdupdated (MN-010: masternode tab stays consistent across a network switch)Attribution
🤖 Co-authored by Claudius the Magnificent AI Agent