This commit is contained in:
Matias Furszyfer 2024-04-29 04:28:59 +02:00 committed by GitHub
commit 84887bede7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 253 additions and 107 deletions

View File

@ -91,6 +91,7 @@ bench_bench_bitcoin_SOURCES += bench/wallet_create.cpp
bench_bench_bitcoin_SOURCES += bench/wallet_loading.cpp
bench_bench_bitcoin_SOURCES += bench/wallet_create_tx.cpp
bench_bench_bitcoin_SOURCES += bench/wallet_ismine.cpp
bench_bench_bitcoin_SOURCES += bench/wallet_migration.cpp
bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(SQLITE_LIBS)
endif

View File

@ -0,0 +1,85 @@
// Copyright (c) 2024 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#include <bench/bench.h>
#include <interfaces/chain.h>
#include <node/context.h>
#include <test/util/mining.h>
#include <test/util/setup_common.h>
#include <wallet/test/util.h>
#include <wallet/context.h>
#include <wallet/receive.h>
#include <wallet/wallet.h>
#include <optional>
#ifdef USE_BDB // only enable benchmark when bdb and sqlite are enabled
#ifdef USE_SQLITE
namespace wallet{
static void WalletMigration(benchmark::Bench& bench)
{
const auto test_setup = MakeNoLogFileContext<TestingSetup>();
WalletContext context;
context.args = &test_setup->m_args;
context.chain = test_setup->m_node.chain.get();
// Number of imported watch only addresses
int NUM_WATCH_ONLY_ADDR = 20;
// Setup legacy wallet
DatabaseOptions options;
options.use_unsafe_sync = true;
options.verify = false;
DatabaseStatus status;
bilingual_str error;
auto database = MakeWalletDatabase(fs::PathToString(test_setup->m_path_root / "legacy"), options, status, error);
uint64_t create_flags = 0;
auto wallet = TestLoadWallet(std::move(database), context, create_flags);
// Add watch-only addresses
std::vector<CScript> scripts_watch_only;
for (int w = 0; w < NUM_WATCH_ONLY_ADDR; ++w) {
CKey key;
key.MakeNewKey(true);
LOCK(wallet->cs_wallet);
const CScript& script = scripts_watch_only.emplace_back(GetScriptForDestination(GetDestinationForKey(key.GetPubKey(), OutputType::LEGACY)));
bool res = wallet->ImportScriptPubKeys(strprintf("watch_%d", w), {script},
/*have_solving_data=*/false, /*apply_label=*/true, /*timestamp=*/1);
assert(res);
}
// Generate transactions and local addresses
for (int j = 0; j < 400; ++j) {
CMutableTransaction mtx;
mtx.vout.emplace_back(COIN, GetScriptForDestination(*Assert(wallet->GetNewDestination(OutputType::BECH32, strprintf("bench_%d", j)))));
mtx.vout.emplace_back(COIN, GetScriptForDestination(*Assert(wallet->GetNewDestination(OutputType::LEGACY, strprintf("legacy_%d", j)))));
mtx.vout.emplace_back(COIN, scripts_watch_only.at(j % NUM_WATCH_ONLY_ADDR));
mtx.vin.resize(2);
wallet->AddToWallet(MakeTransactionRef(mtx), TxStateInactive{}, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, /*rescanning_old_block=*/true);
}
// Unload so the migration process loads it
TestUnloadWallet(std::move(wallet));
bench.epochs(/*numEpochs=*/1).run([&] {
util::Result<MigrationResult> res = MigrateLegacyToDescriptor(fs::PathToString(test_setup->m_path_root / "legacy"), "", context);
assert(res);
assert(res->wallet);
assert(res->watchonly_wallet);
});
}
BENCHMARK(WalletMigration, benchmark::PriorityLevel::LOW);
} // namespace wallet
#endif // end USE_SQLITE
#endif // end USE_BDB

View File

@ -205,6 +205,7 @@ public:
bool TxnBegin() override;
bool TxnCommit() override;
bool TxnAbort() override;
bool HasActiveTxn() override { return activeTxn != nullptr; }
DbTxn* txn() const { return activeTxn; }
};

View File

