From 9e8e18aa5528adf77e04ac680b630efbeeb1432c Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 12 Jan 2026 16:51:55 -0500 Subject: [PATCH 1/3] Pass bypass with block.txs write, use to enable clean. --- .../database/impl/query/archive_write.ipp | 34 ++++++++++--------- include/bitcoin/database/query.hpp | 16 +++++---- test/query/archive_write.cpp | 2 +- test/query/initialize.cpp | 10 +++--- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/include/bitcoin/database/impl/query/archive_write.ipp b/include/bitcoin/database/impl/query/archive_write.ipp index 6a621fe2..bb645741 100644 --- a/include/bitcoin/database/impl/query/archive_write.ipp +++ b/include/bitcoin/database/impl/query/archive_write.ipp @@ -65,10 +65,10 @@ bool CLASS::set(const transaction& tx) NOEXCEPT } TEMPLATE -bool CLASS::set(const block& block, bool strong) NOEXCEPT +bool CLASS::set(const block& block, bool strong, bool bypass) NOEXCEPT { // This sets only the txs of a block with header/context already archived. - return !set_code(block, strong); + return !set_code(block, strong, bypass); } // set transaction @@ -84,11 +84,12 @@ code CLASS::set_code(const transaction& tx) NOEXCEPT if (tx_fk.is_terminal()) return error::tx_tx_allocate; - return set_code(tx_fk, tx); + return set_code(tx_fk, tx, false); } TEMPLATE -code CLASS::set_code(const tx_link& tx_fk, const transaction& tx) NOEXCEPT +code CLASS::set_code(const tx_link& tx_fk, const transaction& tx, + bool bypass) NOEXCEPT { // This is the only multitable write query (except initialize/genesis). @@ -106,9 +107,6 @@ code CLASS::set_code(const tx_link& tx_fk, const transaction& tx) NOEXCEPT // ======================================================================== const auto scope = store_.get_transactor(); - // If dirty we must guard against duplicates. - const auto dirty = store_.is_dirty(); - // Allocate contiguously and store inputs. input_link in_fk{}; if (!store_.input.put_link(in_fk, @@ -167,9 +165,13 @@ code CLASS::set_code(const tx_link& tx_fk, const transaction& tx) NOEXCEPT if (!store_.point.expand(ins_fk + inputs)) return error::tx_point_allocate; + // If dirty we must guard against duplicates. + // Dirty doesn't hold up in the case of an invalidated block, as that + // may result in a duplicated tx. So dirty should be false in the case + // of a non-bypass (valid) block. // Must be set after tx.set and before tx.commit, since searchable and // produces association to tx.link, and is also an integral part of tx. - if (dirty) + if (store_.is_dirty() || !bypass) { // Collect duplicates to store in duplicate table. std::vector twins{}; @@ -336,7 +338,7 @@ code CLASS::set_code(header_link& out_fk, const block& block, const context& ctx, bool milestone, bool strong) NOEXCEPT { const auto ec = set_code(out_fk, block.header(), ctx, milestone); - return ec ? ec : set_code(block, out_fk, strong); + return ec ? ec : set_code(block, out_fk, strong, strong || milestone); } // set txs from block @@ -346,26 +348,26 @@ code CLASS::set_code(header_link& out_fk, const block& block, // releases all memory for parts of itself, due to the custom allocator. TEMPLATE -code CLASS::set_code(const block& block, bool strong) NOEXCEPT +code CLASS::set_code(const block& block, bool strong, bool bypass) NOEXCEPT { header_link unused{}; - return set_code(unused, block, strong); + return set_code(unused, block, strong, bypass); } TEMPLATE -code CLASS::set_code(header_link& out_fk, const block& block, - bool strong) NOEXCEPT +code CLASS::set_code(header_link& out_fk, const block& block, bool strong, + bool bypass) NOEXCEPT { out_fk = to_header(block.get_hash()); if (out_fk.is_terminal()) return error::txs_header; - return set_code(block, out_fk, strong); + return set_code(block, out_fk, strong, bypass); } TEMPLATE code CLASS::set_code(const block& block, const header_link& key, - bool strong) NOEXCEPT + bool strong, bool bypass) NOEXCEPT { using namespace system; if (key.is_terminal()) @@ -383,7 +385,7 @@ code CLASS::set_code(const block& block, const header_link& key, code ec{}; auto fk = tx_fks; for (const auto& tx: *block.transactions_ptr()) - if ((ec = set_code(fk++, *tx))) + if ((ec = set_code(fk++, *tx, bypass))) return ec; using bytes = linkage::integer; diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 04f159ba..da5ddbef 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -410,7 +410,7 @@ class query bool set(const block& block, const context& ctx, bool milestone, bool strong) NOEXCEPT; bool set(const transaction& tx) NOEXCEPT; - bool set(const block& block, bool strong) NOEXCEPT; + bool set(const block& block, bool strong, bool bypass) NOEXCEPT; /// Set transaction. code set_code(const transaction& tx) NOEXCEPT; @@ -436,9 +436,11 @@ class query const chain_context& ctx, bool milestone, bool strong) NOEXCEPT; /// Set block.txs (headers-first). - code set_code(const block& block, bool strong) NOEXCEPT; - code set_code(header_link& out_fk, const block& block, bool strong) NOEXCEPT; - code set_code(const block& block, const header_link& key, bool strong) NOEXCEPT; + code set_code(const block& block, bool strong, bool bypass) NOEXCEPT; + code set_code(header_link& out_fk, const block& block, bool strong, + bool bypass) NOEXCEPT; + code set_code(const block& block, const header_link& key, bool strong, + bool bypass) NOEXCEPT; /// Context. /// ----------------------------------------------------------------------- @@ -702,11 +704,13 @@ class query code get_confirmed_unspent_outputs_turbo(std::atomic_bool& cancel, outpoints& out, const hash_digest& key) const NOEXCEPT; code get_minimum_unspent_outputs_turbo(std::atomic_bool& cancel, - outpoints& out, const hash_digest& key, uint64_t minimum) const NOEXCEPT; + outpoints& out, const hash_digest& key, + uint64_t minimum) const NOEXCEPT; /// tx_fk must be allocated. /// ----------------------------------------------------------------------- - code set_code(const tx_link& tx_fk, const transaction& tx) NOEXCEPT; + code set_code(const tx_link& tx_fk, const transaction& tx, + bool bypass) NOEXCEPT; private: // Chain objects. diff --git a/test/query/archive_write.cpp b/test/query/archive_write.cpp index cb1629e5..5fa4a9cb 100644 --- a/test/query/archive_write.cpp +++ b/test/query/archive_write.cpp @@ -705,7 +705,7 @@ BOOST_AUTO_TEST_CASE(query_archive_write__set_block_txs__get_block__expected) BOOST_REQUIRE(!query.is_block(test::genesis.hash())); BOOST_REQUIRE(query.set(test::genesis.header(), test::context, milestone)); BOOST_REQUIRE(!query.is_associated(0)); - BOOST_REQUIRE(query.set(test::genesis, false)); + BOOST_REQUIRE(query.set(test::genesis, false, false)); BOOST_REQUIRE(query.is_block(test::genesis.hash())); BOOST_REQUIRE(query.is_associated(0)); diff --git a/test/query/initialize.cpp b/test/query/initialize.cpp index b1f19fd7..3d1264d7 100644 --- a/test/query/initialize.cpp +++ b/test/query/initialize.cpp @@ -415,17 +415,17 @@ BOOST_AUTO_TEST_CASE(query_initialize__get_unassociated_above__gapped_candidate_ BOOST_REQUIRE_EQUAL(unassociated3.size(), 0u); // There are two unassociated blocks above block 1 (new fork point). - BOOST_REQUIRE(query.set(test::block1, false)); + BOOST_REQUIRE(query.set(test::block1, false, false)); BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block1.hash()), false)); BOOST_REQUIRE_EQUAL(query.get_all_unassociated().size(), 2u); // There is one unassociated block above block 2 (new fork point). - BOOST_REQUIRE(query.set(test::block2, false)); + BOOST_REQUIRE(query.set(test::block2, false, false)); BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block2.hash()), false)); BOOST_REQUIRE_EQUAL(query.get_all_unassociated().size(), 1u); // There are no unassociated blocks above block 3 (new fork point). - BOOST_REQUIRE(query.set(test::block3, false)); + BOOST_REQUIRE(query.set(test::block3, false, false)); BOOST_REQUIRE(query.push_confirmed(query.to_header(test::block3.hash()), false)); BOOST_REQUIRE_EQUAL(query.get_all_unassociated().size(), 0u); } @@ -482,7 +482,7 @@ BOOST_AUTO_TEST_CASE(query_initialize__get_unassociated_count_above__gapped_cand BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(3, 1), 0u); // There is one unassociated block at block 2. - BOOST_REQUIRE(query.set(test::block3, false)); // associated + BOOST_REQUIRE(query.set(test::block3, false, false)); // associated BOOST_REQUIRE_EQUAL(query.get_unassociated_count(), 1u); BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(0), 1u); BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(1), 1u); @@ -490,7 +490,7 @@ BOOST_AUTO_TEST_CASE(query_initialize__get_unassociated_count_above__gapped_cand BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(3), 0u); // There are no unassociated blocks. - BOOST_REQUIRE(query.set(test::block2, false)); // associated + BOOST_REQUIRE(query.set(test::block2, false, false)); // associated BOOST_REQUIRE_EQUAL(query.get_unassociated_count(), 0u); BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(0), 0u); BOOST_REQUIRE_EQUAL(query.get_unassociated_count_above(1), 0u); From 50561a99f6ad2e31217007cca30a6d8a844c8676 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 12 Jan 2026 16:52:39 -0500 Subject: [PATCH 2/3] Move empty body check outside of get_doubles() loop. --- include/bitcoin/database/impl/query/consensus.ipp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/bitcoin/database/impl/query/consensus.ipp b/include/bitcoin/database/impl/query/consensus.ipp index 562b8e12..b9929b18 100644 --- a/include/bitcoin/database/impl/query/consensus.ipp +++ b/include/bitcoin/database/impl/query/consensus.ipp @@ -472,9 +472,7 @@ bool CLASS::set_unstrong(const header_link& link) NOEXCEPT TEMPLATE bool CLASS::get_doubles(tx_links& out, const point& point) const NOEXCEPT { - // Body size check avoids a header hit when no duplicates (common). - if (is_zero(store_.duplicate.body_size()) || - !store_.duplicate.exists(point)) + if (!store_.duplicate.exists(point)) return true; auto success = false; @@ -510,8 +508,10 @@ bool CLASS::set_prevouts(const header_link& link, const block& block) NOEXCEPT if (block.transactions() <= one) return true; + // Body size check avoids a header hit when no duplicates (common). tx_links doubles{}; - if (!get_doubles(doubles, block)) + if (!is_zero(store_.duplicate.body_size()) && + !get_doubles(doubles, block)) return false; const auto prevout = to_prevout(link); From 8322f85efa3cbbdaf45e1fdf226a680598d13bbb Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 12 Jan 2026 18:12:13 -0500 Subject: [PATCH 3/3] Make weak block association (to_block) protected. --- .../database/impl/query/archive_read.ipp | 6 +-- .../bitcoin/database/impl/query/confirm.ipp | 2 +- .../bitcoin/database/impl/query/translate.ipp | 39 ++++++++++++++++--- include/bitcoin/database/query.hpp | 5 ++- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/include/bitcoin/database/impl/query/archive_read.ipp b/include/bitcoin/database/impl/query/archive_read.ipp index 7e30b4b4..9f9cfefb 100644 --- a/include/bitcoin/database/impl/query/archive_read.ipp +++ b/include/bitcoin/database/impl/query/archive_read.ipp @@ -156,16 +156,14 @@ inline hash_digest CLASS::get_point_hash(const point_link& link) const NOEXCEPT TEMPLATE bool CLASS::get_tx_height(size_t& out, const tx_link& link) const NOEXCEPT { - // to_block is strong but not necessarily confirmed. - const auto fk = to_block(link); + const auto fk = to_strong(link); return is_confirmed_block(fk) && get_height(out, fk); } TEMPLATE bool CLASS::get_tx_position(size_t& out, const tx_link& link) const NOEXCEPT { - // to_block is strong but not necessarily confirmed. - const auto fk = to_block(link); + const auto fk = to_strong(link); if (!is_confirmed_block(fk)) return false; diff --git a/include/bitcoin/database/impl/query/confirm.ipp b/include/bitcoin/database/impl/query/confirm.ipp index c7b23e98..5cbd0fc5 100644 --- a/include/bitcoin/database/impl/query/confirm.ipp +++ b/include/bitcoin/database/impl/query/confirm.ipp @@ -68,7 +68,7 @@ TEMPLATE bool CLASS::is_confirmed_tx(const tx_link& link) const NOEXCEPT { // The tx is strong *and* its block is confirmed (by height). - const auto fk = to_block(link); + const auto fk = to_strong(link); return !fk.is_terminal() && is_confirmed_block(fk); } diff --git a/include/bitcoin/database/impl/query/translate.ipp b/include/bitcoin/database/impl/query/translate.ipp index b150b4c5..d59fbec1 100644 --- a/include/bitcoin/database/impl/query/translate.ipp +++ b/include/bitcoin/database/impl/query/translate.ipp @@ -165,18 +165,47 @@ output_link CLASS::to_prevout(const point_link& link) const NOEXCEPT // block/tx to block (reverse navigation) // ---------------------------------------------------------------------------- +TEMPLATE +tx_link CLASS::to_strong_tx(const tx_link& link) const NOEXCEPT +{ + return to_strong_tx(get_tx_key(link)); +} + +TEMPLATE +tx_link CLASS::to_strong_tx(const hash_digest& tx_hash) const NOEXCEPT +{ + // Get all tx links for tx_hash. + tx_links txs{}; + for (auto it = store_.tx.it(tx_hash); it; ++it) + txs.push_back(*it); + + // Find the first strong tx of the set and return its link. + for (const auto& tx : txs) + if (!to_block(tx).is_terminal()) + return tx; + + return {}; +} + +// protected (weak association) // Required for confirmation processing. TEMPLATE -header_link CLASS::to_block(const tx_link& key) const NOEXCEPT +header_link CLASS::to_block(const tx_link& link) const NOEXCEPT { table::strong_tx::record strong{}; - if (!store_.strong_tx.find(key, strong) || !strong.positive()) + if (!store_.strong_tx.find(link, strong) || !strong.positive()) return {}; // Terminal implies not in strong block (reorganized). return strong.header_fk(); } +TEMPLATE +header_link CLASS::to_strong(const tx_link& link) const NOEXCEPT +{ + return to_strong(get_tx_key(link)); +} + // Required for confirmation processing. TEMPLATE header_link CLASS::to_strong(const hash_digest& tx_hash) const NOEXCEPT @@ -188,14 +217,12 @@ header_link CLASS::to_strong(const hash_digest& tx_hash) const NOEXCEPT // Find the first strong tx of the set and return its block. for (const auto& tx: txs) - { - const auto block = to_block(tx); - if (!block.is_terminal()) + if (const auto block = to_block(tx); !block.is_terminal()) return block; - } return {}; } + TEMPLATE header_link CLASS::to_parent(const header_link& link) const NOEXCEPT { diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index da5ddbef..7edab789 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -263,7 +263,9 @@ class query output_link to_prevout(const point_link& link) const NOEXCEPT; /// block/tx to block (reverse navigation) - header_link to_block(const tx_link& key) const NOEXCEPT; + tx_link to_strong_tx(const tx_link& link) const NOEXCEPT; + tx_link to_strong_tx(const hash_digest& tx_hash) const NOEXCEPT; + header_link to_strong(const tx_link& link) const NOEXCEPT; header_link to_strong(const hash_digest& tx_hash) const NOEXCEPT; header_link to_parent(const header_link& link) const NOEXCEPT; @@ -604,6 +606,7 @@ class query /// Translate. /// ----------------------------------------------------------------------- + header_link to_block(const tx_link& link) const NOEXCEPT; uint32_t to_input_index(const tx_link& parent_fk, const point_link& point_fk) const NOEXCEPT; uint32_t to_output_index(const tx_link& parent_fk,