From 5b654dba9931a5ae2342e8b8954f4110059069ef Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Tue, 21 Jul 2026 23:14:33 -0400 Subject: [PATCH 1/3] Add linkage<> + and += unsigned value operators. --- .../database/impl/primitives/linkage.ipp | 19 ++++++++ .../impl/query/archive/chain_writer.ipp | 4 +- .../impl/query/archive/wire_writer.ipp | 16 +++---- .../bitcoin/database/primitives/arrayhead.hpp | 2 +- .../bitcoin/database/primitives/hashhead.hpp | 2 +- .../bitcoin/database/primitives/linkage.hpp | 6 +++ .../bitcoin/database/primitives/nohead.hpp | 2 +- test/primitives/linkage.cpp | 44 +++++++++++++++++++ 8 files changed, 81 insertions(+), 14 deletions(-) diff --git a/include/bitcoin/database/impl/primitives/linkage.ipp b/include/bitcoin/database/impl/primitives/linkage.ipp index 0e05a881f..3312a83e5 100644 --- a/include/bitcoin/database/impl/primitives/linkage.ipp +++ b/include/bitcoin/database/impl/primitives/linkage.ipp @@ -73,6 +73,25 @@ inline CLASS CLASS::operator++(int) NOEXCEPT return self; } +TEMPLATE +template > +constexpr CLASS& CLASS::operator+=(Integer offset) NOEXCEPT +{ + // An actual row cannot be terminal, so the sum must be less than terminal. + BC_ASSERT(!system::is_add_overflow(value, offset)); + BC_ASSERT(system::is_lesser(system::add(value, offset), terminal)); + value = system::add(value, offset); + return *this; +} + +TEMPLATE +template > +constexpr CLASS CLASS::operator+(Integer offset) const NOEXCEPT +{ + auto self = *this; + return self += offset; +} + TEMPLATE constexpr CLASS::operator CLASS::integer() const NOEXCEPT { diff --git a/include/bitcoin/database/impl/query/archive/chain_writer.ipp b/include/bitcoin/database/impl/query/archive/chain_writer.ipp index 77ce7acdf..672c5f1e8 100644 --- a/include/bitcoin/database/impl/query/archive/chain_writer.ipp +++ b/include/bitcoin/database/impl/query/archive/chain_writer.ipp @@ -225,8 +225,8 @@ code CLASS::set_code(const tx_link& tx_fk, const transaction& tx, // See outs::put_ref. // Calculate next corresponding output fk from serialized size. // (variable_size(value) + (value + script)) - (value - parent) - out_fk.value += (variable_size(output->value()) + - output->serialized_size() - value_parent); + out_fk += variable_size(output->value()) + + output->serialized_size() - value_parent; } } diff --git a/include/bitcoin/database/impl/query/archive/wire_writer.ipp b/include/bitcoin/database/impl/query/archive/wire_writer.ipp index 07d54d174..8248ed2cc 100644 --- a/include/bitcoin/database/impl/query/archive/wire_writer.ipp +++ b/include/bitcoin/database/impl/query/archive/wire_writer.ipp @@ -164,9 +164,8 @@ code CLASS::set_code(std::vector& twins, const accessors& ptrs, table::address::record{ {}, out_fk })) return error::tx_address_put; - out_fk.value += possible_narrow_cast( - tx_link::size + variable_size(value) + variable_size(bytes) + - bytes); + out_fk += tx_link::size + variable_size(value) + + variable_size(bytes) + bytes; } BC_ASSERT(osource); @@ -217,7 +216,6 @@ code CLASS::set_code(const block_view& block, const header_link& key, using out_t = output_link::integer; using ins_t = ins_link::integer; using outs_t = outs_link::integer; - using address_t = address_link::integer; if (key.is_terminal()) return error::txs_header; @@ -324,11 +322,11 @@ code CLASS::set_code(const block_view& block, const header_link& key, tx.output_table_size(); fks.tx_fk++; - fks.ins_fk.value += possible_narrow_cast(tx.inputs()); - fks.outs_fk.value += possible_narrow_cast(tx.outputs()); - fks.in_fk.value += possible_narrow_cast(tx.input_table_size()); - fks.out_fk.value += possible_narrow_cast(out_bytes); - fks.ad_fk.value += possible_narrow_cast(tx.outputs()); + fks.ins_fk += tx.inputs(); + fks.outs_fk += tx.outputs(); + fks.in_fk += tx.input_table_size(); + fks.out_fk += out_bytes; + fks.ad_fk += tx.outputs(); } // Release all accessors (subsequent writes allocate). diff --git a/include/bitcoin/database/primitives/arrayhead.hpp b/include/bitcoin/database/primitives/arrayhead.hpp index 4af44a78a..71e0bd115 100644 --- a/include/bitcoin/database/primitives/arrayhead.hpp +++ b/include/bitcoin/database/primitives/arrayhead.hpp @@ -96,7 +96,7 @@ class arrayhead using namespace system; BC_ASSERT(!is_multiply_overflow(index, bucket_size)); BC_ASSERT(!is_add_overflow(bucket_size, index * bucket_size)); - return possible_narrow_cast(add1(index) * bucket_size); + return possible_narrow_cast(add1(index) * bucket_size); } // These are thread safe. diff --git a/include/bitcoin/database/primitives/hashhead.hpp b/include/bitcoin/database/primitives/hashhead.hpp index 146614857..254ac4e1b 100644 --- a/include/bitcoin/database/primitives/hashhead.hpp +++ b/include/bitcoin/database/primitives/hashhead.hpp @@ -131,7 +131,7 @@ class hashhead using namespace system; BC_ASSERT(!is_multiply_overflow(index, cell_size)); BC_ASSERT(!is_add_overflow(cell_size, index * cell_size)); - return possible_narrow_cast(add1(index) * cell_size); + return possible_narrow_cast(add1(index) * cell_size); } // These are thread safe. diff --git a/include/bitcoin/database/primitives/linkage.hpp b/include/bitcoin/database/primitives/linkage.hpp index f8047e00a..eac07be08 100644 --- a/include/bitcoin/database/primitives/linkage.hpp +++ b/include/bitcoin/database/primitives/linkage.hpp @@ -56,6 +56,12 @@ struct linkage inline self& operator++() NOEXCEPT; inline self operator++(int) NOEXCEPT; + /// Offset addition operators (records, or bytes for slab links). + template = true> + constexpr self& operator+=(Integer offset) NOEXCEPT; + template = true> + constexpr self operator+(Integer offset) const NOEXCEPT; + /// Integral and array cast operators. constexpr operator integer() const NOEXCEPT; inline operator bytes() const NOEXCEPT; diff --git a/include/bitcoin/database/primitives/nohead.hpp b/include/bitcoin/database/primitives/nohead.hpp index 1fdb6237d..b31e392dc 100644 --- a/include/bitcoin/database/primitives/nohead.hpp +++ b/include/bitcoin/database/primitives/nohead.hpp @@ -84,7 +84,7 @@ class nohead using namespace system; BC_ASSERT(!is_multiply_overflow(index, bucket_size)); BC_ASSERT(!is_add_overflow(bucket_size, index * bucket_size)); - return possible_narrow_cast(add1(index) * bucket_size); + return possible_narrow_cast(add1(index) * bucket_size); } // These are thread safe. diff --git a/test/primitives/linkage.cpp b/test/primitives/linkage.cpp index 41b585e1c..f3a52ee16 100644 --- a/test/primitives/linkage.cpp +++ b/test/primitives/linkage.cpp @@ -110,6 +110,17 @@ static_assert(linkage<6>{ 42 } == 42_u64); static_assert(linkage<7>{ 42 } == 42_u64); static_assert(linkage<8>{ 42 } == 42_u64); +// Offset addition (integer() cast operator for equality test). +static_assert(linkage<0>{ 40 } + 2u == 42_size); +static_assert(linkage<1>{ 40 } + 2u == 42_u8); +static_assert(linkage<2>{ 40 } + 2u == 42_u16); +static_assert(linkage<3>{ 40 } + 2u == 42_u32); +static_assert(linkage<4>{ 40 } + 2u == 42_u32); +static_assert(linkage<5>{ 40 } + 2u == 42_u64); +static_assert(linkage<6>{ 40 } + 2u == 42_u64); +static_assert(linkage<7>{ 40 } + 2u == 42_u64); +static_assert(linkage<8>{ 40 } + 2u == 42_u64); + // assign integer BOOST_AUTO_TEST_CASE(linkage__assign_integer__1__expected) @@ -352,6 +363,39 @@ BOOST_AUTO_TEST_CASE(linkage__increment__8__expected) BOOST_REQUIRE_EQUAL(instance, 0x4201020304050602u); } +// add-assign + +BOOST_AUTO_TEST_CASE(linkage__add_assign__0__expected) +{ + linkage<0> instance{ 0x42 }; + instance += 1u; + BOOST_REQUIRE_EQUAL(instance, 0x43u); + + instance += 0x10_size; + BOOST_REQUIRE_EQUAL(instance, 0x53u); +} + +BOOST_AUTO_TEST_CASE(linkage__add_assign__2__expected) +{ + linkage<2> instance{ 0x4200 }; + instance += 2u; + BOOST_REQUIRE_EQUAL(instance, 0x4202u); +} + +BOOST_AUTO_TEST_CASE(linkage__add_assign__5__expected) +{ + linkage<5> instance{ 0x4201020300 }; + instance += 0x100u; + BOOST_REQUIRE_EQUAL(instance, 0x4201020400u); +} + +BOOST_AUTO_TEST_CASE(linkage__add_assign__8__expected) +{ + linkage<8> instance{ 0x4201020304050600 }; + instance += 0x42u; + BOOST_REQUIRE_EQUAL(instance, 0x4201020304050642u); +} + // cast bytes BOOST_AUTO_TEST_CASE(linkage__bytes__0__expected) From 88e6eecbf958624e7e5000009c220ddda83e2803 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Tue, 21 Jul 2026 23:14:56 -0400 Subject: [PATCH 2/3] Parallelize set_strong in the confirmation path. --- .../impl/query/archive/chain_writer.ipp | 3 +- .../impl/query/archive/wire_writer.ipp | 3 +- .../impl/query/consensus/consensus_strong.ipp | 35 +++++++++++-------- .../bitcoin/database/impl/query/height.ipp | 4 +-- include/bitcoin/database/query.hpp | 2 +- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/include/bitcoin/database/impl/query/archive/chain_writer.ipp b/include/bitcoin/database/impl/query/archive/chain_writer.ipp index 672c5f1e8..e568a69c2 100644 --- a/include/bitcoin/database/impl/query/archive/chain_writer.ipp +++ b/include/bitcoin/database/impl/query/archive/chain_writer.ipp @@ -409,7 +409,8 @@ code CLASS::set_code(const block& block, const header_link& key, constexpr auto positive = true; // Transactor assures cannot be restored without txs, as required to unset. - if (strong && !set_strong(key, txs, tx_fks, positive)) + // Sequential write, as archival is already concurrent across blocks. + if (strong && !set_strong(key, txs, tx_fks, positive, false)) return error::txs_confirm; // Header link is the key for the txs table. diff --git a/include/bitcoin/database/impl/query/archive/wire_writer.ipp b/include/bitcoin/database/impl/query/archive/wire_writer.ipp index 8248ed2cc..950fd7cad 100644 --- a/include/bitcoin/database/impl/query/archive/wire_writer.ipp +++ b/include/bitcoin/database/impl/query/archive/wire_writer.ipp @@ -347,7 +347,8 @@ code CLASS::set_code(const block_view& block, const header_link& key, constexpr auto positive = true; // Transactor assures cannot be restored without txs, as required to unset. - if (strong && !set_strong(key, txs, tx_fks, positive)) + // Sequential write, as archival is already concurrent across blocks. + if (strong && !set_strong(key, txs, tx_fks, positive, false)) return error::txs_confirm; // Header link is the key for the txs table. diff --git a/include/bitcoin/database/impl/query/consensus/consensus_strong.ipp b/include/bitcoin/database/impl/query/consensus/consensus_strong.ipp index f16848de9..7f1853e49 100644 --- a/include/bitcoin/database/impl/query/consensus/consensus_strong.ipp +++ b/include/bitcoin/database/impl/query/consensus/consensus_strong.ipp @@ -19,6 +19,7 @@ #ifndef LIBBITCOIN_DATABASE_QUERY_CONSENSUS_STRONG_IPP #define LIBBITCOIN_DATABASE_QUERY_CONSENSUS_STRONG_IPP +#include #include namespace libbitcoin { @@ -107,27 +108,33 @@ header_link CLASS::find_strong(const hash_digest& tx_hash) const NOEXCEPT // protected TEMPLATE bool CLASS::set_strong(const header_link& link, size_t count, - const tx_link& first_fk, bool positive) NOEXCEPT + const tx_link& first_fk, bool positive, bool parallel) NOEXCEPT { using namespace system; using link_t = table::strong_tx::link; using element_t = table::strong_tx::record; + const auto policy = poolstl::execution::par_if(parallel); // Preallocate all strong_tx records for the block and reuse memory ptr. + // Terminal allocation must not be indexed (would wrap to valid records). const auto records = possible_narrow_cast(count); - auto record = store_.strong_tx.allocate(records); - const auto ptr = store_.strong_tx.get_memory(); - const auto end = first_fk + count; + const auto record = store_.strong_tx.allocate(records); + if (record.is_terminal()) + return false; - // Contiguous tx links. - for (auto fk = first_fk; fk < end; ++fk) - if (!store_.strong_tx.put(ptr, record++, fk, element_t - { - {}, - table::strong_tx::merge(positive, link) - })) return false; + const auto ptr = store_.strong_tx.get_memory(); - return true; + // All records of the block share one element value. + const element_t element{ {}, table::strong_tx::merge(positive, link) }; + + // Contiguous tx links to contiguous records. + return std::all_of(policy, poolstl::iota_iter(zero), + poolstl::iota_iter(count), + [&](size_t index) NOEXCEPT + { + return store_.strong_tx.put(ptr, record + index, first_fk + index, + element); + }); } TEMPLATE @@ -144,7 +151,7 @@ bool CLASS::set_strong(const header_link& link) NOEXCEPT const auto scope = get_transactor(); // Clean allocation failure (e.g. disk full). - return set_strong(link, txs.number, txs.coinbase_fk , true); + return set_strong(link, txs.number, txs.coinbase_fk, true, true); // ======================================================================== } @@ -162,7 +169,7 @@ bool CLASS::set_unstrong(const header_link& link) NOEXCEPT const auto scope = get_transactor(); // Clean allocation failure (e.g. disk full). - return set_strong(link, txs.number, txs.coinbase_fk, false); + return set_strong(link, txs.number, txs.coinbase_fk, false, true); // ======================================================================== } diff --git a/include/bitcoin/database/impl/query/height.ipp b/include/bitcoin/database/impl/query/height.ipp index f9c4ac4f4..ee9db7e7c 100644 --- a/include/bitcoin/database/impl/query/height.ipp +++ b/include/bitcoin/database/impl/query/height.ipp @@ -85,7 +85,7 @@ bool CLASS::push_confirmed(const header_link& link, bool strong) NOEXCEPT const auto scope = get_transactor(); // This reservation guard assumes no concurrent writes to the table. - if (strong && !set_strong(link, txs.number, txs.coinbase_fk, true)) + if (strong && !set_strong(link, txs.number, txs.coinbase_fk, true, true)) return false; const table::height::record confirmed{ {}, link }; @@ -110,7 +110,7 @@ bool CLASS::pop_confirmed() NOEXCEPT const auto scope = get_transactor(); // Clean single allocation failure. - if (!set_strong(link, txs.number, txs.coinbase_fk, false)) + if (!set_strong(link, txs.number, txs.coinbase_fk, false, true)) return false; /////////////////////////////////////////////////////////////////////////// diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index b1d505f59..0ba841216 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -837,7 +837,7 @@ class query /// Support set_strong and set_unstrong writers. bool set_strong(const header_link& link, size_t count, - const tx_link& first_fk, bool positive) NOEXCEPT; + const tx_link& first_fk, bool positive, bool parallel) NOEXCEPT; /// Get all tx links for any point of block that is also in duplicate table. bool get_doubles(tx_links& out, const block& block) const NOEXCEPT; From bd82e0b1c7676378ffce594386649ffb6486d8f9 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Wed, 22 Jul 2026 00:46:08 -0400 Subject: [PATCH 3/3] Fix debug assert hit by test case. --- test/primitives/linkage.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/primitives/linkage.cpp b/test/primitives/linkage.cpp index f3a52ee16..30cf63e7a 100644 --- a/test/primitives/linkage.cpp +++ b/test/primitives/linkage.cpp @@ -111,7 +111,7 @@ static_assert(linkage<7>{ 42 } == 42_u64); static_assert(linkage<8>{ 42 } == 42_u64); // Offset addition (integer() cast operator for equality test). -static_assert(linkage<0>{ 40 } + 2u == 42_size); +// Size 0 excluded, as its terminal is zero (no row is valid). static_assert(linkage<1>{ 40 } + 2u == 42_u8); static_assert(linkage<2>{ 40 } + 2u == 42_u16); static_assert(linkage<3>{ 40 } + 2u == 42_u32); @@ -365,14 +365,14 @@ BOOST_AUTO_TEST_CASE(linkage__increment__8__expected) // add-assign -BOOST_AUTO_TEST_CASE(linkage__add_assign__0__expected) +BOOST_AUTO_TEST_CASE(linkage__add_assign__4__expected) { - linkage<0> instance{ 0x42 }; + linkage<4> instance{ 0x42010200 }; instance += 1u; - BOOST_REQUIRE_EQUAL(instance, 0x43u); + BOOST_REQUIRE_EQUAL(instance, 0x42010201u); instance += 0x10_size; - BOOST_REQUIRE_EQUAL(instance, 0x53u); + BOOST_REQUIRE_EQUAL(instance, 0x42010211u); } BOOST_AUTO_TEST_CASE(linkage__add_assign__2__expected)