Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Makefile.am
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requires artifact regeneration.

Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ test_libbitcoin_database_test_SOURCES = \
test/query/properties_block.cpp \
test/query/properties_tx.cpp \
test/query/sequences.cpp \
test/query/sp_prevout_summaries.cpp \
test/query/sizes.cpp \
test/query/address/address_balance.cpp \
test/query/address/address_history.cpp \
Expand Down Expand Up @@ -217,7 +218,8 @@ include_bitcoin_database_impl_query_HEADERS = \
include/bitcoin/database/impl/query/properties_tx.ipp \
include/bitcoin/database/impl/query/query.ipp \
include/bitcoin/database/impl/query/sequences.ipp \
include/bitcoin/database/impl/query/sizes.ipp
include/bitcoin/database/impl/query/sizes.ipp \
include/bitcoin/database/impl/query/sp_prevout_summaries.ipp

include_bitcoin_database_impl_query_addressdir = ${includedir}/bitcoin/database/impl/query/address
include_bitcoin_database_impl_query_address_HEADERS = \
Expand Down Expand Up @@ -329,14 +331,16 @@ include_bitcoin_database_tables_optionalsdir = ${includedir}/bitcoin/database/ta
include_bitcoin_database_tables_optionals_HEADERS = \
include/bitcoin/database/tables/optionals/address.hpp \
include/bitcoin/database/tables/optionals/filter_bk.hpp \
include/bitcoin/database/tables/optionals/filter_tx.hpp
include/bitcoin/database/tables/optionals/filter_tx.hpp \
include/bitcoin/database/tables/optionals/sp_prevout_summaries.hpp

include_bitcoin_database_typesdir = ${includedir}/bitcoin/database/types
include_bitcoin_database_types_HEADERS = \
include/bitcoin/database/types/fee_rate.hpp \
include/bitcoin/database/types/header_state.hpp \
include/bitcoin/database/types/history.hpp \
include/bitcoin/database/types/position.hpp \
include/bitcoin/database/types/sp_prevout_summaries.hpp \
include/bitcoin/database/types/span.hpp \
include/bitcoin/database/types/type.hpp \
include/bitcoin/database/types/types.hpp \
Expand Down
2 changes: 2 additions & 0 deletions include/bitcoin/database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,12 @@
#include <bitcoin/database/tables/optionals/address.hpp>
#include <bitcoin/database/tables/optionals/filter_bk.hpp>
#include <bitcoin/database/tables/optionals/filter_tx.hpp>
#include <bitcoin/database/tables/optionals/sp_prevout_summaries.hpp>
#include <bitcoin/database/types/fee_rate.hpp>
#include <bitcoin/database/types/header_state.hpp>
#include <bitcoin/database/types/history.hpp>
#include <bitcoin/database/types/position.hpp>
#include <bitcoin/database/types/sp_prevout_summaries.hpp>
#include <bitcoin/database/types/span.hpp>
#include <bitcoin/database/types/type.hpp>
#include <bitcoin/database/types/types.hpp>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ header_links CLASS::get_confirmed_fork(const header_link& fork) const NOEXCEPT
// node/confirmer
TEMPLATE
header_states CLASS::get_validated_fork(size_t& fork_point,
size_t top_checkpoint, bool filter) const NOEXCEPT
size_t top_checkpoint, bool filter, bool sp_prevout_summaries,
size_t sp_prevout_summaries_activation) const NOEXCEPT
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC these parameters are configuration, so shouldn't be passed here. This applies to filter as well, its retention and passage by node::chaser_confirm is redundant/unnecessary. I've removed it from node/database in upcoming PRs.

