From 659b95c281e10655f047d1bd974bd19971747b43 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Tue, 16 Jun 2026 08:32:50 +0000 Subject: [PATCH] btc(v36): advertise raw head at handshake + re-advertise on best-change (root-2) On a --genesis node the verified share set is empty at the version handshake, so send_version advertised best_share_hash()==ZERO and the connecting peer never issued download_shares() for our chain. Add advertised_best_share(): returns think()s current best, else the tallest raw chain head, else ZERO only at true genesis. Wire it into send_version so a peer always learns we have a chain and pulls it, while work/template selection stays on the verified-only best_share_hash(). Re-advertise the head via readvertise_best() on every successful think IO-phase work refresh and on clean_tracker best-change, so a peer that handshook during the verified-empty window still gets our head. Guarded on non-empty peers and non-ZERO head; broadcast_share keeps its try_to_lock so this cannot reintroduce the think/broadcast livelock. BTC mirror of LTC b56bfb28 (ltc-doge/root2-genesis-null-advert). --- src/impl/btc/node.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++- src/impl/btc/node.hpp | 18 +++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/impl/btc/node.cpp b/src/impl/btc/node.cpp index 50d69fbeb..cfd0aecfd 100644 --- a/src/impl/btc/node.cpp +++ b/src/impl/btc/node.cpp @@ -256,7 +256,7 @@ void NodeImpl::send_version(peer_ptr peer) m_nonce, m_software_version, 1, // mode (always 1 for legacy compat) - best_share_hash() // advertise our tallest chain head + advertised_best_share() // verified head, or raw head pre-sync (ROOT-2) ); peer->write(std::move(rmsg)); } @@ -901,6 +901,49 @@ uint256 NodeImpl::best_share_hash() return best; } +// Head advertised to peers in the version handshake and re-advertisement ONLY. +// Unlike best_share_hash() -- which is verified-only and collapses to ZERO once +// peers exist so work/template never builds on a MAX_TARGET head -- this returns +// our tallest RAW chain head. Root-2: on a fresh (--genesis) node the verified +// set is empty at handshake, so best_share_hash() advertised ZERO and the peer +// never issued download_shares() for our shares. Advertising the raw head makes +// a connecting peer always learn we have a chain and pull it, while work/template +// stays on the verified head via best_share_hash(). +uint256 NodeImpl::advertised_best_share() +{ + // Prefer think()s current best (verified or not) -- our canonical head. + if (!m_best_share_hash.IsNull()) + return m_best_share_hash; + + // Otherwise the tallest raw chain head (mirrors the genesis fallback above). + if (m_chain && m_chain->size() > 0) { + uint256 best; + int32_t best_height = -1; + for (const auto& [head_hash, tail_hash] : m_chain->get_heads()) { + auto h = m_chain->get_height(head_hash); + if (h > best_height) { + best = head_hash; + best_height = h; + } + } + if (!best.IsNull()) + return best; + } + + // True genesis: no shares at all yet -- advertise ZERO exactly as before. + return uint256::ZERO; +} + +void NodeImpl::readvertise_best() +{ + if (m_peers.empty()) + return; + uint256 adv = advertised_best_share(); + if (adv.IsNull() || adv == uint256::ZERO) + return; + broadcast_share(adv); +} + void NodeImpl::download_shares(peer_ptr /*unused_peer*/, const uint256& target_hash) { // p2pool node.py:108-141 download_shares() — exact translation. @@ -1571,6 +1614,10 @@ void NodeImpl::run_think() auto wr_ms = std::chrono::duration_cast( std::chrono::steady_clock::now() - wr_t0).count(); LOG_INFO << "[ASYNC-THINK] IO-phase: work refresh done in " << wr_ms << "ms"; + // Root-2: re-advertise our new head. A peer that handshook + // before we had a chain saw a ZERO advert and never issued + // download_shares -- re-announce so it pulls our shares now. + readvertise_best(); } else if (result.best.IsNull()) { LOG_WARNING << "[ASYNC-THINK] IO-phase: result.best is NULL — verified_tails=" << m_tracker.verified.get_tails().size() @@ -2080,6 +2127,7 @@ void NodeImpl::clean_tracker() if (clean_best_changed && m_on_best_share_changed) { LOG_INFO << "[CLEAN] IO-phase: work refresh (best changed)"; m_on_best_share_changed(); + readvertise_best(); // root-2: re-announce new head to peers } drain_pending_adds(); m_think_running.store(false); diff --git a/src/impl/btc/node.hpp b/src/impl/btc/node.hpp index 15b11e937..8c87260c2 100644 --- a/src/impl/btc/node.hpp +++ b/src/impl/btc/node.hpp @@ -323,13 +323,28 @@ class NodeImpl : public pool::BaseNode /// Broadcast a locally-generated (or newly-received) share to all peers. void broadcast_share(const uint256& share_hash); + /// Re-announce our current advertised head (advertised_best_share()) to all + /// connected peers. Invoked when think()/clean updates the best share so a + /// peer that completed the version handshake before we had any shares (when + /// our advert was ZERO) still learns our head and pulls via download_shares. + void readvertise_best(); + /// Start downloading shares from a peer, beginning at `target_hash`. /// Recursively fetches parents until the chain is connected or CHAIN_LENGTH reached. void download_shares(peer_ptr peer, const uint256& target_hash); - /// Return the hash of our tallest chain head, or uint256::ZERO if empty. + /// Return the best VERIFIED share head for work/template selection, or + /// uint256::ZERO when no verified chain exists yet and peers are connected + /// (so work never builds on a MAX_TARGET head). NOT for the wire advert — + /// use advertised_best_share() there. uint256 best_share_hash(); + /// Head advertised to peers (version handshake + re-advertisement ONLY). + /// Returns our tallest RAW chain head so a connecting peer always learns we + /// have a chain and pulls it; unlike best_share_hash() it does not collapse + /// to ZERO once peers exist. Root-2 genesis-null-advert fix. + uint256 advertised_best_share(); + /// Load persisted shares from LevelDB storage into the tracker. void load_persisted_shares(); void flush_verified_to_leveldb(); @@ -537,6 +552,7 @@ class NodeImpl : public pool::BaseNode // Cached best share hash from the most recent think() cycle uint256 m_best_share_hash; + // Cache of original raw serialized bytes keyed by share hash. // Used for relay so we send the exact bytes we received, avoiding // any round-trip serialization differences.