From 5bc2be4bc17fb66008c6528f3e3a22997d79fa1d Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Tue, 1 Sep 2026 01:23:05 -0400 Subject: [PATCH] Accumulate unspendable coin amounts in the unspent scan. --- builds/gnu/Makefile.am | 1 + .../libbitcoin-database.vcxproj | 1 + .../libbitcoin-database.vcxproj.filters | 3 + .../libbitcoin-database.vcxproj | 1 + .../libbitcoin-database.vcxproj.filters | 3 + include/bitcoin/database.hpp | 1 + .../bitcoin/database/impl/query/unspent.ipp | 92 ++++++++++++++++--- include/bitcoin/database/query.hpp | 13 ++- .../bitcoin/database/types/block_amounts.hpp | 49 ++++++++++ include/bitcoin/database/types/types.hpp | 1 + .../bitcoin/database/types/unspent_totals.hpp | 6 ++ 11 files changed, 155 insertions(+), 16 deletions(-) create mode 100644 include/bitcoin/database/types/block_amounts.hpp diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 2bb03c06b..3a3819cae 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -324,6 +324,7 @@ include_bitcoin_database_typesdir = \ include_bitcoin_database_types_HEADERS = \ ${srcdir}/../../include/bitcoin/database/types/association.hpp \ ${srcdir}/../../include/bitcoin/database/types/associations.hpp \ + ${srcdir}/../../include/bitcoin/database/types/block_amounts.hpp \ ${srcdir}/../../include/bitcoin/database/types/block_state.hpp \ ${srcdir}/../../include/bitcoin/database/types/difference_set.hpp \ ${srcdir}/../../include/bitcoin/database/types/envelope.hpp \ diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj index 4c22f9137..570e9b435 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj @@ -211,6 +211,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters index 543fa3a6f..c8b684323 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -353,6 +353,9 @@ include\bitcoin\database\types + + include\bitcoin\database\types + include\bitcoin\database\types diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj index 5b5917e91..c2f545055 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj @@ -211,6 +211,7 @@ + diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters index 543fa3a6f..c8b684323 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -353,6 +353,9 @@ include\bitcoin\database\types + + include\bitcoin\database\types + include\bitcoin\database\types diff --git a/include/bitcoin/database.hpp b/include/bitcoin/database.hpp index a21a08de0..94a9bfef0 100644 --- a/include/bitcoin/database.hpp +++ b/include/bitcoin/database.hpp @@ -84,6 +84,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/database/impl/query/unspent.ipp b/include/bitcoin/database/impl/query/unspent.ipp index 89465a23e..d482ba0de 100644 --- a/include/bitcoin/database/impl/query/unspent.ipp +++ b/include/bitcoin/database/impl/query/unspent.ipp @@ -70,11 +70,58 @@ bool CLASS::is_bip30_exception(bool& out, } TEMPLATE -code CLASS::scan_unspent(difference_set<>& set, const stopper& cancel, - const header_links& branch, bool turbo) const NOEXCEPT +code CLASS::get_block_amounts(block_amounts& out, + const header_link& link) const NOEXCEPT +{ + out = {}; + auto bip30_exception = false; + if (!is_bip30_exception(bip30_exception, link)) + return error::integrity; + + auto coinbase = true; + table::output::get_spendable value{}; + for (const auto& tx: to_transactions(link)) + { + for (const auto& put: to_outputs(tx)) + { + if (!store_.output.get(put, value)) + return error::integrity; + + if (value.unspendable) + out.unspendable += value.value; + + if (coinbase) + out.coinbase += value.value; + else + out.outputs += value.value; + } + + coinbase = false; + } + + // The duplicated coinbase overwrites (destroys) that of the original. + if (bip30_exception) + out.bip30 = out.coinbase; + + for (const auto& put: to_block_prevouts(link)) + { + if (!store_.output.get(put, value)) + return error::integrity; + + out.prevouts += value.value; + } + + return error::success; +} + +TEMPLATE +code CLASS::scan_unspent(difference_set<>& set, unspent_totals& out, + const stopper& cancel, const header_links& branch, bool turbo) const NOEXCEPT { using namespace system; std::atomic_bool fail{}; + std::atomic unspendable{}; + std::atomic overwritten{}; const auto parallel = poolstl::execution::par_if(turbo); // Reverse order in best effort attempt to minimize set size. @@ -92,12 +139,27 @@ code CLASS::scan_unspent(difference_set<>& set, const stopper& cancel, return; } - // The bip30 exception blocks are coinbase only. - if (bip30_exception) - return; - auto coinbase = true; table::output::get_spendable value{}; + + // The bip30 exception blocks are coinbase only, their duplicated + // outputs overwrite (destroy) those of the original blocks. + if (bip30_exception) + { + for (const auto& tx: to_transactions(link)) + for (const auto& out_: to_outputs(tx)) + { + if (!store_.output.get(out_, value)) + { + fail = true; + return; + } + + overwritten += value.value; + } + + return; + } for (const auto& tx: to_transactions(link)) { if (cancel || fail) @@ -112,7 +174,9 @@ code CLASS::scan_unspent(difference_set<>& set, const stopper& cancel, return; } - if (!value.unspendable) + if (value.unspendable) + unspendable += value.value; + else set.toggle(tx, index); ++index; @@ -165,6 +229,8 @@ code CLASS::scan_unspent(difference_set<>& set, const stopper& cancel, if (cancel) return error::query_canceled; + out.unspendable = unspendable; + out.bip30 = overwritten; return error::success; } @@ -174,7 +240,6 @@ code CLASS::count_unspent(unspent_totals& out, const stopper& cancel, { using namespace system; - out = {}; output_links puts{}; auto previous = max_uint64; table::output::get_spendable value{}; @@ -280,20 +345,23 @@ TEMPLATE code CLASS::get_unspent_totals(const stopper& cancel, unspent_totals& out, const header_links& branch, bool turbo) const NOEXCEPT { + out = {}; difference_set<> set{}; - const auto ec = scan_unspent(set, cancel, branch, turbo); + const auto ec = scan_unspent(set, out, cancel, branch, turbo); return ec ? ec : count_unspent(out, cancel, set.drain()); } TEMPLATE template -code CLASS::get_unspent_coins(const stopper& cancel, const Visitor& visit, - const header_links& branch, bool ordered, bool turbo) const NOEXCEPT +code CLASS::get_unspent_coins(const stopper& cancel, unspent_totals& out, + const Visitor& visit, const header_links& branch, bool ordered, + bool turbo) const NOEXCEPT { using namespace system; + out = {}; difference_set<> set{}; - if (const auto ec = scan_unspent(set, cancel, branch, turbo)) + if (const auto ec = scan_unspent(set, out, cancel, branch, turbo)) return ec; auto survivors = set.drain(); diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 4d4d78d82..137b25599 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -775,6 +775,10 @@ class query /// Unspent. /// ----------------------------------------------------------------------- + /// Coin amounts moved by the block. + code get_block_amounts(block_amounts& out, + const header_link& link) const NOEXCEPT; + /// Unspent output totals over the branch (administrative scan). code get_unspent_totals(const stopper& cancel, unspent_totals& out, const header_links& branch, bool turbo=false) const NOEXCEPT; @@ -783,8 +787,8 @@ class query /// canonical (txid, index) order as required for utxo set serialization, /// otherwise unordered (ordering requires a full materialized sort). template - code get_unspent_coins(const stopper& cancel, const Visitor& visit, - const header_links& branch, bool ordered, + code get_unspent_coins(const stopper& cancel, unspent_totals& out, + const Visitor& visit, const header_links& branch, bool ordered, bool turbo=false) const NOEXCEPT; protected: @@ -989,8 +993,9 @@ class query bool is_bip30_exception(bool& out, const header_link& link) const NOEXCEPT; - code scan_unspent(difference_set<>& set, const stopper& cancel, - const header_links& branch, bool turbo) const NOEXCEPT; + code scan_unspent(difference_set<>& set, unspent_totals& out, + const stopper& cancel, const header_links& branch, + bool turbo) const NOEXCEPT; code count_unspent(unspent_totals& out, const stopper& cancel, const difference_set<>::entries& survivors) const NOEXCEPT; template diff --git a/include/bitcoin/database/types/block_amounts.hpp b/include/bitcoin/database/types/block_amounts.hpp new file mode 100644 index 000000000..c4863a218 --- /dev/null +++ b/include/bitcoin/database/types/block_amounts.hpp @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_DATABASE_TYPES_BLOCK_AMOUNTS_HPP +#define LIBBITCOIN_DATABASE_TYPES_BLOCK_AMOUNTS_HPP + +#include + +namespace libbitcoin { +namespace database { + +/// Coin amounts moved by a single block. +struct BCD_API block_amounts +{ + /// Total value of the prevouts spent by the block. + uint64_t prevouts{}; + + /// Total value of the coinbase outputs. + uint64_t coinbase{}; + + /// Total value of the non-coinbase outputs. + uint64_t outputs{}; + + /// Total value of the provably unspendable outputs created. + uint64_t unspendable{}; + + /// Total value of the coinbase outputs overwritten by bip30 duplication. + uint64_t bip30{}; +}; + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/types/types.hpp b/include/bitcoin/database/types/types.hpp index 815321a90..1cd664bad 100644 --- a/include/bitcoin/database/types/types.hpp +++ b/include/bitcoin/database/types/types.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/database/types/unspent_totals.hpp b/include/bitcoin/database/types/unspent_totals.hpp index a5e23e7b6..e2b550bec 100644 --- a/include/bitcoin/database/types/unspent_totals.hpp +++ b/include/bitcoin/database/types/unspent_totals.hpp @@ -41,6 +41,12 @@ struct BCD_API unspent_totals /// Total value of unspent outputs. uint64_t value{}; + + /// Total value of provably unspendable outputs (excluded from the set). + uint64_t unspendable{}; + + /// Total value of coinbase outputs overwritten by bip30 duplication. + uint64_t bip30{}; }; } // namespace database