From edb7154bc3ec9a386f167c24cd731ceeba1fcf8c Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 20 Apr 2026 18:10:30 -0400 Subject: [PATCH] Don't collect fees on confirmed address history queries. --- .../impl/query/address/address_history.ipp | 22 +++++---- .../impl/query/address/address_outpoints.ipp | 24 ++++++++++ include/bitcoin/database/query.hpp | 3 ++ include/bitcoin/database/types/history.hpp | 2 +- test/query/address/address_history.cpp | 46 +++++++++---------- test/query/address/address_outpoints.cpp | 30 ++++++++++-- 6 files changed, 90 insertions(+), 37 deletions(-) diff --git a/include/bitcoin/database/impl/query/address/address_history.ipp b/include/bitcoin/database/impl/query/address/address_history.ipp index fa717bd9..feeb50cb 100644 --- a/include/bitcoin/database/impl/query/address/address_history.ipp +++ b/include/bitcoin/database/impl/query/address/address_history.ipp @@ -68,10 +68,11 @@ code CLASS::get_unconfirmed_history(const stopper& cancel, histories& out, } uint64_t fee{}; - auto height = history::unrooted_height; if (!get_tx_fee(fee, link)) fee = history::missing_prevout; - else if (is_confirmed_all_prevouts(link)) + + auto height = history::unrooted_height; + if (is_confirmed_all_prevouts(link)) height = history::rooted_height; return history{ { std::move(hash), height }, fee, @@ -115,9 +116,10 @@ code CLASS::get_confirmed_history(const stopper& cancel, histories& out, return history{}; } - uint64_t fee{}; - if (!get_tx_fee(fee, link)) - fee = history::missing_prevout; + // Electrum uses fees only on unconfirmed (and expensive). + constexpr auto fee = history::missing_prevout; + ////if (!get_tx_fee(fee, link)) + //// fee = history::missing_prevout; return history{ { std::move(hash), height }, fee, position }; }); @@ -176,10 +178,8 @@ history CLASS::get_tx_history(hash_digest&& key, if (link.is_terminal()) return {}; - uint64_t fee{}; - if (!get_tx_fee(fee, link)) - fee = history::missing_prevout; - + // Electrum uses fees only on unconfirmed (and expensive). + auto fee = history::missing_prevout; auto height = history::unrooted_height; auto position = history::unconfirmed_position; if (const auto block = find_confirmed_block(link); !block.is_terminal()) @@ -190,6 +190,9 @@ history CLASS::get_tx_history(hash_digest&& key, } else { + if (!get_tx_fee(fee, link)) + fee = history::missing_prevout; + if (is_confirmed_all_prevouts(link)) height = history::rooted_height; } @@ -197,6 +200,7 @@ history CLASS::get_tx_history(hash_digest&& key, return { { std::move(key), height }, fee, position }; } +// server/electrum TEMPLATE histories CLASS::get_spenders_history( const system::chain::point& prevout) const NOEXCEPT diff --git a/include/bitcoin/database/impl/query/address/address_outpoints.ipp b/include/bitcoin/database/impl/query/address/address_outpoints.ipp index 3bb26ee7..b8ff090e 100644 --- a/include/bitcoin/database/impl/query/address/address_outpoints.ipp +++ b/include/bitcoin/database/impl/query/address/address_outpoints.ipp @@ -117,6 +117,30 @@ code CLASS::get_address_outpoints(const stopper& cancel, outpoints& out, }); } +// TODO: server/native +TEMPLATE +code CLASS::get_address_outpoints(const stopper& cancel, address_link& cursor, + outpoints& out, const hash_digest& key, bool turbo) const NOEXCEPT +{ + out.clear(); + output_links links{}; + if (const code ec = to_address_outputs(cancel, cursor, links, key)) + return ec; + + return parallel_outpoint_transform(cancel, turbo, out, links, + [this](const output_link& link, auto& cancel, auto& fail) NOEXCEPT + { + if (cancel || fail) + return outpoint{}; + + const auto point = get_outpoint(link); + if (!point.point().is_valid()) + fail = true; + + return point; + }); +} + // utilities // ---------------------------------------------------------------------------- // private/static diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 4abf9353..a925ffb4 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -630,6 +630,8 @@ class query const hash_digest& key, uint64_t value, bool turbo=false) const NOEXCEPT; code get_address_outpoints(const stopper& cancel, outpoints& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; + code get_address_outpoints(const stopper& cancel, address_link& cursor, + outpoints& out, const hash_digest& key, bool turbo=false) const NOEXCEPT; /// Electrum queries (histories, deduped, electrum sort). code get_unconfirmed_history(const stopper& cancel, histories& out, @@ -657,6 +659,7 @@ class query bool turbo=false) const NOEXCEPT; /// History queries. + // TODO: add point_link cursor to new get_spenders(cursor, point) query. history get_tx_history(const tx_link& link) const NOEXCEPT; history get_tx_history(const hash_digest& key) const NOEXCEPT; histories get_spenders_history(const point& prevout) const NOEXCEPT; diff --git a/include/bitcoin/database/types/history.hpp b/include/bitcoin/database/types/history.hpp index 43aa4a0b..f2db8f30 100644 --- a/include/bitcoin/database/types/history.hpp +++ b/include/bitcoin/database/types/history.hpp @@ -29,8 +29,8 @@ struct BCD_API history { static constexpr size_t rooted_height = zero; static constexpr size_t unrooted_height = max_size_t; - static constexpr size_t missing_prevout = max_uint64; static constexpr size_t unconfirmed_position = max_size_t; + static constexpr uint64_t missing_prevout = max_uint64; static void filter_sort_and_dedup(std::vector& history) NOEXCEPT; diff --git a/test/query/address/address_history.cpp b/test/query/address/address_history.cpp index 89939f1b..ba6cfc0c 100644 --- a/test/query/address/address_history.cpp +++ b/test/query/address/address_history.cpp @@ -42,14 +42,14 @@ BOOST_AUTO_TEST_CASE(query_address__get_history__genesis__expected) out.clear(); BOOST_REQUIRE(!query.get_confirmed_history(cancel, out, test::genesis_address0)); - BOOST_REQUIRE_EQUAL(out.at(0).fee, 0u); + BOOST_REQUIRE_EQUAL(out.at(0).fee, history::missing_prevout); BOOST_REQUIRE_EQUAL(out.at(0).position, 0u); BOOST_REQUIRE_EQUAL(out.at(0).tx.height(), 0u); BOOST_REQUIRE_EQUAL(out.at(0).tx.hash(), test::genesis.transactions_ptr()->at(0)->hash(false)); out.clear(); BOOST_REQUIRE(!query.get_history(cancel, out, test::genesis_address0)); - BOOST_REQUIRE_EQUAL(out.at(0).fee, 0u); + BOOST_REQUIRE_EQUAL(out.at(0).fee, history::missing_prevout); BOOST_REQUIRE_EQUAL(out.at(0).position, 0u); BOOST_REQUIRE_EQUAL(out.at(0).tx.height(), 0u); BOOST_REQUIRE_EQUAL(out.at(0).tx.hash(), test::genesis.transactions_ptr()->at(0)->hash(false)); @@ -96,34 +96,34 @@ BOOST_AUTO_TEST_CASE(query_address__get_unconfirmed_history__turbo_block1a_addre BOOST_REQUIRE_EQUAL(out.size(), 4u); // Identities (not part of sort). - BOOST_REQUIRE_EQUAL(out.at(0).tx.hash(), test::tx4.hash(false)); // tx4 - BOOST_REQUIRE_EQUAL(out.at(1).tx.hash(), test::block2b.transactions_ptr()->at(0)->hash(false)); // tx8 - BOOST_REQUIRE_EQUAL(out.at(2).tx.hash(), test::tx5.hash(false)); // tx5 - BOOST_REQUIRE_EQUAL(out.at(3).tx.hash(), test::block1b.transactions_ptr()->at(0)->hash(false)); // tx7 + BOOST_CHECK_EQUAL(out.at(0).tx.hash(), test::tx5.hash(false)); // tx5 + BOOST_CHECK_EQUAL(out.at(1).tx.hash(), test::tx4.hash(false)); // tx4 + BOOST_CHECK_EQUAL(out.at(2).tx.hash(), test::block2b.transactions_ptr()->at(0)->hash(false)); // tx8 + BOOST_CHECK_EQUAL(out.at(3).tx.hash(), test::block1b.transactions_ptr()->at(0)->hash(false)); // tx7 // Confirmed by height ascending (not part of sort). // Unconfirmed rooted before unrooted. - BOOST_REQUIRE_EQUAL(out.at(0).tx.height(), history::rooted_height); // spends block1a (tx0 both outputs). - BOOST_REQUIRE_EQUAL(out.at(1).tx.height(), history::unrooted_height); // bk2btx0 unrooted (spends bk1). - BOOST_REQUIRE_EQUAL(out.at(2).tx.height(), history::unrooted_height); // spend exceeds value (treated as missing prevout). - BOOST_REQUIRE_EQUAL(out.at(3).tx.height(), history::unrooted_height); // bk1btx0 unrooted (missing prevouts). + BOOST_CHECK_EQUAL(out.at(0).tx.height(), history::rooted_height); // tx5 spends block1a + BOOST_CHECK_EQUAL(out.at(1).tx.height(), history::rooted_height); // tx4 spends block1a + BOOST_CHECK_EQUAL(out.at(2).tx.height(), history::unrooted_height); // block2b spends block1b + BOOST_CHECK_EQUAL(out.at(3).tx.height(), history::unrooted_height); // block1b missing prevouts // Confirmed height by block position (not part of sort). - BOOST_REQUIRE_EQUAL(out.at(0).position, history::unconfirmed_position); - BOOST_REQUIRE_EQUAL(out.at(1).position, history::unconfirmed_position); - BOOST_REQUIRE_EQUAL(out.at(2).position, history::unconfirmed_position); - BOOST_REQUIRE_EQUAL(out.at(3).position, history::unconfirmed_position); + BOOST_CHECK_EQUAL(out.at(0).position, history::unconfirmed_position); + BOOST_CHECK_EQUAL(out.at(1).position, history::unconfirmed_position); + BOOST_CHECK_EQUAL(out.at(2).position, history::unconfirmed_position); + BOOST_CHECK_EQUAL(out.at(3).position, history::unconfirmed_position); // Unconfirmed system::encode_hash(hash) lexically sorted. - BOOST_REQUIRE(encode_hash(out.at(1).tx.hash()) < encode_hash(out.at(2).tx.hash())); - BOOST_REQUIRE(encode_hash(out.at(2).tx.hash()) < encode_hash(out.at(3).tx.hash())); + BOOST_CHECK(encode_hash(out.at(0).tx.hash()) < encode_hash(out.at(1).tx.hash())); + BOOST_CHECK(encode_hash(out.at(2).tx.hash()) < encode_hash(out.at(3).tx.hash())); // Fee (not part of sort). - BOOST_REQUIRE_EQUAL(out.at(0).fee, floored_subtract(0x18u + 0x2au, 0x08u)); - BOOST_REQUIRE_EQUAL(out.at(1).fee, floored_subtract(0xb1u + 0xb1u, 0xb2u)); - BOOST_REQUIRE_EQUAL(out.at(2).fee, history::missing_prevout); // spend exceeds value (treated as missing prevout). - BOOST_REQUIRE_EQUAL(out.at(3).fee, 0u); // coinbase (archived with null single point). + BOOST_CHECK_EQUAL(out.at(0).fee, history::missing_prevout); // spend exceeds value (treated as missing prevout). + BOOST_CHECK_EQUAL(out.at(1).fee, floored_subtract(0x18u + 0x2au, 0x08u)); + BOOST_CHECK_EQUAL(out.at(2).fee, floored_subtract(0xb1u + 0xb1u, 0xb2u)); + BOOST_CHECK_EQUAL(out.at(3).fee, 0u); // coinbase (archived with null single point). } BOOST_AUTO_TEST_CASE(query_address__get_confirmed_history__turbo_block1a_address0__expected) @@ -229,7 +229,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_history__turbo_block1a_address0__expecte BOOST_REQUIRE_EQUAL(out.at(4).fee, history::missing_prevout); // tx5/tx4 BOOST_REQUIRE_EQUAL(out.at(5).fee, floored_subtract(0x18u + 0x2au, 0x08u)); // tx4/tx8 BOOST_REQUIRE_EQUAL(out.at(6).fee, floored_subtract(0xb1u + 0xb1u, 0xb2u)); // tx8/tx5 - BOOST_REQUIRE_EQUAL(out.at(7).fee, 0u); // tx7 + BOOST_REQUIRE_EQUAL(out.at(7).fee, history::missing_prevout); // tx7 } // get_tx_history1 @@ -260,14 +260,14 @@ BOOST_AUTO_TEST_CASE(query_address__get_tx_history__genesis__expected) const auto hash = test::genesis.transactions_ptr()->at(0)->hash(false); auto history = query.get_tx_history(0); BOOST_REQUIRE(history.valid()); - BOOST_REQUIRE_EQUAL(history.fee, 0u); + BOOST_REQUIRE_EQUAL(history.fee, history::missing_prevout); BOOST_REQUIRE_EQUAL(history.position, 0u); BOOST_REQUIRE_EQUAL(history.tx.height(), 0u); BOOST_REQUIRE_EQUAL(history.tx.hash(), hash); history = query.get_tx_history(hash); BOOST_REQUIRE(history.valid()); - BOOST_REQUIRE_EQUAL(history.fee, 0u); + BOOST_REQUIRE_EQUAL(history.fee, history::missing_prevout); BOOST_REQUIRE_EQUAL(history.position, 0u); BOOST_REQUIRE_EQUAL(history.tx.height(), 0u); BOOST_REQUIRE_EQUAL(history.tx.hash(), hash); diff --git a/test/query/address/address_outpoints.cpp b/test/query/address/address_outpoints.cpp index c39c2e6d..296727e5 100644 --- a/test/query/address/address_outpoints.cpp +++ b/test/query/address/address_outpoints.cpp @@ -124,9 +124,9 @@ BOOST_AUTO_TEST_CASE(query_address__get_minimum_unspent_outpoints__below__includ BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); } -// get_address_outpoints +// get_address_outpoints1 -BOOST_AUTO_TEST_CASE(query_address__get_address_outpoints__turbo_genesis__expected) +BOOST_AUTO_TEST_CASE(query_address__get_address_outpoints1__turbo_genesis__expected) { settings settings{}; settings.path = TEST_DIRECTORY; @@ -142,7 +142,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_address_outpoints__turbo_genesis__expect BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); } -BOOST_AUTO_TEST_CASE(query_address__get_address_outpoints__genesis__expected) +BOOST_AUTO_TEST_CASE(query_address__get_address_outpoints1__genesis__expected) { settings settings{}; settings.path = TEST_DIRECTORY; @@ -158,7 +158,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_address_outpoints__genesis__expected) BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); } -BOOST_AUTO_TEST_CASE(query_address__get_address_outpoints__cancel__canceled_false) +BOOST_AUTO_TEST_CASE(query_address__get_address_outpoints1__cancel__canceled_false) { settings settings{}; settings.path = TEST_DIRECTORY; @@ -173,4 +173,26 @@ BOOST_AUTO_TEST_CASE(query_address__get_address_outpoints__cancel__canceled_fals BOOST_REQUIRE(out.empty()); } +// get_address_outpoints2 + +BOOST_AUTO_TEST_CASE(query_address__get_address_outpoints2__progressive_cursor__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)); + + outpoints out{}; + address_link cursor{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.get_address_outpoints(cancel, cursor, out, test::genesis_address0)); + BOOST_REQUIRE_EQUAL(out.size(), 1u); + BOOST_REQUIRE_EQUAL(cursor, 0u); + BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); + + // TODO: add same tx again. +} + BOOST_AUTO_TEST_SUITE_END()