Skip to content

Menu action to export a watchonly wallet #872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
@@ -310,6 +310,9 @@ class Wallet

//! Return pointer to internal wallet class, useful for testing.
virtual wallet::CWallet* wallet() { return nullptr; }

//! Export a watchonly wallet file. See CWallet::ExportWatchOnlyWallet
virtual util::Result<std::string> exportWatchOnlyWallet(const fs::path& destination) = 0;
};

//! Wallet chain client that in addition to having chain client methods for
11 changes: 11 additions & 0 deletions src/qt/bitcoingui.cpp
Original file line number Diff line number Diff line change
@@ -371,6 +371,10 @@ void BitcoinGUI::createActions()
m_mask_values_action->setStatusTip(tr("Mask the values in the Overview tab"));
m_mask_values_action->setCheckable(true);

m_export_watchonly_action = new QAction(tr("Export watch-only wallet"), this);
m_export_watchonly_action->setEnabled(false);
m_export_watchonly_action->setStatusTip(tr("Export a watch-only version of the current wallet that can be restored onto another node."));

connect(quitAction, &QAction::triggered, this, &BitcoinGUI::quitRequested);
connect(aboutAction, &QAction::triggered, this, &BitcoinGUI::aboutClicked);
connect(aboutQtAction, &QAction::triggered, qApp, QApplication::aboutQt);
@@ -488,6 +492,11 @@ void BitcoinGUI::createActions()
});
connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy);
connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::enableHistoryAction);
connect(m_export_watchonly_action, &QAction::triggered, [this] {
QString destination = GUIUtil::getSaveFileName(this, tr("Save Watch-only Wallet Export"), QString(), QString(), nullptr);
if (destination.isEmpty()) return;
walletFrame->currentWalletModel()->wallet().exportWatchOnlyWallet(GUIUtil::QStringToPath(destination));
});
}
#endif // ENABLE_WALLET

@@ -511,6 +520,7 @@ void BitcoinGUI::createMenuBar()
file->addSeparator();
file->addAction(backupWalletAction);
file->addAction(m_restore_wallet_action);
file->addAction(m_export_watchonly_action);
file->addSeparator();
file->addAction(openAction);
file->addAction(signMessageAction);
@@ -719,6 +729,7 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller, bool s
m_restore_wallet_action->setEnabled(true);
m_migrate_wallet_action->setEnabled(true);
m_migrate_wallet_action->setMenu(m_migrate_wallet_menu);
m_export_watchonly_action->setEnabled(true);

GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet);
1 change: 1 addition & 0 deletions src/qt/bitcoingui.h
Original file line number Diff line number Diff line change
@@ -163,6 +163,7 @@ class BitcoinGUI : public QMainWindow
QAction* m_mask_values_action{nullptr};
QAction* m_migrate_wallet_action{nullptr};
QMenu* m_migrate_wallet_menu{nullptr};
QAction* m_export_watchonly_action{nullptr};

QLabel *m_wallet_selector_label = nullptr;
QComboBox* m_wallet_selector = nullptr;
43 changes: 43 additions & 0 deletions src/script/descriptor.cpp
Original file line number Diff line number Diff line change
@@ -221,6 +221,9 @@ struct PubkeyProvider

/** Make a deep copy of this PubkeyProvider */
virtual std::unique_ptr<PubkeyProvider> Clone() const = 0;

/** Whether this PubkeyProvider can always provide a public key without cache or private key arguments */
virtual bool CanSelfExpand() const = 0;
};

class OriginPubkeyProvider final : public PubkeyProvider
@@ -290,6 +293,7 @@ class OriginPubkeyProvider final : public PubkeyProvider
{
return std::make_unique<OriginPubkeyProvider>(m_expr_index, m_origin, m_provider->Clone(), m_apostrophe);
}
bool CanSelfExpand() const override { return m_provider->CanSelfExpand(); }
};

/** An object representing a parsed constant public key in a descriptor. */
@@ -350,6 +354,7 @@ class ConstPubkeyProvider final : public PubkeyProvider
{
return std::make_unique<ConstPubkeyProvider>(m_expr_index, m_pubkey, m_xonly);
}
bool CanSelfExpand() const final { return true; }
};

enum class DeriveType {
@@ -572,6 +577,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
{
return std::make_unique<BIP32PubkeyProvider>(m_expr_index, m_root_extkey, m_path, m_derive, m_apostrophe);
}
bool CanSelfExpand() const override { return !IsHardened(); }
};

