Merge bitcoin/bitcoin#35018: wallet, bench: Use Nanobench setup() for wallet benchmarks, and remove DuplicateMockDatabase

1d1ae6f0c4 wallet, test: Remove DuplicateMockDatabase (Ava Chow)
57820c472b bench: Utilize setup() for WalletLoading and use a real database (Ava Chow)
9a7604fd25 bench: Use setup() in WalletMigration to prepare the legacy wallet (Ava Chow)
426a94e7bd bench: Utilize setup() in WalletEncrypt to create the encryption wallet (Ava Chow)
d672455d20 bench: Utilitze setup() in WalletBalance for marking caches dirty (Ava Chow)
61412ef887 bench: Utilize setup() in WalletCreate to cleanup previous wallets (Ava Chow)

Pull request description:

  Several of the wallet benchmarks have some setup or cleanup that needs to be done per run. Now that #34208 is merged, these can use `setup()`. Additionally, this allows for removing `DuplicateMockDatabase` in `WalletEncryptDescriptors`.

  This PR also removes `DuplicateMockDatabase` in `WalletLoadingDescriptors`. `DuplicateMockDatabase` was added here in #24924 as part of benchmark performance improvements. However, it does not appear to make a significant difference today.

  Removing `DuplicateMockDatabase` makes future database changes easier. In particular it should simplify #33032 and #33034, and any future changes that introduce sqlite features.

ACKs for top commit:
  l0rinc:
    code review ACK 1d1ae6f0c4
  furszy:
    Other than that, ACK 1d1ae6f0c4
  sedited:
    ACK 1d1ae6f0c4

Tree-SHA512: 41130144972b759b401f990820eaf524d1f17f47d81bf1afea4a529d15a21d253521838a9e31df8f424996582b718a92634ab255204c6fce703b7e47a1d23670
This commit is contained in:
merge-script
2026-05-02 10:27:17 +02:00
7 changed files with 123 additions and 124 deletions

View File

@@ -53,11 +53,14 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
auto bal = GetBalance(wallet); // Cache
bench.run([&] {
if (set_dirty) wallet.MarkDirty();
bal = GetBalance(wallet);
if (add_mine) assert(bal.m_mine_trusted > 0);
});
bench.setup([&] {
if (set_dirty) wallet.MarkDirty();
})
.run([&] {
bal = GetBalance(wallet);
ankerl::nanobench::doNotOptimizeAway(bal);
assert(add_mine == (bal.m_mine_trusted > 0));
});
}
static void WalletBalanceDirty(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/true, /*add_mine=*/true); }

View File

@@ -47,17 +47,21 @@ static void WalletCreate(benchmark::Bench& bench, bool encrypted)
const auto wallet_path = test_setup->m_path_root / "test_wallet";
const auto wallet_name = fs::PathToString(wallet_path);
bench.run([&] {
auto wallet = CreateWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error_string, warnings);
assert(status == DatabaseStatus::SUCCESS);
assert(wallet != nullptr);
std::shared_ptr<CWallet> wallet;
auto cleanup{[&] {
if (!wallet) return;
// Release wallet
RemoveWallet(context, wallet, /*load_on_start=*/ std::nullopt);
RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt);
WaitForDeleteWallet(std::move(wallet));
fs::remove(wallet_path / "wallet.dat");
fs::remove(wallet_path);
}};
bench.setup(cleanup).run([&] {
wallet = CreateWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error_string, warnings);
assert(status == DatabaseStatus::SUCCESS);
assert(wallet != nullptr);
});
cleanup();
}
static void WalletCreatePlain(benchmark::Bench& bench) { WalletCreate(bench, /*encrypted=*/false); }

View File

