Skip to content

Commit 045bb06

Browse files
committed
Merge bitcoin/bitcoin#19651: wallet: importdescriptors update existing
3efaf83 wallet: deactivate descriptor (S3RK) 6737d96 test: wallet importdescriptors update existing (S3RK) 586f1d5 wallet: maintain SPK consistency on internal flag change (S3RK) f1b7db1 wallet: don't mute exceptions in importdescriptors (S3RK) bf68ebc wallet: allow to import same descriptor twice (S3RK) Pull request description: Rationale: allow updating existing descriptors with `importdescriptors` command. Currently if you run same `importdescriptors` command twice with a descriptor containing private key you will get very confusing error — `Missing required fields`. What happens is that Wallet tries to write imported private key to the disk, but it exists already so we get `DB_KEYEXIST (-30995)` from BerkelyDB. Please note, that we set `DB_NOOVERWRITE` (I guess not to lose some keys accidentally). The exception is caught in `catch (...)` in rpcdump.cpp with a generic error. With this PR if a descriptor is already present than we will update its activeness, internalness, label, range and next_index. For the range only expansion is allowed (range start can only decrease, range end increase). ACKs for top commit: achow101: re-ACK 3efaf83 meshcollider: Code review ACK 3efaf83 jonatack: Light ACK 3efaf83 per `git range-diff a000cb0 5d96704 3efaf83` and as a sanity check, re-debug-built on debian with gcc 10.2.1 and clang 11, ran wallet_importdescriptors.py Tree-SHA512: 122c4b621d64ec8a3b625f3aed9f01a2b5cbaf2029ad0325b5ff38d67fff5cd35324335fabe2dd5169548b01b267c81be6ae0f5c834342f3d5f6eeed515c4843
2 parents 722776c + 3efaf83 commit 045bb06

File tree

8 files changed

+241
-60
lines changed

8 files changed

+241
-60
lines changed

src/wallet/rpcdump.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,9 +1566,8 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
15661566
// Check if the wallet already contains the descriptor
15671567
auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc);
15681568
if (existing_spk_manager) {
1569-
LOCK(existing_spk_manager->cs_desc_man);
1570-
if (range_start > existing_spk_manager->GetWalletDescriptor().range_start) {
1571-
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));
1569+
if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) {
1570+
throw JSONRPCError(RPC_INVALID_PARAMETER, error);
15721571
}
15731572
}
15741573

@@ -1585,16 +1584,16 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
15851584
} else {
15861585
wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
15871586
}
1587+
} else {
1588+
if (w_desc.descriptor->GetOutputType()) {
1589+
wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
1590+
}
15881591
}
15891592

15901593
result.pushKV("success", UniValue(true));
15911594
} catch (const UniValue& e) {
15921595
result.pushKV("success", UniValue(false));
15931596
result.pushKV("error", e);
1594-
} catch (...) {
1595-
result.pushKV("success", UniValue(false));
1596-
1597-
result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
15981597
}
15991598
if (warnings.size()) result.pushKV("warnings", warnings);
16001599
return result;

src/wallet/scriptpubkeyman.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,12 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
18511851
AssertLockHeld(cs_desc_man);
18521852
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
18531853

1854+
// Check if provided key already exists
1855+
if (m_map_keys.find(pubkey.GetID()) != m_map_keys.end() ||
1856+
m_map_crypted_keys.find(pubkey.GetID()) != m_map_crypted_keys.end()) {
1857+
return true;
1858+
}
1859+
18541860
if (m_storage.HasEncryptionKeys()) {
18551861
if (m_storage.IsLocked()) {
18561862
return false;
@@ -2304,3 +2310,37 @@ void DescriptorScriptPubKeyMan::UpgradeDescriptorCache()
23042310
throw std::runtime_error(std::string(__func__) + ": writing cache items failed");
23052311
}
23062312
}
2313+
2314+
void DescriptorScriptPubKeyMan::UpdateWalletDescriptor(WalletDescriptor& descriptor)
2315+
{
2316+
LOCK(cs_desc_man);
2317+
std::string error;
2318+
if (!CanUpdateToWalletDescriptor(descriptor, error)) {
2319+
throw std::runtime_error(std::string(__func__) + ": " + error);
2320+
}
2321+
2322+
m_map_pubkeys.clear();
2323+
m_map_script_pub_keys.clear();
2324+
m_max_cached_index = -1;
2325+
m_wallet_descriptor = descriptor;
2326+
}
2327+
2328+
bool DescriptorScriptPubKeyMan::CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error)
2329+
{
2330+
LOCK(cs_desc_man);
2331+
if (!HasWalletDescriptor(descriptor)) {
2332+
error = "can only update matching descriptor";
2333+
return false;
2334+
}
2335+
2336+
if (descriptor.range_start > m_wallet_descriptor.range_start ||
2337+
descriptor.range_end < m_wallet_descriptor.range_end) {
2338+
// Use inclusive range for error
2339+
error = strprintf("new range must include current range = [%d,%d]",
2340+
m_wallet_descriptor.range_start,
2341+
m_wallet_descriptor.range_end - 1);
2342+
return false;
2343+
}
2344+
2345+
return true;
2346+
}

src/wallet/scriptpubkeyman.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,8 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
624624
bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key);
625625

626626
bool HasWalletDescriptor(const WalletDescriptor& desc) const;
627+
void UpdateWalletDescriptor(WalletDescriptor& descriptor);
628+
bool CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error);
627629
void AddDescriptorKey(const CKey& key, const CPubKey &pubkey);
628630
void WriteDescriptor();
629631

src/wallet/wallet.cpp

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3168,12 +3168,38 @@ void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool interna
31683168

