From 2418379c0cd548330ca2a2be51ddc1cfadfcec25 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Sat, 18 Jul 2026 16:41:29 -0400 Subject: [PATCH 1/5] Optimize (minor) interval_span(). --- include/bitcoin/database/impl/query/merkle.ipp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/bitcoin/database/impl/query/merkle.ipp b/include/bitcoin/database/impl/query/merkle.ipp index c68292953..ac461235d 100644 --- a/include/bitcoin/database/impl/query/merkle.ipp +++ b/include/bitcoin/database/impl/query/merkle.ipp @@ -282,13 +282,13 @@ code CLASS::get_merkle_subroots(hashes& roots, size_t waypoint) const NOEXCEPT TEMPLATE size_t CLASS::interval_span() const NOEXCEPT { - if (const auto span = span_.load(std::memory_order_relaxed); - is_nonzero(span)) - return span; + auto span = span_.load(std::memory_order_relaxed); // initialize_span() never returns zero. - span_.store(initialize_span(), std::memory_order_relaxed); - return span_; + if (is_zero(span)) + span_.store(span = initialize_span(), std::memory_order_relaxed); + + return span; } // protected From 0eb04ab046b6cc031db4ec7d168452fb4b580cda Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Sat, 18 Jul 2026 16:41:52 -0400 Subject: [PATCH 2/5] Add fork_flags setting. --- include/bitcoin/database/settings.hpp | 5 ++++- test/settings.cpp | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/bitcoin/database/settings.hpp b/include/bitcoin/database/settings.hpp index 6e5096668..407e59ba9 100644 --- a/include/bitcoin/database/settings.hpp +++ b/include/bitcoin/database/settings.hpp @@ -45,9 +45,12 @@ struct BCD_API settings /// Mark blocks as unconfirmable (disorganize vs. stall). bool mark_unconfirmable{ true }; - /// Depth of electrum merkle tree interval caching. + /// Depth of merkle tree interval caching (used at store create only). uint16_t interval_depth{ max_uint8 }; + /// Fork flags upon store creation (used at store create only). + uint32_t fork_flags{}; + /// Path to the database directory. std::filesystem::path path{ "bitcoin" }; diff --git a/test/settings.cpp b/test/settings.cpp index 5d13898ca..6a47f9130 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -26,6 +26,7 @@ BOOST_AUTO_TEST_CASE(settings__construct__default__expected) BOOST_REQUIRE_EQUAL(configuration.turbo, false); BOOST_REQUIRE_EQUAL(configuration.mark_unconfirmable, true); BOOST_REQUIRE_EQUAL(configuration.interval_depth, 255u); + BOOST_REQUIRE_EQUAL(configuration.fork_flags, 0u); BOOST_REQUIRE_EQUAL(configuration.path, "bitcoin"); // Archives. From 835ab86bf7b69e0222146e79f6bd37f657ef27a5 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Sat, 18 Jul 2026 16:42:22 -0400 Subject: [PATCH 3/5] Add store fork_flags() setting reader. --- include/bitcoin/database/impl/store/store.ipp | 6 ++++++ include/bitcoin/database/store.hpp | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/bitcoin/database/impl/store/store.ipp b/include/bitcoin/database/impl/store/store.ipp index a0313b25c..3e4edab3a 100644 --- a/include/bitcoin/database/impl/store/store.ipp +++ b/include/bitcoin/database/impl/store/store.ipp @@ -165,6 +165,12 @@ uint8_t CLASS::interval_depth() const NOEXCEPT return system::limit(configuration_.interval_depth); } +TEMPLATE +uint32_t CLASS::fork_flags() const NOEXCEPT +{ + return configuration_.fork_flags; +} + TEMPLATE bool CLASS::is_dirty() const NOEXCEPT { diff --git a/include/bitcoin/database/store.hpp b/include/bitcoin/database/store.hpp index 69a338ae6..94dea7e18 100644 --- a/include/bitcoin/database/store.hpp +++ b/include/bitcoin/database/store.hpp @@ -91,9 +91,12 @@ class store /// Mark blocks as unconfirmable (disorganize vs. stall), configuration. bool mark_unconfirmable() const NOEXCEPT; - /// Depth of electrum merkle tree interval caching. + /// Depth of electrum merkle tree interval caching upon store creation. uint8_t interval_depth() const NOEXCEPT; + /// Fork flags upon store creation. + uint32_t fork_flags() const NOEXCEPT; + /// Determine if the store is non-empty/initialized. bool is_dirty() const NOEXCEPT; void set_dirty() NOEXCEPT; From eec692f4b72023c8c77e39234498e536028e2af4 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Sat, 18 Jul 2026 17:23:50 -0400 Subject: [PATCH 4/5] Add fork flags to configured genesis state. --- .../impl/query/archive/wire_writer.ipp | 6 +- .../bitcoin/database/tables/archives/txs.hpp | 72 ++++++++++++++++--- test/query/archive/chain_writer.cpp | 10 +-- test/query/extent.cpp | 2 +- test/tables/archives/txs.cpp | 12 +++- 5 files changed, 83 insertions(+), 19 deletions(-) diff --git a/include/bitcoin/database/impl/query/archive/wire_writer.ipp b/include/bitcoin/database/impl/query/archive/wire_writer.ipp index 156bf3f31..dade2b230 100644 --- a/include/bitcoin/database/impl/query/archive/wire_writer.ipp +++ b/include/bitcoin/database/impl/query/archive/wire_writer.ipp @@ -263,8 +263,9 @@ code CLASS::set_code(const block_view& block, const header_link& key, // Optional hash, only has value on height intervals. auto interval = create_interval(key, height); - // Depth is only set by writer for genesis (is_zero(tx_fks[0])). + // Depth/forks only set by writer for genesis (is_zero(tx_fks[0])). const auto depth = store_.interval_depth(); + const auto forks = store_.fork_flags(); using bytes = linkage::integer; const auto light = possible_narrow_cast( @@ -290,7 +291,8 @@ code CLASS::set_code(const block_view& block, const header_link& key, count, tx_fks, std::move(interval), - depth + depth, + forks }) ? error::success : error::txs_txs_put; // ======================================================================== } diff --git a/include/bitcoin/database/tables/archives/txs.hpp b/include/bitcoin/database/tables/archives/txs.hpp index 6825736a6..3c31dbba7 100644 --- a/include/bitcoin/database/tables/archives/txs.hpp +++ b/include/bitcoin/database/tables/archives/txs.hpp @@ -40,6 +40,7 @@ struct txs : public array_map { using ct = linkage; + using flags = linkage; using tx = schema::transaction::link; using keys = std::vector; using bytes = linkage; @@ -83,7 +84,7 @@ struct txs return system::possible_narrow_cast( skip_sizes + ct::size + (tx_fks.size() * tx::size) + (interval.has_value() ? schema::hash : zero) + - to_int(is_genesis())); + (is_genesis() ? one + flags::size : zero)); } inline bool from_data(reader& source) NOEXCEPT @@ -104,8 +105,13 @@ struct txs interval.reset(); if (is_interval(merged)) interval = source.read_hash(); - // depth (genesis only) - depth = is_genesis() ? source.read_byte() : zero; + // depth/forks (genesis only) + if (is_genesis()) + { + depth = source.read_byte(); + forks = source.read_little_endian(); + } + BC_ASSERT(!source || source.get_read_position() == count()); return source; } @@ -131,8 +137,13 @@ struct txs // interval (when specified) if (interval.has_value()) sink.write_bytes(interval.value()); - // depth (genesis only) - if (is_genesis()) sink.write_byte(depth); + // depth/forks (genesis only) + if (is_genesis()) + { + sink.write_byte(depth); + sink.write_little_endian(forks); + } + BC_ASSERT(!sink || sink.get_write_position() == count()); return sink; } @@ -143,7 +154,8 @@ struct txs && heavy == other.heavy && tx_fks == other.tx_fks && interval == other.interval - && depth == other.depth; + && depth == other.depth + && forks == other.forks; } bytes::integer light{}; @@ -151,6 +163,7 @@ struct txs keys tx_fks{}; hash interval{}; uint8_t depth{}; + flags::integer forks{}; }; // put a contiguous set of tx identifiers. @@ -167,7 +180,7 @@ struct txs return system::possible_narrow_cast( skip_sizes + ct::size + (number * tx::size) + (interval.has_value() ? schema::hash : zero) + - to_int(is_zero(tx_fk))); + (is_genesis() ? one + flags::size : zero)); } inline bool to_data(finalizer& sink) const NOEXCEPT @@ -185,8 +198,13 @@ struct txs // interval (when specified) if (interval.has_value()) sink.write_bytes(interval.value()); - // depth (genesis only) - if (is_genesis()) sink.write_byte(depth); + // depth/forks (genesis only) + if (is_genesis()) + { + sink.write_byte(depth); + sink.write_little_endian(forks); + } + BC_ASSERT(!sink || sink.get_write_position() == count()); return sink; } @@ -197,6 +215,7 @@ struct txs tx::integer tx_fk{}; hash interval{}; uint8_t depth{}; + flags::integer forks{}; }; struct get_interval @@ -259,6 +278,41 @@ struct txs uint8_t depth{}; }; + // This reader is only applicable to the genesis block. + struct get_genesis_forks + : public schema::txs + { + inline link count() const NOEXCEPT + { + BC_ASSERT(false); + return {}; + } + + // Stored at end since only read once (at startup). + inline bool from_data(reader& source) NOEXCEPT + { + // tx sizes + const auto merged = source.read_little_endian(); + source.skip_bytes(bytes::size); + + // tx fks + const auto number = source.read_little_endian(); + source.skip_bytes(number * tx::size); + + // interval + source.skip_bytes(is_interval(merged) ? schema::hash : zero); + + // depth + source.skip_byte(); + + // forks + forks = source.read_little_endian(); + return source; + } + + flags::integer forks{}; + }; + struct get_position : public schema::txs { diff --git a/test/query/archive/chain_writer.cpp b/test/query/archive/chain_writer.cpp index eb841e940..4d190a7bf 100644 --- a/test/query/archive/chain_writer.cpp +++ b/test/query/archive/chain_writer.cpp @@ -516,7 +516,7 @@ BOOST_AUTO_TEST_CASE(query_chain_writer__set_block__get_block__expected) "4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" // script "00"); // witness const auto genesis_txs_head = system::base16_chunk( - "0d00000000" // slab size + "1100000000" // slab size "0000000000" // pk-> "ffffffffff" "ffffffffff" @@ -538,7 +538,8 @@ BOOST_AUTO_TEST_CASE(query_chain_writer__set_block__get_block__expected) "1d0100" // size heavy (285) "0100" // txs count (1) "00000000" // transaction[0] - "ff"); // depth (255) + "ff" // depth (255) - genesis only + "00000000"); // forks (0) - genesis only settings settings{}; settings.header_buckets = 8; @@ -664,7 +665,7 @@ BOOST_AUTO_TEST_CASE(query_chain_writer__set_block_txs__get_block__expected) "4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" // script "00"); // witness const auto genesis_txs_head = system::base16_chunk( - "0d00000000" // slab size + "1100000000" // slab size "0000000000" // pk-> "ffffffffff" "ffffffffff" @@ -686,7 +687,8 @@ BOOST_AUTO_TEST_CASE(query_chain_writer__set_block_txs__get_block__expected) "1d0100" // size heavy (285) "0100" // txs count (1) "00000000" // transaction[0] - "ff"); // depth (255) + "ff" // depth (255) - genesis only + "00000000"); // forks (0) - genesis only settings settings{}; settings.header_buckets = 8; diff --git a/test/query/extent.cpp b/test/query/extent.cpp index 68d165fcd..f3f48333b 100644 --- a/test/query/extent.cpp +++ b/test/query/extent.cpp @@ -45,7 +45,7 @@ BOOST_AUTO_TEST_CASE(query_extent__body_sizes__genesis__expected) BOOST_REQUIRE_EQUAL(query.point_body_size(), schema::point::minrow); BOOST_REQUIRE_EQUAL(query.ins_body_size(), schema::ins::minrow); BOOST_REQUIRE_EQUAL(query.outs_body_size(), schema::outs::minrow); - BOOST_REQUIRE_EQUAL(query.txs_body_size(), add1(schema::txs::minrow)); + BOOST_REQUIRE_EQUAL(query.txs_body_size(), schema::txs::minrow + one + schema::flags); BOOST_REQUIRE_EQUAL(query.tx_body_size(), schema::transaction::minrow); BOOST_REQUIRE_EQUAL(query.candidate_body_size(), schema::height::minrow); diff --git a/test/tables/archives/txs.cpp b/test/tables/archives/txs.cpp index f629dfd40..1981a4988 100644 --- a/test/tables/archives/txs.cpp +++ b/test/tables/archives/txs.cpp @@ -33,8 +33,9 @@ const table::txs::slab slab0 // tx fk 0 uniquely identifies genesis, resulting in depth storage. 0x00000000_u32 }, - {}, // interval (unused due to default span) - 0x42 // depth (genesis only) + {}, // interval (unused due to default span) + 0x42, // depth (genesis only) + 0x12345678 // flags (genesis only) }; const table::txs::slab slab1 { @@ -81,7 +82,10 @@ const data_chunk expected0 0x00, 0x00, 0x00, 0x00, // depth (genesis) - 0x42 + 0x42, + + // flags (genesis) + 0x78, 0x56, 0x34, 0x12 }; const data_chunk expected1 { @@ -147,6 +151,8 @@ BOOST_AUTO_TEST_CASE(txs__put__get__expected) BOOST_CHECK(slab == slab0); BOOST_CHECK_EQUAL(body_store.buffer(), build_chunk({ expected0 })); + slab.depth = {}; + slab.forks = {}; BOOST_CHECK(instance.put(key, slab1)); BOOST_CHECK(instance.exists(key)); BOOST_CHECK(instance.at(key, slab)); From 162152d11fa8d8c2a29f1b318daed40be2425e77 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Sat, 18 Jul 2026 17:55:38 -0400 Subject: [PATCH 5/5] Add initialized_forks() query. --- include/bitcoin/database/impl/query/initialize.ipp | 8 ++++++++ include/bitcoin/database/query.hpp | 3 +++ 2 files changed, 11 insertions(+) diff --git a/include/bitcoin/database/impl/query/initialize.ipp b/include/bitcoin/database/impl/query/initialize.ipp index 4e02bf202..d3cb0c56f 100644 --- a/include/bitcoin/database/impl/query/initialize.ipp +++ b/include/bitcoin/database/impl/query/initialize.ipp @@ -29,6 +29,14 @@ namespace database { // Initialization (natural-keyed). // ---------------------------------------------------------------------------- +// server (warning) +TEMPLATE +uint32_t CLASS::initialized_forks() const NOEXCEPT +{ + table::txs::get_genesis_forks txs{}; + return store_.txs.at(to_txs(0), txs) ? txs.forks : store_.fork_flags(); +} + // server/dumps TEMPLATE hash_digest CLASS::get_top_confirmed_hash() const NOEXCEPT diff --git a/include/bitcoin/database/query.hpp b/include/bitcoin/database/query.hpp index 3295bda40..28fa256f7 100644 --- a/include/bitcoin/database/query.hpp +++ b/include/bitcoin/database/query.hpp @@ -103,6 +103,9 @@ class query /// Count of puts not resulting in table body search to detect duplication. size_t negative_search_count() const NOEXCEPT; + /// Forks configured at store creation. + uint32_t initialized_forks() const NOEXCEPT; + /// Store extent. /// -----------------------------------------------------------------------