Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 49 additions & 9 deletions src/impl/dgb/stratum/work_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#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>
Expand Down Expand Up @@ -333,16 +334,49 @@ std::pair<std::string, std::string> DGBWorkSource::get_coinbase_parts() const
}

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
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
{
// Stage 4c: build per-connection coinbase using the SSOT gentx assembler
// (coin/gentx_coinbase.hpp) + ShareTracker ref_hash + PPLNS payout map.
// For now return an empty result; sessions calling this will get an empty
// job and skip pushing work, which is safe but non-functional.
return {};
// 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;
}

// ─────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -610,6 +644,12 @@ void DGBWorkSource::set_fallback_payout_fn(FallbackPayoutFn fn)
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;
Expand Down
27 changes: 27 additions & 0 deletions src/impl/dgb/stratum/work_source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include <core/stratum_work_source.hpp>
#include <core/uint256.hpp>
#include <core/pow.hpp> // core::SubsidyFunc — embedded coinbasevalue SSOT feed
#include <impl/dgb/coin/connection_coinbase.hpp> // ConnCoinbasePplnsInputs (PPLNS->coinbase SSOT)

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

/// Producer seam for the per-connection coinbase (Phase B live-wire).
/// Given the share-chain tip a miner builds ON TOP OF, plus this session's
/// extranonce1 and resolved payout/merged scripts, returns the fully
/// populated PPLNS inputs (weight map sourced from the ShareTracker, ref_hash,
/// subsidy, donation script). Bound once at startup in main_dgb.cpp where the
/// ShareTracker + CoinParams are in scope; the tracker walk lives THERE so no
/// sharechain logic leaks into the work source. While UNBOUND,
/// build_connection_coinbase() returns an empty result (byte-identical to the
/// pre-wire stub -- no behavior change until the producer is bound). Returning
/// std::nullopt (e.g. tip not yet known) also yields an empty, safe job. The
/// emitted coinbase is byte-identical to the verifier's
/// generate_share_transaction() BY CONSTRUCTION: both delegate to the single
/// compute_pplns_payout_split() SSOT via build_connection_coinbase_from_pplns.
using PplnsInputsFn = std::function<std::optional<dgb::coin::ConnCoinbasePplnsInputs>(
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)>;
void set_pplns_inputs_fn(PplnsInputsFn fn);

/// Dispatch one share-difficulty submission to the bound mint callback.
/// The stage-4d mining_submit classify branch calls this on the
/// "pow_hash <= share target" outcome. Returns the minted share hash, or a
Expand Down Expand Up @@ -269,6 +290,12 @@ class DGBWorkSource : public core::stratum::IWorkSource
mutable std::mutex fallback_payout_mutex_;
FallbackPayoutFn fallback_payout_fn_;

// Per-connection coinbase PPLNS-inputs producer (#327/#330 live-wire).
// Empty until set_pplns_inputs_fn() bound in main_dgb; while empty the
// per-connection coinbase path returns an empty job (pre-wire behavior).
mutable std::mutex pplns_inputs_mutex_;
PplnsInputsFn pplns_inputs_fn_;

// Template cache (filled lazily; invalidated when work_generation_ bumps)
// Stage 4c populates these.
mutable std::mutex template_mutex_;
Expand Down
87 changes: 87 additions & 0 deletions src/impl/dgb/test/work_source_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <impl/dgb/stratum/work_source.hpp>
#include <impl/dgb/coin/header_chain.hpp>
#include <impl/dgb/coin/mempool.hpp>
#include <impl/dgb/coin/connection_coinbase.hpp> // build_connection_coinbase_from_pplns (SSOT under test)
#include <impl/dgb/config_coin.hpp> // dgb::CoinParams::subsidy (oracle SSOT)

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

#include <gtest/gtest.h>

#include <map>
#include <memory>
#include <optional>

Expand Down Expand Up @@ -520,4 +522,89 @@ TEST(DgbWorkSource, GbtPrevhashGetterMatchesTemplateField)
expected);
}