@ -116,6 +116,7 @@ public:
virtual bool TxnBegin() = 0;
virtual bool TxnCommit() = 0;
virtual bool TxnAbort() = 0;
virtual bool HasActiveTxn() = 0;
};
/** An instance of this class represents one database.

View File

@ -44,6 +44,7 @@ public:
bool TxnBegin() override { return true; }
bool TxnCommit() override { return true; }
bool TxnAbort() override { return true; }
bool HasActiveTxn() override { return false; }
};
/** A dummy WalletDatabase that does nothing and never fails. Only used by salvage.

View File

@ -1784,6 +1784,12 @@ std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor()
keyid_it++;
}
WalletBatch batch(m_storage.GetDatabase());
if (!batch.TxnBegin()) {
LogPrintf("Error generating descriptors for migration, cannot initialize db transaction\n");
return std::nullopt;
}
// keyids is now all non-HD keys. Each key will have its own combo descriptor
for (const CKeyID& keyid : keyids) {
CKey key;
@ -1813,8 +1819,8 @@ std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor()
// Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size));
desc_spk_man->AddDescriptorKey(key, key.GetPubKey());
desc_spk_man->TopUp();
WITH_LOCK(desc_spk_man->cs_desc_man, desc_spk_man->AddDescriptorKeyWithDB(batch, key, key.GetPubKey()));
desc_spk_man->TopUpWithDB(batch);
auto desc_spks = desc_spk_man->GetScriptPubKeys();
// Remove the scriptPubKeys from our current set
@ -1858,8 +1864,8 @@ std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor()
// Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size));
desc_spk_man->AddDescriptorKey(master_key.key, master_key.key.GetPubKey());
desc_spk_man->TopUp();
WITH_LOCK(desc_spk_man->cs_desc_man, desc_spk_man->AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey()));
desc_spk_man->TopUpWithDB(batch);
auto desc_spks = desc_spk_man->GetScriptPubKeys();
// Remove the scriptPubKeys from our current set
@ -1925,9 +1931,9 @@ std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor()
if (!GetKey(keyid, key)) {
continue;
}
desc_spk_man->AddDescriptorKey(key, key.GetPubKey());
WITH_LOCK(desc_spk_man->cs_desc_man, desc_spk_man->AddDescriptorKeyWithDB(batch, key, key.GetPubKey()));
}
desc_spk_man->TopUp();
desc_spk_man->TopUpWithDB(batch);
auto desc_spks_set = desc_spk_man->GetScriptPubKeys();
desc_spks.insert(desc_spks.end(), desc_spks_set.begin(), desc_spks_set.end());
@ -1994,13 +2000,26 @@ std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor()
// Make sure that we have accounted for all scriptPubKeys
assert(spks.size() == 0);
// Finalize transaction
if (!batch.TxnCommit()) {
LogPrintf("Error generating descriptors for migration, cannot commit db transaction\n");
return std::nullopt;
}
return out;
}
bool LegacyScriptPubKeyMan::DeleteRecords()
{
return RunWithinTxn(m_storage.GetDatabase(), /*process_desc=*/"delete legacy records", [&](WalletBatch& batch){
return DeleteRecords(batch);
});
}
bool LegacyScriptPubKeyMan::DeleteRecords(WalletBatch& batch)
{
LOCK(cs_KeyStore);
WalletBatch batch(m_storage.GetDatabase());
return batch.EraseRecords(DBKeys::LEGACY_TYPES);
}

View File

