Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 6 additions & 16 deletions src/bench/coin_selection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
#include <primitives/transaction.h>
#include <random.h>
#include <sync.h>
#include <test/util/setup_common.h>
#include <util/result.h>
#include <wallet/coinselection.h>
#include <wallet/context.h>
#include <wallet/spend.h>
#include <wallet/test/util.h>
#include <wallet/transaction.h>
Expand All @@ -26,19 +28,7 @@
#include <utility>
#include <vector>

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<std::unique_ptr<CWalletTx>>& wtxs)
{
static int nextLockTime = 0;
Expand All @@ -58,9 +48,8 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<st
// (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484)
static void CoinSelection(benchmark::Bench& bench)
{
NodeContext node;
auto chain = interfaces::MakeChain(node);
CWallet wallet(chain.get(), "", CreateMockableWalletDatabase());
const auto test_setup = MakeNoLogFileContext<TestingSetup>();
CWallet wallet(test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase());
std::vector<std::unique_ptr<CWalletTx>> wtxs;
LOCK(wallet.cs_wallet);

Expand Down Expand Up @@ -139,3 +128,4 @@ static void BnBExhaustion(benchmark::Bench& bench)

BENCHMARK(CoinSelection);
BENCHMARK(BnBExhaustion);
}; // namespace wallet

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit:

Suggested change
}; // namespace wallet
} // namespace wallet

62 changes: 32 additions & 30 deletions src/bench/wallet_migration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,40 +29,42 @@ static void WalletMigration(benchmark::Bench& bench)

// Setup legacy wallet
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(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<CScript> 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<CScript> 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
Expand Down
1 change: 1 addition & 0 deletions src/test/util/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ target_link_libraries(test_util
PRIVATE
core_interface
Boost::headers
$<$<BOOL:${ENABLE_WALLET}>:SQLite3::SQLite3>
PUBLIC
univalue
)
22 changes: 14 additions & 8 deletions src/wallet/sqlite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();
Expand Down Expand Up @@ -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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nano nit: a constant "SQLITE_NO_OPEN_FLAG = 0" would be slightly more correct.

}

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);
Expand Down
10 changes: 7 additions & 3 deletions src/wallet/sqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class SQLiteBatch : public DatabaseBatch
void SetupSQLStatements();
bool ExecStatement(sqlite3_stmt* stmt, std::span<const std::byte> blob);

protected:
bool ReadKey(DataStream&& key, DataStream& value) override;
bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override;
bool EraseKey(DataStream&& key) override;
Expand Down Expand Up @@ -102,8 +103,6 @@ class SQLiteBatch : public DatabaseBatch
class SQLiteDatabase : public WalletDatabase
{
private:
const bool m_mock{false};

const fs::path m_dir_path;

const std::string m_file_path;
Expand All @@ -119,11 +118,16 @@ class SQLiteDatabase : public WalletDatabase

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();

Expand Down
120 changes: 24 additions & 96 deletions src/wallet/test/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <wallet/wallet.h>
#include <wallet/walletdb.h>

#include <sqlite3.h>

#include <memory>

namespace wallet {
Expand Down Expand Up @@ -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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nano nit: should be status == DatabaseCursor::Status::DONE (could also do a while(status != DONE) to be clearer).

batch_new->WriteKey(std::move(key), std::move(value));
}

return new_db;
}

std::string getnewaddress(CWallet& w)
Expand All @@ -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});
}

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<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);
}
MockableSQLiteDatabase::MockableSQLiteDatabase()
: SQLiteDatabase(fs::PathFromString("mock/"), fs::PathFromString("mock/wallet.dat"), DatabaseOptions(), SQLITE_OPEN_MEMORY)
{}

MockableDatabase& GetMockableDatabase(CWallet& wallet)
std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase()
{
return dynamic_cast<MockableDatabase&>(wallet.GetDatabase());
return std::make_unique<MockableSQLiteDatabase>();
}

wallet::DescriptorScriptPubKeyMan* CreateDescriptor(CWallet& keystore, const std::string& desc_str, const bool success)
Expand Down
Loading
Loading