wallet: Add importdescriptors interface #34861

pull polespinasa wants to merge 3 commits into bitcoin:master from polespinasa:2026-03-19-importdescriptors changing 6 files +436 −243
  1. polespinasa commented at 9:24 AM on March 19, 2026: member

    This PR adds an interface for importing descriptors.

    The motivation behind this is that currently, importing descriptors is only possible via RPC. Bitcoin Core GUI doesn't use the RPC interface so it cannot offer descriptor import functionality, which is needed to support more complex wallet setups such as multisig.

    This PR also adds a refactor by moving the importdescriptors logic from the RPC layer into CWallet::ImportDescriptor, making it reusable by both the RPC and this new interface.

    The main changes are:

    • Introduces CWallet::ImportDescriptor() containing the core import logic, previously embedded in the RPC ProcessDescriptorImport function.
    • Introduces wallet::ImportDescriptorResult, a new result struct that carries success status, error message, warnings, and a FailureReason enum. The RPC layer uses FailureReason to map results back to the appropriate JSON-RPC error codes, keeping RPC concerns out of CWallet.
    • Updates ProcessDescriptorImport in rpc/backup.cpp to delegate to CWallet::ImportDescriptor.
    • Adds interfaces::Wallet::importDescriptors() as a new interface method, allowing the GUI to import descriptors without going through RPC.

    I have a vibe coded PoC commit for how the GUI could consume it here: https://github.com/polespinasa/bitcoin/commit/d1cb120a1106103234a91e693f6e2d3ba653f5eb It might be useful to cherry-pick the commit to test the interface.

  2. DrahtBot commented at 9:24 AM on March 19, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK Sjors, sedited, stickies-v, rkrux, davidgumberg
    Approach ACK w0xlt

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #32861 (Have createwalletdescriptor auto-detect an unused(KEY) by Sjors)
    • #32784 (wallet: derivehdkey RPC to get xpub at arbitrary path by Sjors)
    • #31668 (Added rescan option for import descriptors by saikiran57)
    • #29136 (wallet: addhdkey RPC to add just keys to wallets via new unused(KEY) descriptor by achow101)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • Ranged descrptors should not have a label -> Ranged descriptors should not have a label [Typo: “descrptors” is misspelled, which can momentarily confuse the intended term.]
    • as we didnt import the descriptor in the request -> as we didn’t import the descriptor in the request [Missing apostrophe/grammatical error in the comment (“didnt” → “didn’t”).]

    <sup>2026-04-10 12:57:25</sup>

  3. polespinasa renamed this:
    wallet, refactor, ipc: Add importdescriptors IPC interface
    wallet, ipc: Add importdescriptors IPC interface
    on Mar 19, 2026
  4. polespinasa force-pushed on Mar 19, 2026
  5. DrahtBot added the label CI failed on Mar 19, 2026
  6. DrahtBot removed the label CI failed on Mar 19, 2026
  7. stickies-v commented at 1:57 PM on March 19, 2026: contributor

    currently, importing descriptors is only possible via RPC. This makes it impossible for the GUI to offer descriptor import functionality,

    Why can a GUI not use the RPC interface?

  8. polespinasa commented at 3:03 PM on March 19, 2026: member

    Why can a GUI not use the RPC interface?

    Sorry, a GUI can. I ment Bitcoin Core GUI needs it because it doesn't use RPC. Will rephrase to make it more clear.

  9. Sjors commented at 3:41 PM on March 19, 2026: member

    Concept ACK

    We'll eventually want a way to import descriptors in the GUI, and moving this code out of src/wallet/rpc is a sensible refactor in any case.

    Let's update the PR description to make it clear this isn't an IPC change. Until #10102 lands it's just an interface change, used internally.

  10. sedited commented at 3:49 PM on March 19, 2026: contributor

    Concept ACK

    Makes sense to move this into the wallet interface, but this does indeed have nothing to do with ipc for now.

  11. polespinasa renamed this:
    wallet, ipc: Add importdescriptors IPC interface
    wallet, ipc: Add importdescriptors interface
    on Mar 19, 2026
  12. polespinasa commented at 4:56 PM on March 19, 2026: member

    Updated the title and description to avoid confusion

  13. polespinasa renamed this:
    wallet, ipc: Add importdescriptors interface
    wallet: Add importdescriptors interface
    on Mar 19, 2026
  14. DrahtBot added the label Wallet on Mar 19, 2026
  15. stickies-v commented at 9:52 AM on March 20, 2026: contributor

    Thanks for the clarification, Concept ACK.

  16. w0xlt commented at 8:08 AM on March 22, 2026: contributor

    Approach ACK

  17. in src/wallet/interfaces.cpp:452 in 5e9dab43fd outdated
     447 | +                        r.error = "Rescan aborted by user.";
     448 | +                        r.reason = wallet::ImportDescriptorResult::FailureReason::MISC_ERROR;
     449 | +                    }
     450 | +                }
     451 | +                return response;
     452 | +            }
    


    rxbryan commented at 9:41 PM on March 22, 2026:
    • Adds interfaces::Wallet::importDescriptors() as a new interface method, allowing the GUI to import descriptors without going through RPC.

    Looking at importDescriptors() , it uses the same scaffolding as the RPC path in src/wallet/rpc/backup.cpp, but RescanFromTime is never called when rescan == true. The RPC calls RescanFromTime before ResubmitWalletTransactions, but the interface only calls ResubmitWalletTransactions. Would importing a descriptor with a historical timestamp via this interface scan for past transactions? The IsAbortingRescan() check also appears unreachable as written, no rescan was started.


    polespinasa commented at 10:03 PM on March 22, 2026:

    Yep you are right, good catch thanks! I tried to keep the same structure to make sure the behavior is as close as possible to the RPC one and for some reason I missed that function call.

    Force pushed to add it.


    rxbryan commented at 11:07 PM on March 22, 2026:

    Looks correct.

  18. polespinasa force-pushed on Mar 22, 2026
  19. in src/wallet/wallet.cpp:578 in 1571cdb005
     574 | @@ -575,6 +575,183 @@ void CWallet::UpgradeDescriptorCache()
     575 |      SetWalletFlag(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED);
     576 |  }
     577 |  
     578 | +wallet::ImportDescriptorResult CWallet::ImportDescriptor(const std::string& descriptor,
    


    rkrux commented at 7:42 AM on March 23, 2026:

    In 1571cdb00517a62769f635e077581c2095289646 "refactor: Add ImportDescriptor to CWallet"

    This is close to a 200 line method being added in a file that's already more than 4500 lines long and hard enough to follow - I don't believe we should add more lengthy code in this file anymore. An alternative can be to add this implementation in a separate file while being in the same wallet namespace.

    Something like below in a src/wallet/descriptor.cpp file, this is a starting point and not a full solution. PR #34681 does a simlar refactoring and can be referred to.

    <details open> <summary>Same method impementation but in a different fle</summary>

    #include <wallet/wallet.h>
    
    namespace wallet {
      ImportDescriptorResult CWallet::ImportDescriptor(const std::string& descriptor,
        bool active,
        const std::optional<bool>& internal,
        const std::optional<std::string>& label,
        int64_t timestamp,
        const std::optional<std::pair<int64_t, int64_t>>& range,
        const std::optional<int64_t>& next_idx)
      {
        AssertLockHeld(cs_wallet);
    
        ImportDescriptorResult result;
    
        // Parse descriptor string
        FlatSigningProvider keys;
        std::string error;
        auto parsed_descs = Parse(descriptor, keys, error, /*require_checksum=*/true);
        if (parsed_descs.empty()) {
            result.error = error;
            result.reason = ImportDescriptorResult::FailureReason::INVALID_DESCRIPTOR;
            return result;
        }
        if (internal.has_value() && parsed_descs.size() > 1) {
            result.error = "Cannot have multipath descriptor while also specifying 'internal'";
            result.reason = ImportDescriptorResult::FailureReason::INVALID_DESCRIPTOR;
            return result;
        }
    
        // Range check
        std::optional<bool> is_ranged;
        int64_t range_start = 0, range_end = 1, next_index = 0;
        if (!parsed_descs.at(0)->IsRange() && range.has_value()) {
            result.error = "Range should not be specified for an un-ranged descriptor";
            result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
            return result;
        } else if (parsed_descs.at(0)->IsRange()) {
            if (range.has_value()) {
                range_start = range->first;
                range_end   = range->second + 1;
            } else {
                result.warnings.emplace_back("Range not given, using default keypool range");
                result.used_default_range = true;
                range_start = 0;
                range_end = m_keypool_size;
            }
            next_index  = range_start;
            is_ranged = true;
    
            if (next_idx.has_value()) {
                next_index = next_idx.value();
                if (next_index < range_start || next_index >= range_end) {
                    result.error = "next_index is out of range";
                    result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
                    return result;
                }
            }
        }
    
        // Active descriptors must be ranged
        if (active && !parsed_descs.at(0)->IsRange()) {
            result.error = "Active descriptors must be ranged";
            result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
            return result;
        }
    
        // Multipath descriptors should not have a label
        if (parsed_descs.size() > 1 && label.has_value()) {
            result.error = "Multipath descriptors should not have a label";
            result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
            return result;
        }
    
        // Ranged descrptors should not have a label
        if (is_ranged.has_value() && is_ranged.value() && label.has_value()) {
            result.error = "Ranged descriptors should not have a label";
            result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
            return result;
        }
    
        // Internal descriptors should not have a label
        bool desc_internal = internal.value_or(false);
        if (desc_internal && label.has_value()) {
            result.error = "Internal addresses should not have a label";
            result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
            return result;
        }
    
        //Combo descriptor check
        if (active && !parsed_descs.at(0)->IsSingleType()) {
            result.error = "Combo descriptors cannot be set to active";
            result.reason = ImportDescriptorResult::FailureReason::WALLET_ERROR;
            return result;
        }
    
        // If the wallet disabled private keys, abort if private keys exist
        if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) {
            result.error = "Cannot import private keys to a wallet with private keys disabled";
            result.reason = ImportDescriptorResult::FailureReason::WALLET_ERROR;
            return result;
        }
    
        const std::string label_str = label.value_or(std::string{});
    
        for (size_t j = 0; j < parsed_descs.size(); ++j) {
            auto parsed_desc = std::move(parsed_descs[j]);
            if (parsed_descs.size() == 2) {
                desc_internal = j == 1;
            } else if (parsed_descs.size() > 2) {
                CHECK_NONFATAL(!desc_internal);
            }
            // Need to ExpandPrivate to check if private keys are available for all pubkeys
            FlatSigningProvider expand_keys;
            std::vector<CScript> scripts;
            if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
                result.error = "Cannot expand descriptor. Probably because of hardened derivations without private keys provided";
                result.reason = ImportDescriptorResult::FailureReason::WALLET_ERROR;
                return result;
            }
            parsed_desc->ExpandPrivate(0, keys, expand_keys);
    
            for (const auto& w : parsed_desc->Warnings()) {
                result.warnings.push_back(w);
            }
    
            // Check if all private keys are provided
            bool have_all_privkeys = !expand_keys.keys.empty();
            for (const auto& entry : expand_keys.origins) {
                const CKeyID& key_id = entry.first;
                CKey key;
                if (!expand_keys.GetKey(key_id, key)) {
                    have_all_privkeys = false;
                    break;
                }
            }
    
            // If private keys are enabled, check some things.
            if (!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
                if (keys.keys.empty()) {
                    result.error = "Cannot import descriptor without private keys to a wallet with private keys enabled";
                    result.reason = ImportDescriptorResult::FailureReason::WALLET_ERROR;
                    return result;
                }
                if (!have_all_privkeys) {
                    result.warnings.emplace_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
                }
            }
    
            WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
    
            // Add descriptor to wallet
            auto spk_manager_res = AddWalletDescriptor(w_desc, keys, label_str, desc_internal);
    
            if (!spk_manager_res) {
                result.error = strprintf("Could not add descriptor '%s': %s", descriptor, util::ErrorString(spk_manager_res).original);
                result.reason = ImportDescriptorResult::FailureReason::WALLET_ERROR;
                return result;
            }
    
            auto& spk_manager = spk_manager_res.value().get();
    
            // Set descriptor as active if necessary
            if (active) {
                if (!w_desc.descriptor->GetOutputType()) {
                    result.warnings.emplace_back("Unknown output type, cannot set descriptor to active.");
                } else {
                    AddActiveScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
                }
            } else {
                if (w_desc.descriptor->GetOutputType()) {
                    DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
                }
            }
        }
    
        result.success = true;
        return result;
      }
    }
    

    </details>


    rkrux commented at 1:56 PM on March 24, 2026:

    I've raised an extraction PR #34909 to support this point of mine. It's an attempt to reduce the size of this file.


    achow101 commented at 11:24 PM on March 26, 2026:

    This function does not need to be a member of CWallet. In the interest of not making wallet.cpp any bigger than it already is, I would suggest moving this to a new file, maybe imports.cpp and having it take a CWallet& as an argument.


    davidgumberg commented at 12:55 AM on March 27, 2026:

    One other alternative is to split the stateful part of the function that modifies / queries CWallet from the stateless error parsing stuff


    polespinasa commented at 4:41 PM on April 9, 2026:

    Done :)


    polespinasa commented at 5:08 PM on April 9, 2026:

    Done :)

  20. rkrux changes_requested
  21. rkrux commented at 7:44 AM on March 23, 2026: contributor

    Concept ACK b88462d758e2d24f278607dc5fbd9dcee6c1a076

    I have not reviewed the PR fully but only looked at one portion that was conspicuous in the diff.

  22. davidgumberg commented at 10:39 PM on March 26, 2026: contributor

    Concept ACK, this refactor is a nice win for maintainability, and makes adding importdescriptors to the GUI and/or to an IPC interface or any other interface trivial.

    I could be mistaken as I haven't tried yet to make this change myself, but I think that the RPC code should consume the interface you are adding, because it deduplicates code and increases confidence that the interface added in this PR (which in the current branch is untested) works correctly since we reuse all of our functional tests on it.

    If that's not feasible, the interface commit should probably be held off until the PR that introduces its consumer in the GUI.

  23. in src/wallet/interfaces.cpp:456 in b88462d758 outdated
     451 | +                }
     452 | +                return response;
     453 | +            }
     454 | +        }
     455 | +        return response;
     456 | +    }
    


    davidgumberg commented at 10:56 PM on March 26, 2026:

    Alot of this business logic should be moved out of the interface and into the wallet, the interface functions should probably be as thin as possible.

  24. achow101 commented at 11:22 PM on March 26, 2026: member

    but I think that the RPC code should consume the interface you are adding

    That is not how RPCs access the wallet, and making RPCs access the wallet through this interface is a pretty significant change that is massively outside of the scope of this PR. It would also mean that a bunch of things use the interface, while other things access CWallet directly, which is very weird.


    I think the layout of the commits could be changed to make this easier to review. In general, making a new function that is mostly a copy of another function that will end up replacing the other function makes review more difficult as it does not allow git to show reviewers where things are moved. It also runs the risk of things that are added to the original function not making it into the copy when the PR is rebased, although this is less likely to be an issue for this PR. Since the difference between the 2 functions is primarily in the data types, I would suggest having a first commit that separates ImportDescriptor from ProcessDescriptorImport so that the type changes can be reviewed in that commit. Then having a second commit that moves ImportDescriptor to its new location so that the move diff is minimal.

  25. in src/wallet/interfaces.cpp:388 in b88462d758 outdated
     384 | @@ -373,6 +385,75 @@ class WalletImpl : public Wallet
     385 |      {
     386 |          return m_wallet->FillPSBT(psbtx, complete, sighash_type, sign, bip32derivs, n_signed);
     387 |      }
     388 | +    std::vector<wallet::ImportDescriptorResult> importDescriptors(const std::vector<interfaces::ImportDescriptorRequest>& requests) override
    


    achow101 commented at 11:25 PM on March 26, 2026:

    I don't think most of the logic that this interface function is doing needs to be in the interface. It seems like it could probably be pushed into the underlying ImportDescriptors and the RPC refactored so that it can also use this logic rather than duplicating code.

  26. in src/wallet/wallet.cpp:597 in b88462d758
     592 | +    std::string error;
     593 | +    auto parsed_descs = Parse(descriptor, keys, error, /*require_checksum=*/true);
     594 | +    if (parsed_descs.empty()) {
     595 | +        result.error = error;
     596 | +        result.reason = ImportDescriptorResult::FailureReason::INVALID_DESCRIPTOR;
     597 | +        return result;
    


    achow101 commented at 11:28 PM on March 26, 2026:

    Instead of having errors take up 3 lines, this could be consolidated into a return result.Error() function in a similar way to how ValidationState works.


    polespinasa commented at 5:08 PM on April 9, 2026:

    Nice idea, done! :)

  27. in src/wallet/wallet.cpp:615 in 1571cdb005
     610 | +        result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
     611 | +        return result;
     612 | +    } else if (parsed_descs.at(0)->IsRange()) {
     613 | +        if (range.has_value()) {
     614 | +            range_start = range->first;
     615 | +            range_end   = range->second + 1;
    


    davidgumberg commented at 12:01 AM on March 27, 2026:

    dropped a comment:

                range_end = range->second + 1; // Specified range end is inclusive, but we need range end as exclusive
    

    polespinasa commented at 5:13 PM on April 9, 2026:

    Done

  28. in src/wallet/wallet.cpp:633 in 1571cdb005
     628 | +                result.error = "next_index is out of range";
     629 | +                result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
     630 | +                return result;
     631 | +            }
     632 | +        }
     633 | +    }
    


    davidgumberg commented at 12:18 AM on March 27, 2026:

    can be simplified (the arg also has to be renamed above)

            next_index = next_index_arg.value_or(range_start);
            is_ranged = true;
            if (next_index < range_start || next_index >= range_end) {
                result.error = "next_index is out of range";
                result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
                return result;
            }
    

    polespinasa commented at 5:19 PM on April 9, 2026:

    nice catch, done :)

  29. davidgumberg commented at 12:54 AM on March 27, 2026: contributor

    One approach to simplifying review here is with a prologue commit to make ProcessDescriptorImport and ImportDescriptor more similar, and squashing the commits where ImportDescriptor is added and where it ProcessDescriptorImport is simplified....something like: https://github.com/bitcoin/bitcoin/compare/master...davidgumberg:bitcoin:2026-03-26-34861-squashed-move (https://github.com/bitcoin/bitcoin/commit/e8f2604372df9bba9f5bf22bfcf7243ebb6546b4)

    This makes most lines more obviously moves if you do:

    git show 7eb1e638ae1e7510bdc0e895ca7cc44e20e5deea --color-moved=dimmed-zebra --color-moved-ws=ignore-all-space
    
  30. davidgumberg commented at 2:39 AM on March 27, 2026: contributor

    That is not how RPCs access the wallet, and making RPCs access the wallet through this interface is a pretty significant change that is massively outside of the scope of this PR.

    I don't think the change would be that significant, here is a rough draft as PoC (like the current pr branch some of the business logic of importDescriptors should probably be move out of the interface code)

    https://github.com/davidgumberg/bitcoin/commit/72b781d066cae3734ef30e4bbbbe66f20b500750 (https://github.com/bitcoin/bitcoin/compare/master...davidgumberg:bitcoin:2026-03-26-34861-rpc-use-interface)

    It seems like the best way to be reasonably confident that the importdescriptors of the wallet's RPC interface and the wallet's GUI interface are equivalent, and moves wallet business logic out of the RPC code.

  31. achow101 commented at 1:29 AM on March 28, 2026: member

    I don't think the change would be that significant

    It makes the RPC mix both of the public interfaces for CWallet, which I think is generally bad. Either all of the RPCs should use the CWallet, or they should all use interfaces::Wallet. Not mixing both, and definitely not mixing them within a single RPC. This constraint is less of a technical one and more on principle to not be mixing up how things are being accessed.

  32. in src/wallet/wallet.cpp:606 in b88462d758
     601 | +        result.reason = ImportDescriptorResult::FailureReason::INVALID_DESCRIPTOR;
     602 | +        return result;
     603 | +    }
     604 | +
     605 | +    // Range check
     606 | +    std::optional<bool> is_ranged;
    


    xyzconstant commented at 12:37 PM on April 9, 2026:

    In 1571cdb00517a62769f635e077581c2095289646 "refactor: Add ImportDescriptor to CWallet":

    is_ranged has only 2 possible values: true or nullopt (never false), so std::optional<bool> is a bit misleading.

    A plain bool is_ranged{false} would be clearer and simplifies the check at line 650 to if (is_ranged && label.has_value())


    polespinasa commented at 5:25 PM on April 9, 2026:

    You are right, done! This comes from before this PR. It was done this way in backup.cpp.

  33. in src/wallet/wallet.cpp:607 in b88462d758
     602 | +        return result;
     603 | +    }
     604 | +
     605 | +    // Range check
     606 | +    std::optional<bool> is_ranged;
     607 | +    int64_t range_start = 0, range_end = 1, next_index = 0;
    


    xyzconstant commented at 12:40 PM on April 9, 2026:

    nit: next_idx and next_index in the same scope can be confusing. Either renaming the parameter to requested_next_index or the local to resolved_next_index would make their meanings clearer.


    polespinasa commented at 5:30 PM on April 9, 2026:

    Done! Used next_index_arg I liked it more and saw it in @davidgumberg 's comment: #34861 (review)

  34. polespinasa force-pushed on Apr 9, 2026
  35. polespinasa force-pushed on Apr 9, 2026
  36. DrahtBot added the label CI failed on Apr 9, 2026
  37. polespinasa force-pushed on Apr 9, 2026
  38. polespinasa commented at 4:40 PM on April 9, 2026: member

    Force pushed to move the ImportDescriptor(...) function out of CWallet and into a new file imports.h/cpp as suggested by @achow101 @rkrux and @davidgumberg

    https://github.com/bitcoin/bitcoin/compare/b88462d758e2d24f278607dc5fbd9dcee6c1a076..f6671d9ac5cec296fe20b311369b5a134a52d0fa

  39. polespinasa force-pushed on Apr 9, 2026
  40. polespinasa force-pushed on Apr 9, 2026
  41. polespinasa force-pushed on Apr 9, 2026
  42. polespinasa force-pushed on Apr 9, 2026
  43. in src/interfaces/wallet.h:55 in 7bff033c56 outdated
      51 | @@ -51,6 +52,7 @@ struct WalletContext;
      52 |  namespace interfaces {
      53 |  
      54 |  class Handler;
      55 | +struct ImportDescriptorRequest;
    


    w0xlt commented at 6:16 PM on April 9, 2026:

    ImportDescriptorResult can be defined in the public interface. That makes interfaces::Wallet::importDescriptors() usable from interfaces/wallet.h.

    diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
    index eaf12e620a..83a3b5790b 100644
    --- a/src/interfaces/wallet.h
    +++ b/src/interfaces/wallet.h
    @@ -43,10 +43,32 @@ namespace wallet {
     struct CreatedTransactionResult;
     class CCoinControl;
     class CWallet;
    -struct ImportDescriptorResult;
     enum class AddressPurpose;
     struct CRecipient;
     struct WalletContext;
    +
    +//! Result of importing one descriptor.
    +struct ImportDescriptorResult {
    +    enum class FailureReason {
    +        NONE,
    +        INVALID_DESCRIPTOR,
    +        INVALID_PARAMETER,
    +        WALLET_ERROR,
    +        MISC_ERROR,
    +    };
    +    bool success{false};
    +    bool used_default_range{false};
    +    FailureReason reason{FailureReason::NONE};
    +    std::string error;
    +    std::vector<std::string> warnings;
    +
    +    ImportDescriptorResult& Error(FailureReason r, std::string e)
    +    {
    +        reason = r;
    +        error = std::move(e);
    +        return *this;
    +    }
    +};
     } // namespace wallet
     
     namespace interfaces {
    diff --git a/src/wallet/imports.h b/src/wallet/imports.h
    index fb2e296d5c..7f65afdc6f 100644
    --- a/src/wallet/imports.h
    +++ b/src/wallet/imports.h
    @@ -5,31 +5,10 @@
     #ifndef BITCOIN_WALLET_IMPORTS_H
     #define BITCOIN_WALLET_IMPORTS_H
     
    +#include <interfaces/wallet.h>
     #include <wallet/wallet.h>
     
     namespace wallet{
    -
    -    struct ImportDescriptorResult {
    -        enum class FailureReason {
    -            NONE,
    -            INVALID_DESCRIPTOR,
    -            INVALID_PARAMETER,
    -            WALLET_ERROR,
    -            MISC_ERROR
    -        };
    -        bool success{false};
    -        bool used_default_range{false};
    -        FailureReason reason{FailureReason::NONE};
    -        std::string error;
    -        std::vector<std::string> warnings;
    -
    -        ImportDescriptorResult& Error(FailureReason r, std::string e) {
    -            reason = r;
    -            error = std::move(e);
    -            return *this;
    -        }
    -    };
    -
         ImportDescriptorResult ImportDescriptor(CWallet& wallet, const std::string& descriptor,
             bool active,
             const std::optional<bool>& internal,
    

    polespinasa commented at 6:23 PM on April 9, 2026:

    But then I would need to be accessed by the RPC code. Which I don't have a strong opinion about if that's good or not, but @achow101 and @davidgumberg have been discussing it in this PR (see prior comments).

    In any case I think that rebase is out of the scope for this PR.

  44. DrahtBot removed the label CI failed on Apr 9, 2026
  45. in src/wallet/interfaces.cpp:418 in 7bff033c56 outdated
     413 | +        LOCK(m_wallet->m_relock_mutex);
     414 | +
     415 | +        const int64_t minimum_timestamp = 1;
     416 | +        int64_t now = 0;
     417 | +        int64_t lowest_timestamp = 0;
     418 | +        bool rescan = false;
    


    w0xlt commented at 6:35 PM on April 9, 2026:

    This patch below makes interfaces::Wallet::importDescriptors() consistent with the RPC importdescriptors behavior for locked-wallet imports and incomplete rescans.

    diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
    index 447a098a95..f468f107b3 100644
    --- a/src/wallet/interfaces.cpp
    +++ b/src/wallet/interfaces.cpp
    @@ -4,6 +4,7 @@
     
     #include <interfaces/wallet.h>
     
    +#include <chain.h>
     #include <common/args.h>
     #include <consensus/amount.h>
     #include <interfaces/chain.h>
    @@ -419,6 +420,17 @@ public:
             {
                 LOCK(m_wallet->cs_wallet);
     
    +            if (m_wallet->IsLocked()) {
    +                wallet::ImportDescriptorResult result;
    +                result.success = false;
    +                result.error = "Error: Please enter the wallet passphrase with walletpassphrase first.";
    +                result.reason = wallet::ImportDescriptorResult::FailureReason::WALLET_ERROR;
    +                for (size_t i = 0; i < requests.size(); ++i) {
    +                    response.push_back(result);
    +                }
    +                return response;
    +            }
    +
                 CHECK_NONFATAL(m_wallet->chain().findBlock(m_wallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));
     
                 for (const interfaces::ImportDescriptorRequest& request : requests) {
    @@ -439,7 +451,7 @@ public:
             }
     
             if (rescan) {
    -            m_wallet->RescanFromTime(lowest_timestamp, reserver, /*update=*/true);
    +            const int64_t scanned_time = m_wallet->RescanFromTime(lowest_timestamp, reserver, /*update=*/true);
                 m_wallet->ResubmitWalletTransactions(node::TxBroadcast::MEMPOOL_NO_BROADCAST, /*force=*/true);
     
                 if (m_wallet->IsAbortingRescan()) {
    @@ -453,6 +465,33 @@ public:
                     }
                     return response;
                 }
    +
    +            if (scanned_time > lowest_timestamp) {
    +                for (size_t i = 0; i < requests.size(); ++i) {
    +                    wallet::ImportDescriptorResult& result = response.at(i);
    +                    if (scanned_time <= requests.at(i).timestamp || !result.success) continue;
    +
    +                    result.success = false;
    +                    result.reason = wallet::ImportDescriptorResult::FailureReason::MISC_ERROR;
    +                    result.error = strprintf("Rescan failed for descriptor with timestamp %d. There "
    +                            "was an error reading a block from time %d, which is after or within %d seconds "
    +                            "of key creation, and could contain transactions pertaining to the descriptor. As a "
    +                            "result, transactions and coins using this descriptor may not appear in the wallet.",
    +                            requests.at(i).timestamp, scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW);
    +                    if (m_wallet->chain().havePruned()) {
    +                        result.error += " This error could be caused by pruning or data corruption "
    +                                "(see bitcoind log for details) and could be dealt with by downloading and "
    +                                "rescanning the relevant blocks (see -reindex option and rescanblockchain RPC).";
    +                    } else if (m_wallet->chain().hasAssumedValidChain()) {
    +                        result.error += " This error is likely caused by an in-progress assumeutxo "
    +                                "background sync. Check logs or getchainstates RPC for assumeutxo background "
    +                                "sync progress and try again later.";
    +                    } else {
    +                        result.error += " This error could potentially caused by data corruption. If "
    +                                "the issue persists you may want to reindex (see -reindex option).";
    +                    }
    +                }
    +            }
             }
             return response;
         }
    

    polespinasa commented at 8:57 PM on April 9, 2026:

    You are right, I forgot to include that last part. :)

    I have modified it a bit to make it consistent with the current PR implementation.

  46. polespinasa force-pushed on Apr 9, 2026
  47. polespinasa commented at 9:04 PM on April 9, 2026: member

    Made interfaces::Wallet::importDescriptors() consistent with the RPC importdescriptors behavior for locked-wallet imports and incomplete rescans as suggested in #34861 (review)

    https://github.com/bitcoin/bitcoin/compare/7bff033c566e8293dc8d22ac05402e9cd3fa3a6a..3be7a9a06d5608719ccb3f523cc9ac63b13a1466

  48. refactor: Moves ImportDescriptors to imports.h/cpp
    Moves ImportDescriptor to a new file imports.h/cpp. ImportDescriptor does the same as ProcessDescriptorImport from rpc/backup.cpp,
    but removes the UniValue dependency so it can be used for other interfaces apart from RPC.
    
    RPC importdescriptors is refactored in the next commit to use this new method.
    26cf603b46
  49. refactor: Make importdescriptors rpc use ProcessDescriptorsImport from imports.h/cpp
    Removes the ProcessDescriptorImport in favour or ProcessDescriptorsImport.
    A new function ProcessUniValueDescriptor translates requests from UniValue to ImportDescriptorRequest defined in imports.h
    949a6ccf61
  50. wallet: Add importdescriptors interface 2672bd569a
  51. polespinasa force-pushed on Apr 10, 2026
  52. polespinasa commented at 1:04 PM on April 10, 2026: member

    @achow101 @davidgumberg as suggested I have extracted all the functionality from the wallet interface and moved it into imports.

    It included huge changes in the RPC code in order to use that code too. Changes can be seen in the diff.

    https://github.com/bitcoin/bitcoin/compare/3be7a9a06d5608719ccb3f523cc9ac63b13a1466..2672bd569a34d3abb386912465e4ad4e47d36661

    I will try to work on a new commit structure soon to make it easier to review..


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-13 18:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me