31693169
void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal)
31703170
{
3171+
// Activating ScriptPubKeyManager for a given output and change type is incompatible with legacy wallets.
3172+
// Legacy wallets have only one ScriptPubKeyManager and it's active for all output and change types.
3173+
Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
3174+
31713175
WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
31723176
auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
3177+
auto& spk_mans_other = internal ? m_external_spk_managers : m_internal_spk_managers;
31733178
auto spk_man = m_spk_managers.at(id).get();
31743179
spk_man->SetInternal(internal);
31753180
spk_mans[type] = spk_man;
31763181

3182+
if (spk_mans_other[type] == spk_man) {
3183+
spk_mans_other[type] = nullptr;
3184+
}
3185+
3186+
NotifyCanGetAddressesChanged();
3187+
}
3188+
3189+
void CWallet::DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal)
3190+
{
3191+
auto spk_man = GetScriptPubKeyMan(type, internal);
3192+
if (spk_man != nullptr && spk_man->GetID() == id) {
3193+
WalletLogPrintf("Deactivate spkMan: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
3194+
WalletBatch batch(GetDatabase());
3195+
if (!batch.EraseActiveScriptPubKeyMan(static_cast<uint8_t>(type), internal)) {
3196+
throw std::runtime_error(std::string(__func__) + ": erasing active ScriptPubKeyMan id failed");
3197+
}
3198+
3199+
auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
3200+
spk_mans[type] = nullptr;
3201+
}
3202+
31773203
NotifyCanGetAddressesChanged();
31783204
}
31793205

@@ -3207,52 +3233,34 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
32073233
}
32083234

32093235
LOCK(cs_wallet);
3210-
auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
3211-
3212-
// If we already have this descriptor, remove it from the maps but add the existing cache to desc
3213-
auto old_spk_man = GetDescriptorScriptPubKeyMan(desc);
3214-
if (old_spk_man) {
3236+
auto spk_man = GetDescriptorScriptPubKeyMan(desc);
3237+
if (spk_man) {
32153238
WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString());
3239+
spk_man->UpdateWalletDescriptor(desc);
3240+
} else {
3241+
auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
3242+
spk_man = new_spk_man.get();
32163243

3217-
{
3218-
LOCK(old_spk_man->cs_desc_man);
3219-
new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache);
3220-
}
3221-
3222-
// Remove from maps of active spkMans
3223-
auto old_spk_man_id = old_spk_man->GetID();
3224-
for (bool internal : {false, true}) {
3225-
for (OutputType t : OUTPUT_TYPES) {
3226-
auto active_spk_man = GetScriptPubKeyMan(t, internal);
3227-
if (active_spk_man && active_spk_man->GetID() == old_spk_man_id) {
3228-
if (internal) {
3229-
m_internal_spk_managers.erase(t);
3230-
} else {
3231-
m_external_spk_managers.erase(t);
3232-
}
3233-
break;
3234-
}
3235-
}
3236-
}
3237-
m_spk_managers.erase(old_spk_man_id);
3244+
// Save the descriptor to memory
3245+
m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
32383246
}
32393247

32403248
// Add the private keys to the descriptor
32413249
for (const auto& entry : signing_provider.keys) {
32423250
const CKey& key = entry.second;
3243-
new_spk_man->AddDescriptorKey(key, key.GetPubKey());
3251+
spk_man->AddDescriptorKey(key, key.GetPubKey());
32443252
}
32453253

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

32523260
// Apply the label if necessary
32533261
// Note: we disable labels for ranged descriptors
32543262
if (!desc.descriptor->IsRange()) {
3255-
auto script_pub_keys = new_spk_man->GetScriptPubKeys();
3263+
auto script_pub_keys = spk_man->GetScriptPubKeys();
32563264
if (script_pub_keys.empty()) {
32573265
WalletLogPrintf("Could not generate scriptPubKeys (cache is empty)\n");
32583266
return nullptr;
@@ -3264,12 +3272,8 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
32643272
}
32653273
}
32663274

3267-
// Save the descriptor to memory
3268-
auto ret = new_spk_man.get();
3269-
m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
3270-
32713275
// Save the descriptor to DB
3272-
ret->WriteDescriptor();
3276+
spk_man->WriteDescriptor();
32733277

3274-
return ret;
3278+
return spk_man;
32753279
}

src/wallet/wallet.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,12 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
899899
//! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
900900
void LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal);
901901

902+
//! Remove specified ScriptPubKeyMan from set of active SPK managers. Writes the change to the wallet file.
903+
//! @param[in] id The unique id for the ScriptPubKeyMan
904+
//! @param[in] type The OutputType this ScriptPubKeyMan provides addresses for
905+
//! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
906+
void DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal);
907+
902908
//! Create new DescriptorScriptPubKeyMans and add them to the wallet
903909
void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
904910

src/wallet/walletdb.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ bool WalletBatch::WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bo
210210
return WriteIC(make_pair(key, type), id);
211211
}
212212

213+
bool WalletBatch::EraseActiveScriptPubKeyMan(uint8_t type, bool internal)
214+
{
215+
const std::string key{internal ? DBKeys::ACTIVEINTERNALSPK : DBKeys::ACTIVEEXTERNALSPK};
216+
return EraseIC(make_pair(key, type));
217+
}
218+
213219
bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey)
214220
{
215221
// hash pubkey/privkey to accelerate wallet load

src/wallet/walletdb.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ class WalletBatch
255255
bool EraseDestData(const std::string &address, const std::string &key);
256256

257257
bool WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bool internal);
258+
bool EraseActiveScriptPubKeyMan(uint8_t type, bool internal);
258259

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

0 commit comments

Comments
 (0)