Skip to content

btc(v36): livelock mirror — lock-yield + think() watchdog (scaffold) - #97

Merged
frstrtr merged 3 commits into
masterfrom
btc/v36-livelock-lockyield-watchdog
Jun 16, 2026
Merged

btc(v36): livelock mirror — lock-yield + think() watchdog (scaffold)#97
frstrtr merged 3 commits into
masterfrom
btc/v36-livelock-lockyield-watchdog

Conversation

@frstrtr

@frstrtr frstrtr commented Jun 15, 2026

Copy link
Copy Markdown
Owner

frstrtr added 3 commits June 15, 2026 23:11
Mirror of the ltc-doge-locked livelock fix shape onto the BTC variant.
Branch-only scaffold; soak freeze in effect, deploy operator-gated.

PRIMARY (lock-yield / bound think): extend the existing cooperative-yield
(needs_continue) mechanism into the v36 decayed-cumulative-weight + PPLNS
scoring walk so the compute thread periodically releases m_tracker_mutex,
drain_pending_adds() runs, and a continuation is re-posted — breaking the
IO-thread starvation. Enforced at a COARSE per-scored-head boundary
(THINK_WALK_YIELD_BUDGET) sized far above a healthy full-window pass so it
never trips on normal load. Exact intra-walk yield point + optional
zero-divisor guard (degenerate target_ratio==0) left as a marked TODO at
get_decayed_cumulative_weights, pending ltc-doge PIE-core symbolization.

SECONDARY (defense-in-depth) think() watchdog: IO-thread steady_timer that
NEVER touches m_tracker_mutex; on a >THINK_WATCHDOG_SECONDS cycle it dumps
the stack, clears the deadline, and resets m_think_running so the pipeline
recovers. Atomic deadline + generation counter guard against late/dup fires.
MAX_PENDING_ADDS caps the deferred queue with backpressure (drop + warn) so
a wedged think() cannot grow memory unbounded.

Touches the try-lock-with-defer twin sites (node.cpp:395 defer path).
Builds clean: c2pool-btc links (EXIT=0).
…nodiscard)

std::shared_lock::try_lock() is [[nodiscard]]; the guard previously called
it then re-derived the outcome via owns_lock(), so the return value was
dropped and -Wunused-result -> -Werror failed btc smoke (node.cpp). Assign
the return directly to ok_ — the same boolean that drives operator bool()
and the IO-thread yield/defer decision — instead of discarding it. Mirrored
in ltc/node.hpp to keep the shared TrackerReadGuard idiom identical.
CI Boost.ASIO (conan2 boostdcfa654164f57) lacks the deprecated
cancel(error_code&) overload; only zero-arg cancel() (returns size_t,
noexcept) exists. Switch to it and drop the local error_code.

ltc mirror unaffected: it has no watchdog_timer; its only cancel() is
the zero-arg socket cancel in coin/p2p_connection.hpp.
@frstrtr
frstrtr merged commit ee906e8 into master Jun 16, 2026
17 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>
frstrtr added a commit that referenced this pull request Jun 21, 2026
…60%-work switch rule

Completes the mint<->accept coupling. The prior commit (27a4463) made the
AutoRatchet MINT guard work-weighted, but BTC share_check step 2 still
admitted a version BOUNDARY via the non-canonical 95%-flat-COUNT
should_punish_version path -- the inverse of the #97 LTC wedge: a
work-weighted mint guard in front of a flat-count accept gate.

Port the canonical accept rule from ltc/share_check.hpp ~1715-1762
(p2pool data.py check() 1396-1414): a boundary share (share.version !=
parent.version) is valid only when, in the window [CHAIN_LENGTH*9/10,
CHAIN_LENGTH] behind the PARENT, the new version holds >= 60% of the
PPLNS-WORK-WEIGHTED desired-version tally (get_desired_version_weights).
same-version always valid; -1 downgrade valid (AutoRatchet V35-after-V36);
any other jump throws "invalid version jump". Removes the
should_punish_version call from the accept path entirely (F10/(b): the 60%
switch rule is now the ONLY version gate, matching ltc/dgb).

