From 865fdd78209b9453cd2fc6355f40d7f479144b47 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sun, 21 Jun 2026 05:14:23 +0000 Subject: [PATCH] ltc: couple AutoRatchet mint gate to the work-weighted accept gate The v35->v36 crossing soak (#97) wedged: AutoRatchet activated on a 95%-by-COUNT desired-version tally, but the consensus accept gate (share_check step 2 / p2pool check() data.py:1399) admits a V36 boundary share only when desired-version reaches 60% by WORK over the [9/10*CL, CL] window -- canonical get_desired_version_counts (data.py:2651) weights each share by target_to_average_attempts(target). Under heterogeneous hashrate a small miner set can carry the COUNT past 95% while the work-weighted tail sits below 60%, so the activated node mints a V36 boundary share that every peer rejects, freezing the crossing. Switch AutoRatchet tail guard from flat-count get_desired_version_counts to work-weighted get_desired_version_weights, over the same window at the same 60% threshold the accept gate enforces. Activation now strictly implies the accept rule, so a minted boundary share can never be rejected. Supersedes the earlier F10 keep-activation-flat-count choice, which the soak proved unsafe. Covers LTC and DOGE (merged, rides LTC AutoRatchet). --- src/impl/ltc/auto_ratchet.hpp | 38 +++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/impl/ltc/auto_ratchet.hpp b/src/impl/ltc/auto_ratchet.hpp index bd235ca3a..9adb8f98e 100644 --- a/src/impl/ltc/auto_ratchet.hpp +++ b/src/impl/ltc/auto_ratchet.hpp @@ -140,25 +140,41 @@ class AutoRatchet // (jtoomim rule). Prevents activation before the entire PPLNS // window has transitioned — p2pool check() rejects V36 shares // when the oldest 10% has < 60% signaling. + // WORK-WEIGHTED tail guard (mint<->accept coupling). The + // consensus accept gate (share_check step 2 / p2pool check() + // data.py:1399) keys the 60% switch rule off + // get_desired_version_counts, which in canonical p2pool + // (data.py:2651) weights each share by + // target_to_average_attempts(target) -- i.e. WORK, not a flat + // head-count. AutoRatchet must evaluate the SAME work-weighted + // 60% rule over the SAME [9/10*CL, CL] window before it + // activates; otherwise a 95%-by-COUNT activation can outrun the + // 60%-by-WORK accept gate under heterogeneous hashrate, the node + // mints a V36 boundary share that every peer rejects, and the + // crossing wedges. The weighted tally makes activation strictly + // imply the accept gate, so a minted boundary share can never be + // rejected. uint32_t tail_start = (chain_length * 9) / 10; uint32_t tail_size = chain_length / 10; auto tail_ancestor = tracker.chain.get_nth_parent_key(best_share_hash, tail_start); - auto tail_counts = tracker.get_desired_version_counts(tail_ancestor, tail_size); - - int64_t tail_target = 0, tail_total = 0; - for (auto& [ver, cnt] : tail_counts) { - tail_total += cnt; - if (ver >= target_version_) - tail_target += cnt; + auto tail_weights = tracker.get_desired_version_weights(tail_ancestor, tail_size); + + // mapped_type is the work-weight accumulator (uint288); default 0. + decltype(tail_weights)::mapped_type tail_target{}, tail_total{}; + for (auto& [ver, w] : tail_weights) { + tail_total = tail_total + w; + if (static_cast(ver) >= target_version_) + tail_target = tail_target + w; } - int tail_pct = (tail_total > 0) ? static_cast(tail_target * 100 / tail_total) : 0; + // Canonical: counts.get(VERSION,0) < sum(counts)*60//100 + bool tail_ok = !(tail_target * uint32_t(100) < tail_total * uint32_t(SWITCH_THRESHOLD)); - if (tail_pct < SWITCH_THRESHOLD) { + if (!tail_ok) { static int tail_log = 0; if (tail_log++ % 20 == 0) LOG_INFO << "[AutoRatchet] VOTING: full window " << vote_pct - << "% >= " << ACTIVATION_THRESHOLD << "% but oldest 10% only " - << tail_pct << "% (need " << SWITCH_THRESHOLD << "%) — waiting"; + << "% >= " << ACTIVATION_THRESHOLD << "% but oldest 10% work-weighted V" + << target_version_ << " desire < " << SWITCH_THRESHOLD << "%) — waiting"; // Don't transition yet } else