// ── Per-connection coinbase live-wire (set_pplns_inputs_fn / build_connection_coinbase) ──
// The producer half of the Phase-B coinbase wire: build_connection_coinbase
// delegates to the build_connection_coinbase_from_pplns SSOT (which the verifier
// also calls). These pin three contracts: (1) UNBOUND -> empty job (pre-wire
// byte-identical no-op), (2) bound -> coinb1/coinb2 byte-identical to the SSOT
// called directly with the same inputs (proving build_connection_coinbase is a
// pure pass-through, not a second payout implementation), (3) producer nullopt
// -> empty job.
namespace {

using Script = std::vector<unsigned char>;

// A fixed, fully-populated PPLNS input set (two payout scripts, v36 no-finder).
dgb::coin::ConnCoinbasePplnsInputs sample_pplns_inputs()
{
dgb::coin::ConnCoinbasePplnsInputs in;
in.coinbase_script = Script{0x03, 0x01, 0x02, 0x03}; // BIP34-ish scriptSig
in.weights = { {Script{0x76, 0xa9, 0x14, 0xaa}, uint288(3)},
{Script{0x76, 0xa9, 0x14, 0xbb}, uint288(1)} };
in.total_weight = uint288(4);
in.subsidy = 1234567;
in.use_v36_pplns = true;
in.donation_script = Script{0xa9, 0x14, 0xcc};
in.ref_hash = uint256(std::vector<unsigned char>(32, 0xab));
in.last_txout_nonce = 0x0102030405060708ULL;
return in;
}

} // namespace

TEST(DgbWorkSource, ConnectionCoinbaseEmptyUntilPplnsInputsWired)
{
Fixture fx;
auto ws = fx.make();
auto r = ws->build_connection_coinbase(
uint256::ZERO, "deadbeef", Script{}, {});
EXPECT_TRUE(r.coinb1.empty());
EXPECT_TRUE(r.coinb2.empty());
}

TEST(DgbWorkSource, ConnectionCoinbaseDelegatesToPplnsSsotByteIdentical)
{
Fixture fx;
auto ws = fx.make();
const auto inputs = sample_pplns_inputs();

// Bind a producer that hands back the fixed inputs regardless of args.
ws->set_pplns_inputs_fn(
[&](const uint256&, const std::string&, const Script&,
const std::vector<std::pair<uint32_t, Script>>&)
-> std::optional<dgb::coin::ConnCoinbasePplnsInputs> {
return inputs;
});

auto wired = ws->build_connection_coinbase(
uint256(std::vector<unsigned char>(32, 0x11)), "cafef00d", Script{0x01}, {});

// The SSOT called directly with the same inputs is the oracle.
auto oracle = dgb::coin::build_connection_coinbase_from_pplns(inputs);

EXPECT_EQ(wired.coinb1, oracle.coinb1);
EXPECT_EQ(wired.coinb2, oracle.coinb2);
EXPECT_FALSE(wired.coinb1.empty());
// Consensus ref fields are frozen onto the snapshot for the submit path.
EXPECT_EQ(wired.snapshot.frozen_ref.ref_hash, inputs.ref_hash);
EXPECT_EQ(wired.snapshot.frozen_ref.last_txout_nonce, inputs.last_txout_nonce);
EXPECT_EQ(wired.snapshot.subsidy, inputs.subsidy);
}

TEST(DgbWorkSource, ConnectionCoinbaseProducerNulloptYieldsEmptyJob)
{
Fixture fx;
auto ws = fx.make();
ws->set_pplns_inputs_fn(
[&](const uint256&, const std::string&, const Script&,
const std::vector<std::pair<uint32_t, Script>>&)
-> std::optional<dgb::coin::ConnCoinbasePplnsInputs> {
return std::nullopt; // tip not yet known
});
auto r = ws->build_connection_coinbase(uint256::ZERO, "00", Script{}, {});
EXPECT_TRUE(r.coinb1.empty());
EXPECT_TRUE(r.coinb2.empty());
}

} // namespace
Loading