Skip to content

Commit 2b50be4

Browse files
committed
dgb(phase-b): wire build_connection_coinbase to PPLNS->coinbase SSOT via producer seam
Replace the empty build_connection_coinbase() stub with a delegation to build_connection_coinbase_from_pplns (the #330 SSOT that itself routes through the single compute_pplns_payout_split the verifier calls). A new set_pplns_inputs_fn producer seam supplies the PPLNS weight map (walked from the ShareTracker), ref_hash, subsidy and donation script; the tracker walk stays in main_dgb where the ShareTracker + CoinParams are in scope, so no sharechain logic leaks into the work source. Fenced: while the seam is UNBOUND (or returns nullopt) the method is byte-identical to the pre-wire stub (empty job, no work pushed) -- runtime behavior is unchanged until main_dgb binds the producer (follow-on slice). The emitted coinb1/coinb2 are byte-identical to the verifier per a KAT comparing the wired output to the SSOT called directly with the same inputs. 3 KATs added (empty-until-wired, byte-identical delegation, nullopt->empty); dgb_work_source_test 26/26 green.
1 parent 6efada0 commit 2b50be4

3 files changed

Lines changed: 163 additions & 9 deletions

File tree

src/impl/dgb/stratum/work_source.cpp

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <impl/dgb/coin/hash_format.hpp>
2626
#include <impl/dgb/coin/scrypt_pow.hpp> // scrypt_pow_hash (DGB-Scrypt PoW SSOT)
2727
#include <impl/dgb/coin/submit_classify.hpp> // classify_submission (Stage-4d decision SSOT)
28+
#include <impl/dgb/coin/connection_coinbase.hpp> // build_connection_coinbase_from_pplns SSOT
2829
#include <impl/dgb/coin/header_sample_build.hpp>// compact_to_target (compact bits -> u256)
2930

3031
#include <core/log.hpp>
@@ -333,16 +334,49 @@ std::pair<std::string, std::string> DGBWorkSource::get_coinbase_parts() const
333334
}
334335

335336
core::stratum::CoinbaseResult DGBWorkSource::build_connection_coinbase(
336-
const uint256& /*prev_share_hash*/,
337-
const std::string& /*extranonce1_hex*/,
338-
const std::vector<unsigned char>& /*payout_script*/,
339-
const std::vector<std::pair<uint32_t, std::vector<unsigned char>>>& /*merged_addrs*/) const
337+
const uint256& prev_share_hash,
338+
const std::string& extranonce1_hex,
339+
const std::vector<unsigned char>& payout_script,
340+
const std::vector<std::pair<uint32_t, std::vector<unsigned char>>>& merged_addrs) const
340341
{
341-
// Stage 4c: build per-connection coinbase using the SSOT gentx assembler
342-
// (coin/gentx_coinbase.hpp) + ShareTracker ref_hash + PPLNS payout map.
343-
// For now return an empty result; sessions calling this will get an empty
344-
// job and skip pushing work, which is safe but non-functional.
345-
return {};
342+
// Phase B live-wire: delegate to the PPLNS->coinbase SSOT
343+
// (build_connection_coinbase_from_pplns), which itself routes through the
344+
// single compute_pplns_payout_split() the verifier uses -- so the coinbase a
345+
// miner hashes here is byte-identical to the one generate_share_transaction()
346+
// enforces, by construction (no second payout implementation to keep in sync).
347+
//
348+
// The PPLNS inputs (weight map walked from the ShareTracker, ref_hash,
349+
// subsidy, donation script) are produced by the seam bound in main_dgb.cpp
350+
// -- the tracker walk lives there, not in the work source. While the seam is
351+
// UNBOUND (or returns nullopt) this is byte-identical to the pre-wire stub:
352+
// an empty result, so the session pushes no work (safe, non-functional).
353+
PplnsInputsFn fn;
354+
{
355+
std::lock_guard<std::mutex> lk(pplns_inputs_mutex_);
356+
fn = pplns_inputs_fn_; // copy under lock; a concurrent set_*_fn() cannot
357+
// tear it out mid-call.
358+
}
359+
if (!fn)
360+
return {}; // unbound: pre-wire behavior (empty job).
361+
362+
std::optional<dgb::coin::ConnCoinbasePplnsInputs> inputs =
363+
fn(prev_share_hash, extranonce1_hex, payout_script, merged_addrs);
364+
if (!inputs)
365+
return {}; // producer declined (e.g. tip not yet known) -> safe empty job.
366+
367+
dgb::coin::ConnCoinbaseParts parts =
368+
dgb::coin::build_connection_coinbase_from_pplns(*inputs);
369+
370+
core::stratum::CoinbaseResult out;
371+
out.coinb1 = std::move(parts.coinb1);
372+
out.coinb2 = std::move(parts.coinb2);
373+
// Freeze the consensus-bearing ref fields the submit path (mining_submit)
374+
// re-derives the won-block reconstruct from; the remaining snapshot fields
375+
// (merkle_branches / segwit) are populated by the template-cache follow-on.
376+
out.snapshot.subsidy = inputs->subsidy;
377+
out.snapshot.frozen_ref.ref_hash = inputs->ref_hash;
378+
out.snapshot.frozen_ref.last_txout_nonce = inputs->last_txout_nonce;
379+
return out;
346380
}
347381

