Skip to content

dash: vardiff hysteresis on power-of-two boundary (F-2)#777

Merged
frstrtr merged 1 commit into
masterfrom
dash/vardiff-hysteresis
Jul 21, 2026
Merged

dash: vardiff hysteresis on power-of-two boundary (F-2)#777
frstrtr merged 1 commit into
masterfrom
dash/vardiff-hysteresis

Conversation

@frstrtr

@frstrtr frstrtr commented Jul 20, 2026

Copy link
Copy Markdown
Owner

F-2 vardiff hysteresis (DASH hashrate-vardiff)

Low-priority polish identified in the #773 review. Cosmetic + consensus-neutral — smooths rig-side hashrate graphs and cuts set_difficulty churn; no effect on shares, targets, or payout.

Problem

After #773 (F-1) quantizes advertised vardiff to powers of two (d = exp2(floor(log2(d)))), the 10% dead-band (vardiff_deadband_) is inert — it compares already-quantized values, so ratios are only 1, 2, 0.5. A rig whose EWMA-estimated ideal D sits near a 2^n boundary flaps its advertised difficulty 2^n <-> 2^(n+1) on estimator noise (~±24% at tau=90s), roughly doubling set_difficulty events and jittering rig hashrate graphs. No rejects (the P5 gate uses min(live, issued), both PoT), just churn.

Fix

Apply hysteresis on the un-quantized ideal D. Hold the current power-of-two bucket [C, 2C) and only re-quantize when D leaves it decisively:

  • step up when D >= 2C * (1 + deadband)
  • step down when D < C * (1 - deadband)
  • values in [C*(1-deadband), 2C*(1+deadband)) keep the current advertisement.

Only the current advertisement being on the 2^n grid triggers the hold (a non-grid seed/hint is corrected onto the grid immediately). Preserved: round-DOWN cadence, warm-up, min/max clamp, and the quantized floor (F-1). DASH use_hashrate_vardiff_ path only.

Tests

Extends the allowlisted test_dash_work_source (where the HashrateVardiffQuantize KATs live) with HashrateVardiffHysteresis:

  • BoundaryNoiseHeldButDecisiveMoveStepsUp — ideal D rises past the 2^n edge but within the band → bucket held; a decisive move past 2C*(1+deadband)steps (round-DOWN preserved).
  • LowerMarginHeldButDecisiveDropStepsDownD dips below the lower edge but within margin → held; a decisive drop below C*(1-deadband)steps down.

Both would FAIL pre-fix (a bare round-DOWN quantize steps the instant D crosses the plain edge). All 9 tests in the binary pass; the 3 existing quantize KATs are unaffected.

Consensus-neutrality

git diff origin/master | grep -iE 'get_share_bits|share_bits|share_target|DGW|oracle|payout|pplns'empty. Diff touches only tracker.cpp (vardiff advertisement) and the test.

Draft — for merge-tap review, not for merge/deploy.

After F-1 quantizes advertised vardiff to powers of two, the 10% dead-band
compares quantized values (ratios only 1/2/0.5) so it is inert. A rig whose
un-quantized ideal D sits near a 2^n boundary flaps its advertised bucket
2^n <-> 2^(n+1) on EWMA noise, doubling set_difficulty churn and jittering
rig-side hashrate graphs.

Apply hysteresis on the UN-QUANTIZED ideal D: hold the current power-of-two
bucket [C, 2C) and only re-quantize when D leaves it decisively -- at/above
2C*(1+deadband) (step up) or below C*(1-deadband) (step down). Only widens the
transition; round-DOWN cadence, warm-up, min/max clamp and the quantized floor
are all preserved. DASH hashrate-vardiff path only; consensus-neutral.

Extends test_dash_work_source with hysteresis KATs (up hold+decisive-step,
down hold+decisive-step); existing quantize KATs still pass.
// (a value we previously advertised). A non-grid seed/hint (e.g. the raw
// min_difficulty_ or an operator hint) must be corrected onto the grid
// immediately, not frozen in place by the hysteresis band.
const bool current_on_grid = (C == std::exp2(std::floor(std::log2(C))));
}
LOG_INFO << "[Stratum] VARDIFF(hashrate): " << current_difficulty_ << " -> " << d

if (d_q == current_difficulty_) return; // decisive move re-adopts same grid step
@frstrtr
frstrtr marked this pull request as ready for review July 21, 2026 00:51
@frstrtr
frstrtr merged commit b8f8cdf into master Jul 21, 2026
26 checks passed
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.
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