Skip to content

Fix stale target_length when nbits changes on same base_hash#706

Merged
NamecoinGithub merged 2 commits into
MINERfrom
copilot/a44e71ef-3448-4cb2-80a9-f38bed797e7c
May 27, 2026
Merged

Fix stale target_length when nbits changes on same base_hash#706
NamecoinGithub merged 2 commits into
MINERfrom
copilot/a44e71ef-3448-4cb2-80a9-f38bed797e7c

Conversation

Copilot AI commented May 27, 2026

Copy link
Copy Markdown

The sieve rebind gate in PrimeMiningEngine::run_pool_thread only fires on base_hash change. When nbits changes across two same-base templates (possible at difficulty retarget via LLL-TAO's singleflight waiter path), the sieve continues with the stale target_length. Symptom: Best 7.x climbing while bucket 7 stays at 0; rejected_below_diff dominates validate_attempts.

Changes

  • Extend rebind condition — add || my_nbits != bound_session->nbits so the sieve re-prepares when difficulty changes without a Merkle rotation
  • Add reason= to diagnostic log — emits "base", "nbits", or "base+nbits" so the rare nbits-only rebind is greppable in operator logs
  • Centralise derive_target_length(nbits) in mining/prime_thresholds.hpp — single formula (clamp(ceil(nbits / 1e7))) replaces inline derivations in both CPU and GPU engines, preventing future drift
const bool need_rebind = !bound
    || !bound_session
    || my_base_hash != bound_session->base_hash
    || my_nbits != bound_session->nbits;
Original prompt

Context

Deep research session https://github.com/NamecoinGithub/NexusMiner/tasks/a44e71ef-3448-4cb2-80a9-f38bed797e7c identified the root cause of the observed Best 7.14 / slot 7 = 0 symptom under low network difficulty (~6.48):

The engine's target_length rebind is keyed to base_hash, NOT nBits. When nBits changes but base_hash is unchanged (which happens at difficulty retarget events when the chain tip doesn't move, or at the boundary between two same-base-hash templates with different difficulty), the engine misses the invalidation. That distorts slot/funnel behavior — Best keeps updating from the raw sieve, but the slot-counter increment path (which depends on target_length being in sync with the current nBits) drops length-7 candidates as below-difficulty.

The miner's own documentation references this as an older regression at docs/diagrams/mining-loops/prime-mining-flow.md:111-126, with the implementation site at src/cpu/src/cpu/prime/prime_mining_engine.cpp:639-645.

This is observable to operators as:

  • Chain Count: 5:594 6:25 7:0 8:0 9:0 10:0 11:0 Best 7.14 Current Difficulty 6.48
  • rejected_below_diff=614 out of validate_attempts=615 over 19 hours of mining
  • Blocks accepted: 1 rejected: 0 (so the rare share that DOES tick through is valid — the gate isn't broken, just over-rejecting)

Hard constraints

  1. Read the existing code at src/cpu/src/cpu/prime/prime_mining_engine.cpp:639-645 FIRST. The exact symbol names below are inferred from the research report and may need adjustment.
  2. Do NOT change the sieve algorithm, the share submission path, or any networking code. This PR is scoped to the invalidation key only.
  3. Do NOT add a new opcode or change the wire format. This is a pure miner-internal change.
  4. Preserve backward compatibility with single-key callers. If base_hash alone is used elsewhere in the codebase (e.g., for cache lookups, telemetry), the change must not break those.
  5. Open from a new branch off the default branch. Suggested name: copilot/prime-engine-nbits-invalidation-key.

Task

Step 1 — Read and confirm the invalidation site

Open src/cpu/src/cpu/prime/prime_mining_engine.cpp around lines 630-680. Locate:

  • The field that stores the "currently bound base hash" (likely something like m_base_hash, m_current_base, or m_last_base_hash).
  • The field that stores target_length (likely m_target_length or similar).
  • The function that rebinds target_length — probably triggered inside the main mining loop when a new work unit arrives. Look for a comparison like if (new_base_hash != m_base_hash) { rebind_target_length(); m_base_hash = new_base_hash; }.

Also locate where the engine receives nBits from the upstream template path. Search for:

  • nBits field assignments
  • target_length derivation from nBits (typically target_length = bits_to_chain_length(nBits) or similar)

If the actual code structure differs significantly from this description, stop and report rather than guessing. The research report may have referenced an older line range.

Step 2 — Extend the invalidation key

Change the rebind condition from:

if (new_base_hash != m_base_hash) {
    rebind_target_length();
    m_base_hash = new_base_hash;
}

to:

if (new_base_hash != m_base_hash || new_nbits != m_nbits) {
    rebind_target_length();
    m_base_hash = new_base_hash;
    m_nbits = new_nbits;
}

Add a new member m_nbits (or equivalent name matching local conventions) initialized to 0 (or 0xFFFFFFFF, whichever is the "unset" sentinel used elsewhere for nBits in this file). Initialize it in the constructor and in any Reset() / Reinit() method.

Step 3 — Add a diagnostic log line

When the rebind fires due to nBits change only (base_hash unchanged), log it at info level:

debug::log(0, FUNCTION, "[NBITS_REBIND] target_length rebound: nBits ", 
    std::hex, m_nbits, " -> ", new_nbits, std::dec, 
    " base_hash unchanged (",  m_base_hash.SubString(), ")");

When the rebind fires due to base_hash change (the existing path), preserve existing logging behavior — don't add a new line if one already exists. If no log exists, add a symmetric one:

debug::log(0, FUNCTION, "[BASE_REBIND] target_length rebound: base_hash changed (", 
    m_base_hash.SubString(), " -> ", new_base_hash.SubString(), ")");

Match the project's existing debug/log style — if the codebase uses a different logger (spdlog, custom, etc.), use that instead.

Step 4 — Add a regression test if a test harness exists

Check tests/ or test/ directory for existing PrimeMiningEngine tests. If a unit-test harness exists (Catch2, gtest, or similar):

Add a test case that:

  1. Constructs a PrimeMiningEngine.
  2. Submits a work unit with (base_hash=H1, nBits=N1) — assert target_length is bound to derivation-of-N1.
  3. Submits a second work unit with (base_hash=H1, nBits=N2) where N2 != N1 —...

This pull request was created from Copilot chat.

Copilot AI changed the title [WIP] Fix target length rebind logic for network difficulty changes Prime engine: rebind target_length on nBits-only template updates May 27, 2026
Copilot AI requested a review from NamecoinGithub May 27, 2026 09:11
…length helper

Extend PrimeMiningEngine::run_pool_thread rebind gate to also fire when
session->nbits changes without a base_hash change (rare singleflight
waiter race at difficulty retarget).  Without this, the sieve continues
with the previous target_length and silently drops candidates.

Add a reason= field to the rebind diagnostic log so operators can grep
for the nbits-only case.

Centralise the ceil(nbits/1e7) derivation into
mining::derive_target_length() so CPU and GPU cannot drift apart.

Co-authored-by: NamecoinGithub <130555019+NamecoinGithub@users.noreply.github.com>
Copilot AI changed the title Prime engine: rebind target_length on nBits-only template updates Fix stale target_length when nbits changes on same base_hash May 27, 2026
@NamecoinGithub NamecoinGithub marked this pull request as ready for review May 27, 2026 09:55
Copilot AI review requested due to automatic review settings May 27, 2026 09:55
@NamecoinGithub NamecoinGithub merged commit d95c17e into MINER May 27, 2026
0 of 2 checks passed
Copilot AI removed the request for review from Copilot May 27, 2026 10:18
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.

2 participants