wallet: add option to create in-memory SQLite DB in the migration

This commit is contained in:
brunoerg 2024-12-10 17:52:19 -03:00
parent f409444d02
commit ba8d151fe0
6 changed files with 20 additions and 13 deletions

View file

@ -63,7 +63,7 @@ static void WalletMigration(benchmark::Bench& bench)
bench.epochs(/*numEpochs=*/1).epochIterations(/*numIters=*/1) // run the migration exactly once
.run([&] {
auto res{MigrateLegacyToDescriptor(std::move(wallet), /*passphrase=*/"", *loader->context(), /*was_loaded=*/false)};
auto res{MigrateLegacyToDescriptor(std::move(wallet), /*passphrase=*/"", *loader->context(), /*was_loaded=*/false, /*in_memory=*/false)};
assert(res);
assert(res->wallet);
assert(res->watchonly_wallet);

View file

@ -184,12 +184,13 @@ public:
enum class DatabaseFormat {
SQLITE,
BERKELEY_RO,
BERKELEY_SWAP,
BERKELEY_SWAP
};
struct DatabaseOptions {
bool require_existing = false;
bool require_create = false;
bool in_memory = false;
std::optional<DatabaseFormat> require_format;
uint64_t create_flags = 0;
SecureString create_passphrase;

View file

@ -695,7 +695,7 @@ std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const D
{
try {
fs::path data_file = SQLiteDataFile(path);
auto db = std::make_unique<SQLiteDatabase>(data_file.parent_path(), data_file, options);
auto db = std::make_unique<SQLiteDatabase>(data_file.parent_path(), data_file, options, options.in_memory ? true : false);
if (options.verify && !db->Verify(error)) {
status = DatabaseStatus::FAILED_VERIFY;
return nullptr;

View file

@ -4010,7 +4010,7 @@ util::Result<ScriptPubKeyMan*> CWallet::AddWalletDescriptor(WalletDescriptor& de
return spk_man;
}
bool CWallet::MigrateToSQLite(bilingual_str& error)
bool CWallet::MigrateToSQLite(bilingual_str& error, bool in_memory)
{
AssertLockHeld(cs_wallet);
@ -4061,6 +4061,7 @@ bool CWallet::MigrateToSQLite(bilingual_str& error)
DatabaseOptions opts;
opts.require_create = true;
opts.require_format = DatabaseFormat::SQLITE;
opts.in_memory = in_memory;
DatabaseStatus db_status;
std::unique_ptr<WalletDatabase> new_db = MakeDatabase(wallet_path, opts, db_status, error);
assert(new_db); // This is to prevent doing anything further with this wallet. The original file was deleted, but a backup exists.
@ -4476,10 +4477,10 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
return util::Error{Untranslated("Wallet loading failed.") + Untranslated(" ") + error};
}
return MigrateLegacyToDescriptor(std::move(local_wallet), passphrase, context, was_loaded);
return MigrateLegacyToDescriptor(std::move(local_wallet), passphrase, context, was_loaded, /*in_memory=*/false);
}
util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet> local_wallet, const SecureString& passphrase, WalletContext& context, bool was_loaded)
util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet> local_wallet, const SecureString& passphrase, WalletContext& context, bool was_loaded, bool in_memory)
{
MigrationResult res;
bilingual_str error;
@ -4541,7 +4542,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
{
LOCK(local_wallet->cs_wallet);
// First change to using SQLite
if (!local_wallet->MigrateToSQLite(error)) return util::Error{error};
if (!local_wallet->MigrateToSQLite(error, in_memory)) return util::Error{error};
// Do the migration of keys and scripts for non-empty wallets, and cleanup if it fails
if (HasLegacyRecords(*local_wallet)) {
@ -4580,7 +4581,10 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
// Migration failed, cleanup
// Before deleting the wallet's directory, copy the backup file to the top-level wallets dir
fs::path temp_backup_location = fsbridge::AbsPathJoin(GetWalletDir(), backup_filename);
fs::copy_file(backup_path, temp_backup_location, fs::copy_options::none);
if (!in_memory) {
fs::copy_file(backup_path, temp_backup_location, fs::copy_options::none);
}
// Make list of wallets to cleanup
std::vector<std::shared_ptr<CWallet>> created_wallets;
@ -4625,8 +4629,10 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
}
// The wallet directory has been restored, but just in case, copy the previously created backup to the wallet dir
fs::copy_file(temp_backup_location, backup_path, fs::copy_options::none);
fs::remove(temp_backup_location);
if (!in_memory) {
fs::copy_file(temp_backup_location, backup_path, fs::copy_options::none);
fs::remove(temp_backup_location);
}
// Verify that there is no dangling wallet: when the wallet wasn't loaded before, expect null.
// This check is performed after restoration to avoid an early error before saving the backup.

View file

@ -1046,7 +1046,7 @@ public:
* A backup is not created.
* May crash if something unexpected happens in the filesystem.
*/
bool MigrateToSQLite(bilingual_str& error) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool MigrateToSQLite(bilingual_str& error, bool in_memory = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Get all of the descriptors from a legacy wallet
std::optional<MigrationData> GetDescriptorsForLegacy(bilingual_str& error) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@ -1137,7 +1137,7 @@ struct MigrationResult {
//! Do all steps to migrate a legacy wallet to a descriptor wallet
[[nodiscard]] util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& wallet_name, const SecureString& passphrase, WalletContext& context);
//! Requirement: The wallet provided to this function must be isolated, with no attachment to the node's context.
[[nodiscard]] util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet> local_wallet, const SecureString& passphrase, WalletContext& context, bool was_loaded);
[[nodiscard]] util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet> local_wallet, const SecureString& passphrase, WalletContext& context, bool was_loaded, bool in_memory);
} // namespace wallet
#endif // BITCOIN_WALLET_WALLET_H

View file

@ -1427,7 +1427,7 @@ std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const Databas
{
bool exists;
try {
exists = fs::symlink_status(path).type() != fs::file_type::not_found;
exists = fs::symlink_status(path).type() != fs::file_type::not_found && !options.in_memory;
} catch (const fs::filesystem_error& e) {
error = Untranslated(strprintf("Failed to access database path '%s': %s", fs::PathToString(path), fsbridge::get_filesystem_error_message(e)));
status = DatabaseStatus::FAILED_BAD_PATH;