{
// Reservation may limit allocation to most common scenario.
header_states out{};
Expand All @@ -126,6 +127,7 @@ header_states CLASS::get_validated_fork(size_t& fork_point,

// Disable filter constraint if filtering is disabled.
filter &= filter_enabled();
sp_prevout_summaries &= sp_prevout_summaries_enabled();

///////////////////////////////////////////////////////////////////////////
std::shared_lock interlock{ candidate_reorganization_mutex_ };
Expand All @@ -134,7 +136,9 @@ 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_body(link)))
(!filter || is_filtered_body(link)) &&
(!sp_prevout_summaries || height < sp_prevout_summaries_activation ||
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link-based state of activation should be reduced to a config-driven method, similar to those for the other optional tables (address/filters).

Copy link
Copy Markdown
Member

@evoskuil evoskuil Jun 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_sp_prevout_summaries_indexed(link)))
{
out.emplace_back(link, ec);
link = to_candidate(++height);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ bool CLASS::populate_with_metadata(const input& input,
// ----------------------------------------------------------------------------
// These are used when not performing confirmation. This also implies that
// validation is not being performed, so is used for populating prevouts for
// the purpose of computing client filters in the validation stage. So these
// are not used for in consensus but are kept here for close similarity.
// the purpose of computing optional block indexes. So these are not used in
// consensus but are kept here for close similarity.

TEMPLATE
bool CLASS::populate_without_metadata(const block& block) const NOEXCEPT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ TEMPLATE
height_link CLASS::find_strong_spender_height(
const point& point) const NOEXCEPT
{
size_t out{};
for (const auto& in: to_spenders(point))
{
size_t out{};
if (const auto tx = to_input_tx(in); get_tx_height(out, tx))
break;
return { system::possible_narrow_cast<uint32_t>(out) };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (original code) cast is incorrect, should be:
system::possible_narrow_cast<height_link::integer>(out)

I've fixed this in an independent PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, yes, needs default return for terminal value. Prefer to keep out declaration outside the loop to reduce the clutter.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also one small fix unrelated to the silent payments table...

Yes, please split out.

}

return { system::possible_narrow_cast<uint32_t>(out) };
return {};
}

// find_strong (block)
Expand Down
20 changes: 18 additions & 2 deletions include/bitcoin/database/impl/query/extent.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ size_t CLASS::store_body_size() const NOEXCEPT
+ validated_tx_body_size()
+ address_body_size()
+ filter_bk_body_size()
+ filter_tx_body_size();
+ filter_tx_body_size()
+ sp_prevout_summaries_body_size();
}

TEMPLATE
Expand Down Expand Up @@ -126,7 +127,8 @@ size_t CLASS::store_head_size() const NOEXCEPT
+ validated_tx_head_size()
+ address_head_size()
+ filter_bk_head_size()
+ filter_tx_head_size();
+ filter_tx_head_size()
+ sp_prevout_summaries_head_size();
}

// Sizes.
Expand All @@ -150,6 +152,7 @@ DEFINE_SIZES(validated_bk)
DEFINE_SIZES(validated_tx)
DEFINE_SIZES(filter_bk)
DEFINE_SIZES(filter_tx)
DEFINE_SIZES(sp_prevout_summaries)
DEFINE_SIZES(address)

// Buckets (hashmap + arraymap).
Expand All @@ -167,6 +170,7 @@ DEFINE_BUCKETS(validated_bk)
DEFINE_BUCKETS(validated_tx)
DEFINE_BUCKETS(filter_bk)
DEFINE_BUCKETS(filter_tx)
DEFINE_BUCKETS(sp_prevout_summaries)
DEFINE_BUCKETS(address)

// Records (arrays).
Expand Down Expand Up @@ -255,6 +259,18 @@ bool CLASS::filter_enabled() const NOEXCEPT
return store_.filter_bk.enabled() && store_.filter_tx.enabled();
}

TEMPLATE
bool CLASS::sp_prevout_summaries_enabled() const NOEXCEPT
{
return store_.sp_prevout_summaries.enabled();
}

TEMPLATE
bool CLASS::sp_prevout_summaries_uncompressed() const NOEXCEPT
{
return store_.sp_prevout_summaries_uncompressed();
}

} // namespace database
} // namespace libbitcoin

Expand Down
1 change: 1 addition & 0 deletions include/bitcoin/database/impl/query/initialize.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ bool CLASS::initialize(const block& genesis) NOEXCEPT
// Unsafe for allocation failure, but only used in store creation.
return set_filter_body(link, genesis)
&& set_filter_head(link)
&& set_sp_prevout_summaries(link, genesis)
&& push_candidate(link)
&& push_confirmed(link, true);
// ========================================================================
Expand Down
10 changes: 10 additions & 0 deletions include/bitcoin/database/impl/query/navigate/navigate_arraymap.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ constexpr size_t CLASS::to_filter_tx(const header_link& link) const NOEXCEPT
return link.is_terminal() ? table::filter_tx::link::terminal : link.value;
}

TEMPLATE
constexpr size_t CLASS::to_sp_prevout_summaries(const header_link& link) const
NOEXCEPT
{
static_assert(header_link::terminal <=
table::sp_prevout_summaries::link::terminal);
return link.is_terminal() ? table::sp_prevout_summaries::link::terminal :
link.value;
}