@ -536,8 +536,9 @@ public:
/** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan.
* Does not modify this ScriptPubKeyMan. */
std::optional<MigrationData> MigrateToDescriptor();
/** Delete all the records ofthis LegacyScriptPubKeyMan from disk*/
/** Delete all the records of this LegacyScriptPubKeyMan from disk*/
bool DeleteRecords();
bool DeleteRecords(WalletBatch& batch);
};
/** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr. Does not provide privkeys */
@ -558,6 +559,7 @@ public:
class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
{
friend class LegacyScriptPubKeyMan;
private:
using ScriptPubKeyMap = std::map<CScript, int32_t>; // Map of scripts to descriptor range index
using PubKeyMap = std::map<CPubKey, int32_t>; // Map of pubkeys involved in scripts to descriptor range index

View File

@ -95,6 +95,7 @@ public:
bool TxnBegin() override;
bool TxnCommit() override;
bool TxnAbort() override;
bool HasActiveTxn() override { return m_txn; }
};
/** An instance of this class represents one SQLite3 database.

View File

@ -97,6 +97,7 @@ public:
bool TxnBegin() override { return m_pass; }
bool TxnCommit() override { return m_pass; }
bool TxnAbort() override { return m_pass; }
bool HasActiveTxn() override { return false; }
};
/** A WalletDatabase whose contents and return values can be modified as needed for testing

View File

@ -1685,10 +1685,16 @@ bool CWallet::CanGetAddresses(bool internal) const
}
void CWallet::SetWalletFlag(uint64_t flags)
{
WalletBatch batch(GetDatabase());
return SetWalletFlagWithDB(batch, flags);
}
void CWallet::SetWalletFlagWithDB(WalletBatch& batch, uint64_t flags)
{
LOCK(cs_wallet);
m_wallet_flags |= flags;
if (!WalletBatch(GetDatabase()).WriteWalletFlags(m_wallet_flags))
if (!batch.WriteWalletFlags(m_wallet_flags))
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
@ -2366,8 +2372,19 @@ DBErrors CWallet::LoadWallet()
util::Result<void> CWallet::RemoveTxs(std::vector<uint256>& txs_to_remove)
{
AssertLockHeld(cs_wallet);
WalletBatch batch(GetDatabase());
if (!batch.TxnBegin()) return util::Error{_("Error starting db txn for wallet transactions removal")};
util::Result<void> res{util::Error{}};
bool was_txn_committed = RunWithinTxn(GetDatabase(), /*process_desc=*/"remove transactions", [&](WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
return (res = RemoveTxs(batch, txs_to_remove)).has_value();
});
if (!res) return util::Error{util::ErrorString(res)};
if (!was_txn_committed) return util::Error{_("Error starting/committing db txn for wallet transactions removal process")};
return res;
}
util::Result<void> CWallet::RemoveTxs(WalletBatch& batch, std::vector<uint256>& txs_to_remove)
{
AssertLockHeld(cs_wallet);
if (!batch.HasActiveTxn()) return util::Error{strprintf(_("The transactions removal process can only be executed within a db txn"))};
// Check for transaction existence and remove entries from disk
using TxIterator = std::unordered_map<uint256, CWalletTx, SaltedTxidHasher>::const_iterator;
@ -2376,38 +2393,30 @@ util::Result<void> CWallet::RemoveTxs(std::vector<uint256>& txs_to_remove)
for (const uint256& hash : txs_to_remove) {
auto it_wtx = mapWallet.find(hash);
if (it_wtx == mapWallet.end()) {
str_err = strprintf(_("Transaction %s does not belong to this wallet"), hash.GetHex());
break;
return util::Error{strprintf(_("Transaction %s does not belong to this wallet"), hash.GetHex())};
}
if (!batch.EraseTx(hash)) {
str_err = strprintf(_("Failure removing transaction: %s"), hash.GetHex());
break;
return util::Error{strprintf(_("Failure removing transaction: %s"), hash.GetHex())};
}
erased_txs.emplace_back(it_wtx);
}
// Roll back removals in case of an error
if (!str_err.empty()) {
batch.TxnAbort();
return util::Error{str_err};
}
// Register callback to update the memory state only when the db txn is actually dumped to disk
batch.RegisterTxnListener({.on_commit=[&, erased_txs]() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
// Update the in-memory state and notify upper layers about the removals
for (const auto& it : erased_txs) {
const uint256 hash{it->first};
wtxOrdered.erase(it->second.m_it_wtxOrdered);
for (const auto& txin : it->second.tx->vin)
mapTxSpends.erase(txin.prevout);
mapWallet.erase(it);
NotifyTransactionChanged(hash, CT_DELETED);
}
// Dump changes to disk
if (!batch.TxnCommit()) return util::Error{_("Error committing db txn for wallet transactions removal")};
MarkDirty();
}, .on_abort={}});
// Update the in-memory state and notify upper layers about the removals
for (const auto& it : erased_txs) {
const uint256 hash{it->first};
wtxOrdered.erase(it->second.m_it_wtxOrdered);
for (const auto& txin : it->second.tx->vin)
mapTxSpends.erase(txin.prevout);
mapWallet.erase(it);
NotifyTransactionChanged(hash, CT_DELETED);
}
MarkDirty();
return {}; // all good
return {};
}
bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::optional<AddressPurpose>& new_purpose)
@ -3682,22 +3691,29 @@ DescriptorScriptPubKeyMan& CWallet::SetupDescriptorScriptPubKeyMan(WalletBatch&
return *out;
}
void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key)
void CWallet::SetupDescriptorScriptPubKeyMans(WalletBatch& batch, const CExtKey& master_key)
{
AssertLockHeld(cs_wallet);
// Create single batch txn
WalletBatch batch(GetDatabase());
if (!batch.TxnBegin()) throw std::runtime_error("Error: cannot create db transaction for descriptors setup");
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
SetupDescriptorScriptPubKeyMan(batch, master_key, t, internal);
}
}
}
// Ensure information is committed to disk
if (!batch.TxnCommit()) throw std::runtime_error("Error: cannot commit db transaction for descriptors setup");
void CWallet::SetupOwnDescriptorScriptPubKeyMans(WalletBatch& batch)
{
assert(!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
// Make a seed
CKey seed_key = GenerateRandomKey();
CPubKey seed = seed_key.GetPubKey();
assert(seed_key.VerifyPubKey(seed));
// Get the extended key
CExtKey master_key;
master_key.SetSeed(seed_key);
SetupDescriptorScriptPubKeyMans(batch, master_key);
}
void CWallet::SetupDescriptorScriptPubKeyMans()
@ -3705,16 +3721,10 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
AssertLockHeld(cs_wallet);
if (!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
// Make a seed
CKey seed_key = GenerateRandomKey();
CPubKey seed = seed_key.GetPubKey();
assert(seed_key.VerifyPubKey(seed));
// Get the extended key
CExtKey master_key;
master_key.SetSeed(seed_key);
SetupDescriptorScriptPubKeyMans(master_key);
if (!RunWithinTxn(GetDatabase(), /*process_desc=*/"setup descriptors", [&](WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet){
SetupOwnDescriptorScriptPubKeyMans(batch);
return true;
})) throw std::runtime_error("Error: cannot process db transaction for descriptors setup");
} else {
ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
@ -4004,15 +4014,14 @@ std::optional<MigrationData> CWallet::GetDescriptorsForLegacy(bilingual_str& err
return res;
}
bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, MigrationData& data)
{
AssertLockHeld(cs_wallet);
LegacyScriptPubKeyMan* legacy_spkm = GetLegacyScriptPubKeyMan();
if (!Assume(legacy_spkm)) {
// This shouldn't happen
error = Untranslated(STR_INTERNAL_BUG("Error: Legacy wallet data missing"));
return false;
return util::Error{Untranslated(STR_INTERNAL_BUG("Error: Legacy wallet data missing"))};
}
// Get all invalid or non-watched scripts that will not be migrated
@ -4026,16 +4035,15 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
for (auto& desc_spkm : data.desc_spkms) {
if (m_spk_managers.count(desc_spkm->GetID()) > 0) {
error = _("Error: Duplicate descriptors created during migration. Your wallet may be corrupted.");
return false;
return util::Error{_("Error: Duplicate descriptors created during migration. Your wallet may be corrupted.")};
}
uint256 id = desc_spkm->GetID();
AddScriptPubKeyMan(id, std::move(desc_spkm));
}
// Remove the LegacyScriptPubKeyMan from disk
if (!legacy_spkm->DeleteRecords()) {
return false;
if (!legacy_spkm->DeleteRecords(local_wallet_batch)) {
return util::Error{_("Error: cannot remove legacy wallet records")};
}
// Remove the LegacyScriptPubKeyMan from memory
@ -4044,22 +4052,21 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
m_internal_spk_managers.clear();
// Setup new descriptors
SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
SetWalletFlagWithDB(local_wallet_batch, WALLET_FLAG_DESCRIPTORS);
if (!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
// Use the existing master key if we have it
if (data.master_key.key.IsValid()) {
SetupDescriptorScriptPubKeyMans(data.master_key);
SetupDescriptorScriptPubKeyMans(local_wallet_batch, data.master_key);
} else {
// Setup with a new seed if we don't.
SetupDescriptorScriptPubKeyMans();
SetupOwnDescriptorScriptPubKeyMans(local_wallet_batch);
}
}
// Get best block locator so that we can copy it to the watchonly and solvables
CBlockLocator best_block_locator;
if (!WalletBatch(GetDatabase()).ReadBestBlock(best_block_locator)) {
error = _("Error: Unable to read wallet's best block locator record");
return false;
if (!local_wallet_batch.ReadBestBlock(best_block_locator)) {
return util::Error{_("Error: Unable to read wallet's best block locator record")};
}
// Check if the transactions in the wallet are still ours. Either they belong here, or they belong in the watchonly wallet.
@ -4074,15 +4081,13 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
watchonly_batch->WriteOrderPosNext(data.watchonly_wallet->nOrderPosNext);
// Write the best block locator to avoid rescanning on reload
if (!watchonly_batch->WriteBestBlock(best_block_locator)) {
error = _("Error: Unable to write watchonly wallet best block locator record");
return false;
return util::Error{_("Error: Unable to write watchonly wallet best block locator record")};
}
}
if (data.solvable_wallet) {
// Write the best block locator to avoid rescanning on reload
if (!WalletBatch(data.solvable_wallet->GetDatabase()).WriteBestBlock(best_block_locator)) {
error = _("Error: Unable to write solvable wallet best block locator record");
return false;
return util::Error{_("Error: Unable to write solvable wallet best block locator record")};
}
}
for (const auto& [_pos, wtx] : wtxOrdered) {
@ -4101,8 +4106,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
ins_wtx.CopyFrom(to_copy_wtx);
return true;
})) {
error = strprintf(_("Error: Could not add watchonly tx %s to watchonly wallet"), wtx->GetHash().GetHex());
return false;
return util::Error{strprintf(_("Error: Could not add watchonly tx %s to watchonly wallet"), wtx->GetHash().GetHex())};
}
watchonly_batch->WriteTx(data.watchonly_wallet->mapWallet.at(hash));
// Mark as to remove from the migrated wallet only if it does not also belong to it
@ -4114,16 +4118,14 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
}
if (!is_mine) {
// Both not ours and not in the watchonly wallet
error = strprintf(_("Error: Transaction %s in wallet cannot be identified to belong to migrated wallets"), wtx->GetHash().GetHex());
return false;
return util::Error{strprintf(_("Error: Transaction %s in wallet cannot be identified to belong to migrated wallets"), wtx->GetHash().GetHex())};
}
}
watchonly_batch.reset(); // Flush
// Do the removes
if (txids_to_delete.size() > 0) {
if (auto res = RemoveTxs(txids_to_delete); !res) {
error = _("Error: Could not delete watchonly transactions. ") + util::ErrorString(res);
return false;
if (auto res = RemoveTxs(local_wallet_batch, txids_to_delete); !res) {
return util::Error{_("Error: Could not delete watchonly transactions. ") + util::ErrorString(res)};
}
}
@ -4134,8 +4136,7 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
std::unique_ptr<WalletBatch> batch = std::make_unique<WalletBatch>(ext_wallet->GetDatabase());
if (!batch->TxnBegin()) {
error = strprintf(_("Error: database transaction cannot be executed for wallet %s"), ext_wallet->GetName());
return false;
return util::Error{strprintf(_("Error: database transaction cannot be executed for wallet %s"), ext_wallet->GetName())};
}
wallets_vec.emplace_back(ext_wallet, std::move(batch));
}
@ -4185,39 +4186,27 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
continue;
}
error = _("Error: Address book data in wallet cannot be identified to belong to migrated wallets");
return false;
return util::Error{_("Error: Address book data in wallet cannot be identified to belong to migrated wallets")};
}
}
// Persist external wallets address book entries
for (auto& [wallet, batch] : wallets_vec) {
if (!batch->TxnCommit()) {
error = strprintf(_("Error: address book copy failed for wallet %s"), wallet->GetName());
return false;
return util::Error{strprintf(_("Error: address book copy failed for wallet %s"), wallet->GetName())};
}
}
// Remove the things to delete in this wallet
WalletBatch local_wallet_batch(GetDatabase());
local_wallet_batch.TxnBegin();
if (dests_to_delete.size() > 0) {
for (const auto& dest : dests_to_delete) {
if (!DelAddressBookWithDB(local_wallet_batch, dest)) {
error = _("Error: Unable to remove watchonly address book data");
return false;
return util::Error{_("Error: Unable to remove watchonly address book data")};
}
}
}
local_wallet_batch.TxnCommit();
// Connect the SPKM signals
ConnectScriptPubKeyManNotifiers();
NotifyCanGetAddressesChanged();
WalletLogPrintf("Wallet migration complete.\n");
return true;
return {}; // all good
}
bool CWallet::CanGrindR() const
@ -4328,10 +4317,14 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error,
}
// Add the descriptors to wallet, remove LegacyScriptPubKeyMan, and cleanup txs and address book data
if (!wallet.ApplyMigrationData(*data, error)) {
return false;
}
return true;
return RunWithinTxn(wallet.GetDatabase(), /*process_desc=*/"apply migration process", [&](WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet){
if (auto res_migration = wallet.ApplyMigrationData(batch, *data); !res_migration) {
error = util::ErrorString(res_migration);
return false;
}
wallet.WalletLogPrintf("Wallet migration complete.\n");
return true;
});
}
util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& wallet_name, const SecureString& passphrase, WalletContext& context)

