Skip to content

refactor: extract WalletManager accessors and error types#599

Merged
QuantumExplorer merged 1 commit intov0.42-devfrom
refactor/key-wallet-manager-improvements
Mar 30, 2026
Merged

refactor: extract WalletManager accessors and error types#599
QuantumExplorer merged 1 commit intov0.42-devfrom
refactor/key-wallet-manager-improvements

Conversation

@QuantumExplorer
Copy link
Copy Markdown
Member

@QuantumExplorer QuantumExplorer commented Mar 30, 2026

Summary

Follow-up to #594. Improves the key-wallet-manager crate structure by breaking up the monolithic lib.rs:

  • New accessors.rs (~213 lines): Wallet creation, import, removal, and lookup methods extracted from WalletManager
  • New error.rs (~99 lines): WalletError enum extracted into its own module
  • Slimmed lib.rs: Now contains only the core WalletManager struct definition and field management
  • Moved examples and tests from key-wallet-manager to key-wallet (they test wallet primitives, not the manager layer)

Supersedes #590 which was based on a pre-#594 branch.

Test plan

  • cargo check --workspace passes
  • CI passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added wallet query and management APIs: fetch wallet data, list wallets, query accounts and transaction history.
    • Added wallet operations: remove wallets, update metadata (name/description/sync status).
    • Added balance and UTXO aggregation for individual and all wallets.
    • Added event subscription and monitoring utilities.
  • Refactor

    • Reorganized code into separate modules for improved maintainability.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

The PR introduces a dedicated accessors.rs module implementing accessor, query, and mutation methods for WalletManager<T>, a new error.rs module defining WalletError and trait implementations, and refactors lib.rs to centralize error handling and reorganize module structure.

Changes

Cohort / File(s) Summary
New Accessor Module
key-wallet-manager/src/accessors.rs
New file implementing 22 public methods for WalletManager<T> including wallet/info getters, removal, enumeration, account/transaction/UTXO queries, balance aggregation, metadata updates, event subscription, and monitored address/outpoint aggregation, plus 2 crate-visible balance snapshot/emission helpers.
New Error Module
key-wallet-manager/src/error.rs
New file defining WalletError enum with 11 variants for wallet lifecycle, derivation, network, and transaction failures; implements Display, Error traits, and From<key_wallet::Error> conversion.
Module Reorganization
key-wallet-manager/src/lib.rs
Removed inline WalletError definition and all 22 accessor methods; added private mod accessors; and mod error;; publicly re-exported WalletError; updated imports and doc comments; moved test utilities block to end.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hop, hop, the wallet's neat,
Error types now complete!
Accessors arranged just right,
Code's refactored, oh what delight!
Manager's methods, organized flight!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: extract WalletManager accessors and error types' accurately describes the main change: splitting lib.rs into accessors.rs and error.rs modules.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/key-wallet-manager-improvements

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 30, 2026

Codecov Report

❌ Patch coverage is 46.99454% with 97 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.03%. Comparing base (5004b52) to head (5616282).
⚠️ Report is 3 commits behind head on v0.42-dev.

Files with missing lines Patch % Lines
key-wallet-manager/src/accessors.rs 61.87% 53 Missing ⚠️
key-wallet-manager/src/error.rs 0.00% 44 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           v0.42-dev     #599      +/-   ##
=============================================
- Coverage      67.24%   67.03%   -0.21%     
=============================================
  Files            318      320       +2     
  Lines          67018    67249     +231     
=============================================
+ Hits           45066    45082      +16     
- Misses         21952    22167     +215     
Flag Coverage Δ
core 75.21% <ø> (ø)
ffi 35.47% <ø> (-0.55%) ⬇️
rpc 19.92% <ø> (ø)
spv 83.76% <ø> (-0.08%) ⬇️
wallet 66.63% <46.99%> (-0.01%) ⬇️
Files with missing lines Coverage Δ
key-wallet-manager/src/lib.rs 62.50% <ø> (+4.37%) ⬆️
key-wallet-manager/src/error.rs 0.00% <0.00%> (ø)
key-wallet-manager/src/accessors.rs 61.87% <61.87%> (ø)

... and 18 files with indirect coverage changes

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
fuzz/Cargo.toml (1)

18-18: Minor: redundant feature configuration.

Since key-wallet's default feature is ["getrandom"], writing default-features = false, features = ["getrandom"] is equivalent to just using defaults. This could be simplified to:

key-wallet = { path = "../key-wallet" }