/** Base class for all Descriptor implementations. */
@@ -800,6 +806,7 @@ class AddressDescriptor final : public DescriptorImpl
}
bool IsSingleType() const final { return true; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
bool CanSelfExpand() const final { return true; }

std::optional<int64_t> ScriptSize() const override { return GetScriptForDestination(m_destination).size(); }
std::unique_ptr<DescriptorImpl> Clone() const override
@@ -827,6 +834,7 @@ class RawDescriptor final : public DescriptorImpl
}
bool IsSingleType() const final { return true; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
bool CanSelfExpand() const final { return true; }

std::optional<int64_t> ScriptSize() const override { return m_script.size(); }

@@ -854,6 +862,7 @@ class PKDescriptor final : public DescriptorImpl
public:
PKDescriptor(std::unique_ptr<PubkeyProvider> prov, bool xonly = false) : DescriptorImpl(Vector(std::move(prov)), "pk"), m_xonly(xonly) {}
bool IsSingleType() const final { return true; }
bool CanSelfExpand() const override { return m_pubkey_args[0]->CanSelfExpand(); }

std::optional<int64_t> ScriptSize() const override {
return 1 + (m_xonly ? 32 : m_pubkey_args[0]->GetSize()) + 1;
@@ -889,6 +898,7 @@ class PKHDescriptor final : public DescriptorImpl
PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pkh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; }
bool IsSingleType() const final { return true; }
bool CanSelfExpand() const override { return m_pubkey_args[0]->CanSelfExpand(); }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 1 + 20 + 1 + 1; }

@@ -922,6 +932,7 @@ class WPKHDescriptor final : public DescriptorImpl
WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "wpkh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
bool IsSingleType() const final { return true; }
bool CanSelfExpand() const override { return m_pubkey_args[0]->CanSelfExpand(); }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20; }

@@ -963,6 +974,7 @@ class ComboDescriptor final : public DescriptorImpl
public:
ComboDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "combo") {}
bool IsSingleType() const final { return false; }
bool CanSelfExpand() const override { return m_pubkey_args[0]->CanSelfExpand(); }
std::unique_ptr<DescriptorImpl> Clone() const override
{
return std::make_unique<ComboDescriptor>(m_pubkey_args.at(0)->Clone());
@@ -987,6 +999,13 @@ class MultisigDescriptor final : public DescriptorImpl
public:
MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti" : "multi"), m_threshold(threshold), m_sorted(sorted) {}
bool IsSingleType() const final { return true; }
bool CanSelfExpand() const override {
bool can_expand = true;
for (const auto& key : m_pubkey_args) {
can_expand &= key->CanSelfExpand();
}
return can_expand;
}

std::optional<int64_t> ScriptSize() const override {
const auto n_keys = m_pubkey_args.size();
@@ -1038,6 +1057,13 @@ class MultiADescriptor final : public DescriptorImpl
public:
MultiADescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti_a" : "multi_a"), m_threshold(threshold), m_sorted(sorted) {}
bool IsSingleType() const final { return true; }
bool CanSelfExpand() const override {
bool can_expand = true;
for (const auto& key : m_pubkey_args) {
can_expand &= key->CanSelfExpand();
}
return can_expand;
}

std::optional<int64_t> ScriptSize() const override {
const auto n_keys = m_pubkey_args.size();
@@ -1084,6 +1110,7 @@ class SHDescriptor final : public DescriptorImpl
return OutputType::LEGACY;
}
bool IsSingleType() const final { return true; }
bool CanSelfExpand() const override { return m_subdescriptor_args[0]->CanSelfExpand(); }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20 + 1; }

@@ -1125,6 +1152,7 @@ class WSHDescriptor final : public DescriptorImpl
WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
bool IsSingleType() const final { return true; }
bool CanSelfExpand() const override { return m_subdescriptor_args[0]->CanSelfExpand(); }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }

@@ -1202,6 +1230,13 @@ class TRDescriptor final : public DescriptorImpl
}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
bool IsSingleType() const final { return true; }
bool CanSelfExpand() const override {
bool can_expand = m_pubkey_args[0]->CanSelfExpand();
for (const auto& sub : m_subdescriptor_args) {
can_expand &= sub->CanSelfExpand();
}
return can_expand;
}

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }

@@ -1329,6 +1364,13 @@ class MiniscriptDescriptor final : public DescriptorImpl

bool IsSolvable() const override { return true; }
bool IsSingleType() const final { return true; }
bool CanSelfExpand() const override {
bool can_expand = true;
for (const auto& key : m_pubkey_args) {
can_expand &= key->CanSelfExpand();
}
return can_expand;
}

std::optional<int64_t> ScriptSize() const override { return m_node->ScriptSize(); }

@@ -1368,6 +1410,7 @@ class RawTRDescriptor final : public DescriptorImpl
RawTRDescriptor(std::unique_ptr<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
bool IsSingleType() const final { return true; }
bool CanSelfExpand() const override { return m_pubkey_args[0]->CanSelfExpand(); }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }

