Skip to content

Commit f371f7d

Browse files
committed
dash(embedded): oracle-shadow validator — proposal-mode verdict + graduation gate (#793)
Cross-checks the daemonless embedded CbTx/block against dashd getblocktemplate{mode:proposal} per tip; regime-aware field-compare diagnosis; persistent coverage ledger + /embedded_oracle GRADUATED verdict. Observe-only, consensus-neutral.
1 parent c4bd8ef commit f371f7d

21 files changed

Lines changed: 1351 additions & 1486 deletions

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ Development is supported by Anthropic's [Claude for Open Source](https://claude.
4040
>
4141
> **Recent Bitcoin Block Mined by P2pool** (2025-03-07 06:08:22 UTC) BTC [#886688](https://blockchair.com/bitcoin/block/886688)
4242
>
43-
> **First DASH block, c2pool** (2026-07-20 01:15:15 UTC) DASH [#2507753](https://blockchair.com/dash/block/2507753) — a solo X11 block. The DIP4 coinbase pays the masternode the network requires at that height, and dashd accepted it. The payee is checked against the template before the block is broadcast; work built on a stale template is discarded, not mined.
44-
>
45-
> **DASH block at a full-payment height** (2026-07-20 23:25:32 UTC) DASH [#2508254](https://blockchair.com/dash/block/2508254) — six transactions, three consensus-mandated payments in the coinbase. The full payee set was assembled and verified against the template before submission, and dashd accepted the block. The demanding case takes the same path as the trivial one.
43+
> **First DASH Block Mined by c2pool** (2026-07-20 01:15:15 UTC) DASH [#2507753](https://blockchair.com/dash/block/2507753) — solo X11 block, DIP4 coinbase reward-safe with the correct masternode payee, accepted by dashd
4644
4745
---
4846

src/c2pool/hashrate/tracker.cpp

Lines changed: 11 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -182,58 +182,30 @@ void HashrateTracker::set_difficulty_from_hashrate(double now) {
182182
if (ewma_share_count_ < static_cast<uint64_t>(vardiff_warmup_shares_)) return; // keep seed diff
183183
double h = get_recent_hashrate(now);
184184
if (h <= 0.0) return;
185-
// Un-quantized ideal difficulty from the smoothed hashrate. The [min,max]
186-
// clamp is part of the ideal (a rig outside the bounds should sit at the
187-
// bound), so hysteresis below operates on this clamped-but-unquantized value.
188-
double d_ideal = h * target_time_per_mining_share_ / 4294967296.0;
189-
d_ideal = std::max(min_difficulty_, std::min(max_difficulty_, d_ideal));
185+
double d = h * target_time_per_mining_share_ / 4294967296.0;
186+
d = std::max(min_difficulty_, std::min(max_difficulty_, d));
190187
// Firmware-grid fix: many ASIC firmwares round the advertised pool difficulty
191188
// DOWN to a power-of-two grid, then mine that easier target and submit shares
192189
// the pool's exact (higher) required difficulty rejects. Advertise only
193190
// power-of-two difficulties so advertised == applied == required. Round DOWN so
194191
// accepted-share cadence never drops below target.
192+
d = std::exp2(std::floor(std::log2(d)));
195193
// Quantize the floor too so a floor-pinned/warm-up advertise is still a
196194
// power of two. min_difficulty_ (e.g. 0.0005) is not itself on the grid, so
197195
// re-flooring at it would re-open the firmware reject gap at the floor.
198196
// Advertise at most one grid step below the configured floor (0.000488 vs
199197
// 0.0005), preserving the round-DOWN cadence invariant.
200-
double d_q = std::max(std::exp2(std::floor(std::log2(min_difficulty_))),
201-
std::exp2(std::floor(std::log2(d_ideal))));
202-
203-
// Hysteresis around the current power-of-two bucket. After the firmware-grid
204-
// fix quantizes advertised diff to powers of two, both d_q and
205-
// current_difficulty_ live on the 2^n grid, so a dead-band on the QUANTIZED
206-
// ratio is inert (ratios are only 1, 2, 0.5). A rig whose un-quantized ideal
207-
// D sits near a 2^n boundary would then flap its advertised bucket
208-
// 2^n <-> 2^(n+1) on estimator noise (~+/-24% at tau=90s), doubling
209-
// set_difficulty churn and jittering rig-side hashrate graphs.
210-
//
211-
// Fix: hold the current bucket [C, 2C) and only re-quantize when the
212-
// UN-QUANTIZED ideal D leaves it DECISIVELY -- at/above 2C by the dead-band
213-
// margin (step up) or below C by the dead-band margin (step down). Values in
214-
// the band [C*(1-deadband), 2C*(1+deadband)) keep the current advertisement,
215-
// so boundary noise no longer churns. This only widens the transition: it
216-
// never advertises above ideal D by more than the margin, and never below the
217-
// (quantized) floor. Round-DOWN cadence and warm-up/clamp are preserved.
198+
d = std::max(std::exp2(std::floor(std::log2(min_difficulty_))),
199+
std::exp2(std::floor(std::log2(d))));
218200
if (current_difficulty_ > 0.0) {
219-
const double C = current_difficulty_;
220-
// Only hold when the current advertisement is itself on the 2^n grid
221-
// (a value we previously advertised). A non-grid seed/hint (e.g. the raw
222-
// min_difficulty_ or an operator hint) must be corrected onto the grid
223-
// immediately, not frozen in place by the hysteresis band.
224-
const bool current_on_grid = (C == std::exp2(std::floor(std::log2(C))));
225-
if (current_on_grid) {
226-
const double up_threshold = 2.0 * C * (1.0 + vardiff_deadband_);
227-
const double down_threshold = C * (1.0 - vardiff_deadband_);
228-
if (d_ideal >= down_threshold && d_ideal < up_threshold)
229-
return; // inside the hysteresis band -- keep the current bucket
230-
}
201+
double ratio = d / current_difficulty_;
202+
// Dead-band: absorb estimator noise, no needless set_difficulty churn.
203+
if (ratio < 1.0 + vardiff_deadband_ && ratio > 1.0 / (1.0 + vardiff_deadband_))
204+
return;
231205
}
232-
233-
if (d_q == current_difficulty_) return; // decisive move re-adopts same grid step
234-
LOG_INFO << "[Stratum] VARDIFF(hashrate): " << current_difficulty_ << " -> " << d_q
206+
LOG_INFO << "[Stratum] VARDIFF(hashrate): " << current_difficulty_ << " -> " << d
235207
<< " (H_est=" << h << " H/s, target=" << target_time_per_mining_share_ << "s)";
236-
current_difficulty_ = d_q; // downstream notify + stratum 1% resend guard unchanged
208+
current_difficulty_ = d; // downstream notify + stratum 1% resend guard unchanged
237209
}
238210

239211
void HashrateTracker::adjust_difficulty() {

0 commit comments

Comments
 (0)