However, if the intent is to be explicit about minimal dependencies for fuzz targets, the current form is acceptable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fuzz/Cargo.toml` at line 18, The dependency entry for key-wallet is
redundant: it sets default-features = false and features = ["getrandom"] while
key-wallet's default feature is already ["getrandom"]; simplify by removing the
explicit feature override and use the default form for key-wallet (reference:
dependency key-wallet in Cargo.toml) unless you intentionally want to force
minimal features for fuzz targets—in that case keep the current explicit form.
key-wallet-manager/src/error.rs (1)

6-30: Consider using thiserror for error type derivation.

The coding guidelines indicate using thiserror for proper error types. The current manual Display and Error implementations work correctly but could be simplified:

♻️ Example using thiserror
use thiserror::Error;

#[derive(Debug, Error)]
pub enum WalletError {
    #[error("Wallet creation failed: {0}")]
    WalletCreation(String),
    #[error("Wallet not found: {}", hex::encode(.0))]
    WalletNotFound(WalletId),
    // ... etc
}

Note: This would require adding thiserror and hex dependencies. The current implementation is correct; this is a stylistic suggestion per coding guidelines.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@key-wallet-manager/src/error.rs` around lines 6 - 30, Replace the manual
Debug/Display/Error implementations for WalletError by deriving thiserror::Error
on the enum: add the thiserror dependency and annotate WalletError with
#[derive(Debug, Error)] and put per-variant #[error("...")] messages for
variants like WalletCreation, WalletNotFound(WalletId), WalletExists,
InvalidMnemonic, AccountCreation, AccountNotFound, AddressGeneration,
InvalidNetwork, InvalidParameter, TransactionBuild, InsufficientFunds; for
hex-encoded WalletId messages add the hex crate and use hex::encode(.0) inside
the error format strings, then remove the hand-rolled Display/Error impls to
rely on thiserror.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dash-spv/src/client/config.rs`:
- Around line 103-109: The current ClientConfig construction and
default_peers_for_network implicitly add regtest/localhost peers (via
ClientConfig::new using Self::default_peers_for_network), causing
ClientConfig::regtest() to be non-empty; change the logic so regtest has no
default peers and callers must opt-in explicitly. Concretely, update
default_peers_for_network to return an empty list for Network::Regtest (or make
ClientConfig::regtest() call Self::default() with peers cleared) and ensure
ClientConfig::new does not inject localhost for Regtest; keep add_peer(addr)
semantics unchanged so tests/CLI can explicitly add the local peer.

In `@key-wallet-manager/src/accessors.rs`:
- Around line 37-45: The remove_wallet method currently removes from
self.wallets before verifying a matching entry in self.wallet_infos, risking
partial mutation; change it to check existence of both entries first (e.g., use
self.wallets.get(wallet_id) and self.wallet_infos.get(wallet_id) or contains_key
for both) and return WalletError::WalletNotFound without mutating if either is
missing, then perform the removals (self.wallets.remove and
self.wallet_infos.remove), update self.structural_revision, and return the pair
(wallet, info); alternatively use a remove_entry-style API to atomically take
both after confirming presence.
- Around line 125-143: The update_wallet_metadata function currently always
advances last_synced (calls
managed_info.update_last_synced(current_timestamp())) even for metadata-only
updates; change it so update_last_synced is only called when a real chain sync
occurs (i.e., do NOT call update_last_synced inside update_wallet_metadata when
only name/description are provided or when both name and description are None),
by checking whether the update is a metadata-only change before invoking
managed_info.update_last_synced; remove current_timestamp from this file's
imports and, if audit is needed for metadata edits, add a dedicated field (e.g.,
metadata_last_modified) or similar on the managed_info struct instead of reusing
last_synced.

In `@key-wallet-manager/src/lib.rs`:
- Line 1: Replace the outer doc comment used for the crate overview with an
inner crate-level doc comment at the top of lib.rs: add a `//!` block as the
very first lines containing the crate overview (move or rewrite the existing
overview text there), and leave the existing `///` comments in place directly
above `pub use key_wallet;` and `mod accessors;` so they continue to document
those items rather than the crate itself; ensure the `//!` block appears before
any code or items in the file.

---

Nitpick comments:
In `@fuzz/Cargo.toml`:
- Line 18: The dependency entry for key-wallet is redundant: it sets
default-features = false and features = ["getrandom"] while key-wallet's default
feature is already ["getrandom"]; simplify by removing the explicit feature
override and use the default form for key-wallet (reference: dependency
key-wallet in Cargo.toml) unless you intentionally want to force minimal
features for fuzz targets—in that case keep the current explicit form.

