diff --git a/include/bitcoin/database/impl/query/pool.ipp b/include/bitcoin/database/impl/query/pool.ipp index a3d690037..7c6e6b869 100644 --- a/include/bitcoin/database/impl/query/pool.ipp +++ b/include/bitcoin/database/impl/query/pool.ipp @@ -71,6 +71,19 @@ bool CLASS::is_confirmable_spent(const output_link& link) const NOEXCEPT return false; } +TEMPLATE +ins_link CLASS::find_confirmable_spender(const point& prevout) const NOEXCEPT +{ + for (const auto& in: to_spenders(prevout)) + { + const auto tx = to_input_tx(in); + if (is_pooled(tx) && is_confirmable_tx(tx)) + return in; + } + + return {}; +} + TEMPLATE code CLASS::get_pooled(pooled_tx& out, const tx_link& link, const context& ctx) const NOEXCEPT @@ -185,6 +198,18 @@ tx_links CLASS::to_confirmable_children(const tx_link& link) const NOEXCEPT return out; } +TEMPLATE +tx_links CLASS::to_confirmable_ancestors(const tx_link& link) const NOEXCEPT +{ + return to_confirmable_relatives(link, true); +} + +TEMPLATE +tx_links CLASS::to_confirmable_descendants(const tx_link& link) const NOEXCEPT +{ + return to_confirmable_relatives(link, false); +} + TEMPLATE bool CLASS::get_ancestor_totals(fee_totals& out, const tx_link& link) const NOEXCEPT @@ -376,31 +401,50 @@ bool CLASS::get_confirmable_totals(fee_totals& out, const tx_link& link, bool ancestors) const NOEXCEPT { using namespace system; + auto links = to_confirmable_relatives(link, ancestors); + links.push_back(link.value); + out = {}; - std::vector pending{ link.value }; - std::unordered_set visited{ link.value }; - while (!pending.empty()) + for (const auto fk: links) { - const tx_link next{ pending.back() }; - pending.pop_back(); - fee_rate rate{}; - if (!get_tx_fees(rate, next)) + if (!get_tx_fees(rate, fk)) return false; ++out.count; out.bytes = ceilinged_add(out.bytes, rate.bytes); out.fee = ceilinged_add(out.fee, rate.fee); + } + + return true; +} + +TEMPLATE +tx_links CLASS::to_confirmable_relatives(const tx_link& link, + bool ancestors) const NOEXCEPT +{ + tx_links out{}; + std::vector pending{ link.value }; + std::unordered_set visited{ link.value }; + while (!pending.empty()) + { + const tx_link next{ pending.back() }; + pending.pop_back(); const auto links = ancestors ? to_confirmable_parents(next) : to_confirmable_children(next); for (const auto fk: links) + { if (visited.insert(fk).second) + { + out.push_back(fk); pending.push_back(fk); + } + } } - return true; + return out; } // writers diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index d35b4ad80..3cc6c7551 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -726,6 +726,9 @@ class query /// Spent by a pooled and confirmable tx. bool is_confirmable_spent(const output_link& link) const NOEXCEPT; + /// Spender of prevout in a pooled and confirmable tx, terminal if none. + ins_link find_confirmable_spender(const point& prevout) const NOEXCEPT; + /// Confirmable tx links, from cursor toward end, at most limit. code get_confirmable_txs(pool_link& cursor, tx_links& out, const pool_link& end, size_t limit) const NOEXCEPT; @@ -737,6 +740,10 @@ class query tx_links to_confirmable_parents(const tx_link& link) const NOEXCEPT; tx_links to_confirmable_children(const tx_link& link) const NOEXCEPT; + /// Confirmable ancestors, or descendants, of the tx (excluding the tx). + tx_links to_confirmable_ancestors(const tx_link& link) const NOEXCEPT; + tx_links to_confirmable_descendants(const tx_link& link) const NOEXCEPT; + /// Totals of the tx with its confirmable ancestors, or descendants. bool get_ancestor_totals(fee_totals& out, const tx_link& link) const NOEXCEPT; @@ -1119,6 +1126,10 @@ class query bool is_confirmable_tx(confirmables& memo, const tx_link& link) const NOEXCEPT; + /// Confirmable ancestors or descendants of the tx (excluding the tx). + tx_links to_confirmable_relatives(const tx_link& link, + bool ancestors) const NOEXCEPT; + /// Totals of the tx with its confirmable ancestors or descendants. bool get_confirmable_totals(fee_totals& out, const tx_link& link, bool ancestors) const NOEXCEPT; diff --git a/test/query/pool.cpp b/test/query/pool.cpp index 7b37677ba..a246ece22 100644 --- a/test/query/pool.cpp +++ b/test/query/pool.cpp @@ -184,6 +184,48 @@ BOOST_AUTO_TEST_CASE(query_pool__is_confirmable_spent__conflicted_spender__false BOOST_REQUIRE(!query.is_confirmable_spent(query.to_output(parent.hash(false), 0))); } +// find_confirmable_spender + +BOOST_AUTO_TEST_CASE(query_pool__find_confirmable_spender__pooled_spender__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + store.set_pooling(); + + const auto parent = to_spend(genesis_coinbase(), 2u); + const auto child = to_spend(parent.hash(false), 1u); + const point prevout{ parent.hash(false), 0 }; + BOOST_REQUIRE(archive_pool(query, parent)); + BOOST_REQUIRE(query.find_confirmable_spender(prevout).is_terminal()); + + BOOST_REQUIRE(archive_pool(query, child)); + const auto spender = query.find_confirmable_spender(prevout); + BOOST_REQUIRE(query.to_input_tx(spender) == query.to_tx(child.hash(false))); +} + +BOOST_AUTO_TEST_CASE(query_pool__find_confirmable_spender__conflicted_spender__terminal) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + store.set_pooling(); + + const auto parent = to_spend(genesis_coinbase(), 2u); + const auto child = to_spend(parent.hash(false), 1u); + const point prevout{ parent.hash(false), 0 }; + BOOST_REQUIRE(archive_pool(query, parent)); + BOOST_REQUIRE(archive_pool(query, child)); + BOOST_REQUIRE(!archive_confirm(query, to_spend(genesis_coinbase(), 3u)).is_terminal()); + BOOST_REQUIRE(query.find_confirmable_spender(prevout).is_terminal()); +} + // get_pooled_time BOOST_AUTO_TEST_CASE(query_pool__get_pooled_time__pooled__expected) @@ -739,6 +781,66 @@ BOOST_AUTO_TEST_CASE(query_pool__to_confirmable_children__conflicted_child__empt BOOST_REQUIRE(query.to_confirmable_children(parent_link).empty()); } +// to_confirmable_ancestors + +BOOST_AUTO_TEST_CASE(query_pool__to_confirmable_ancestors__pooled_chain__ancestors) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + store.set_pooling(); + + const auto parent = to_spend(genesis_coinbase(), 2u); + const auto child = to_spend(parent.hash(false), 1u); + const auto grandchild = to_spend(child.hash(false), 1u); + BOOST_REQUIRE(archive_pool(query, parent)); + BOOST_REQUIRE(archive_pool(query, child)); + BOOST_REQUIRE(archive_pool(query, grandchild)); + + const auto parent_link = query.to_tx(parent.hash(false)); + const auto child_link = query.to_tx(child.hash(false)); + const auto grandchild_link = query.to_tx(grandchild.hash(false)); + BOOST_REQUIRE(query.to_confirmable_ancestors(parent_link).empty()); + + const auto ancestors = query.to_confirmable_ancestors(grandchild_link); + BOOST_REQUIRE_EQUAL(ancestors.size(), two); + BOOST_REQUIRE(system::contains(ancestors, parent_link.value)); + BOOST_REQUIRE(system::contains(ancestors, child_link.value)); +} + +// to_confirmable_descendants + +BOOST_AUTO_TEST_CASE(query_pool__to_confirmable_descendants__pooled_chain__descendants) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + store.set_pooling(); + + const auto parent = to_spend(genesis_coinbase(), 2u); + const auto child = to_spend(parent.hash(false), 1u); + const auto grandchild = to_spend(child.hash(false), 1u); + BOOST_REQUIRE(archive_pool(query, parent)); + BOOST_REQUIRE(archive_pool(query, child)); + BOOST_REQUIRE(archive_pool(query, grandchild)); + + const auto parent_link = query.to_tx(parent.hash(false)); + const auto child_link = query.to_tx(child.hash(false)); + const auto grandchild_link = query.to_tx(grandchild.hash(false)); + BOOST_REQUIRE(query.to_confirmable_descendants(grandchild_link).empty()); + + const auto descendants = query.to_confirmable_descendants(parent_link); + BOOST_REQUIRE_EQUAL(descendants.size(), two); + BOOST_REQUIRE(system::contains(descendants, child_link.value)); + BOOST_REQUIRE(system::contains(descendants, grandchild_link.value)); +} + // get_ancestor_totals BOOST_AUTO_TEST_CASE(query_pool__get_ancestor_totals__pooled_parent__both)