diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 397a29076..af8f95b57 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -112,6 +112,7 @@ include_bitcoin_database_impl_primitivesdir = \ include_bitcoin_database_impl_primitives_HEADERS = \ ${srcdir}/../../include/bitcoin/database/impl/primitives/arrayhead.ipp \ ${srcdir}/../../include/bitcoin/database/impl/primitives/arraymap.ipp \ + ${srcdir}/../../include/bitcoin/database/impl/primitives/arraymaps.ipp \ ${srcdir}/../../include/bitcoin/database/impl/primitives/body.ipp \ ${srcdir}/../../include/bitcoin/database/impl/primitives/hashhead.ipp \ ${srcdir}/../../include/bitcoin/database/impl/primitives/hashmap.ipp \ @@ -269,6 +270,7 @@ include_bitcoin_database_primitivesdir = \ include_bitcoin_database_primitives_HEADERS = \ ${srcdir}/../../include/bitcoin/database/primitives/arrayhead.hpp \ ${srcdir}/../../include/bitcoin/database/primitives/arraymap.hpp \ + ${srcdir}/../../include/bitcoin/database/primitives/arraymaps.hpp \ ${srcdir}/../../include/bitcoin/database/primitives/body.hpp \ ${srcdir}/../../include/bitcoin/database/primitives/column.hpp \ ${srcdir}/../../include/bitcoin/database/primitives/hashhead.hpp \ @@ -327,7 +329,6 @@ include_bitcoin_database_tables_indexes_HEADERS = \ ${srcdir}/../../include/bitcoin/database/tables/indexes/duplicate.hpp \ ${srcdir}/../../include/bitcoin/database/tables/indexes/height.hpp \ ${srcdir}/../../include/bitcoin/database/tables/indexes/scan.hpp \ - ${srcdir}/../../include/bitcoin/database/tables/indexes/scan_start.hpp \ ${srcdir}/../../include/bitcoin/database/tables/indexes/state.hpp \ ${srcdir}/../../include/bitcoin/database/tables/indexes/strong_tx.hpp \ ${srcdir}/../../include/bitcoin/database/tables/indexes/wtxid.hpp @@ -501,7 +502,6 @@ test_libbitcoin_database_test_SOURCES = \ ${srcdir}/../../test/tables/indexes/duplicate.cpp \ ${srcdir}/../../test/tables/indexes/height.cpp \ ${srcdir}/../../test/tables/indexes/scan.cpp \ - ${srcdir}/../../test/tables/indexes/scan_start.cpp \ ${srcdir}/../../test/tables/indexes/state.cpp \ ${srcdir}/../../test/tables/indexes/strong_tx.cpp \ ${srcdir}/../../test/tables/indexes/wtxid.cpp \ diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj index 6aaabee03..706bf717b 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj @@ -157,6 +157,7 @@ + @@ -194,7 +195,6 @@ - @@ -247,6 +247,7 @@ + diff --git a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters index 9b34964f5..0b618bd38 100644 --- a/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-database/libbitcoin-database.vcxproj.filters @@ -239,6 +239,9 @@ include\bitcoin\database\primitives + + include\bitcoin\database\primitives + include\bitcoin\database\primitives @@ -350,9 +353,6 @@ include\bitcoin\database\tables\indexes - - include\bitcoin\database\tables\indexes - include\bitcoin\database\tables\indexes @@ -510,6 +510,9 @@ include\bitcoin\database\impl\primitives + + include\bitcoin\database\impl\primitives + include\bitcoin\database\impl\primitives diff --git a/include/bitcoin/database.hpp b/include/bitcoin/database.hpp index c54bf5008..8a2a7ee8a 100644 --- a/include/bitcoin/database.hpp +++ b/include/bitcoin/database.hpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -84,7 +85,6 @@ #include #include #include -#include #include #include #include diff --git a/include/bitcoin/database/impl/primitives/arraymaps.ipp b/include/bitcoin/database/impl/primitives/arraymaps.ipp new file mode 100644 index 000000000..e30797c7c --- /dev/null +++ b/include/bitcoin/database/impl/primitives/arraymaps.ipp @@ -0,0 +1,261 @@ +/** + * 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_PRIMITIVES_ARRAYMAPS_IPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAPS_IPP + +#include +#include + +namespace libbitcoin { +namespace database { + +TEMPLATE +CLASS::arraymaps(storage& header, storage& body, + const Link& buckets) NOEXCEPT + : head_(header, buckets), + body_(body) +{ +} + +// not thread safe +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::create() NOEXCEPT +{ + Link count{}; + return head_.create() && + head_.get_body_count(count) && body_.truncate(count); +} + +TEMPLATE +bool CLASS::close() NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::backup(bool) NOEXCEPT +{ + return head_.set_body_count(body_.count()); +} + +TEMPLATE +bool CLASS::restore() NOEXCEPT +{ + Link count{}; + return head_.verify() && + head_.get_body_count(count) && body_.truncate(count); +} + +TEMPLATE +bool CLASS::verify() const NOEXCEPT +{ + Link count{}; + return head_.verify() && + head_.get_body_count(count) && count == body_.count(); +} + +// sizing +// ---------------------------------------------------------------------------- + +TEMPLATE +bool CLASS::enabled() const NOEXCEPT +{ + return head_.enabled(); +} + +TEMPLATE +size_t CLASS::buckets() const NOEXCEPT +{ + return head_.buckets(); +} + +TEMPLATE +size_t CLASS::head_size() const NOEXCEPT +{ + return head_.size(); +} + +TEMPLATE +size_t CLASS::body_size() const NOEXCEPT +{ + return body_.size(); +} + +TEMPLATE +Link CLASS::count() const NOEXCEPT +{ + return body_.count(); +} + +TEMPLATE +Link CLASS::allocate(const Link& count) NOEXCEPT +{ + return body_.allocate(count); +} + +TEMPLATE +bool CLASS::truncate(const Link& count) NOEXCEPT +{ + return body_.truncate(count); +} + +TEMPLATE +bool CLASS::drop() NOEXCEPT +{ + return head_.clear() && body_.truncate(0) && backup(); +} + +// Faults. +// ---------------------------------------------------------------------------- + +TEMPLATE +code CLASS::get_fault() const NOEXCEPT +{ + const auto ec = head_.get_fault(); + return ec ? ec : body_.get_fault(); +} + +TEMPLATE +size_t CLASS::get_space() const NOEXCEPT +{ + return system::ceilinged_add(head_.get_space(), body_.get_space()); +} + +TEMPLATE +code CLASS::reload() NOEXCEPT +{ + return body_.reload(); +} + +// query interface (head) +// ---------------------------------------------------------------------------- + +TEMPLATE +Link CLASS::at(size_t key) const NOEXCEPT +{ + return head_.at(key); +} + +TEMPLATE +bool CLASS::push(const Link& link, size_t key) NOEXCEPT +{ + // Avoid setting at/above terminal sentinel into a bucket position. + if (key >= Link::terminal) + return false; + + return head_.push(link, head_.index(key)); +} + +// query interface (columnar) +// ---------------------------------------------------------------------------- + +TEMPLATE +memory CLASS::guard() const NOEXCEPT +{ + return get_memory(); +} + +TEMPLATE +template +memory CLASS::get_memory() const NOEXCEPT +{ + return body_.template get(); +} + +// static +TEMPLATE +template +bool CLASS::get(const memory& ptr, const Link& link, Element& element) NOEXCEPT +{ + static_assert(Element::size == width); + if (!ptr || link.is_terminal()) + return false; + + using namespace system; + const auto start = body::template link_to_position(link); + if (is_limited(start)) + return false; + + const auto size = ptr.size(); + const auto position = possible_narrow_sign_cast(start); + if (position >= size) + return false; + + const auto offset = ptr.offset(start); + if (is_null(offset)) + return false; + + iostream stream{ offset, size - position }; + reader source{ stream }; + + BC_DEBUG_ONLY(source.set_limit(width * element.count());) + return element.from_data(source); +} + +// TODO: gets are not optimized for shared remap guard. +TEMPLATE +template +bool CLASS::get(const Link& link, Element& element) const NOEXCEPT +{ + return get(get_memory(), link, element); +} + +// TODO: puts are optimized for shared remap guard (required). +TEMPLATE +template +bool CLASS::put(const Link& link, const Element& element) NOEXCEPT +{ + const auto ptr = body_.template get_raw(link); + if (!put(ptr, element)) + return false; + + body_.complete(link, element.count()); + return true; +} + +TEMPLATE +void CLASS::complete(const Link& link, const Link& count) NOEXCEPT +{ + body_.complete(link, count); +} + +// protected (unguarded memory access) +TEMPLATE +template +bool CLASS::put(memory::iterator it, const Element& element) NOEXCEPT +{ + static_assert(Element::size == width); + if (is_null(it)) + return false; + + using namespace system; + const auto bytes = width * element.count(); + iostream stream{ it, possible_narrow_sign_cast(bytes) }; + flipper sink{ stream }; + + BC_DEBUG_ONLY(sink.set_limit(width * element.count());) + return element.to_data(sink); +} + +} // namespace database +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/database/impl/query/archive/chain_writer.ipp b/include/bitcoin/database/impl/query/archive/chain_writer.ipp index a8d1dcc8f..134c1e5e9 100644 --- a/include/bitcoin/database/impl/query/archive/chain_writer.ipp +++ b/include/bitcoin/database/impl/query/archive/chain_writer.ipp @@ -315,12 +315,17 @@ code CLASS::set_code(header_link& out_fk, const header& header, if (parent_fk.is_terminal() != (previous == system::null_hash)) return system::error::orphan_block; + // Bypass states, and completion states that are not required. using namespace system; - const auto checkpoint = ctx.height <= - store_.get_envelope().top_checkpoint.height(); - const auto bypass = bit_or( - milestone ? block_state::milestoned : block_state::no_state, - checkpoint ? block_state::checkpointed : block_state::no_state); + uint8_t flags{}; + if (milestone) + flags = bit_or(flags, block_state::milestoned); + if (ctx.height <= store_.get_envelope().top_checkpoint.height()) + flags = bit_or(flags, block_state::checkpointed); + if (!is_silent(ctx)) + flags = bit_or(flags, block_state::scanned); + if (!filter_enabled()) + flags = bit_or(flags, block_state::filtered); // ======================================================================== const auto scope = get_transactor(); @@ -340,8 +345,8 @@ code CLASS::set_code(header_link& out_fk, const header& header, return error::header_put; // Clean single allocation failure (e.g. disk full). - if (is_nonzero(bypass) && !store_.state.set(to_state(out_fk), - table::state::merge{ {}, bypass })) + if (is_nonzero(flags) && !store_.state.set(to_state(out_fk), + table::state::merge{ {}, flags })) return error::header_state_put; return error::success; diff --git a/include/bitcoin/database/impl/query/batch/silent.ipp b/include/bitcoin/database/impl/query/batch/silent.ipp index 96025bd92..14488e962 100644 --- a/include/bitcoin/database/impl/query/batch/silent.ipp +++ b/include/bitcoin/database/impl/query/batch/silent.ipp @@ -99,18 +99,82 @@ size_t CLASS::get_silent_frontier(size_t first) const NOEXCEPT TEMPLATE scan_link CLASS::get_scan_start(const header_link& link) const NOEXCEPT { - return store_.scan_start.at(to_scan_start(link)); + return store_.scan.at(to_scan_start(link)); } TEMPLATE bool CLASS::set_scan_start(const header_link& link, const scan_link& start) NOEXCEPT { - if (store_.is_pooling()) - return false; + // ======================================================================== + const auto scope = get_transactor(); + return store_.scan.push(start, to_scan_start(link)); + // ======================================================================== +} + +// A block's rows link above its coinbase, so the first row linked above the +// lowest coinbase at or above a height starts the rows of blocks at or above +// it. Rows of blocks archived after a mapped ancestor link above its coinbase +// and so follow its start, otherwise the seek is from the first row. Headers +// are written top down, so genesis is mapped only by a complete map. +TEMPLATE +bool CLASS::set_scan_starts(const stopper& cancel, const header_link& top, + size_t bound, bool remap) NOEXCEPT +{ + using namespace system; + auto link = top; + auto floor = tx_link::terminal; + auto lowest = tx_link::terminal; + std::vector> floors{}; + while (!link.is_terminal() && (remap || get_scan_start(link).is_terminal())) + { + const auto coinbase = to_coinbase(link); + if (coinbase.is_terminal()) + return false; + + lowest = std::min(lowest, coinbase.value); + floor = std::min(floor, coinbase.value); + floors.emplace_back(link, floor); + link = to_parent(link); + } + + size_t row{}; + if (!link.is_terminal() && lowest > to_coinbase(link).value) + row = get_scan_start(link).value; + + auto last = std::min(bound, size_t{ store_.scan.count().value }); + std::vector starts(floors.size()); + auto start = starts.rbegin(); + for (auto it = floors.crbegin(); it != floors.crend(); ++it, ++start) + { + if (cancel) + return false; + + seek_scan_start(row, last, it->second); + *start = possible_narrow_cast(row); + } + + for (size_t index{}; index < floors.size(); ++index) + if (cancel || !set_scan_start(floors.at(index).first, starts.at(index))) + return false; + + return true; +} +TEMPLATE +scan_link CLASS::get_scan_floor(size_t height) const NOEXCEPT +{ + auto index = std::min(height, get_top_confirmed()); + while (true) + { + const auto start = get_scan_start(to_confirmed(index)); + if (!start.is_terminal()) + return start; - const table::scan_start::record record{ {}, start.value }; - return store_.scan_start.set(to_scan_start(link), record); + if (is_zero(index)) + return scan_link{ 0 }; + + --index; + } } // setters @@ -123,48 +187,14 @@ bool CLASS::set_scan_start(const header_link& link, TEMPLATE bool CLASS::set_silent(const header_link& link, const block& block) NOEXCEPT { - const auto& txs = block.transactions_ptr(); - const auto count = txs->size(); - if (is_one(count)) - return true; - - const auto links = to_transactions(link); - if (links.size() != count) - return false; - - const auto first = links.front(); - for (auto index = one; index < count; ++index) - { - const auto& fk = links.at(index); - if (is_silent_selected(fk, first) && !set_silent(fk, *txs->at(index))) - return false; - } - - return true; + return set_silent_(link, *block.transactions_ptr()); } TEMPLATE bool CLASS::set_silent(const header_link& link, const block_view& block) NOEXCEPT { - const auto& txs = block.views(); - const auto count = txs.size(); - if (is_one(count)) - return true; - - const auto links = to_transactions(link); - if (links.size() != count) - return false; - - const auto first = links.front(); - for (auto index = one; index < count; ++index) - { - const auto& fk = links.at(index); - if (is_silent_selected(fk, first) && !set_silent(fk, txs.at(index))) - return false; - } - - return true; + return set_silent_(link, block.views()); } // Ineligible txs have no records. @@ -200,6 +230,60 @@ size_t CLASS::silent_records(bool bank) const NOEXCEPT return store_.silent_bank(bank).count(); } +TEMPLATE +size_t CLASS::presilent_records(bool bank) const NOEXCEPT +{ + return store_.presilent_bank(bank).count(); +} + +TEMPLATE +header_links CLASS::get_presilents(bool bank) const NOEXCEPT +{ + // Get all records, starting at the first position. + const auto all = presilent_records(bank); + const table::presilent::link first{ 0 }; + + header_links links(all); + table::presilent::get_refs out{ {}, links }; + if (store_.presilent_bank(bank).get(first, out)) + return links; + + // Empty return in case of fault is an efficiency loss, table is dropped. + return {}; +} + +TEMPLATE +bool CLASS::purge_presilents(bool bank) NOEXCEPT +{ + // ======================================================================== + const auto scope = get_transactor(); + return store_.presilent_bank(bank).truncate(0); + // ======================================================================== +} + +TEMPLATE +bool CLASS::set_presilent(const header_link& link, bool bank) NOEXCEPT +{ + // ======================================================================== + const auto scope = get_transactor(); + + // Clean single allocation failure (e.g. disk full). + return store_.presilent_bank(bank).put(table::presilent::record{ {}, link }); + // ======================================================================== +} + +TEMPLATE +bool CLASS::set_presilents(const header_links& links, bool bank) NOEXCEPT +{ + // ======================================================================== + const auto scope = get_transactor(); + + // Clean single allocation failure (e.g. disk full). + return store_.presilent_bank(bank).put( + table::presilent::put_refs{ {}, links }); + // ======================================================================== +} + TEMPLATE bool CLASS::purge_silents(bool bank) NOEXCEPT { @@ -290,7 +374,12 @@ code CLASS::compute_silents(const stopper& cancel, bool& device, auto& bank_prefix = store_.silent_prefix_bank(bank); const auto txs = possible_narrow_cast(bank_table.count().value); if (is_zero(txs)) - return error::success; + { + // ==================================================================== + const auto scope = get_transactor(); + return set_scanned_(bank) ? error::success : error::integrity; + // ==================================================================== + } using prefix_t = const scan::batch::prefix; using span_t = table::scan_prefix::put_span; @@ -404,13 +493,120 @@ code CLASS::compute_silents(const stopper& cancel, bool& device, } store_.scan.complete(fk, possible_narrow_cast(txs)); - return error::success; + return set_scanned_(bank) ? error::success : error::integrity; // ======================================================================== } // protected // ---------------------------------------------------------------------------- +// Ineligible txs have no records. The block's records and its scanned state +// are written in one transactor scope, and once both allocations succeed the +// writes cannot fail. +TEMPLATE +template +bool CLASS::set_silent_(const header_link& link, + const Transactions& txs) NOEXCEPT +{ + struct capture + { + tx_link link{}; + ec_compressed point{}; + std::vector prefixes{}; + }; + + const auto count = txs.size(); + const auto links = to_transactions(link); + if (links.size() != count) + return false; + + // Object txs are held by pointer, views by value. + const auto to_tx = [](const auto& tx) NOEXCEPT -> decltype(auto) + { + using type = std::decay_t; + if constexpr (is_same_type) + return (tx); + else + return (*tx); + }; + + using namespace system::wallet; + size_t rows{}; + std::vector captures{}; + const auto first = links.front(); + for (auto index = one; index < count; ++index) + { + capture item{ links.at(index) }; + const auto& tx = to_tx(txs.at(index)); + if (is_silent_selected(item.link, first) && + get_prefixes(item.prefixes, tx) && + silent_payment::summarize(item.point, tx)) + { + rows += item.prefixes.size(); + captures.push_back(std::move(item)); + } + } + + using namespace system; + using word_t = table::scan_correlate::tx::integer; + using point_t = table::scan_point::put_ref; + using prefix_t = table::scan_prefix::put_refs; + const table::state::merge scanned{ {}, block_state::scanned }; + + // ======================================================================== + const auto scope = get_transactor(); + + if (is_zero(rows)) + return store_.state.set(to_state(link), scanned); + + // Allocate the block's outputs, then its txs across all columns. Outputs + // are reached only through txs, so failure strands no unwritten tx. + const auto pk = store_.scan_prefix.allocate( + possible_narrow_cast(rows)); + if (pk.is_terminal()) + return false; + + const auto fk = store_.scan.allocate( + possible_narrow_cast(captures.size())); + if (fk.is_terminal()) + return false; + + // Guard against remap (required for nomaps::put(fk)). + const auto guard = store_.scan.guard(); + const auto words = pointer_cast(guard.data()); + + auto tx = fk; + auto out = pk; + for (const auto& item: captures) + { + const auto outputs = item.prefixes.size(); + if (!store_.scan_prefix.put(out, prefix_t{ {}, item.prefixes }) || + !store_.scan.point.put(tx, point_t{ {}, item.point }) || + !put_silent_(tx, item.link, out, outputs, words)) + return false; + + tx += one; + out += outputs; + } + + store_.scan.complete(fk, + possible_narrow_cast(captures.size())); + return store_.state.set(to_state(link), scanned); + // ======================================================================== +} + +// Caller holds the transactor. +TEMPLATE +bool CLASS::set_scanned_(bool bank) NOEXCEPT +{ + for (const auto& link: get_presilents(bank)) + if (!store_.state.set(to_state(link), + table::state::merge{ {}, block_state::scanned })) + return false; + + return true; +} + // Pooled txs linked below the coinbase were indexed (see set_silent). TEMPLATE template @@ -428,7 +624,7 @@ bool CLASS::set_silents_(size_t& rows, const header_link& link, rows = zero; const auto count = txs.size(); if (is_one(count)) - return true; + return set_block_scanned(link); const auto links = to_transactions(link); if (links.size() != count) @@ -461,7 +657,7 @@ bool CLASS::set_silents_(size_t& rows, const header_link& link, } if (is_zero(rows)) - return true; + return set_block_scanned(link); using correlate_t = table::silent_correlate::record; using sum_t = table::silent_sum::put_ref; @@ -474,13 +670,21 @@ bool CLASS::set_silents_(size_t& rows, const header_link& link, auto& bank_table = store_.silent_bank(bank); auto& bank_prefix = store_.silent_prefix_bank(bank); - // Allocate all of the block's txs across all columns, and their outputs. + // The link precedes the rows, so that rows are never banked without it. + if (!store_.presilent_bank(bank).put(table::presilent::record{ {}, link })) + return false; + + // Allocate the block's outputs, then its txs across all columns. Outputs + // are reached only through txs, so failure strands no unwritten tx. using namespace system; - const auto fk = bank_table.allocate( - possible_narrow_cast(captures.size())); const auto pk = bank_prefix.allocate( possible_narrow_cast(rows)); - if (fk.is_terminal() || pk.is_terminal()) + if (pk.is_terminal()) + return false; + + const auto fk = bank_table.allocate( + possible_narrow_cast(captures.size())); + if (fk.is_terminal()) return false; // Guard against remap (required for nomaps::put(fk)). @@ -591,6 +795,36 @@ bool CLASS::put_silent_(const scan_link& fk, const tx_link& link, return true; } +// Rows are read in chunks so the guard is not held across a long seek. +TEMPLATE +void CLASS::seek_scan_start(size_t& row, size_t& last, + tx_link::integer floor) const NOEXCEPT +{ + using namespace system; + using word_t = table::scan_correlate::tx::integer; + constexpr size_t chunk = 1 << 20; + + while (row < last) + { + const auto guard = store_.scan.correlate.get_memory(); + const auto words = pointer_cast(guard.data()); + const auto end = std::min(last, row + chunk); + for (; row < end; ++row) + { + std::atomic_ref word{ *std::next(words, row) }; + const auto link = native_from_little_end(word.load( + std::memory_order_acquire)); + if (is_zero(link)) + { + last = row; + return; + } + + if (link > floor) + return; + } + } +} } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp b/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp index 50b0f0479..e0e21e6d1 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_forks.ipp @@ -114,16 +114,12 @@ 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 silent_limit) const NOEXCEPT +header_states CLASS::get_validated_fork(size_t& fork_point) const NOEXCEPT { // Reservation may limit allocation to most common scenario. header_states out{}; out.reserve(one); - // Disable filter constraint if filtering is disabled. - const auto filter = filter_enabled(); - /////////////////////////////////////////////////////////////////////////// std::shared_lock interlock{ candidate_reorganization_mutex_ }; @@ -131,19 +127,15 @@ header_states CLASS::get_validated_fork(size_t& fork_point, auto height = add1(fork_point); auto link = to_candidate(height); - // Filter body always written before validated, but validated state is not - // written for bypassed blocks. So for bypassed blocks the existence of the - // filter must be verified. Silent payment records of blocks at or above - // silent_limit are not yet set. - while (height < silent_limit) + while (true) { const auto state = get_block_state(link); - const auto bypass = database::is_bypass(state); - if (bypass && (!database::is_associated(state) || - (filter && !is_filtered_body(link)))) + if (!database::is_associated(state) || + !database::is_scanned(state) || + !database::is_filtered(state)) break; - if (!bypass && !database::is_validated(state)) + if (!database::is_bypass(state) && !database::is_validated(state)) break; out.emplace_back(link, state); diff --git a/include/bitcoin/database/impl/query/consensus/consensus_states.ipp b/include/bitcoin/database/impl/query/consensus/consensus_states.ipp index 3a8dba3f2..95b050869 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_states.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_states.ipp @@ -88,6 +88,12 @@ bool CLASS::set_block_confirmable(const header_link& link) NOEXCEPT return set_block_state(link, block_state::confirmable); } +TEMPLATE +bool CLASS::set_block_scanned(const header_link& link) NOEXCEPT +{ + return set_block_state(link, block_state::scanned); +} + TEMPLATE bool CLASS::set_block_unconfirmable(const header_link& link) NOEXCEPT { diff --git a/include/bitcoin/database/impl/query/extent.ipp b/include/bitcoin/database/impl/query/extent.ipp index 480ac51b7..8178946dd 100644 --- a/include/bitcoin/database/impl/query/extent.ipp +++ b/include/bitcoin/database/impl/query/extent.ipp @@ -92,7 +92,6 @@ size_t CLASS::store_body_size() const NOEXCEPT + strong_tx_body_size() + scan_body_size() + scan_prefix_body_size() - + scan_start_body_size() + ecdsa0_body_size() + ecdsa1_body_size() + schnorr0_body_size() @@ -104,6 +103,8 @@ size_t CLASS::store_body_size() const NOEXCEPT + duplicate_body_size() + prevalid0_body_size() + prevalid1_body_size() + + presilent0_body_size() + + presilent1_body_size() + prevout_body_size() + state_body_size() + pool_body_size() @@ -134,7 +135,6 @@ size_t CLASS::store_head_size() const NOEXCEPT + strong_tx_head_size() + scan_head_size() + scan_prefix_head_size() - + scan_start_head_size() + ecdsa0_head_size() + ecdsa1_head_size() + schnorr0_head_size() @@ -146,6 +146,8 @@ size_t CLASS::store_head_size() const NOEXCEPT + duplicate_head_size() + prevalid0_head_size() + prevalid1_head_size() + + presilent0_head_size() + + presilent1_head_size() + prevout_head_size() + state_head_size() + pool_head_size() @@ -171,7 +173,6 @@ DEFINE_SIZES(confirmed) DEFINE_SIZES(strong_tx) DEFINE_SIZES(scan) DEFINE_SIZES(scan_prefix) -DEFINE_SIZES(scan_start) DEFINE_SIZES(ecdsa0) DEFINE_SIZES(ecdsa1) DEFINE_SIZES(schnorr0) @@ -183,6 +184,8 @@ DEFINE_SIZES(silent1_prefix) DEFINE_SIZES(duplicate) DEFINE_SIZES(prevalid0) DEFINE_SIZES(prevalid1) +DEFINE_SIZES(presilent0) +DEFINE_SIZES(presilent1) DEFINE_SIZES(prevout) DEFINE_SIZES(state) DEFINE_SIZES(pool) @@ -201,6 +204,7 @@ DEFINE_BUCKETS(txs) DEFINE_BUCKETS(tx) DEFINE_BUCKETS(strong_tx) +DEFINE_BUCKETS(scan) DEFINE_BUCKETS(duplicate) DEFINE_BUCKETS(prevout) DEFINE_BUCKETS(pool) @@ -221,7 +225,6 @@ DEFINE_RECORDS(confirmed) DEFINE_RECORDS(strong_tx) DEFINE_RECORDS(scan) DEFINE_RECORDS(scan_prefix) -DEFINE_RECORDS(scan_start) DEFINE_RECORDS(ecdsa0) DEFINE_RECORDS(ecdsa1) DEFINE_RECORDS(schnorr0) @@ -233,6 +236,8 @@ DEFINE_RECORDS(silent1_prefix) DEFINE_RECORDS(duplicate) DEFINE_RECORDS(prevalid0) DEFINE_RECORDS(prevalid1) +DEFINE_RECORDS(presilent0) +DEFINE_RECORDS(presilent1) DEFINE_RECORDS(state) DEFINE_RECORDS(pool) DEFINE_RECORDS(spends) @@ -323,12 +328,19 @@ size_t CLASS::silent_start_height() const NOEXCEPT } // A block is indexed from the start height once taproot is active. +TEMPLATE +bool CLASS::is_silent(const context& ctx) const NOEXCEPT +{ + return ctx.height >= silent_start_height() && + ctx.is_enabled(system::chain::flags::bip341_rule); +} + TEMPLATE bool CLASS::is_silent(const header_link& link, size_t height) const NOEXCEPT { context ctx{}; return height >= silent_start_height() && get_context(ctx, link) && - ctx.is_enabled(system::chain::flags::bip341_rule); + is_silent(ctx); } } // namespace database diff --git a/include/bitcoin/database/impl/query/filters.ipp b/include/bitcoin/database/impl/query/filters.ipp index e76e2e560..32d9bbec3 100644 --- a/include/bitcoin/database/impl/query/filters.ipp +++ b/include/bitcoin/database/impl/query/filters.ipp @@ -180,10 +180,18 @@ bool CLASS::set_filter_body(const header_link& link, const auto scope = get_transactor(); // Clean single allocation failure (e.g. disk full). - return store_.filter_tx.put(to_filter_tx(link), table::filter_tx::put_ref + if (!store_.filter_tx.put(to_filter_tx(link), table::filter_tx::put_ref { {}, filter + })) + return false; + + // Clean single allocation failure (e.g. disk full). + return store_.state.set(to_state(link), table::state::merge + { + {}, + block_state::filtered }); // ======================================================================== } diff --git a/include/bitcoin/database/impl/query/navigate/navigate_arraymap.ipp b/include/bitcoin/database/impl/query/navigate/navigate_arraymap.ipp index b01840b5f..dfe365dc9 100644 --- a/include/bitcoin/database/impl/query/navigate/navigate_arraymap.ipp +++ b/include/bitcoin/database/impl/query/navigate/navigate_arraymap.ipp @@ -34,16 +34,11 @@ constexpr size_t CLASS::to_state(const header_link& link) const NOEXCEPT return link.is_terminal() ? table::state::link::terminal : link.value; } -// Headers linked below the taproot activation header are not mapped. TEMPLATE -size_t CLASS::to_scan_start(const header_link& link) const NOEXCEPT +constexpr size_t CLASS::to_scan_start(const header_link& link) const NOEXCEPT { - const auto& taproot = store_.get_envelope().bip9_bit2_active_checkpoint; - const auto first = to_header(taproot.hash()); - if (link.is_terminal() || first.is_terminal() || link.value < first.value) - return table::scan_start::link::terminal; - - return link.value - first.value; + static_assert(header_link::terminal <= scan_link::terminal); + return link.is_terminal() ? scan_link::terminal : link.value; } TEMPLATE diff --git a/include/bitcoin/database/impl/query/query.ipp b/include/bitcoin/database/impl/query/query.ipp index 8a57e377a..b0f0e2bec 100644 --- a/include/bitcoin/database/impl/query/query.ipp +++ b/include/bitcoin/database/impl/query/query.ipp @@ -121,6 +121,18 @@ void CLASS::set_pooling() NOEXCEPT store_.set_pooling(); } +TEMPLATE +size_t CLASS::get_scan_frontier() const NOEXCEPT +{ + return store_.get_scan_frontier(); +} + +TEMPLATE +size_t CLASS::get_latch_tx() const NOEXCEPT +{ + return store_.get_latch_tx(); +} + TEMPLATE size_t CLASS::get_space() const NOEXCEPT { diff --git a/include/bitcoin/database/impl/store/store.ipp b/include/bitcoin/database/impl/store/store.ipp index abe47e48b..6cefbb466 100644 --- a/include/bitcoin/database/impl/store/store.ipp +++ b/include/bitcoin/database/impl/store/store.ipp @@ -75,11 +75,10 @@ CLASS::store(const settings& config) NOEXCEPT strong_tx_head_(head(config.path / schema::dir::heads, schema::indexes::strong_tx), head_settings(config.strong_tx), random), strong_tx_body_(body(config.path, schema::indexes::strong_tx), config.strong_tx, sequential, staged), - scan_head_(head(config.path / schema::dir::heads, schema::indexes::scan), head_settings(config.scan), sequential), + scan_head_(head(config.path / schema::dir::heads, schema::indexes::scan), head_settings(config.scan), random), scan_body_(body(config.path, schema::indexes::scan), config.scan, sequential, staged), scan_prefix_head_(head(config.path / schema::dir::heads, schema::indexes::scan_prefix), head_settings(config.scan), sequential), scan_prefix_body_(body(config.path, schema::indexes::scan_prefix), config.scan, sequential, staged), - scan_start_head_(head(config.path / schema::dir::heads, schema::indexes::scan_start), head_settings(config.scan_start), random), // Caches. // ------------------------------------------------------------------------ @@ -113,6 +112,10 @@ CLASS::store(const settings& config) NOEXCEPT prevalid0_body_(body(config.path, schema::caches::prevalid0), config.prevalid, sequential, staged), prevalid1_head_(head(config.path / schema::dir::heads, schema::caches::prevalid1), head_settings(config.prevalid), sequential), prevalid1_body_(body(config.path, schema::caches::prevalid1), config.prevalid, sequential, staged), + presilent0_head_(head(config.path / schema::dir::heads, schema::caches::presilent0), head_settings(config.presilent), sequential), + presilent0_body_(body(config.path, schema::caches::presilent0), config.presilent, sequential, staged), + presilent1_head_(head(config.path / schema::dir::heads, schema::caches::presilent1), head_settings(config.presilent), sequential), + presilent1_body_(body(config.path, schema::caches::presilent1), config.presilent, sequential, staged), prevout_head_(head(config.path / schema::dir::heads, schema::caches::prevout), head_settings(config.prevout), random), prevout_body_(body(config.path, schema::caches::prevout), config.prevout, sequential, staged), @@ -155,9 +158,8 @@ CLASS::store(const settings& config) NOEXCEPT candidate(candidate_head_), confirmed(confirmed_head_), strong_tx(strong_tx_head_, strong_tx_body_, config.strong_tx.buckets, config.strong_tx.expected), - scan(scan_head_, scan_body_), + scan(scan_head_, scan_body_, config.scan.buckets), scan_prefix(scan_prefix_head_, scan_prefix_body_), - scan_start(scan_start_head_), ecdsa0(ecdsa0_head_, ecdsa0_body_), ecdsa1(ecdsa1_head_, ecdsa1_body_), @@ -171,6 +173,8 @@ CLASS::store(const settings& config) NOEXCEPT duplicate(duplicate_head_, duplicate_body_, config.duplicate.buckets, config.duplicate.expected), prevalid0(prevalid0_head_, prevalid0_body_), prevalid1(prevalid1_head_, prevalid1_body_), + presilent0(presilent0_head_, presilent0_body_), + presilent1(presilent1_head_, presilent1_body_), prevout(prevout_head_, prevout_body_, config.prevout.buckets), state(state_head_), pool(pool_head_, pool_body_, config.pool.buckets, config.pool.expected), @@ -232,6 +236,18 @@ void CLASS::set_dirty() NOEXCEPT return dirty_.store(true, std::memory_order_relaxed); } +TEMPLATE +size_t CLASS::get_scan_frontier() const NOEXCEPT +{ + return scan_frontier_.load(std::memory_order_relaxed); +} + +TEMPLATE +size_t CLASS::get_latch_tx() const NOEXCEPT +{ + return latch_tx_.load(std::memory_order_relaxed); +} + TEMPLATE bool CLASS::is_pooling() const NOEXCEPT { @@ -244,8 +260,12 @@ void CLASS::set_pooling() NOEXCEPT if (is_pooling()) return; - pooling_.store(true, std::memory_order_relaxed); envelope_.pooling = true; + envelope_.scan_frontier = scan.count().value; + envelope_.latch_tx = tx.count().value; + scan_frontier_.store(envelope_.scan_frontier, std::memory_order_relaxed); + latch_tx_.store(envelope_.latch_tx, std::memory_order_relaxed); + pooling_.store(true, std::memory_order_relaxed); code ec{}; store_envelope(ec); @@ -288,6 +308,12 @@ table::prevalid& CLASS::prevalid_bank(bool bank) NOEXCEPT return bank ? prevalid1 : prevalid0; } +TEMPLATE +table::presilent& CLASS::presilent_bank(bool bank) NOEXCEPT +{ + return bank ? presilent1 : presilent0; +} + // Current is coalesced (confirmed at candidate top) with a recent top header. TEMPLATE bool CLASS::is_current() const NOEXCEPT @@ -325,7 +351,6 @@ void CLASS::set_current(bool current) NOEXCEPT strong_tx_head_.current(current); scan_head_.current(current); scan_prefix_head_.current(current); - scan_start_head_.current(current); ecdsa0_head_.current(current); ecdsa1_head_.current(current); schnorr0_head_.current(current); @@ -338,6 +363,8 @@ void CLASS::set_current(bool current) NOEXCEPT duplicate_head_.current(current); prevalid0_head_.current(current); prevalid1_head_.current(current); + presilent0_head_.current(current); + presilent1_head_.current(current); prevout_head_.current(current); state_head_.current(current); pool_head_.current(current); diff --git a/include/bitcoin/database/impl/store/store_backup.ipp b/include/bitcoin/database/impl/store/store_backup.ipp index c053b2936..25fb98cab 100644 --- a/include/bitcoin/database/impl/store/store_backup.ipp +++ b/include/bitcoin/database/impl/store/store_backup.ipp @@ -55,7 +55,6 @@ code CLASS::backup(const event_handler& handler, bool prune) NOEXCEPT backup(ec, strong_tx, table_t::strong_tx_table); backup(ec, scan, table_t::scan_table); backup(ec, scan_prefix, table_t::scan_prefix_table); - backup(ec, scan_start, table_t::scan_start_table); backup(ec, ecdsa0, table_t::ecdsa0_table); backup(ec, ecdsa1, table_t::ecdsa1_table); @@ -69,6 +68,8 @@ code CLASS::backup(const event_handler& handler, bool prune) NOEXCEPT backup(ec, duplicate, table_t::duplicate_table); backup(ec, prevalid0, table_t::prevalid0_table, prune); backup(ec, prevalid1, table_t::prevalid1_table, prune); + backup(ec, presilent0, table_t::presilent0_table, prune); + backup(ec, presilent1, table_t::presilent1_table, prune); backup(ec, prevout, table_t::prevout_table, prune); backup(ec, state, table_t::state_table); backup(ec, pool, table_t::pool_table, prune); diff --git a/include/bitcoin/database/impl/store/store_close.ipp b/include/bitcoin/database/impl/store/store_close.ipp index 6318d0c35..930ee459f 100644 --- a/include/bitcoin/database/impl/store/store_close.ipp +++ b/include/bitcoin/database/impl/store/store_close.ipp @@ -59,7 +59,6 @@ code CLASS::close(const event_handler& handler) NOEXCEPT close(ec, strong_tx, table_t::strong_tx_table); close(ec, scan, table_t::scan_table); close(ec, scan_prefix, table_t::scan_prefix_table); - close(ec, scan_start, table_t::scan_start_table); close(ec, ecdsa0, table_t::ecdsa0_table); close(ec, ecdsa1, table_t::ecdsa1_table); @@ -73,6 +72,8 @@ code CLASS::close(const event_handler& handler) NOEXCEPT close(ec, duplicate, table_t::duplicate_table); close(ec, prevalid0, table_t::prevalid0_table); close(ec, prevalid1, table_t::prevalid1_table); + close(ec, presilent0, table_t::presilent0_table); + close(ec, presilent1, table_t::presilent1_table); close(ec, prevout, table_t::prevout_table); close(ec, state, table_t::state_table); close(ec, pool, table_t::pool_table); diff --git a/include/bitcoin/database/impl/store/store_create.ipp b/include/bitcoin/database/impl/store/store_create.ipp index 2458520b2..55c5d93c6 100644 --- a/include/bitcoin/database/impl/store/store_create.ipp +++ b/include/bitcoin/database/impl/store/store_create.ipp @@ -102,7 +102,6 @@ code CLASS::create_load(const event_handler& handler) NOEXCEPT create(ec, scan_body_, table_t::scan_body); create(ec, scan_prefix_head_, table_t::scan_prefix_head); create(ec, scan_prefix_body_, table_t::scan_prefix_body); - create(ec, scan_start_head_, table_t::scan_start_head); create(ec, ecdsa0_head_, table_t::ecdsa0_head); create(ec, ecdsa0_body_, table_t::ecdsa0_body); @@ -127,6 +126,10 @@ code CLASS::create_load(const event_handler& handler) NOEXCEPT create(ec, prevalid0_body_, table_t::prevalid0_body); create(ec, prevalid1_head_, table_t::prevalid1_head); create(ec, prevalid1_body_, table_t::prevalid1_body); + create(ec, presilent0_head_, table_t::presilent0_head); + create(ec, presilent0_body_, table_t::presilent0_body); + create(ec, presilent1_head_, table_t::presilent1_head); + create(ec, presilent1_body_, table_t::presilent1_body); create(ec, prevout_head_, table_t::prevout_head); create(ec, prevout_body_, table_t::prevout_body); create(ec, state_head_, table_t::state_head); @@ -169,7 +172,6 @@ code CLASS::create_load(const event_handler& handler) NOEXCEPT populate(ec, strong_tx, table_t::strong_tx_table); populate(ec, scan, table_t::scan_table); populate(ec, scan_prefix, table_t::scan_prefix_table); - populate(ec, scan_start, table_t::scan_start_table); populate(ec, ecdsa0, table_t::ecdsa0_table); populate(ec, ecdsa1, table_t::ecdsa1_table); @@ -183,6 +185,8 @@ code CLASS::create_load(const event_handler& handler) NOEXCEPT populate(ec, duplicate, table_t::duplicate_table); populate(ec, prevalid0, table_t::prevalid0_table); populate(ec, prevalid1, table_t::prevalid1_table); + populate(ec, presilent0, table_t::presilent0_table); + populate(ec, presilent1, table_t::presilent1_table); populate(ec, prevout, table_t::prevout_table); populate(ec, state, table_t::state_table); populate(ec, pool, table_t::pool_table); diff --git a/include/bitcoin/database/impl/store/store_dump.ipp b/include/bitcoin/database/impl/store/store_dump.ipp index 137fabffc..914fddbc3 100644 --- a/include/bitcoin/database/impl/store/store_dump.ipp +++ b/include/bitcoin/database/impl/store/store_dump.ipp @@ -55,7 +55,6 @@ code CLASS::dump(const path& folder, dump(ec, strong_tx_head_, schema::indexes::strong_tx, table_t::strong_tx_head); dump(ec, scan_head_, schema::indexes::scan, table_t::scan_head); dump(ec, scan_prefix_head_, schema::indexes::scan_prefix, table_t::scan_prefix_head); - dump(ec, scan_start_head_, schema::indexes::scan_start, table_t::scan_start_head); dump(ec, ecdsa0_head_, schema::caches::ecdsa0, table_t::ecdsa0_head); dump(ec, ecdsa1_head_, schema::caches::ecdsa1, table_t::ecdsa1_head); @@ -69,6 +68,8 @@ code CLASS::dump(const path& folder, dump(ec, duplicate_head_, schema::indexes::duplicate, table_t::duplicate_head); dump(ec, prevalid0_head_, schema::caches::prevalid0, table_t::prevalid0_head); dump(ec, prevalid1_head_, schema::caches::prevalid1, table_t::prevalid1_head); + dump(ec, presilent0_head_, schema::caches::presilent0, table_t::presilent0_head); + dump(ec, presilent1_head_, schema::caches::presilent1, table_t::presilent1_head); dump(ec, prevout_head_, schema::caches::prevout, table_t::prevout_head); dump(ec, state_head_, schema::indexes::state, table_t::state_head); dump(ec, pool_head_, schema::caches::pool, table_t::pool_head); diff --git a/include/bitcoin/database/impl/store/store_open.ipp b/include/bitcoin/database/impl/store/store_open.ipp index 2d3072987..17fb844fd 100644 --- a/include/bitcoin/database/impl/store/store_open.ipp +++ b/include/bitcoin/database/impl/store/store_open.ipp @@ -77,7 +77,6 @@ code CLASS::open(const event_handler& handler) NOEXCEPT verify(ec, strong_tx, table_t::strong_tx_table); verify(ec, scan, table_t::scan_table); verify(ec, scan_prefix, table_t::scan_prefix_table); - verify(ec, scan_start, table_t::scan_start_table); verify(ec, ecdsa0, table_t::ecdsa0_table); verify(ec, ecdsa1, table_t::ecdsa1_table); @@ -91,6 +90,8 @@ code CLASS::open(const event_handler& handler) NOEXCEPT verify(ec, duplicate, table_t::duplicate_table); verify(ec, prevalid0, table_t::prevalid0_table); verify(ec, prevalid1, table_t::prevalid1_table); + verify(ec, presilent0, table_t::presilent0_table); + verify(ec, presilent1, table_t::presilent1_table); verify(ec, prevout, table_t::prevout_table); verify(ec, state, table_t::state_table); verify(ec, pool, table_t::pool_table); @@ -199,6 +200,8 @@ code CLASS::load_envelope() NOEXCEPT } // The stored latch governs, it is one-way and survives the session. + scan_frontier_.store(envelope_.scan_frontier, std::memory_order_relaxed); + latch_tx_.store(envelope_.latch_tx, std::memory_order_relaxed); if (envelope_.pooling) pooling_.store(true, std::memory_order_relaxed); diff --git a/include/bitcoin/database/impl/store/store_open_load.ipp b/include/bitcoin/database/impl/store/store_open_load.ipp index 715f030cf..7c1f9c0e8 100644 --- a/include/bitcoin/database/impl/store/store_open_load.ipp +++ b/include/bitcoin/database/impl/store/store_open_load.ipp @@ -86,7 +86,6 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT open(ec, scan_body_, table_t::scan_body); open(ec, scan_prefix_head_, table_t::scan_prefix_head); open(ec, scan_prefix_body_, table_t::scan_prefix_body); - open(ec, scan_start_head_, table_t::scan_start_head); open(ec, ecdsa0_head_, table_t::ecdsa0_head); open(ec, ecdsa0_body_, table_t::ecdsa0_body); @@ -110,6 +109,10 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT open(ec, prevalid0_body_, table_t::prevalid0_body); open(ec, prevalid1_head_, table_t::prevalid1_head); open(ec, prevalid1_body_, table_t::prevalid1_body); + open(ec, presilent0_head_, table_t::presilent0_head); + open(ec, presilent0_body_, table_t::presilent0_body); + open(ec, presilent1_head_, table_t::presilent1_head); + open(ec, presilent1_body_, table_t::presilent1_body); open(ec, prevout_head_, table_t::prevout_head); open(ec, prevout_body_, table_t::prevout_body); open(ec, state_head_, table_t::state_head); @@ -148,7 +151,6 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT load(ec, scan_body_, table_t::scan_body); load(ec, scan_prefix_head_, table_t::scan_prefix_head); load(ec, scan_prefix_body_, table_t::scan_prefix_body); - load(ec, scan_start_head_, table_t::scan_start_head); load(ec, ecdsa0_head_, table_t::ecdsa0_head); load(ec, ecdsa0_body_, table_t::ecdsa0_body); @@ -172,6 +174,10 @@ code CLASS::open_load(const event_handler& handler) NOEXCEPT load(ec, prevalid0_body_, table_t::prevalid0_body); load(ec, prevalid1_head_, table_t::prevalid1_head); load(ec, prevalid1_body_, table_t::prevalid1_body); + load(ec, presilent0_head_, table_t::presilent0_head); + load(ec, presilent0_body_, table_t::presilent0_body); + load(ec, presilent1_head_, table_t::presilent1_head); + load(ec, presilent1_body_, table_t::presilent1_body); load(ec, prevout_head_, table_t::prevout_head); load(ec, prevout_body_, table_t::prevout_body); load(ec, state_head_, table_t::state_head); diff --git a/include/bitcoin/database/impl/store/store_reload.ipp b/include/bitcoin/database/impl/store/store_reload.ipp index a64d2824b..831b6371b 100644 --- a/include/bitcoin/database/impl/store/store_reload.ipp +++ b/include/bitcoin/database/impl/store/store_reload.ipp @@ -76,7 +76,6 @@ code CLASS::reload(const event_handler& handler) NOEXCEPT reload(ec, scan_body_, table_t::scan_body); reload(ec, scan_prefix_head_, table_t::scan_prefix_head); reload(ec, scan_prefix_body_, table_t::scan_prefix_body); - reload(ec, scan_start_head_, table_t::scan_start_head); reload(ec, ecdsa0_head_, table_t::ecdsa0_head); reload(ec, ecdsa0_body_, table_t::ecdsa0_body); @@ -101,6 +100,10 @@ code CLASS::reload(const event_handler& handler) NOEXCEPT reload(ec, prevalid0_body_, table_t::prevalid0_body); reload(ec, prevalid1_head_, table_t::prevalid1_head); reload(ec, prevalid1_body_, table_t::prevalid1_body); + reload(ec, presilent0_head_, table_t::presilent0_head); + reload(ec, presilent0_body_, table_t::presilent0_body); + reload(ec, presilent1_head_, table_t::presilent1_head); + reload(ec, presilent1_body_, table_t::presilent1_body); reload(ec, prevout_head_, table_t::prevout_head); reload(ec, prevout_body_, table_t::prevout_body); reload(ec, state_head_, table_t::state_head); diff --git a/include/bitcoin/database/impl/store/store_report.ipp b/include/bitcoin/database/impl/store/store_report.ipp index d6a8be2bb..87a8cb247 100644 --- a/include/bitcoin/database/impl/store/store_report.ipp +++ b/include/bitcoin/database/impl/store/store_report.ipp @@ -62,6 +62,8 @@ void CLASS::report(const error_handler& handler) const NOEXCEPT report(duplicate_body_, table_t::duplicate_body); report(prevalid0_body_, table_t::prevalid0_body); report(prevalid1_body_, table_t::prevalid1_body); + report(presilent0_body_, table_t::presilent0_body); + report(presilent1_body_, table_t::presilent1_body); report(prevout_body_, table_t::prevout_body); report(pool_body_, table_t::pool_body); report(spends_body_, table_t::spends_body); @@ -97,7 +99,6 @@ code CLASS::get_fault() const NOEXCEPT if ((ec = scan_body_.get_fault())) return ec; if ((ec = scan_prefix_head_.get_fault())) return ec; if ((ec = scan_prefix_body_.get_fault())) return ec; - if ((ec = scan_start_head_.get_fault())) return ec; if ((ec = ecdsa0_head_.get_fault())) return ec; if ((ec = ecdsa0_body_.get_fault())) return ec; if ((ec = ecdsa1_head_.get_fault())) return ec; @@ -121,6 +122,10 @@ code CLASS::get_fault() const NOEXCEPT if ((ec = prevalid0_body_.get_fault())) return ec; if ((ec = prevalid1_head_.get_fault())) return ec; if ((ec = prevalid1_body_.get_fault())) return ec; + if ((ec = presilent0_head_.get_fault())) return ec; + if ((ec = presilent0_body_.get_fault())) return ec; + if ((ec = presilent1_head_.get_fault())) return ec; + if ((ec = presilent1_body_.get_fault())) return ec; if ((ec = prevout_head_.get_fault())) return ec; if ((ec = prevout_body_.get_fault())) return ec; if ((ec = state_head_.get_fault())) return ec; @@ -169,7 +174,6 @@ size_t CLASS::get_space() const NOEXCEPT space(scan_body_); space(scan_prefix_head_); space(scan_prefix_body_); - space(scan_start_head_); space(ecdsa0_head_); space(ecdsa0_body_); space(ecdsa1_head_); @@ -193,6 +197,10 @@ size_t CLASS::get_space() const NOEXCEPT space(prevalid0_body_); space(prevalid1_head_); space(prevalid1_body_); + space(presilent0_head_); + space(presilent0_body_); + space(presilent1_head_); + space(presilent1_body_); space(prevout_head_); space(prevout_body_); space(state_head_); diff --git a/include/bitcoin/database/impl/store/store_restore.ipp b/include/bitcoin/database/impl/store/store_restore.ipp index 6b54f7174..7e05bf84a 100644 --- a/include/bitcoin/database/impl/store/store_restore.ipp +++ b/include/bitcoin/database/impl/store/store_restore.ipp @@ -140,9 +140,8 @@ code CLASS::restore(const event_handler& handler) NOEXCEPT restore(ec, strong_tx, table_t::strong_tx_table); restore(ec, scan, table_t::scan_table); restore(ec, scan_prefix, table_t::scan_prefix_table); - restore(ec, scan_start, table_t::scan_start_table); - // ecdsa, schnorr, and prevalid are dropped. + // ecdsa, schnorr, silent, prevalid, and presilent are dropped. //--------------------------------------------------------------------- // Signatures could be retained and verified, but this just becomes // redundant work unless prevalid is retained. That would require flush @@ -155,14 +154,16 @@ code CLASS::restore(const event_handler& handler) NOEXCEPT dropped(ec, ecdsa1, table_t::ecdsa1_table); dropped(ec, schnorr0, table_t::schnorr0_table); dropped(ec, schnorr1, table_t::schnorr1_table); - restore(ec, silent0, table_t::silent0_table); - restore(ec, silent1, table_t::silent1_table); - restore(ec, silent0_prefix, table_t::silent0_prefix_table); - restore(ec, silent1_prefix, table_t::silent1_prefix_table); + dropped(ec, silent0, table_t::silent0_table); + dropped(ec, silent1, table_t::silent1_table); + dropped(ec, silent0_prefix, table_t::silent0_prefix_table); + dropped(ec, silent1_prefix, table_t::silent1_prefix_table); restore(ec, envelope, table_t::envelope_table); restore(ec, duplicate, table_t::duplicate_table); dropped(ec, prevalid0, table_t::prevalid0_table); dropped(ec, prevalid1, table_t::prevalid1_table); + dropped(ec, presilent0, table_t::presilent0_table); + dropped(ec, presilent1, table_t::presilent1_table); restore(ec, prevout, table_t::prevout_table); restore(ec, state, table_t::state_table); restore(ec, pool, table_t::pool_table); diff --git a/include/bitcoin/database/impl/store/store_snapshot.ipp b/include/bitcoin/database/impl/store/store_snapshot.ipp index 9e4c799e1..c945c573b 100644 --- a/include/bitcoin/database/impl/store/store_snapshot.ipp +++ b/include/bitcoin/database/impl/store/store_snapshot.ipp @@ -68,6 +68,8 @@ code CLASS::snapshot(const event_handler& handler, bool prune) NOEXCEPT flush(ec, duplicate_body_, table_t::duplicate_body); flush(ec, prevalid0_body_, table_t::prevalid0_body); flush(ec, prevalid1_body_, table_t::prevalid1_body); + flush(ec, presilent0_body_, table_t::presilent0_body); + flush(ec, presilent1_body_, table_t::presilent1_body); if (!prune) flush(ec, prevout_body_, table_t::prevout_body); if (!prune) flush(ec, pool_body_, table_t::pool_body); if (!prune) flush(ec, spends_body_, table_t::spends_body); diff --git a/include/bitcoin/database/impl/store/store_tables.ipp b/include/bitcoin/database/impl/store/store_tables.ipp index 254bdc9c6..4fdadf3e4 100644 --- a/include/bitcoin/database/impl/store/store_tables.ipp +++ b/include/bitcoin/database/impl/store/store_tables.ipp @@ -66,8 +66,6 @@ const CLASS::table_map CLASS::tables { table_t::scan_prefix_table, "scan_prefix_table" }, { table_t::scan_prefix_head, "scan_prefix_head" }, { table_t::scan_prefix_body, "scan_prefix_body" }, - { table_t::scan_start_table, "scan_start_table" }, - { table_t::scan_start_head, "scan_start_head" }, // Caches. { table_t::ecdsa0_table, "ecdsa0_table" }, @@ -105,6 +103,12 @@ const CLASS::table_map CLASS::tables { table_t::prevalid1_table, "prevalid1_table" }, { table_t::prevalid1_head, "prevalid1_head" }, { table_t::prevalid1_body, "prevalid1_body" }, + { table_t::presilent0_table, "presilent0_table" }, + { table_t::presilent0_head, "presilent0_head" }, + { table_t::presilent0_body, "presilent0_body" }, + { table_t::presilent1_table, "presilent1_table" }, + { table_t::presilent1_head, "presilent1_head" }, + { table_t::presilent1_body, "presilent1_body" }, { table_t::prevout_table, "prevout_table" }, { table_t::prevout_head, "prevout_head" }, { table_t::prevout_body, "prevout_body" }, diff --git a/include/bitcoin/database/impl/store/store_unload_close.ipp b/include/bitcoin/database/impl/store/store_unload_close.ipp index 2975ee589..b534b58f8 100644 --- a/include/bitcoin/database/impl/store/store_unload_close.ipp +++ b/include/bitcoin/database/impl/store/store_unload_close.ipp @@ -63,7 +63,6 @@ code CLASS::unload_close(const event_handler& handler) NOEXCEPT unload(ec, scan_body_, table_t::scan_body); unload(ec, scan_prefix_head_, table_t::scan_prefix_head); unload(ec, scan_prefix_body_, table_t::scan_prefix_body); - unload(ec, scan_start_head_, table_t::scan_start_head); unload(ec, ecdsa0_head_, table_t::ecdsa0_head); unload(ec, ecdsa0_body_, table_t::ecdsa0_body); @@ -88,6 +87,10 @@ code CLASS::unload_close(const event_handler& handler) NOEXCEPT unload(ec, prevalid0_body_, table_t::prevalid0_body); unload(ec, prevalid1_head_, table_t::prevalid1_head); unload(ec, prevalid1_body_, table_t::prevalid1_body); + unload(ec, presilent0_head_, table_t::presilent0_head); + unload(ec, presilent0_body_, table_t::presilent0_body); + unload(ec, presilent1_head_, table_t::presilent1_head); + unload(ec, presilent1_body_, table_t::presilent1_body); unload(ec, prevout_head_, table_t::prevout_head); unload(ec, prevout_body_, table_t::prevout_body); unload(ec, state_head_, table_t::state_head); @@ -135,7 +138,6 @@ code CLASS::unload_close(const event_handler& handler) NOEXCEPT close(ec, scan_body_, table_t::scan_body); close(ec, scan_prefix_head_, table_t::scan_prefix_head); close(ec, scan_prefix_body_, table_t::scan_prefix_body); - close(ec, scan_start_head_, table_t::scan_start_head); close(ec, ecdsa0_head_, table_t::ecdsa0_head); close(ec, ecdsa0_body_, table_t::ecdsa0_body); @@ -160,6 +162,10 @@ code CLASS::unload_close(const event_handler& handler) NOEXCEPT close(ec, prevalid0_body_, table_t::prevalid0_body); close(ec, prevalid1_head_, table_t::prevalid1_head); close(ec, prevalid1_body_, table_t::prevalid1_body); + close(ec, presilent0_head_, table_t::presilent0_head); + close(ec, presilent0_body_, table_t::presilent0_body); + close(ec, presilent1_head_, table_t::presilent1_head); + close(ec, presilent1_body_, table_t::presilent1_body); close(ec, prevout_head_, table_t::prevout_head); close(ec, prevout_body_, table_t::prevout_body); close(ec, state_head_, table_t::state_head); diff --git a/include/bitcoin/database/primitives/arraymaps.hpp b/include/bitcoin/database/primitives/arraymaps.hpp new file mode 100644 index 000000000..84b685980 --- /dev/null +++ b/include/bitcoin/database/primitives/arraymaps.hpp @@ -0,0 +1,142 @@ +/** + * 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_PRIMITIVES_ARRAYMAPS_HPP +#define LIBBITCOIN_DATABASE_PRIMITIVES_ARRAYMAPS_HPP + +#include +#include + +namespace libbitcoin { +namespace database { + +/// SoA aggregate array map: an array head of body links and one unified +/// bodys body. Head buckets are indexed by key. +template +class arraymaps +{ +public: + DELETE_COPY_MOVE_DESTRUCT(arraymaps); + + using link = Link; + static_assert((is_same_type && ...), + "all columns must share the row link type"); + + template + static constexpr size_t width = std::get + ( + std::array{ Columns::width... } + ); + + arraymaps(storage& header, storage& body, const Link& buckets) NOEXCEPT; + + /// Setup. + /// ----------------------------------------------------------------------- + + bool create() NOEXCEPT; + bool close() NOEXCEPT; + bool backup(bool=false) NOEXCEPT; + bool restore() NOEXCEPT; + bool verify() const NOEXCEPT; + + /// Sizing. + /// ----------------------------------------------------------------------- + + /// The instance is enabled (has buckets). + bool enabled() const NOEXCEPT; + + /// Head bucket count. + size_t buckets() const NOEXCEPT; + + size_t head_size() const NOEXCEPT; + size_t body_size() const NOEXCEPT; + Link count() const NOEXCEPT; + Link allocate(const Link& count) NOEXCEPT; + bool truncate(const Link& count) NOEXCEPT; + bool drop() NOEXCEPT; + + /// Faults. + /// ----------------------------------------------------------------------- + + code get_fault() const NOEXCEPT; + size_t get_space() const NOEXCEPT; + code reload() NOEXCEPT; + + /// Query interface (head). + /// ----------------------------------------------------------------------- + + /// Return link at key, terminal if not set or beyond the head. + Link at(size_t key) const NOEXCEPT; + + /// Set link at key, expanding the head as necessary. + bool push(const Link& link, size_t key) NOEXCEPT; + + /// Query interface (columnar). + /// ----------------------------------------------------------------------- + + /// Must hold as remap guard across puts(). + memory guard() const NOEXCEPT; + + /// Column base ptr for batch processing (holds shared lock on body remap). + template + memory get_memory() const NOEXCEPT; + + /// Get element from column at link using column base ptr (deserialize). + template + static bool get(const memory& ptr, const Link& link, + Element& element) NOEXCEPT; + + /// Get element from column at link. + template + bool get(const Link& link, Element& element) const NOEXCEPT; + + /// Put previously allocated element to column at link (guard required). + template + bool put(const Link& link, const Element& element) NOEXCEPT; + + /// Complete count rows at link written directly to a column. + void complete(const Link& link, const Link& count) NOEXCEPT; + +protected: + using head = database::arrayhead; + using body = database::bodys, + Columns::width...>; + + template + bool put(memory::iterator it, const Element& element) NOEXCEPT; + + // Thread safe (index/top/push). + // Not thread safe (create/open/close/backup/restore). + head head_; + + // This is thread safe. + body body_; +}; + +} // namespace database +} // namespace libbitcoin + +#define TEMPLATE template +#define CLASS arraymaps + +#include + +#undef CLASS +#undef TEMPLATE + +#endif diff --git a/include/bitcoin/database/primitives/primitives.hpp b/include/bitcoin/database/primitives/primitives.hpp index 794cde0c8..186e7dfb9 100644 --- a/include/bitcoin/database/primitives/primitives.hpp +++ b/include/bitcoin/database/primitives/primitives.hpp @@ -35,6 +35,7 @@ // tables #include +#include #include #include #include diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 1288200c3..2d31f12af 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -95,6 +95,10 @@ class query bool is_pooling() const NOEXCEPT; void set_pooling() NOEXCEPT; + /// Scan table row count and tx table count when pooling was latched. + size_t get_scan_frontier() const NOEXCEPT; + size_t get_latch_tx() const NOEXCEPT; + /// Get the space required to clear the disk full condition. size_t get_space() const NOEXCEPT; @@ -143,7 +147,6 @@ class query size_t strong_tx_head_size() const NOEXCEPT; size_t scan_head_size() const NOEXCEPT; size_t scan_prefix_head_size() const NOEXCEPT; - size_t scan_start_head_size() const NOEXCEPT; size_t ecdsa0_head_size() const NOEXCEPT; size_t ecdsa1_head_size() const NOEXCEPT; size_t schnorr0_head_size() const NOEXCEPT; @@ -155,6 +158,8 @@ class query size_t duplicate_head_size() const NOEXCEPT; size_t prevalid0_head_size() const NOEXCEPT; size_t prevalid1_head_size() const NOEXCEPT; + size_t presilent0_head_size() const NOEXCEPT; + size_t presilent1_head_size() const NOEXCEPT; size_t prevout_head_size() const NOEXCEPT; size_t state_head_size() const NOEXCEPT; size_t pool_head_size() const NOEXCEPT; @@ -177,7 +182,6 @@ class query size_t strong_tx_body_size() const NOEXCEPT; size_t scan_body_size() const NOEXCEPT; size_t scan_prefix_body_size() const NOEXCEPT; - size_t scan_start_body_size() const NOEXCEPT; size_t ecdsa0_body_size() const NOEXCEPT; size_t ecdsa1_body_size() const NOEXCEPT; size_t schnorr0_body_size() const NOEXCEPT; @@ -189,6 +193,8 @@ class query size_t duplicate_body_size() const NOEXCEPT; size_t prevalid0_body_size() const NOEXCEPT; size_t prevalid1_body_size() const NOEXCEPT; + size_t presilent0_body_size() const NOEXCEPT; + size_t presilent1_body_size() const NOEXCEPT; size_t prevout_body_size() const NOEXCEPT; size_t state_body_size() const NOEXCEPT; size_t pool_body_size() const NOEXCEPT; @@ -211,7 +217,6 @@ class query size_t strong_tx_size() const NOEXCEPT; size_t scan_size() const NOEXCEPT; size_t scan_prefix_size() const NOEXCEPT; - size_t scan_start_size() const NOEXCEPT; size_t ecdsa0_size() const NOEXCEPT; size_t ecdsa1_size() const NOEXCEPT; size_t schnorr0_size() const NOEXCEPT; @@ -223,6 +228,8 @@ class query size_t duplicate_size() const NOEXCEPT; size_t prevalid0_size() const NOEXCEPT; size_t prevalid1_size() const NOEXCEPT; + size_t presilent0_size() const NOEXCEPT; + size_t presilent1_size() const NOEXCEPT; size_t prevout_size() const NOEXCEPT; size_t state_size() const NOEXCEPT; size_t pool_size() const NOEXCEPT; @@ -239,6 +246,7 @@ class query size_t tx_buckets() const NOEXCEPT; size_t strong_tx_buckets() const NOEXCEPT; + size_t scan_buckets() const NOEXCEPT; size_t duplicate_buckets() const NOEXCEPT; size_t prevout_buckets() const NOEXCEPT; size_t pool_buckets() const NOEXCEPT; @@ -257,7 +265,6 @@ class query size_t strong_tx_records() const NOEXCEPT; size_t scan_records() const NOEXCEPT; size_t scan_prefix_records() const NOEXCEPT; - size_t scan_start_records() const NOEXCEPT; size_t ecdsa0_records() const NOEXCEPT; size_t ecdsa1_records() const NOEXCEPT; size_t schnorr0_records() const NOEXCEPT; @@ -269,6 +276,8 @@ class query size_t duplicate_records() const NOEXCEPT; size_t prevalid0_records() const NOEXCEPT; size_t prevalid1_records() const NOEXCEPT; + size_t presilent0_records() const NOEXCEPT; + size_t presilent1_records() const NOEXCEPT; size_t state_records() const NOEXCEPT; size_t pool_records() const NOEXCEPT; size_t spends_records() const NOEXCEPT; @@ -288,6 +297,7 @@ class query bool filter_enabled() const NOEXCEPT; bool silent_enabled() const NOEXCEPT; size_t silent_start_height() const NOEXCEPT; + bool is_silent(const context& ctx) const NOEXCEPT; bool is_silent(const header_link& link, size_t height) const NOEXCEPT; size_t interval_span() const NOEXCEPT; @@ -388,7 +398,7 @@ class query /// header to arraymap tables (guard domain transitions) constexpr size_t to_state(const header_link& link) const NOEXCEPT; - size_t to_scan_start(const header_link& link) const NOEXCEPT; + constexpr size_t to_scan_start(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_prevout(const header_link& link) const NOEXCEPT; @@ -680,6 +690,7 @@ class query /// Setters. bool set_block_valid(const header_link& link) NOEXCEPT; + bool set_block_scanned(const header_link& link) NOEXCEPT; bool set_block_unconfirmable(const header_link& link) NOEXCEPT; bool set_block_confirmable(const header_link& link) NOEXCEPT; bool set_block_unknown(const header_link& link) NOEXCEPT; @@ -734,8 +745,8 @@ class query bool set_silent(const header_link& link, const block& block) NOEXCEPT; bool set_silent(const header_link& link, const block_view& block) NOEXCEPT; - /// Commit a block's silent payment batch rows, with rows the number - /// committed. + /// Commit a block's silent payment batch rows and presilent link, with + /// rows the number committed (a block with no rows is set scanned). bool set_silents(size_t& rows, const header_link& link, const block& block, bool bank) NOEXCEPT; bool set_silents(size_t& rows, const header_link& link, @@ -772,11 +783,19 @@ class query /// The scan fk that starts the header's scan range, terminal if unmapped. scan_link get_scan_start(const header_link& link) const NOEXCEPT; - /// Map the header to the scan fk that starts its scan range, false if - /// unmapped or pooling (the map is closed at the pooling latch). + /// Map the header to the scan fk that starts its scan range. bool set_scan_start(const header_link& link, const scan_link& start) NOEXCEPT; + /// Map headers from top down to the first mapped ancestor, or to genesis + /// if remap, over rows below bound, false if canceled or unarchived. + bool set_scan_starts(const stopper& cancel, const header_link& top, + size_t bound, bool remap) NOEXCEPT; + + /// The scan fk that starts the confirmed header at or below height (zero + /// if none mapped), a scan from it covers all blocks at or above height. + scan_link get_scan_floor(size_t height) const NOEXCEPT; + /// Verify all signatures in table, false implies cancel, device set if /// verified on the device. bool verify_ecdsa_signatures(const stopper& cancel, header_links&, @@ -789,17 +808,24 @@ class query bool purge_ecdsa_signatures(bool bank) NOEXCEPT; bool purge_schnorr_signatures(bool bank) NOEXCEPT; bool purge_silents(bool bank) NOEXCEPT; + bool purge_presilents(bool bank) NOEXCEPT; /// Cache all prevalids. header_links get_prevalids(bool bank) const NOEXCEPT; bool set_prevalid(const header_link& link, bool bank) NOEXCEPT; bool set_prevalids(const header_links& links, bool bank) NOEXCEPT; + /// Cache all presilents. + header_links get_presilents(bool bank) const NOEXCEPT; + bool set_presilent(const header_link& link, bool bank) NOEXCEPT; + bool set_presilents(const header_links& links, bool bank) NOEXCEPT; + /// Records of a batch table bank. size_t ecdsa_records(bool bank) const NOEXCEPT; size_t schnorr_records(bool bank) const NOEXCEPT; size_t silent_records(bool bank) const NOEXCEPT; size_t prevalid_records(bool bank) const NOEXCEPT; + size_t presilent_records(bool bank) const NOEXCEPT; /// Confirmation. /// ----------------------------------------------------------------------- @@ -864,8 +890,7 @@ class query height_link get_confirmed_height(const header_link& link) const NOEXCEPT; header_links get_confirmed_fork(const header_link& fork) const NOEXCEPT; header_links get_candidate_fork(size_t& fork_point) const NOEXCEPT; - header_states get_validated_fork(size_t& fork_point, - size_t silent_limit=max_size_t) const NOEXCEPT; + header_states get_validated_fork(size_t& fork_point) const NOEXCEPT; bool initialize(const block& genesis) NOEXCEPT; bool push_candidate(const header_link& link) NOEXCEPT; @@ -1174,6 +1199,16 @@ class query bool set_silent_(const tx_link& link, const ec_compressed& point, const std::vector& prefixes) NOEXCEPT; + /// Advance row to the first row linked above floor, an unpublished row + /// lowering last to itself (the row at or past which rows are unknown). + void seek_scan_start(size_t& row, size_t& last, + tx_link::integer floor) const NOEXCEPT; + + /// Set silent payment records of a block's txs and its scanned state. + template + bool set_silent_(const header_link& link, + const Transactions& txs) NOEXCEPT; + /// Write a scan tx's outputs and publish it, caller must hold transactor. bool put_silent_(const scan_link& fk, const tx_link& link, const prefix_link& first, size_t outputs, @@ -1183,6 +1218,9 @@ class query bool is_silent_selected(const tx_link& link, const tx_link& coinbase) const NOEXCEPT; + /// Mark the blocks of a bank's presilent table scanned. + bool set_scanned_(bool bank) NOEXCEPT; + /// Commit the silent payment batch rows of a block's txs. template bool set_silents_(size_t& rows, const header_link& link, diff --git a/include/bitcoin/database/settings.hpp b/include/bitcoin/database/settings.hpp index 120434ce6..18765d21b 100644 --- a/include/bitcoin/database/settings.hpp +++ b/include/bitcoin/database/settings.hpp @@ -102,8 +102,7 @@ struct BCD_API settings bucket_table candidate{}; bucket_table confirmed{}; hash_table strong_tx{}; - simple_table scan{}; - bucket_table scan_start{}; + bucket_table scan{}; /// Caches. /// ----------------------------------------------------------------------- @@ -113,6 +112,7 @@ struct BCD_API settings simple_table silent{}; hash_table duplicate{}; simple_table prevalid{}; + simple_table presilent{}; bucket_table prevout{}; bucket_table state{}; hash_table pool{}; diff --git a/include/bitcoin/database/store.hpp b/include/bitcoin/database/store.hpp index 6b49f9b1b..9205b87f5 100644 --- a/include/bitcoin/database/store.hpp +++ b/include/bitcoin/database/store.hpp @@ -108,6 +108,10 @@ class store bool is_pooling() const NOEXCEPT; void set_pooling() NOEXCEPT; + /// Scan table row count and tx table count when pooling was latched. + size_t get_scan_frontier() const NOEXCEPT; + size_t get_latch_tx() const NOEXCEPT; + /// Get first fault code or error::success. code get_fault() const NOEXCEPT; @@ -123,6 +127,7 @@ class store table::silent& silent_bank(bool bank) NOEXCEPT; table::silent_prefix& silent_prefix_bank(bool bank) NOEXCEPT; table::prevalid& prevalid_bank(bool bank) NOEXCEPT; + table::presilent& presilent_bank(bool bank) NOEXCEPT; /// Reevaluate currency (called by chain writes). void evaluate_currency() NOEXCEPT; @@ -218,7 +223,6 @@ class store table::scan_storage scan_body_; Storage scan_prefix_head_; Storage scan_prefix_body_; - Storage scan_start_head_; /// Caches. /// ----------------------------------------------------------------------- @@ -257,6 +261,10 @@ class store Storage prevalid0_body_; Storage prevalid1_head_; Storage prevalid1_body_; + Storage presilent0_head_; + Storage presilent0_body_; + Storage presilent1_head_; + Storage presilent1_body_; // blob arraymap Storage prevout_head_; @@ -295,6 +303,8 @@ class store // This is thread safe. stopper dirty_{ true }; stopper pooling_{ false }; + std::atomic scan_frontier_{}; + std::atomic latch_tx_{}; private: static constexpr bool random = true; @@ -360,7 +370,6 @@ class store table::strong_tx strong_tx; table::scan scan; table::scan_prefix scan_prefix; - table::scan_start scan_start; /// Caches. table::ecdsa ecdsa0; @@ -375,6 +384,8 @@ class store table::duplicate duplicate; table::prevalid prevalid0; table::prevalid prevalid1; + table::presilent presilent0; + table::presilent presilent1; table::prevout prevout; table::state state; table::pool pool; diff --git a/include/bitcoin/database/tables/caches/prevalid.hpp b/include/bitcoin/database/tables/caches/prevalid.hpp index 25b5cf42e..5c073bcf7 100644 --- a/include/bitcoin/database/tables/caches/prevalid.hpp +++ b/include/bitcoin/database/tables/caches/prevalid.hpp @@ -112,6 +112,9 @@ struct prevalid }; }; +/// presilent is an array of header fk records (same as prevalid). +using presilent = prevalid; + } // namespace table } // namespace database } // namespace libbitcoin diff --git a/include/bitcoin/database/tables/envelope.hpp b/include/bitcoin/database/tables/envelope.hpp index c1f2ba86e..6260cecdf 100644 --- a/include/bitcoin/database/tables/envelope.hpp +++ b/include/bitcoin/database/tables/envelope.hpp @@ -106,6 +106,8 @@ struct BCD_API envelope /// Node state. bool pooling{}; + uint32_t scan_frontier{}; + uint32_t latch_tx{}; }; } // namespace database diff --git a/include/bitcoin/database/tables/indexes/scan.hpp b/include/bitcoin/database/tables/indexes/scan.hpp index e499115b1..382331b6e 100644 --- a/include/bitcoin/database/tables/indexes/scan.hpp +++ b/include/bitcoin/database/tables/indexes/scan.hpp @@ -204,9 +204,10 @@ class scan_storage /// Aggregate (table) /// --------------------------------------------------------------------------- -using scan_table = nomaps +using scan_table = arraymaps < scan_correlate::link, + schema::scan_correlate::align, scan_correlate, scan_point, scan_outputs @@ -217,8 +218,9 @@ class scan : public scan_table { public: - scan(database::storage& head, scan_storage& body) NOEXCEPT - : scan_table(head, body), + scan(database::storage& head, scan_storage& body, + const scan_correlate::link& buckets) NOEXCEPT + : scan_table(head, body, buckets), correlate(*this), point(*this), outputs(*this) diff --git a/include/bitcoin/database/tables/indexes/scan_start.hpp b/include/bitcoin/database/tables/indexes/scan_start.hpp deleted file mode 100644 index 42ea4e5b1..000000000 --- a/include/bitcoin/database/tables/indexes/scan_start.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * 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_TABLES_INDEXES_SCAN_START_HPP -#define LIBBITCOIN_DATABASE_TABLES_INDEXES_SCAN_START_HPP - -#include -#include -#include - -namespace libbitcoin { -namespace database { -namespace table { - -/// scan_start is a head-only array of scan fks, each starting the scan range -/// of a header, indexed by header.fk less the first taproot header.fk. -struct scan_start - : public head_map -{ - using head_map::headmap; - - /// Replace the scan fk. - struct record - : public schema::scan_start - { - inline bool from_data(reader& source) NOEXCEPT - { - source.skip_bytes(link::size); - return source; - } - - inline bool to_data(flipper& sink) const NOEXCEPT - { - sink.write_little_endian(start); - return sink; - } - - link::integer start{}; - }; -}; - -} // namespace table -} // namespace database -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/database/tables/names.hpp b/include/bitcoin/database/tables/names.hpp index 60897046f..c958fd0b1 100644 --- a/include/bitcoin/database/tables/names.hpp +++ b/include/bitcoin/database/tables/names.hpp @@ -62,7 +62,6 @@ namespace indexes constexpr auto confirmed = "index_confirmed"; constexpr auto duplicate = "index_duplicate"; constexpr auto state = "index_state"; - constexpr auto scan_start = "index_scan_start"; constexpr auto strong_tx = "index_strong"; constexpr auto wtxid = "index_wtxid"; } @@ -100,6 +99,8 @@ namespace caches constexpr auto envelope = "envelope"; constexpr auto prevalid0 = "batch_prevalid0"; constexpr auto prevalid1 = "batch_prevalid1"; + constexpr auto presilent0 = "batch_presilent0"; + constexpr auto presilent1 = "batch_presilent1"; constexpr auto prevout = "cache_prevout"; constexpr auto pool = "cache_pool"; constexpr auto pool_id0 = "id0"_t; diff --git a/include/bitcoin/database/tables/schema.hpp b/include/bitcoin/database/tables/schema.hpp index 9f013860c..838755422 100644 --- a/include/bitcoin/database/tables/schema.hpp +++ b/include/bitcoin/database/tables/schema.hpp @@ -26,6 +26,7 @@ #define TABLE_COLUMN(group, table, bytes) \ struct table \ { \ + static constexpr size_t align = false; \ static constexpr size_t pk = schema::outs::pk; \ using link = schema::outs::link; \ static constexpr size_t minsize = bytes; \ @@ -55,7 +56,9 @@ namespace schema { /// 4.0.10.0 Pool arrival time. /// 4.0.11.0 Scan start index by taproot header. /// 4.0.12.0 Association, milestone and checkpoint as block state flags. -constexpr std::array version{ 4, 0, 12, 0 }; +/// 4.0.13.0 Scanned and filtered block state flags, presilent batch tables. +/// 4.0.14.0 Scan table head of scan starts, scan and tx counts at the latch. +constexpr std::array version{ 4, 0, 14, 0 }; /// Values. /// ----------------------------------------------------------------------- @@ -358,22 +361,6 @@ TABLE_COLUMN(indexes, scan_correlate, schema::transaction::pk); TABLE_COLUMN(indexes, scan_point, system::ec_compressed_size); TABLE_COLUMN(indexes, scan_outputs, scan_prefix::pk + schema::index); -// headmap (scan fks by taproot header) -struct scan_start -{ - static constexpr size_t pk = schema::outs_; - using link = linkage; - static constexpr size_t minsize = pk; - static constexpr size_t minrow = minsize; - static constexpr size_t size = minsize; - static constexpr size_t cell = minsize; - static_assert(minsize == 4u); - static_assert(minrow == 4u); - static_assert(link::size == 4u); - static_assert(cell == 4u); - bool operator==(const scan_start&) const NOEXCEPT = default; -}; - // array (same as scan_prefix) using silent_prefix = scan_prefix; diff --git a/include/bitcoin/database/tables/table.hpp b/include/bitcoin/database/tables/table.hpp index d1253161a..c4ea687e3 100644 --- a/include/bitcoin/database/tables/table.hpp +++ b/include/bitcoin/database/tables/table.hpp @@ -65,8 +65,6 @@ enum class table_t scan_prefix_table, scan_prefix_head, scan_prefix_body, - scan_start_table, - scan_start_head, /// Caches. ecdsa0_table, @@ -104,6 +102,12 @@ enum class table_t prevalid1_table, prevalid1_head, prevalid1_body, + presilent0_table, + presilent0_head, + presilent0_body, + presilent1_table, + presilent1_head, + presilent1_body, prevout_table, prevout_head, prevout_body, diff --git a/include/bitcoin/database/tables/tables.hpp b/include/bitcoin/database/tables/tables.hpp index cf723fff4..15326612b 100644 --- a/include/bitcoin/database/tables/tables.hpp +++ b/include/bitcoin/database/tables/tables.hpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include diff --git a/include/bitcoin/database/types/block_state.hpp b/include/bitcoin/database/types/block_state.hpp index 730103e9d..33be69da4 100644 --- a/include/bitcoin/database/types/block_state.hpp +++ b/include/bitcoin/database/types/block_state.hpp @@ -46,7 +46,13 @@ enum block_state : uint8_t milestoned = system::bit_right(4), /// At or below the top checkpoint (bypassed). - checkpointed = system::bit_right(5) + checkpointed = system::bit_right(5), + + /// Silent payment records are set or not required. + scanned = system::bit_right(6), + + /// Client filter is set or not required. + filtered = system::bit_right(7) }; /// Validation flags, cleared by reset. @@ -97,6 +103,18 @@ constexpr bool is_bypass(block_state state) NOEXCEPT return is_any(state, bypass_states); } +/// Silent payment records are set or not required. +constexpr bool is_scanned(block_state state) NOEXCEPT +{ + return is_any(state, scanned); +} + +/// Client filter is set or not required. +constexpr bool is_filtered(block_state state) NOEXCEPT +{ + return is_any(state, filtered); +} + } // namespace database } // namespace libbitcoin diff --git a/src/tables/envelope.cpp b/src/tables/envelope.cpp index e3e671db1..e1bfb597e 100644 --- a/src/tables/envelope.cpp +++ b/src/tables/envelope.cpp @@ -193,6 +193,8 @@ bool envelope::from_data(reader& source) NOEXCEPT silent_start_height = source.read_little_endian(); pooling = to_bool(source.read_byte()); + scan_frontier = source.read_little_endian(); + latch_tx = source.read_little_endian(); return source; } @@ -285,6 +287,8 @@ bool envelope::to_data(flipper& sink) const NOEXCEPT sink.write_little_endian(silent_start_height); sink.write_byte(to_int(pooling)); + sink.write_little_endian(scan_frontier); + sink.write_little_endian(latch_tx); return sink; } @@ -293,7 +297,7 @@ size_t envelope::serialized_size() const NOEXCEPT constexpr auto forks_size = 24_size; constexpr auto fixed = (4 * sizeof(uint32_t)) + sizeof(uint16_t) + (7 * sizeof(uint32_t)) + (10 * sizeof(uint8_t)) + forks_size + - sizeof(uint64_t) + (16 * sizeof(uint32_t)); + sizeof(uint64_t) + (18 * sizeof(uint32_t)); const auto checkpoint_size = [](const chain::checkpoint& in) NOEXCEPT { diff --git a/test/mocks/chunk_store.hpp b/test/mocks/chunk_store.hpp index 42e070309..dca7b09d9 100644 --- a/test/mocks/chunk_store.hpp +++ b/test/mocks/chunk_store.hpp @@ -245,11 +245,6 @@ class chunk_store return state_head_.buffer(); } - system::data_chunk& scan_start_head() NOEXCEPT - { - return scan_start_head_.buffer(); - } - system::data_chunk& pool_head() NOEXCEPT { return pool_head_.buffer(); diff --git a/test/mocks/map_store.hpp b/test/mocks/map_store.hpp index ff0fa090a..632df614e 100644 --- a/test/mocks/map_store.hpp +++ b/test/mocks/map_store.hpp @@ -184,11 +184,6 @@ class map_store return state_head_.file(); } - inline const path& scan_start_head_file() const NOEXCEPT - { - return scan_start_head_.file(); - } - inline const path& wtxid_head_file() const NOEXCEPT { return wtxid_head_.file(); diff --git a/test/query/batch/silent.cpp b/test/query/batch/silent.cpp index be2020bdd..12fc24aa3 100644 --- a/test/query/batch/silent.cpp +++ b/test/query/batch/silent.cpp @@ -83,9 +83,77 @@ static receiver get_keys() NOEXCEPT return silent_payment{ scan_secret, spend, {} }.keys(); } +// presilent +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(query_batch_silent__get_presilents__empty__empty) +{ + const database::settings configuration{}; + test::chunk_store store{ configuration }; + test::query_accessor query{ store }; + + BOOST_REQUIRE(query.get_presilents(false).empty()); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__set_presilents__two__round_trips) +{ + const database::settings configuration{}; + test::chunk_store store{ configuration }; + test::query_accessor query{ store }; + + const header_links links{ 0x00345678, 0x00cdef12 }; + BOOST_REQUIRE(query.set_presilents(links, false)); + BOOST_REQUIRE(query.get_presilents(false) == links); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__purge_presilents__after_set__empty) +{ + const database::settings configuration{}; + test::chunk_store store{ configuration }; + test::query_accessor query{ store }; + + const header_links links{ 0x00345678, 0x00cdef12 }; + BOOST_REQUIRE(query.set_presilents(links, false)); + BOOST_REQUIRE(query.purge_presilents(false)); + BOOST_REQUIRE(query.get_presilents(false).empty()); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__set_presilent__banks__independent) +{ + const database::settings configuration{}; + test::chunk_store store{ configuration }; + test::query_accessor query{ store }; + + BOOST_REQUIRE(query.set_presilent(0x00000001, false)); + BOOST_REQUIRE(query.set_presilent(0x00000002, true)); + BOOST_REQUIRE(query.set_presilent(0x00000003, true)); + BOOST_REQUIRE_EQUAL(query.presilent_records(false), 1u); + BOOST_REQUIRE_EQUAL(query.presilent_records(true), 2u); + BOOST_REQUIRE(query.purge_presilents(true)); + BOOST_REQUIRE_EQUAL(query.presilent_records(false), 1u); + BOOST_REQUIRE_EQUAL(query.presilent_records(true), 0u); +} + // set_silent // ---------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE(query_batch_silent__set_silent__eligible_header__scanned) +{ + database::settings settings{}; + settings.path = TEST_DIRECTORY; + settings.initialize(system::settings{ system::chain::selection::regtest }, false, 1); + 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, database::context{ chain::flags::bip341_rule, 1, 0 }, {}, false, false)); + const auto link = query.to_header(test::block1.hash()); + BOOST_REQUIRE(!is_scanned(query.get_block_state(link))); + BOOST_REQUIRE(query.set_silent(link, test::block1)); + BOOST_REQUIRE(is_scanned(query.get_block_state(link))); +} + BOOST_AUTO_TEST_CASE(query_batch_silent__set_silent__coinbase_only__true) { database::settings settings{}; @@ -184,6 +252,8 @@ BOOST_AUTO_TEST_CASE(query_batch_silent__set_silents__coinbase_only__no_rows) BOOST_REQUIRE(query.set_silents(rows, 0, test::genesis, false)); BOOST_REQUIRE_EQUAL(rows, 0u); BOOST_REQUIRE_EQUAL(query.silent_records(false), 0u); + BOOST_REQUIRE_EQUAL(query.presilent_records(false), 0u); + BOOST_REQUIRE(is_scanned(query.get_block_state(0))); } // Archive the block's spend before its coinbase, as a pooled tx is archived. @@ -453,6 +523,51 @@ BOOST_AUTO_TEST_CASE(query_batch_silent__purge_silents__banked_row__empty) BOOST_REQUIRE_EQUAL(query.silent_records(false), 0u); } +BOOST_AUTO_TEST_CASE(query_batch_silent__compute_silents__presilent_banked_row__scanned) +{ + database::settings settings{}; + settings.path = TEST_DIRECTORY; + settings.initialize(system::settings{ system::chain::selection::regtest }, false, 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 block = silent_block(); + BOOST_REQUIRE(query.set(block, database::context{ chain::flags::bip341_rule, 1, 0 }, {}, false, false)); + const auto link = query.to_header(block.hash()); + BOOST_REQUIRE(!is_scanned(query.get_block_state(link))); + + size_t rows{}; + const stopper cancel{}; + bool device{}; + BOOST_REQUIRE(query.set_silents(rows, link, block, false)); + BOOST_REQUIRE_EQUAL(query.presilent_records(false), 1u); + BOOST_REQUIRE_EQUAL(query.compute_silents(cancel, device, false), database::error::success); + BOOST_REQUIRE(is_scanned(query.get_block_state(link))); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__set_silents__no_rows__scanned) +{ + database::settings settings{}; + settings.path = TEST_DIRECTORY; + settings.initialize(system::settings{ system::chain::selection::regtest }, false, 1); + 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, database::context{ chain::flags::bip341_rule, 1, 0 }, {}, false, false)); + const auto link = query.to_header(test::block1.hash()); + BOOST_REQUIRE(!is_scanned(query.get_block_state(link))); + + size_t rows{}; + BOOST_REQUIRE(query.set_silents(rows, link, test::block1, false)); + BOOST_REQUIRE_EQUAL(rows, 0u); + BOOST_REQUIRE_EQUAL(query.presilent_records(false), 0u); + BOOST_REQUIRE(is_scanned(query.get_block_state(link))); +} + // scan_silent // ---------------------------------------------------------------------------- @@ -566,7 +681,7 @@ BOOST_AUTO_TEST_CASE(query_batch_silent__get_silent_frontier__written__count) // get_scan_start/set_scan_start // ---------------------------------------------------------------------------- -BOOST_AUTO_TEST_CASE(query_batch_silent__get_scan_start__no_taproot__terminal) +BOOST_AUTO_TEST_CASE(query_batch_silent__get_scan_start__unset__terminal) { database::settings settings{}; settings.path = TEST_DIRECTORY; @@ -574,41 +689,219 @@ BOOST_AUTO_TEST_CASE(query_batch_silent__get_scan_start__no_taproot__terminal) test::query_accessor query{ store }; BOOST_REQUIRE(!store.create(test::events_handler)); BOOST_REQUIRE(query.initialize(test::genesis)); - BOOST_REQUIRE(!query.set_scan_start(0, 42)); BOOST_REQUIRE(query.get_scan_start(0).is_terminal()); + BOOST_REQUIRE(query.get_scan_start(header_link::terminal).is_terminal()); + BOOST_REQUIRE(!query.set_scan_start(header_link::terminal, 42)); } -BOOST_AUTO_TEST_CASE(query_batch_silent__set_scan_start__taproot__mapped) +BOOST_AUTO_TEST_CASE(query_batch_silent__set_scan_start__gapped__mapped) { database::settings settings{}; settings.path = TEST_DIRECTORY; - settings.envelope.bip9_bit2_active_checkpoint = { test::block1.hash(), 1 }; 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, database::context{ 0, 1, 0 }, {}, false, false)); - BOOST_REQUIRE(query.set(test::block2, database::context{ 0, 2, 0 }, {}, false, false)); - BOOST_REQUIRE(!query.set_scan_start(0, 42)); BOOST_REQUIRE(query.set_scan_start(2, 42)); BOOST_REQUIRE(query.get_scan_start(0).is_terminal()); BOOST_REQUIRE(query.get_scan_start(1).is_terminal()); BOOST_REQUIRE_EQUAL(query.get_scan_start(2), 42u); + BOOST_REQUIRE(query.get_scan_start(3).is_terminal()); } -BOOST_AUTO_TEST_CASE(query_batch_silent__set_scan_start__pooling__false) +// set_scan_starts/get_scan_floor +// ---------------------------------------------------------------------------- + +// Blocks 1-3 archived in order (coinbase links 1-3), rows linked 1, 3, 2, 4. +static bool setup_scan_starts(test::query_accessor& query) NOEXCEPT +{ + return query.initialize(test::genesis) + && query.set(test::block1, database::context{ 0, 1, 0 }, {}, false, false) + && query.set(test::block2, database::context{ 0, 2, 0 }, {}, false, false) + && query.set(test::block3, database::context{ 0, 3, 0 }, {}, false, false) + && query.push_confirmed(1, false) + && query.push_confirmed(2, false) + && query.push_confirmed(3, false) + && query.set_silent(1, simple_send()) + && query.set_silent(3, simple_send()) + && query.set_silent(2, simple_send()) + && query.set_silent(4, simple_send()); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__set_scan_starts__unarchived__false) { database::settings settings{}; settings.path = TEST_DIRECTORY; - settings.envelope.bip9_bit2_active_checkpoint = { test::genesis.hash(), 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.set_scan_start(0, 42)); - store.set_pooling(); - BOOST_REQUIRE(!query.set_scan_start(0, 7)); - BOOST_REQUIRE_EQUAL(query.get_scan_start(0), 42u); + BOOST_REQUIRE(query.set(test::block1.header(), database::context{ 0, 1, 0 }, {}, false)); + + const stopper cancel{ false }; + BOOST_REQUIRE(!query.set_scan_starts(cancel, 1, max_size_t, true)); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__set_scan_starts__canceled__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(setup_scan_starts(query)); + + const stopper cancel{ true }; + BOOST_REQUIRE(!query.set_scan_starts(cancel, 3, max_size_t, true)); + BOOST_REQUIRE(query.get_scan_start(3).is_terminal()); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__set_scan_starts__unbounded__first_rows_above_floors) +{ + 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(setup_scan_starts(query)); + + const stopper cancel{ false }; + BOOST_REQUIRE(query.set_scan_starts(cancel, 3, max_size_t, true)); + BOOST_REQUIRE_EQUAL(query.get_scan_start(0), 0u); + BOOST_REQUIRE_EQUAL(query.get_scan_start(1), 1u); + BOOST_REQUIRE_EQUAL(query.get_scan_start(2), 1u); + BOOST_REQUIRE_EQUAL(query.get_scan_start(3), 3u); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__set_scan_starts__bounded__clamped) +{ + 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(setup_scan_starts(query)); + + const stopper cancel{ false }; + BOOST_REQUIRE(query.set_scan_starts(cancel, 3, 2, true)); + BOOST_REQUIRE_EQUAL(query.get_scan_start(0), 0u); + BOOST_REQUIRE_EQUAL(query.get_scan_start(1), 1u); + BOOST_REQUIRE_EQUAL(query.get_scan_start(2), 1u); + BOOST_REQUIRE_EQUAL(query.get_scan_start(3), 2u); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__set_scan_starts__extended__after_ancestor_start) +{ + 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(setup_scan_starts(query)); + + const stopper cancel{ false }; + BOOST_REQUIRE(query.set_scan_starts(cancel, 3, max_size_t, true)); + BOOST_REQUIRE(query.set(test::block4, database::context{ 0, 4, 0 }, {}, false, false)); + BOOST_REQUIRE(query.set_silent(5, simple_send())); + BOOST_REQUIRE(query.set_scan_starts(cancel, 4, max_size_t, false)); + BOOST_REQUIRE_EQUAL(query.get_scan_start(3), 3u); + BOOST_REQUIRE_EQUAL(query.get_scan_start(4), 4u); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__set_scan_starts__mapped_top__unchanged) +{ + 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(setup_scan_starts(query)); + BOOST_REQUIRE(query.set_scan_start(3, 42)); + + const stopper cancel{ false }; + BOOST_REQUIRE(query.set_scan_starts(cancel, 3, max_size_t, false)); + BOOST_REQUIRE_EQUAL(query.get_scan_start(3), 42u); + BOOST_REQUIRE(query.get_scan_start(2).is_terminal()); +} + +// Blocks archived 3, 1, 2 (coinbase links 1, 2, 3), rows linked 1, 3, 2, 4. +BOOST_AUTO_TEST_CASE(query_batch_silent__set_scan_starts__archived_before_ancestor__from_first_row) +{ + 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.header(), database::context{ 0, 1, 0 }, {}, false)); + BOOST_REQUIRE(query.set(test::block2.header(), database::context{ 0, 2, 0 }, {}, false)); + BOOST_REQUIRE(query.set(test::block3.header(), database::context{ 0, 3, 0 }, {}, false)); + BOOST_REQUIRE(query.set(test::block3, false, false)); + BOOST_REQUIRE(query.set(test::block1, false, false)); + BOOST_REQUIRE(query.set(test::block2, false, false)); + BOOST_REQUIRE(query.push_confirmed(1, false)); + BOOST_REQUIRE(query.push_confirmed(2, false)); + BOOST_REQUIRE(query.push_confirmed(3, false)); + BOOST_REQUIRE(query.set_silent(1, simple_send())); + BOOST_REQUIRE(query.set_silent(3, simple_send())); + BOOST_REQUIRE(query.set_silent(2, simple_send())); + BOOST_REQUIRE(query.set_silent(4, simple_send())); + BOOST_REQUIRE(query.set_scan_start(2, 42)); + BOOST_REQUIRE_LT(query.to_coinbase(3), query.to_coinbase(2)); + + const stopper cancel{ false }; + BOOST_REQUIRE(query.set_scan_starts(cancel, 3, max_size_t, false)); + BOOST_REQUIRE_EQUAL(query.get_scan_start(3), 1u); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__get_scan_floor__unmapped__zero) +{ + 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(setup_scan_starts(query)); + BOOST_REQUIRE_EQUAL(query.get_scan_floor(0), 0u); + BOOST_REQUIRE_EQUAL(query.get_scan_floor(3), 0u); + BOOST_REQUIRE_EQUAL(query.get_scan_floor(42), 0u); +} + +BOOST_AUTO_TEST_CASE(query_batch_silent__get_scan_floor__mapped__nearest_at_or_below) +{ + 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(setup_scan_starts(query)); + BOOST_REQUIRE(query.set_scan_start(1, 1)); + BOOST_REQUIRE(query.set_scan_start(3, 3)); + BOOST_REQUIRE_EQUAL(query.get_scan_floor(0), 0u); + BOOST_REQUIRE_EQUAL(query.get_scan_floor(1), 1u); + BOOST_REQUIRE_EQUAL(query.get_scan_floor(2), 1u); + BOOST_REQUIRE_EQUAL(query.get_scan_floor(3), 3u); + BOOST_REQUIRE_EQUAL(query.get_scan_floor(42), 3u); +} +// get_scan_frontier +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(query_batch_silent__get_scan_frontier__latched__rows_at_latch) +{ + 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_silent(42, simple_send())); + BOOST_REQUIRE(query.set_silent(43, simple_send())); + BOOST_REQUIRE_EQUAL(query.get_scan_frontier(), 0u); + BOOST_REQUIRE_EQUAL(query.get_latch_tx(), 0u); + query.set_pooling(); + BOOST_REQUIRE(query.set_silent(44, simple_send())); + BOOST_REQUIRE_EQUAL(query.get_scan_frontier(), 2u); + BOOST_REQUIRE_EQUAL(query.get_latch_tx(), 1u); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/consensus/consensus_forks.cpp b/test/query/consensus/consensus_forks.cpp index dff84a8ac..27707558c 100644 --- a/test/query/consensus/consensus_forks.cpp +++ b/test/query/consensus/consensus_forks.cpp @@ -87,7 +87,7 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__filters_disabled__bypa BOOST_REQUIRE(is_bypass(fork.back().state)); } -BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__bypassed_silent_limit__stops_below_limit) +BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__bypassed_unscanned__stops_at_unscanned) { settings settings{}; settings.path = TEST_DIRECTORY; @@ -108,12 +108,13 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_validated_fork__bypassed_silent_limit_ BOOST_REQUIRE(query.push_candidate(link2)); size_t fork_point{}; - auto fork = query.get_validated_fork(fork_point, 2); + 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(is_bypass(fork.front().state)); + BOOST_REQUIRE(query.set_block_scanned(link2)); fork = query.get_validated_fork(fork_point); BOOST_REQUIRE_EQUAL(fork.size(), 2u); BOOST_REQUIRE(fork.back().link == link2); diff --git a/test/query/consensus/consensus_states.cpp b/test/query/consensus/consensus_states.cpp index 00d01d975..85abd0a45 100644 --- a/test/query/consensus/consensus_states.cpp +++ b/test/query/consensus/consensus_states.cpp @@ -24,7 +24,7 @@ BOOST_FIXTURE_TEST_SUITE(query_consensus_tests, test::directory_setup_fixture) // get_block_state -BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__genesis__associated_checkpointed) +BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__genesis__expected) { settings settings{}; settings.path = TEST_DIRECTORY; @@ -32,7 +32,7 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__genesis__associated_check test::query_accessor query{ store }; BOOST_REQUIRE(!store.create(test::events_handler)); BOOST_REQUIRE(query.initialize(test::genesis)); - BOOST_REQUIRE_EQUAL(query.get_block_state(0), block_state::associated | block_state::checkpointed); + BOOST_REQUIRE_EQUAL(query.get_block_state(0), block_state::associated | block_state::checkpointed | block_state::scanned | block_state::filtered); } BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__invalid_link__no_state) @@ -47,7 +47,7 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__invalid_link__no_state) BOOST_REQUIRE(!query.is_associated(1)); } -BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__unassociated_link__no_state) +BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__unassociated_link__scanned) { settings settings{}; settings.path = TEST_DIRECTORY; @@ -57,7 +57,7 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__unassociated_link__no_sta BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(query.set(test::block1.header(), context{ 0, 1, 0 }, {}, false)); BOOST_REQUIRE(query.set(*test::block1.transactions_ptr()->front())); - BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::no_state); + BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::scanned); BOOST_REQUIRE(!query.is_associated(1)); } @@ -70,7 +70,7 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__associated_link__associat 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)); - BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated); + BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::scanned); BOOST_REQUIRE(query.is_associated(1)); } @@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__milestone__associated_mil BOOST_REQUIRE(!store.create(test::events_handler)); BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, {}, true, false)); - BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::milestoned); + BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::milestoned | block_state::scanned); BOOST_REQUIRE(query.is_milestone(1)); BOOST_REQUIRE(query.is_bypass(1)); } @@ -97,7 +97,7 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__checkpoint__associated_ch BOOST_REQUIRE(!store.create(test::events_handler)); BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(query.set(test::block1, context{}, {}, false, false)); - BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::checkpointed); + BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::checkpointed | block_state::scanned); BOOST_REQUIRE(!query.is_milestone(1)); BOOST_REQUIRE(query.is_bypass(1)); } @@ -112,7 +112,7 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__confirmable__associated_c BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, {}, false, false)); BOOST_REQUIRE(query.set_block_confirmable(1)); - BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::confirmable); + BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::confirmable | block_state::scanned); BOOST_REQUIRE(query.is_confirmable(1)); BOOST_REQUIRE(query.is_validated(1)); } @@ -127,7 +127,7 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__valid__associated_valid) BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, {}, false, false)); BOOST_REQUIRE(query.set_block_valid(1)); - BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::valid); + BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::valid | block_state::scanned); BOOST_REQUIRE(!query.is_confirmable(1)); BOOST_REQUIRE(query.is_validated(1)); } @@ -142,7 +142,7 @@ BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__unconfirmable__associated BOOST_REQUIRE(query.initialize(test::genesis)); BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, {}, false, false)); BOOST_REQUIRE(query.set_block_unconfirmable(1)); - BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::unconfirmable); + BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::unconfirmable | block_state::scanned); BOOST_REQUIRE(query.is_unconfirmable(1)); } @@ -188,7 +188,34 @@ BOOST_AUTO_TEST_CASE(query_consensus__set_block_unknown__valid__associated) BOOST_REQUIRE(query.set(test::block1, context{ 0, 1, 0 }, {}, false, false)); BOOST_REQUIRE(query.set_block_valid(1)); BOOST_REQUIRE(query.set_block_unknown(1)); - BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated); + BOOST_REQUIRE_EQUAL(query.get_block_state(1), block_state::associated | block_state::scanned); +} + +BOOST_AUTO_TEST_CASE(query_consensus__get_block_state__filters_disabled__filtered) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + settings.filter_tx.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.set(test::block1, context{ 0, 1, 0 }, {}, false, false)); + BOOST_REQUIRE(is_filtered(query.get_block_state(1))); +} + +BOOST_AUTO_TEST_CASE(query_consensus__set_filter_body__filters_enabled__filtered) +{ + 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)); + BOOST_REQUIRE(!is_filtered(query.get_block_state(1))); + BOOST_REQUIRE(query.set_filter_body(1, test::block1)); + BOOST_REQUIRE(is_filtered(query.get_block_state(1))); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/extent.cpp b/test/query/extent.cpp index 472b47372..11dcc8630 100644 --- a/test/query/extent.cpp +++ b/test/query/extent.cpp @@ -55,6 +55,7 @@ BOOST_AUTO_TEST_CASE(query_extent__body_sizes__genesis__expected) BOOST_REQUIRE_EQUAL(query.schnorr0_body_size(), zero); BOOST_REQUIRE_EQUAL(query.duplicate_body_size(), zero); BOOST_REQUIRE_EQUAL(query.prevalid0_body_size(), zero); + BOOST_REQUIRE_EQUAL(query.presilent0_body_size(), zero); BOOST_REQUIRE_EQUAL(query.prevout_body_size(), zero); BOOST_REQUIRE_EQUAL(query.state_body_size(), zero); BOOST_REQUIRE_EQUAL(query.pool_body_size(), zero); @@ -126,6 +127,7 @@ BOOST_AUTO_TEST_CASE(query_extent__records__genesis__expected) BOOST_REQUIRE_EQUAL(query.schnorr0_records(), zero); BOOST_REQUIRE_EQUAL(query.duplicate_records(), zero); BOOST_REQUIRE_EQUAL(query.prevalid0_records(), zero); + BOOST_REQUIRE_EQUAL(query.presilent0_records(), zero); BOOST_REQUIRE_EQUAL(query.state_records(), one); BOOST_REQUIRE_EQUAL(query.pool_records(), zero); BOOST_REQUIRE_EQUAL(query.spends_records(), zero); @@ -254,7 +256,6 @@ BOOST_AUTO_TEST_CASE(query_extent__store_head_size__genesis__archive_plus_indexe + query.strong_tx_head_size() + query.scan_head_size() + query.scan_prefix_head_size() - + query.scan_start_head_size() + query.ecdsa0_head_size() + query.ecdsa1_head_size() + query.schnorr0_head_size() @@ -266,6 +267,8 @@ BOOST_AUTO_TEST_CASE(query_extent__store_head_size__genesis__archive_plus_indexe + query.duplicate_head_size() + query.prevalid0_head_size() + query.prevalid1_head_size() + + query.presilent0_head_size() + + query.presilent1_head_size() + query.prevout_head_size() + query.state_head_size() + query.pool_head_size() @@ -300,6 +303,8 @@ BOOST_AUTO_TEST_CASE(query_extent__store_body_size__genesis__archive_plus_indexe + query.duplicate_body_size() + query.prevalid0_body_size() + query.prevalid1_body_size() + + query.presilent0_body_size() + + query.presilent1_body_size() + query.prevout_body_size() + query.state_body_size() + query.pool_body_size() diff --git a/test/store/store.cpp b/test/store/store.cpp index a399f0772..20642f602 100644 --- a/test/store/store.cpp +++ b/test/store/store.cpp @@ -162,7 +162,6 @@ BOOST_AUTO_TEST_CASE(store__paths__default_configuration__expected) BOOST_REQUIRE_EQUAL(instance.duplicate_head_file(), "bitcoin/heads/index_duplicate.head"); BOOST_REQUIRE_EQUAL(instance.duplicate_body_file(), "bitcoin/index_duplicate.data"); BOOST_REQUIRE_EQUAL(instance.state_head_file(), "bitcoin/heads/index_state.head"); - BOOST_REQUIRE_EQUAL(instance.scan_start_head_file(), "bitcoin/heads/index_scan_start.head"); BOOST_REQUIRE_EQUAL(instance.wtxid_head_file(), "bitcoin/heads/index_wtxid.head"); BOOST_REQUIRE_EQUAL(instance.wtxid_body_file(), "bitcoin/index_wtxid.data"); diff --git a/test/tables/envelope.cpp b/test/tables/envelope.cpp index ac4514c58..9a245346f 100644 --- a/test/tables/envelope.cpp +++ b/test/tables/envelope.cpp @@ -22,7 +22,7 @@ BOOST_AUTO_TEST_SUITE(envelope_tests) using namespace system; -constexpr auto default_size = 357_size; +constexpr auto default_size = 365_size; static data_chunk to_chunk(const envelope& instance) { @@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(envelope__serialized_size__default__expected) BOOST_AUTO_TEST_CASE(envelope__to_data__default__expected_version) { const envelope instance{}; - BOOST_REQUIRE_EQUAL(instance.schema, system::config::version(4, 0, 12, 0)); + BOOST_REQUIRE_EQUAL(instance.schema, system::config::version(4, 0, 14, 0)); BOOST_REQUIRE_EQUAL(to_chunk(instance).front(), schema::version.front()); } diff --git a/test/tables/indexes/scan.cpp b/test/tables/indexes/scan.cpp index c2e131b22..ca134aca2 100644 --- a/test/tables/indexes/scan.cpp +++ b/test/tables/indexes/scan.cpp @@ -32,20 +32,51 @@ using scan_storage = default_storage>; BOOST_AUTO_TEST_CASE(scan__create_verify_close__aggregate__expected) { - scan_storage head{ "head" }; + data_chunk head_file{}; + test::chunk_storage head{ head_file }; scan_storage body{ "body" }; - scan_table instance{ head, body }; + scan_table instance{ head, body, 1 }; BOOST_REQUIRE(instance.create()); BOOST_REQUIRE(instance.verify()); BOOST_REQUIRE(instance.close()); } +BOOST_AUTO_TEST_CASE(scan__at__unset__terminal) +{ + data_chunk head_file{}; + test::chunk_storage head{ head_file }; + scan_storage body{ "body" }; + scan_table instance{ head, body, 1 }; + BOOST_REQUIRE(instance.create()); + BOOST_REQUIRE(instance.at(0).is_terminal()); + BOOST_REQUIRE(instance.at(1).is_terminal()); + BOOST_REQUIRE(instance.close()); +} + +BOOST_AUTO_TEST_CASE(scan__push__beyond_buckets__expanded) +{ + data_chunk head_file{}; + test::chunk_storage head{ head_file }; + scan_storage body{ "body" }; + scan_table instance{ head, body, 1 }; + BOOST_REQUIRE(instance.create()); + BOOST_REQUIRE(instance.push(7, 2)); + BOOST_REQUIRE(instance.at(0).is_terminal()); + BOOST_REQUIRE(instance.at(1).is_terminal()); + BOOST_REQUIRE_EQUAL(instance.at(2), 7u); + BOOST_REQUIRE(instance.push(9, 2)); + BOOST_REQUIRE_EQUAL(instance.at(2), 9u); + BOOST_REQUIRE(instance.verify()); + BOOST_REQUIRE(instance.close()); +} + BOOST_AUTO_TEST_CASE(scan__put_columns__two_rows__expected) { - scan_storage head{ "head" }; + data_chunk head_file{}; + test::chunk_storage head{ head_file }; scan_storage body{ "body" }; - scan_table instance{ head, body }; + scan_table instance{ head, body, 1 }; BOOST_REQUIRE(instance.create()); constexpr auto compressed = base16_array diff --git a/test/tables/indexes/scan_start.cpp b/test/tables/indexes/scan_start.cpp deleted file mode 100644 index fb0b4ac1f..000000000 --- a/test/tables/indexes/scan_start.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/** - * 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 . - */ -#include "../../test.hpp" -#include "../../mocks/chunk_storage.hpp" - -BOOST_AUTO_TEST_SUITE(scan_start_tests) - -using namespace system; - -BOOST_AUTO_TEST_CASE(scan_start__at__unset__terminal) -{ - data_chunk head_file{}; - test::chunk_storage head_store{ head_file }; - table::scan_start instance{ head_store }; - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE(instance.at(0).is_terminal()); -} - -BOOST_AUTO_TEST_CASE(scan_start__set__gapped__unset_terminal) -{ - data_chunk head_file{}; - test::chunk_storage head_store{ head_file }; - table::scan_start instance{ head_store }; - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE(instance.set(1, table::scan_start::record{ {}, 0x01020304 })); - BOOST_REQUIRE_EQUAL(instance.count(), 2u); - BOOST_REQUIRE_EQUAL(head_file, base16_chunk("ffffffff04030201")); - BOOST_REQUIRE(instance.at(0).is_terminal()); - BOOST_REQUIRE_EQUAL(instance.at(1), 0x01020304u); - BOOST_REQUIRE(instance.at(2).is_terminal()); -} - -BOOST_AUTO_TEST_CASE(scan_start__set__replace__replaced) -{ - data_chunk head_file{}; - test::chunk_storage head_store{ head_file }; - table::scan_start instance{ head_store }; - BOOST_REQUIRE(instance.create()); - BOOST_REQUIRE(instance.set(0, table::scan_start::record{ {}, 42 })); - BOOST_REQUIRE(instance.set(0, table::scan_start::record{ {}, 7 })); - BOOST_REQUIRE_EQUAL(instance.at(0), 7u); -} - -BOOST_AUTO_TEST_SUITE_END()