In `@key-wallet-manager/src/error.rs`:
- Around line 6-30: Replace the manual Debug/Display/Error implementations for
WalletError by deriving thiserror::Error on the enum: add the thiserror
dependency and annotate WalletError with #[derive(Debug, Error)] and put
per-variant #[error("...")] messages for variants like WalletCreation,
WalletNotFound(WalletId), WalletExists, InvalidMnemonic, AccountCreation,
AccountNotFound, AddressGeneration, InvalidNetwork, InvalidParameter,
TransactionBuild, InsufficientFunds; for hex-encoded WalletId messages add the
hex crate and use hex::encode(.0) inside the error format strings, then remove
the hand-rolled Display/Error impls to rely on thiserror.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a7ba0ff7-bc5e-4ddb-93ec-22f57d61c25f

📥 Commits

Reviewing files that changed from the base of the PR and between 5004b52 and 2b58bb1.

📒 Files selected for processing (17)
  • .codecov.yml
  • dash-spv/Cargo.toml
  • dash-spv/src/client/config.rs
  • dash-spv/src/client/config_test.rs
  • dash-spv/tests/dashd_sync/setup.rs
  • fuzz/Cargo.toml
  • key-wallet-manager/Cargo.toml
  • key-wallet-manager/src/accessors.rs
  • key-wallet-manager/src/error.rs
  • key-wallet-manager/src/lib.rs
  • key-wallet-manager/src/test_utils/mock_wallet.rs
  • key-wallet/Cargo.toml
  • key-wallet/examples/wallet_creation.rs
  • key-wallet/tests/integration_test.rs
  • key-wallet/tests/spv_integration_tests.rs
  • key-wallet/tests/test_serialized_wallets.rs
  • rpc-json/Cargo.toml
💤 Files with no reviewable changes (1)
  • .codecov.yml