348382
// ─────────────────────────────────────────────────────────────────────────────
@@ -610,6 +644,12 @@ void DGBWorkSource::set_fallback_payout_fn(FallbackPayoutFn fn)
610644
fallback_payout_fn_ = std::move(fn);
611645
}
612646

647+
void DGBWorkSource::set_pplns_inputs_fn(PplnsInputsFn fn)
648+
{
649+
std::lock_guard<std::mutex> lk(pplns_inputs_mutex_);
650+
pplns_inputs_fn_ = std::move(fn);
651+
}
652+
613653
uint256 DGBWorkSource::try_mint_share(const MintShareInputs& in) const
614654
{
615655
MintShareFn fn;

src/impl/dgb/stratum/work_source.hpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include <core/stratum_work_source.hpp>
4343
#include <core/uint256.hpp>
4444
#include <core/pow.hpp> // core::SubsidyFunc — embedded coinbasevalue SSOT feed
45+
#include <impl/dgb/coin/connection_coinbase.hpp> // ConnCoinbasePplnsInputs (PPLNS->coinbase SSOT)
4546

4647
#include <atomic>
4748
#include <cstdint>
@@ -209,6 +210,26 @@ class DGBWorkSource : public core::stratum::IWorkSource
209210
using FallbackPayoutFn = std::function<std::vector<unsigned char>()>;
210211
void set_fallback_payout_fn(FallbackPayoutFn fn);
211212

213+
/// Producer seam for the per-connection coinbase (Phase B live-wire).
214+
/// Given the share-chain tip a miner builds ON TOP OF, plus this session's
215+
/// extranonce1 and resolved payout/merged scripts, returns the fully
216+
/// populated PPLNS inputs (weight map sourced from the ShareTracker, ref_hash,
217+
/// subsidy, donation script). Bound once at startup in main_dgb.cpp where the
218+
/// ShareTracker + CoinParams are in scope; the tracker walk lives THERE so no
219+
/// sharechain logic leaks into the work source. While UNBOUND,
220+
/// build_connection_coinbase() returns an empty result (byte-identical to the
221+
/// pre-wire stub -- no behavior change until the producer is bound). Returning
222+
/// std::nullopt (e.g. tip not yet known) also yields an empty, safe job. The
223+
/// emitted coinbase is byte-identical to the verifier's
224+
/// generate_share_transaction() BY CONSTRUCTION: both delegate to the single
225+
/// compute_pplns_payout_split() SSOT via build_connection_coinbase_from_pplns.
226+
using PplnsInputsFn = std::function<std::optional<dgb::coin::ConnCoinbasePplnsInputs>(
227+
const uint256& prev_share_hash,
228+
const std::string& extranonce1_hex,
229+
const std::vector<unsigned char>& payout_script,
230+
const std::vector<std::pair<uint32_t, std::vector<unsigned char>>>& merged_addrs)>;
231+
void set_pplns_inputs_fn(PplnsInputsFn fn);
232+
212233
/// Dispatch one share-difficulty submission to the bound mint callback.
213234
/// The stage-4d mining_submit classify branch calls this on the
214235
/// "pow_hash <= share target" outcome. Returns the minted share hash, or a
@@ -269,6 +290,12 @@ class DGBWorkSource : public core::stratum::IWorkSource
269290
mutable std::mutex fallback_payout_mutex_;
270291
FallbackPayoutFn fallback_payout_fn_;
271292

293+
// Per-connection coinbase PPLNS-inputs producer (#327/#330 live-wire).
294+
// Empty until set_pplns_inputs_fn() bound in main_dgb; while empty the
295+
// per-connection coinbase path returns an empty job (pre-wire behavior).
296+
mutable std::mutex pplns_inputs_mutex_;
297+
PplnsInputsFn pplns_inputs_fn_;
298+
272299
// Template cache (filled lazily; invalidated when work_generation_ bumps)
273300
// Stage 4c populates these.
274301
mutable std::mutex template_mutex_;

src/impl/dgb/test/work_source_test.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <impl/dgb/stratum/work_source.hpp>
1717
#include <impl/dgb/coin/header_chain.hpp>
1818
#include <impl/dgb/coin/mempool.hpp>
19+
#include <impl/dgb/coin/connection_coinbase.hpp> // build_connection_coinbase_from_pplns (SSOT under test)
1920
#include <impl/dgb/config_coin.hpp> // dgb::CoinParams::subsidy (oracle SSOT)
2021

2122
#include <core/pow.hpp> // core::SubsidyFunc
@@ -24,6 +25,7 @@
2425

2526
#include <gtest/gtest.h>
2627

28+
#include <map>
2729
#include <memory>
2830
#include <optional>
2931

@@ -520,4 +522,89 @@ TEST(DgbWorkSource, GbtPrevhashGetterMatchesTemplateField)
520522
expected);
521523
}
522524

