From 4ba43cc0a54968690fafecd05f2b56cff2895fab Mon Sep 17 00:00:00 2001 From: frstrtr Date: Fri, 19 Jun 2026 13:33:53 +0000 Subject: [PATCH] dgb: track absolute block height in HeaderChain (M3 4c prerequisite) Add tip_height()/next_block_height()/base_height()/set_base_height() to dgb::HeaderChain. Block height is a pure function of base height + appended header count: index i holds absolute height base+i, no separate counter that could drift from m_chain. DGB counts every block of every algo as one height, so height advances on BOTH VALIDATED_SCRYPT and ACCEPTED_CONTINUITY ingests and only those -- a REJECTED header is checked before push_back and never advances. set_base_height seeds a fast-start checkpoint while the chain is empty (default 0 = genesis). This is the next_h source the embedded TemplateBuilder port needs for the BIP34 height push and the subsidy_func(height) lookup that feeds the embedded coinbasevalue SSOT (#207); external-daemon GBT keeps its own authoritative height field. Fenced to src/impl/dgb/. +4 tests into header_chain_test (no new gtest target), 31/31 pass. --- src/impl/dgb/coin/header_chain.hpp | 46 ++++++++++++++ src/impl/dgb/test/header_chain_test.cpp | 81 +++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/src/impl/dgb/coin/header_chain.hpp b/src/impl/dgb/coin/header_chain.hpp index be529a2be..fde409b4f 100644 --- a/src/impl/dgb/coin/header_chain.hpp +++ b/src/impl/dgb/coin/header_chain.hpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -321,6 +322,50 @@ class HeaderChain { uint64_t cumulative_work() const { return m_cumulative_work; } std::size_t size() const { return m_chain.size(); } + // ── Absolute block-height tracking (M3 §4c prerequisite) ──────────────── + // DGB block height counts EVERY block of EVERY algo: one block == one + // height regardless of Scrypt / SHA256d / Skein / Qubit / Odocrypt. The + // Scrypt-only HeaderChain still appends non-Scrypt headers via + // accept-by-continuity (work-neutral but chain-extending), so block height + // advances on BOTH VALIDATED_SCRYPT and ACCEPTED_CONTINUITY ingests and + // ONLY those -- a REJECTED header never push_back()s and never advances + // height (rejection is checked before every push_back above). Height is + // therefore a pure function of the seed height + appended-header count: + // index i holds absolute height (m_base_height + i), with no separate + // mutable counter that could drift from m_chain. + // + // The embedded TemplateBuilder port (mirror of btc build_template) reads + // next_block_height() for the coinbase BIP34 height push and the + // subsidy_func(height) lookup that feeds embedded_coinbase_value (#207). + // External-daemon GBT still carries its own authoritative "height"; this + // accessor is the embedded-path source for the SAME field. + + // Absolute height assigned to m_chain[0] (the seed/genesis or checkpoint + // header). Default 0 == sync-from-genesis; set_base_height() seeds a + // fast-start checkpoint before the first append. + uint32_t base_height() const { return m_base_height; } + + // Seed the absolute height of the FIRST appended header. Must be called + // while the chain is still empty (a checkpoint fast-start); a no-op call + // order otherwise would silently mis-number live headers. + void set_base_height(uint32_t h) { m_base_height = h; } + + // Absolute height of the newest header, or nullopt for an empty chain. + std::optional tip_height() const + { + if (m_chain.empty()) + return std::nullopt; + return static_cast(m_base_height + (m_chain.size() - 1)); + } + + // Absolute height of the block the next template builds on top of the tip + // == tip_height()+1, or m_base_height for an empty chain. This is the + // template builder's `next_h` (btc template_builder.hpp: tip.height + 1). + uint32_t next_block_height() const + { + return static_cast(m_base_height + m_chain.size()); + } + // Bitcoin/DigiByte median-time-past span (ContextualCheckBlockHeader uses // the tip + its 10 nearest ancestors == 11 timestamps). static constexpr std::size_t MEDIAN_TIME_SPAN = 11; @@ -357,6 +402,7 @@ class HeaderChain { } DigiShieldParams m_ds_params{}; // retarget gate params + uint32_t m_base_height = 0; // abs height of m_chain[0] std::size_t m_retarget_window = 0; // Scrypt window depth std::vector m_chain; // oldest .. newest uint64_t m_cumulative_work = 0; diff --git a/src/impl/dgb/test/header_chain_test.cpp b/src/impl/dgb/test/header_chain_test.cpp index 85e850502..e6edf44c1 100644 --- a/src/impl/dgb/test/header_chain_test.cpp +++ b/src/impl/dgb/test/header_chain_test.cpp @@ -646,3 +646,84 @@ TEST(HeaderChainMtp, ContinuityHeaderItselfBypassesMtp) IngestResult::ACCEPTED_CONTINUITY); EXPECT_EQ(hc.size(), 2u); } + + +// --------------------------------------------------------------------------- +// Absolute block-height tracking (M3 §4c prerequisite). Height is the +// embedded TemplateBuilder's next_h source (subsidy_func(height) + BIP34 +// push). DGB counts every block of every algo, so BOTH Scrypt-validated and +// continuity appends advance height; a rejected header never does. +// --------------------------------------------------------------------------- + +TEST(HeaderChainBlockHeight, EmptyChainHasNoTipHeight) +{ + HeaderChain hc; + EXPECT_FALSE(hc.tip_height().has_value()); + EXPECT_EQ(hc.base_height(), 0u); + EXPECT_EQ(hc.next_block_height(), 0u); // genesis is the next block +} + +TEST(HeaderChainBlockHeight, EveryAlgoAdvancesHeight) +{ + HeaderChain hc; + // Scrypt block at height 0. + ASSERT_EQ(hc.validate_and_append({SCRYPT, 1000, 100}), + IngestResult::VALIDATED_SCRYPT); + EXPECT_EQ(hc.tip_height().value(), 0u); + EXPECT_EQ(hc.next_block_height(), 1u); + + // A continuity (non-Scrypt) block STILL occupies a height -- DGB height + // counts all five algos, not just the Scrypt-validated subset. + ASSERT_EQ(hc.validate_and_append({SHA256D, 1500, 100}), + IngestResult::ACCEPTED_CONTINUITY); + EXPECT_EQ(hc.tip_height().value(), 1u); + EXPECT_EQ(hc.next_block_height(), 2u); + + ASSERT_EQ(hc.validate_and_append({SKEIN, 1200, 100}), + IngestResult::ACCEPTED_CONTINUITY); + EXPECT_EQ(hc.tip_height().value(), 2u); + + // Another Scrypt block (time strictly above the MTP of {1000,1500,1200}). + ASSERT_EQ(hc.validate_and_append({SCRYPT, 2000, 100}), + IngestResult::VALIDATED_SCRYPT); + EXPECT_EQ(hc.tip_height().value(), 3u); + EXPECT_EQ(hc.next_block_height(), 4u); + EXPECT_EQ(hc.size(), 4u); +} + +TEST(HeaderChainBlockHeight, RejectedHeaderNeverAdvancesHeight) +{ + HeaderChain hc; + ASSERT_EQ(hc.validate_and_append({SCRYPT, 1000, 100}), + IngestResult::VALIDATED_SCRYPT); + EXPECT_EQ(hc.tip_height().value(), 0u); + + // Malformed Scrypt header (zero target) is rejected -- it must not occupy + // a height. tip/next stay pinned exactly where the last accepted block left + // them, so the embedded coinbasevalue can never be computed for a phantom + // block the chain never accepted. + EXPECT_EQ(hc.validate_and_append({SCRYPT, 1100, 0}), + IngestResult::REJECTED); + EXPECT_EQ(hc.tip_height().value(), 0u); + EXPECT_EQ(hc.next_block_height(), 1u); + EXPECT_EQ(hc.size(), 1u); + + // The next genuinely-valid block takes the height the reject did NOT. + ASSERT_EQ(hc.validate_and_append({SCRYPT, 2000, 100}), + IngestResult::VALIDATED_SCRYPT); + EXPECT_EQ(hc.tip_height().value(), 1u); +} + +TEST(HeaderChainBlockHeight, CheckpointSeedNumbersFirstHeader) +{ + // Fast-start: seed the absolute height of m_chain[0] before any append. + HeaderChain hc; + hc.set_base_height(12345); + EXPECT_FALSE(hc.tip_height().has_value()); + EXPECT_EQ(hc.next_block_height(), 12345u); // the seed block itself + + ASSERT_EQ(hc.validate_and_append({SCRYPT, 1000, 100}), + IngestResult::VALIDATED_SCRYPT); + EXPECT_EQ(hc.tip_height().value(), 12345u); + EXPECT_EQ(hc.next_block_height(), 12346u); +}