Skip to content
Closed
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
39 changes: 37 additions & 2 deletions src/impl/nmc/coin/header_chain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,41 @@ class HeaderChain {
return it->second;
}

/// P1f: build a block locator for getheaders (BIP 31-style exponential
/// backoff) by walking the tip's ancestry through m_previous_block. Walking
/// parent links always follows the BEST chain we just fork-chose, so the
/// locator stays correct across reorgs without a separate height index.
/// Returns hashes tip..root: dense near the tip, doubling the stride after
/// the first 10 entries, with the chain root always anchoring the list.
std::vector<uint256> get_locator() const {
std::lock_guard<std::mutex> lock(m_mutex);
std::vector<uint256> locator;
if (m_tip.IsNull()) return locator;

// Materialise the tip's ancestry (tip..root) via parent links.
std::vector<uint256> ancestry;
uint256 cursor = m_tip;
while (true) {
auto it = m_index.find(cursor);
if (it == m_index.end()) break; // dangling link -- stop cleanly
ancestry.push_back(cursor);
if (it->second.height == 0) break; // reached chain root
cursor = it->second.header.m_previous_block;
}

// BIP 31-style exponential backoff over the ancestry.
int64_t step = 1;
for (size_t i = 0; i < ancestry.size(); i += static_cast<size_t>(step)) {
locator.push_back(ancestry[i]);
if (locator.size() > 10)
step *= 2;
}
// Guarantee the chain root anchors the locator.
if (locator.back() != ancestry.back())
locator.push_back(ancestry.back());
return locator;
}

/// Add a single plain header.
/// P0-DEFER: NO difficulty validation, NO PoW check, NO connection check,
/// NO persistence. Structural skeleton only — returns false.
Expand Down Expand Up @@ -827,8 +862,8 @@ class HeaderChain {
uint256 m_best_work;

// P0-DEFER: difficulty validation, AuxPow verification, LevelDB
// persistence, height-index rebuild, and locator generation are all
// deliberately absent in this structural leaf.
// persistence and height-index rebuild are deliberately absent in this
// structural leaf (locator generation landed in P1f).
};

} // namespace coin
Expand Down
103 changes: 103 additions & 0 deletions src/impl/nmc/test/auxpow_merkle_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1013,4 +1013,107 @@ TEST(NmcP1eForkChoice, FirstHeaderBecomesTipRegardlessOfPriorState)
EXPECT_EQ(chain_.cumulative_work(), nmc::coin::get_block_proof(kEasyBits));
}

// ── P1f: HeaderChain::get_locator — BIP31 block locator over tip ancestry ──

TEST(NmcP1fLocator, EmptyChainProducesEmptyLocator)
{
HeaderChain chain_(params_activation(19200));
EXPECT_TRUE(chain_.get_locator().empty());
}

TEST(NmcP1fLocator, SingleHeaderLocatorIsJustTheTip)
{
HeaderChain chain_(params_activation(19200));
uint256 z; z.SetNull();
BlockHeaderType g = plain_header(z, kEasyBits, 1);
ASSERT_TRUE(chain_.add_header(g));
auto loc = chain_.get_locator();
ASSERT_EQ(loc.size(), 1u);
EXPECT_EQ(loc.front(), block_hash(g));
}

TEST(NmcP1fLocator, ShortChainLocatorIsDenseTipToRoot)
{
HeaderChain chain_(params_activation(19200));
uint256 z; z.SetNull();
BlockHeaderType g = plain_header(z, kEasyBits, 1);
ASSERT_TRUE(chain_.add_header(g));
BlockHeaderType a1 = plain_header(block_hash(g), kEasyBits, 2);
ASSERT_TRUE(chain_.add_header(a1));
BlockHeaderType a2 = plain_header(block_hash(a1), kEasyBits, 3);
ASSERT_TRUE(chain_.add_header(a2));
auto loc = chain_.get_locator();
ASSERT_EQ(loc.size(), 3u); // dense: every block while <= 10 deep
EXPECT_EQ(loc[0], block_hash(a2)); // tip first
EXPECT_EQ(loc[1], block_hash(a1));
EXPECT_EQ(loc[2], block_hash(g)); // root last
}

TEST(NmcP1fLocator, LongChainFrontIsContiguousAndAnchoredAtRoot)
{
HeaderChain chain_(params_activation(19200));
uint256 z; z.SetNull();
BlockHeaderType g = plain_header(z, kEasyBits, 1);
ASSERT_TRUE(chain_.add_header(g));
std::vector<uint256> hashes{block_hash(g)};
uint256 prev = block_hash(g);
for (uint32_t i = 1; i <= 30; ++i) { // height 0..30 (31 headers)
BlockHeaderType h = plain_header(prev, kEasyBits, i + 1);
ASSERT_TRUE(chain_.add_header(h));
prev = block_hash(h);
hashes.push_back(prev);
}
auto loc = chain_.get_locator();
EXPECT_EQ(chain_.height(), 30u);
// First 11 entries are contiguous from the tip (step stays 1 until >10).
for (size_t i = 0; i < 11u; ++i)
EXPECT_EQ(loc[i], hashes[hashes.size() - 1 - i]);
// Backoff makes the locator far shorter than the chain, root-anchored.
EXPECT_LT(loc.size(), hashes.size());
EXPECT_EQ(loc.back(), block_hash(g));
}

TEST(NmcP1fLocator, BackoffStrideDoublesPastTheDenseHead)
{
HeaderChain chain_(params_activation(19200));
uint256 z; z.SetNull();
BlockHeaderType g = plain_header(z, kEasyBits, 1);
ASSERT_TRUE(chain_.add_header(g));
uint256 prev = block_hash(g);
for (uint32_t i = 1; i <= 30; ++i) {
BlockHeaderType h = plain_header(prev, kEasyBits, i + 1);
ASSERT_TRUE(chain_.add_header(h));
prev = block_hash(h);
}
auto loc = chain_.get_locator();
// No duplicates, strictly descending toward the root, all known to the chain.
for (size_t i = 0; i < loc.size(); ++i)
EXPECT_TRUE(chain_.has_header(loc[i]));
for (size_t i = 1; i < loc.size(); ++i)
EXPECT_NE(loc[i], loc[i - 1]);
}

TEST(NmcP1fLocator, LocatorFollowsTheTipAfterAWorkReorg)
{
HeaderChain chain_(params_activation(19200));
uint256 z; z.SetNull();
BlockHeaderType g = plain_header(z, kEasyBits, 1);
ASSERT_TRUE(chain_.add_header(g));
// Light branch a1<-a2 builds the initial tip.
BlockHeaderType a1 = plain_header(block_hash(g), kEasyBits, 2);
ASSERT_TRUE(chain_.add_header(a1));
BlockHeaderType a2 = plain_header(block_hash(a1), kEasyBits, 3);
ASSERT_TRUE(chain_.add_header(a2));
// One heavy sibling at height 1 reorgs the tip onto branch b.
BlockHeaderType b1 = plain_header(block_hash(g), kHardBits, 4);
ASSERT_TRUE(chain_.add_header(b1));
ASSERT_EQ(chain_.tip()->block_hash, block_hash(b1));
auto loc = chain_.get_locator();
// Locator tracks the NEW tip's ancestry (b1 <- g), not the abandoned a-branch.
ASSERT_EQ(loc.size(), 2u);
EXPECT_EQ(loc[0], block_hash(b1));
EXPECT_EQ(loc[1], block_hash(g));
EXPECT_EQ(std::count(loc.begin(), loc.end(), block_hash(a2)), 0);
}

} // namespace