View File

@ -423,6 +423,9 @@ private:
// Same as 'AddActiveScriptPubKeyMan' but designed for use within a batch transaction context
void AddActiveScriptPubKeyManWithDb(WalletBatch& batch, uint256 id, OutputType type, bool internal);
/** Store wallet flags */
void SetWalletFlagWithDB(WalletBatch& batch, uint64_t flags);
//! Cache of descriptor ScriptPubKeys used for IsMine. Maps ScriptPubKey to set of spkms
std::unordered_map<CScript, std::vector<ScriptPubKeyMan*>, SaltedSipHasher> m_cached_spks;
@ -792,6 +795,7 @@ public:
/** Erases the provided transactions from the wallet. */
util::Result<void> RemoveTxs(std::vector<uint256>& txs_to_remove) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
util::Result<void> RemoveTxs(WalletBatch& batch, std::vector<uint256>& txs_to_remove) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::optional<AddressPurpose>& purpose);
@ -1017,9 +1021,12 @@ public:
//! Create new DescriptorScriptPubKeyMan and add it to the wallet
DescriptorScriptPubKeyMan& SetupDescriptorScriptPubKeyMan(WalletBatch& batch, const CExtKey& master_key, const OutputType& output_type, bool internal) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Create new DescriptorScriptPubKeyMans and add them to the wallet
void SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SetupDescriptorScriptPubKeyMans(WalletBatch& batch, const CExtKey& master_key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Create new seed and default DescriptorScriptPubKeyMans for this wallet
void SetupOwnDescriptorScriptPubKeyMans(WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is already in the wallet
DescriptorScriptPubKeyMan* GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const;
@ -1043,7 +1050,7 @@ public:
//! Adds the ScriptPubKeyMans given in MigrationData to this wallet, removes LegacyScriptPubKeyMan,
//! and where needed, moves tx and address book entries to watchonly_wallet or solvable_wallet
bool ApplyMigrationData(MigrationData& data, bilingual_str& error) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
util::Result<void> ApplyMigrationData(WalletBatch& local_wallet_batch, MigrationData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Whether the (external) signer performs R-value signature grinding
bool CanGrindR() const;

View File

@ -1325,10 +1325,8 @@ bool WalletBatch::WriteWalletFlags(const uint64_t flags)
bool WalletBatch::EraseRecords(const std::unordered_set<std::string>& types)
{
return RunWithinTxn(*this, "erase records", [&types](WalletBatch& self) {
return std::all_of(types.begin(), types.end(), [&self](const std::string& type) {
return self.m_batch->ErasePrefix(DataStream() << type);
});
return std::all_of(types.begin(), types.end(), [&](const std::string& type) {
return m_batch->ErasePrefix(DataStream() << type);
});
}
@ -1339,12 +1337,34 @@ bool WalletBatch::TxnBegin()
bool WalletBatch::TxnCommit()
{
return m_batch->TxnCommit();
bool res = m_batch->TxnCommit();
if (res) {
for (const auto& listener : m_txn_listeners) {
listener.on_commit();
}
// txn finished, clear listeners
m_txn_listeners.clear();
}
return res;
}
bool WalletBatch::TxnAbort()
{
return m_batch->TxnAbort();
bool res = m_batch->TxnAbort();
if (res) {
for (const auto& listener : m_txn_listeners) {
listener.on_abort();
}
// txn finished, clear listeners
m_txn_listeners.clear();
}
return res;
}
void WalletBatch::RegisterTxnListener(const DbTxnListener& l)
{
assert(m_batch->HasActiveTxn());
m_txn_listeners.emplace_back(l);
}
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)

View File

@ -180,6 +180,11 @@ public:
}
};
struct DbTxnListener
{
std::function<void()> on_commit, on_abort;
};
/** Access to the wallet database.
* Opens the database and provides read and write access to it. Each read and write is its own transaction.
* Multiple operation transactions can be started using TxnBegin() and committed using TxnCommit()
@ -289,9 +294,18 @@ public:
bool TxnCommit();
//! Abort current transaction
bool TxnAbort();
bool HasActiveTxn() { return m_batch->HasActiveTxn(); }
//! Registers db txn callback functions
void RegisterTxnListener(const DbTxnListener& l);
private:
std::unique_ptr<DatabaseBatch> m_batch;
WalletDatabase& m_database;
// External functions listening to the current db txn outcome.
// Listeners are cleared at the end of the transaction.
std::vector<DbTxnListener> m_txn_listeners;
};
/**