diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 74d8e4b6006..7a7f6dafbd2 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); @@ -136,3 +125,4 @@ static void BnBExhaustion(benchmark::Bench& bench) BENCHMARK(CoinSelection); BENCHMARK(BnBExhaustion); +}; // namespace wallet 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 diff --git a/src/test/util/CMakeLists.txt b/src/test/util/CMakeLists.txt index d6864a2720e..5d9832fc144 100644 --- a/src/test/util/CMakeLists.txt +++ b/src/test/util/CMakeLists.txt @@ -24,6 +24,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 c34597e60be..fb0fa39c8bd 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; @@ -102,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; @@ -119,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 9ee63f6e228..b21c96f17ab 100644 --- a/src/wallet/test/util.cpp +++ b/src/wallet/test/util.cpp @@ -14,6 +14,8 @@ #include #include +#include + #include namespace wallet { @@ -105,7 +107,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 = CreateMockableWalletDatabase(); + std::unique_ptr new_db_batch = new_db->MakeBatch(); + MockableSQLiteBatch* 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) @@ -119,103 +137,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}); -} +MockableSQLiteDatabase::MockableSQLiteDatabase() + : SQLiteDatabase(fs::PathFromString("mock/"), fs::PathFromString("mock/wallet.dat"), DatabaseOptions(), SQLITE_OPEN_MEMORY) +{} -DatabaseCursor::Status MockableCursor::Next(DataStream& key, DataStream& value) +std::unique_ptr CreateMockableWalletDatabase() { - 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; -} - -std::unique_ptr CreateMockableWalletDatabase(MockableData records) -{ - return std::make_unique(records); -} - -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 d5deba295d2..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; - - 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 {} - - 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(MockableData records = {}) : WalletDatabase(), m_records(records) {} - ~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(MockableData records = {}); -MockableDatabase& GetMockableDatabase(CWallet& wallet); +std::unique_ptr CreateMockableWalletDatabase(); +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 777efe4172c..1b8f547f903 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) {