From 964eafb71c17f2d8532ab3fc32901b12a4b9b555 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 7 Jul 2025 15:15:07 -0700 Subject: [PATCH 1/5] bench, wallet: Make WalletMigration's setup WalletBatch scoped WalletBatch needs to be in a scope so that it is destroyed before the database is closed during migration. --- src/bench/wallet_migration.cpp | 62 ++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/bench/wallet_migration.cpp b/src/bench/wallet_migration.cpp index 910ad1e906c..7d7aca113d1 100644 --- a/src/bench/wallet_migration.cpp +++ b/src/bench/wallet_migration.cpp @@ -29,40 +29,42 @@ static void WalletMigration(benchmark::Bench& bench) // Setup legacy wallet std::unique_ptr wallet = std::make_unique(test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase()); - LegacyDataSPKM* legacy_spkm = wallet->GetOrCreateLegacyDataSPKM(); - WalletBatch batch{wallet->GetDatabase()}; + { + LegacyDataSPKM* legacy_spkm = wallet->GetOrCreateLegacyDataSPKM(); + WalletBatch batch{wallet->GetDatabase()}; - // Write a best block record as migration expects one to exist - CBlockLocator loc; - batch.WriteBestBlock(loc); + // Write a best block record as migration expects one to exist + CBlockLocator loc; + batch.WriteBestBlock(loc); - // Add watch-only addresses - std::vector scripts_watch_only; - for (int w = 0; w < NUM_WATCH_ONLY_ADDR; ++w) { - CKey key = GenerateRandomKey(); - LOCK(wallet->cs_wallet); - const PKHash dest{key.GetPubKey()}; - const CScript& script = scripts_watch_only.emplace_back(GetScriptForDestination(dest)); - assert(legacy_spkm->LoadWatchOnly(script)); - assert(wallet->SetAddressBook(dest, strprintf("watch_%d", w), /*purpose=*/std::nullopt)); - batch.WriteWatchOnly(script, CKeyMetadata()); - } + // Add watch-only addresses + std::vector scripts_watch_only; + for (int w = 0; w < NUM_WATCH_ONLY_ADDR; ++w) { + CKey key = GenerateRandomKey(); + LOCK(wallet->cs_wallet); + const PKHash dest{key.GetPubKey()}; + const CScript& script = scripts_watch_only.emplace_back(GetScriptForDestination(dest)); + assert(legacy_spkm->LoadWatchOnly(script)); + assert(wallet->SetAddressBook(dest, strprintf("watch_%d", w), /*purpose=*/std::nullopt)); + batch.WriteWatchOnly(script, CKeyMetadata()); + } - // Generate transactions and local addresses - for (int j = 0; j < 500; ++j) { - CKey key = GenerateRandomKey(); - CPubKey pubkey = key.GetPubKey(); - // Load key, scripts and create address book record - Assert(legacy_spkm->LoadKey(key, pubkey)); - CTxDestination dest{PKHash(pubkey)}; - Assert(wallet->SetAddressBook(dest, strprintf("legacy_%d", j), /*purpose=*/std::nullopt)); + // Generate transactions and local addresses + for (int j = 0; j < 500; ++j) { + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); + // Load key, scripts and create address book record + Assert(legacy_spkm->LoadKey(key, pubkey)); + CTxDestination dest{PKHash(pubkey)}; + Assert(wallet->SetAddressBook(dest, strprintf("legacy_%d", j), /*purpose=*/std::nullopt)); - CMutableTransaction mtx; - mtx.vout.emplace_back(COIN, GetScriptForDestination(dest)); - 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, /*rescanning_old_block=*/true); - batch.WriteKey(pubkey, key.GetPrivKey(), CKeyMetadata()); + CMutableTransaction mtx; + mtx.vout.emplace_back(COIN, GetScriptForDestination(dest)); + 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, /*rescanning_old_block=*/true); + batch.WriteKey(pubkey, key.GetPrivKey(), CKeyMetadata()); + } } bench.epochs(/*numEpochs=*/1).epochIterations(/*numIters=*/1) // run the migration exactly once From e7d67c9fd9a4a94d448ad122abe98a63d2e64077 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 7 Jul 2025 14:12:58 -0700 Subject: [PATCH 2/5] test: Make duplicating MockableDatabases use cursor and batch Instead of directly copying the stored records map when duplicating a MockableDatabase, use a Cursor to read the records, and a Batch to write them into the new database. This prepares for using SQLite as the database backend for MockableDatabase. --- src/wallet/test/util.cpp | 22 +++++++++++++++++++--- src/wallet/test/util.h | 16 ++++++++-------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp index 9ee63f6e228..9c101d7ec02 100644 --- a/src/wallet/test/util.cpp +++ b/src/wallet/test/util.cpp @@ -105,7 +105,23 @@ void TestUnloadWallet(std::shared_ptr&& wallet) std::unique_ptr DuplicateMockDatabase(WalletDatabase& database) { - return std::make_unique(dynamic_cast(database).m_records); + std::unique_ptr batch_orig = database.MakeBatch(); + std::unique_ptr cursor_orig = batch_orig->GetNewCursor(); + + std::unique_ptr new_db = std::make_unique(); + std::unique_ptr new_db_batch = new_db->MakeBatch(); + MockableBatch* batch_new = dynamic_cast(new_db_batch.get()); + Assert(batch_new); + + while (true) { + DataStream key, value; + DatabaseCursor::Status status = cursor_orig->Next(key, value); + Assert(status != DatabaseCursor::Status::FAIL); + if (status != DatabaseCursor::Status::MORE) break; + batch_new->WriteKey(std::move(key), std::move(value)); + } + + return new_db; } std::string getnewaddress(CWallet& w) @@ -208,9 +224,9 @@ bool MockableBatch::ErasePrefix(std::span prefix) return true; } -std::unique_ptr CreateMockableWalletDatabase(MockableData records) +std::unique_ptr CreateMockableWalletDatabase() { - return std::make_unique(records); + return std::make_unique(); } MockableDatabase& GetMockableDatabase(CWallet& wallet) diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h index d5deba295d2..f812b0ccfaf 100644 --- a/src/wallet/test/util.h +++ b/src/wallet/test/util.h @@ -68,18 +68,18 @@ private: MockableData& m_records; bool m_pass; - bool ReadKey(DataStream&& key, DataStream& value) override; - bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite=true) override; - bool EraseKey(DataStream&& key) override; - bool HasKey(DataStream&& key) override; - bool ErasePrefix(std::span prefix) override; - public: explicit MockableBatch(MockableData& records, bool pass) : m_records(records), m_pass(pass) {} ~MockableBatch() = default; void Close() override {} + bool ReadKey(DataStream&& key, DataStream& value) override; + bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite=true) override; + bool EraseKey(DataStream&& key) override; + bool HasKey(DataStream&& key) override; + bool ErasePrefix(std::span prefix) override; + std::unique_ptr GetNewCursor() override { return std::make_unique(m_records, m_pass); @@ -101,7 +101,7 @@ public: MockableData m_records; bool m_pass{true}; - MockableDatabase(MockableData records = {}) : WalletDatabase(), m_records(records) {} + MockableDatabase() : WalletDatabase() {} ~MockableDatabase() = default; void Open() override {} @@ -116,7 +116,7 @@ public: std::unique_ptr MakeBatch() override { return std::make_unique(m_records, m_pass); } }; -std::unique_ptr CreateMockableWalletDatabase(MockableData records = {}); +std::unique_ptr CreateMockableWalletDatabase(); MockableDatabase& GetMockableDatabase(CWallet& wallet); DescriptorScriptPubKeyMan* CreateDescriptor(CWallet& keystore, const std::string& desc_str, bool success); From b69f989dc539bc0f893b71b7390a1aec8ca0e292 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 19 Jan 2026 12:32:43 -0800 Subject: [PATCH 3/5] wallet, bench: Use TestingSetup in CoinSelection benchmark --- src/bench/coin_selection.cpp | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 2f108a310b4..6d0d89e41cb 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -12,8 +12,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -26,19 +28,7 @@ #include #include -using node::NodeContext; -using wallet::AttemptSelection; -using wallet::CHANGE_LOWER; -using wallet::COutput; -using wallet::CWallet; -using wallet::CWalletTx; -using wallet::CoinEligibilityFilter; -using wallet::CoinSelectionParams; -using wallet::CreateMockableWalletDatabase; -using wallet::OutputGroup; -using wallet::SelectCoinsBnB; -using wallet::TxStateInactive; - +namespace wallet { static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector>& wtxs) { static int nextLockTime = 0; @@ -58,9 +48,8 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector(); + CWallet wallet(test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase()); std::vector> wtxs; LOCK(wallet.cs_wallet); @@ -139,3 +128,4 @@ static void BnBExhaustion(benchmark::Bench& bench) BENCHMARK(CoinSelection); BENCHMARK(BnBExhaustion); +}; // namespace wallet From 59484e2fdbd45364b119090e57e701142e501499 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Thu, 24 Jul 2025 11:31:47 -0700 Subject: [PATCH 4/5] wallet: Make Mockable{Database,Batch} subclasses of SQLite classes The mocking functionality of MockableDatabase, MockableBatch, and MockableCursor was not really being used. These are changed to be subclasses of their respective SQLite* classes and will use in-memory SQLite databases so that the tests are more representative of actual database behavior. MockableCursor is removed as there are no overrides needed in SQLiteCursor for the tests. --- src/wallet/sqlite.h | 1 + src/wallet/test/util.cpp | 102 +++------------------------------------ src/wallet/test/util.h | 66 +++++-------------------- src/wallet/wallet.cpp | 2 +- 4 files changed, 19 insertions(+), 152 deletions(-) diff --git a/src/wallet/sqlite.h b/src/wallet/sqlite.h index c34597e60be..f467996dc56 100644 --- a/src/wallet/sqlite.h +++ b/src/wallet/sqlite.h @@ -75,6 +75,7 @@ private: void SetupSQLStatements(); bool ExecStatement(sqlite3_stmt* stmt, std::span blob); +protected: bool ReadKey(DataStream&& key, DataStream& value) override; bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override; bool EraseKey(DataStream&& key) override; diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp index 9c101d7ec02..c799721cfb4 100644 --- a/src/wallet/test/util.cpp +++ b/src/wallet/test/util.cpp @@ -108,9 +108,9 @@ std::unique_ptr DuplicateMockDatabase(WalletDatabase& database) std::unique_ptr batch_orig = database.MakeBatch(); std::unique_ptr cursor_orig = batch_orig->GetNewCursor(); - std::unique_ptr new_db = std::make_unique(); + std::unique_ptr new_db = CreateMockableWalletDatabase(); std::unique_ptr new_db_batch = new_db->MakeBatch(); - MockableBatch* batch_new = dynamic_cast(new_db_batch.get()); + MockableSQLiteBatch* batch_new = dynamic_cast(new_db_batch.get()); Assert(batch_new); while (true) { @@ -135,103 +135,13 @@ CTxDestination getNewDestination(CWallet& w, OutputType output_type) return *Assert(w.GetNewDestination(output_type, "")); } -MockableCursor::MockableCursor(const MockableData& records, bool pass, std::span prefix) -{ - m_pass = pass; - std::tie(m_cursor, m_cursor_end) = records.equal_range(BytePrefix{prefix}); -} - -DatabaseCursor::Status MockableCursor::Next(DataStream& key, DataStream& value) -{ - if (!m_pass) { - return Status::FAIL; - } - if (m_cursor == m_cursor_end) { - return Status::DONE; - } - key.clear(); - value.clear(); - const auto& [key_data, value_data] = *m_cursor; - key.write(key_data); - value.write(value_data); - m_cursor++; - return Status::MORE; -} - -bool MockableBatch::ReadKey(DataStream&& key, DataStream& value) -{ - if (!m_pass) { - return false; - } - SerializeData key_data{key.begin(), key.end()}; - const auto& it = m_records.find(key_data); - if (it == m_records.end()) { - return false; - } - value.clear(); - value.write(it->second); - return true; -} - -bool MockableBatch::WriteKey(DataStream&& key, DataStream&& value, bool overwrite) -{ - if (!m_pass) { - return false; - } - SerializeData key_data{key.begin(), key.end()}; - SerializeData value_data{value.begin(), value.end()}; - auto [it, inserted] = m_records.emplace(key_data, value_data); - if (!inserted && overwrite) { // Overwrite if requested - it->second = value_data; - inserted = true; - } - return inserted; -} - -bool MockableBatch::EraseKey(DataStream&& key) -{ - if (!m_pass) { - return false; - } - SerializeData key_data{key.begin(), key.end()}; - m_records.erase(key_data); - return true; -} - -bool MockableBatch::HasKey(DataStream&& key) -{ - if (!m_pass) { - return false; - } - SerializeData key_data{key.begin(), key.end()}; - return m_records.contains(key_data); -} - -bool MockableBatch::ErasePrefix(std::span prefix) -{ - if (!m_pass) { - return false; - } - auto it = m_records.begin(); - while (it != m_records.end()) { - auto& key = it->first; - if (key.size() < prefix.size() || std::search(key.begin(), key.end(), prefix.begin(), prefix.end()) != key.begin()) { - it++; - continue; - } - it = m_records.erase(it); - } - return true; -} +MockableSQLiteDatabase::MockableSQLiteDatabase() + : SQLiteDatabase(fs::PathFromString("mock/"), fs::PathFromString("mock/wallet.dat"), DatabaseOptions(), /*mock=*/true) +{} std::unique_ptr CreateMockableWalletDatabase() { - return std::make_unique(); -} - -MockableDatabase& GetMockableDatabase(CWallet& wallet) -{ - return dynamic_cast(wallet.GetDatabase()); + return std::make_unique(); } wallet::DescriptorScriptPubKeyMan* CreateDescriptor(CWallet& keystore, const std::string& desc_str, const bool success) diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h index f812b0ccfaf..fbc5188fc14 100644 --- a/src/wallet/test/util.h +++ b/src/wallet/test/util.h @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -48,76 +49,31 @@ CTxDestination getNewDestination(CWallet& w, OutputType output_type); using MockableData = std::map>; -class MockableCursor: public DatabaseCursor + +class MockableSQLiteBatch : public SQLiteBatch { public: - MockableData::const_iterator m_cursor; - MockableData::const_iterator m_cursor_end; - bool m_pass; - - explicit MockableCursor(const MockableData& records, bool pass) : m_cursor(records.begin()), m_cursor_end(records.end()), m_pass(pass) {} - MockableCursor(const MockableData& records, bool pass, std::span prefix); - ~MockableCursor() = default; - - Status Next(DataStream& key, DataStream& value) override; -}; - -class MockableBatch : public DatabaseBatch -{ -private: - MockableData& m_records; - bool m_pass; - -public: - explicit MockableBatch(MockableData& records, bool pass) : m_records(records), m_pass(pass) {} - ~MockableBatch() = default; - - void Close() override {} - - bool ReadKey(DataStream&& key, DataStream& value) override; - bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite=true) override; - bool EraseKey(DataStream&& key) override; - bool HasKey(DataStream&& key) override; - bool ErasePrefix(std::span prefix) override; - - std::unique_ptr GetNewCursor() override - { - return std::make_unique(m_records, m_pass); - } - std::unique_ptr GetNewPrefixCursor(std::span prefix) override { - return std::make_unique(m_records, m_pass, prefix); - } - bool TxnBegin() override { return m_pass; } - bool TxnCommit() override { return m_pass; } - bool TxnAbort() override { return m_pass; } - bool HasActiveTxn() override { return false; } + using SQLiteBatch::SQLiteBatch; + using SQLiteBatch::WriteKey; }; /** A WalletDatabase whose contents and return values can be modified as needed for testing **/ -class MockableDatabase : public WalletDatabase +class MockableSQLiteDatabase : public SQLiteDatabase { public: - MockableData m_records; - bool m_pass{true}; + MockableSQLiteDatabase(); - MockableDatabase() : WalletDatabase() {} - ~MockableDatabase() = default; - - void Open() override {} - - bool Rewrite() override { return m_pass; } - bool Backup(const std::string& strDest) const override { return m_pass; } - void Close() override {} + bool Backup(const std::string& strDest) const override { return true; } std::string Filename() override { return "mockable"; } std::vector Files() override { return {}; } - std::string Format() override { return "mock"; } - std::unique_ptr MakeBatch() override { return std::make_unique(m_records, m_pass); } + std::string Format() override { return "sqlite-mock"; } + std::unique_ptr MakeBatch() override { return std::make_unique(*this); } }; std::unique_ptr CreateMockableWalletDatabase(); -MockableDatabase& GetMockableDatabase(CWallet& wallet); +MockableSQLiteDatabase& GetMockableDatabase(CWallet& wallet); DescriptorScriptPubKeyMan* CreateDescriptor(CWallet& keystore, const std::string& desc_str, bool success); } // namespace wallet diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3724c3f2b3d..027f1d40432 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3521,7 +3521,7 @@ void CWallet::SetupLegacyScriptPubKeyMan() return; } - Assert(m_database->Format() == "bdb_ro" || m_database->Format() == "mock"); + Assert(m_database->Format() == "bdb_ro" || m_database->Format() == "sqlite-mock"); std::unique_ptr spk_manager = std::make_unique(*this); for (const auto& type : LEGACY_OUTPUT_TYPES) { From 037ea2c714cb1a9a8287a480b87c0ada3aba3ddf Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Thu, 24 Jul 2025 12:21:47 -0700 Subject: [PATCH 5/5] walletdb: Remove m_mock from SQLiteDatabase --- src/test/util/CMakeLists.txt | 1 + src/wallet/sqlite.cpp | 22 ++++++++++++++-------- src/wallet/sqlite.h | 9 ++++++--- src/wallet/test/util.cpp | 4 +++- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/test/util/CMakeLists.txt b/src/test/util/CMakeLists.txt index 32396c41c35..4cb00cecba1 100644 --- a/src/test/util/CMakeLists.txt +++ b/src/test/util/CMakeLists.txt @@ -25,6 +25,7 @@ target_link_libraries(test_util PRIVATE core_interface Boost::headers + $<$:SQLite3::SQLite3> PUBLIC univalue ) diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp index e72455a3be1..3d6583bb037 100644 --- a/src/wallet/sqlite.cpp +++ b/src/wallet/sqlite.cpp @@ -111,8 +111,12 @@ static void SetPragma(sqlite3* db, const std::string& key, const std::string& va Mutex SQLiteDatabase::g_sqlite_mutex; int SQLiteDatabase::g_sqlite_count = 0; -SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock) - : WalletDatabase(), m_mock(mock), m_dir_path(dir_path), m_file_path(fs::PathToString(file_path)), m_write_semaphore(1), m_use_unsafe_sync(options.use_unsafe_sync) +SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options) + : SQLiteDatabase(dir_path, file_path, options, /*additional_flags=*/0) +{} + +SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, int additional_flags) + : WalletDatabase(), m_dir_path(dir_path), m_file_path(fs::PathToString(file_path)), m_write_semaphore(1), m_use_unsafe_sync(options.use_unsafe_sync) { { LOCK(g_sqlite_mutex); @@ -135,7 +139,7 @@ SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_pa } try { - Open(); + Open(additional_flags); } catch (const std::runtime_error&) { // If open fails, cleanup this object and rethrow the exception Cleanup(); @@ -243,13 +247,15 @@ bool SQLiteDatabase::Verify(bilingual_str& error) void SQLiteDatabase::Open() { - int flags = SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; - if (m_mock) { - flags |= SQLITE_OPEN_MEMORY; // In memory database for mock db - } + Open(/*additional_flags*/0); +} + +void SQLiteDatabase::Open(int additional_flags) +{ + int flags = SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | additional_flags; if (m_db == nullptr) { - if (!m_mock) { + if (!(flags & SQLITE_OPEN_MEMORY)) { TryCreateDirectories(m_dir_path); } int ret = sqlite3_open_v2(m_file_path.c_str(), &m_db, flags, nullptr); diff --git a/src/wallet/sqlite.h b/src/wallet/sqlite.h index f467996dc56..fb0fa39c8bd 100644 --- a/src/wallet/sqlite.h +++ b/src/wallet/sqlite.h @@ -103,8 +103,6 @@ public: class SQLiteDatabase : public WalletDatabase { private: - const bool m_mock{false}; - const fs::path m_dir_path; const std::string m_file_path; @@ -120,11 +118,16 @@ private: void Cleanup() noexcept EXCLUSIVE_LOCKS_REQUIRED(!g_sqlite_mutex); + void Open(int additional_flags); + +protected: + SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, int additional_flags); + public: SQLiteDatabase() = delete; /** Create DB handle to real database */ - SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock = false); + SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options); ~SQLiteDatabase(); diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp index c799721cfb4..b21c96f17ab 100644 --- a/src/wallet/test/util.cpp +++ b/src/wallet/test/util.cpp @@ -14,6 +14,8 @@ #include #include +#include + #include namespace wallet { @@ -136,7 +138,7 @@ CTxDestination getNewDestination(CWallet& w, OutputType output_type) } MockableSQLiteDatabase::MockableSQLiteDatabase() - : SQLiteDatabase(fs::PathFromString("mock/"), fs::PathFromString("mock/wallet.dat"), DatabaseOptions(), /*mock=*/true) + : SQLiteDatabase(fs::PathFromString("mock/"), fs::PathFromString("mock/wallet.dat"), DatabaseOptions(), SQLITE_OPEN_MEMORY) {} std::unique_ptr CreateMockableWalletDatabase()