mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-05-12 06:53:11 +02:00
Merge bitcoin/bitcoin#35018: wallet, bench: Use Nanobench setup() for wallet benchmarks, and remove DuplicateMockDatabase
1d1ae6f0c4wallet, test: Remove DuplicateMockDatabase (Ava Chow)57820c472bbench: Utilize setup() for WalletLoading and use a real database (Ava Chow)9a7604fd25bench: Use setup() in WalletMigration to prepare the legacy wallet (Ava Chow)426a94e7bdbench: Utilize setup() in WalletEncrypt to create the encryption wallet (Ava Chow)d672455d20bench: Utilitze setup() in WalletBalance for marking caches dirty (Ava Chow)61412ef887bench: 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 ACK1d1ae6f0c4furszy: Other than that, ACK1d1ae6f0c4sedited: ACK1d1ae6f0c4Tree-SHA512: 41130144972b759b401f990820eaf524d1f17f47d81bf1afea4a529d15a21d253521838a9e31df8f424996582b718a92634ab255204c6fce703b7e47a1d23670
This commit is contained in:
@@ -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); }
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user