Skip to content

ltc(v36): advertise raw head pre-sync + re-advert with 10s timer (root-2, complete) - #104

Merged
frstrtr merged 1 commit into
masterfrom
ltc-doge/root2-advert-readvert
Jun 16, 2026
Merged

ltc(v36): advertise raw head pre-sync + re-advert with 10s timer (root-2, complete)#104
frstrtr merged 1 commit into
masterfrom
ltc-doge/root2-advert-readvert

Conversation

@frstrtr

@frstrtr frstrtr commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Root-2: genesis-null head advertisement — COMPLETE fix (supersedes #103)

This is the canonical Root-2 fix (commit e01c160, GPG-signed). It supersedes PR #103 (head ltc-doge/root2-genesis-null-advert @ b56bfb2), which carried only the on-think / best-change re-advert — a half-fix that regresses when think() is wedged. Recommend closing #103 in favour of this PR. LTC only; BTC mirror tracked separately by btc-heap-opt.

(a) Bug fixed — genesis-null-advert

On a fresh (--genesis) LTC node the verified share set is empty at the version handshake. best_share_hash() is verified-only and collapses to uint256::ZERO once peers exist (so work/template never builds on a MAX_TARGET head), so the node advertised ZERO in send_version() → the peer never issued download_shares() → the chain failed to propagate.

Fix: send_version() now advertises via advertised_best_share() (raw head: think's current best, else tallest m_chain head, else ZERO at true genesis). best_share_hash() stays verified-only for work/template selection — unchanged.

(b) Re-advertisement — two legs, think-independent

  • Event leg: readvertise_best_share() re-announces on think work-refresh and on best-change (5-deep tip walk, de-dup bypass, try_to_lock, skips rejected shares).
  • Timer leg (point c): m_readvert_timer (core::Timer) is armed lazily once the verified chain first becomes non-empty and fires readvertise_best_share() on a 10s cadence, plus a one-shot delayed re-advert on first non-empty verified chain. This covers the case the event leg cannot — a peer that connects during a long quiescent gap with no head change, and re-advert progress even if think() is wedged.

(c) Timer — IMPLEMENTED, not deviated

Unlike #103 (which deviated to think-cycle cadence only), this branch implements the explicit ~10s re-advert timer the spec called for. The timer arms inside the run_think IO-phase and composes with the already-merged PR #97 lock-yield + think-watchdog (ee906e8), which guarantees think progress → arm.

Livelock-safety — confirmed, no new exclusive lock

No new mutex. advertised_best_share() / readvertise_best_share() are reads; the broadcast path keeps try_to_lock (non-blocking), so nothing on the compute-thread hot path can reintroduce the phase2 livelock signature. +93/-1 node.cpp, +21 node.hpp.

…ert)

On a --genesis node whose verified chain is still empty at the moment of a
version handshake, best_share_hash() returns NULL, so the peer never calls
download_shares() and the broadcast path cannot backfill (our head shares are
already de-dup-marked in m_shared_share_hashes). The two sides deadlock with
Peers connected but zero share exchange.

Decouple advertisement from the verified chain:

- advertised_best_share(): peer-facing only (version handshake + timer
  re-announce). Returns the verified head when present, else the tallest RAW
  head. Never used for work/share creation, so it cannot cause us to build on
  an unagreed chain (best_share_hash() still owns that path unchanged).
- send_version() now advertises advertised_best_share().
- readvertise_best_share(): re-push the tip walk to all peers bypassing the
  de-dup set. Fired from run_think() on best-change, and once on a 10s one-shot
  timer when the verified chain first becomes non-empty, so a peer that
  handshook during the empty window can still ingest our chain.

All advertise paths use the established try_to_lock discipline; the IO thread
never blocks on m_tracker_mutex.
@frstrtr
frstrtr merged commit 68c993f into master Jun 16, 2026
16 checks passed
frstrtr added a commit that referenced this pull request Jun 16, 2026
Mirror the LTC timer leg from #104/e01c1606. The event leg
(readvertise_best on think work-refresh + clean_tracker best-change)
goes dead exactly when think() wedges -- the livelock-adjacent scenario
this fix family targets. Arm a one-shot core::Timer(10s) -> readvertise_best()
in the run_think IO-phase, fired exactly once when the verified chain
first becomes non-empty, so a peer that handshook during the empty
window can still ingest our chain even if a later think() cycle wedges.

Composes with the merged #97 think-watchdog: timer runs on the IO
thread, pure reads, broadcast stays try_to_lock, no new mutex.
frstrtr added a commit that referenced this pull request Jun 16, 2026
…r leg

Mirror the LTC timer leg from #104/e01c1606. The event leg
(readvertise_best on think work-refresh + clean_tracker best-change)
goes dead exactly when think() wedges -- the livelock-adjacent scenario
this fix family targets. Arm a one-shot core::Timer(10s) -> readvertise_best()
in the run_think IO-phase, fired exactly once when the verified chain
first becomes non-empty, so a peer that handshook during the empty
window can still ingest our chain even if a later think() cycle wedges.

Composes with the merged #97 think-watchdog: timer runs on the IO
thread, pure reads, broadcast stays try_to_lock, no new mutex.

Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant