Skip to content

Commit

Permalink
merge bitcoin#19651: importdescriptors update existing
Browse files Browse the repository at this point in the history
  • Loading branch information
kwvg committed Jun 27, 2024
1 parent 1f31823 commit 960e768
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 60 deletions.
13 changes: 6 additions & 7 deletions src/wallet/rpcdump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1750,9 +1750,8 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue&
// Check if the wallet already contains the descriptor
auto existing_spk_manager = pwallet->GetDescriptorScriptPubKeyMan(w_desc);
if (existing_spk_manager) {
LOCK(existing_spk_manager->cs_desc_man);
if (range_start > existing_spk_manager->GetWalletDescriptor().range_start) {
throw JSONRPCError(RPC_INVALID_PARAMS, strprintf("range_start can only decrease; current range = [%d,%d]", existing_spk_manager->GetWalletDescriptor().range_start, existing_spk_manager->GetWalletDescriptor().range_end));
if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, error);
}
}

Expand All @@ -1769,16 +1768,16 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue&
} else {
pwallet->AddActiveScriptPubKeyMan(spk_manager->GetID(), internal);
}
} else {
if (w_desc.descriptor->GetOutputType()) {
pwallet->DeactivateScriptPubKeyMan(spk_manager->GetID(), internal);
}
}

result.pushKV("success", UniValue(true));
} catch (const UniValue& e) {
result.pushKV("success", UniValue(false));
result.pushKV("error", e);
} catch (...) {
result.pushKV("success", UniValue(false));

result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
}
if (warnings.size()) result.pushKV("warnings", warnings);
return result;
Expand Down
40 changes: 40 additions & 0 deletions src/wallet/scriptpubkeyman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2007,6 +2007,12 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
AssertLockHeld(cs_desc_man);
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));

// Check if provided key already exists
if (m_map_keys.find(pubkey.GetID()) != m_map_keys.end() ||
m_map_crypted_keys.find(pubkey.GetID()) != m_map_crypted_keys.end()) {
return true;
}

if (m_storage.HasEncryptionKeys()) {
if (m_storage.IsLocked()) {
return false;
Expand Down Expand Up @@ -2442,3 +2448,37 @@ void DescriptorScriptPubKeyMan::UpgradeDescriptorCache()
throw std::runtime_error(std::string(__func__) + ": writing cache items failed");
}
}

void DescriptorScriptPubKeyMan::UpdateWalletDescriptor(WalletDescriptor& descriptor)
{
LOCK(cs_desc_man);
std::string error;
if (!CanUpdateToWalletDescriptor(descriptor, error)) {
throw std::runtime_error(std::string(__func__) + ": " + error);
}

m_map_pubkeys.clear();
m_map_script_pub_keys.clear();
m_max_cached_index = -1;
m_wallet_descriptor = descriptor;
}

bool DescriptorScriptPubKeyMan::CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error)
{
LOCK(cs_desc_man);
if (!HasWalletDescriptor(descriptor)) {
error = "can only update matching descriptor";
return false;
}

if (descriptor.range_start > m_wallet_descriptor.range_start ||
descriptor.range_end < m_wallet_descriptor.range_end) {
// Use inclusive range for error
error = strprintf("new range must include current range = [%d,%d]",
m_wallet_descriptor.range_start,
m_wallet_descriptor.range_end - 1);
return false;
}

return true;
}
2 changes: 2 additions & 0 deletions src/wallet/scriptpubkeyman.h
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,8 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key);

bool HasWalletDescriptor(const WalletDescriptor& desc) const;
void UpdateWalletDescriptor(WalletDescriptor& descriptor);
bool CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error);
void AddDescriptorKey(const CKey& key, const CPubKey &pubkey);
void WriteDescriptor();

Expand Down
74 changes: 39 additions & 35 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5750,16 +5750,42 @@ void CWallet::AddActiveScriptPubKeyMan(uint256 id, bool internal)

void CWallet::LoadActiveScriptPubKeyMan(uint256 id, bool internal)
{
// Activating ScriptPubKeyManager for a given output and change type is incompatible with legacy wallets.
// Legacy wallets have only one ScriptPubKeyManager and it's active for all output and change types.
Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));

WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(OutputType::LEGACY), static_cast<int>(internal));
auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
auto& spk_mans_other = internal ? m_external_spk_managers : m_internal_spk_managers;
auto spk_man = m_spk_managers.at(id).get();
spk_man->SetInternal(internal);
spk_mans = spk_man;

if (spk_mans_other == spk_man) {
spk_mans_other = nullptr;
}

NotifyCanGetAddressesChanged();

}

