Repository: bitcoin/bitcoin
File: src/script/descriptor.cpp (C++)

Code before:
        PRIVATE,
        NORMALIZED,
        COMPAT, // string calculation that mustn't change over time to stay compatible with previous software versions
    };

    // NOLINTNEXTLINE(misc-no-recursion)
    bool IsSolvable() const override
    {
        for (const auto& arg : m_subdescriptor_args) {
            if (!arg->IsSolvable()) return false;
        }
        return true;
    }

    bool IsWatchOnly(const SigningProvider& arg) const override
    {
        size_t privkey_count{0};
        std::set<CPubKey> pubkeys;
        std::set<CExtPubKey> ext_pubkeys;
        this->GetPubKeys(pubkeys, ext_pubkeys);
        auto output_type{this->GetOutputType()};
        for (auto pubkey : pubkeys) {
            CKey key;
            if (arg.GetKey(pubkey.GetID(), key)) {
                privkey_count += 1;
            } else if (output_type.has_value() && *output_type == OutputType::BECH32M && arg.GetKeyByXOnly(XOnlyPubKey(pubkey), key)) {
                privkey_count += 1;
            }
        }
        for (auto extpubkey : ext_pubkeys) {
            CKey dummy;
            if (arg.GetKey(extpubkey.pubkey.GetID(), dummy)) {
                privkey_count += 1;
            }
        }
        size_t pubkey_count{pubkeys.size() + ext_pubkeys.size()};
        return (privkey_count == 0 || pubkey_count > privkey_count);
    }

    // NOLINTNEXTLINE(misc-no-recursion)
    bool IsRange() const final
    {
        for (const auto& pubkey : m_pubkey_args) {
            if (pubkey->IsRange()) return true;
        }
        for (const auto& arg : m_subdescriptor_args) {
            if (arg->IsRange()) return true;
        }
        return false;
    }


Review comment (refactor):
In https://github.com/bitcoin/bitcoin/commit/2c0a84f1e2ebe98bcf5066acb0de0ba5138c2101 descriptors: add IsWatchOnly(): since c++17 you can safely compare std::optional<T> with T, see item 21 here: https://en.cppreference.com/w/cpp/utility/optional/operator_cmp.html

I also find it easier to read if `privkey_count` and `pubkey_count` are tracked in the same manner, but feel free to ignore:

```diff
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 98ae29ceb3..a86b4743b3 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -628,26 +628,27 @@ public:

     bool IsWatchOnly(const SigningProvider& arg) const override
     {
+        size_t pubkey_count{0};
         size_t privkey_count{0};
         std::set<CPubKey> pubkeys;
         std::set<CExtPubKey> ext_pubkeys;
         this->GetPubKeys(pubkeys, ext_pubkeys);
-        auto output_type{this->GetOutputType()};
-        for (auto pubkey : pubkeys) {
-            CKey key;
-            if (arg.GetKey(pubkey.GetID(), key)) {
+        for (const CPubKey pubkey : pubkeys) {
+            pubkey_count++;
+            CKey dummy;
+            if (arg.GetKey(pubkey.GetID(), dummy)) {
                 privkey_count += 1;
-            } else if (output_type.has_value() && *output_type == OutputType::BECH32M && arg.GetKeyByXOnly(XOnlyPubKey(pubkey), key)) {
+            } else if (this->GetOutputType() == OutputType::BECH32M && arg.GetKeyByXOnly(XOnlyPubKey(pubkey), dummy)) {
                 privkey_count += 1;
             }
         }
-        for (auto extpubkey : ext_pubkeys) {
+        for (const CExtPubKey extpubkey : ext_pubkeys) {
+            pubkey_count++;
             CKey dummy;
             if (arg.GetKey(extpubkey.pubkey.GetID(), dummy)) {
                 privkey_count += 1;
             }
         }
-        size_t pubkey_count{pubkeys.size() + ext_pubkeys.size()};
         return (privkey_count == 0 || pubkey_count > privkey_count);
     }
```


Code after:
        DeriveType derive
    )
        : PubkeyProvider(exp_index),
        m_participants(std::move(providers)),
        m_path(std::move(path)),
        m_derive(derive),
        m_ranged_participants(std::any_of(m_participants.begin(), m_participants.end(), [](const auto& pubkey) { return pubkey->IsRange(); }))
    {
        if (!Assume(!(m_ranged_participants && IsRangedDerivation()))) {
            throw std::runtime_error("musig(): Cannot have both ranged participants and ranged derivation");
        }
        if (!Assume(m_derive != DeriveType::HARDENED)) {
            throw std::runtime_error("musig(): Cannot have hardened derivation");
        }
    }

    std::optional<CPubKey> GetPubKey(int pos, const SigningProvider& arg, FlatSigningProvider& out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
    {
        FlatSigningProvider dummy;
        // If the participants are not ranged, we can compute and cache the aggregate pubkey by creating a PubkeyProvider for it
        if (!m_aggregate_provider && !m_ranged_participants) {
            // Retrieve the pubkeys from the providers
            std::vector<CPubKey> pubkeys;
            for (const auto& prov : m_participants) {
                std::optional<CPubKey> pubkey = prov->GetPubKey(0, arg, dummy, read_cache, write_cache);
                if (!pubkey.has_value()) {
                    return std::nullopt;
                }
                pubkeys.push_back(pubkey.value());
            }
            std::sort(pubkeys.begin(), pubkeys.end());

            // Aggregate the pubkey
            m_aggregate_pubkey = MuSig2AggregatePubkeys(pubkeys);
            if (!Assume(m_aggregate_pubkey.has_value())) return std::nullopt;

            // Make our pubkey provider
            if (IsRangedDerivation() || !m_path.empty()) {
                // Make the synthetic xpub and construct the BIP32PubkeyProvider
                CExtPubKey extpub = CreateMuSig2SyntheticXpub(m_aggregate_pubkey.value());
                m_aggregate_provider = std::make_unique<BIP32PubkeyProvider>(m_expr_index, extpub, m_path, m_derive, /*apostrophe=*/false);
            } else {
                m_aggregate_provider = std::make_unique<ConstPubkeyProvider>(m_expr_index, m_aggregate_pubkey.value(), /*xonly=*/false);
            }
        }

        // Retrieve all participant pubkeys
        std::vector<CPubKey> pubkeys;
        for (const auto& prov : m_participants) {
            std::optional<CPubKey> pub = prov->GetPubKey(pos, arg, out, read_cache, write_cache);
            if (!pub) return std::nullopt;