@@ -30,48 +30,47 @@ static void WalletEncrypt(benchmark::Bench& bench, unsigned int key_count)
context.chain = test_setup->m_node.chain.get();
uint64_t create_flags = WALLET_FLAG_DESCRIPTORS;
auto database = CreateMockableWalletDatabase();
auto wallet = TestCreateWallet(std::move(database), context, create_flags);
{
LOCK(wallet->cs_wallet);
for (unsigned int i = 0; i < key_count; i++) {
CKey key = GenerateRandomKey();
FlatSigningProvider keys;
std::string error;
std::vector<std::unique_ptr<Descriptor>> desc = Parse("combo(" + EncodeSecret(key) + ")", keys, error, /*require_checksum=*/false);
WalletDescriptor w_desc(std::move(desc.at(0)), /*creation_time=*/0, /*range_start=*/0, /*range_end=*/0, /*next_index=*/0);
Assert(wallet->AddWalletDescriptor(w_desc, keys, /*label=*/"", /*internal=*/false));
}
std::vector<std::pair<WalletDescriptor, FlatSigningProvider>> descs;
descs.reserve(key_count);
for (unsigned int i = 0; i < key_count; i++) {
CKey key = GenerateRandomKey();
FlatSigningProvider keys;
std::string error;
std::vector<std::unique_ptr<Descriptor>> desc = Parse("combo(" + EncodeSecret(key) + ")", keys, error, /*require_checksum=*/false);
WalletDescriptor w_desc(std::move(desc.at(0)), /*creation_time=*/0, /*range_start=*/0, /*range_end=*/0, /*next_index=*/0);
descs.emplace_back(w_desc, keys);
}
database = DuplicateMockDatabase(wallet->GetDatabase());
// reload the wallet for the actual benchmark
TestUnloadWallet(std::move(wallet));
// Setting a mock time is necessary to force default derive iteration count during
// wallet encryption.
SetMockTime(1);
// This benchmark has a lot of overhead, this should be good enough to catch
// any regressions, but for an accurate measurement of how long wallet
// encryption takes, this should be reworked after something like
// https://github.com/bitcoin/bitcoin/pull/34208 is merged.
bench.batch(key_count).unit("key").run([&] {
wallet = TestLoadWallet(std::move(database), context);
std::unique_ptr<WalletDatabase> database;
std::shared_ptr<CWallet> wallet;
bench.batch(key_count).unit("key").setup([&] {
if (wallet) {
TestUnloadWallet(std::move(wallet));
}
// Save a copy of the db before encrypting
database = DuplicateMockDatabase(wallet->GetDatabase());
std::unique_ptr<WalletDatabase> database = CreateMockableWalletDatabase();
wallet = TestCreateWallet(std::move(database), context, create_flags);
wallet->EncryptWallet(secure_pass);
{
LOCK(wallet->cs_wallet);
for (auto& [desc, keys] : descs) {
Assert(wallet->AddWalletDescriptor(desc, keys, /*label=*/"", /*internal=*/false));
}
}
})
.run([&] {
wallet->EncryptWallet(secure_pass);
for (const auto& [_, key] : wallet->mapMasterKeys){
assert(key.nDeriveIterations == CMasterKey::DEFAULT_DERIVE_ITERATIONS);
}
TestUnloadWallet(std::move(wallet));
});
for (const auto& [_, key] : wallet->mapMasterKeys){
assert(key.nDeriveIterations == CMasterKey::DEFAULT_DERIVE_ITERATIONS);
}
});
TestUnloadWallet(std::move(wallet));
}
constexpr unsigned int KEY_COUNT = 2000;

View File

@@ -42,7 +42,12 @@ static void WalletLoadingDescriptors(benchmark::Bench& bench)
// Setup the wallet
// Loading the wallet will also create it
uint64_t create_flags = WALLET_FLAG_DESCRIPTORS;
auto database = CreateMockableWalletDatabase();
DatabaseStatus status;
DatabaseOptions options;
options.require_format = DatabaseFormat::SQLITE;
options.require_create = true;
bilingual_str error;
auto database = MakeWalletDatabase("", options, status, error);
auto wallet = TestCreateWallet(std::move(database), context, create_flags);
// Generate a bunch of transactions and addresses to put into the wallet
@@ -50,18 +55,20 @@ static void WalletLoadingDescriptors(benchmark::Bench& bench)
AddTx(*wallet);
}
database = DuplicateMockDatabase(wallet->GetDatabase());
options.require_create = false;
options.require_existing = true;
// reload the wallet for the actual benchmark
bench.epochs(5)
.setup([&] {
TestUnloadWallet(std::move(wallet));
database = MakeWalletDatabase("", options, status, error);
})
.run([&] {
wallet = TestLoadWallet(std::move(database), context);
});
// Cleanup
TestUnloadWallet(std::move(wallet));
bench.epochs(5).run([&] {
wallet = TestLoadWallet(std::move(database), context);
// Cleanup
database = DuplicateMockDatabase(wallet->GetDatabase());
TestUnloadWallet(std::move(wallet));
});
}
BENCHMARK(WalletLoadingDescriptors);

