99#include " share_types.hpp"
1010
1111#include < impl/dgb/coin/gentx_coinbase.hpp>
12+ #include < impl/dgb/coin/pplns_payout_split.hpp>
1213
1314#include < core/coin_params.hpp>
1415#include < core/hash.hpp>
@@ -1029,7 +1030,6 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
10291030 // donation = subsidy - sum(amounts)
10301031
10311032 auto gst_t1 = std::chrono::steady_clock::now (); // after PPLNS walk
1032- std::map<std::vector<unsigned char >, uint64_t > amounts;
10331033
10341034 // Periodic dump of PPLNS weights for cross-impl comparison
10351035 {
@@ -1051,89 +1051,36 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
10511051 }
10521052 }
10531053
1054- if (!total_weight.IsNull ())
1055- {
1056- for (auto & [script, weight] : weights)
1057- {
1058- uint64_t amount;
1059- if (use_v36_pplns)
1060- {
1061- // V36: amounts[script] = subsidy * weight / total_weight
1062- uint288 num = uint288 (subsidy) * weight;
1063- amount = (num / total_weight).GetLow64 ();
1064- }
1065- else
1066- {
1067- // Pre-V36: amounts[script] = subsidy * (199 * weight) / (200 * total_weight)
1068- uint288 num = uint288 (subsidy) * (weight * 199 );
1069- uint288 den = total_weight * 200 ;
1070- amount = (num / den).GetLow64 ();
1071- }
1072- if (amount > 0 )
1073- amounts[script] = amount;
1074- }
1075- }
1076-
1077- // Pre-V36: add 0.5% finder fee to share creator
1078- if (!use_v36_pplns)
1079- {
1080- auto finder_script = get_share_script (&share);
1081- amounts[finder_script] += subsidy / 200 ;
1082- }
1054+ // --- 2-3. PPLNS amount math + consensus output ordering (SSOT) ----------
1055+ // Steps 2-3 are now the single tracker-free helper compute_pplns_payout_split().
1056+ // The per-connection Stratum coinbase assembler draws the SAME split, so
1057+ // emission and this verification path cannot diverge on a payout satoshi.
1058+ // The helper is a verbatim lift of the former inline math (exact V36 /
1059+ // pre-V36 formulae, 0.5% finder fee, >=1-sat V36 donation floor with the
1060+ // (amount, script) tiebreak, and the ascending sort truncated to [-4000:]).
1061+ // Reference: frstrtr/p2pool-merged-v36 data.py generate_transaction().
1062+ auto finder_script = get_share_script (&share);
1063+ auto pplns_split = dgb::coin::compute_pplns_payout_split (
1064+ weights, total_weight, subsidy, use_v36_pplns, finder_script);
1065+ auto & payout_outputs = pplns_split.payout_outputs ;
1066+ uint64_t donation_amount = pplns_split.donation_amount ;
10831067
1084- // Donation output = subsidy minus sum of all payout amounts
1085- uint64_t sum_amounts = 0 ;
1086- for (auto & [s, a] : amounts)
1087- sum_amounts += a;
1088- uint64_t donation_amount = (subsidy > sum_amounts) ? (subsidy - sum_amounts) : 0 ;
1068+ auto gst_t2 = std::chrono::steady_clock::now (); // after amounts+ordering
10891069
1090- // Dump amounts for cross-impl debugging
1070+ // Dump payouts for cross-impl debugging
10911071 if (dump_diag) {
1092- LOG_DEBUG_DIAG << " [GST-AMOUNTS] subsidy=" << subsidy << " addrs=" << amounts.size ()
1072+ uint64_t sum_amounts = 0 ;
1073+ for (auto & [s, a] : payout_outputs) sum_amounts += a;
1074+ LOG_DEBUG_DIAG << " [GST-AMOUNTS] subsidy=" << subsidy << " addrs=" << payout_outputs.size ()
10931075 << " sum=" << sum_amounts << " donation=" << donation_amount
10941076 << " prev=" << prev_hash.GetHex ().substr (0 ,16 );
1095- for (auto & [s, a] : amounts ) {
1077+ for (auto & [s, a] : payout_outputs ) {
10961078 static const char * HX = " 0123456789abcdef" ;
10971079 std::string sh; for (size_t i = 0 ; i < std::min (s.size (), size_t (10 )); ++i) { sh += HX [s[i]>>4 ]; sh += HX [s[i]&0xf ]; }
10981080 LOG_DEBUG_DIAG << " [GST-AMOUNTS] " << sh << " ... = " << a;
10991081 }
11001082 }
11011083
1102- // V36 consensus: donation output must carry >= 1 satoshi (a60f7f7f)
1103- if (use_v36_pplns) {
1104- if (donation_amount < 1 && subsidy > 0 && !amounts.empty ()) {
1105- // Deduct 1 sat from the largest miner payout
1106- // Deterministic tiebreak: (amount, script) — largest script wins when equal
1107- auto largest = std::max_element (amounts.begin (), amounts.end (),
1108- [](const auto & a, const auto & b) {
1109- if (a.second != b.second ) return a.second < b.second ;
1110- return a.first < b.first ;
1111- });
1112- if (largest != amounts.end () && largest->second > 0 ) {
1113- largest->second -= 1 ;
1114- sum_amounts -= 1 ;
1115- donation_amount = subsidy - sum_amounts;
1116- }
1117- }
1118- }
1119-
1120- // --- 3. Build sorted output list ---
1121- auto gst_t2 = std::chrono::steady_clock::now (); // after amounts
1122- // Python: sorted(dests, key=lambda a: (amounts[a], a))[-4000:]
1123- // = ascending by (amount, script), keep last 4000 (highest amounts)
1124- std::vector<std::pair<std::vector<unsigned char >, uint64_t >> payout_outputs (
1125- amounts.begin (), amounts.end ());
1126- std::sort (payout_outputs.begin (), payout_outputs.end (),
1127- [](const auto & a, const auto & b) {
1128- if (a.second != b.second ) return a.second < b.second ; // asc by amount
1129- return a.first < b.first ; // asc by script for tie-breaking
1130- });
1131-
1132- // Keep last MAX_OUTPUTS (highest amounts), matching Python's [-4000:]
1133- constexpr size_t MAX_OUTPUTS = 4000 ;
1134- if (payout_outputs.size () > MAX_OUTPUTS )
1135- payout_outputs.erase (payout_outputs.begin (), payout_outputs.end () - MAX_OUTPUTS );
1136-
11371084 // --- 4. Serialise the coinbase transaction ---
11381085 // Wire layout (version|vin|vouts|locktime) and txid are produced by the
11391086 // SSOT assembler dgb::coin::assemble_gentx_coinbase() so that emission and
0 commit comments