TEMPLATE
constexpr size_t CLASS::to_prevout(const header_link& link) const NOEXCEPT
{
Expand Down
187 changes: 187 additions & 0 deletions include/bitcoin/database/impl/query/sp_prevout_summaries.ipp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/**
* Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS)
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef LIBBITCOIN_DATABASE_QUERY_SP_PREVOUT_SUMMARIES_IPP
#define LIBBITCOIN_DATABASE_QUERY_SP_PREVOUT_SUMMARIES_IPP

#include <utility>
#include <bitcoin/database/define.hpp>
#include <bitcoin/system/chain/silent_payment.hpp>

namespace libbitcoin {
namespace database {

// sp_prevout_summaries
// ----------------------------------------------------------------------------

TEMPLATE
bool CLASS::is_sp_prevout_summaries_indexed(const header_link& link) const
NOEXCEPT
{
const auto uncompressed = sp_prevout_summaries_uncompressed();
table::sp_prevout_summaries::get_format summaries{ {}, uncompressed };
return store_.sp_prevout_summaries.at(to_sp_prevout_summaries(link),
summaries);
}

TEMPLATE
bool CLASS::get_sp_prevout_summaries(sp_prevout_summaries& out,
const header_link& link) const NOEXCEPT
{
const auto uncompressed = sp_prevout_summaries_uncompressed();
table::sp_prevout_summaries::get_summaries summaries{ {}, uncompressed };
if (!store_.sp_prevout_summaries.at(to_sp_prevout_summaries(link),
summaries))
return false;

out = std::move(summaries.summaries);
return true;
}

// node/validator
TEMPLATE
bool CLASS::set_sp_prevout_summaries(const header_link& link,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we intend to support unconfirmed txs as well? Implies an tx override, similar to the block/tx writers. In that case the block writer just iterates over the tx writer.

const block& block) NOEXCEPT
{
using namespace system::chain;
if (!sp_prevout_summaries_enabled())
return true;

const auto txs = to_transactions(link);
const auto& transactions = *block.transactions_ptr();
if (txs.size() != transactions.size())
return false;

database::sp_prevout_summaries summaries{};
summaries.format = sp_prevout_summaries_uncompressed() ?
sp_prevout_summaries::uncompressed : sp_prevout_summaries::compressed;
summaries.records.reserve(transactions.size());

for (size_t index{}; index < transactions.size(); ++index)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally we prefer to use iterator syntax here:
for (const auto& tx: transactions)...
unless the numeric index is required. In this case you can just set:
auto tx_fk = txs.begin() and postfix iterate as *tx_fk++.

This reduces the cognitive load for the reader, not having to associate the additional index var.

{
silent_payment::record record{};
if (!silent_payment::compute_scan_record(record, *transactions[index]))
continue;

sp_prevout_summary entry
{
txs[index],
record.tweak_key,
{},
std::move(record.outputs)
};

if (summaries.format == sp_prevout_summaries::uncompressed &&
!system::decompress(entry.tweak_point, entry.tweak_key))
return false;

summaries.records.push_back(std::move(entry));
}

return set_sp_prevout_summaries(link, summaries);
}

TEMPLATE
bool CLASS::set_sp_prevout_summaries(const header_link& link,
const sp_prevout_summaries& summaries) NOEXCEPT
{
if (!sp_prevout_summaries_enabled())
return true;

const auto uncompressed = sp_prevout_summaries_uncompressed();
if (uncompressed && summaries.format != sp_prevout_summaries::uncompressed)
{
auto copy = summaries;
copy.format = sp_prevout_summaries::uncompressed;
for (auto& record: copy.records)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this should be implemented where the records are created, or by the caller, not in the table writer. The values should really just be stored everywhere as compressed. Def don't want to initially overallocate and then copy the full set.

if (!system::decompress(record.tweak_point, record.tweak_key))
return false;

// ====================================================================
const auto scope = store_.get_transactor();

return store_.sp_prevout_summaries.put(to_sp_prevout_summaries(link),
table::sp_prevout_summaries::put_ref
{
{},
true,
copy
});
// ====================================================================
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a missing return here?

}

// ========================================================================
const auto scope = store_.get_transactor();

// Clean single allocation failure (e.g. disk full).
return store_.sp_prevout_summaries.put(to_sp_prevout_summaries(link),
table::sp_prevout_summaries::put_ref
{
{},
uncompressed,
summaries
});
// ========================================================================
}

TEMPLATE
bool CLASS::get_sp_prevout_summaries_tip(size_t& out) const NOEXCEPT
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Top, not tip. It's a stack, not a spear. ;).

{
const auto tip = sp_prevout_summaries_tip_.load(std::memory_order_relaxed);
if (tip == max_size_t)
return false;

out = tip;
return true;
}

TEMPLATE
void CLASS::set_sp_prevout_summaries_tip(size_t height) NOEXCEPT
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't do this even for the candidate and confirmed top heights. truth in once place.

{
sp_prevout_summaries_tip_.store(height, std::memory_order_relaxed);
}

TEMPLATE
size_t CLASS::recover_sp_prevout_summaries_tip(size_t activation) NOEXCEPT
{
const auto top = get_top_confirmed();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the block is validated the entry exists. Once the block is confirmed, the full set of entries up to the confirmed top exist.

auto tip = max_size_t;

if (sp_prevout_summaries_enabled() && top >= activation)
{
for (auto height = activation; height <= top; ++height)
{
const auto link = to_confirmed(height);
if (link.is_terminal() || !is_sp_prevout_summaries_indexed(link))
break;

tip = height;
}
}

if (tip == max_size_t && !is_zero(activation))
tip = sub1(activation);

set_sp_prevout_summaries_tip(tip);
return tip;
}

} // namespace database
} // namespace libbitcoin

#endif
Loading