3 changes: 3 additions & 0 deletions src/script/descriptor.h
Original file line number Diff line number Diff line change
@@ -117,6 +117,9 @@ struct Descriptor {
/** Convert the descriptor to a normalized string. Normalized descriptors have the xpub at the last hardened step. This fails if the provided provider does not have the private keys to derive that xpub. */
virtual bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const = 0;

/** Whether the descriptor can be used to get more addresses without needing a cache or private keys. */
virtual bool CanSelfExpand() const = 0;

/** Expand a descriptor at a specified position.
*
* @param[in] pos The position at which to expand the descriptor. If IsRange() is false, this is ignored.
11 changes: 7 additions & 4 deletions src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
@@ -249,14 +249,12 @@ class WalletImpl : public Wallet
bool lockCoin(const COutPoint& output, const bool write_to_db) override
{
LOCK(m_wallet->cs_wallet);
std::unique_ptr<WalletBatch> batch = write_to_db ? std::make_unique<WalletBatch>(m_wallet->GetDatabase()) : nullptr;
return m_wallet->LockCoin(output, batch.get());
return m_wallet->LockCoin(output, write_to_db);
}
bool unlockCoin(const COutPoint& output) override
{
LOCK(m_wallet->cs_wallet);
std::unique_ptr<WalletBatch> batch = std::make_unique<WalletBatch>(m_wallet->GetDatabase());
return m_wallet->UnlockCoin(output, batch.get());
return m_wallet->UnlockCoin(output);
}
bool isLockedCoin(const COutPoint& output) override
{
@@ -540,6 +538,11 @@ class WalletImpl : public Wallet
}
CWallet* wallet() override { return m_wallet.get(); }

util::Result<std::string> exportWatchOnlyWallet(const fs::path& destination) override {
LOCK(m_wallet->cs_wallet);
return m_wallet->ExportWatchOnlyWallet(destination, m_context);
}

WalletContext& m_context;
std::shared_ptr<CWallet> m_wallet;
};
37 changes: 4 additions & 33 deletions src/wallet/rpc/backup.cpp
Original file line number Diff line number Diff line change
@@ -503,40 +503,11 @@ RPCHelpMan listdescriptors()
}

LOCK(wallet->cs_wallet);

const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans();

struct WalletDescInfo {
std::string descriptor;
uint64_t creation_time;
bool active;
std::optional<bool> internal;
std::optional<std::pair<int64_t,int64_t>> range;
int64_t next_index;
};

std::vector<WalletDescInfo> wallet_descriptors;
for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) {
const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
if (!desc_spk_man) {
throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type.");
}
LOCK(desc_spk_man->cs_desc_man);
const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
std::string descriptor;
if (!desc_spk_man->GetDescriptorString(descriptor, priv)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string.");
}
const bool is_range = wallet_descriptor.descriptor->IsRange();
wallet_descriptors.push_back({
descriptor,
wallet_descriptor.creation_time,
active_spk_mans.count(desc_spk_man) != 0,
wallet->IsInternalScriptPubKeyMan(desc_spk_man),
is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt,
wallet_descriptor.next_index
});
util::Result<std::vector<WalletDescInfo>> exported = wallet->ExportDescriptors(priv);
if (!exported) {
throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(exported).original);
}
std::vector<WalletDescInfo> wallet_descriptors = *exported;

std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
return a.descriptor < b.descriptor;
8 changes: 2 additions & 6 deletions src/wallet/rpc/coins.cpp
Original file line number Diff line number Diff line change
@@ -356,16 +356,12 @@ RPCHelpMan lockunspent()
outputs.push_back(outpt);
}

std::unique_ptr<WalletBatch> batch = nullptr;
// Unlock is always persistent
if (fUnlock || persistent) batch = std::make_unique<WalletBatch>(pwallet->GetDatabase());

// Atomically set (un)locked status for the outputs.
for (const COutPoint& outpt : outputs) {
if (fUnlock) {
if (!pwallet->UnlockCoin(outpt, batch.get())) throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coin failed");
if (!pwallet->UnlockCoin(outpt)) throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coin failed");
} else {
if (!pwallet->LockCoin(outpt, batch.get())) throw JSONRPCError(RPC_WALLET_ERROR, "Locking coin failed");
if (!pwallet->LockCoin(outpt, persistent)) throw JSONRPCError(RPC_WALLET_ERROR, "Locking coin failed");
}
}

2 changes: 1 addition & 1 deletion src/wallet/rpc/spend.cpp
Original file line number Diff line number Diff line change
@@ -1582,7 +1582,7 @@ RPCHelpMan sendall()
const bool lock_unspents{options.exists("lock_unspents") ? options["lock_unspents"].get_bool() : false};
if (lock_unspents) {
for (const CTxIn& txin : rawTx.vin) {
pwallet->LockCoin(txin.prevout);
pwallet->LockCoin(txin.prevout, /*persist=*/false);
}
}

5 changes: 4 additions & 1 deletion src/wallet/rpc/util.cpp
Original file line number Diff line number Diff line change
@@ -109,7 +109,10 @@ void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey,
{
UniValue parent_descs(UniValue::VARR);
for (const auto& desc: wallet.GetWalletDescriptors(script_pubkey)) {
parent_descs.push_back(desc.descriptor->ToString());
std::string desc_str;
FlatSigningProvider dummy_provider;
if (!CHECK_NONFATAL(desc.descriptor->ToNormalizedString(dummy_provider, desc_str, &desc.cache))) continue;
parent_descs.push_back(desc_str);
}
entry.pushKV("parent_descs", std::move(parent_descs));
}
Loading