Skip to content

Commit 57cb51d

Browse files
committed
dgb(phase-b): delegate generate_share_transaction payout split to SSOT helper
Replace the inline steps 2-3 of the share verification SSOT generate_share_transaction() (PPLNS amount math + consensus output ordering) with a call to coin/pplns_payout_split.hpp compute_pplns_payout_split(). The helper is a verbatim lift of that inline math; delegating collapses the two copies into a single consensus implementation so the per-connection Stratum coinbase assembler and the verifier can never diverge on a payout satoshi. Behavior-neutral by construction (same formulae, finder fee, >=1-sat V36 donation floor + (amount, script) tiebreak, ascending sort truncated to [-4000:]). The helper oracle-vector KAT (dgb_pplns_payout_split_test) pins the values against p2pool-merged-v36; the verifier-path framing tests (dgb_gentx_share_path_test, dgb_gentx_coinbase_test) stay green, proving the share path still emits identical coinbase framing. Stacked on dgb/pplns-payout-split (helper extraction).
1 parent 34726f1 commit 57cb51d

1 file changed

Lines changed: 20 additions & 73 deletions

File tree

src/impl/dgb/share_check.hpp

Lines changed: 20 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
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

Comments
 (0)