|
47 | 47 |
|
48 | 48 | #include <core/pack.hpp> |
49 | 49 | #include <core/uint256.hpp> |
| 50 | +#include <core/version_gate.hpp> // SSOT: core::version_gate::is_v36_active (DASH 16->36) |
50 | 51 | #include <core/hash.hpp> |
51 | 52 | #include <core/coin_params.hpp> |
52 | 53 | #include <core/log.hpp> |
@@ -107,31 +108,64 @@ inline std::vector<MinerPayout> compute_dash_payouts( |
107 | 108 | // 2. worker_payout = subsidy - Σpayment_amounts |
108 | 109 | uint64_t worker_payout = (subsidy > total_payments) ? (subsidy - total_payments) : 0; |
109 | 110 |
|
110 | | - // 3. amounts[script] = worker_payout * 49 * weight / (50 * total_weight) |
111 | | - // (skipped for genesis where total_weight==0; populated for |
112 | | - // non-genesis shares via walk_cumulative_weights). |
| 111 | + // 3-4. PPLNS split. Two consensus arms, gated on the minted share version |
| 112 | + // (core::version_gate SSOT; DASH transition 16 -> 36): |
| 113 | + // |
| 114 | + // pre-v36 (bucket-3 per-coin compat, UNCHANGED p2pool-dash shape): |
| 115 | + // amounts[script] = worker_payout * 49 * weight / (50 * total_weight) |
| 116 | + // amounts[this_script] += worker_payout / 50 (2% block-finder) |
| 117 | + // |
| 118 | + // v36 (bucket-2 standardize-toward-merged-v36; mirrors DGB |
| 119 | + // share_tracker::get_expected_payouts): |
| 120 | + // amounts[script] = worker_payout * weight / total_weight (FULL weight) |
| 121 | + // NO block-finder fee. |
| 122 | + // total_weight is the GRAND total (includes donation_weight), so the |
| 123 | + // donation absorbs its decayed-weight share via the step-5 remainder. |
| 124 | + const bool v36 = |
| 125 | + core::version_gate::is_v36_active(params.current_share_version); |
113 | 126 | std::map<Script, uint64_t> amounts; |
114 | 127 | if (total_weight > 0) { |
115 | | - // uint128-ish: worker_payout < 2^50, 49 < 2^6, weight < ~2^80 for a few |
116 | | - // shares; use explicit 128-bit to avoid silent overflow. |
117 | | - __uint128_t den = static_cast<__uint128_t>(total_weight) * 50; |
| 128 | + // weights/total_weight fit uint64 at the DASH layer; __uint128_t covers |
| 129 | + // the products (worker_payout < 2^50, 49 < 2^6, weight < 2^64). |
| 130 | + __uint128_t den = v36 |
| 131 | + ? static_cast<__uint128_t>(total_weight) |
| 132 | + : static_cast<__uint128_t>(total_weight) * 50; |
118 | 133 | for (const auto& [script, w] : weights) { |
119 | 134 | __uint128_t num = static_cast<__uint128_t>(w) |
120 | | - * static_cast<__uint128_t>(worker_payout) |
121 | | - * 49; |
| 135 | + * static_cast<__uint128_t>(worker_payout); |
| 136 | + if (!v36) num *= 49; |
122 | 137 | amounts[script] = static_cast<uint64_t>(num / den); |
123 | 138 | } |
124 | 139 | } |
125 | 140 |
|
126 | | - // 4. amounts[this_script] += worker_payout // 50 (2 % block-finder). |
| 141 | + // 4. (pre-v36 only) 2% block-finder fee to the share creator. |
127 | 142 | auto this_script = dash::pubkey_hash_to_script2(miner_pubkey_hash); |
128 | | - amounts[this_script] = amounts[this_script] + (worker_payout / 50); |
| 143 | + if (!v36) |
| 144 | + amounts[this_script] = amounts[this_script] + (worker_payout / 50); |
129 | 145 |
|
130 | 146 | // 5. amounts[DONATION] += worker_payout - Σamounts (remainder incl. rounding). |
131 | 147 | uint64_t current_sum = 0; |
132 | 148 | for (const auto& [s, a] : amounts) current_sum += a; |
133 | 149 | uint64_t donation_remainder = (worker_payout > current_sum) |
134 | 150 | ? (worker_payout - current_sum) : 0; |
| 151 | + |
| 152 | + // v36 consensus (a60f7f7f): the donation output MUST carry >= 1 satoshi. |
| 153 | + // If the remainder rounds to 0, deduct 1 sat from the largest miner |
| 154 | + // (deterministic tiebreak: (amount, script)); the sum invariant holds. |
| 155 | + if (v36 && donation_remainder < 1 && worker_payout > 0 && !amounts.empty()) { |
| 156 | + auto largest = std::max_element(amounts.begin(), amounts.end(), |
| 157 | + [&](const auto& a, const auto& b) { |
| 158 | + if (a.first == donation_script) return true; // never pick donation |
| 159 | + if (b.first == donation_script) return false; |
| 160 | + if (a.second != b.second) return a.second < b.second; |
| 161 | + return a.first < b.first; |
| 162 | + }); |
| 163 | + if (largest != amounts.end() && largest->first != donation_script |
| 164 | + && largest->second >= 1) { |
| 165 | + largest->second -= 1; |
| 166 | + donation_remainder += 1; |
| 167 | + } |
| 168 | + } |
135 | 169 | amounts[donation_script] = amounts[donation_script] + donation_remainder; |
136 | 170 |
|
137 | 171 | // Sanity: Σ(amounts) == worker_payout (matches p2pool-dash assertion). |
|
0 commit comments