Skip to content

fix(wallet): stop a bounded scan from erasing a live CrowdNode link - #1058

Open
romchornyi wants to merge 1 commit into
developfrom
fix/crowdnode-ownership-scan
Open

fix(wallet): stop a bounded scan from erasing a live CrowdNode link#1058
romchornyi wants to merge 1 commit into
developfrom
fix/crowdnode-ownership-scan

Conversation

@romchornyi

@romchornyi romchornyi commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Issue being fixed or feature implemented

Support ticket 32026: a customer reports CrowdNode has disappeared from the app after the update. Their logs show it happening on every launch:

CrowdNode reloading for active wallet change
CrowdNode reset triggered
CrowdNode: account not found
Found alien address in CrowdNode prefs
restoring CrowdNode state

The stored CrowdNode account address is theirs, and their funds are untouched on CrowdNode's side — the app just decided the address was not its own and erased the link.

What was done?

CrowdNodeMessageSigner.ownsAddress answers "is this stored address mine?" by re-deriving m/44'/<coin>'/0'/{0,1}/{0..299} and matching hash160, because the SDK exposes no address→index lookup. That bound arrived with the SDK-native message signer (2e44a26), where exhausting the scan is the safe answer: no key found, no signature produced. A day later (3766008) the same helper started answering the ownership question, and there exhaustion means something entirely different — yet it was still reported as false, and CrowdNode.validatePrefs responds to false by resetting the stored account.

The reset is not cosmetic. Once signUpState is no longer .finished/.linkedOnline, ShortcutActionType.customizableActions stops offering the CrowdNode shortcut — and that shortcut is the only entry point, since CrowdNode has no menu entry. A wallet whose CrowdNode address sits beyond index 300 loses access to its account entirely.

ownsAddress now returns nil (unknown) when the scan runs out. validatePrefs already treats nil as "cannot validate — keep the stored account and re-check on the next restoreState", so no control flow changed; the destructive path simply no longer fires for a case that never proved anything. false is reserved for the one answer that does prove foreignness: an address that is not a P2PKH address of the running network. The signer is untouched and still fails closed.

Raising the bound was deliberately not the fix — it would postpone the same failure to the next customer with a longer history. The doc comments were corrected too: they claimed an out-of-bound address had "never been observed in practice", which this ticket disproves.

Breaking Changes

None.

Behaviour trade-off worth stating: an address belonging to a genuinely different wallet now reads as unknown rather than foreign, so a stale link survives where it used to be cleared. Keeping a stale pointer is recoverable and visible; erasing a live account is neither. The proper end state is to ask the SDK directly (ManagedCoreWallet.signMessage(address:) knows the wallet's own addresses authoritatively) and drop the manual scan — out of scope here.

How Has This Been Tested?

Clean dashpay Debug build, no new warnings in the touched files.

The condition cannot be reproduced on a normal wallet, whose CrowdNode address sits well within the first 300 indices — that is precisely why it took a customer report to surface. Reviewers can force it by temporarily setting scanLimit to 1: before this change the CrowdNode shortcut disappears on the next launch and the stored account is wiped; after it, the account survives and the log reads "validation inconclusive … keeping stored account".

The unit-test target does not build repo-wide, so no test was added; a meaningful one would need a live SDK Wallet and a seam around SwiftDashSDKHost.shared.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • Bug Fixes
    • Prevented CrowdNode wallet links from being incorrectly reset when an address cannot be confirmed within the wallet’s derivation scan range.
    • Improved ownership validation to distinguish between addresses confirmed as external and cases where ownership cannot be determined.
    • Updated related messaging to more accurately reflect wallet availability and address validation results.

`ownsAddress` answers "is this stored CrowdNode address mine?" by re-deriving
`m/44'/<coin>'/0'/{0,1}/{0..299}` and matching hash160 — the SDK exposes no
address→index lookup. That bound came from the message signer (2e44a26),
where running out is safe: no key, no signature. Reusing it for the ownership
question (3766008) turned the bound into an ownership horizon: a wallet whose
CrowdNode address sits past index 300 was told the address was foreign, and
`validatePrefs` answered by resetting the stored account.

The reset is not cosmetic. Without `signUpState == .finished`/`.linkedOnline`
the CrowdNode shortcut leaves `customizableActions`, and that shortcut is the
only way into CrowdNode — there is no menu entry — so the customer's account
becomes unreachable in the app (ticket 32026: "CrowdNode disappeared", logs
show "Found alien address in CrowdNode prefs" on every launch).

Exhaustion now answers `nil` (unknown), which `validatePrefs` already handles
by keeping the stored account and re-checking on the next `restoreState`.
`false` is left to the one case that really proves foreignness: an address that
is not a P2PKH address of the running network. The signer is untouched and
still fails closed.

Trade-off taken deliberately: an address belonging to a genuinely different
wallet now reads as unknown rather than foreign, so a stale link survives where
it used to be cleared. Keeping a stale pointer is recoverable; erasing a live
account is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a53a4c76-0085-46f1-b908-79f3a4c0b763

📥 Commits

Reviewing files that changed from the base of the PR and between 40257a2 and 3311d74.

📒 Files selected for processing (2)
  • DashWallet/Sources/Models/CrowdNode/CrowdNode.swift
  • DashWallet/Sources/UI/CrowdNode/CrowdNodeModel.swift

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

CrowdNode ownership checks now return nil when bounded address scanning cannot determine ownership. They return false only for addresses that cannot belong to the active wallet or network. Preference validation comments and logs reflect these semantics.

Changes

CrowdNode ownership validation

Layer / File(s) Summary
Signer tri-state ownership handling
DashWallet/Sources/UI/CrowdNode/CrowdNodeModel.swift
ownsAddress now returns nil when derivation scanning reaches its bound without finding the address. It returns false only for non-P2PKH addresses on the active network. Related documentation describes the bounded scan and signing behavior.
Preference validation reporting
DashWallet/Sources/Models/CrowdNode/CrowdNode.swift
Validation comments and logs identify wallet unavailability and scan-bound exhaustion as inconclusive ownership results.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 3311d

The change preserves a stored CrowdNode link when a bounded address scan cannot prove ownership, preventing valid links from being erased; no actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: llbartekll, jeanpierreroma

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the fix that prevents bounded ownership scans from erasing active CrowdNode links.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/crowdnode-ownership-scan

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.

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