dash(stratum): firmware-safe power-of-two vardiff quantization (v0.2.3.6) [stacks on #770]#773
Merged
Merged
Conversation
frstrtr
marked this pull request as ready for review
July 20, 2026 14:55
…ssued-diff hashrate credit Two consensus-neutral stratum fixes for the mining-hotel DASH node. Both touch only the per-connection vardiff/pseudoshare path and the template-refresh/notify path; get_share_bits/pool share_bits, the DGW retarget, the sharechain share target, and block-acceptance are untouched. FIX 1 -- fallback-arm event-driven tip refresh. On the dashd-fallback arm there is no tip-change signal like the embedded arm's header-chain tip-changed callback; the template cache only re-sources on a 30 s staleness TTL. DASH blocks arrive ~every 150 s, so up to ~30 s of stale-tip mining can occur per block (accepted pseudoshares that can no longer win the current block). Add a lightweight run-path poller (gated on the fallback arm: coin-P2P null + armed rpc + live stratum acceptor) that reads dashd getbestblockhash every 3 s; on a tip change it drops the cached template + bumps work-generation + notify_all() -- the SAME refresh pair the embedded arm fires. Reuses the existing NodeRPC client (new trivial getbestblockhash method); RPC failures are swallowed so a daemon hiccup never crashes the run-loop. FIX 2 -- credit the hashrate EWMA + RateMonitor at the difficulty the job was issued/mined at (job.issued_difficulty), not the live vardiff, so a vardiff retarget between job-issue and submit does not skew the estimate. Falls back to the live vardiff when issued_difficulty is unset. Estimate/stats only; the acceptance gate is unchanged. Extends test_dash_stratum_work_source with a KAT pinning the fallback-arm tip-change refresh contract (template swaps to the new tip + work generation bumps -- the clean_jobs/notify signal). The target is already in both build.yml --target allowlists, so CI discovery is unchanged.
…re-safe) ASIC firmwares round the advertised pool difficulty DOWN to a power-of-two grid, mine that easier target, then submit shares between the rig's rounded target and the pool's exact required difficulty — every one silently rejected at the P5 vardiff gate (share_difficulty < required_difficulty). ~28% of the DASH hotel's shares, matching the 23-30% observed reject rate. Rejects are cosmetic for block-finding (block-check passes by orders of magnitude) but drive miner-firmware failover to backup pools, bleeding tenant hashrate. F1 (retention): in HashrateTracker::set_difficulty_from_hashrate, after the min/max clamp, snap the advertised difficulty to exp2(floor(log2(d))) — the largest power of two not exceeding the estimator's ideal D — then re-floor at min. Advertised == applied == required, so the firmware grid no longer opens a reject window. Rounding DOWN keeps accepted-share cadence at/above target. Only the DASH use_hashrate_vardiff path is affected; bounds/target/deadband unchanged. issued_difficulty (frozen at job creation from get_current_difficulty()) and the advertised set_difficulty now carry the same quantized value by construction. F2 (observability): rate-limited (<= 1 / 5s / session) INFO diagnostics at the P5 low-diff reject (logs hash_diff / required / issued / vardiff) and the P1 malformed-params reject, so any residual grid gap is visible in the field without flooding. KAT: extends test_dash_work_source with a power-of-two-quantization check that drives the tracker through its public API and asserts the advertised difficulty is an exact power of two and never exceeds the estimator's ideal D (rounds down). Consensus-neutral: per-connection vardiff/stratum path only.
The power-of-two hashrate-vardiff quantization re-floored the advertised diff at min_difficulty_ (max(min_difficulty_, d)). DASH's min_difficulty_ (0.0005) is not on the power-of-two grid, so a floor-pinned or warm-up advertise fell back to the raw 0.0005 and re-opened the ~2.3% firmware reject gap at the floor. Quantize the floor as well so the result is a power of two by construction: advertise the largest grid step <= the configured floor (0.00048828125), at most one step below it, preserving the round-DOWN cadence invariant. Scoped to the hashrate-vardiff path (set_difficulty_from_hashrate, DASH-only); the shared StratumConfig min_difficulty default is unchanged. Adds a floor-pinned KAT: a slow miner whose ideal D falls below the floor must still be advertised an exact power of two (fails pre-fix).
frstrtr
force-pushed
the
dash/vardiff-poweroftwo-quantize
branch
from
July 20, 2026 15:32
a0e4d47 to
aafb66c
Compare
This was referenced Jul 20, 2026
frstrtr
pushed a commit
that referenced
this pull request
Jul 21, 2026
…reward-safe --embedded-mainnet (default OFF) Rebased onto master (#781 io-decouple + #773/#777/#775/#782). Integrates the embedded DASH template arm into the decoupled tip-poll/rpc_pool architecture WHILE preserving both: #781's refresh_executor/resource_template_now split AND every v0.2.4 embedded guard. Default behaviour is UNCHANGED and reward-safe — with --embedded-mainnet OFF (the default) the template stays arm=dashd-fallback, the path the hotel runs, byte-for-byte. Wires the vendored SML/QuorumManager/CCbTx machinery into the live embedded template so the daemonless arm assembles a real DIP-0004 type-5 CCbTx, proven byte-identical to a real dashd getblocktemplate from the raw mnlistdiff wire (merkleRootMNList + merkleRootQuorums recomputed from wire; bestCL; creditPool accrual). The mainnet gate lifts behind the explicit --embedded-mainnet opt-in. Consensus guards (all fail-closed to the reward-safe dashd fallback): - merkleRootQuorums: hash ALL active commitments (no rotated per-index dedup) — byte-identical to dashd (109-quorum from-wire KAT). - H-1: a malformed quorum tail wipes + full-resyncs (never papered over by a later incremental; base-continuity tightened). - DKG mining-phase heights (is_dkg_commitment_window over every enabled llmqType) and superblock heights: refuse embedded. - bestCL freshness: committed ChainLock must be within one block of the tip. - creditPool: INDEPENDENT seed-height gate (the seed cbTx's own nHeight must equal the tip) — catches a seed one block behind that value/hash self-checks miss; pre-emit value re-check + serve-time cache re-validation for the build-vs-serve skew; GBT-xcheck backstop cross-checks the embedded creditPool vs dashd. - C-3: the embedded block excludes all DIP special txs (1-4,6,8,9) — their own-block state effects are not modelled in the committed CbTx (Phase-2). - pre-emit HARD GATE re-validates the built CbTx (both roots + all height-class + bestCL + creditPool checks) before serve; discards to fallback on any failure. Reward-safety posture: --embedded-mainnet is default OFF (unconfigured mainnet nodes are byte-unchanged dashd-fallback). The embedded-ON daemonless seed-lag (arm falls back rather than serving stale at some heights) is deferred as a separate goal; ON is reward-safe (worst case = fallback, never a bad block). Tests (dash embedded suite): node_coin_state 15, coin_state_maintainer 11, stratum_work_source 38, embedded_gbt 10, mnlistdiff_root_parity 4, byte_parity 4, quorum_root 11, mempool 19, node_reception_wire 21, header_chain 8. c2pool-dash builds+links against the #781 io-decoupled main_dash.
frstrtr
pushed a commit
that referenced
this pull request
Jul 21, 2026
…reward-safe --embedded-mainnet (default OFF) Rebased onto master (#781 io-decouple + #773/#777/#775/#782). Integrates the embedded DASH template arm into the decoupled tip-poll/rpc_pool architecture WHILE preserving both: #781's refresh_executor/resource_template_now split AND every v0.2.4 embedded guard. Default behaviour is UNCHANGED and reward-safe — with --embedded-mainnet OFF (the default) the template stays arm=dashd-fallback, the path the hotel runs, byte-for-byte. Wires the vendored SML/QuorumManager/CCbTx machinery into the live embedded template so the daemonless arm assembles a real DIP-0004 type-5 CCbTx, proven byte-identical to a real dashd getblocktemplate from the raw mnlistdiff wire (merkleRootMNList + merkleRootQuorums recomputed from wire; bestCL; creditPool accrual). The mainnet gate lifts behind the explicit --embedded-mainnet opt-in. Consensus guards (all fail-closed to the reward-safe dashd fallback): - merkleRootQuorums: hash ALL active commitments (no rotated per-index dedup) — byte-identical to dashd (109-quorum from-wire KAT). - H-1: a malformed quorum tail wipes + full-resyncs (never papered over by a later incremental; base-continuity tightened). - DKG mining-phase heights (is_dkg_commitment_window over every enabled llmqType) and superblock heights: refuse embedded. - bestCL freshness: committed ChainLock must be within one block of the tip. - creditPool: INDEPENDENT seed-height gate (the seed cbTx's own nHeight must equal the tip) — catches a seed one block behind that value/hash self-checks miss; pre-emit value re-check + serve-time cache re-validation for the build-vs-serve skew; GBT-xcheck backstop cross-checks the embedded creditPool vs dashd. - C-3: the embedded block excludes all DIP special txs (1-4,6,8,9) — their own-block state effects are not modelled in the committed CbTx (Phase-2). - pre-emit HARD GATE re-validates the built CbTx (both roots + all height-class + bestCL + creditPool checks) before serve; discards to fallback on any failure. Reward-safety posture: --embedded-mainnet is default OFF (unconfigured mainnet nodes are byte-unchanged dashd-fallback). The embedded-ON daemonless seed-lag (arm falls back rather than serving stale at some heights) is deferred as a separate goal; ON is reward-safe (worst case = fallback, never a bad block). Tests (dash embedded suite): node_coin_state 15, coin_state_maintainer 11, stratum_work_source 38, embedded_gbt 10, mnlistdiff_root_parity 4, byte_parity 4, quorum_root 11, mempool 19, node_reception_wire 21, header_chain 8. c2pool-dash builds+links against the #781 io-decoupled main_dash. CI-fold: build.yml "Build tests" allowlists test_dash_embedded_gbt but not the two byte-parity executables, so CTest registered them (gtest_add_tests AUTO) while CI never built them -> ctest "Not Run" exit 8 (Linux x86_64 red), and the from-wire merkleRoot + real-dashd CbTx round-trip proofs ran in NO CI job. Folded test_dash_embedded_cbtx_byte_parity.cpp + test_dash_mnlistdiff_root_parity.cpp INTO the test_dash_embedded_gbt executable (distinct TEST suites; DASH_FIXTURE_DIR moved to that target) and removed the standalone add_executable/gtest_add_tests registrations. test_dash_embedded_gbt 10 -> 18 cases (byte-parity 4 + from-wire 4), all green; no unbuilt-executable CTest registrations remain.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DASH firmware-safe power-of-two vardiff quantization (v0.2.3.6)
Resolves the retention-critical ~25% (23–30% observed) share-reject rate on the live DASH mining hotel.
Stacks on #770 (dash/tip-notify-fallback-arm / v0.2.3.5 tip-poll). Base is
master; review only the two commits on top of #770. Merge #770 first.Root cause
#766's hashrate-vardiff advertises arbitrary real difficulties. ASIC firmware rounds the advertised pool difficulty DOWN to a power-of-two grid, mines that easier target, then submits shares between the rig's rounded target and the pool's exact required difficulty. Every such share is silently rejected at the P5 gate (
share_difficulty < required_difficulty,src/core/stratum_server.cpp). ~28% expected — matches the field. Rejects are cosmetic for block-finding (the block-check passes by orders of magnitude) but trigger miner-firmware failover to backup pools, bleeding the tenant's hashrate.F1 — retention fix
In
HashrateTracker::set_difficulty_from_hashrate, right after the min/max clamp, snap the advertised difficulty toexp2(floor(log2(d)))— the largest power of two not exceeding the estimator's ideal D — then re-floor atmin_difficulty_. Advertised == applied == required, closing the firmware-grid reject window. Rounding DOWN keeps accepted-share cadence at/above target.issued_difficulty(frozen at job creation fromget_current_difficulty()) and the advertisedset_difficultycarry the same quantized value by construction. Bounds / target_time / deadband unchanged. DASH-scoped by construction (only theuse_hashrate_vardiffpath).set_difficulty_hint/ suggest-difficulty is intentionally left untouched: it is the miner-requested fixed-diff path, shared across all coins, and quantizing it would take the change out of DASH scope.F2 — observability
Rate-limited (≤ 1 / 5 s / session) INFO diagnostics at the P5 low-diff reject (logs
hash_diff / required / issued / vardiff) and the P1 malformed-params reject. INFO + throttled so it cannot flood.KAT
Extends the already-allowlisted
test_dash_work_sourcetarget (in build.yml--targeton both Linux legs) withHashrateVardiffQuantize.*: drives the tracker through its public API and asserts the advertised difficulty is an exact power of two and never exceeds the estimator's ideal D (rounds down). Plus a pure-math floor-on-log2-grid check.Consensus-neutrality (pre-v36 vardiff rule)
Per-connection vardiff/stratum path only.
git diff origin/master | grep -iE 'get_share_bits|share_bits|share_target|DGW|oracle|payout|pplns'over this change's code lines is EMPTY — the only matches in the full range are comments (a pre-existing context line and the KAT's own doc comment). PASS.Build / test
cmake --build build_ci --target c2pool-dash test_dash_work_source→ exit 0.HashrateVardiffQuantize.AdvertisesPowerOfTwoRoundedDown,...QuantizationFormulaIsFloorLog2, plus the 4 pre-existing DashWorkSource routing tests).c2pool-dash-0.2.3.6-linux-x86_64.tar.gz, bundle self-reports0.2.3.6.Not deployed; systemd drop-in not staged — integrator handles staging/deploy.