Comment on lines +37 to +45
pub fn remove_wallet(&mut self, wallet_id: &WalletId) -> Result<(Wallet, T), WalletError> {
let wallet =
self.wallets.remove(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;
let info =
self.wallet_infos.remove(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;
// Absorb the removed wallet's account-level revision so the total
// stays monotonically increasing even though we lost a contributor.
self.structural_revision += info.monitor_revision() + 1;
Ok((wallet, info))
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.

⚠️ Potential issue | 🟠 Major

Make remove_wallet all-or-nothing.

Line 39 removes from self.wallets before Line 41 proves the paired wallet_infos entry exists. If those maps ever drift, this returns WalletNotFound after mutating state and leaves the manager more inconsistent.

Suggested fix
 pub fn remove_wallet(&mut self, wallet_id: &WalletId) -> Result<(Wallet, T), WalletError> {
-    let wallet =
-        self.wallets.remove(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;
-    let info =
-        self.wallet_infos.remove(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;
+    let revision = match (
+        self.wallets.contains_key(wallet_id),
+        self.wallet_infos.get(wallet_id),
+    ) {
+        (true, Some(info)) => info.monitor_revision(),
+        _ => return Err(WalletError::WalletNotFound(*wallet_id)),
+    };
+    let wallet =
+        self.wallets.remove(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;
+    let info =
+        self.wallet_infos.remove(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;
     // Absorb the removed wallet's account-level revision so the total
     // stays monotonically increasing even though we lost a contributor.
-    self.structural_revision += info.monitor_revision() + 1;
+    self.structural_revision += revision + 1;
     Ok((wallet, info))
 }
📝 Committable suggestion

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

Suggested change
pub fn remove_wallet(&mut self, wallet_id: &WalletId) -> Result<(Wallet, T), WalletError> {
let wallet =
self.wallets.remove(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;
let info =
self.wallet_infos.remove(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;
// Absorb the removed wallet's account-level revision so the total
// stays monotonically increasing even though we lost a contributor.
self.structural_revision += info.monitor_revision() + 1;
Ok((wallet, info))
pub fn remove_wallet(&mut self, wallet_id: &WalletId) -> Result<(Wallet, T), WalletError> {
let revision = match (
self.wallets.contains_key(wallet_id),
self.wallet_infos.get(wallet_id),
) {
(true, Some(info)) => info.monitor_revision(),
_ => return Err(WalletError::WalletNotFound(*wallet_id)),
};
let wallet =
self.wallets.remove(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;
let info =
self.wallet_infos.remove(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;
// Absorb the removed wallet's account-level revision so the total
// stays monotonically increasing even though we lost a contributor.
self.structural_revision += revision + 1;
Ok((wallet, info))
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@key-wallet-manager/src/accessors.rs` around lines 37 - 45, The remove_wallet
method currently removes from self.wallets before verifying a matching entry in
self.wallet_infos, risking partial mutation; change it to check existence of
both entries first (e.g., use self.wallets.get(wallet_id) and
self.wallet_infos.get(wallet_id) or contains_key for both) and return
WalletError::WalletNotFound without mutating if either is missing, then perform
the removals (self.wallets.remove and self.wallet_infos.remove), update
self.structural_revision, and return the pair (wallet, info); alternatively use
a remove_entry-style API to atomically take both after confirming presence.

Comment on lines +125 to +143
/// Update wallet metadata
pub fn update_wallet_metadata(
&mut self,
wallet_id: &WalletId,
name: Option<String>,
description: Option<String>,
) -> Result<(), WalletError> {
let managed_info =
self.wallet_infos.get_mut(wallet_id).ok_or(WalletError::WalletNotFound(*wallet_id))?;

if let Some(new_name) = name {
managed_info.set_name(new_name);
}

if let Some(desc) = description {
managed_info.set_description(Some(desc));
}

managed_info.update_last_synced(current_timestamp());
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.

⚠️ Potential issue | 🟡 Minor

Don't advance last_synced on metadata-only writes.

Line 143 runs even for update_wallet_metadata(id, None, None), so a rename or description edit looks like a fresh chain sync. Anything using that field for wallet freshness will get a false positive.

Suggested fix
-        managed_info.update_last_synced(current_timestamp());
-
         Ok(())

If you need audit info for metadata edits, track it in a dedicated field instead. Also drop current_timestamp from the import list at the top of this file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@key-wallet-manager/src/accessors.rs` around lines 125 - 143, The
update_wallet_metadata function currently always advances last_synced (calls
managed_info.update_last_synced(current_timestamp())) even for metadata-only
updates; change it so update_last_synced is only called when a real chain sync
occurs (i.e., do NOT call update_last_synced inside update_wallet_metadata when
only name/description are provided or when both name and description are None),
by checking whether the update is a metadata-only change before invoking
managed_info.update_last_synced; remove current_timestamp from this file's
imports and, if audit is needed for metadata edits, add a dedicated field (e.g.,
metadata_last_modified) or similar on the managed_info struct instead of reusing
last_synced.

//! - Address generation and gap limit handling
//! - Blockchain synchronization

/// Re-export key-wallet so consumers can access wallet primitives through this crate.
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd -type f -name "lib.rs" | grep key-wallet-manager

Repository: dashpay/rust-dashcore

Length of output: 235


🏁 Script executed:

head -30 key-wallet-manager/src/lib.rs

Repository: dashpay/rust-dashcore

Length of output: 1039


Use inner doc comments at crate root for the crate overview.

The crate root currently uses only outer doc comments (///), which document the following items rather than the crate itself. Per Rust conventions, crate-level documentation should use inner doc comments (//!) at the start of lib.rs. Without //!, the generated rustdoc will have no overview on the crate's documentation homepage. The current /// at line 1 documents the pub use key_wallet; item; lines 4–15 document mod accessors;. Add a //! block at the file start with the intended crate overview, and keep the outer /// comments for their respective items.

Suggested fix
+//! High-level wallet management for Dash
+//!
+//! This module provides high-level wallet functionality that builds on top of
+//! the low-level primitives in `key-wallet`
+//!
+//! ## Features
+//!
+//! - Multiple wallet management
+//! - BIP 157/158 compact block filter support
+//! - Address generation and gap limit handling
+//! - Blockchain synchronization
+
 /// Re-export key-wallet so consumers can access wallet primitives through this crate.
 pub use key_wallet;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@key-wallet-manager/src/lib.rs` at line 1, Replace the outer doc comment used
for the crate overview with an inner crate-level doc comment at the top of
lib.rs: add a `//!` block as the very first lines containing the crate overview
(move or rewrite the existing overview text there), and leave the existing `///`
comments in place directly above `pub use key_wallet;` and `mod accessors;` so
they continue to document those items rather than the crate itself; ensure the
`//!` block appears before any code or items in the file.

Improves the key-wallet-manager crate structure by breaking up lib.rs:

- New accessors.rs (~213 lines): wallet creation, import, removal,
  and lookup methods extracted from WalletManager
- New error.rs (~99 lines): WalletError enum extracted into its own
  module
- Slimmed lib.rs: now contains only the core WalletManager struct
  definition and field management

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@QuantumExplorer QuantumExplorer force-pushed the refactor/key-wallet-manager-improvements branch from 472a397 to 5616282 Compare March 30, 2026 17:28
@QuantumExplorer QuantumExplorer merged commit 5db46b4 into v0.42-dev Mar 30, 2026
35 of 36 checks passed
@QuantumExplorer QuantumExplorer deleted the refactor/key-wallet-manager-improvements branch March 30, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants