Skip to content

Commit 9d582fd

Browse files
authored
Merge pull request #762 from frstrtr/fix/dash-vardiff-grace-window
stratum(vardiff): validate share against its job's issued difficulty (fix DASH hotel reject storm)
2 parents 155118f + 0a1c538 commit 9d582fd

4 files changed

Lines changed: 38 additions & 2 deletions

File tree

src/core/stratum_server.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,8 +1069,21 @@ nlohmann::json StratumSession::handle_submit(const nlohmann::json& params, const
10691069
if (sb != 0)
10701070
pool_difficulty = chain::target_to_difficulty(chain::bits_to_target(sb));
10711071

1072-
// Use VARDIFF as stratum acceptance threshold
1072+
// Use VARDIFF as stratum acceptance threshold — but validate the share
1073+
// against the difficulty THIS job was issued at (the target the miner
1074+
// actually received), not the live vardiff. A vardiff UP-retarget between
1075+
// job-issue and submit must not reject the miner's in-flight shares — that
1076+
// was the sole source of the DASH hotel's ~28% "Low difficulty share"
1077+
// rejects (X11 hashrate swings oscillate vardiff; every up-retarget
1078+
// rejected in-flight work). Faithful port of p2pool-dash work.py:477
1079+
// ("within 3 work events" grace): p2pool judges each share by its OWN job's
1080+
// target. Take the lenient lower of {live vardiff, this job's issued diff}.
1081+
// issued_difficulty==0 (unset) ⇒ old behavior. REWARD-NEUTRAL: the pool
1082+
// target + block-target checks below are independent and unchanged, so a
1083+
// real block (which far exceeds any vardiff) is unaffected on all coins.
10731084
double required_difficulty = vardiff_difficulty;
1085+
if (job.issued_difficulty > 0.0 && job.issued_difficulty < required_difficulty)
1086+
required_difficulty = job.issued_difficulty;
10741087

10751088
// Build JobSnapshot BEFORE the rejection gate — needed for block-level
10761089
// checking which must run on ALL submissions, not just accepted ones.
@@ -1604,6 +1617,10 @@ void StratumSession::send_notify_work(bool force_clean, const uint256* frozen_be
16041617
je.version = version_u32;
16051618
je.gbt_block_nbits = gbt_block_nbits;
16061619
je.created_at = now_steady;
1620+
// Freeze the vardiff advertised with this job so the submit gate can
1621+
// validate a share against the target the miner actually received
1622+
// (p2pool-dash work.py:477 grace), immune to later vardiff retargets.
1623+
je.issued_difficulty = hashrate_tracker_.get_current_difficulty();
16071624
active_jobs_[job_id] = std::move(je);
16081625
job_order_.push_back(job_id);
16091626
}

src/core/stratum_server.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ class StratumSession : public std::enable_shared_from_this<StratumSession>
157157
// Creation stamp for TTL eviction (steady clock — immune to wall-clock
158158
// jumps; the wire ntime field above is unchanged and stays wall-clock).
159159
std::chrono::steady_clock::time_point created_at{};
160+
// VARDIFF difficulty advertised WITH this job, frozen at creation. The
161+
// submit gate validates a share against THIS (the target the miner
162+
// actually received), not the live vardiff, so a vardiff up-retarget
163+
// between job-issue and submit does not reject in-flight shares. Port
164+
// of p2pool-dash work.py:477 grace (p2pool judges each share by its own
165+
// job's target). 0.0 = unset ⇒ gate falls back to the live vardiff.
166+
double issued_difficulty{0.0};
160167
};
161168
std::unordered_map<std::string, JobEntry> active_jobs_;
162169
// Insertion-order companion to active_jobs_ (hotel interim fix #1).

src/impl/dash/stratum/work_source.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ DASHWorkSource::DASHWorkSource(const coin::NodeCoinState& coin_state,
127127
// (WorkSnapshot::has_header) and suppresses the job otherwise. This also
128128
// makes the core's legacy stub-coinbase fallback unreachable for DASH.
129129
config_.require_job_snapshot = true;
130+
// Vardiff target share rate: adopt p2pool-dash's FIELD-TUNED default of 10s
131+
// per pseudoshare (p2pool-dash/p2pool/main.py:1153 default=10., dest='share_rate'),
132+
// not the 3s jtoomim/BTC default (stratum_types.hpp:40). DASH's variable X11
133+
// hashrate at 3s recomputes vardiff 3.3x as often and chases the variance,
134+
// producing the observed oscillation (1170->2264->1405->2041 in minutes) and
135+
// the ~28% "Low difficulty share" reject storm. 10s cuts retarget frequency
136+
// 3.3x and stabilises vardiff at the source. DASH-only; other coins keep the
137+
// 3s StratumConfig default. Pairs with the per-job issued_difficulty grace.
138+
config_.target_time = 10.0;
130139
LOG_INFO << "[DASH-STRATUM] DASHWorkSource constructed"
131140
<< " (min_diff=" << config_.min_difficulty
132141
<< " max_diff=" << config_.max_difficulty

test/test_dash_stratum_work_source.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,10 @@ TEST(DashStratumWorkSource, ConfigDefaultsMatchStratumConfigWithX11Overrides)
193193
const auto& cfg = ws->get_stratum_config();
194194
EXPECT_DOUBLE_EQ(cfg.min_difficulty, 0.0005);
195195
EXPECT_DOUBLE_EQ(cfg.max_difficulty, 65536.0);
196-
EXPECT_DOUBLE_EQ(cfg.target_time, 3.0);
196+
// DASH adopts p2pool-dash's field-tuned 10s vardiff share-rate default (set in
197+
// the work-source ctor, ec26caef) to stop the X11 vardiff oscillation/reject
198+
// storm -- NOT the 3s BTC StratumConfig default. Keep this KAT in lockstep.
199+
EXPECT_DOUBLE_EQ(cfg.target_time, 10.0);
197200
EXPECT_TRUE(cfg.vardiff_enabled);
198201
// X11 = standard diff-1 scale (p2pool-dash DUMB_SCRYPT_DIFF == 1), NOT the
199202
// scrypt 2^16 default -- otherwise advertised difficulty inflates 65536x.

0 commit comments

Comments
 (0)