Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/impl/dgb/coin/header_chain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include <cstdint>
#include <functional>
#include <limits>
#include <optional>
#include <vector>

#include <impl/dgb/coin/dgb_arith256.hpp>
Expand Down Expand Up @@ -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<uint32_t> tip_height() const
{
if (m_chain.empty())
return std::nullopt;
return static_cast<uint32_t>(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<uint32_t>(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;
Expand Down Expand Up @@ -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<HeaderSample> m_chain; // oldest .. newest
uint64_t m_cumulative_work = 0;
Expand Down
81 changes: 81 additions & 0 deletions src/impl/dgb/test/header_chain_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading