diff --git a/src/impl/dgb/coin/connection_coinbase.hpp b/src/impl/dgb/coin/connection_coinbase.hpp index b6071bad1..481a097d8 100644 --- a/src/impl/dgb/coin/connection_coinbase.hpp +++ b/src/impl/dgb/coin/connection_coinbase.hpp @@ -21,12 +21,14 @@ // ============================================================================ #include "gentx_coinbase.hpp" +#include "pplns_payout_split.hpp" // compute_pplns_payout_split SSOT #include #include // HexStr #include // Span #include +#include #include #include #include @@ -103,4 +105,50 @@ inline ConnCoinbaseParts build_connection_coinbase_parts(const ConnCoinbaseInput return out; } + +// ============================================================================ +// PPLNS-sourced per-connection coinbase (the #328/#329 SSOT bridge). +// +// Instead of pre-resolved payout_outputs/donation_amount, the caller passes the +// raw PPLNS weight map + subsidy and we delegate the amount split to +// compute_pplns_payout_split() -- the SAME helper share_check.hpp +// generate_share_transaction() (the verification path) calls (see #329). The +// per-connection coinbase a miner hashes and the coinbase the share check +// enforces are therefore byte-identical on every payout satoshi BY +// CONSTRUCTION: there is exactly one payout implementation, not two that must +// be kept in agreement. No payout arithmetic lives here -- pure delegation + +// field forwarding into build_connection_coinbase_parts(). +// ============================================================================ +struct ConnCoinbasePplnsInputs +{ + std::vector coinbase_script; // scriptSig (BIP34 height + tag) + std::optional> segwit_commitment_script; + // PPLNS weight map + total, exactly as produced by the ShareTracker + // (share_tracker.hpp get_v36_decayed_cumulative_weights / get_cumulative_weights). + std::map, uint288> weights; + uint288 total_weight; + uint64_t subsidy{0}; // block subsidy + fees to split + bool use_v36_pplns{true}; // V36 (no finder fee) vs pre-V36 + std::vector finder_script; // pre-V36 0.5% finder-fee target + std::vector donation_script; + uint256 ref_hash; // p2pool ref_hash (32B) + uint64_t last_txout_nonce{0}; // OP_RETURN nonce (extranonce slot) +}; + +inline ConnCoinbaseParts build_connection_coinbase_from_pplns(const ConnCoinbasePplnsInputs& in) +{ + PplnsPayoutSplit split = compute_pplns_payout_split( + in.weights, in.total_weight, in.subsidy, in.use_v36_pplns, in.finder_script); + + ConnCoinbaseInputs ci; + ci.coinbase_script = in.coinbase_script; + ci.segwit_commitment_script = in.segwit_commitment_script; + ci.payout_outputs = std::move(split.payout_outputs); + ci.donation_amount = split.donation_amount; + ci.donation_script = in.donation_script; + ci.ref_hash = in.ref_hash; + ci.last_txout_nonce = in.last_txout_nonce; + return build_connection_coinbase_parts(ci); +} + } // namespace dgb::coin diff --git a/src/impl/dgb/test/connection_coinbase_test.cpp b/src/impl/dgb/test/connection_coinbase_test.cpp index 7c2146baf..430556bdc 100644 --- a/src/impl/dgb/test/connection_coinbase_test.cpp +++ b/src/impl/dgb/test/connection_coinbase_test.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -101,4 +102,106 @@ TEST(ConnCoinbase, RefOpReturnLayout) { EXPECT_EQ(tohex(op), std::string("6a28") + ref32 + "0807060504030201"); } + +// ============================================================================ +// (5)-(8) PPLNS SSOT wiring: build_connection_coinbase_from_pplns delegates the +// payout split to compute_pplns_payout_split (the #329 verification SSOT) and +// forwards every non-payout field unchanged into build_connection_coinbase_parts. +// +// These are DELEGATION-IDENTITY tests, not a second payout implementation: each +// asserts the PPLNS-sourced path produces bytes identical to the manual path +// fed by the SAME SSOT split. Reintroducing inline payout math, dropping the +// v36 floor, or mis-forwarding a field would diverge them. The split MATH +// itself is pinned independently in pplns_payout_split_test.cpp. + +using Script = std::vector; + +// Build the manual reference: run the SSOT split, hand it to the parts builder. +dgb::coin::ConnCoinbaseParts manual_from_split( + const std::map& w, const uint288& total, uint64_t subsidy, + bool v36, const Script& finder, + const std::optional