Skip to content

Conversation

knst
Copy link
Collaborator

@knst knst commented Sep 2, 2025

Issue being fixed or feature implemented

#6745

Currently, funds mixed by Coin Join by Android wallet may be invisible to other wallets, including Dash Core.

What was done?

This PR introduces 3rd default descriptor for Descriptor wallets, which makes this funds available in Dash Core.

How Has This Been Tested?

Install Android Dash wallet -> get some funds -> mix them -> import mnemonic to Dash Core. Funds (including CJ transactions) expect to visible.
Without this PR, some of these transactions are shown as "spending", and funds are not available.

Breaking Changes

Amount of descriptors created by default for a new wallet on mainnet is increased from 2 (internal, external) to 3.
Amount of descriptors created by default for a new wallet on regtest is increased from 3 (internal, external, cbtx private key) to 4.

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
  • I have assigned this pull request to a milestone

@knst knst added this to the 23 milestone Sep 2, 2025
Copy link

github-actions bot commented Sep 2, 2025

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

Copy link

coderabbitai bot commented Sep 2, 2025

Walkthrough

Adds an optional boolean coinjoin field to the listdescriptors RPC output for descriptors with a defined output type by detecting a CoinJoin derivation path that includes the dynamic ExtCoinType. Introduces PathDerivationType (BIP44_External, BIP44_Internal, DIP0009_CoinJoin) and changes DescriptorScriptPubKeyMan::SetupDescriptorGeneration to accept this enum. Adds BIP32_PURPOSE_FEATURE (9) and BIP32_PURPOSE_STANDARD (44) constants, updates HD key path formatting and descriptor generation to use purpose constants, updates wallet activation/registration flow (including AddActiveScriptPubKeyMan and adjusted handling for CoinJoin), and updates related tests and release notes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly refers to the primary change of the pull request, namely adding an extra scriptPubKey (SPK) by default for the mobile CoinJoin derivation path. Although it uses technical acronyms (“SPK” and “CJ”), it accurately reflects the main feature being introduced and is directly related to the changeset.
Description Check ✅ Passed The description succinctly outlines the issue being addressed, the feature implemented, the testing performed, and the breaking changes, all of which correspond directly to the changes introduced in the pull request.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


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
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

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

Copy link

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/wallet/rpc/backup.cpp (1)

1979-1986: Doc mismatch: coinjoin should not be “only for active descriptors.”

CJ descriptors are intentionally inactive by default; the field should be documented as present when the type is known, regardless of activity.

-                    {RPCResult::Type::BOOL, "coinjoin", /*optional=*/true, "True if this descriptor is used to generate CoinJoin addresses. False if this descriptor is used to generate receiving addresses; defined only for active descriptors"},
+                    {RPCResult::Type::BOOL, "coinjoin", /*optional=*/true, "True if this descriptor derives CoinJoin addresses (purpose 9'); present when the descriptor output type is known."},
🧹 Nitpick comments (7)
src/wallet/scriptpubkeyman.h (1)

150-155: Good addition; please document the new key class.

Add a short comment explaining semantics (External, Internal, CoinJoin) and intended activation (CJ inactive by default) to aid future readers.

+// Distinguishes descriptor categories:
+// - External: receiving addresses (change=0)
+// - Internal: change addresses (change=1)
+// - CoinJoin: mobile CJ funds (purpose 9'), created by default but kept inactive
 enum class InternalKey
 {
     External,
     Internal,
     CoinJoin,
 };
src/wallet/rpc/wallet.cpp (1)

171-172: Terminology nit: capitalize CoinJoin in help text.

- "how many new keys are pre-generated for internal use (used for change outputs and mobile coinjoin, only appears if the wallet is using this feature, otherwise external keys are used)"},
+ "how many new keys are pre-generated for internal use (used for change outputs and mobile CoinJoin; appears only if the wallet is using this feature, otherwise external keys are used)"},
test/functional/wallet_listdescriptors.py (2)

49-51: Safe dict access in assertions; drop noisy logging.

Use dict.get to avoid KeyError if schema changes, and remove debug log.

-        assert_equal(3, len(result['descriptors']))
-        assert_equal(2, len([d for d in result['descriptors'] if d['active']]))
-        self.log.info(f"result: {result['descriptors']}")
+        assert_equal(3, len(result['descriptors']))
+        assert_equal(2, len([d for d in result['descriptors'] if d.get('active')]))

52-54: Use .get and simplify coinjoin check (RUF019).

-        assert_equal(1, len([d for d in result['descriptors'] if d['internal']]))
-        assert_equal(1, len([d for d in result['descriptors'] if 'coinjoin' in d and d['coinjoin']]))
+        assert_equal(1, len([d for d in result['descriptors'] if d.get('internal')]))
+        assert_equal(1, len([d for d in result['descriptors'] if d.get('coinjoin')]))
test/functional/wallet_mnemonicbits.py (1)

55-58: Make counts robust; avoid reliance on schema details.

Keep counts but tolerate descriptors lacking ‘internal’.

-            assert_equal(mnemonic_count, 3)
+            assert_equal(mnemonic_count, 3)
             assert_equal(cb_count, 1)
-            assert_equal(len(descriptors), 4)
+            assert_equal(len(descriptors), 4)
src/wallet/rpc/backup.cpp (1)

1960-2065: Longer-term: avoid string matching; carry CJ metadata.

Prefer tagging CJ at creation (e.g., store a flag alongside WalletDescriptor) and expose it here, rather than parsing strings.

src/wallet/wallet.cpp (1)

3827-3836: Switch on bool is unconventional; prefer if/else for clarity

Minor readability nit; avoids odd switch(true/false).

