mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-02-04 06:12:07 +01:00
9c7e4771b1test: Test listdescs with priv works even with missing priv keys (Novo)ed945a6854walletrpc: reject listdes with priv key on w-only wallets (Novo)9e5e9824f1descriptor: ToPrivateString() pass if at least 1 priv key exists (Novo)5c4db25b61descriptor: refactor ToPrivateString for providers (Novo)2dc74e3f4ewallet/migration: use HavePrivateKeys in place of ToPrivateString (Novo)e842eb90bbdescriptors: add HavePrivateKeys() (Novo) Pull request description: _TLDR: Currently, `listdescriptors [private=true]` will fail for a non-watch-only wallet if any descriptor has a missing private key(e.g `tr()`, `multi()`, etc.). This PR changes that while making sure `listdescriptors [private=true]` still fails if there no private keys. Closes #32078_ In non-watch-only wallets, it's possible to import descriptors as long as at least one private key is included. It's important that users can still view these descriptors when they need to create a backup—even if some private keys are missing ([#32078 (comment)](https://github.com/bitcoin/bitcoin/issues/32078#issuecomment-2781428475)). This change makes it possible to do so. This change also helps prevent `listdescriptors true` from failing completely, because one descriptor is missing some private keys. ### Notes - The new behaviour is applied to all descriptors including miniscript descriptors - `listdescriptors true` still fails for watch-only wallets to preserve existing behaviour https://github.com/bitcoin/bitcoin/pull/24361#discussion_r920801352 - Wallet migration logic previously used `Descriptor::ToPrivateString()` to determine which descriptor was watchonly. This means that modifying the `ToPrivateString()` behaviour caused descriptors that were previously recognized as "watchonly" to be "non-watchonly". **In order to keep the scope of this PR limited to the RPC behaviour, this PR uses a different method to determine `watchonly` descriptors for the purpose of wallet migration.** A follow-up PR can be opened to update migration logic to exclude descriptors with some private keys from the `watchonly` migration wallet. ### Relevant PRs https://github.com/bitcoin/bitcoin/pull/24361 https://github.com/bitcoin/bitcoin/pull/32186 ### Testing Functional tests were added to test the new behaviour EDIT **`listdescriptors [private=true]` will still fail when there are no private keys because non-watchonly wallets must have private keys and calling `listdescriptors [private=true]` for watchonly wallet returns an error** ACKs for top commit: Sjors: ACK9c7e4771b1achow101: ACK9c7e4771b1w0xlt: reACK9c7e4771b1with minor nits rkrux: re-ACK9c7e4771b1Tree-SHA512: f9b3b2c3e5425a26e158882e39e82e15b7cb13ffbfb6a5fa2868c79526e9b178fcc3cd88d3e2e286f64819d041f687353780bbcf5a355c63a136fb8179698b60
90 lines
4.3 KiB
C++
90 lines
4.3 KiB
C++
// Copyright (c) 2022-present The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <wallet/test/util.h>
|
|
#include <wallet/wallet.h>
|
|
#include <test/util/logging.h>
|
|
#include <test/util/setup_common.h>
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
namespace wallet {
|
|
|
|
BOOST_AUTO_TEST_SUITE(walletload_tests)
|
|
|
|
class DummyDescriptor final : public Descriptor {
|
|
private:
|
|
std::string desc;
|
|
public:
|
|
explicit DummyDescriptor(const std::string& descriptor) : desc(descriptor) {};
|
|
~DummyDescriptor() = default;
|
|
|
|
std::string ToString(bool compat_format) const override { return desc; }
|
|
std::optional<OutputType> GetOutputType() const override { return OutputType::UNKNOWN; }
|
|
|
|
bool IsRange() const override { return false; }
|
|
bool IsSolvable() const override { return false; }
|
|
bool IsSingleType() const override { return true; }
|
|
bool HavePrivateKeys(const SigningProvider&) const override { return false; }
|
|
bool ToPrivateString(const SigningProvider& provider, std::string& out) const override { return false; }
|
|
bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const override { return false; }
|
|
bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache = nullptr) const override { return false; };
|
|
bool ExpandFromCache(int pos, const DescriptorCache& read_cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override { return false; }
|
|
void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const override {}
|
|
std::optional<int64_t> ScriptSize() const override { return {}; }
|
|
std::optional<int64_t> MaxSatisfactionWeight(bool) const override { return {}; }
|
|
std::optional<int64_t> MaxSatisfactionElems() const override { return {}; }
|
|
void GetPubKeys(std::set<CPubKey>& pubkeys, std::set<CExtPubKey>& ext_pubs) const override {}
|
|
std::vector<std::string> Warnings() const override { return {}; }
|
|
};
|
|
|
|
BOOST_FIXTURE_TEST_CASE(wallet_load_descriptors, TestingSetup)
|
|
{
|
|
std::unique_ptr<WalletDatabase> database = CreateMockableWalletDatabase();
|
|
{
|
|
// Write unknown active descriptor
|
|
WalletBatch batch(*database);
|
|
std::string unknown_desc = "trx(tpubD6NzVbkrYhZ4Y4S7m6Y5s9GD8FqEMBy56AGphZXuagajudVZEnYyBahZMgHNCTJc2at82YX6s8JiL1Lohu5A3v1Ur76qguNH4QVQ7qYrBQx/86'/1'/0'/0/*)#8pn8tzdt";
|
|
WalletDescriptor wallet_descriptor(std::make_shared<DummyDescriptor>(unknown_desc), 0, 0, 0, 0);
|
|
BOOST_CHECK(batch.WriteDescriptor(uint256(), wallet_descriptor));
|
|
BOOST_CHECK(batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(OutputType::UNKNOWN), uint256(), false));
|
|
}
|
|
|
|
{
|
|
// Now try to load the wallet and verify the error.
|
|
const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(database)));
|
|
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::UNKNOWN_DESCRIPTOR);
|
|
}
|
|
|
|
// Test 2
|
|
// Now write a valid descriptor with an invalid ID.
|
|
// As the software produces another ID for the descriptor, the loading process must be aborted.
|
|
database = CreateMockableWalletDatabase();
|
|
|
|
// Verify the error
|
|
bool found = false;
|
|
DebugLogHelper logHelper("The descriptor ID calculated by the wallet differs from the one in DB", [&](const std::string* s) {
|
|
found = true;
|
|
return false;
|
|
});
|
|
|
|
{
|
|
// Write valid descriptor with invalid ID
|
|
WalletBatch batch(*database);
|
|
std::string desc = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu";
|
|
WalletDescriptor wallet_descriptor(std::make_shared<DummyDescriptor>(desc), 0, 0, 0, 0);
|
|
BOOST_CHECK(batch.WriteDescriptor(uint256::ONE, wallet_descriptor));
|
|
}
|
|
|
|
{
|
|
// Now try to load the wallet and verify the error.
|
|
const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(database)));
|
|
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::CORRUPT);
|
|
BOOST_CHECK(found); // The error must be logged
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|
|
} // namespace wallet
|