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()