-    switch (internal) {
-        case true:
-        m_internal_spk_managers = spk_man;
-        break;
-
-        case false:
-        m_external_spk_managers = spk_man;
-        break;
-    }
+    if (internal) {
+        m_internal_spk_managers = spk_man;
+    } else {
+        m_external_spk_managers = spk_man;
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e23a658 and 5f0fcf9.

📒 Files selected for processing (7)
  • src/wallet/rpc/backup.cpp (2 hunks)
  • src/wallet/rpc/wallet.cpp (1 hunks)
  • src/wallet/scriptpubkeyman.cpp (2 hunks)
  • src/wallet/scriptpubkeyman.h (2 hunks)
  • src/wallet/wallet.cpp (6 hunks)
  • test/functional/wallet_listdescriptors.py (1 hunks)
  • test/functional/wallet_mnemonicbits.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/wallet/rpc/wallet.cpp
  • src/wallet/scriptpubkeyman.h
  • src/wallet/rpc/backup.cpp
  • src/wallet/wallet.cpp
  • src/wallet/scriptpubkeyman.cpp
test/functional/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Functional tests should be written in Python and placed in test/functional/

Files:

  • test/functional/wallet_listdescriptors.py
  • test/functional/wallet_mnemonicbits.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.203Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.
📚 Learning: 2025-08-08T07:01:47.332Z
Learnt from: knst
PR: dashpay/dash#6805
File: src/wallet/rpc/wallet.cpp:357-357
Timestamp: 2025-08-08T07:01:47.332Z
Learning: In src/wallet/rpc/wallet.cpp, the upgradetohd RPC now returns a UniValue string message (RPCResult::Type::STR) instead of a boolean, including guidance about mnemonic backup and null-character passphrase handling; functional tests have been updated to assert returned strings in several cases.

Applied to files:

  • src/wallet/rpc/wallet.cpp
📚 Learning: 2025-09-02T07:34:28.203Z
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.203Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.

Applied to files:

  • src/wallet/rpc/backup.cpp
  • test/functional/wallet_mnemonicbits.py
  • src/wallet/wallet.cpp
  • src/wallet/scriptpubkeyman.cpp
🧬 Code graph analysis (4)
test/functional/wallet_listdescriptors.py (1)
test/functional/test_framework/util.py (1)
  • assert_equal (69-74)
src/wallet/scriptpubkeyman.h (1)
src/wallet/scriptpubkeyman.cpp (2)
  • SetupDescriptorGeneration (2077-2129)
  • SetupDescriptorGeneration (2077-2077)
src/wallet/rpc/backup.cpp (1)
src/script/descriptor.cpp (1)
  • nullopt (657-657)
test/functional/wallet_mnemonicbits.py (1)
test/functional/test_framework/util.py (1)
  • assert_equal (69-74)
🪛 Ruff (0.12.2)
test/functional/wallet_listdescriptors.py

53-53: Unnecessary key check before dictionary access

Replace with dict.get

(RUF019)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Lint / Run linters
  • GitHub Check: x86_64-apple-darwin / Build depends
  • GitHub Check: x86_64-w64-mingw32 / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_multiprocess / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_nowallet / Build depends
  • GitHub Check: arm-linux-gnueabihf / Build depends
  • GitHub Check: x86_64-pc-linux-gnu / Build depends
🔇 Additional comments (5)
src/wallet/scriptpubkeyman.h (1)

584-586: Signature change looks right; confirm all callers updated.

Given the switch from bool to enum, ensure all invocations now pass InternalKey::{External,Internal,CoinJoin}. Compile will catch most, but please double-check wallet setup paths.

src/wallet/scriptpubkeyman.cpp (1)

2077-2079: Confirm all calls updated—no remaining boolean overloads
Grep shows only one invocation in src/wallet/wallet.cpp:3800, passing the local internal variable, which is already of type InternalKey (not bool). No literal true/false or stale overloads remain.

src/wallet/wallet.cpp (3)

2357-2369: Use external SPKM directly for receive addresses — OK

Straightforward; no behavior change beyond comment cleanup.


3789-3806: Creating 3 SPKMs and activating only External/Internal — OK

Matches goal: CJ SPKM exists for discovery but is inactive for address issuance.


3838-3844: De-aliasing active SPKM pointers when reassigning — OK

Prevents both pointers referencing the same manager; good defensive state management.

Comment on lines 2102 to 2107
std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, internal == InternalKey::CoinJoin ? 9 : 44, Params().ExtCoinType());
std::string desc_suffix = "/*)";

std::string internal_path = internal ? "/1" : "/0";
std::string internal_path = (internal == InternalKey::Internal) ? "/1" : "/0";
std::string desc_str = desc_prefix + "/0'" + internal_path + desc_suffix;

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

CoinJoin derivation path likely mismatched with Android “9'/1'/0'” expectation

Code uses Params().ExtCoinType() for CJ coin type, but RPC/tests reportedly detect CJ via path “/9'/1'/0'”. On mainnet ExtCoinType() != 1 (Dash is 5), so generated CJ descriptor would be “9'/5'/0'…”, breaking visibility with Android CJ funds. Fix by forcing coin type 1 for CJ (or align detection to ExtCoinType()).

Apply:

-    std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, internal == InternalKey::CoinJoin ? 9 : 44, Params().ExtCoinType());
+    const int coin_type = (internal == InternalKey::CoinJoin) ? 1 : Params().ExtCoinType();
+    std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, internal == InternalKey::CoinJoin ? 9 : 44, coin_type);

Also confirm listdescriptors’ CJ detection logic matches this choice:

If Android CJ uses a different coin_type per network, wire it explicitly and update tests.


🏁 Script executed:

#!/bin/bash
rg -n "listdescriptors|coinjoin|9'/1'|9'\/1'" -C3

Length of output: 200


🏁 Script executed:

#!/bin/bash
# Confirm CJ descriptor generation uses Params().ExtCoinType()
rg -n "desc_prefix" -C5 src/wallet/scriptpubkeyman.cpp

# Search for listdescriptors RPC command occurrences
rg -n "listdescriptors" -C5 .

# Inspect functional tests for CJ path detection (9'/1)
rg -n "9'/1" -C5 test

Length of output: 25750


Force CJ descriptor coin type to 1
Functional tests (wallet_mnemonicbits.py:50) detect CJ descriptors inactive via the substring "/9'/1" in the descriptor. On mainnet, Params().ExtCoinType() returns 5, so CJ descriptors are currently generated with 9'/5…, breaking both the functional test detection and Android’s “9'/1'/0'” assumption. Update the code as follows:

-    std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, internal == InternalKey::CoinJoin ? 9 : 44, Params().ExtCoinType());
+    const int coin_type = internal == InternalKey::CoinJoin ? 1 : Params().ExtCoinType();
+    std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, internal == InternalKey::CoinJoin ? 9 : 44, coin_type);
📝 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
std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, internal == InternalKey::CoinJoin ? 9 : 44, Params().ExtCoinType());
std::string desc_suffix = "/*)";
std::string internal_path = internal ? "/1" : "/0";
std::string internal_path = (internal == InternalKey::Internal) ? "/1" : "/0";
std::string desc_str = desc_prefix + "/0'" + internal_path + desc_suffix;
const int coin_type = internal == InternalKey::CoinJoin ? 1 : Params().ExtCoinType();
std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, internal == InternalKey::CoinJoin ? 9 : 44, coin_type);
std::string desc_suffix = "/*)";
std::string internal_path = (internal == InternalKey::Internal) ? "/1" : "/0";
std::string desc_str = desc_prefix + "/0'" + internal_path + desc_suffix;
🤖 Prompt for AI Agents
In src/wallet/scriptpubkeyman.cpp around lines 2102 to 2107, the CJ (CoinJoin)
descriptor uses Params().ExtCoinType() which on mainnet yields 5, causing CJ
descriptors to be generated as "9'/5" and breaking tests and Android
assumptions; change the code to force coin type to 1 when internal ==
InternalKey::CoinJoin (e.g. compute a coinType variable = (internal ==
InternalKey::CoinJoin ? 1 : Params().ExtCoinType()) and use that coinType in the
desc_prefix construction), leaving other path parts unchanged.

