Skip to content

Commit b8cefeb

Browse files
committed
Merge bitcoin/bitcoin#32149: wallet, migration: Fix empty wallet crash
0f602c5 wallet, migration: Fix crash on empty wallet (pablomartin4btc) 42c1314 wallet, refactor: Decouple into HasLegacyRecords() (pablomartin4btc) Pull request description: Same as with a blank wallet (#28976), wallets with no legacy records (i.e. empty, non-blank, watch-only wallet) do not require to be migrated. Steps to reproduce the issue: 1.- `createwallet "empty_wo_noblank_legacy_wallet" true false "" false false` 2.- `migratewallet` ``` wallet/wallet.cpp:4071 GetDescriptorsForLegacy: Assertion `legacy_spkm' failed. Aborted (core dumped) ``` ACKs for top commit: davidgumberg: untested reACK bitcoin/bitcoin@0f602c5 fjahr: re-ACK 0f602c5 achow101: ACK 0f602c5 furszy: ACK 0f602c5 BrandonOdiwuor: Code Review ACK 0f602c5 Tree-SHA512: 796c3f0b1946281097f7ffc3563bc79f879e80a98237012535cc530a4a2239fd2d71a17b4f54e30258886dc9f0b83206d7a5d50312e4fc6d0abe4f559fbe07ec
2 parents 874da96 + 0f602c5 commit b8cefeb

File tree

5 files changed

+51
-20
lines changed

5 files changed

+51
-20
lines changed

src/wallet/test/walletdb_tests.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,14 @@ BOOST_AUTO_TEST_CASE(walletdb_read_write_deadlock)
4747
// Create legacy spkm
4848
LOCK(wallet->cs_wallet);
4949
auto legacy_spkm = wallet->GetOrCreateLegacyScriptPubKeyMan();
50+
BOOST_CHECK(!HasLegacyRecords(*wallet));
5051
BOOST_CHECK(legacy_spkm->SetupGeneration(true));
52+
BOOST_CHECK(HasLegacyRecords(*wallet));
5153
wallet->Flush();
5254

5355
// Now delete all records, which performs a read write operation.
5456
BOOST_CHECK(wallet->GetLegacyScriptPubKeyMan()->DeleteRecords());
57+
BOOST_CHECK(!HasLegacyRecords(*wallet));
5558
}
5659
}
5760

src/wallet/wallet.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4534,13 +4534,13 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
45344534
// First change to using SQLite
45354535
if (!local_wallet->MigrateToSQLite(error)) return util::Error{error};
45364536

4537-
// Do the migration of keys and scripts for non-blank wallets, and cleanup if it fails
4538-
success = local_wallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
4539-
if (!success) {
4537+
// Do the migration of keys and scripts for non-empty wallets, and cleanup if it fails
4538+
if (HasLegacyRecords(*local_wallet)) {
45404539
success = DoMigration(*local_wallet, context, error, res);
45414540
} else {
45424541
// Make sure that descriptors flag is actually set
45434542
local_wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
4543+
success = true;
45444544
}
45454545
}
45464546

src/wallet/walletdb.cpp

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -540,30 +540,45 @@ static LoadResult LoadRecords(CWallet* pwallet, DatabaseBatch& batch, const std:
540540
return LoadRecords(pwallet, batch, key, prefix, load_func);
541541
}
542542

543+
bool HasLegacyRecords(CWallet& wallet)
544+
{
545+
const auto& batch = wallet.GetDatabase().MakeBatch();
546+
return HasLegacyRecords(wallet, *batch);
547+
}
548+
549+
bool HasLegacyRecords(CWallet& wallet, DatabaseBatch& batch)
550+
{
551+
for (const auto& type : DBKeys::LEGACY_TYPES) {
552+
DataStream key;
553+
DataStream value{};
554+
DataStream prefix;
555+
556+
prefix << type;
557+
std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);
558+
if (!cursor) {
559+
// Could only happen on a closed db, which means there is an error in the code flow.
560+
wallet.WalletLogPrintf("Error getting database cursor for '%s' records", type);
561+
throw std::runtime_error(strprintf("Error getting database cursor for '%s' records", type));
562+
}
563+
564+
DatabaseCursor::Status status = cursor->Next(key, value);
565+
if (status != DatabaseCursor::Status::DONE) {
566+
return true;
567+
}
568+
}
569+
return false;
570+
}
571+
543572
static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch, int last_client) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
544573
{
545574
AssertLockHeld(pwallet->cs_wallet);
546575
DBErrors result = DBErrors::LOAD_OK;
547576

548577
// Make sure descriptor wallets don't have any legacy records
549578
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
550-
for (const auto& type : DBKeys::LEGACY_TYPES) {
551-
DataStream key;
552-
DataStream value{};
553-
554-
DataStream prefix;
555-
prefix << type;
556-
std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);
557-
if (!cursor) {
558-
pwallet->WalletLogPrintf("Error getting database cursor for '%s' records\n", type);
559-
return DBErrors::CORRUPT;
560-
}
561-
562-
DatabaseCursor::Status status = cursor->Next(key, value);
563-
if (status != DatabaseCursor::Status::DONE) {
564-
pwallet->WalletLogPrintf("Error: Unexpected legacy entry found in descriptor wallet %s. The wallet might have been tampered with or created with malicious intent.\n", pwallet->GetName());
565-
return DBErrors::UNEXPECTED_LEGACY_ENTRY;
566-
}
579+
if (HasLegacyRecords(*pwallet, batch)) {
580+
pwallet->WalletLogPrintf("Error: Unexpected legacy entry found in descriptor wallet %s. The wallet might have been tampered with or created with malicious intent.\n", pwallet->GetName());
581+
return DBErrors::UNEXPECTED_LEGACY_ENTRY;
567582
}
568583

569584
return DBErrors::LOAD_OK;

src/wallet/walletdb.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,10 @@ bool LoadKey(CWallet* pwallet, DataStream& ssKey, DataStream& ssValue, std::stri
333333
bool LoadCryptedKey(CWallet* pwallet, DataStream& ssKey, DataStream& ssValue, std::string& strErr);
334334
bool LoadEncryptionKey(CWallet* pwallet, DataStream& ssKey, DataStream& ssValue, std::string& strErr);
335335
bool LoadHDChain(CWallet* pwallet, DataStream& ssValue, std::string& strErr);
336+
337+
//! Returns true if there are any DBKeys::LEGACY_TYPES record in the wallet db
338+
bool HasLegacyRecords(CWallet& wallet);
339+
bool HasLegacyRecords(CWallet& wallet, DatabaseBatch& batch);
336340
} // namespace wallet
337341

338342
#endif // BITCOIN_WALLET_WALLETDB_H

test/functional/wallet_migration.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,15 @@ def test_no_privkeys(self):
445445
# After migrating, the "keypool" is empty
446446
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", watchonly1.getnewaddress)
447447

448+
self.log.info("Test migration of a watch-only empty wallet")
449+
for idx, is_blank in enumerate([True, False], start=1):
450+
wallet_name = f"watchonly_empty{idx}"
451+
self.create_legacy_wallet(wallet_name, disable_private_keys=True, blank=is_blank)
452+
_, watchonly_empty = self.migrate_and_get_rpc(wallet_name)
453+
info = watchonly_empty.getwalletinfo()
454+
assert_equal(info["private_keys_enabled"], False)
455+
assert_equal(info["blank"], is_blank)
456+
448457
def test_pk_coinbases(self):
449458
self.log.info("Test migration of a wallet using old pk() coinbases")
450459
wallet = self.create_legacy_wallet("pkcb")

0 commit comments

Comments
 (0)