Merge bitcoin/bitcoin#33032: wallet, test: Replace MockableDatabase with in-memory SQLiteDatabase

037ea2c714 walletdb: Remove m_mock from SQLiteDatabase (Ava Chow)
59484e2fdb wallet: Make Mockable{Database,Batch} subclasses of SQLite classes (Ava Chow)
b69f989dc5 wallet, bench: Use TestingSetup in CoinSelection benchmark (Ava Chow)
e7d67c9fd9 test: Make duplicating MockableDatabases use cursor and batch (Ava Chow)
964eafb71c bench, wallet: Make WalletMigration's setup WalletBatch scoped (Ava Chow)

Pull request description:

  `MockableDatabase` was introduced for the tests to avoid tying non-database tests to a particular database type. However, since the only database type now is sqlite, and because the mockable behavior is no longer used by the tests, we can replace usage of the `MockabeDatabase` with a SQLite database that lives only in memory.

  This is particularly useful for future work that has the wallet make use of SQLite's capabilities more, which are less conducive to having a separate mock database implementation.

ACKs for top commit:
  brunoerg:
    code review ACK 037ea2c714
  sedited:
    Re-ACK 037ea2c714
  furszy:
    Code review ACK 037ea2c714

Tree-SHA512: 0a99c27ef4e590966b3af929bf3acf99666861905aabf150fe5660ea07c881a49935a4e7dcd676dcd5e70616898d89d872b6e156ae9c600de1361c1b2469b64d
This commit is contained in:
merge-script
2026-04-19 10:54:39 +02:00
8 changed files with 97 additions and 210 deletions

View File

@@ -14,6 +14,8 @@
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
#include <sqlite3.h>
#include <memory>
namespace wallet {
@@ -105,7 +107,23 @@ void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet)
std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database)
{
return std::make_unique<MockableDatabase>(dynamic_cast<MockableDatabase&>(database).m_records);
std::unique_ptr<DatabaseBatch> batch_orig = database.MakeBatch();
std::unique_ptr<DatabaseCursor> cursor_orig = batch_orig->GetNewCursor();
std::unique_ptr<WalletDatabase> new_db = CreateMockableWalletDatabase();
std::unique_ptr<DatabaseBatch> new_db_batch = new_db->MakeBatch();
MockableSQLiteBatch* batch_new = dynamic_cast<MockableSQLiteBatch*>(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<const std::byte> 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<WalletDatabase> 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<const std::byte> 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<WalletDatabase> CreateMockableWalletDatabase(MockableData records)
{
return std::make_unique<MockableDatabase>(records);
}
MockableDatabase& GetMockableDatabase(CWallet& wallet)
{
return dynamic_cast<MockableDatabase&>(wallet.GetDatabase());
return std::make_unique<MockableSQLiteDatabase>();
}
wallet::DescriptorScriptPubKeyMan* CreateDescriptor(CWallet& keystore, const std::string& desc_str, const bool success)

View File

@@ -8,6 +8,7 @@
#include <addresstype.h>
#include <wallet/db.h>
#include <wallet/scriptpubkeyman.h>
#include <wallet/sqlite.h>
#include <memory>
@@ -48,76 +49,31 @@ CTxDestination getNewDestination(CWallet& w, OutputType output_type);
using MockableData = std::map<SerializeData, SerializeData, std::less<>>;
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<const std::byte> 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<const std::byte> prefix) override;
public:
explicit MockableBatch(MockableData& records, bool pass) : m_records(records), m_pass(pass) {}
~MockableBatch() = default;
void Close() override {}
std::unique_ptr<DatabaseCursor> GetNewCursor() override
{
return std::make_unique<MockableCursor>(m_records, m_pass);
}
std::unique_ptr<DatabaseCursor> GetNewPrefixCursor(std::span<const std::byte> prefix) override {
return std::make_unique<MockableCursor>(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<fs::path> Files() override { return {}; }
std::string Format() override { return "mock"; }
std::unique_ptr<DatabaseBatch> MakeBatch() override { return std::make_unique<MockableBatch>(m_records, m_pass); }
std::string Format() override { return "sqlite-mock"; }
std::unique_ptr<DatabaseBatch> MakeBatch() override { return std::make_unique<MockableSQLiteBatch>(*this); }
};
std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase(MockableData records = {});
MockableDatabase& GetMockableDatabase(CWallet& wallet);
std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase();
MockableSQLiteDatabase& GetMockableDatabase(CWallet& wallet);
DescriptorScriptPubKeyMan* CreateDescriptor(CWallet& keystore, const std::string& desc_str, bool success);
} // namespace wallet