@knst knst marked this pull request as draft September 2, 2025 13:43
@knst knst added RPC Some notable changes to RPC params/behaviour/descriptions Needs Release Notes This PR includes breaking changes for which release notes have not yet been merged. labels Sep 2, 2025
@knst knst force-pushed the feat-cj-mobile-derivation-inactive-descriptor branch from 5f0fcf9 to 9d7be36 Compare September 5, 2025 16:28
@knst
Copy link
Collaborator Author

knst commented Sep 5, 2025

@coderabbitai help to prepare release notes for this PR

See PR description and #6745 for information source

UPD: it's a bit too long; see as a reference release notes for v21: https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.0.0.md It should be just one paragraph of text; maximum 2

Copy link

coderabbitai bot commented Sep 5, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

I'll help you prepare release notes for this PR. Let me first gather some additional information about the issue and PR details.

Based on the information gathered and the PR changes, here are the release notes for this feature:

Release Notes - Mobile CoinJoin Descriptor Support

New Feature: Enhanced CoinJoin Compatibility Between Mobile and Desktop Wallets

Problem Solved:

  • Fixed an issue where funds mixed using CoinJoin in the Dash Android wallet were invisible to Dash Core and other desktop wallets
  • Previously, when importing a mnemonic from Android wallet to Dash Core, some CoinJoin transactions would appear as "spending" transactions but the funds would not be available

Changes:

  • Breaking Change: New descriptor wallets now include an additional default descriptor for mobile CoinJoin funds
    • Mainnet: Default descriptors increased from 2 (internal, external) to 3 (internal, external, mobile CJ)
    • Regtest: Default descriptors increased from 3 (internal, external, cbtx private key) to 4 (internal, external, mobile CJ, cbtx private key)

API Changes:

  • listdescriptors RPC: Added new optional coinjoin boolean field to descriptor objects
    • When true, indicates the descriptor is used for CoinJoin funds
    • Only present for descriptors with defined output types