525+
526+
// ── Per-connection coinbase live-wire (set_pplns_inputs_fn / build_connection_coinbase) ──
527+
// The producer half of the Phase-B coinbase wire: build_connection_coinbase
528+
// delegates to the build_connection_coinbase_from_pplns SSOT (which the verifier
529+
// also calls). These pin three contracts: (1) UNBOUND -> empty job (pre-wire
530+
// byte-identical no-op), (2) bound -> coinb1/coinb2 byte-identical to the SSOT
531+
// called directly with the same inputs (proving build_connection_coinbase is a
532+
// pure pass-through, not a second payout implementation), (3) producer nullopt
533+
// -> empty job.
534+
namespace {
535+
536+
using Script = std::vector<unsigned char>;
537+
538+
// A fixed, fully-populated PPLNS input set (two payout scripts, v36 no-finder).
539+
dgb::coin::ConnCoinbasePplnsInputs sample_pplns_inputs()
540+
{
541+
dgb::coin::ConnCoinbasePplnsInputs in;
542+
in.coinbase_script = Script{0x03, 0x01, 0x02, 0x03}; // BIP34-ish scriptSig
543+
in.weights = { {Script{0x76, 0xa9, 0x14, 0xaa}, uint288(3)},
544+
{Script{0x76, 0xa9, 0x14, 0xbb}, uint288(1)} };
545+
in.total_weight = uint288(4);
546+
in.subsidy = 1234567;
547+
in.use_v36_pplns = true;
548+
in.donation_script = Script{0xa9, 0x14, 0xcc};
549+
in.ref_hash = uint256(std::vector<unsigned char>(32, 0xab));
550+
in.last_txout_nonce = 0x0102030405060708ULL;
551+
return in;
552+
}
553+
554+
} // namespace
555+
556+
TEST(DgbWorkSource, ConnectionCoinbaseEmptyUntilPplnsInputsWired)
557+
{
558+
Fixture fx;
559+
auto ws = fx.make();
560+
auto r = ws->build_connection_coinbase(
561+
uint256::ZERO, "deadbeef", Script{}, {});
562+
EXPECT_TRUE(r.coinb1.empty());
563+
EXPECT_TRUE(r.coinb2.empty());
564+
}
565+
566+
TEST(DgbWorkSource, ConnectionCoinbaseDelegatesToPplnsSsotByteIdentical)
567+
{
568+
Fixture fx;
569+
auto ws = fx.make();
570+
const auto inputs = sample_pplns_inputs();
571+
572+
// Bind a producer that hands back the fixed inputs regardless of args.
573+
ws->set_pplns_inputs_fn(
574+
[&](const uint256&, const std::string&, const Script&,
575+
const std::vector<std::pair<uint32_t, Script>>&)
576+
-> std::optional<dgb::coin::ConnCoinbasePplnsInputs> {
577+
return inputs;
578+
});
579+
580+
auto wired = ws->build_connection_coinbase(
581+
uint256(std::vector<unsigned char>(32, 0x11)), "cafef00d", Script{0x01}, {});
582+
583+
// The SSOT called directly with the same inputs is the oracle.
584+
auto oracle = dgb::coin::build_connection_coinbase_from_pplns(inputs);
585+
586+
EXPECT_EQ(wired.coinb1, oracle.coinb1);
587+
EXPECT_EQ(wired.coinb2, oracle.coinb2);
588+
EXPECT_FALSE(wired.coinb1.empty());
589+
// Consensus ref fields are frozen onto the snapshot for the submit path.
590+
EXPECT_EQ(wired.snapshot.frozen_ref.ref_hash, inputs.ref_hash);
591+
EXPECT_EQ(wired.snapshot.frozen_ref.last_txout_nonce, inputs.last_txout_nonce);
592+
EXPECT_EQ(wired.snapshot.subsidy, inputs.subsidy);
593+
}
594+
595+
TEST(DgbWorkSource, ConnectionCoinbaseProducerNulloptYieldsEmptyJob)
596+
{
597+
Fixture fx;
598+
auto ws = fx.make();
599+
ws->set_pplns_inputs_fn(
600+
[&](const uint256&, const std::string&, const Script&,
601+
const std::vector<std::pair<uint32_t, Script>>&)
602+
-> std::optional<dgb::coin::ConnCoinbasePplnsInputs> {
603+
return std::nullopt; // tip not yet known
604+
});
605+
auto r = ws->build_connection_coinbase(uint256::ZERO, "00", Script{}, {});
606+
EXPECT_TRUE(r.coinb1.empty());
607+
EXPECT_TRUE(r.coinb2.empty());
608+
}
609+
523610
} // namespace

0 commit comments

Comments
 (0)