void CWallet::DeactivateScriptPubKeyMan(uint256 id, bool internal)
{
auto spk_man = GetScriptPubKeyMan(internal);
if (spk_man != nullptr && spk_man->GetID() == id) {
WalletLogPrintf("Deactivate spkMan: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(OutputType::LEGACY), static_cast<int>(internal));
WalletBatch batch(GetDatabase());
if (!batch.EraseActiveScriptPubKeyMan(internal)) {
throw std::runtime_error(std::string(__func__) + ": erasing active ScriptPubKeyMan id failed");
}

auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
spk_mans = nullptr;
}

NotifyCanGetAddressesChanged();
}

bool CWallet::IsLegacy() const
{
if (m_internal_spk_managers == nullptr) return false;
Expand Down Expand Up @@ -5788,52 +5814,34 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}

LOCK(cs_wallet);
auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));

// If we already have this descriptor, remove it from the maps but add the existing cache to desc
auto old_spk_man = GetDescriptorScriptPubKeyMan(desc);
if (old_spk_man) {
auto spk_man = GetDescriptorScriptPubKeyMan(desc);
if (spk_man) {
WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString());
spk_man->UpdateWalletDescriptor(desc);
} else {
auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
spk_man = new_spk_man.get();

{
LOCK(old_spk_man->cs_desc_man);
new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache);
}

// Remove from maps of active spkMans
auto old_spk_man_id = old_spk_man->GetID();
for (bool internal : {false, true}) {
{ // only one OutputType - LEGACY
auto active_spk_man = GetScriptPubKeyMan(internal);
if (active_spk_man && active_spk_man->GetID() == old_spk_man_id) {
if (internal) {
m_internal_spk_managers = nullptr;
} else {
m_external_spk_managers = nullptr;
}
break;
}
}
}
m_spk_managers.erase(old_spk_man_id);
// Save the descriptor to memory
m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
}

// Add the private keys to the descriptor
for (const auto& entry : signing_provider.keys) {
const CKey& key = entry.second;
new_spk_man->AddDescriptorKey(key, key.GetPubKey());
spk_man->AddDescriptorKey(key, key.GetPubKey());
}

// Top up key pool, the manager will generate new scriptPubKeys internally
if (!new_spk_man->TopUp()) {
if (!spk_man->TopUp()) {
WalletLogPrintf("Could not top up scriptPubKeys\n");
return nullptr;
}

// Apply the label if necessary
// Note: we disable labels for ranged descriptors
if (!desc.descriptor->IsRange()) {
auto script_pub_keys = new_spk_man->GetScriptPubKeys();
auto script_pub_keys = spk_man->GetScriptPubKeys();
if (script_pub_keys.empty()) {
WalletLogPrintf("Could not generate scriptPubKeys (cache is empty)\n");
return nullptr;
Expand All @@ -5845,12 +5853,8 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}
}

// Save the descriptor to memory
auto ret = new_spk_man.get();
m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);

// Save the descriptor to DB
ret->WriteDescriptor();
spk_man->WriteDescriptor();

return ret;
return spk_man;
}
6 changes: 6 additions & 0 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,12 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
//! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
void LoadActiveScriptPubKeyMan(uint256 id, bool internal);

//! Remove specified ScriptPubKeyMan from set of active SPK managers. Writes the change to the wallet file.
//! @param[in] id The unique id for the ScriptPubKeyMan
//! @param[in] type The OutputType this ScriptPubKeyMan provides addresses for
//! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
void DeactivateScriptPubKeyMan(uint256 id, bool internal);

//! Create new DescriptorScriptPubKeyMans and add them to the wallet
void SetupDescriptorScriptPubKeyMans();

Expand Down
6 changes: 6 additions & 0 deletions src/wallet/walletdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,12 @@ bool WalletBatch::WriteActiveScriptPubKeyMan(const uint256& id, bool internal)
return WriteIC(key, id);
}

bool WalletBatch::EraseActiveScriptPubKeyMan(bool internal)
{
const std::string key{internal ? DBKeys::ACTIVEINTERNALSPK : DBKeys::ACTIVEEXTERNALSPK};
return EraseIC(key);
}

bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey)
{
// hash pubkey/privkey to accelerate wallet load
Expand Down
1 change: 1 addition & 0 deletions src/wallet/walletdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ class WalletBatch
bool EraseDestData(const std::string &address, const std::string &key);

bool WriteActiveScriptPubKeyMan(const uint256& id, bool internal);
bool EraseActiveScriptPubKeyMan(bool internal);

DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWalletTx>& vWtx);
Expand Down
Loading

0 comments on commit 960e768

Please sign in to comment.