Per-coin lane, src/impl/btc/ only. Semantics byte-mirror LTC. Merge held
pending full gh CI rollup CLEAN + BTC crossing re-soak; no self-merge.
frstrtr added a commit that referenced this pull request Jun 21, 2026
…pling

ltc: couple AutoRatchet mint gate to the work-weighted accept gate (fix #97 crossing wedge)
frstrtr added a commit that referenced this pull request Jun 21, 2026
)

* btc: couple AutoRatchet mint gate to the work-weighted accept gate

Mirror of the LTC mint<->accept coupling fix onto the BTC lane. The
v35->v36 crossing soak (#97) wedged on LTC: AutoRatchet activated on a
95%-by-flat-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.

btc/auto_ratchet.hpp carried the byte-identical flat-count tail guard, so
the same latent wedge existed on this SHA256d sharechain. Switch the tail
guard from get_desired_version_counts to a work-weighted tally over the
same [9/10*CL, CL] window at the same 60% threshold the accept gate
enforces, so activation strictly implies the accept rule and a minted
boundary share can never be rejected.

BTC tracker had only get_desired_version_counts, so this also ports
get_desired_version_weights onto btc/share_tracker.hpp (work-weighted via
get_index()->work, mirroring the ltc/dgb tracker shape). Per-coin lane,
btc/ tree only; v36-native consensus-version-signaling standardization.
Merge held pending BTC crossing re-soak.

* btc(#290): replace 95%-flat should_punish accept gate with canonical 60%-work switch rule

Completes the mint<->accept coupling. The prior commit (27a4463) made the
AutoRatchet MINT guard work-weighted, but BTC share_check step 2 still
admitted a version BOUNDARY via the non-canonical 95%-flat-COUNT
should_punish_version path -- the inverse of the #97 LTC wedge: a
work-weighted mint guard in front of a flat-count accept gate.

Port the canonical accept rule from ltc/share_check.hpp ~1715-1762
(p2pool data.py check() 1396-1414): a boundary share (share.version !=
parent.version) is valid only when, in the window [CHAIN_LENGTH*9/10,
CHAIN_LENGTH] behind the PARENT, the new version holds >= 60% of the
PPLNS-WORK-WEIGHTED desired-version tally (get_desired_version_weights).
same-version always valid; -1 downgrade valid (AutoRatchet V35-after-V36);
any other jump throws "invalid version jump". Removes the
should_punish_version call from the accept path entirely (F10/(b): the 60%
switch rule is now the ONLY version gate, matching ltc/dgb).

Per-coin lane, src/impl/btc/ only. Semantics byte-mirror LTC. Merge held
pending full gh CI rollup CLEAN + BTC crossing re-soak; no self-merge.

---------

Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
frstrtr added a commit that referenced this pull request Jun 23, 2026
Mirror of LTC v36 commit 18dd945 onto the BTC variant. All three gentx
sites (generate_share_transaction / create_local_share_v35 /
create_local_share) now exclude BOTH donation scripts (COMBINED + P2PK)
from per-miner payout dests and fold the COMBINED_DONATION_SCRIPT-keyed
weight into the single donation-last output. Rides core/donation.hpp SSOT
(FLAG6 #139). No F1 entanglement (F11 touches only the payout-sort path).

Prep-branch staging; PR stays unopened until integrator dispatches the
F-mirror post crossing-soak (#97). +51/-6, matches LTC F11 line counts.
frstrtr added a commit that referenced this pull request Jun 23, 2026
…te (contract)

Mirrors the intent of ltc 865fdd7 onto the DGB lane. ltc-doge root-caused
the v35->v36 crossing wedge (#97): the AutoRatchet mint gate activated on a
95%-by-flat-COUNT desired-version tally, but the consensus accept gate
(share_check step 2 / p2pool check() data.py:1399) admits a V36 boundary
share only at the WORK-WEIGHTED 60% rule (canonical get_desired_version_counts,
data.py:2651, weights each share by target_to_average_attempts). Under
heterogeneous hashrate the flat count outran the work-weighted accept gate,
so the minter produced boundary shares every peer rejected -> frozen crossing.

DGB carries NO AutoRatchet minter yet (no src/impl/dgb/auto_ratchet.hpp; the
minter lands in Phase B). The DGB accept gate (share_check.hpp version-switch
rule) is ALREADY work-weighted/canonical via get_desired_version_weights, and
that accessor is KAT-pinned (#249). The ONLY artifact of the unsafe F10
flat-count choice on this lane is the design-contract recorded in comments,
which would seed the exact wedge into the future port. This corrects those
comments so DGB's AutoRatchet is born work-weighted: its activation tail guard
MUST read get_desired_version_weights over the same [9/10*CL, CL] window at the
same 60% threshold the accept gate enforces, superseding F10.

v36-native shared structure (consensus version-signaling), standardized
cross-coin for a clean v37 migration. Comment-only; zero behavior change (no
minter exists to alter). Per-coin isolation preserved: touches src/impl/dgb/ only.
frstrtr added a commit that referenced this pull request Jun 23, 2026
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).
frstrtr added a commit that referenced this pull request Jun 23, 2026
…pling

ltc: couple AutoRatchet mint gate to the work-weighted accept gate (fix #97 crossing wedge)
frstrtr added a commit that referenced this pull request Jun 23, 2026
)

* btc: couple AutoRatchet mint gate to the work-weighted accept gate

Mirror of the LTC mint<->accept coupling fix onto the BTC lane. The
v35->v36 crossing soak (#97) wedged on LTC: AutoRatchet activated on a
95%-by-flat-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.

btc/auto_ratchet.hpp carried the byte-identical flat-count tail guard, so
the same latent wedge existed on this SHA256d sharechain. Switch the tail
guard from get_desired_version_counts to a work-weighted tally over the
same [9/10*CL, CL] window at the same 60% threshold the accept gate
enforces, so activation strictly implies the accept rule and a minted
boundary share can never be rejected.

BTC tracker had only get_desired_version_counts, so this also ports
get_desired_version_weights onto btc/share_tracker.hpp (work-weighted via
get_index()->work, mirroring the ltc/dgb tracker shape). Per-coin lane,
btc/ tree only; v36-native consensus-version-signaling standardization.
Merge held pending BTC crossing re-soak.

* btc(#290): replace 95%-flat should_punish accept gate with canonical 60%-work switch rule

Completes the mint<->accept coupling. The prior commit (27a4463) made the
AutoRatchet MINT guard work-weighted, but BTC share_check step 2 still
admitted a version BOUNDARY via the non-canonical 95%-flat-COUNT
should_punish_version path -- the inverse of the #97 LTC wedge: a
work-weighted mint guard in front of a flat-count accept gate.

Port the canonical accept rule from ltc/share_check.hpp ~1715-1762
(p2pool data.py check() 1396-1414): a boundary share (share.version !=
parent.version) is valid only when, in the window [CHAIN_LENGTH*9/10,
CHAIN_LENGTH] behind the PARENT, the new version holds >= 60% of the
PPLNS-WORK-WEIGHTED desired-version tally (get_desired_version_weights).
same-version always valid; -1 downgrade valid (AutoRatchet V35-after-V36);
any other jump throws "invalid version jump". Removes the
should_punish_version call from the accept path entirely (F10/(b): the 60%
switch rule is now the ONLY version gate, matching ltc/dgb).

Per-coin lane, src/impl/btc/ only. Semantics byte-mirror LTC. Merge held
pending full gh CI rollup CLEAN + BTC crossing re-soak; no self-merge.

---------

Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
frstrtr added a commit that referenced this pull request Jun 23, 2026
Mirror of LTC v36 commit 18dd945 onto the BTC variant. All three gentx
sites (generate_share_transaction / create_local_share_v35 /
create_local_share) now exclude BOTH donation scripts (COMBINED + P2PK)
from per-miner payout dests and fold the COMBINED_DONATION_SCRIPT-keyed
weight into the single donation-last output. Rides core/donation.hpp SSOT
(FLAG6 #139). No F1 entanglement (F11 touches only the payout-sort path).

Prep-branch staging; PR stays unopened until integrator dispatches the
F-mirror post crossing-soak (#97). +51/-6, matches LTC F11 line counts.
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