diff --git a/include/bitcoin/database/impl/query/height.ipp b/include/bitcoin/database/impl/query/height.ipp index bcc8169c..92f40a33 100644 --- a/include/bitcoin/database/impl/query/height.ipp +++ b/include/bitcoin/database/impl/query/height.ipp @@ -149,6 +149,9 @@ header_states CLASS::get_validated_fork(size_t& fork_point, out.reserve(one); code ec{}; + // Disable filter constraint if filtering is disabled. + filter &= filter_enabled(); + /////////////////////////////////////////////////////////////////////////// std::shared_lock interlock{ candidate_reorganization_mutex_ }; @@ -156,7 +159,7 @@ header_states CLASS::get_validated_fork(size_t& fork_point, auto height = add1(fork_point); auto link = to_candidate(height); while (is_block_validated(ec, link, height, top_checkpoint) && - (!filter || is_filtered(link))) + (!filter || is_filtered_body(link))) { out.emplace_back(link, ec); link = to_candidate(++height); diff --git a/include/bitcoin/database/impl/query/network.ipp b/include/bitcoin/database/impl/query/network.ipp index 55717e74..e3c5ccca 100644 --- a/include/bitcoin/database/impl/query/network.ipp +++ b/include/bitcoin/database/impl/query/network.ipp @@ -120,6 +120,28 @@ size_t CLASS::get_fork(const hashes& locator) const NOEXCEPT return zero; } +TEMPLATE +bool CLASS::get_ancestry(header_links& ancestry, const header_link& descendant, + size_t count) const NOEXCEPT +{ + size_t height{}; + if (!get_height(height, descendant)) + return false; + + // Limit to genesis. + count = std::min(add1(height), count); + ancestry.resize(count); + auto link = descendant; + + // Ancestry navigation ensures continuity without locks. + // If count is zero then not even descendant is pushed. + // link terminal if previous was genesis (avoided by count <= height). + for (auto& ancestor: ancestry) + link = to_parent((ancestor = link)); + + return true; +} + } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/impl/query/optional.ipp b/include/bitcoin/database/impl/query/optional.ipp index 376f5381..d4a5fc19 100644 --- a/include/bitcoin/database/impl/query/optional.ipp +++ b/include/bitcoin/database/impl/query/optional.ipp @@ -19,6 +19,8 @@ #ifndef LIBBITCOIN_DATABASE_QUERY_OPTIONAL_IPP #define LIBBITCOIN_DATABASE_QUERY_OPTIONAL_IPP +#include +#include #include #include #include @@ -152,6 +154,12 @@ bool CLASS::get_confirmed_balance(uint64_t& out, // filter_tx (surrogate-keyed). // ---------------------------------------------------------------------------- +TEMPLATE +bool CLASS::is_filtered_body(const header_link& link) const NOEXCEPT +{ + return store_.filter_tx.exists(to_filter_tx(link)); +} + TEMPLATE bool CLASS::get_filter_body(filter& out, const header_link& link) const NOEXCEPT { @@ -163,18 +171,78 @@ bool CLASS::get_filter_body(filter& out, const header_link& link) const NOEXCEPT return true; } +// filter_bk (surrogate-keyed). +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::is_filtered_head(const header_link& link) const NOEXCEPT +{ + return store_.filter_bk.exists(to_filter_bk(link)); +} + TEMPLATE bool CLASS::get_filter_head(hash_digest& out, const header_link& link) const NOEXCEPT { - table::filter_bk::get_head filter_bk{}; + table::filter_bk::get_head_only filter_bk{}; if (!store_.filter_bk.at(to_filter_bk(link), filter_bk)) return false; - + out = std::move(filter_bk.head); return true; } +TEMPLATE +bool CLASS::get_filter_hash(hash_digest& out, + const header_link& link) const NOEXCEPT +{ + table::filter_bk::get_hash_only filter_bk{}; + if (!store_.filter_bk.at(to_filter_bk(link), filter_bk)) + return false; + + out = std::move(filter_bk.hash); + return true; +} + +TEMPLATE +bool CLASS::get_filter_hashes(hashes& filter_hashes, + hash_digest& previous_header, const header_link& stop_link, + size_t count) const NOEXCEPT +{ + size_t height{}; + if (!get_height(height, stop_link)) + return false; + + count = std::min(add1(height), count); + filter_hashes.resize(count); + auto link = stop_link; + + // Reversal allows ancenstry population into forward vector. + for (auto& hash: std::views::reverse(filter_hashes)) + { + // Implies that stop_link is not a filtered block. + if (!get_filter_hash(hash, link)) + return false; + + // Ancestry from stop link (included) ensures continuity without locks. + link = to_parent(link); + } + + // There's no trailing increment without at least one loop iteration. + if (is_zero(count)) + link = to_parent(link); + + // link is genesis, previous is null. + if (link.is_terminal()) + { + previous_header = system::null_hash; + return true; + } + + // Obtaining previous from ancestry eansures its continuity as well. + return get_filter_head(previous_header, link); +} + // set_filter_body // ---------------------------------------------------------------------------- @@ -213,14 +281,6 @@ bool CLASS::set_filter_body(const header_link& link, // set_filter_head // ---------------------------------------------------------------------------- -TEMPLATE -bool CLASS::is_filtered(const header_link& link) const NOEXCEPT -{ - // The current block has been filtered. So when order is imposed in confirm - // this can be checked in the case of bump events (maybe not validated). - return !filter_enabled() || store_.filter_tx.exists(to_filter_tx(link)); -} - TEMPLATE bool CLASS::set_filter_head(const header_link& link) NOEXCEPT { @@ -240,13 +300,14 @@ bool CLASS::set_filter_head(const header_link& link) NOEXCEPT if (!get_filter_head(previous, parent)) return false; - // Use the previous head and current body to compute the current head. - return set_filter_head(link, compute_filter_header(previous, body)); + // Use previous head and current body to compute current hash and head. + hash_digest hash{}; + return set_filter_head(link, compute_header(hash, previous, body), hash); } TEMPLATE -bool CLASS::set_filter_head(const header_link& link, - const hash_digest& head) NOEXCEPT +bool CLASS::set_filter_head(const header_link& link, const hash_digest& head, + const hash_digest& hash) NOEXCEPT { if (!filter_enabled()) return true; @@ -258,6 +319,7 @@ bool CLASS::set_filter_head(const header_link& link, return store_.filter_bk.put(to_filter_bk(link), table::filter_bk::put_ref { {}, + hash, head }); // ======================================================================== diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index cae1bb7a..08c25273 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -550,6 +550,10 @@ class query hashes get_blocks(const hashes& locator, const hash_digest& stop, size_t limit) const NOEXCEPT; + /// Get descending list of ancestry starting with descendant (inclusive). + bool get_ancestry(header_links& ancestry, const header_link& descendant, + size_t count) const NOEXCEPT; + /// Optional Tables. /// ----------------------------------------------------------------------- @@ -563,14 +567,19 @@ class query bool get_confirmed_balance(uint64_t& out, const hash_digest& key) const NOEXCEPT; - bool is_filtered(const header_link& link) const NOEXCEPT; + bool is_filtered_body(const header_link& link) const NOEXCEPT; bool get_filter_body(filter& out, const header_link& link) const NOEXCEPT; - bool get_filter_head(hash_digest& out, const header_link& link) const NOEXCEPT; bool set_filter_body(const header_link& link, const block& block) NOEXCEPT; bool set_filter_body(const header_link& link, const filter& body) NOEXCEPT; + + bool is_filtered_head(const header_link& link) const NOEXCEPT; + bool get_filter_head(hash_digest& out, const header_link& link) const NOEXCEPT; + bool get_filter_hash(hash_digest& out, const header_link& link) const NOEXCEPT; + bool get_filter_hashes(hashes& filter_hashes, hash_digest& previous_header, + const header_link& stop_link, size_t count) const NOEXCEPT; bool set_filter_head(const header_link& link) NOEXCEPT; - bool set_filter_head(const header_link& link, - const hash_digest& head) NOEXCEPT; + bool set_filter_head(const header_link& link, const hash_digest& head, + const hash_digest& hash) NOEXCEPT; protected: struct span diff --git a/include/bitcoin/database/tables/optionals/filter_bk.hpp b/include/bitcoin/database/tables/optionals/filter_bk.hpp index 44c729a6..42021f4c 100644 --- a/include/bitcoin/database/tables/optionals/filter_bk.hpp +++ b/include/bitcoin/database/tables/optionals/filter_bk.hpp @@ -39,23 +39,52 @@ struct filter_bk { inline bool from_data(reader& source) NOEXCEPT { + hash = source.read_hash(); head = source.read_hash(); return source; } + hash_digest hash{}; hash_digest head{}; }; + struct get_head_only + : public schema::filter_bk + { + inline bool from_data(reader& source) NOEXCEPT + { + source.skip_bytes(system::hash_size); + head = source.read_hash(); + return source; + } + + hash_digest head{}; + }; + + struct get_hash_only + : public schema::filter_bk + { + inline bool from_data(reader& source) NOEXCEPT + { + hash = source.read_hash(); + return source; + } + + hash_digest hash{}; + }; + struct put_ref : public schema::filter_bk { inline bool to_data(finalizer& sink) const NOEXCEPT { + sink.write_bytes(hash); sink.write_bytes(head); BC_ASSERT(!sink || sink.get_write_position() == count() * minrow); return sink; } + const hash_digest& hash{}; const hash_digest& head{}; }; }; diff --git a/include/bitcoin/database/tables/schema.hpp b/include/bitcoin/database/tables/schema.hpp index 79cbb4c6..dbecf34d 100644 --- a/include/bitcoin/database/tables/schema.hpp +++ b/include/bitcoin/database/tables/schema.hpp @@ -363,12 +363,13 @@ struct filter_bk static constexpr size_t pk = schema::header::pk; using link = linkage; static constexpr size_t minsize = + schema::hash + schema::hash; static constexpr size_t minrow = minsize; static constexpr size_t size = minsize; static constexpr link count() NOEXCEPT { return 1; } - static_assert(minsize == 32u); - static_assert(minrow == 32u); + static_assert(minsize == 64u); + static_assert(minrow == 64u); static_assert(link::size == 3u); }; diff --git a/test/tables/optional/filter_bk.cpp b/test/tables/optional/filter_bk.cpp index 801287b6..d3d52e26 100644 --- a/test/tables/optional/filter_bk.cpp +++ b/test/tables/optional/filter_bk.cpp @@ -51,22 +51,27 @@ BOOST_AUTO_TEST_CASE(filter_bk__put__ordered__expected) ); const data_chunk expected_body = base16_chunk ( - "0000000000000000000000000000000000000000000000000000000000000000" - "0100000000000000000000000000000000000000000000000000000000000000" - "0200000000000000000000000000000000000000000000000000000000000000" - "0300000000000000000000000000000000000000000000000000000000000000" - "0400000000000000000000000000000000000000000000000000000000000000" + // hash, head + "0000000000000000000000000000000000000000000000000000000000000000""0000000000000000000000000000000000000000000000000000000000000000" + "0100000000000000000000000000000000000000000000000000000000000000""0500000000000000000000000000000000000000000000000000000000000000" + "0200000000000000000000000000000000000000000000000000000000000000""0600000000000000000000000000000000000000000000000000000000000000" + "0300000000000000000000000000000000000000000000000000000000000000""0700000000000000000000000000000000000000000000000000000000000000" + "0400000000000000000000000000000000000000000000000000000000000000""0800000000000000000000000000000000000000000000000000000000000000" ); - constexpr hash_digest two_hash = from_uintx(uint256_t(2)); - constexpr hash_digest three_hash = from_uintx(uint256_t(3)); - constexpr hash_digest four_hash = from_uintx(uint256_t(4)); + constexpr auto two_hash = from_uintx(uint256_t(2)); + constexpr auto three_hash = from_uintx(uint256_t(3)); + constexpr auto four_hash = from_uintx(uint256_t(4)); + constexpr auto five_hash = from_uintx(uint256_t(5)); + constexpr auto six_hash = from_uintx(uint256_t(6)); + constexpr auto seven_hash = from_uintx(uint256_t(7)); + constexpr auto eight_hash = from_uintx(uint256_t(8)); - const table::filter_bk::put_ref put0{ {}, null_hash }; - const table::filter_bk::put_ref put1{ {}, one_hash }; - const table::filter_bk::put_ref put2{ {}, two_hash }; - const table::filter_bk::put_ref put3{ {}, three_hash }; - const table::filter_bk::put_ref put4{ {}, four_hash }; + const table::filter_bk::put_ref put0{ {}, null_hash, null_hash }; + const table::filter_bk::put_ref put1{ {}, one_hash, five_hash }; + const table::filter_bk::put_ref put2{ {}, two_hash, six_hash }; + const table::filter_bk::put_ref put3{ {}, three_hash, seven_hash }; + const table::filter_bk::put_ref put4{ {}, four_hash, eight_hash }; test::chunk_storage head_store{}; test::chunk_storage body_store{}; @@ -95,11 +100,18 @@ BOOST_AUTO_TEST_CASE(filter_bk__put__ordered__expected) BOOST_REQUIRE(instance.at(2, get2)); BOOST_REQUIRE(instance.at(3, get3)); BOOST_REQUIRE(instance.at(4, get4)); + + BOOST_REQUIRE_EQUAL(get0.hash, null_hash); + BOOST_REQUIRE_EQUAL(get1.hash, one_hash); + BOOST_REQUIRE_EQUAL(get2.hash, two_hash); + BOOST_REQUIRE_EQUAL(get3.hash, three_hash); + BOOST_REQUIRE_EQUAL(get4.hash, four_hash); + BOOST_REQUIRE_EQUAL(get0.head, null_hash); - BOOST_REQUIRE_EQUAL(get1.head, one_hash); - BOOST_REQUIRE_EQUAL(get2.head, two_hash); - BOOST_REQUIRE_EQUAL(get3.head, three_hash); - BOOST_REQUIRE_EQUAL(get4.head, four_hash); + BOOST_REQUIRE_EQUAL(get1.head, five_hash); + BOOST_REQUIRE_EQUAL(get2.head, six_hash); + BOOST_REQUIRE_EQUAL(get3.head, seven_hash); + BOOST_REQUIRE_EQUAL(get4.head, eight_hash); } BOOST_AUTO_TEST_CASE(filter_bk__put__disordered__expected) @@ -128,24 +140,29 @@ BOOST_AUTO_TEST_CASE(filter_bk__put__disordered__expected) "ffffff" "ffffff" ); - const data_chunk expected_body = base16_chunk + const data_chunk expected_body = base16_chunk ( - "0100000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000000" - "0400000000000000000000000000000000000000000000000000000000000000" - "0200000000000000000000000000000000000000000000000000000000000000" - "0300000000000000000000000000000000000000000000000000000000000000" + // hash, head + "0100000000000000000000000000000000000000000000000000000000000000""0500000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000""0000000000000000000000000000000000000000000000000000000000000000" + "0400000000000000000000000000000000000000000000000000000000000000""0800000000000000000000000000000000000000000000000000000000000000" + "0200000000000000000000000000000000000000000000000000000000000000""0600000000000000000000000000000000000000000000000000000000000000" + "0300000000000000000000000000000000000000000000000000000000000000""0700000000000000000000000000000000000000000000000000000000000000" ); - constexpr hash_digest two_hash = from_uintx(uint256_t(2)); - constexpr hash_digest three_hash = from_uintx(uint256_t(3)); - constexpr hash_digest four_hash = from_uintx(uint256_t(4)); + constexpr auto two_hash = from_uintx(uint256_t(2)); + constexpr auto three_hash = from_uintx(uint256_t(3)); + constexpr auto four_hash = from_uintx(uint256_t(4)); + constexpr auto five_hash = from_uintx(uint256_t(5)); + constexpr auto six_hash = from_uintx(uint256_t(6)); + constexpr auto seven_hash = from_uintx(uint256_t(7)); + constexpr auto eight_hash = from_uintx(uint256_t(8)); - const table::filter_bk::put_ref put0{ {}, null_hash }; - const table::filter_bk::put_ref put1{ {}, one_hash }; - const table::filter_bk::put_ref put2{ {}, two_hash }; - const table::filter_bk::put_ref put3{ {}, three_hash }; - const table::filter_bk::put_ref put4{ {}, four_hash }; + const table::filter_bk::put_ref put0{ {}, null_hash, null_hash }; + const table::filter_bk::put_ref put1{ {}, one_hash, five_hash }; + const table::filter_bk::put_ref put2{ {}, two_hash, six_hash }; + const table::filter_bk::put_ref put3{ {}, three_hash, seven_hash }; + const table::filter_bk::put_ref put4{ {}, four_hash, eight_hash }; test::chunk_storage head_store{}; test::chunk_storage body_store{}; @@ -174,11 +191,18 @@ BOOST_AUTO_TEST_CASE(filter_bk__put__disordered__expected) BOOST_REQUIRE(instance.at(2, get2)); BOOST_REQUIRE(instance.at(3, get3)); BOOST_REQUIRE(instance.at(4, get4)); + + BOOST_REQUIRE_EQUAL(get0.hash, null_hash); + BOOST_REQUIRE_EQUAL(get1.hash, one_hash); + BOOST_REQUIRE_EQUAL(get2.hash, two_hash); + BOOST_REQUIRE_EQUAL(get3.hash, three_hash); + BOOST_REQUIRE_EQUAL(get4.hash, four_hash); + BOOST_REQUIRE_EQUAL(get0.head, null_hash); - BOOST_REQUIRE_EQUAL(get1.head, one_hash); - BOOST_REQUIRE_EQUAL(get2.head, two_hash); - BOOST_REQUIRE_EQUAL(get3.head, three_hash); - BOOST_REQUIRE_EQUAL(get4.head, four_hash); + BOOST_REQUIRE_EQUAL(get1.head, five_hash); + BOOST_REQUIRE_EQUAL(get2.head, six_hash); + BOOST_REQUIRE_EQUAL(get3.head, seven_hash); + BOOST_REQUIRE_EQUAL(get4.head, eight_hash); } BOOST_AUTO_TEST_SUITE_END()