From b492f159d532e5d24cc2ceefa050f3275f25a3e6 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Sun, 4 Oct 2026 19:34:23 -0400 Subject: [PATCH] Add is_witness_committed and is_candidate_extension queries. --- .../bitcoin/database/impl/query/confirmed.ipp | 8 ++++ .../database/impl/query/properties_block.ipp | 23 ++++++++++++ include/bitcoin/database/query.hpp | 3 ++ test/query/confirmed.cpp | 22 +++++++++++ test/query/properties_block.cpp | 37 +++++++++++++++++++ 5 files changed, 93 insertions(+) diff --git a/include/bitcoin/database/impl/query/confirmed.ipp b/include/bitcoin/database/impl/query/confirmed.ipp index baf82b4a2..294a53544 100644 --- a/include/bitcoin/database/impl/query/confirmed.ipp +++ b/include/bitcoin/database/impl/query/confirmed.ipp @@ -80,6 +80,14 @@ bool CLASS::is_candidate_header(const header_link& link) const NOEXCEPT return store_.candidate.at(height) == link; } +// An unassociated candidate extending the confirmed top. +TEMPLATE +bool CLASS::is_candidate_extension(const header_link& link) const NOEXCEPT +{ + return !is_associated(link) && is_candidate_header(link) && + (to_confirmed(get_top_confirmed()) == to_parent(link)); +} + TEMPLATE bool CLASS::is_confirmed_block(const header_link& link) const NOEXCEPT { diff --git a/include/bitcoin/database/impl/query/properties_block.ipp b/include/bitcoin/database/impl/query/properties_block.ipp index 846444852..42b056f1f 100644 --- a/include/bitcoin/database/impl/query/properties_block.ipp +++ b/include/bitcoin/database/impl/query/properties_block.ipp @@ -273,6 +273,29 @@ hashes CLASS::get_wtxids(const header_link& link) const NOEXCEPT return hashes; } +// The coinbase commits to the witness root, otherwise the block is +// unsegregated and its witness root is the header merkle root. +TEMPLATE +bool CLASS::is_witness_committed(const hash_digest& witness_root, + const header_link& link) const NOEXCEPT +{ + using namespace system; + const auto coinbase = get_transaction(to_coinbase(link), true); + if (!coinbase) + return false; + + hash_cref commitment{ null_hash }; + hash_cref reservation{ null_hash }; + if (!coinbase->get_witness_commitment(commitment)) + { + const auto header = get_header(link); + return header && (header->merkle_root() == witness_root); + } + + return coinbase->get_witness_reservation(reservation) && + (bitcoin_hash(witness_root, reservation) == commitment.get()); +} + // The pool id columns hold the witness hash of a pooled tx, otherwise (such as // for any coinbase) the witness hash is computed from the stored tx. TEMPLATE diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index a0e5f833a..644ed4324 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -404,6 +404,8 @@ class query hash_digest get_top_candidate_hash() const NOEXCEPT; hashes get_tx_keys(const header_link& link) const NOEXCEPT; hashes get_wtxids(const header_link& link) const NOEXCEPT; + bool is_witness_committed(const hash_digest& witness_root, + const header_link& link) const NOEXCEPT; size_t get_tx_count(const header_link& link) const NOEXCEPT; size_t get_branch_tx_count(const header_link& link) const NOEXCEPT; inline hash_digest get_header_key(const header_link& link) const NOEXCEPT; @@ -711,6 +713,7 @@ class query /// These compare strong with height index. bool is_candidate_header(const header_link& link) const NOEXCEPT; + bool is_candidate_extension(const header_link& link) const NOEXCEPT; bool is_confirmed_block(const header_link& link) const NOEXCEPT; bool is_confirmed_tx(const tx_link& link) const NOEXCEPT; bool is_confirmed_input(const ins_link& link) const NOEXCEPT; diff --git a/test/query/confirmed.cpp b/test/query/confirmed.cpp index 26d662334..c70e0c1d4 100644 --- a/test/query/confirmed.cpp +++ b/test/query/confirmed.cpp @@ -56,6 +56,28 @@ BOOST_AUTO_TEST_CASE(query_confirmed__is_candidate_block__push_pop_candidate__ex BOOST_REQUIRE(!query.is_candidate_header(database::header_link::terminal)); } +BOOST_AUTO_TEST_CASE(query_confirmed__is_candidate_extension__candidates__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)); + BOOST_REQUIRE(query.set(test::block1.header(), context{ 0, 1, 0 }, {}, false)); + BOOST_REQUIRE(query.set(test::block2.header(), context{ 0, 2, 0 }, {}, false)); + BOOST_REQUIRE(!query.is_candidate_extension(0)); + BOOST_REQUIRE(!query.is_candidate_extension(1)); + + BOOST_REQUIRE(query.push_candidate(1)); + BOOST_REQUIRE(query.push_candidate(2)); + BOOST_REQUIRE(query.is_candidate_extension(1)); + BOOST_REQUIRE(!query.is_candidate_extension(2)); + + BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, {}, false, false)); + BOOST_REQUIRE(!query.is_candidate_extension(1)); +} + BOOST_AUTO_TEST_CASE(query_confirmed__get_confirmed_height__push_pop_confirmed__expected) { settings settings{}; diff --git a/test/query/properties_block.cpp b/test/query/properties_block.cpp index cab201936..7a4fff6a9 100644 --- a/test/query/properties_block.cpp +++ b/test/query/properties_block.cpp @@ -255,4 +255,41 @@ BOOST_AUTO_TEST_CASE(query_properties_block__get_block_state__unconfirmable__blo BOOST_REQUIRE_EQUAL(query.get_block_state(1), error::block_unconfirmable); } +BOOST_AUTO_TEST_CASE(query_properties_block__is_witness_committed__genesis__header_merkle_root) +{ + 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)); + + BOOST_REQUIRE(query.is_witness_committed(test::genesis.header().merkle_root(), 0)); + BOOST_REQUIRE(!query.is_witness_committed(one_hash, 0)); +} + +BOOST_AUTO_TEST_CASE(query_properties_block__is_witness_committed__committed__expected) +{ + using namespace system::chain; + 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)); + + const hash_digest reserved_hash{ 0x02 }; + const auto commitment = bitcoin_hash(one_hash, reserved_hash); + const script commitment_script(splice(base16_chunk("6a24aa21a9ed"), commitment), false); + const witness reserved{ chunk_cptrs{ to_shared(to_chunk(reserved_hash)) } }; + const inputs ins{ input{ point{}, script{ operations{ operation{ data_chunk{ 0x01, 0x02 }, false } } }, reserved, 0xffffffff } }; + const transaction coinbase{ 1, ins, outputs{ output{ 0, commitment_script } }, 0 }; + const block block1{ header{ 1, test::genesis.hash(), null_hash, 0, 0, 0 }, transactions{ coinbase } }; + BOOST_REQUIRE(query.set(block1, context{}, {}, false, false)); + + const auto link = query.to_header(block1.hash()); + BOOST_REQUIRE(query.is_witness_committed(one_hash, link)); + BOOST_REQUIRE(!query.is_witness_committed(reserved_hash, link)); +} + BOOST_AUTO_TEST_SUITE_END()