-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathwork_source.cpp
More file actions
669 lines (598 loc) · 33.4 KB
/
Copy pathwork_source.cpp
File metadata and controls
669 lines (598 loc) · 33.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
// dgb::stratum::DGBWorkSource — Stage 4a skeleton.
//
// All IWorkSource methods are stubbed to safe defaults. Subsequent
// sub-stages flesh them out (mirroring btc::stratum::BTCWorkSource's own
// 4b/4c/4d progression):
// Stage 4b: read-only getters (config, prevhash, generation, workers)
// Stage 4c: Scrypt work generation (template, merkle branches, coinbase)
// Stage 4d: mining_submit hot path (Scrypt PoW classify + won-block dispatch)
//
// The skeleton is intentionally non-functional but compiles, instantiates,
// and lets us validate the StratumServer wiring in main_dgb.cpp end-to-end
// before implementing the substantive logic. DGB validates Scrypt blocks
// only (V36 / project_v36_dgb_scrypt_only); the other four DGB algos are
// accept-by-continuity / V37 and never reach this work source.
#include <impl/dgb/stratum/work_source.hpp>
#include <impl/dgb/coin/header_chain.hpp>
#include <impl/dgb/coin/mempool.hpp>
#include <impl/dgb/coin/embedded_coinbase_value.hpp>
#include <impl/dgb/coin/dgb_block_algo.hpp>
#include <impl/dgb/coin/template_builder.hpp>
#include <impl/dgb/coin/embedded_tx_select.hpp> // make_mempool_tx_source (mempool -> GBT transactions[])
#include <impl/dgb/config_pool.hpp> // dgb::PoolConfig::BLOCK_MAX_WEIGHT
#include <impl/dgb/coin/hash_format.hpp>
#include <impl/dgb/coin/scrypt_pow.hpp> // scrypt_pow_hash (DGB-Scrypt PoW SSOT)
#include <impl/dgb/coin/submit_classify.hpp> // classify_submission (Stage-4d decision SSOT)
#include <impl/dgb/coin/connection_coinbase.hpp> // build_connection_coinbase_from_pplns SSOT
#include <impl/dgb/coin/header_sample_build.hpp>// compact_to_target (compact bits -> u256)
#include <core/log.hpp>
#include <core/hash.hpp> // Hash (sha256d) for coinbase txid + merkle pair
#include <core/address_utils.hpp> // address_to_script (share payout from username)
#include <btclibs/util/strencodings.h> // ParseHex
#include <array>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <span>
#include <string>
#include <limits>
namespace dgb::stratum {
namespace {
// previousblockhash big-endian display-hex rendering is the dgb::coin SSOT
// (coin/hash_format.hpp), shared with the embedded work path
// (coin/embedded_coin_node.hpp) so the two build_work_template callers
// cannot emit a divergent previousblockhash encoding.
using dgb::coin::u256_be_display_hex;
// -- Byte-stream helpers for the Stage-4d header/block reconstruction --------
// Little-endian Bitcoin wire encodings, identical to btc::stratum's own
// mining_submit assembly (the DGB header layout is byte-for-byte Bitcoin's;
// only the PoW digest differs -- Scrypt, not SHA256d).
inline void push_u32_le(std::vector<uint8_t>& v, uint32_t x) {
v.push_back(static_cast<uint8_t>( x & 0xff));
v.push_back(static_cast<uint8_t>((x >> 8) & 0xff));
v.push_back(static_cast<uint8_t>((x >> 16) & 0xff));
v.push_back(static_cast<uint8_t>((x >> 24) & 0xff));
}
inline void push_u64_le(std::vector<uint8_t>& v, uint64_t x) {
for (int i = 0; i < 8; ++i)
v.push_back(static_cast<uint8_t>((x >> (i * 8)) & 0xff));
}
inline void push_varint(std::vector<uint8_t>& v, uint64_t n) {
if (n < 0xfd) {
v.push_back(static_cast<uint8_t>(n));
} else if (n <= 0xffff) {
v.push_back(0xfd);
v.push_back(static_cast<uint8_t>(n & 0xff));
v.push_back(static_cast<uint8_t>((n >> 8) & 0xff));
} else if (n <= 0xffffffff) {
v.push_back(0xfe);
push_u32_le(v, static_cast<uint32_t>(n));
} else {
v.push_back(0xff);
push_u64_le(v, n);
}
}
// Stratum sends ntime/nonce/version as big-endian hex; sscanf(%x) decodes them.
inline uint32_t parse_be_hex_u32(const std::string& s) {
uint32_t v = 0;
std::sscanf(s.c_str(), "%x", &v);
return v;
}
// One merkle ascent step: sha256d(left||right) over the two LE-internal 32-byte
// node hashes (the dgb-local equivalent of btc::coin::merkle_hash_pair -- kept
// in-tree per the per-coin isolation invariant rather than reaching into btc).
inline uint256 merkle_pair(const uint256& left, const uint256& right) {
return Hash(std::span<const uint8_t>(left.data(), 32),
std::span<const uint8_t>(right.data(), 32));
}
} // namespace
DGBWorkSource::DGBWorkSource(c2pool::dgb::HeaderChain& chain,
dgb::coin::Mempool& mempool,
bool is_testnet,
SubmitBlockFn submit_fn,
core::SubsidyFunc subsidy_func,
core::stratum::StratumConfig config)
: chain_(chain)
, mempool_(mempool)
, is_testnet_(is_testnet)
, submit_block_fn_(std::move(submit_fn))
, subsidy_func_(std::move(subsidy_func))
, config_(std::move(config))
{
LOG_INFO << "[DGB-STRATUM] DGBWorkSource constructed"
<< " (testnet=" << is_testnet_
<< " min_diff=" << config_.min_difficulty
<< " max_diff=" << config_.max_difficulty
<< " target_time=" << config_.target_time
<< "s vardiff=" << (config_.vardiff_enabled ? "on" : "off")
<< " subsidy_func=" << (subsidy_func_ ? "wired" : "UNSET") << ")";
}
DGBWorkSource::~DGBWorkSource() = default;
// ──────────────────────────────────────────────────────────────────
// Embedded coinbasevalue — first PRODUCTION caller of CoinParams::subsidy_func.
// Delegates to the dgb::coin SSOT (embedded_coinbase_value.hpp) so the embedded
// TemplateBuilder coinbasevalue and the external-daemon GBT coinbasevalue are
// computed from ONE definition and can never silently diverge. The GBT value,
// when present, is authoritative and returned verbatim (external-daemon
// fallback MUST PERSIST); otherwise subsidy_func(height)+fees is derived from
// the DGB oracle decay schedule.
// ──────────────────────────────────────────────────────────────────
uint64_t DGBWorkSource::coinbase_value(uint32_t height, uint64_t total_fees,
std::optional<uint64_t> gbt_coinbasevalue) const
{
return dgb::coin::resolve_coinbase_value(subsidy_func_, height, total_fees,
gbt_coinbasevalue);
}
// ─────────────────────────────────────────────────────────────────────────────
// IWorkSource: config + read-only state — Stage 4b will fill these in.
// ─────────────────────────────────────────────────────────────────────────────
const core::stratum::StratumConfig& DGBWorkSource::get_stratum_config() const
{
return config_;
}
std::function<uint256()> DGBWorkSource::get_best_share_hash_fn() const
{
std::lock_guard<std::mutex> lk(best_share_mutex_);
return best_share_hash_fn_; // empty function until set_best_share_hash_fn() called
}
std::string DGBWorkSource::get_current_gbt_prevhash() const
{
// The tip block id as GBT-conventional big-endian display hex, drawn from
// the SAME source as get_current_work_template()'s previousblockhash field
// (chain_.tip_hash(), the #216 HeaderChain accessor) through the SAME
// u256_be_display_hex formatter -- ONE truthful source, so the dedicated
// getter and the assembled template can never silently diverge. Empty
// string when the chain carries no real tip hash (tip_hash() == nullopt:
// an empty chain, or the block_hash==0 sentinel from the not-yet-wired
// embedded P2P header ingest) -- a truthful absence, never a fabricated id.
if (auto th = chain_.tip_hash())
return u256_be_display_hex(*th);
return {};
}
uint64_t DGBWorkSource::get_work_generation() const
{
return work_generation_.load(std::memory_order_relaxed);
}
bool DGBWorkSource::has_merged_chain(uint32_t /*chain_id*/) const
{
// DGB V36 default build: standalone Scrypt parent, no merged mining.
// The DGB-as-DOGE-parent dual-parent path (-DAUX_DOGE=ON) is a V36
// STRETCH, parked behind the shared DOGE-aux settle — not wired here.
return false;
}
// ─────────────────────────────────────────────────────────────────────────────
// IWorkSource: per-connection bookkeeping — minimal but real now.
// ─────────────────────────────────────────────────────────────────────────────
void DGBWorkSource::register_stratum_worker(const std::string& session_id,
const core::stratum::WorkerInfo& info)
{
std::lock_guard<std::mutex> lk(workers_mutex_);
workers_[session_id] = info;
LOG_INFO << "[DGB-STRATUM] worker registered: session=" << session_id
<< " user=" << info.username
<< " worker=" << info.worker_name
<< " endpoint=" << info.remote_endpoint;
}
void DGBWorkSource::unregister_stratum_worker(const std::string& session_id)
{
std::lock_guard<std::mutex> lk(workers_mutex_);
auto it = workers_.find(session_id);
if (it != workers_.end()) {
LOG_INFO << "[DGB-STRATUM] worker unregistered: session=" << session_id
<< " user=" << it->second.username
<< " accepted=" << it->second.accepted
<< " rejected=" << it->second.rejected
<< " stale=" << it->second.stale;
workers_.erase(it);
}
}
void DGBWorkSource::update_stratum_worker(const std::string& session_id,
double hashrate, double dead_hashrate,
double difficulty,
uint64_t accepted, uint64_t rejected, uint64_t stale)
{
std::lock_guard<std::mutex> lk(workers_mutex_);
auto it = workers_.find(session_id);
if (it == workers_.end()) return;
it->second.hashrate = hashrate;
it->second.dead_hashrate = dead_hashrate;
it->second.difficulty = difficulty;
it->second.accepted = accepted;
it->second.rejected = rejected;
it->second.stale = stale;
}
// ─────────────────────────────────────────────────────────────────────────────
// IWorkSource: work generation — Stage 4c will fill these in.
// ─────────────────────────────────────────────────────────────────────────────
nlohmann::json DGBWorkSource::get_current_work_template() const
{
// Stage 4c (coinbasevalue wire): the full GBT-shaped template
// (previousblockhash, bits, version, curtime, mintime, transactions[])
// is assembled by the embedded dgb::coin::TemplateBuilder in a following
// slice. What lands here is the consensus-bearing reward field: the
// coinbasevalue for the NEXT block, derived through the #207 SSOT
// (coinbase_value -> resolve_coinbase_value -> subsidy_func) keyed on the
// absolute next-block height from the #209 HeaderChain accessor
// (next_block_height() == tip_height()+1, or base_height for an empty
// chain). Embedded path: no external-daemon GBT value is plumbed in yet
// and mempool-fee aggregation is not wired, so total_fees = 0 here. Both
// compose in later slices WITHOUT changing this SSOT call (a present GBT
// coinbasevalue stays authoritative; fees add on top of subsidy).
const uint32_t next_h = chain_.next_block_height();
// Phase B: feed the served template from the in-process embedded Mempool,
// mirroring the embedded node path (#248, embedded_coin_node.hpp). The
// make_mempool_tx_source SSOT selects fee-sorted txs up to BLOCK_MAX_WEIGHT
// and shapes them into the GBT transactions[] form build_work_template passes
// through verbatim; their fee total folds into coinbasevalue via the #207
// resolve_coinbase_value SSOT (NOT added to the template). On today's empty
// embedded mempool this yields an empty transactions[] and total_fees=0 --
// byte-identical to the prior coinbase-only template; it lights up unchanged
// once the embedded P2P mempool feed populates the pool.
const dgb::coin::EmbeddedTxSelection tx_sel =
dgb::coin::make_mempool_tx_source(mempool_, dgb::PoolConfig::BLOCK_MAX_WEIGHT)();
const uint64_t coinbasevalue =
coinbase_value(next_h, /*total_fees=*/tx_sel.total_fees, /*gbt_coinbasevalue=*/std::nullopt);
// GBT-scaffold fields the embedded path can derive TRUTHFULLY from current
// chain state, ahead of a full dgb::coin::TemplateBuilder port (M3 TODO):
// version — BIP9 base | the DGB Scrypt algo nibble. A DGB block
// template MUST pin the Scrypt lane: the mining algo lives
// in 4 nVersion bits (coin/dgb_block_algo.hpp SSOT) and
// Scrypt is the all-zero codepoint (DGB_BLOCK_VERSION_SCRYPT
// == 0x0000); any other nibble is a non-Scrypt algo that is
// accept-by-continuity / V37 here, never a template we emit.
// curtime — current wall-clock; GBT's suggested header nTime.
// mintime — median_time_past()+1 (#209 accessor): DGB Core's
// ContextualCheckBlockHeader lower bound (nTime > MTP). An
// empty chain returns INT64_MIN (unconstrained) -> emit 0.
// transactions — fee-sorted mempool selection via make_mempool_tx_source
// (BLOCK_MAX_WEIGHT cap), shaped into GBT {data,txid,hash,fee}
// objects; their fee total folds into the coinbasevalue above
// (#207 SSOT). Empty embedded mempool -> empty array, fees 0.
//
// previousblockhash — the tip block id. Emitted ONLY when the HeaderChain
// carries a real tip hash (tip_hash() accessor): the
// Scrypt-only HeaderSample now carries a block_hash slot,
// but the embedded P2P header-download -> validate_and_append
// ingest that POPULATES it lands in a following slice, so on
// today's chain state tip_hash() is nullopt and the field is
// held back -- a truthful absence, never a fabricated hash.
// bits — HELD BACK. The only embedded next-target source is the
// DigiShield/MultiShield damped multiply, which DGB Core
// runs as MultiShield V4: a GLOBAL window across all 5 algos
// with per-algo adjust + MTP deltas. A Scrypt-only header
// walk cannot reconstruct that window (== V37, 5-algo
// validation), so the ingest path deliberately demotes the
// retarget gate to a no-op (see header_chain.hpp). Emitting
// a digishield_next_target()-derived bits would be a
// KNOWN-WRONG value -- the same fabrication the empty
// transactions[] and total_fees=0 avoid. The authoritative
// bits is the external-daemon GBT value, which is not
// plumbed into this embedded template path yet; bits stays
// absent until then. [decision-needed] surfaced to integrator.
// and the per-connection coinbase (gentx + ShareTracker ref_hash + PPLNS
// payout map) assembles in build_connection_coinbase() — that output is
// consensus-bearing and surfaces for an operator tap, not in this field wire.
// Shape the truthfully-derivable fields into the GBT template via the
// dgb::coin::build_work_template SSOT so the embedded path and this work
// source emit one template (Stage 4c extraction). version (Scrypt lane
// pin), mintime (MTP+1 / 0 on empty chain), curtime, empty transactions[]
// and the conditional previousblockhash all live in the builder now; this
// method only resolves the chain-state inputs.
dgb::coin::WorkTemplateInputs in;
in.next_height = next_h;
in.coinbasevalue = coinbasevalue;
in.median_time_past = chain_.median_time_past();
in.curtime = static_cast<int64_t>(std::time(nullptr));
in.transactions = tx_sel.transactions;
if (auto th = chain_.tip_hash())
in.previousblockhash = u256_be_display_hex(*th);
return dgb::coin::build_work_template(in);
}
std::vector<std::string> DGBWorkSource::get_stratum_merkle_branches() const
{
// Stage 4c: return cached branches from last template build.
return {};
}
std::pair<std::string, std::string> DGBWorkSource::get_coinbase_parts() const
{
// Stage 4c: return cached coinb1/coinb2 (extranonce slot between them).
return { {}, {} };
}
core::stratum::CoinbaseResult DGBWorkSource::build_connection_coinbase(
const uint256& prev_share_hash,
const std::string& extranonce1_hex,
const std::vector<unsigned char>& payout_script,
const std::vector<std::pair<uint32_t, std::vector<unsigned char>>>& merged_addrs) const
{
// Phase B live-wire: delegate to the PPLNS->coinbase SSOT
// (build_connection_coinbase_from_pplns), which itself routes through the
// single compute_pplns_payout_split() the verifier uses -- so the coinbase a
// miner hashes here is byte-identical to the one generate_share_transaction()
// enforces, by construction (no second payout implementation to keep in sync).
//
// The PPLNS inputs (weight map walked from the ShareTracker, ref_hash,
// subsidy, donation script) are produced by the seam bound in main_dgb.cpp
// -- the tracker walk lives there, not in the work source. While the seam is
// UNBOUND (or returns nullopt) this is byte-identical to the pre-wire stub:
// an empty result, so the session pushes no work (safe, non-functional).
PplnsInputsFn fn;
{
std::lock_guard<std::mutex> lk(pplns_inputs_mutex_);
fn = pplns_inputs_fn_; // copy under lock; a concurrent set_*_fn() cannot
// tear it out mid-call.
}
if (!fn)
return {}; // unbound: pre-wire behavior (empty job).
std::optional<dgb::coin::ConnCoinbasePplnsInputs> inputs =
fn(prev_share_hash, extranonce1_hex, payout_script, merged_addrs);
if (!inputs)
return {}; // producer declined (e.g. tip not yet known) -> safe empty job.
dgb::coin::ConnCoinbaseParts parts =
dgb::coin::build_connection_coinbase_from_pplns(*inputs);
core::stratum::CoinbaseResult out;
out.coinb1 = std::move(parts.coinb1);
out.coinb2 = std::move(parts.coinb2);
// Freeze the consensus-bearing ref fields the submit path (mining_submit)
// re-derives the won-block reconstruct from; the remaining snapshot fields
// (merkle_branches / segwit) are populated by the template-cache follow-on.
out.snapshot.subsidy = inputs->subsidy;
out.snapshot.frozen_ref.ref_hash = inputs->ref_hash;
out.snapshot.frozen_ref.last_txout_nonce = inputs->last_txout_nonce;
return out;
}
// ─────────────────────────────────────────────────────────────────────────────
// IWorkSource: share submission — Stage 4d (the hot path).
// ─────────────────────────────────────────────────────────────────────────────
nlohmann::json DGBWorkSource::mining_submit(
const std::string& username, const std::string& job_id,
const std::string& extranonce1, const std::string& extranonce2,
const std::string& ntime, const std::string& nonce,
const std::string& /*request_id*/,
const std::map<uint32_t, std::vector<unsigned char>>& /*merged_addresses*/,
const core::stratum::JobSnapshot* job)
{
// Stage 4d -- the hot path. Reconstruct the 80-byte block header from the
// JobSnapshot + miner inputs, run the DGB-Scrypt PoW digest, and place the
// result in EXACTLY ONE of three classes via the decision SSOT
// (coin/submit_classify.hpp): WonBlock -> dual-path broadcaster (#82),
// ShareAccept -> sharechain mint (try_mint_share -> #294), Reject -> stratum
// low-difficulty error. The header assembly mirrors btc::stratum byte-for-
// byte (the DGB header layout IS Bitcoin's); ONLY the PoW digest differs
// (scrypt_1024_1_1_256, the sole algo c2pool-dgb validates in V36).
// Stratum JSON-RPC error payload (false + [code, message, null]).
auto reject = [](int code, const char* msg) {
return nlohmann::json::array({
false, nlohmann::json::array({code, msg, nullptr})
});
};
if (!job) {
LOG_WARNING << "[DGB-STRATUM] submit reject (no JobSnapshot): user=" << username
<< " job=" << job_id;
return reject(21, "Job not found");
}
// 1. coinbase = coinb1 || extranonce1 || extranonce2 || coinb2
auto coinb1_bytes = ParseHex(job->coinb1);
auto en1_bytes = ParseHex(extranonce1);
auto en2_bytes = ParseHex(extranonce2);
auto coinb2_bytes = ParseHex(job->coinb2);
std::vector<uint8_t> coinbase;
coinbase.reserve(coinb1_bytes.size() + en1_bytes.size()
+ en2_bytes.size() + coinb2_bytes.size());
coinbase.insert(coinbase.end(), coinb1_bytes.begin(), coinb1_bytes.end());
coinbase.insert(coinbase.end(), en1_bytes.begin(), en1_bytes.end());
coinbase.insert(coinbase.end(), en2_bytes.begin(), en2_bytes.end());
coinbase.insert(coinbase.end(), coinb2_bytes.begin(), coinb2_bytes.end());
// 2. coinbase_txid = sha256d(coinbase) (non-witness txid for the merkle root)
uint256 coinbase_txid = Hash(
std::span<const uint8_t>(coinbase.data(), coinbase.size()));
// 3. merkle_root = ascend the frozen stratum merkle branches from the txid.
// Branches are LE-internal bytes (ParseHex+memcpy, NOT SetHex which
// reverses). Keep the parsed branches to hand to the share-mint path.
std::vector<uint256> branch_hashes;
branch_hashes.reserve(job->merkle_branches.size());
uint256 merkle_root = coinbase_txid;
for (const auto& branch_hex : job->merkle_branches) {
uint256 b;
auto bb = ParseHex(branch_hex);
if (bb.size() == 32) std::memcpy(b.begin(), bb.data(), 32);
branch_hashes.push_back(b);
merkle_root = merkle_pair(merkle_root, b);
}
// 4. header = version(LE) || prev(LE) || merkle_root || ntime(LE)
// || nbits(LE) || nonce(LE) -- prevhash arrives BE-display, so
// reverse to internal byte order.
auto prevhash_be = ParseHex(job->gbt_prevhash);
std::vector<uint8_t> prevhash_le(prevhash_be.rbegin(), prevhash_be.rend());
std::vector<uint8_t> header;
header.reserve(80);
push_u32_le(header, job->version);
header.insert(header.end(), prevhash_le.begin(), prevhash_le.end());
header.insert(header.end(), merkle_root.data(), merkle_root.data() + 32);
push_u32_le(header, parse_be_hex_u32(ntime));
push_u32_le(header, parse_be_hex_u32(job->nbits)); // header carries share bits
push_u32_le(header, parse_be_hex_u32(nonce));
if (header.size() != 80) {
LOG_WARNING << "[DGB-STRATUM] submit reject (reconstructed header "
<< header.size() << "B != 80): user=" << username
<< " job=" << job_id;
return reject(20, "Malformed header reconstruction");
}
// 5. DGB-Scrypt PoW digest (scrypt_1024_1_1_256, the ONLY algo V36 validates).
dgb::coin::u256 pow_hash = dgb::coin::scrypt_pow_hash(header.data());
// Expand both compact targets to u256 (compact_to_target SSOT). Fall back to
// a permissive diff-1 share target for jobs frozen before share_bits was set
// so a missing share target never silently rejects every share.
dgb::coin::u256 share_target = c2pool::dgb::compact_to_target(
job->share_bits != 0 ? job->share_bits : 0x1d00ffffu);
dgb::coin::u256 block_target = c2pool::dgb::compact_to_target(parse_be_hex_u32(
job->block_nbits.empty() ? job->nbits : job->block_nbits));
// 6. Classify via the Stage-4d SSOT (tighten-first: block target before share).
const dgb::coin::SubmitClass klass =
dgb::coin::classify_submission(pow_hash, block_target, share_target);
auto bump_accepted = [&] {
std::lock_guard<std::mutex> lk(workers_mutex_);
for (auto& kv : workers_) {
if (kv.second.username == username) { kv.second.accepted++; break; }
}
};
switch (klass) {
case dgb::coin::SubmitClass::WonBlock: {
// pow_hash <= block_target -> a full network block. NEVER drop it.
const uint32_t height = chain_.next_block_height();
LOG_WARNING << "[DGB-STRATUM-BLOCK] *** BLOCK FOUND *** user=" << username
<< " height~=" << height << " job=" << job_id;
// Serialize the block: header || tx_count || coinbase[+witness] || txs.
// A segwit-active coinbase is reserialized in BIP144 form with the
// 32-byte witness reserved value (digibyted validates the aa21a9ed
// commitment by hashing witness_root||reserved; a missing witness ->
// bad-witness-merkle-match -> block rejected).
std::vector<uint8_t> coinbase_serialized = coinbase;
if (job->segwit_active) {
const std::array<uint8_t, 2> marker_flag = {0x00, 0x01};
coinbase_serialized.insert(coinbase_serialized.begin() + 4,
marker_flag.begin(), marker_flag.end());
std::array<uint8_t, 34> witness_bytes{};
witness_bytes[0] = 0x01; // stack_count = 1
witness_bytes[1] = 0x20; // item_len = 32 (reserved value, all zero)
coinbase_serialized.insert(coinbase_serialized.end() - 4,
witness_bytes.begin(), witness_bytes.end());
}
static const std::vector<std::string> kEmptyTxData;
const std::vector<std::string>& txs =
job->tx_data ? *job->tx_data : kEmptyTxData;
std::vector<uint8_t> block_bytes;
block_bytes.reserve(80 + 9 + coinbase_serialized.size() + txs.size() * 256);
block_bytes.insert(block_bytes.end(), header.begin(), header.end());
push_varint(block_bytes, 1 + txs.size()); // total tx count (coinbase + others)
block_bytes.insert(block_bytes.end(),
coinbase_serialized.begin(), coinbase_serialized.end());
for (const auto& tx_hex : txs) {
auto tx_bytes = ParseHex(tx_hex);
block_bytes.insert(block_bytes.end(), tx_bytes.begin(), tx_bytes.end());
}
// Dual-path broadcaster (#82): submit_block_fn_ relays via P2P (primary)
// and falls back to the submitblock RPC; true iff it reached >=1 sink. A
// won block reaching NEITHER is a lost subsidy -- scream, never drop.
bool reached_network = false;
if (submit_block_fn_) {
try {
reached_network = submit_block_fn_(block_bytes, height);
} catch (const std::exception& e) {
LOG_ERROR << "[DGB-STRATUM-BLOCK] submit_block_fn threw: " << e.what();
}
if (!reached_network) {
LOG_ERROR << "[DGB-STRATUM-BLOCK] WON BLOCK height=" << height
<< " reached NEITHER P2P relay NOR submitblock RPC -- "
<< "lost subsidy!";
}
} else {
LOG_ERROR << "[DGB-STRATUM-BLOCK] no submit_block_fn wired -- WON BLOCK"
<< " height=" << height << " not broadcast -- lost subsidy!";
}
bump_accepted();
return nlohmann::json(true);
}
case dgb::coin::SubmitClass::ShareAccept: {
// share_target >= pow_hash > block_target -> meets sharechain difficulty
// but not the full block. Hand the found-share fields to the run-loop
// mint dispatch (try_mint_share -> mint_local_share_with_ratchet, #294).
MintShareInputs in;
in.header_bytes = header;
in.coinbase_bytes = coinbase;
in.subsidy = job->subsidy;
in.prev_share = job->prev_share_hash;
in.merkle_branches = branch_hashes;
in.payout_script = core::address_to_script(username);
// Redistribute V2 (#307): a miner with empty/broken stratum creds
// yields no payout script. When the operator opted into a
// --redistribute policy (fallback bound) let it choose the pubkey this
// node stamps onto the minted share; else leave it empty (byte-
// identical to before). Fail-safe: an empty fallback is left empty.
if (in.payout_script.empty()) {
FallbackPayoutFn fb;
{ std::lock_guard<std::mutex> lk(fallback_payout_mutex_); fb = fallback_payout_fn_; }
if (fb) {
in.payout_script = fb();
if (!in.payout_script.empty())
LOG_INFO << "[DGB-STRATUM-SHARE] empty payout addr (user=" << username
<< ") -> redistribute policy stamped fallback script ("
<< in.payout_script.size() << "B)";
}
}
in.segwit_active = job->segwit_active;
uint256 share_hash = try_mint_share(in);
if (!share_hash.IsNull()) {
LOG_INFO << "[DGB-STRATUM-SHARE] ACCEPTED + MINTED user=" << username
<< " share_hash=" << share_hash.GetHex().substr(0, 16)
<< " job=" << job_id;
} else {
// PoW was valid (cleared the share target) so the miner still gets a
// success reply; the share just earned no sharechain credit (no mint
// fn wired, or the mint deferred/failed). try_mint_share logs the
// reason -- never a silent drop.
LOG_INFO << "[DGB-STRATUM-SHARE] accepted (no sharechain credit) user="
<< username << " job=" << job_id;
}
bump_accepted();
return nlohmann::json(true);
}
case dgb::coin::SubmitClass::Reject:
default:
return reject(23, "Low difficulty share");
}
}
double DGBWorkSource::compute_share_difficulty(
const std::string& /*coinb1*/, const std::string& /*coinb2*/,
const std::string& /*extranonce1*/, const std::string& /*extranonce2*/,
const std::string& /*ntime*/, const std::string& /*nonce*/,
uint32_t /*version*/, const std::string& /*prevhash_hex*/,
const std::string& /*nbits_hex*/,
const std::vector<std::string>& /*merkle_branches*/) const
{
// Stage 4b/4c: reconstruct the 80-byte header from (coinb1+en1+en2+coinb2)
// merkle-rooted with merkle_branches, then scrypt_1024_1_1_256(header) and
// return diff1 / pow_hash. Until then the coin-agnostic StratumServer must
// not credit pseudoshares it cannot score, so we return 0.0 — the documented
// parse-error / not-yet sentinel that the vardiff gate already treats as a
// hard reject (no garbage difficulty leaks into the rate monitor).
return 0.0;
}
// ─────────────────────────────────────────────────────────────────────────────
// DGB-specific control surface
// ─────────────────────────────────────────────────────────────────────────────
void DGBWorkSource::set_best_share_hash_fn(std::function<uint256()> fn)
{
std::lock_guard<std::mutex> lk(best_share_mutex_);
best_share_hash_fn_ = std::move(fn);
}
void DGBWorkSource::set_mint_share_fn(MintShareFn fn)
{
std::lock_guard<std::mutex> lk(mint_share_mutex_);
mint_share_fn_ = std::move(fn);
}
void DGBWorkSource::set_fallback_payout_fn(FallbackPayoutFn fn)
{
std::lock_guard<std::mutex> lk(fallback_payout_mutex_);
fallback_payout_fn_ = std::move(fn);
}
void DGBWorkSource::set_pplns_inputs_fn(PplnsInputsFn fn)
{
std::lock_guard<std::mutex> lk(pplns_inputs_mutex_);
pplns_inputs_fn_ = std::move(fn);
}
uint256 DGBWorkSource::try_mint_share(const MintShareInputs& in) const
{
MintShareFn fn;
{
std::lock_guard<std::mutex> lk(mint_share_mutex_);
fn = mint_share_fn_; // copy so a concurrent set cannot tear it out mid-call
}
if (!fn) {
LOG_WARNING << "[DGB-STRATUM] share met share-target but no mint callback "
"wired -- share NOT recorded (set_mint_share_fn unbound). "
"No silent drop: logged.";
return uint256{};
}
return fn(in);
}
} // namespace dgb::stratum