diff --git a/include/bitcoin/database/impl/query/optional.ipp b/include/bitcoin/database/impl/query/optional.ipp index 30eeea9d..da8f887e 100644 --- a/include/bitcoin/database/impl/query/optional.ipp +++ b/include/bitcoin/database/impl/query/optional.ipp @@ -33,12 +33,85 @@ namespace database { // Address (natural-keyed). // ---------------------------------------------------------------------------- -// Pushing into vectors is more efficient than precomputation of size. +// private/static TEMPLATE -code CLASS::get_address_outputs(const std::atomic_bool& cancel, +template +inline code CLASS::parallel_address_transform(const std::atomic_bool& cancel, + outpoints& out, const output_links& links, Functor&& functor) NOEXCEPT +{ + constexpr auto parallel = poolstl::execution::par; + + std::atomic_bool fail{}; + std::vector outpoints(links.size()); + std::transform(parallel, links.begin(), links.end(), outpoints.begin(), + [&functor, &fail](const auto& link) NOEXCEPT + { + return functor(link, fail); + }); + + out.clear(); + if (fail) return error::integrity; + if (cancel) return error::canceled; + for (auto& outpoint: outpoints) + { + if (cancel) return error::canceled; + if (outpoint.point().index() != point::null_index) + out.insert(std::move(outpoint)); + } + + return error::success; +} + +// protected +TEMPLATE +code CLASS::to_address_outputs(const std::atomic_bool& cancel, + output_links& out, const hash_digest& key) const NOEXCEPT +{ + // Pushing into the vector is more efficient than precomputation of size. + out.clear(); + for (auto it = store_.address.it(key); it; ++it) + { + if (cancel) + return error::canceled; + + table::address::record address{}; + if (!store_.address.get(it, address)) + return error::integrity; + + out.push_back(address.output_fk); + } + + return error::success; +} + +// protected +TEMPLATE +code CLASS::get_address_outputs_turbo(const std::atomic_bool& cancel, outpoints& out, const hash_digest& key) const NOEXCEPT { + out.clear(); + output_links links{}; + if (const code ec = to_address_outputs(cancel, links, key)) + return ec; + + return parallel_address_transform(cancel, out, links, + [this, &cancel](const auto& link, auto& fail) NOEXCEPT + { + if (cancel || fail) return outpoint{}; + auto outpoint = get_spent(link); + fail = (outpoint.point().index() == point::null_index); + return outpoint; + }); +} + +TEMPLATE +code CLASS::get_address_outputs(const std::atomic_bool& cancel, + outpoints& out, const hash_digest& key, bool turbo) const NOEXCEPT +{ + if (turbo && store_.turbo()) + return get_address_outputs_turbo(cancel, out, key); + out.clear(); for (auto it = store_.address.it(key); it; ++it) { @@ -55,10 +128,35 @@ code CLASS::get_address_outputs(const std::atomic_bool& cancel, return error::success; } +// protected TEMPLATE -code CLASS::get_confirmed_unspent_outputs(const std::atomic_bool& cancel, +code CLASS::get_confirmed_unspent_outputs_turbo(const std::atomic_bool& cancel, outpoints& out, const hash_digest& key) const NOEXCEPT { + out.clear(); + output_links links{}; + if (const code ec = to_address_outputs(cancel, links, key)) + return ec; + + return parallel_address_transform(cancel, out, links, + [this, &cancel](const auto& link, auto& fail) NOEXCEPT + { + if (cancel || fail || !is_confirmed_unspent(link)) + return outpoint{}; + + auto outpoint = get_spent(link); + fail = (outpoint.point().index() == point::null_index); + return outpoint; + }); +} + +TEMPLATE +code CLASS::get_confirmed_unspent_outputs(const std::atomic_bool& cancel, + outpoints& out, const hash_digest& key, bool turbo) const NOEXCEPT +{ + if (turbo && store_.turbo()) + return get_confirmed_unspent_outputs_turbo(cancel, out, key); + out.clear(); for (auto it = store_.address.it(key); it; ++it) { @@ -76,10 +174,46 @@ code CLASS::get_confirmed_unspent_outputs(const std::atomic_bool& cancel, return error::success; } +// protected TEMPLATE -code CLASS::get_minimum_unspent_outputs(const std::atomic_bool& cancel, +code CLASS::get_minimum_unspent_outputs_turbo(const std::atomic_bool& cancel, outpoints& out, const hash_digest& key, uint64_t minimum) const NOEXCEPT { + out.clear(); + output_links links{}; + if (const code ec = to_address_outputs(cancel, links, key)) + return ec; + + return parallel_address_transform(cancel, out, links, + [this, &cancel, minimum](const auto& link, auto& fail) NOEXCEPT + { + if (cancel || fail || !is_confirmed_unspent(link)) + return outpoint{}; + + uint64_t value{}; + if (!get_value(value, link)) + { + fail = true; + return outpoint{}; + } + + if (value < minimum) + return outpoint{}; + + auto outpoint = get_spent(link); + fail = (outpoint.point().index() == point::null_index); + return outpoint; + }); +} + +TEMPLATE +code CLASS::get_minimum_unspent_outputs(const std::atomic_bool& cancel, + outpoints& out, const hash_digest& key, + uint64_t minimum, bool turbo) const NOEXCEPT +{ + if (turbo && store_.turbo()) + return get_minimum_unspent_outputs_turbo(cancel, out, key, minimum); + out.clear(); for (auto it = store_.address.it(key); it; ++it) { @@ -106,10 +240,10 @@ code CLASS::get_minimum_unspent_outputs(const std::atomic_bool& cancel, TEMPLATE code CLASS::get_confirmed_balance(const std::atomic_bool& cancel, - uint64_t& balance, const hash_digest& key) const NOEXCEPT + uint64_t& balance, const hash_digest& key, bool turbo) const NOEXCEPT { outpoints outs{}; - if (code ec = get_confirmed_unspent_outputs(cancel, outs, key)) + if (code ec = get_confirmed_unspent_outputs(cancel, outs, key, turbo)) { balance = zero; return ec; diff --git a/include/bitcoin/database/impl/store.ipp b/include/bitcoin/database/impl/store.ipp index ce8a82d3..9b88445d 100644 --- a/include/bitcoin/database/impl/store.ipp +++ b/include/bitcoin/database/impl/store.ipp @@ -225,6 +225,12 @@ CLASS::store(const settings& config) NOEXCEPT { } +TEMPLATE +bool CLASS::turbo() const NOEXCEPT +{ + return configuration_.turbo; +} + TEMPLATE code CLASS::create(const event_handler& handler) NOEXCEPT { diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 5e2c1fb5..90dcd471 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -313,6 +313,10 @@ class query point_link top_point(size_t bucket) const NOEXCEPT; tx_link top_tx(size_t bucket) const NOEXCEPT; + /// optional enumeration + code to_address_outputs(const std::atomic_bool& cancel, + output_links& out, const hash_digest& key) const NOEXCEPT; + /// Archive reads. /// ----------------------------------------------------------------------- @@ -550,13 +554,14 @@ class query /// ----------------------------------------------------------------------- code get_address_outputs(const std::atomic_bool& cancel, - outpoints& out, const hash_digest& key) const NOEXCEPT; + outpoints& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; code get_confirmed_unspent_outputs(const std::atomic_bool& cancel, - outpoints& out, const hash_digest& key) const NOEXCEPT; + outpoints& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; code get_minimum_unspent_outputs(const std::atomic_bool& cancel, - outpoints& out, const hash_digest& key, uint64_t value) const NOEXCEPT; + outpoints& out, const hash_digest& key, uint64_t value, + bool turbo=false) const NOEXCEPT; code get_confirmed_balance(const std::atomic_bool& cancel, - uint64_t& balance, const hash_digest& key) const NOEXCEPT; + uint64_t& balance, const hash_digest& key, bool turbo=false) const NOEXCEPT; bool is_filtered_body(const header_link& link) const NOEXCEPT; bool get_filter_body(filter& out, const header_link& link) const NOEXCEPT; @@ -687,6 +692,16 @@ class query const system::settings& settings, const header& header, const header_link& link, size_t height) const NOEXCEPT; + /// address + /// ----------------------------------------------------------------------- + + code get_address_outputs_turbo(const std::atomic_bool& cancel, + outpoints& out, const hash_digest& key) const NOEXCEPT; + code get_confirmed_unspent_outputs_turbo(const std::atomic_bool& cancel, + outpoints& out, const hash_digest& key) const NOEXCEPT; + code get_minimum_unspent_outputs_turbo(const std::atomic_bool& cancel, + outpoints& out, const hash_digest& key, uint64_t minimum) const NOEXCEPT; + /// tx_fk must be allocated. /// ----------------------------------------------------------------------- code set_code(const tx_link& tx_fk, const transaction& tx) NOEXCEPT; @@ -696,6 +711,10 @@ class query template static inline bool push_bool(std_vector& stack, const Bool& element) NOEXCEPT; + template + static inline code parallel_address_transform( + const std::atomic_bool& cancel, outpoints& out, + const output_links& links, Functor&& functor) NOEXCEPT; // Not thread safe. size_t get_fork_() const NOEXCEPT; diff --git a/include/bitcoin/database/settings.hpp b/include/bitcoin/database/settings.hpp index 4f06f623..ab606f67 100644 --- a/include/bitcoin/database/settings.hpp +++ b/include/bitcoin/database/settings.hpp @@ -37,6 +37,7 @@ struct BCD_API settings settings(system::chain::selection context) NOEXCEPT; /// Properties. + bool turbo; std::filesystem::path path; /// Archives. diff --git a/include/bitcoin/database/store.hpp b/include/bitcoin/database/store.hpp index ac90f57a..72061d69 100644 --- a/include/bitcoin/database/store.hpp +++ b/include/bitcoin/database/store.hpp @@ -54,6 +54,12 @@ class store /// Construct a store from settings. store(const settings& config) NOEXCEPT; + /// Properties + /// ----------------------------------------------------------------------- + + /// Allow full throttle concurrent query execution (may use 100% CPU). + bool turbo() const NOEXCEPT; + /// Methods. /// ----------------------------------------------------------------------- diff --git a/src/settings.cpp b/src/settings.cpp index a2f23f77..c075b275 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -27,7 +27,8 @@ namespace database { using namespace bc::system; settings::settings() NOEXCEPT - : path{ "bitcoin" }, + : turbo{ false }, + path{ "bitcoin" }, // Archives. diff --git a/test/query/optional.cpp b/test/query/optional.cpp index b4d1b92e..2a463ba0 100644 --- a/test/query/optional.cpp +++ b/test/query/optional.cpp @@ -27,6 +27,42 @@ const auto events_handler = [](auto, auto) {}; const auto genesis_address = test::genesis.transactions_ptr()->front()->outputs_ptr()->front()->script().hash(); +// to_address_outputs + +BOOST_AUTO_TEST_CASE(query_optional__to_address_outputs__genesis__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + output_links out{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.to_address_outputs(cancel, out, genesis_address)); + BOOST_REQUIRE_EQUAL(out.size(), 1u); + BOOST_REQUIRE_EQUAL(out.front(), query.to_output(0, 0)); +} + +// get_address_outputs + +BOOST_AUTO_TEST_CASE(query_optional__get_address_outputs__turbo_genesis__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + outpoints out{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.get_address_outputs(cancel, out, genesis_address, true)); + BOOST_REQUIRE_EQUAL(out.size(), 1u); + ////BOOST_REQUIRE_EQUAL(out.front(), query.to_output(0, 0)); +} + BOOST_AUTO_TEST_CASE(query_optional__get_address_outputs__genesis__expected) { settings settings{}; @@ -38,7 +74,7 @@ BOOST_AUTO_TEST_CASE(query_optional__get_address_outputs__genesis__expected) outpoints out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_address_outputs(cancel, out, genesis_address)); + BOOST_REQUIRE(!query.get_address_outputs(cancel, out, genesis_address)); BOOST_REQUIRE_EQUAL(out.size(), 1u); ////BOOST_REQUIRE_EQUAL(out.front(), query.to_output(0, 0)); } @@ -58,6 +94,24 @@ BOOST_AUTO_TEST_CASE(query_optional__get_address_outputs__cancel__canceled_false BOOST_REQUIRE(out.empty()); } +// get_confirmed_unspent_outputs + +BOOST_AUTO_TEST_CASE(query_optional__get_confirmed_unspent_outputs__turbo_genesis__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + outpoints out{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.get_confirmed_unspent_outputs(cancel, out, genesis_address, true)); + BOOST_REQUIRE_EQUAL(out.size(), 1u); + ////BOOST_REQUIRE_EQUAL(out.front(), 0); +} + BOOST_AUTO_TEST_CASE(query_optional__get_confirmed_unspent_outputs__genesis__expected) { settings settings{}; @@ -74,6 +128,23 @@ BOOST_AUTO_TEST_CASE(query_optional__get_confirmed_unspent_outputs__genesis__exp ////BOOST_REQUIRE_EQUAL(out.front(), 0); } +// get_minimum_unspent_outputs + +BOOST_AUTO_TEST_CASE(query_optional__get_minimum_unspent_outputs__turbo_above__excluded) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + outpoints out{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, genesis_address, 5000000001, true)); + BOOST_REQUIRE(out.empty()); +} + BOOST_AUTO_TEST_CASE(query_optional__get_minimum_unspent_outputs__above__excluded) { settings settings{}; @@ -124,6 +195,23 @@ BOOST_AUTO_TEST_CASE(query_optional__get_minimum_unspent_outputs__below__include ////BOOST_REQUIRE_EQUAL(out.front(), 0); } +// get_confirmed_balance + +BOOST_AUTO_TEST_CASE(query_optional__get_confirmed_balance__turbo_genesis__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE_EQUAL(store.create(events_handler), error::success); + BOOST_REQUIRE(query.initialize(test::genesis)); + + uint64_t out{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.get_confirmed_balance(cancel, out, genesis_address, true)); + BOOST_REQUIRE_EQUAL(out, 5000000000u); +} + BOOST_AUTO_TEST_CASE(query_optional__get_confirmed_balance__genesis__expected) { settings settings{}; diff --git a/test/settings.cpp b/test/settings.cpp index 5250f116..f8beee0a 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -23,6 +23,7 @@ BOOST_AUTO_TEST_SUITE(settings_tests) BOOST_AUTO_TEST_CASE(settings__construct__default__expected) { database::settings configuration; + BOOST_REQUIRE_EQUAL(configuration.turbo, false); BOOST_REQUIRE_EQUAL(configuration.path, "bitcoin"); // Archives.