From 3482a6f4ff475196350aa16e6009954dd9459d8c Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Tue, 15 Sep 2026 16:37:32 -0400 Subject: [PATCH] Update populate_with_metadata for spent differentiation. --- .../query/consensus/consensus_populate.ipp | 11 +++++---- test/query/archive/chain_reader.cpp | 24 ++++++++++++++----- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/include/bitcoin/database/impl/query/consensus/consensus_populate.ipp b/include/bitcoin/database/impl/query/consensus/consensus_populate.ipp index 3c78b2150..544a14a3a 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_populate.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_populate.ipp @@ -119,11 +119,12 @@ bool CLASS::populate_with_metadata(const input& input, bool chain, metadata.median_time_past = max_uint32; } - if (pool) + // The genesis coinbase is unspendable, so no confirmed spender is at + // zero, which therefore distinguishes an unconfirmed spender. + if (pool && !is_spent(input.point())) { - // Any spender (including duplicate tx) implies spent. - const auto spent = is_spent(input.point()); - metadata.spender_height = spent ? 0_u32 : max_uint32; + // Any spender (including duplicate tx) not found. + metadata.spender_height = max_uint32; } else if (const auto height = find_strong_spender_height(input.point()); !height.is_terminal()) @@ -135,7 +136,7 @@ bool CLASS::populate_with_metadata(const input& input, bool chain, else { // Confirmed spender not found. - metadata.spender_height = max_uint32; + metadata.spender_height = pool ? 0_u32 : max_uint32; } } diff --git a/test/query/archive/chain_reader.cpp b/test/query/archive/chain_reader.cpp index c337631d0..b3bc69996 100644 --- a/test/query/archive/chain_reader.cpp +++ b/test/query/archive/chain_reader.cpp @@ -852,7 +852,7 @@ BOOST_AUTO_TEST_CASE(query_chain_reader__populate_with_metadata__metadata__expec BOOST_CHECK_EQUAL(tx4.inputs_ptr()->at(1)->metadata.parent_tx, 1u); } -// A free tx cannot conflict, so any spender of its prevout implies spent. +// A free tx conflicts with any spender, but only a confirmed one has height. BOOST_AUTO_TEST_CASE(query_chain_reader__populate_with_metadata__pool_spender__expected) { settings settings{}; @@ -861,23 +861,35 @@ BOOST_AUTO_TEST_CASE(query_chain_reader__populate_with_metadata__pool_spender__e test::query_accessor query{ store }; BOOST_CHECK(!store.create(test::events_handler)); BOOST_CHECK(query.initialize(test::genesis)); - BOOST_CHECK(query.set(test::block1a, test::context, false, false)); + BOOST_CHECK(query.set(test::block1a, context{ 0, 1, 0 }, false, false)); - // tx5 spends the first output of the first tx of block1a, as does tx4. + // tx5 spends the first output of the first tx of block1a, as do tx4 and + // the first transaction of block2a. const auto& unspent = clean_(test::tx5); BOOST_CHECK(query.populate_with_metadata(unspent, true, true)); BOOST_CHECK_EQUAL(unspent.inputs_ptr()->at(0)->metadata.spender_height, max_uint32); BOOST_CHECK(query.set(test::tx4)); - const auto& spent = clean_(test::tx5); - BOOST_CHECK(query.populate_with_metadata(spent, true, true)); - BOOST_CHECK_EQUAL(spent.inputs_ptr()->at(0)->metadata.spender_height, 0u); + const auto& pooled = clean_(test::tx5); + BOOST_CHECK(query.populate_with_metadata(pooled, true, true)); + BOOST_CHECK_EQUAL(pooled.inputs_ptr()->at(0)->metadata.spender_height, 0u); // The spender is unassociated, so it is not a confirmed spender. const auto& chained = clean_(test::tx5); BOOST_CHECK(query.populate_with_metadata(chained, true, false)); BOOST_CHECK_EQUAL(chained.inputs_ptr()->at(0)->metadata.spender_height, max_uint32); + + BOOST_CHECK(query.set(test::block2a, context{ 0, 2, 0 }, false, false)); + BOOST_CHECK(query.set_strong(2)); + + const auto& confirmed = clean_(test::tx5); + BOOST_CHECK(query.populate_with_metadata(confirmed, true, true)); + BOOST_CHECK_EQUAL(confirmed.inputs_ptr()->at(0)->metadata.spender_height, 2u); + + const auto& strong = clean_(test::tx5); + BOOST_CHECK(query.populate_with_metadata(strong, true, false)); + BOOST_CHECK_EQUAL(strong.inputs_ptr()->at(0)->metadata.spender_height, 2u); } // populate_without_metadata