View File

@@ -27,53 +27,64 @@ static void WalletMigration(benchmark::Bench& bench)
// Number of imported watch only addresses
int NUM_WATCH_ONLY_ADDR = 20;
// 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()};
// 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());
}
// 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());
}
// Add watch-only addresses
std::vector<std::pair<CScript, CTxDestination>> scripts_watch_only;
for (int w = 0; w < NUM_WATCH_ONLY_ADDR; ++w) {
CKey key = GenerateRandomKey();
const PKHash dest{key.GetPubKey()};
scripts_watch_only.emplace_back(GetScriptForDestination(dest), dest);
}
bench.epochs(/*numEpochs=*/1).epochIterations(/*numIters=*/1) // run the migration exactly once
.run([&] {
auto res{MigrateLegacyToDescriptor(std::move(wallet), /*passphrase=*/"", *loader->context())};
assert(res);
assert(res->wallet);
assert(res->watchonly_wallet);
});
// Generate transactions and local addresses
std::vector<CKey> keys(500);
std::ranges::generate(keys, []{ return GenerateRandomKey(); });
std::unique_ptr<CWallet> wallet;
size_t i = 0;
bench.epochs(/*numEpochs=*/1) // run the migration exactly once
.setup([&] {
// Setup legacy wallet
wallet = std::make_unique<CWallet>(test_setup->m_node.chain.get(), std::string(i++, 'A'), CreateMockableWalletDatabase());
LegacyDataSPKM* legacy_spkm = wallet->GetOrCreateLegacyDataSPKM();
WalletBatch batch{wallet->GetDatabase()};
LOCK(wallet->cs_wallet);
// Write a best block record as migration expects one to exist
CBlockLocator loc;
batch.WriteBestBlock(loc);
// Add watch-only addresses
for (size_t w = 0; w < scripts_watch_only.size(); ++w) {
const auto& [script, dest] = scripts_watch_only.at(w);
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 (size_t j = 0; j < keys.size(); ++j) {
const CKey& key = keys.at(j);
// Load key, scripts and create address book record
CPubKey pubkey = key.GetPubKey();
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).first);
mtx.vin.resize(2);
wallet->AddToWallet(MakeTransactionRef(mtx), TxStateInactive{}, /*update_wtx=*/nullptr, /*rescanning_old_block=*/true);
batch.WriteKey(pubkey, key.GetPrivKey(), CKeyMetadata());
}
})
.run([&] {
auto res{MigrateLegacyToDescriptor(std::move(wallet), /*passphrase=*/"", *loader->context())};
assert(res);
assert(res->wallet);
assert(res->watchonly_wallet);
});
}
BENCHMARK(WalletMigration);

View File

@@ -7,7 +7,6 @@
#include <chain.h>
#include <key.h>
#include <key_io.h>
#include <streams.h>
#include <test/util/setup_common.h>
#include <validationinterface.h>
#include <wallet/context.h>
@@ -105,27 +104,6 @@ void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet)
WaitForDeleteWallet(std::move(wallet));
}
std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database)
{
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)
{
constexpr auto output_type = OutputType::BECH32;

View File

@@ -39,9 +39,6 @@ std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context);
std::shared_ptr<CWallet> TestLoadWallet(std::unique_ptr<WalletDatabase> database, WalletContext& context);
void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet);
// Creates a copy of the provided database
std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database);
/** Returns a new encoded destination from the wallet (hardcoded to BECH32) */
std::string getnewaddress(CWallet& w);
/** Returns a new destination, of an specific type, from the wallet */