@@ -140,25 +140,41 @@ class AutoRatchet
140140 // (jtoomim rule). Prevents activation before the entire PPLNS
141141 // window has transitioned — p2pool check() rejects V36 shares
142142 // when the oldest 10% has < 60% signaling.
143+ // WORK-WEIGHTED tail guard (mint<->accept coupling). The
144+ // consensus accept gate (share_check step 2 / p2pool check()
145+ // data.py:1399) keys the 60% switch rule off
146+ // get_desired_version_counts, which in canonical p2pool
147+ // (data.py:2651) weights each share by
148+ // target_to_average_attempts(target) -- i.e. WORK, not a flat
149+ // head-count. AutoRatchet must evaluate the SAME work-weighted
150+ // 60% rule over the SAME [9/10*CL, CL] window before it
151+ // activates; otherwise a 95%-by-COUNT activation can outrun the
152+ // 60%-by-WORK accept gate under heterogeneous hashrate, the node
153+ // mints a V36 boundary share that every peer rejects, and the
154+ // crossing wedges. The weighted tally makes activation strictly
155+ // imply the accept gate, so a minted boundary share can never be
156+ // rejected.
143157 uint32_t tail_start = (chain_length * 9 ) / 10 ;
144158 uint32_t tail_size = chain_length / 10 ;
145159 auto tail_ancestor = tracker.chain .get_nth_parent_key (best_share_hash, tail_start);
146- auto tail_counts = tracker.get_desired_version_counts (tail_ancestor, tail_size);
147-
148- int64_t tail_target = 0 , tail_total = 0 ;
149- for (auto & [ver, cnt] : tail_counts) {
150- tail_total += cnt;
151- if (ver >= target_version_)
152- tail_target += cnt;
160+ auto tail_weights = tracker.get_desired_version_weights (tail_ancestor, tail_size);
161+
162+ // mapped_type is the work-weight accumulator (uint288); default 0.
163+ decltype (tail_weights)::mapped_type tail_target{}, tail_total{};
164+ for (auto & [ver, w] : tail_weights) {
165+ tail_total = tail_total + w;
166+ if (static_cast <int64_t >(ver) >= target_version_)
167+ tail_target = tail_target + w;
153168 }
154- int tail_pct = (tail_total > 0 ) ? static_cast <int >(tail_target * 100 / tail_total) : 0 ;
169+ // Canonical: counts.get(VERSION,0) < sum(counts)*60//100
170+ bool tail_ok = !(tail_target * uint32_t (100 ) < tail_total * uint32_t (SWITCH_THRESHOLD ));
155171
156- if (tail_pct < SWITCH_THRESHOLD ) {
172+ if (!tail_ok ) {
157173 static int tail_log = 0 ;
158174 if (tail_log++ % 20 == 0 )
159175 LOG_INFO << " [AutoRatchet] VOTING: full window " << vote_pct
160- << " % >= " << ACTIVATION_THRESHOLD << " % but oldest 10% only "
161- << tail_pct << " % (need " << SWITCH_THRESHOLD << " %) — waiting" ;
176+ << " % >= " << ACTIVATION_THRESHOLD << " % but oldest 10% work-weighted V "
177+ << target_version_ << " desire < " << SWITCH_THRESHOLD << " %) — waiting" ;
162178 // Don't transition yet
163179 }
164180 else
0 commit comments