diff --git a/Makefile.am b/Makefile.am index 5dcaedea9..9b5d3cf0c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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/silent.cpp \ test/query/sizes.cpp \ test/query/address/address_balance.cpp \ test/query/address/address_history.cpp \ @@ -217,6 +218,7 @@ 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/silent.ipp \ include/bitcoin/database/impl/query/sizes.ipp include_bitcoin_database_impl_query_addressdir = ${includedir}/bitcoin/database/impl/query/address @@ -329,7 +331,8 @@ 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/silent.hpp include_bitcoin_database_typesdir = ${includedir}/bitcoin/database/types include_bitcoin_database_types_HEADERS = \ @@ -337,6 +340,7 @@ include_bitcoin_database_types_HEADERS = \ include/bitcoin/database/types/header_state.hpp \ include/bitcoin/database/types/history.hpp \ include/bitcoin/database/types/position.hpp \ + include/bitcoin/database/types/silent.hpp \ include/bitcoin/database/types/span.hpp \ include/bitcoin/database/types/type.hpp \ include/bitcoin/database/types/types.hpp \ diff --git a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj index ed7e9387b..746eefaf4 100644 --- a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj @@ -184,6 +184,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters index 4aac69976..00a3d45b2 100644 --- a/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters @@ -228,6 +228,9 @@ src\query + + src\query + src\query diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj index 975ce9478..855332c32 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj @@ -197,6 +197,7 @@ + @@ -206,6 +207,7 @@ + @@ -260,6 +262,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters index f867d21d4..cbcdd4b52 100644 --- a/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -302,6 +302,9 @@ include\bitcoin\database\tables\optionals + + include\bitcoin\database\tables\optionals + include\bitcoin\database\tables @@ -329,6 +332,9 @@ include\bitcoin\database\types + + include\bitcoin\database\types + include\bitcoin\database\types @@ -487,6 +493,9 @@ include\bitcoin\database\impl\query + + include\bitcoin\database\impl\query + include\bitcoin\database\impl\query diff --git a/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj b/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj index fb97dc000..ece03f717 100644 --- a/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj @@ -184,6 +184,7 @@ + diff --git a/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters index 4aac69976..00a3d45b2 100644 --- a/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-database-test/libbitcoin-database-test.vcxproj.filters @@ -228,6 +228,9 @@ src\query + + src\query + src\query diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj index 0642cc7e9..f1861c7d3 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj @@ -197,6 +197,7 @@ + @@ -206,6 +207,7 @@ + @@ -260,6 +262,7 @@ + diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters index f867d21d4..cbcdd4b52 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -302,6 +302,9 @@ include\bitcoin\database\tables\optionals + + include\bitcoin\database\tables\optionals + include\bitcoin\database\tables @@ -329,6 +332,9 @@ include\bitcoin\database\types + + include\bitcoin\database\types + include\bitcoin\database\types @@ -487,6 +493,9 @@ include\bitcoin\database\impl\query + + include\bitcoin\database\impl\query + include\bitcoin\database\impl\query diff --git a/include/bitcoin/database.hpp b/include/bitcoin/database.hpp index c7fbe314c..aa741ab16 100644 --- a/include/bitcoin/database.hpp +++ b/include/bitcoin/database.hpp @@ -75,10 +75,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include diff --git a/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp b/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp index 74b4e3b3a..c4d31dc64 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp @@ -124,8 +124,10 @@ header_states CLASS::get_validated_fork(size_t& fork_point, out.reserve(one); code ec{}; - // Disable filter constraint if filtering is disabled. + // Disable optional constraints when the indexes are disabled. const auto filter = filter_enabled(); + const auto silent = silent_enabled(); + const auto silent_start = silent_start_height(); /////////////////////////////////////////////////////////////////////////// std::shared_lock interlock{ candidate_reorganization_mutex_ }; @@ -134,7 +136,8 @@ 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)) && + (!silent || height < silent_start || is_silent_indexed(link))) { out.emplace_back(link, ec); link = to_candidate(++height); diff --git a/include/bitcoin/database/impl/query/consensus/consensus_populate.ipp b/include/bitcoin/database/impl/query/consensus/consensus_populate.ipp index b4220bd65..33f13bb09 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_populate.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_populate.ipp @@ -139,10 +139,8 @@ bool CLASS::populate_with_metadata(const input& input, // populate_without_metadata // ---------------------------------------------------------------------------- -// 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. +// These are used outside confirmation, so validation is not being performed. +// They only populate prevouts and are kept here for close similarity. TEMPLATE bool CLASS::populate_without_metadata(const block& block) const NOEXCEPT diff --git a/include/bitcoin/database/impl/query/extent.ipp b/include/bitcoin/database/impl/query/extent.ipp index b330dab21..f2b600692 100644 --- a/include/bitcoin/database/impl/query/extent.ipp +++ b/include/bitcoin/database/impl/query/extent.ipp @@ -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() + + silent_body_size(); } TEMPLATE @@ -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() + + silent_head_size(); } // Sizes. @@ -150,6 +152,7 @@ DEFINE_SIZES(validated_bk) DEFINE_SIZES(validated_tx) DEFINE_SIZES(filter_bk) DEFINE_SIZES(filter_tx) +DEFINE_SIZES(silent) DEFINE_SIZES(address) // Buckets (hashmap + arraymap). @@ -167,6 +170,7 @@ DEFINE_BUCKETS(validated_bk) DEFINE_BUCKETS(validated_tx) DEFINE_BUCKETS(filter_bk) DEFINE_BUCKETS(filter_tx) +DEFINE_BUCKETS(silent) DEFINE_BUCKETS(address) // Records (arrays). @@ -255,6 +259,18 @@ bool CLASS::filter_enabled() const NOEXCEPT return store_.filter_bk.enabled() && store_.filter_tx.enabled(); } +TEMPLATE +bool CLASS::silent_enabled() const NOEXCEPT +{ + return store_.silent.enabled(); +} + +TEMPLATE +size_t CLASS::silent_start_height() const NOEXCEPT +{ + return store_.silent_start_height(); +} + } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/impl/query/initialize.ipp b/include/bitcoin/database/impl/query/initialize.ipp index 3ea3748d6..decd95a44 100644 --- a/include/bitcoin/database/impl/query/initialize.ipp +++ b/include/bitcoin/database/impl/query/initialize.ipp @@ -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_silent(link, genesis) && push_candidate(link) && push_confirmed(link, true); // ======================================================================== diff --git a/include/bitcoin/database/impl/query/navigate/navigate_arraymap.ipp b/include/bitcoin/database/impl/query/navigate/navigate_arraymap.ipp index 6e1fcb1a9..f8f6eb7b8 100644 --- a/include/bitcoin/database/impl/query/navigate/navigate_arraymap.ipp +++ b/include/bitcoin/database/impl/query/navigate/navigate_arraymap.ipp @@ -48,6 +48,13 @@ 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_silent(const header_link& link) const NOEXCEPT +{ + static_assert(header_link::terminal <= table::silent::link::terminal); + return link.is_terminal() ? table::silent::link::terminal : link.value; +} + TEMPLATE constexpr size_t CLASS::to_prevout(const header_link& link) const NOEXCEPT { diff --git a/include/bitcoin/database/impl/query/silent.ipp b/include/bitcoin/database/impl/query/silent.ipp new file mode 100644 index 000000000..ee1c48919 --- /dev/null +++ b/include/bitcoin/database/impl/query/silent.ipp @@ -0,0 +1,140 @@ +/** + * 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 . + */ +#ifndef LIBBITCOIN_DATABASE_QUERY_SILENT_IPP +#define LIBBITCOIN_DATABASE_QUERY_SILENT_IPP + +#include +#include +#include + +namespace libbitcoin { +namespace database { + +// silent +// ---------------------------------------------------------------------------- + +namespace payment = system::wallet::silent_payment; + +static inline bool to_silent_record(silent_record& out, + const tx_link& tx, const system::chain::transaction& transaction) NOEXCEPT +{ + payment::scan_record record{}; + if (!payment::compute_scan_record(record, transaction)) + return false; + + out = { tx, record.prevouts_summary, std::move(record.outputs) }; + return true; +} + +TEMPLATE +bool CLASS::is_silent_indexed(const header_link& link) const NOEXCEPT +{ + return store_.silent.exists(to_silent(link)); +} + +TEMPLATE +bool CLASS::get_silent(silent& out, const header_link& link) const NOEXCEPT +{ + table::silent::get_records records{}; + if (!store_.silent.at(to_silent(link), records)) + return false; + + out = std::move(records.value); + return true; +} + +TEMPLATE +bool CLASS::set_silent(silent& out, const tx_link& link, + const transaction& tx) const NOEXCEPT +{ + if (link.is_terminal()) + return false; + + if (tx.is_coinbase()) + return true; + + if (!populate_without_metadata(tx)) + return false; + + silent_record record{}; + if (!to_silent_record(record, link, tx)) + return true; + + out.records.push_back(std::move(record)); + return true; +} + +// node/validator +TEMPLATE +bool CLASS::set_silent(const header_link& link, + const block& block) NOEXCEPT +{ + if (!silent_enabled()) + return true; + + const auto height = get_height(link); + if (height.is_terminal()) + return false; + + if (height.value < silent_start_height()) + return true; + + const auto txs = to_transactions(link); + const auto& transactions = *block.transactions_ptr(); + if (txs.size() != transactions.size()) + return false; + + database::silent value{}; + value.records.reserve(transactions.size()); + + auto tx = txs.begin(); + for (const auto& transaction: transactions) + if (!set_silent(value, *tx++, *transaction)) + return false; + + return set_silent(link, value); +} + +TEMPLATE +bool CLASS::set_silent(const header_link& link, + const silent& value) NOEXCEPT +{ + if (!silent_enabled()) + return true; + + const auto height = get_height(link); + if (height.is_terminal()) + return false; + + if (height.value < silent_start_height()) + return true; + + // ======================================================================== + const auto scope = store_.get_transactor(); + + // Clean single allocation failure (e.g. disk full). + const table::silent::put_ref put{ {}, value }; + return store_.silent.put(to_silent(link), put); + // ======================================================================== +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/store.ipp b/include/bitcoin/database/impl/store.ipp index 575859e2d..a8484a41d 100644 --- a/include/bitcoin/database/impl/store.ipp +++ b/include/bitcoin/database/impl/store.ipp @@ -125,7 +125,10 @@ const std::unordered_map CLASS::tables { table_t::filter_bk_body, "filter_bk_body" }, { table_t::filter_tx_table, "filter_tx_table" }, { table_t::filter_tx_head, "filter_tx_head" }, - { table_t::filter_tx_body, "filter_tx_body" } + { table_t::filter_tx_body, "filter_tx_body" }, + { table_t::silent_table, "silent_table" }, + { table_t::silent_head, "silent_head" }, + { table_t::silent_body, "silent_body" } }; TEMPLATE @@ -216,6 +219,10 @@ CLASS::store(const settings& config) NOEXCEPT filter_tx_body_(body(config.path, schema::optionals::filter_tx), config.filter_tx_size, config.filter_tx_rate, sequential), filter_tx(filter_tx_head_, filter_tx_body_, config.filter_tx_buckets), + silent_head_(head(config.path / schema::dir::heads, schema::optionals::silent), 1, 0, random), + silent_body_(body(config.path, schema::optionals::silent), config.silent_size, config.silent_rate, sequential), + silent(silent_head_, silent_body_, config.silent_buckets), + // Locks. // ------------------------------------------------------------------------ @@ -238,6 +245,12 @@ uint8_t CLASS::interval_depth() const NOEXCEPT return system::limit(configuration_.interval_depth); } +TEMPLATE +size_t CLASS::silent_start_height() const NOEXCEPT +{ + return configuration_.silent_start_height; +} + TEMPLATE code CLASS::create(const event_handler& handler) NOEXCEPT { @@ -309,6 +322,8 @@ code CLASS::create(const event_handler& handler) NOEXCEPT create(ec, filter_bk_body_, table_t::filter_bk_body); create(ec, filter_tx_head_, table_t::filter_tx_head); create(ec, filter_tx_body_, table_t::filter_tx_body); + create(ec, silent_head_, table_t::silent_head); + create(ec, silent_body_, table_t::silent_body); const auto populate = [&handler](code& ec, auto& storage, table_t table) NOEXCEPT @@ -345,6 +360,7 @@ code CLASS::create(const event_handler& handler) NOEXCEPT populate(ec, address, table_t::address_table); populate(ec, filter_bk, table_t::filter_bk_table); populate(ec, filter_tx, table_t::filter_tx_table); + populate(ec, silent, table_t::silent_table); if (ec) { @@ -418,6 +434,7 @@ code CLASS::open(const event_handler& handler) NOEXCEPT verify(ec, address, table_t::address_table); verify(ec, filter_bk, table_t::filter_bk_table); verify(ec, filter_tx, table_t::filter_tx_table); + verify(ec, silent, table_t::silent_table); if (ec) { @@ -534,6 +551,7 @@ code CLASS::snapshot(const event_handler& handler, bool prune) NOEXCEPT flush(ec, address_body_, table_t::address_body); flush(ec, filter_bk_body_, table_t::filter_bk_body); flush(ec, filter_tx_body_, table_t::filter_tx_body); + flush(ec, silent_body_, table_t::silent_body); if (!ec) ec = backup(handler, prune); if (!prune) transactor_mutex_.unlock(); @@ -603,6 +621,8 @@ code CLASS::reload(const event_handler& handler) NOEXCEPT reload(ec, filter_bk_body_, table_t::filter_bk_body); reload(ec, filter_tx_head_, table_t::filter_tx_head); reload(ec, filter_tx_body_, table_t::filter_tx_body); + reload(ec, silent_head_, table_t::silent_head); + reload(ec, silent_body_, table_t::silent_body); transactor_mutex_.unlock(); return ec; @@ -649,6 +669,7 @@ code CLASS::close(const event_handler& handler) NOEXCEPT close(ec, address, table_t::address_table); close(ec, filter_bk, table_t::filter_bk_table); close(ec, filter_tx, table_t::filter_tx_table); + close(ec, silent, table_t::silent_table); if (!ec) ec = unload_close(handler); @@ -721,6 +742,8 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT open(ec, filter_bk_body_, table_t::filter_bk_body); open(ec, filter_tx_head_, table_t::filter_tx_head); open(ec, filter_tx_body_, table_t::filter_tx_body); + open(ec, silent_head_, table_t::silent_head); + open(ec, silent_body_, table_t::silent_body); const auto load = [&handler](code& ec, auto& storage, table_t table) NOEXCEPT { @@ -770,6 +793,8 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT load(ec, filter_bk_body_, table_t::filter_bk_body); load(ec, filter_tx_head_, table_t::filter_tx_head); load(ec, filter_tx_body_, table_t::filter_tx_body); + load(ec, silent_head_, table_t::silent_head); + load(ec, silent_body_, table_t::silent_body); // create, open, and restore each invoke open_load. const auto dirty = header_body_.size() > schema::header::minrow; @@ -829,6 +854,8 @@ code CLASS::unload_close(const event_handler& handler) NOEXCEPT unload(ec, filter_bk_body_, table_t::filter_bk_body); unload(ec, filter_tx_head_, table_t::filter_tx_head); unload(ec, filter_tx_body_, table_t::filter_tx_body); + unload(ec, silent_head_, table_t::silent_head); + unload(ec, silent_body_, table_t::silent_body); const auto close = [&handler](code& ec, auto& storage, table_t table) NOEXCEPT { @@ -878,6 +905,8 @@ code CLASS::unload_close(const event_handler& handler) NOEXCEPT close(ec, filter_bk_body_, table_t::filter_bk_body); close(ec, filter_tx_head_, table_t::filter_tx_head); close(ec, filter_tx_body_, table_t::filter_tx_body); + close(ec, silent_head_, table_t::silent_head); + close(ec, silent_body_, table_t::silent_body); return ec; } @@ -918,6 +947,7 @@ code CLASS::backup(const event_handler& handler, bool prune) NOEXCEPT backup(ec, address, table_t::address_table); backup(ec, filter_bk, table_t::filter_bk_table); backup(ec, filter_tx, table_t::filter_tx_table); + backup(ec, silent, table_t::silent_table); if (ec) return ec; @@ -983,6 +1013,7 @@ code CLASS::dump(const path& folder, auto address_buffer = address_head_.get(); auto filter_bk_buffer = filter_bk_head_.get(); auto filter_tx_buffer = filter_tx_head_.get(); + auto silent_buffer = silent_head_.get(); if (!header_buffer) return error::unloaded_file; if (!input_buffer) return error::unloaded_file; @@ -1005,6 +1036,7 @@ code CLASS::dump(const path& folder, if (!address_buffer) return error::unloaded_file; if (!filter_bk_buffer) return error::unloaded_file; if (!filter_tx_buffer) return error::unloaded_file; + if (!silent_buffer) return error::unloaded_file; code ec{ error::success }; const auto dump = [&handler, &folder](code& ec, const auto& storage, @@ -1039,6 +1071,7 @@ code CLASS::dump(const path& folder, dump(ec, address_buffer, schema::optionals::address, table_t::address_head); dump(ec, filter_bk_buffer, schema::optionals::filter_bk, table_t::filter_bk_head); dump(ec, filter_tx_buffer, schema::optionals::filter_tx, table_t::filter_tx_head); + dump(ec, silent_buffer, schema::optionals::silent, table_t::silent_head); return ec; } @@ -1132,6 +1165,7 @@ code CLASS::restore(const event_handler& handler) NOEXCEPT restore(ec, address, table_t::address_table); restore(ec, filter_bk, table_t::filter_bk_table); restore(ec, filter_tx, table_t::filter_tx_table); + restore(ec, silent, table_t::silent_table); if (ec) /* code */ unload_close(handler); @@ -1194,6 +1228,7 @@ code CLASS::get_fault() const NOEXCEPT if ((ec = address_body_.get_fault())) return ec; if ((ec = filter_bk_body_.get_fault())) return ec; if ((ec = filter_tx_body_.get_fault())) return ec; + if ((ec = silent_body_.get_fault())) return ec; return ec; } @@ -1224,6 +1259,7 @@ size_t CLASS::get_space() const NOEXCEPT space(address_body_); space(filter_bk_body_); space(filter_tx_body_); + space(silent_body_); return total; } @@ -1258,6 +1294,7 @@ void CLASS::report(const error_handler& handler) const NOEXCEPT report(address_body_, table_t::address_body); report(filter_bk_body_, table_t::filter_bk_body); report(filter_tx_body_, table_t::filter_tx_body); + report(silent_body_, table_t::silent_body); } BC_POP_WARNING() diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index a936ce35a..3e30c7cc0 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -124,6 +124,7 @@ class query size_t validated_tx_head_size() const NOEXCEPT; size_t filter_bk_head_size() const NOEXCEPT; size_t filter_tx_head_size() const NOEXCEPT; + size_t silent_head_size() const NOEXCEPT; size_t address_head_size() const NOEXCEPT; /// Table body logical byte sizes. @@ -145,6 +146,7 @@ class query size_t validated_tx_body_size() const NOEXCEPT; size_t filter_bk_body_size() const NOEXCEPT; size_t filter_tx_body_size() const NOEXCEPT; + size_t silent_body_size() const NOEXCEPT; size_t address_body_size() const NOEXCEPT; /// Table (head + body) logical byte sizes. @@ -166,6 +168,7 @@ class query size_t validated_tx_size() const NOEXCEPT; size_t filter_bk_size() const NOEXCEPT; size_t filter_tx_size() const NOEXCEPT; + size_t silent_size() const NOEXCEPT; size_t address_size() const NOEXCEPT; /// Buckets (hashmap + arraymap). @@ -181,6 +184,7 @@ class query size_t validated_tx_buckets() const NOEXCEPT; size_t filter_bk_buckets() const NOEXCEPT; size_t filter_tx_buckets() const NOEXCEPT; + size_t silent_buckets() const NOEXCEPT; size_t address_buckets() const NOEXCEPT; /// Records. @@ -208,6 +212,8 @@ class query /// Optional/configured table state. bool address_enabled() const NOEXCEPT; bool filter_enabled() const NOEXCEPT; + bool silent_enabled() const NOEXCEPT; + size_t silent_start_height() const NOEXCEPT; size_t interval_span() const NOEXCEPT; /// Initialization (natural-keyed). @@ -306,6 +312,7 @@ class query constexpr size_t to_validated_bk(const header_link& link) const NOEXCEPT; constexpr size_t to_filter_bk(const header_link& link) const NOEXCEPT; constexpr size_t to_filter_tx(const header_link& link) const NOEXCEPT; + constexpr size_t to_silent(const header_link& link) const NOEXCEPT; constexpr size_t to_prevout(const header_link& link) const NOEXCEPT; constexpr size_t to_txs(const header_link& link) const NOEXCEPT; @@ -708,6 +715,18 @@ class query bool set_filter_head(const header_link& link, const hash_digest& head, const hash_digest& hash) NOEXCEPT; + /// Silent payment scan index. + /// ----------------------------------------------------------------------- + + bool is_silent_indexed(const header_link& link) const NOEXCEPT; + bool get_silent(silent& out, const header_link& link) const NOEXCEPT; + bool set_silent(silent& out, const tx_link& link, + const transaction& tx) const NOEXCEPT; + bool set_silent(const header_link& link, const block& block) + NOEXCEPT; + bool set_silent(const header_link& link, + const silent& value) NOEXCEPT; + protected: /// Network /// ----------------------------------------------------------------------- @@ -942,6 +961,7 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) #include #include #include +#include BC_POP_WARNING() diff --git a/include/bitcoin/database/settings.hpp b/include/bitcoin/database/settings.hpp index 749c90368..e83e0c60b 100644 --- a/include/bitcoin/database/settings.hpp +++ b/include/bitcoin/database/settings.hpp @@ -126,6 +126,11 @@ struct BCD_API settings uint32_t filter_tx_buckets; uint64_t filter_tx_size; uint16_t filter_tx_rate; + + uint32_t silent_buckets; + uint64_t silent_size; + uint16_t silent_rate; + size_t silent_start_height; }; } // namespace database diff --git a/include/bitcoin/database/store.hpp b/include/bitcoin/database/store.hpp index 762ec95b9..a808adb3b 100644 --- a/include/bitcoin/database/store.hpp +++ b/include/bitcoin/database/store.hpp @@ -60,6 +60,9 @@ class store /// Depth of electrum merkle tree interval caching. uint8_t interval_depth() const NOEXCEPT; + /// First height at which the silent payment index is required. + size_t silent_start_height() const NOEXCEPT; + /// Methods. /// ----------------------------------------------------------------------- @@ -131,6 +134,7 @@ class store table::address address; table::filter_bk filter_bk; table::filter_tx filter_tx; + table::silent silent; protected: using path = std::filesystem::path; @@ -227,6 +231,10 @@ class store Storage filter_tx_head_; Storage filter_tx_body_; + // slab + Storage silent_head_; + Storage silent_body_; + /// Locks. /// ----------------------------------------------------------------------- diff --git a/include/bitcoin/database/tables/names.hpp b/include/bitcoin/database/tables/names.hpp index b5a1c018b..b10d89b0b 100644 --- a/include/bitcoin/database/tables/names.hpp +++ b/include/bitcoin/database/tables/names.hpp @@ -68,6 +68,7 @@ namespace optionals constexpr auto address = "address"; constexpr auto filter_bk = "filter_bk"; constexpr auto filter_tx = "filter_tx"; + constexpr auto silent = "silent"; } namespace locks diff --git a/include/bitcoin/database/tables/optionals/silent.hpp b/include/bitcoin/database/tables/optionals/silent.hpp new file mode 100644 index 000000000..b719bcedd --- /dev/null +++ b/include/bitcoin/database/tables/optionals/silent.hpp @@ -0,0 +1,132 @@ +/** + * 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 . + */ +#ifndef LIBBITCOIN_DATABASE_TABLES_OPTIONALS_SILENT_HPP +#define LIBBITCOIN_DATABASE_TABLES_OPTIONALS_SILENT_HPP + +#include +#include +#include +#include + +namespace libbitcoin { +namespace database { +namespace table { + +/// silent is a slab of silent payment scan records indexed by block link. +struct silent + : public array_map +{ + using array_map::arraymap; + + using tx = transaction::link; + using ix = transaction::ix; + + static link serialized_size(const database::silent& value) NOEXCEPT + { + auto size = variable_size(value.records.size()); + + for (const auto& record: value.records) + { + const auto outputs = record.outputs.size(); + size += tx::size + system::ec_compressed_size + + variable_size(outputs); + size += outputs * (ix::size + system::ec_xonly_size); + } + + return system::possible_narrow_cast(size); + } + + struct get_records + : public schema::silent + { + inline link count() const NOEXCEPT + { + return serialized_size(value); + } + + inline bool from_data(reader& source) NOEXCEPT + { + value.records.resize(source.read_variable()); + + for (auto& record: value.records) + { + const auto tx_value = + source.read_little_endian(); + record.tx = tx{ tx_value }; + record.prevouts_summary = + source.read_forward(); + + record.outputs.resize(source.read_variable()); + + for (auto& output: record.outputs) + { + output.index = source.read_little_endian(); + output.key = source.read_forward(); + } + } + + BC_ASSERT(!source || source.get_read_position() == count()); + return source; + } + + database::silent value{}; + }; + + struct put_ref + : public schema::silent + { + inline link count() const NOEXCEPT + { + return serialized_size(value); + } + + inline bool to_data(finalizer& sink) const NOEXCEPT + { + sink.write_variable(value.records.size()); + + for (const auto& record: value.records) + { + sink.write_little_endian( + record.tx.value); + sink.write_bytes(record.prevouts_summary); + sink.write_variable(record.outputs.size()); + + for (const auto& output: record.outputs) + { + const auto index = + system::possible_narrow_cast( + output.index); + sink.write_little_endian(index); + sink.write_bytes(output.key); + } + } + + BC_ASSERT(!sink || sink.get_write_position() == count()); + return sink; + } + + const database::silent& value; + }; +}; + +} // namespace table +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/tables/schema.hpp b/include/bitcoin/database/tables/schema.hpp index 9775871c9..58e9d7a14 100644 --- a/include/bitcoin/database/tables/schema.hpp +++ b/include/bitcoin/database/tables/schema.hpp @@ -50,6 +50,7 @@ constexpr size_t tx = 4; // ->tx record. constexpr size_t block = 3; // ->header record. constexpr size_t tx_slab = 5; // ->validated_tx record. constexpr size_t filter_ = 5; // ->filter record. +constexpr size_t silent_ = 5; // ->silent record. constexpr size_t doubles_ = 4; // doubles bucket (no actual keys). /// Archive tables. @@ -393,6 +394,22 @@ struct filter_tx static_assert(link::size == 5u); }; +// slab arraymap +struct silent +{ + static constexpr size_t align = false; + static constexpr size_t pk = schema::silent_; + using link = linkage; + static constexpr size_t minsize = + one; + static constexpr size_t minrow = minsize; + static constexpr size_t size = max_size_t; + static inline link count() NOEXCEPT; + static_assert(minsize == 1u); + static_assert(minrow == 1u); + static_assert(link::size == 5u); +}; + } // namespace schema } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/tables/table.hpp b/include/bitcoin/database/tables/table.hpp index b35b37806..61957a5f6 100644 --- a/include/bitcoin/database/tables/table.hpp +++ b/include/bitcoin/database/tables/table.hpp @@ -91,7 +91,10 @@ enum class table_t filter_bk_body, filter_tx_table, filter_tx_head, - filter_tx_body + filter_tx_body, + silent_table, + silent_head, + silent_body }; } // namespace database diff --git a/include/bitcoin/database/tables/tables.hpp b/include/bitcoin/database/tables/tables.hpp index ab005a75f..b0eaca3d2 100644 --- a/include/bitcoin/database/tables/tables.hpp +++ b/include/bitcoin/database/tables/tables.hpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include diff --git a/include/bitcoin/database/types/silent.hpp b/include/bitcoin/database/types/silent.hpp new file mode 100644 index 000000000..a3a36ac9b --- /dev/null +++ b/include/bitcoin/database/types/silent.hpp @@ -0,0 +1,47 @@ +/** + * 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 . + */ +#ifndef LIBBITCOIN_DATABASE_TYPES_SILENT_HPP +#define LIBBITCOIN_DATABASE_TYPES_SILENT_HPP + +#include +#include +#include + +namespace libbitcoin { +namespace database { + +using pay_witness_taproot_output = + system::wallet::silent_payment::pay_witness_taproot_output; + +struct BCD_API silent_record +{ + table::transaction::link tx{}; + system::ec_compressed prevouts_summary{}; + std_vector outputs{}; +}; + +struct BCD_API silent +{ + std_vector records{}; +}; + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/types/types.hpp b/include/bitcoin/database/types/types.hpp index 14d6fa215..698d7733e 100644 --- a/include/bitcoin/database/types/types.hpp +++ b/include/bitcoin/database/types/types.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include diff --git a/src/settings.cpp b/src/settings.cpp index b87279e1e..f493b1599 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -99,7 +99,12 @@ settings::settings() NOEXCEPT filter_tx_buckets{ 128 }, filter_tx_size{ 1 }, - filter_tx_rate{ 50 } + filter_tx_rate{ 50 }, + + silent_buckets{ 128 }, + silent_size{ 1 }, + silent_rate{ 50 }, + silent_start_height{ 0 } { } diff --git a/test/query/consensus/consensus_forks.cpp b/test/query/consensus/consensus_forks.cpp index 5e26226fb..29f1f8a9e 100644 --- a/test/query/consensus/consensus_forks.cpp +++ b/test/query/consensus/consensus_forks.cpp @@ -22,4 +22,118 @@ BOOST_FIXTURE_TEST_SUITE(query_consensus_tests, test::directory_setup_fixture) +static void set_validated_fork(test::query_t& query) +{ + BOOST_REQUIRE(query.initialize(test::genesis)); + BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, false, false)); + BOOST_REQUIRE(query.set(test::block2, context{ 0, 2, 0 }, false, false)); + + const auto link1 = query.to_header(test::block1.hash()); + const auto link2 = query.to_header(test::block2.hash()); + BOOST_REQUIRE(query.push_candidate(link1)); + BOOST_REQUIRE(query.push_candidate(link2)); + BOOST_REQUIRE(query.set_block_valid(link1)); + BOOST_REQUIRE(query.set_block_valid(link2)); +} + +static void set_optional_indexes(test::query_t& query, + const header_link& link, const system::chain::block& block) +{ + BOOST_REQUIRE(query.set_filter_body(link, block)); + BOOST_REQUIRE(query.set_filter_head(link)); + BOOST_REQUIRE(query.set_silent(link, block)); +} + +BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__optional_unindexed__empty) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + set_validated_fork(query); + + size_t fork_point{}; + const auto fork = query.get_validated_fork(fork_point); + BOOST_REQUIRE_EQUAL(fork_point, 0u); + BOOST_REQUIRE(fork.empty()); +} + +BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__optional_indexed__returns_validated) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + set_validated_fork(query); + + const auto link1 = query.to_header(test::block1.hash()); + const auto link2 = query.to_header(test::block2.hash()); + set_optional_indexes(query, link1, test::block1); + set_optional_indexes(query, link2, test::block2); + + size_t fork_point{}; + const auto fork = query.get_validated_fork(fork_point); + BOOST_REQUIRE_EQUAL(fork_point, 0u); + BOOST_REQUIRE_EQUAL(fork.size(), 2u); + BOOST_REQUIRE(fork[0].link == link1); + BOOST_REQUIRE(fork[1].link == link2); +} + +BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__silent_below_start__not_required) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + settings.silent_start_height = 2; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + set_validated_fork(query); + + const auto link1 = query.to_header(test::block1.hash()); + const auto link2 = query.to_header(test::block2.hash()); + BOOST_REQUIRE(query.set_filter_body(link1, test::block1)); + BOOST_REQUIRE(query.set_filter_head(link1)); + BOOST_REQUIRE(query.set_filter_body(link2, test::block2)); + BOOST_REQUIRE(query.set_filter_head(link2)); + + size_t fork_point{}; + auto fork = query.get_validated_fork(fork_point); + BOOST_REQUIRE_EQUAL(fork_point, 0u); + BOOST_REQUIRE_EQUAL(fork.size(), 1u); + BOOST_REQUIRE(fork.front().link == link1); + + BOOST_REQUIRE(query.set_silent(link2, test::block2)); + fork = query.get_validated_fork(fork_point); + BOOST_REQUIRE_EQUAL(fork.size(), 2u); + BOOST_REQUIRE(fork[0].link == link1); + BOOST_REQUIRE(fork[1].link == link2); +} + +BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__silent_disabled__filter_indexed__returns_validated) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + settings.silent_buckets = 0; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + set_validated_fork(query); + + const auto link1 = query.to_header(test::block1.hash()); + const auto link2 = query.to_header(test::block2.hash()); + BOOST_REQUIRE(query.set_filter_body(link1, test::block1)); + BOOST_REQUIRE(query.set_filter_head(link1)); + BOOST_REQUIRE(query.set_filter_body(link2, test::block2)); + BOOST_REQUIRE(query.set_filter_head(link2)); + + size_t fork_point{}; + const auto fork = query.get_validated_fork(fork_point); + BOOST_REQUIRE_EQUAL(fork_point, 0u); + BOOST_REQUIRE_EQUAL(fork.size(), 2u); + BOOST_REQUIRE(fork[0].link == link1); + BOOST_REQUIRE(fork[1].link == link2); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/extent.cpp b/test/query/extent.cpp index b2f81f1ea..c2088c412 100644 --- a/test/query/extent.cpp +++ b/test/query/extent.cpp @@ -57,6 +57,7 @@ BOOST_AUTO_TEST_CASE(query_extent__body_sizes__genesis__expected) BOOST_REQUIRE_EQUAL(query.validated_tx_body_size(), zero); BOOST_REQUIRE_EQUAL(query.filter_bk_body_size(), schema::filter_bk::minrow); BOOST_REQUIRE_EQUAL(query.filter_tx_body_size(), 5u); + BOOST_REQUIRE_EQUAL(query.silent_body_size(), schema::silent::minrow); BOOST_REQUIRE_EQUAL(query.address_body_size(), schema::address::minrow); } @@ -81,6 +82,7 @@ BOOST_AUTO_TEST_CASE(query_extent__buckets__genesis__expected) BOOST_REQUIRE_EQUAL(query.validated_bk_buckets(), 128u); BOOST_REQUIRE_EQUAL(query.filter_tx_buckets(), 128u); BOOST_REQUIRE_EQUAL(query.filter_bk_buckets(), 128u); + BOOST_REQUIRE_EQUAL(query.silent_buckets(), 128u); BOOST_REQUIRE_EQUAL(query.address_buckets(), 128u); } @@ -136,6 +138,7 @@ BOOST_AUTO_TEST_CASE(query_extent__optionals_enabled__default__true) BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(query.address_enabled()); BOOST_REQUIRE(query.filter_enabled()); + BOOST_REQUIRE(query.silent_enabled()); } BOOST_AUTO_TEST_CASE(query_extent__address_enabled__disabled__false) @@ -149,6 +152,7 @@ BOOST_AUTO_TEST_CASE(query_extent__address_enabled__disabled__false) BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(!query.address_enabled()); BOOST_REQUIRE(query.filter_enabled()); + BOOST_REQUIRE(query.silent_enabled()); } BOOST_AUTO_TEST_CASE(query_extent__filter_enabled__disabled__false) @@ -162,6 +166,21 @@ BOOST_AUTO_TEST_CASE(query_extent__filter_enabled__disabled__false) BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(query.address_enabled()); BOOST_REQUIRE(!query.filter_enabled()); + BOOST_REQUIRE(query.silent_enabled()); +} + +BOOST_AUTO_TEST_CASE(query_extent__silent_enabled__disabled__false) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + settings.silent_buckets = 0; + 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.address_enabled()); + BOOST_REQUIRE(query.filter_enabled()); + BOOST_REQUIRE(!query.silent_enabled()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/silent.cpp b/test/query/silent.cpp new file mode 100644 index 000000000..9507df9e8 --- /dev/null +++ b/test/query/silent.cpp @@ -0,0 +1,359 @@ +/** + * 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 . + */ +#include "../test.hpp" +#include "../mocks/blocks.hpp" +#include "../mocks/chunk_store.hpp" + +BOOST_FIXTURE_TEST_SUITE(query_silent_tests, test::directory_setup_fixture) + +using namespace system; + +constexpr auto expected_prevouts_summary = base16_array( + "024ac253c216532e961988e2a8ce266a447c894c781e52ef6cee902361db960004"); +constexpr auto expected_block_prevouts_summary = base16_array( + "0234312c771f033144f850d03442e69047e715bcffb27ceab043f5993d452584f7"); +constexpr auto expected_output = base16_array("3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1"); +constexpr auto second_output = base16_array("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + +static chain::transaction funding_transaction() +{ + const chain::script output_script + { + base16_chunk("76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac"), + false + }; + + return + { + 2u, + { chain::input{} }, + { { 0u, output_script } }, + 0u + }; +} + +static chain::transaction silent_spend_transaction( + const hash_digest& prevout_hash) +{ + const chain::script input_script + { + base16_chunk("483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5"), + false + }; + const chain::script output_script + { + base16_chunk("51203e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1"), + false + }; + + return + { + 2u, + { + { + { prevout_hash, 0u }, + input_script, + chain::witness{}, + max_uint32 + } + }, + { { 0u, output_script } }, + 0u + }; +} + +static chain::transaction empty_coinbase() +{ + return + { + 2u, + { chain::input{} }, + { { 0u, chain::script{} } }, + 0u + }; +} + +static chain::block make_block(const hash_digest& previous, + const chain::transactions& transactions, uint32_t nonce) +{ + return + { + { + 0x31323334, + previous, + hash_digest{ 0x1a }, + 0x41424344, + 0x51525354, + nonce + }, + transactions + }; +} + +BOOST_AUTO_TEST_CASE(query_silent__initialize__active_from_genesis__indexed) +{ + database::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 auto link = query.to_confirmed(0); + BOOST_REQUIRE(query.is_silent_indexed(link)); + + silent actual{}; + BOOST_REQUIRE(query.get_silent(actual, link)); + BOOST_REQUIRE(actual.records.empty()); +} + +BOOST_AUTO_TEST_CASE(query_silent__initialize__below_start_height__unindexed) +{ + database::settings settings{}; + settings.path = TEST_DIRECTORY; + settings.silent_start_height = 1; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + + const auto link = query.to_confirmed(0); + BOOST_REQUIRE(!query.is_silent_indexed(link)); + + silent actual{}; + BOOST_REQUIRE(!query.get_silent(actual, link)); + BOOST_REQUIRE(query.set_silent(link, test::genesis)); + BOOST_REQUIRE(!query.is_silent_indexed(link)); +} + +BOOST_AUTO_TEST_CASE(query_silent__set_silent__disabled__unindexed) +{ + database::settings settings{}; + settings.path = TEST_DIRECTORY; + settings.silent_buckets = 0; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + BOOST_REQUIRE(query.initialize(test::genesis)); + + const auto link = query.to_confirmed(0); + BOOST_REQUIRE(query.set_silent(link, test::genesis)); + BOOST_REQUIRE(!query.is_silent_indexed(link)); + + silent actual{}; + BOOST_REQUIRE(!query.get_silent(actual, link)); +} + +BOOST_AUTO_TEST_CASE(query_silent__set_silent__terminal_link__false) +{ + database::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)); + + silent value{}; + BOOST_REQUIRE(!query.set_silent(header_link::terminal, test::genesis)); + BOOST_REQUIRE(!query.set_silent(header_link::terminal, value)); +} + +BOOST_AUTO_TEST_CASE(query_silent__set_silent__records__round_trips) +{ + database::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, context{ 0, 1, 0 }, false, false)); + + const auto link = query.to_header(test::block1.hash()); + BOOST_REQUIRE(!link.is_terminal()); + + const auto txs = query.to_transactions(link); + BOOST_REQUIRE_EQUAL(txs.size(), 1u); + + const auto tx = txs.front(); + const silent expected + { + { + { + tx, + expected_prevouts_summary, + { { 1u, expected_output }, { 2u, second_output } } + } + } + }; + + BOOST_REQUIRE(!query.is_silent_indexed(link)); + BOOST_REQUIRE(query.set_silent(link, expected)); + + silent actual{}; + BOOST_REQUIRE(query.get_silent(actual, link)); + BOOST_REQUIRE_EQUAL(actual.records.size(), 1u); + BOOST_REQUIRE(actual.records.front().tx == tx); + BOOST_REQUIRE_EQUAL(actual.records.front().prevouts_summary, + expected_prevouts_summary); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs.size(), 2u); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs[0].index, 1u); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs[0].key, expected_output); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs[1].index, 2u); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs[1].key, second_output); +} + +BOOST_AUTO_TEST_CASE(query_silent__set_silent_tx__coinbase__no_record) +{ + database::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 auto link = query.to_confirmed(0); + const auto txs = query.to_transactions(link); + BOOST_REQUIRE_EQUAL(txs.size(), 1u); + + silent actual{}; + const auto& tx = *test::genesis.transactions_ptr()->front(); + BOOST_REQUIRE(query.set_silent(actual, txs.front(), tx)); + BOOST_REQUIRE(actual.records.empty()); +} + +BOOST_AUTO_TEST_CASE(query_silent__set_silent_tx__terminal_link__false) +{ + database::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)); + + silent actual{}; + BOOST_REQUIRE(!query.set_silent(actual, tx_link::terminal, empty_coinbase())); + BOOST_REQUIRE(actual.records.empty()); +} + +BOOST_AUTO_TEST_CASE(query_silent__set_silent_tx__populates_prevout__record) +{ + database::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 auto funding_tx = funding_transaction(); + const auto funding = make_block(test::genesis.hash(), { funding_tx }, 0x01); + BOOST_REQUIRE(query.set(funding, context{ 0, 1, 0 }, false, false)); + + const chain::transactions spend_txs + { + empty_coinbase(), + silent_spend_transaction(funding_tx.hash(false)) + }; + auto spend = make_block(funding.hash(), spend_txs, 0x02); + BOOST_REQUIRE(query.set(spend, context{ 0, 2, 0 }, false, false)); + + const auto link = query.to_header(spend.hash()); + const auto txs = query.to_transactions(link); + BOOST_REQUIRE_EQUAL(txs.size(), 2u); + const auto& spend_tx = *spend.transactions_ptr()->at(1); + BOOST_REQUIRE(!spend_tx.inputs_ptr()->front()->prevout); + + silent actual{}; + BOOST_REQUIRE(query.set_silent(actual, txs[1], spend_tx)); + BOOST_REQUIRE(spend_tx.inputs_ptr()->front()->prevout); + BOOST_REQUIRE_EQUAL(actual.records.size(), 1u); + BOOST_REQUIRE(actual.records.front().tx == txs[1]); + BOOST_REQUIRE_EQUAL(actual.records.front().prevouts_summary, + expected_block_prevouts_summary); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs.size(), 1u); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs.front().index, 0u); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs.front().key, + expected_output); +} + +BOOST_AUTO_TEST_CASE(query_silent__set_silent_block__populates_prevouts__indexed) +{ + database::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 auto funding_tx = funding_transaction(); + const auto funding = make_block(test::genesis.hash(), { funding_tx }, 0x01); + BOOST_REQUIRE(query.set(funding, context{ 0, 1, 0 }, false, false)); + + const chain::transactions spend_txs + { + empty_coinbase(), + silent_spend_transaction(funding_tx.hash(false)) + }; + auto spend = make_block(funding.hash(), spend_txs, 0x02); + BOOST_REQUIRE(query.set(spend, context{ 0, 2, 0 }, false, false)); + + const auto link = query.to_header(spend.hash()); + const auto txs = query.to_transactions(link); + BOOST_REQUIRE_EQUAL(txs.size(), 2u); + const auto& spend_tx = *spend.transactions_ptr()->at(1); + BOOST_REQUIRE(!spend_tx.inputs_ptr()->front()->prevout); + + BOOST_REQUIRE(query.set_silent(link, spend)); + BOOST_REQUIRE(spend_tx.inputs_ptr()->front()->prevout); + + silent actual{}; + BOOST_REQUIRE(query.get_silent(actual, link)); + BOOST_REQUIRE_EQUAL(actual.records.size(), 1u); + BOOST_REQUIRE(actual.records.front().tx == txs[1]); + BOOST_REQUIRE_EQUAL(actual.records.front().prevouts_summary, + expected_block_prevouts_summary); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs.size(), 1u); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs.front().index, 0u); + BOOST_REQUIRE_EQUAL(actual.records.front().outputs.front().key, + expected_output); +} + +BOOST_AUTO_TEST_CASE(query_silent__set_silent_block__missing_prevout__false) +{ + database::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 chain::transactions spend_txs + { + empty_coinbase(), + silent_spend_transaction(hash_digest{ 0x42 }) + }; + auto spend = make_block(test::genesis.hash(), spend_txs, 0x03); + BOOST_REQUIRE(query.set(spend, context{ 0, 1, 0 }, false, false)); + + const auto link = query.to_header(spend.hash()); + BOOST_REQUIRE(!link.is_terminal()); + BOOST_REQUIRE(!query.set_silent(link, spend)); + BOOST_REQUIRE(!query.is_silent_indexed(link)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/settings.cpp b/test/settings.cpp index 1d35a7dde..246f37683 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -82,6 +82,10 @@ BOOST_AUTO_TEST_CASE(settings__construct__default__expected) BOOST_REQUIRE_EQUAL(configuration.filter_tx_buckets, 128u); BOOST_REQUIRE_EQUAL(configuration.filter_tx_size, 1u); BOOST_REQUIRE_EQUAL(configuration.filter_tx_rate, 50u); + BOOST_REQUIRE_EQUAL(configuration.silent_buckets, 128u); + BOOST_REQUIRE_EQUAL(configuration.silent_size, 1u); + BOOST_REQUIRE_EQUAL(configuration.silent_rate, 50u); + BOOST_REQUIRE_EQUAL(configuration.silent_start_height, 0u); } BOOST_AUTO_TEST_SUITE_END()