Technical Details:

  • Introduces support for mobile derivation path (m/9'/1'/0') used by Dash Android wallet for CoinJoin operations
  • CoinJoin descriptors are created by default but remain inactive to preserve existing wallet behavior
  • Enhanced descriptor detection logic to identify and properly classify CoinJoin descriptors

User Impact:

  • Users can now seamlessly move between Dash Android wallet and Dash Core without losing visibility of CoinJoin funds
  • Existing wallets are not affected - only new descriptor wallets will include the additional CoinJoin descriptor
  • No action required for existing users; the change only affects newly created descriptor wallets

For Developers:

  • New InternalKey enum introduced with values: External, Internal, CoinJoin
  • Updated SetupDescriptorGeneration method signature to use InternalKey instead of boolean parameter
  • Enhanced wallet descriptor management to support three-way categorization

This change ensures better interoperability between different Dash wallet implementations while maintaining backward compatibility for existing wallets.

@knst


🏁 Script executed:

#!/bin/bash
# Get detailed information about the issue #6745
gh issue view 6745 --repo dashpay/dash

echo "=== PR Description ==="
# Get the PR description 
gh pr view 6836 --repo dashpay/dash

Length of output: 2098


Perfect! Now I have the complete context. Here are the refined release notes for PR #6836:

Release Notes - Mobile CoinJoin Descriptor Support

New Feature: Enhanced CoinJoin Compatibility Between Mobile and Desktop Wallets

Problem Solved:

  • Fixed an issue where funds mixed using CoinJoin in the Dash Android wallet were invisible to Dash Core and other desktop wallets
  • Mobile wallets use specific HD derivation paths for CoinJoin funds that weren't being tracked by desktop wallets
  • When importing a mnemonic from Android wallet to Dash Core, CoinJoin transactions would appear as "spending" transactions but the actual funds remained inaccessible

What Changed:

  • Breaking Change: New descriptor wallets now include an additional default descriptor specifically for mobile CoinJoin funds
    • Mainnet: Default descriptors increased from 2 to 3 (internal, external, mobile CoinJoin)
    • Regtest: Default descriptors increased from 3 to 4 (internal, external, mobile CoinJoin, cbtx private key)

API Enhancements:

  • listdescriptors RPC: New optional coinjoin boolean field added to descriptor objects
    • Indicates when a descriptor is used specifically for CoinJoin funds
    • Only appears for descriptors with defined output types
  • getwalletinfo RPC: Updated documentation for keypoolsize_hd_internal to clarify it includes mobile coinjoin keys

User Benefits:

  • Seamless wallet migration: Users can now freely switch between Dash Android wallet and Dash Core without losing access to CoinJoin funds
  • Complete fund visibility: All mixed funds are now properly displayed and accessible across different wallet implementations
  • No user action required: Existing wallets continue working normally; only newly created descriptor wallets include the enhancement

Technical Implementation:

  • Introduces support for mobile derivation path (m/9'/1'/0') used by Dash Android wallet
  • CoinJoin descriptors are created by default but remain inactive to preserve existing behavior
  • New InternalKey enum system for better categorization of key types (External, Internal, CoinJoin)

Compatibility:

  • Backward compatible: Existing wallets are unaffected
  • Forward compatible: New wallets work seamlessly with mobile applications
  • Cross-platform: Ensures consistent behavior between different Dash wallet implementations

This enhancement significantly improves the user experience when using multiple Dash wallet applications, ensuring that CoinJoin privacy features work consistently across the entire ecosystem.

@knst


🧠 Learnings used
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.226Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.

Copy link

coderabbitai bot commented Sep 5, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@knst, I'll prepare concise release notes following the v21 format. Let me first check the reference format you mentioned:

Based on the v21 release notes format, here are the concise release notes for PR #6836:

Mobile CoinJoin Compatibility

Fixed an issue where CoinJoin funds mixed in Dash Android wallet were invisible when importing the mnemonic into Dash Core or other desktop wallets. New descriptor wallets now include an additional default descriptor for mobile CoinJoin funds, ensuring seamless wallet migration and complete fund visibility across different Dash wallet implementations. This is a breaking change that increases the default number of descriptors from 2 to 3 on mainnet (internal, external, mobile CoinJoin) for newly created descriptor wallets only - existing wallets are unaffected.


🏁 Script executed:

#!/bin/bash
# Fetch the v21 release notes to understand the desired format
curl -s https://raw.githubusercontent.com/dashpay/dash/master/doc/release-notes/dash/release-notes-21.0.0.md | head -50

Length of output: 2241


Perfect! Now I can see the format. Here are the concise release notes following the v21 style:

Mobile CoinJoin Compatibility

Fixed an issue where CoinJoin funds mixed in Dash Android wallet were invisible when importing the mnemonic into Dash Core or other desktop wallets. New descriptor wallets now include an additional default descriptor for mobile CoinJoin funds, ensuring seamless wallet migration and complete fund visibility across different Dash wallet implementations.

This is a breaking change that increases the default number of descriptors from 2 to 3 on mainnet (internal, external, mobile CoinJoin) for newly created descriptor wallets only - existing wallets are unaffected. The listdescriptors RPC now includes an optional coinjoin field to identify CoinJoin descriptors.

@knst


🧠 Learnings used
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.226Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.

@knst knst force-pushed the feat-cj-mobile-derivation-inactive-descriptor branch 3 times, most recently from 018a42d to bdb92b2 Compare September 6, 2025 07:04
@knst knst marked this pull request as ready for review September 8, 2025 07:27
Copy link

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
doc/release-notes-6835.md (1)

1-22: Fix wrong PR reference and capture regtest delta in breaking-change note

  • The reference “(#6835)” appears incorrect for this PR; update to “(#6836)” and optionally include the original issue “(#6745)”.
  • The breaking-change paragraph omits the regtest increase (3 → 4); add it for completeness.

Apply:

- - This is a breaking change that increases the default number of descriptors
-   from 2 to 3 on mainnet (internal, external, mobile CoinJoin) for newly created
-   descriptor wallets only - existing wallets are unaffected.
+ - Breaking change (new descriptor wallets only):
+   - Mainnet: default descriptors increase from 2 to 3 (internal, external, mobile CoinJoin).
+   - Regtest: default descriptors increase from 3 to 4 (internal, external, mobile CoinJoin, cbtx private key).
+   Existing wallets are unaffected.
@@
-(#6835)
+(#6836, #6745)
♻️ Duplicate comments (1)
test/functional/wallet_mnemonicbits.py (1)

50-51: Prefer dict.get for clarity and to satisfy Ruff RUF019

Equivalent and cleaner.

-                assert_equal(desc['active'], ("coinjoin" not in desc or not desc['coinjoin']))
+                assert_equal(desc['active'], not desc.get('coinjoin', False))
🧹 Nitpick comments (3)
test/functional/wallet_mnemonicbits.py (1)

55-57: Update adjacent comment to match new expectations (3 mnemonics, 4 descriptors)

The comment above Lines 52–54 still describes the pre-change state (“3 descriptors total … Two other should be active”). Please align it with the new assertions (mnemonic_count == 3, len(descriptors) == 4). No code change needed here.

test/functional/wallet_listdescriptors.py (1)

53-53: Use dict.get to simplify the coinjoin filter

Shorter and avoids an unnecessary key check.

-        assert_equal(1, len([d for d in result['descriptors'] if 'coinjoin' in d and d['coinjoin']]))
+        assert_equal(1, len([d for d in result['descriptors'] if d.get('coinjoin')]))
src/wallet/wallet.cpp (1)

3790-3807: Minor: avoid “internal” as loop var for the InternalKey enum

Reduces ambiguity with the existing boolean “internal” semantics elsewhere.

-    for (auto internal : {InternalKey::External, InternalKey::Internal, InternalKey::CoinJoin}) {
+    for (auto ikey : {InternalKey::External, InternalKey::Internal, InternalKey::CoinJoin}) {
@@
-            spk_manager->SetupDescriptorGeneration(master_key, mnemonic, mnemonic_passphrase, internal);
+            spk_manager->SetupDescriptorGeneration(master_key, mnemonic, mnemonic_passphrase, ikey);
@@
-            if (internal != InternalKey::CoinJoin) {
-                AddActiveScriptPubKeyMan(id, internal == InternalKey::Internal);
+            if (ikey != InternalKey::CoinJoin) {
+                AddActiveScriptPubKeyMan(id, ikey == InternalKey::Internal);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f0fcf9 and bdb92b2.

📒 Files selected for processing (8)
  • doc/release-notes-6835.md (1 hunks)
  • src/wallet/rpc/backup.cpp (3 hunks)
  • src/wallet/rpc/wallet.cpp (1 hunks)
  • src/wallet/scriptpubkeyman.cpp (2 hunks)
  • src/wallet/scriptpubkeyman.h (2 hunks)
  • src/wallet/wallet.cpp (2 hunks)
  • test/functional/wallet_listdescriptors.py (1 hunks)
  • test/functional/wallet_mnemonicbits.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/wallet/scriptpubkeyman.h
  • src/wallet/scriptpubkeyman.cpp
  • src/wallet/rpc/wallet.cpp
  • src/wallet/rpc/backup.cpp
🧰 Additional context used
📓 Path-based instructions (3)
doc/**

📄 CodeRabbit inference engine (CLAUDE.md)

Unless specifically prompted, avoid making changes to the doc directory (documentation)

Files:

  • doc/release-notes-6835.md
test/functional/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Functional tests should be written in Python and placed in test/functional/

Files:

  • test/functional/wallet_mnemonicbits.py
  • test/functional/wallet_listdescriptors.py
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/wallet/wallet.cpp
🧠 Learnings (2)
📓 Common learnings
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.226Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.
📚 Learning: 2025-09-02T07:34:28.226Z
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.226Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.

Applied to files:

  • doc/release-notes-6835.md
  • test/functional/wallet_mnemonicbits.py
  • src/wallet/wallet.cpp
🧬 Code graph analysis (2)
test/functional/wallet_mnemonicbits.py (1)
test/functional/test_framework/util.py (1)
  • assert_equal (69-74)
test/functional/wallet_listdescriptors.py (1)
test/functional/test_framework/util.py (1)
  • assert_equal (69-74)
🪛 Ruff (0.12.2)
test/functional/wallet_listdescriptors.py

53-53: Unnecessary key check before dictionary access

Replace with dict.get

(RUF019)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: linux64_tsan-test / Test source
🔇 Additional comments (2)
src/wallet/wallet.cpp (2)

3790-3807: LGTM: create CoinJoin SPKM but keep it inactive by default

Creating the CJ SPKM and not activating it (only External/Internal active) matches the intended behavior and tests relying on listdescriptors. No functional issues spotted.


3790-3807: Fetched backup.cpp context for manual verification. No critical issues found: all GetAllScriptPubKeyMans call sites perform non-mutating or safe migrations/reporting (descriptor backup, cache upgrade, signing/fill PSBT across all keyrings, logging, and rescan time determination). Address generation, keypool top-up, and notifications exclusively use GetActiveScriptPubKeyMans.

Copy link
Collaborator

@kwvg kwvg left a comment

Choose a reason for hiding this comment

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

nits

Copy link

@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: 0

🧹 Nitpick comments (1)
src/wallet/hdchain.cpp (1)

206-206: Fix formatting to comply with Clang format check.

The pipeline indicates formatting differences detected by clang-format.

Apply this diff to fix the formatting:

-    return strprintf("m/%d'/%d'/%d'/%d/%d", BIP32_PURPOSE_STANDARD, Params().ExtCoinType(), nAccountIndex, nChangeIndex, extPubKey.nChild);
+    return strprintf("m/%d'/%d'/%d'/%d/%d", BIP32_PURPOSE_STANDARD, Params().ExtCoinType(), nAccountIndex, nChangeIndex, 
+                     extPubKey.nChild);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdb92b2 and 1038ad3.

📒 Files selected for processing (3)
  • src/wallet/hdchain.cpp (1 hunks)
  • src/wallet/hdchain.h (1 hunks)
  • src/wallet/scriptpubkeyman.cpp (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/wallet/hdchain.h
  • src/wallet/hdchain.cpp
  • src/wallet/scriptpubkeyman.cpp
🧠 Learnings (3)
📓 Common learnings
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.226Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.
📚 Learning: 2025-08-08T07:01:47.332Z
Learnt from: knst
PR: dashpay/dash#6805
File: src/wallet/rpc/wallet.cpp:357-357
Timestamp: 2025-08-08T07:01:47.332Z
Learning: In src/wallet/rpc/wallet.cpp, the upgradetohd RPC now returns a UniValue string message (RPCResult::Type::STR) instead of a boolean, including guidance about mnemonic backup and null-character passphrase handling; functional tests have been updated to assert returned strings in several cases.

Applied to files:

  • src/wallet/hdchain.h
  • src/wallet/hdchain.cpp
📚 Learning: 2025-09-02T07:34:28.226Z
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.226Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.

Applied to files:

  • src/wallet/scriptpubkeyman.cpp
🧬 Code graph analysis (2)
src/wallet/hdchain.cpp (2)
src/wallet/hdchain.h (3)
  • BIP32_PURPOSE_STANDARD (147-149)
  • nAccountIndex (131-131)
  • nChangeIndex (132-132)
src/chainparams.cpp (2)
  • Params (1354-1357)
  • Params (1354-1354)
src/wallet/scriptpubkeyman.cpp (2)
src/wallet/hdchain.h (2)
  • BIP32_PURPOSE_FEATURE (145-145)
  • BIP32_PURPOSE_STANDARD (147-149)
src/chainparams.cpp (2)
  • Params (1354-1357)
  • Params (1354-1354)
🪛 GitHub Actions: Clang Diff Format Check
src/wallet/hdchain.cpp

[error] 203-208: Clang format differences detected by clang-format-diff.py. Formatting changes applied would modify src/wallet/hdchain.cpp; please run clang-format (or update CI to accept formatted changes).

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: x86_64-pc-linux-gnu_multiprocess / Build depends
  • GitHub Check: x86_64-w64-mingw32 / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_nowallet / Build depends
  • GitHub Check: x86_64-apple-darwin / Build depends
  • GitHub Check: x86_64-pc-linux-gnu / Build depends
  • GitHub Check: arm-linux-gnueabihf / Build depends
🔇 Additional comments (5)
src/wallet/hdchain.h (1)

144-148: LGTM!

The new constants BIP32_PURPOSE_FEATURE (9) and BIP32_PURPOSE_STANDARD (44) align with the PR objectives to support mobile CoinJoin with purpose 9 and standard derivation with purpose 44. These constants will replace hardcoded values in descriptor path construction.

src/wallet/hdchain.cpp (1)

206-206: LGTM!

The path format change from hardcoded "m/44'/%d'/%d'/%d/%d" to dynamic "m/%d'/%d'/%d'/%d/%d" with BIP32_PURPOSE_STANDARD is correct. This aligns with the new constants defined in hdchain.h and supports variable purpose codes for different derivation paths.

src/wallet/scriptpubkeyman.cpp (3)

2077-2077: LGTM!

The function signature change from bool internal to InternalKey internal properly aligns with the new three-way categorization (External, Internal, CoinJoin) introduced by this PR.


2105-2105: LGTM!

The conditional logic correctly maps the InternalKey enum to the appropriate path component: Internal becomes "/1" and External (including CoinJoin) becomes "/0". This properly differentiates internal change addresses from external receiving addresses.


2102-2102: **Force CJ descriptor coin type to 1.**Based on the web search results, I can see that Dash uses coin type 5 for BIP44 derivation paths. However, the past review comment indicates that functional tests detect CJ descriptors via the substring "/9'/1" and Android CJ uses "9'/1'/0'". This creates a mismatch since Params().ExtCoinType() returns 5 on mainnet, not 1.

Based on the previous review feedback and the retrieved learnings showing that CJ descriptors are detected by matching "/9'/1" patterns, the current code using Params().ExtCoinType() would generate "9'/5'" paths on mainnet instead of the expected "9'/1'" paths. This breaks compatibility with both functional tests and Android CJ wallets.

Apply this fix to force coin type 1 for CJ descriptors:

-    std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, internal == InternalKey::CoinJoin ? BIP32_PURPOSE_FEATURE : BIP32_PURPOSE_STANDARD, Params().ExtCoinType());
+    const int coin_type = (internal == InternalKey::CoinJoin) ? 1 : Params().ExtCoinType();
+    std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, internal == InternalKey::CoinJoin ? BIP32_PURPOSE_FEATURE : BIP32_PURPOSE_STANDARD, coin_type);

kwvg
kwvg previously approved these changes Sep 22, 2025
Copy link
Collaborator

@kwvg kwvg left a comment

Choose a reason for hiding this comment

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

utACK 1038ad3

@knst knst removed the Needs Release Notes This PR includes breaking changes for which release notes have not yet been merged. label Sep 23, 2025
Comment on lines 2042 to 2043
std::string match = strprintf("/9'/%s'/0'", Params().ExtCoinType());
bool is_cj = descriptor.size() > 5 && descriptor.find(match) != std::string::npos;
Copy link
Member

Choose a reason for hiding this comment

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

let's avoid these magic numbers

Copy link
Member

@PastaPastaPasta PastaPastaPasta left a comment

Choose a reason for hiding this comment

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

utACK 78a0979

kwvg
kwvg previously approved these changes Sep 23, 2025
Copy link
Collaborator

@kwvg kwvg left a comment

Choose a reason for hiding this comment

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

utACK 78a0979

Copy link

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

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

This differs a lot from the proposed plan #6745 (comment). Why?

Comment on lines 150 to 155
enum class InternalKey
{
External,
Internal,
CoinJoin,
};
Copy link

Choose a reason for hiding this comment

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

nit: These names are confusing imo. Maybe smth like

Suggested change
enum class InternalKey
{
External,
Internal,
CoinJoin,
};
enum class PathDerivationType
{
BIP44_External,
BIP44_Internal,
DIP0009_CoinJoin,
};

@knst
Copy link
Collaborator Author

knst commented Sep 25, 2025

This differs a lot from the proposed plan #6745 (comment). Why?

we discussed it on weekly calls, there are several reasons to do.

Firstly, I'd like to keep compatibility between Dash Core & 3rd party wallets, include hardware one. If we will have custom derivation path for mixing, same as mobile, it means that these funds won't be easily available at any other 3rd party wallets, it creates "vendor lock" to DCG projects (android wallet, Dash Core). That's why I don't like to use for CJ on Dash Core custom mobile path same as it is done in mobile android wallet.

So far as we have mnemonic in Dash Core; it is great if these mnemonic can be easily imported in any 3rd party wallet and it "just works".

Though, this PR is aim to add a potentially at least 1 wallet beside Android Wallet that could let user to get access to his "lost" funds if user will mix his funds on mobile and it does.

Secondly, Dash Core currently has 2 competing implementation of HD wallets (legacy wallets and descriptor wallets). Both of them them (legacy & descriptor) are backported from bitcoin*(1)(2), but legacy wallets are deprecated in bitcoin and in the latest versions you can't even create legacy wallet anymore.

I wish Dash Core with mnemonic for Descriptor wallets (#6570) has been released long time ago, so we could start deprecating legacy wallets; and create descriptor wallets, but we haven't had a major releases for awhile, so, it's a bit slower that it is.
Due that I don't really want to add new feature to legacy wallets and complicate implementation as proposed in #6745, instead it's much easier to implement & support just adding +1 extra descriptor in descriptor wallets. The patch minimal (50 lines) but it works just fine.

(1). legacy wallets have our own implementation HD, not fully backported.
(2). both descriptor & legacy wallets have our own implementation of mnemonic, it's not backported feature.

@knst knst dismissed stale reviews from kwvg and PastaPastaPasta via 805a7e8 September 25, 2025 07:13
Copy link

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

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

This differs a lot from the proposed plan #6745 (comment). Why?

we discussed it on weekly calls, there are several reasons to do.

Firstly, I'd like to keep compatibility between Dash Core & 3rd party wallets, include hardware one. If we will have custom derivation path for mixing, same as mobile, it means that these funds won't be easily available at any other 3rd party wallets, it creates "vendor lock" to DCG projects (android wallet, Dash Core). That's why I don't like to use for CJ on Dash Core custom mobile path same as it is done in mobile android wallet.

So far as we have mnemonic in Dash Core; it is great if these mnemonic can be easily imported in any 3rd party wallet and it "just works".

Though, this PR is aim to add a potentially at least 1 wallet beside Android Wallet that could let user to get access to his "lost" funds if user will mix his funds on mobile and it does.

Secondly, Dash Core currently has 2 competing implementation of HD wallets (legacy wallets and descriptor wallets). Both of them them (legacy & descriptor) are backported from bitcoin*(1)(2), but legacy wallets are deprecated in bitcoin and in the latest versions you can't even create legacy wallet anymore.

I wish Dash Core with mnemonic for Descriptor wallets (#6570) has been released long time ago, so we could start deprecating legacy wallets; and create descriptor wallets, but we haven't had a major releases for awhile, so, it's a bit slower that it is. Due that I don't really want to add new feature to legacy wallets and complicate implementation as proposed in #6745, instead it's much easier to implement & support just adding +1 extra descriptor in descriptor wallets. The patch minimal (50 lines) but it works just fine.

(1). legacy wallets have our own implementation HD, not fully backported. (2). both descriptor & legacy wallets have our own implementation of mnemonic, it's not backported feature.

Makes sense, thanks!

import mnemonic to Dash Core. Funds (including CJ transactions) expect to visible.

I'm creating an empty descriptor wallet and using "upgradetohd" rpc to import mnemonic I use on mobile. I then do "rescanblockchain" but I see no CJ txes once its done, only "Sent to"... What steps were you using exactly to test this?

@knst
Copy link
Collaborator Author

knst commented Sep 25, 2025

I'm creating an empty descriptor wallet and using "upgradetohd" rpc to import mnemonic I use on mobile. I then do "rescanblockchain" but I see no CJ txes once its done, only "Sent to"... What steps were you using exactly to test this?

Maybe there is a bug, I will re-check. I tested some not the very final version, maybe introduced some new issue. I will re-test and check if it works.

@knst knst marked this pull request as draft September 25, 2025 10:47
@knst knst marked this pull request as ready for review September 29, 2025 20:50
@knst knst requested a review from UdjinM6 September 29, 2025 20:51
@knst
Copy link
Collaborator Author

knst commented Sep 29, 2025

I'm creating an empty descriptor wallet and using "upgradetohd" rpc to import mnemonic I use on mobile. I then do "rescanblockchain" but I see no CJ txes once its done, only "Sent to"... What steps were you using exactly to test this?

Probably you did everything right. Implementation has lost 4'/ part which means "coinjoin purpose", somewhere during refactorings and code edits. Should work now as expected, please test again.

Steps to reproduce:

createwallet "NAME" false true "" false true true 
<select wallet>
upgradetohd "MNEMONIC" "" "" false
rescanblockchain 2300000

Copy link

@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: 0

🧹 Nitpick comments (1)
test/functional/wallet_listdescriptors.py (1)

52-53: Test assertions correctly validate the new CoinJoin descriptor.

The assertions properly verify that exactly one descriptor has the internal flag and one has the coinjoin flag, which aligns with the expected structure (3 total descriptors: external, internal, and CoinJoin). Based on learnings, CoinJoin descriptors are intentionally inactive.

For a more concise style, consider using dict.get():

-        assert_equal(1, len([d for d in result['descriptors'] if 'internal' in d and d['internal']]))
-        assert_equal(1, len([d for d in result['descriptors'] if 'coinjoin' in d and d['coinjoin']]))
+        assert_equal(1, len([d for d in result['descriptors'] if d.get('internal')]))
+        assert_equal(1, len([d for d in result['descriptors'] if d.get('coinjoin')]))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9de7f87 and d846c53.

📒 Files selected for processing (2)
  • src/wallet/rpc/backup.cpp (3 hunks)
  • test/functional/wallet_listdescriptors.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/wallet/rpc/backup.cpp
🧰 Additional context used
📓 Path-based instructions (1)
test/functional/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Functional tests should be written in Python and placed in test/functional/

Files:

  • test/functional/wallet_listdescriptors.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.226Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.
🧬 Code graph analysis (1)
test/functional/wallet_listdescriptors.py (1)
test/functional/test_framework/util.py (1)
  • assert_equal (69-74)
🪛 Ruff (0.13.1)
test/functional/wallet_listdescriptors.py

52-52: Unnecessary key check before dictionary access

Replace with dict.get

(RUF019)


53-53: Unnecessary key check before dictionary access

Replace with dict.get

(RUF019)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: x86_64-w64-mingw32 / Build depends
  • GitHub Check: Lint / Run linters
  • GitHub Check: x86_64-apple-darwin / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_multiprocess / Build depends
  • GitHub Check: x86_64-pc-linux-gnu / Build depends
  • GitHub Check: arm-linux-gnueabihf / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_nowallet / Build depends
🔇 Additional comments (2)
test/functional/wallet_listdescriptors.py (2)

49-49: LGTM!

The descriptor count increase from 2 to 3 correctly reflects the addition of the CoinJoin descriptor alongside the existing external and internal descriptors.


51-51: LGTM!

The debug logging helps verify the descriptor output structure during test execution.

Copy link

This pull request has conflicts, please rebase.

@knst knst force-pushed the feat-cj-mobile-derivation-inactive-descriptor branch from d846c53 to 6d64b87 Compare September 30, 2025 14:12
@knst
Copy link
Collaborator Author

knst commented Sep 30, 2025

@knst knst force-pushed the feat-cj-mobile-derivation-inactive-descriptor branch from d846c53 to 6d64b87 September 30, 2025 21:12

rebased due to conflicts with bitcoin backports PR

Copy link

@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: 1

♻️ Duplicate comments (1)
src/wallet/scriptpubkeyman.cpp (1)

2102-2108: CJ path uses wrong coin_type; must be 1 to match Android m/9'/1'/…

Current code builds CJ descriptors with 9'/ExtCoinType'/… (Dash mainnet ExtCoinType=5), which will not match Android’s mobile CoinJoin path m/9'/1'/…. Funds mixed on mobile won’t be detected.

Apply:

-    std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, type == PathDerivationType::DIP0009_CoinJoin ? BIP32_PURPOSE_FEATURE : BIP32_PURPOSE_STANDARD, Params().ExtCoinType());
+    const int coin_type = (type == PathDerivationType::DIP0009_CoinJoin) ? 1 : Params().ExtCoinType();
+    std::string desc_prefix = strprintf("pkh(%s/%d'/%d'", xpub, type == PathDerivationType::DIP0009_CoinJoin ? BIP32_PURPOSE_FEATURE : BIP32_PURPOSE_STANDARD, coin_type);

Also ensure listdescriptors’ CJ detection logic matches this choice.

Based on learnings

🧹 Nitpick comments (4)
test/functional/wallet_mnemonicbits.py (1)

50-50: Consider simplifying the activity assertion.

The current assertion ("coinjoin" not in desc or not desc['coinjoin']) is logically equivalent to not desc.get('coinjoin', False), which is more concise and idiomatic.

Apply this diff for a cleaner assertion:

-                assert_equal(desc['active'], ("coinjoin" not in desc or not desc['coinjoin']))
+                assert_equal(desc['active'], not desc.get('coinjoin', False))
src/wallet/rpc/backup.cpp (1)

1980-1980: Consider clarifying the field description.

The description "defined only if it is True" is technically correct but slightly awkward. Consider rephrasing to match the pattern used for the internal field or be more explicit about the detection criteria.

For example:

-                    {RPCResult::Type::BOOL, "coinjoin", /*optional=*/true, "True if this descriptor is used to generate CoinJoin addresses; defined only if it is True."},
+                    {RPCResult::Type::BOOL, "coinjoin", /*optional=*/true, "True if this descriptor uses the CoinJoin derivation path (m/9'/cointype'/4'/0'). Only present when true."},
test/functional/wallet_listdescriptors.py (1)

49-53: Add a sanity check that CJ descriptor matches the Android path (9'/1')

Counting is fine, but we can catch regressions like coin_type drift by asserting the path. Example:

 result = node.get_wallet_rpc('w3').listdescriptors()
@@
-assert_equal(3, len(result['descriptors']))
+assert_equal(3, len(result['descriptors']))
@@
 self.log.info(f"result: {result['descriptors']}")
 cj = [d for d in result['descriptors'] if d.get('coinjoin')]
 assert_equal(1, len(cj))
+assert "'/9'/1'/" in cj[0]['desc'], "CJ descriptor should use m/9'/1'/… (Android mobile path)"
src/wallet/scriptpubkeyman.h (1)

150-156: Enum addition looks good

Names are clear and scoped; matches prior naming suggestion. Consider a brief doc comment per value for discoverability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d846c53 and 6d64b87.

📒 Files selected for processing (10)
  • doc/release-notes-6835.md (1 hunks)
  • src/wallet/hdchain.cpp (1 hunks)
  • src/wallet/hdchain.h (1 hunks)
  • src/wallet/rpc/backup.cpp (3 hunks)
  • src/wallet/rpc/wallet.cpp (1 hunks)
  • src/wallet/scriptpubkeyman.cpp (2 hunks)
  • src/wallet/scriptpubkeyman.h (2 hunks)
  • src/wallet/wallet.cpp (2 hunks)
  • test/functional/wallet_listdescriptors.py (1 hunks)
  • test/functional/wallet_mnemonicbits.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/wallet/wallet.cpp
  • src/wallet/hdchain.cpp
  • src/wallet/rpc/wallet.cpp
  • src/wallet/scriptpubkeyman.cpp
  • src/wallet/rpc/backup.cpp
  • src/wallet/scriptpubkeyman.h
  • src/wallet/hdchain.h
doc/**

📄 CodeRabbit inference engine (CLAUDE.md)

Unless specifically prompted, avoid making changes to the doc directory (documentation)

Files:

  • doc/release-notes-6835.md
test/functional/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Functional tests should be written in Python and placed in test/functional/

Files:

  • test/functional/wallet_mnemonicbits.py
  • test/functional/wallet_listdescriptors.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.226Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.
📚 Learning: 2025-09-02T07:34:28.226Z
Learnt from: knst
PR: dashpay/dash#6834
File: test/functional/wallet_mnemonicbits.py:50-51
Timestamp: 2025-09-02T07:34:28.226Z
Learning: CJ (CoinJoin) descriptors with derivation path "9'/1" are intentionally inactive in descriptor wallets, while regular internal/external descriptors with different derivation paths remain active.

Applied to files:

  • src/wallet/wallet.cpp
  • doc/release-notes-6835.md
  • src/wallet/scriptpubkeyman.cpp
  • src/wallet/rpc/backup.cpp
🧬 Code graph analysis (6)
src/wallet/hdchain.cpp (2)
src/wallet/hdchain.h (3)
  • BIP32_PURPOSE_STANDARD (147-149)
  • nAccountIndex (131-131)
  • nChangeIndex (132-132)
src/chainparams.cpp (2)
  • Params (1354-1357)
  • Params (1354-1354)
src/wallet/scriptpubkeyman.cpp (2)
src/wallet/hdchain.h (2)
  • BIP32_PURPOSE_FEATURE (145-145)
  • BIP32_PURPOSE_STANDARD (147-149)
src/chainparams.cpp (2)
  • Params (1354-1357)
  • Params (1354-1354)
src/wallet/rpc/backup.cpp (1)
src/wallet/hdchain.h (1)
  • BIP32_PURPOSE_FEATURE (145-145)
test/functional/wallet_mnemonicbits.py (1)
test/functional/test_framework/util.py (1)
  • assert_equal (69-74)
test/functional/wallet_listdescriptors.py (1)
test/functional/test_framework/util.py (1)
  • assert_equal (69-74)
src/wallet/scriptpubkeyman.h (1)
src/wallet/scriptpubkeyman.cpp (2)
  • SetupDescriptorGeneration (2077-2131)
  • SetupDescriptorGeneration (2077-2077)
🔇 Additional comments (9)
src/wallet/hdchain.h (1)

144-148: LGTM! Purpose constants clearly defined.

The new constants for BIP32 purpose codes are well-named, correctly valued (9 for DIP9 CoinJoin, 44 for standard BIP44), and appropriately typed. The comments provide clear context for their usage.

src/wallet/rpc/wallet.cpp (1)

171-171: LGTM! Help text accurately reflects mobile CoinJoin usage.

The updated help text for keypoolsize_hd_internal correctly documents that the internal keypool serves both change outputs and mobile CoinJoin, improving clarity for users.

doc/release-notes-6835.md (2)

21-21: Verify the PR reference number.

The release notes reference "(#6835)" but the PR objectives indicate this is PR #6836. Please confirm the correct PR number.

Based on learnings


1-20: Release notes are clear and comprehensive.

The release notes effectively describe:

  • The mobile CoinJoin compatibility issue and fix
  • The breaking change scope (new wallets only)
  • The RPC updates

The content aligns well with the PR objectives and provides users with the necessary context.

src/wallet/hdchain.cpp (1)

206-207: Verify that the updated key path format is backward compatible.

The key path format changed from an implicit m/44'/%d'/%d'/%d/%d to an explicit m/%d'/%d'/%d'/%d/%d with BIP32_PURPOSE_STANDARD as the first parameter. While this appears to be a display-only function (returns string), ensure that no code parses or relies on the previous format structure.

Run the following script to check for potential parsing or format dependencies:

src/wallet/rpc/backup.cpp (2)

7-7: LGTM!

The include is necessary for accessing Params().ExtCoinType() used in the CoinJoin detection logic below.


2041-2047: Verify the CoinJoin derivation path pattern matches the mobile wallet specification.

The detection pattern uses /%d'/%s'/4'/0' with BIP32_PURPOSE_FEATURE (9) and ExtCoinType(). However, the PR objectives state the mobile CoinJoin derivation path is m/9'/1'/0', not m/9'/cointype'/4'/0'.

Please verify:

  1. The correct mobile CJ derivation path specification
  2. Whether the pattern should include the "4'" component
  3. The format of ExtCoinType() return value (string vs numeric)

Run the following script to check how CoinJoin descriptors are created and what path they use:

Based on learnings: CJ descriptors with derivation path "9'/1" are intentionally inactive, which suggests the path might not include all components in the match pattern.

src/wallet/wallet.cpp (1)

3790-3807: Tri‑descriptor setup with inactive CJ is correct

Looping over External, Internal, CoinJoin and activating only the first two matches the intended behavior for new descriptor wallets (CJ inactive by default).

src/wallet/scriptpubkeyman.h (1)

585-585: API change: confirm all call sites updated

Signature switch to PathDerivationType is good; please verify no remaining uses of the old boolean form.

Comment on lines 52 to +57
# there should 3 descriptors in total
# One of them is inactive imported private key for coinbase. It has no mnemonic
# Two other should be active and have mnemonic
assert_equal(mnemonic_count, 2)
assert_equal(mnemonic_count, 3)
assert_equal(cb_count, 1)
assert_equal(len(descriptors), 3)
assert_equal(len(descriptors), 4)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update comment to reflect three mnemonic descriptors.

The comment states "Two other should be active and have mnemonic" but the code now expects 3 mnemonic descriptors (line 55). The comment should be updated to match the new behavior.

Apply this diff to fix the comment:

-            # there should 3 descriptors in total
-            # One of them is inactive imported private key for coinbase. It has no mnemonic
-            # Two other should be active and have mnemonic
+            # There should be 4 descriptors in total
+            # One is an inactive imported private key for coinbase (no mnemonic)
+            # Three others have mnemonics: two active (internal, external) and one inactive (coinjoin)
             assert_equal(mnemonic_count, 3)
📝 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
# there should 3 descriptors in total
# One of them is inactive imported private key for coinbase. It has no mnemonic
# Two other should be active and have mnemonic
assert_equal(mnemonic_count, 2)
assert_equal(mnemonic_count, 3)
assert_equal(cb_count, 1)
assert_equal(len(descriptors), 3)
assert_equal(len(descriptors), 4)
# There should be 4 descriptors in total
# One is an inactive imported private key for coinbase (no mnemonic)
# Three others have mnemonics: two active (internal, external) and one inactive (coinjoin)
assert_equal(mnemonic_count, 3)
assert_equal(cb_count, 1)
assert_equal(len(descriptors), 4)
🤖 Prompt for AI Agents
In test/functional/wallet_mnemonicbits.py around lines 52 to 57, the inline
comment incorrectly states "Two other should be active and have mnemonic" while
the assertion expects 3 mnemonic descriptors; update the comment to reflect that
there are three mnemonic descriptors (e.g., "Three other should be active and
have mnemonic") so the comment matches the assertion expectations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RPC Some notable changes to RPC params/behaviour/descriptions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants