diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -67,6 +67,7 @@ if ENABLE_WALLET bench_bench_bitcoin_SOURCES += bench/coin_selection.cpp +bench_bench_bitcoin_SOURCES += bench/wallet_balance.cpp endif bench_bench_bitcoin_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) diff --git a/src/bench/CMakeLists.txt b/src/bench/CMakeLists.txt --- a/src/bench/CMakeLists.txt +++ b/src/bench/CMakeLists.txt @@ -69,7 +69,11 @@ target_link_libraries(bitcoin-bench common bitcoinconsensus server) if(BUILD_BITCOIN_WALLET) - target_sources(bitcoin-bench PRIVATE coin_selection.cpp) + target_sources(bitcoin-bench + PRIVATE + coin_selection.cpp + wallet_balance.cpp + ) target_link_libraries(bitcoin-bench wallet) endif() diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp new file mode 100644 --- /dev/null +++ b/src/bench/wallet_balance.cpp @@ -0,0 +1,95 @@ +// Copyright (c) 2012-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include // For Params() +#include +#include +#include +#include +#include +#include +#include + +struct WalletTestingSetup { + std::unique_ptr m_chain = interfaces::MakeChain(); + CWallet m_wallet; + + WalletTestingSetup() + : m_wallet{Params(), m_chain.get(), WalletLocation(), + WalletDatabase::CreateMock()} {} + + void handleNotifications() { + m_wallet.m_chain_notifications_handler = + m_chain->handleNotifications(m_wallet); + } +}; + +static void WalletBalance(benchmark::State &state, const bool set_dirty, + const bool add_watchonly, const bool add_mine) { + const auto &ADDRESS_WATCHONLY = ADDRESS_BCHREG_UNSPENDABLE; + + WalletTestingSetup wallet_t{}; + auto &wallet = wallet_t.m_wallet; + { + bool first_run; + if (wallet.LoadWallet(first_run) != DBErrors::LOAD_OK) { + assert(false); + } + wallet_t.handleNotifications(); + } + + const Config &config = GetConfig(); + + const Optional address_mine{ + add_mine ? Optional{getnewaddress(config, wallet)} + : nullopt}; + if (add_watchonly) { + importaddress(wallet, ADDRESS_WATCHONLY); + } + + for (int i = 0; i < 100; ++i) { + generatetoaddress(config, address_mine.get_value_or(ADDRESS_WATCHONLY)); + generatetoaddress(config, ADDRESS_WATCHONLY); + } + SyncWithValidationInterfaceQueue(); + + // Cache + auto bal = wallet.GetBalance(); + + while (state.KeepRunning()) { + if (set_dirty) { + wallet.MarkDirty(); + } + bal = wallet.GetBalance(); + if (add_mine) { + assert(bal.m_mine_trusted > Amount::zero()); + } + if (add_watchonly) { + assert(bal.m_watchonly_trusted > Amount::zero()); + } + } +} + +static void WalletBalanceDirty(benchmark::State &state) { + WalletBalance(state, /* set_dirty */ true, /* add_watchonly */ true, + /* add_mine */ true); +} +static void WalletBalanceClean(benchmark::State &state) { + WalletBalance(state, /* set_dirty */ false, /* add_watchonly */ true, + /* add_mine */ true); +} +static void WalletBalanceMine(benchmark::State &state) { + WalletBalance(state, /* set_dirty */ false, /* add_watchonly */ false, + /* add_mine */ true); +} +static void WalletBalanceWatch(benchmark::State &state) { + WalletBalance(state, /* set_dirty */ false, /* add_watchonly */ true, + /* add_mine */ false); +} + +BENCHMARK(WalletBalanceDirty, 2500); +BENCHMARK(WalletBalanceClean, 8000); +BENCHMARK(WalletBalanceMine, 16000); +BENCHMARK(WalletBalanceWatch, 8000); diff --git a/src/test/util.h b/src/test/util.h --- a/src/test/util.h +++ b/src/test/util.h @@ -6,6 +6,7 @@ #define BITCOIN_TEST_UTIL_H #include +#include class CBlock; class Config; @@ -13,6 +14,10 @@ class CTxIn; class CWallet; +// Constants // + +extern const std::string ADDRESS_BCHREG_UNSPENDABLE; + // Lower-level utils // /** Returns the generated coin */ @@ -65,4 +70,13 @@ } } +// RPC-like // + +/** Import the address to the wallet */ +void importaddress(CWallet &wallet, const std::string &address); +/** Returns a new address from the wallet */ +std::string getnewaddress(const Config &config, CWallet &w); +/** Returns the generated coin */ +CTxIn generatetoaddress(const Config &config, const std::string &address); + #endif // BITCOIN_TEST_UTIL_H diff --git a/src/test/util.cpp b/src/test/util.cpp --- a/src/test/util.cpp +++ b/src/test/util.cpp @@ -7,10 +7,57 @@ #include #include #include +#include #include #include #include +#ifdef ENABLE_WALLET +#include +#endif // ENABLE_WALLET + +const std::string ADDRESS_BCHREG_UNSPENDABLE = + "bchreg:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqha9s37tt"; + +#ifdef ENABLE_WALLET +std::string getnewaddress(const Config &config, CWallet &w) { + constexpr auto output_type = OutputType::LEGACY; + + CPubKey new_key; + if (!w.GetKeyFromPool(new_key)) { + assert(false); + } + + w.LearnRelatedScripts(new_key, output_type); + const auto dest = GetDestinationForKey(new_key, output_type); + + w.SetAddressBook(dest, /* label */ "", "receive"); + + return EncodeDestination(dest, config); +} + +void importaddress(CWallet &wallet, const std::string &address) { + LOCK(wallet.cs_wallet); + const auto dest = DecodeDestination(address, wallet.chainParams); + assert(IsValidDestination(dest)); + const auto script = GetScriptForDestination(dest); + wallet.MarkDirty(); + assert(!wallet.HaveWatchOnly(script)); + if (!wallet.AddWatchOnly(script, 0 /* nCreateTime */)) { + assert(false); + } + wallet.SetAddressBook(dest, /* label */ "", "receive"); +} +#endif // ENABLE_WALLET + +CTxIn generatetoaddress(const Config &config, const std::string &address) { + const auto dest = DecodeDestination(address, config.GetChainParams()); + assert(IsValidDestination(dest)); + const auto coinbase_script = GetScriptForDestination(dest); + + return MineBlock(config, coinbase_script); +} + CTxIn MineBlock(const Config &config, const CScript &coinbase_scriptPubKey) { auto block = PrepareBlock(config, coinbase_scriptPubKey);