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.