From fa429611d5a89f98978682f65f7424ecda27f4ea Mon Sep 17 00:00:00 2001 From: frstrtr Date: Mon, 22 Jun 2026 01:05:41 +0000 Subject: [PATCH] btc(F-mirror F2): unify merged-mining PPLNS weight delta into one get_delta Collapse the three divergent V36 merged-weight delta implementations (the compute_merged_payout_hash inline walk, ensure_v36_skiplist, and per-chain ensure_merged_skiplist) into merged_weights_delta() + merged_weights_delta_for_hash(), mirroring LTC F2 (08f269026) and p2pool MergedWeightsSkipList.get_delta (data.py:1864). chain_id==nullopt drives the hash/v36 path (no merged-address resolution); a chain_id value drives the per-chain path (Tier 1 explicit, Tier 1.5 retroactive with normalize as a lookup probe only, else default key). BTC keeps the F1 raw default (#85): default key was already the raw parent script in all three sites, so this is a pure structural unification with no key-semantics change. Applies the canonical (1,{},0,0)-after-key-resolves shape -- the hash-path variant previously set totals before the empty-script check, inflating the denominator for degenerate empty-script shares; convertible scripts are unaffected. Drops the non-consensus [DOGE-TIER] per-share debug log. Held on btc/f-mirror-prep, unopened, pending crossing-soak dispatch. Merged-payout KATs green: V36CrossImplRefHash (3) + PPLNSConsensus (11), 14/14. (cherry picked from commit 53f22296de2abc8aa990de8e0f49a3a850759338) --- src/impl/btc/share_tracker.hpp | 258 ++++++++++++++------------------- 1 file changed, 105 insertions(+), 153 deletions(-) diff --git a/src/impl/btc/share_tracker.hpp b/src/impl/btc/share_tracker.hpp index b3a949ef3..f95675d86 100644 --- a/src/impl/btc/share_tracker.hpp +++ b/src/impl/btc/share_tracker.hpp @@ -2287,6 +2287,105 @@ class ShareTracker return {std::move(result.weights), result.total_weight, result.total_donation_weight}; } + // -- merged_weights_delta (F2: single source of truth) -- + // Unified per-share merged-mining PPLNS weight delta. Replaces the three + // formerly-divergent variants (the compute_merged_payout_hash inline walk, + // ensure_v36_skiplist, and per-chain ensure_merged_skiplist). Mirrors + // p2pool MergedWeightsSkipList.get_delta (data.py:1864) and LTC F2 + // (08f269026); BTC keeps the F1 raw default (#85), so this is a pure + // structural unification -- the default key was already the raw parent + // script in all three sites, no key-semantics change. + // + // chain_id == nullopt : hash / v36 path -- no merged-address resolution. + // chain_id has value : per-chain path -- Tier 1 explicit merged_addresses, + // Tier 1.5 retroactive miner->merged lookup (normalize + // used only as a lookup PROBE), else the default key. + // + // Pre-V36 shares count toward window sizing but contribute zero weight + // (p2pool returns (1, {}, 0, 0)); total_weight/donation are set only once a + // weight key resolves, so unconvertible/empty scripts never inflate the + // denominator. This (1,{},0,0)-after-key-resolves shape converges the + // hash-path variant, which previously set totals before the empty-script + // check; convertible scripts are unaffected, so the merged-payout KAT holds. + template + chain::WeightsDelta merged_weights_delta(ShareT* obj, std::optional chain_id) + { + chain::WeightsDelta delta; + delta.share_count = 1; + if (obj->m_desired_version < 36) + return delta; + + auto target = chain::bits_to_target(obj->m_bits); + auto att = chain::target_to_average_attempts(target); + auto parent_script = get_share_script(obj); + if (parent_script.empty()) + return delta; + + std::vector weight_key; + // Tier 1 / 1.5 merged-address resolution -- only for chain-keyed skiplists + // whose share type carries explicit merged_addresses. + if (chain_id.has_value()) + { + if constexpr (requires { obj->m_merged_addresses; }) + { + // Tier 1: explicit merged_addresses for this chain. + for (const auto& entry : obj->m_merged_addresses) + { + if (entry.m_chain_id == *chain_id) + { + weight_key = make_merged_key(entry.m_script.m_data); + break; + } + } + // Tier 1.5: retroactive lookup of the same miner's explicit merged + // address from their other shares. Try the raw parent script, then + // its normalized P2PKH form (normalize is a lookup PROBE only here). + if (weight_key.empty()) + { + auto table_it = m_miner_merged_addr.find(*chain_id); + if (table_it != m_miner_merged_addr.end()) + { + auto miner_it = table_it->second.find(parent_script); + if (miner_it == table_it->second.end()) + { + auto norm = normalize_script_for_merged(parent_script); + if (!norm.empty() && norm != parent_script) + miner_it = table_it->second.find(norm); + } + if (miner_it != table_it->second.end()) + weight_key = make_merged_key(miner_it->second); + } + } + } + } + + // Default key: RAW parent script, matching p2pool address_key = + // share.new_script (F1, Option A, #85). normalize_script_for_merged + // stays a Tier-1.5 lookup PROBE only (above). + if (weight_key.empty()) + weight_key = parent_script; + if (weight_key.empty()) + return delta; + + delta.total_weight = att * 65535; + delta.total_donation_weight = att * static_cast(obj->m_donation); + delta.weights[weight_key] = att * static_cast(65535 - obj->m_donation); + return delta; + } + + // Per-hash wrapper: the boilerplate shared by every merged-weight skiplist + // lambda -- bounds-check the raw chain, then dispatch to merged_weights_delta. + chain::WeightsDelta merged_weights_delta_for_hash( + const uint256& hash, std::optional chain_id) + { + chain::WeightsDelta delta; + if (!chain.contains(hash)) return delta; + chain.get_share(hash).invoke([&](auto* obj) { + delta = merged_weights_delta(obj, chain_id); + }); + return delta; + } + // -- compute_merged_payout_hash -- // Deterministic hash of V36-only PPLNS weight distribution. // Committed into V36 shares so peers can verify that the share creator's @@ -2334,26 +2433,8 @@ class ShareTracker }; chain::WeightsSkipList raw_sl( [this](const uint256& hash) -> chain::WeightsDelta { - chain::WeightsDelta delta; - if (!chain.contains(hash)) return delta; - delta.share_count = 1; - chain.get_share(hash).invoke([&](auto* obj) { - if (obj->m_desired_version < 36) return; - auto target = chain::bits_to_target(obj->m_bits); - auto att = chain::target_to_average_attempts(target); - delta.total_weight = att * 65535; - delta.total_donation_weight = att * static_cast(obj->m_donation); - auto raw_script = get_share_script(obj); - if (raw_script.empty()) return; - // F1 (Option A; mirror of ltc 8ca17954): key merged weight by the - // RAW parent script (== p2pool address_key == share.new_script), - // matching the payout key. p2pool credits raw weight to every V36 - // share regardless of script type (data.py compute_merged_payout_hash); - // keying by the normalized P2WPKH->P2PKH form (and skipping - // unconvertible P2TR/P2WSH) was the merged-payout key/pay divergence. - delta.weights[raw_script] = att * static_cast(65535 - obj->m_donation); - }); - return delta; + // F2: unified merged-weight delta, hash/v36 path (no chain_id). + return merged_weights_delta_for_hash(hash, std::nullopt); }, std::move(raw_prev_fn) ); @@ -2742,28 +2823,8 @@ class ShareTracker return; m_v36_weights_skiplist.emplace( [this](const uint256& hash) -> chain::WeightsDelta { - chain::WeightsDelta delta; - if (!chain.contains(hash)) return delta; - delta.share_count = 1; - chain.get_share(hash).invoke([&](auto* obj) { - if (obj->m_desired_version < 36) return; - auto target = chain::bits_to_target(obj->m_bits); - auto att = chain::target_to_average_attempts(target); - auto raw_script = get_share_script(obj); - if (raw_script.empty()) return; - // F1 (Option A; mirror of ltc 8ca17954): key merged weight by the - // RAW parent script (== p2pool address_key == share.new_script), - // matching the payout key and compute_merged_payout_hash. p2pool - // credits raw weight + full total to every V36 share regardless of - // script type (data.py get_v36_merged_weights:2756 -- if address_key - // is None: address_key = share.new_script). Keying by the normalized - // P2WPKH->P2PKH form and skipping unconvertible P2TR/P2WSH was the - // key/pay divergence F1 removes. - delta.total_weight = att * 65535; - delta.total_donation_weight = att * static_cast(obj->m_donation); - delta.weights[raw_script] = att * static_cast(65535 - obj->m_donation); - }); - return delta; + // F2: unified merged-weight delta, hash/v36 path (no chain_id). + return merged_weights_delta_for_hash(hash, std::nullopt); }, make_previous_fn() ); @@ -2779,117 +2840,8 @@ class ShareTracker chain_id, chain::WeightsSkipList( [this, chain_id](const uint256& hash) -> chain::WeightsDelta { - chain::WeightsDelta delta; - if (!chain.contains(hash)) return delta; - delta.share_count = 1; - chain.get_share(hash).invoke([&](auto* obj) { - if (obj->m_desired_version < 36) return; - auto target = chain::bits_to_target(obj->m_bits); - auto att = chain::target_to_average_attempts(target); - // NOTE: total_weight and donation_weight are set AFTER - // convertibility check below. p2pool returns (1, {}, 0, 0) - // for unconvertible scripts — zero total, zero donation. - // Setting them here would inflate the denominator. - - std::vector weight_key; - const char* tier_name = "raw:v35"; - // p2pool gates Tier 1 + 1.5 on share.VERSION >= 36. - // V35-format shares always use raw parent script as key, - // even if desired_version >= 36. This keeps V35 and V36 - // weight entries separate per miner during the mixed window. - if constexpr (requires { obj->m_merged_addresses; }) - { - tier_name = "T3:skip"; - // Tier 1: explicit merged_addresses for this chain - for (const auto& entry : obj->m_merged_addresses) - { - if (entry.m_chain_id == chain_id) - { - weight_key = make_merged_key(entry.m_script.m_data); - tier_name = "T1:explicit"; - break; - } - } - // Tier 1.5: retroactive lookup — same miner's - // explicit merged address from their other shares. - // p2pool v0.14.5: try raw, then normalized P2PKH form. - if (weight_key.empty()) - { - auto parent_script = get_share_script(obj); - auto table_it = m_miner_merged_addr.find(chain_id); - if (table_it != m_miner_merged_addr.end()) - { - auto miner_it = table_it->second.find(parent_script); - if (miner_it == table_it->second.end()) { - auto norm = normalize_script_for_merged(parent_script); - if (!norm.empty() && norm != parent_script) - miner_it = table_it->second.find(norm); - } - if (miner_it != table_it->second.end()) { - weight_key = make_merged_key(miner_it->second); - tier_name = "T1.5:retro"; - } - } - // F1 (Option A; mirror of ltc 8ca17954): default - // merged-weight emission key = RAW parent script - // (== p2pool address_key == share.new_script), matching - // the payout key. normalize_script_for_merged stays the - // Tier-1.5 lookup PROBE only (above); keying emission by - // the normalized P2WPKH->P2PKH form was the merged-payout - // accounting divergence. - if (weight_key.empty()) - weight_key = parent_script; - } - } - else - { - // V35-format share: normalize P2WPKH→P2PKH for merged - // chain compatibility. P2TR/P2WSH → empty → skipped. - weight_key = normalize_script_for_merged(get_share_script(obj)); - } - // Per-share tier diagnostic - // Log every T1/T1.5/MERGED hit and every 200th otherwise - { - static int s_tier_log_ctr = 0; - bool is_merged = is_merged_key(weight_key); - bool is_p2wpkh = !is_merged && weight_key.size() == 22 && - weight_key[0] == 0x00 && weight_key[1] == 0x14; - if (is_merged || is_p2wpkh || s_tier_log_ctr++ % 200 == 0) { - auto to_hex_short = [](const std::vector& s, size_t n = 20) { - static const char* H = "0123456789abcdef"; - std::string r; for (size_t i = 0; i < std::min(s.size(), n); ++i) { r += H[s[i]>>4]; r += H[s[i]&0xf]; } - return r; - }; - auto parent_script = get_share_script(obj); - LOG_INFO << "[DOGE-TIER] " << tier_name - << " chain_id=" << chain_id - << " ver=" << obj->version - << " dv=" << obj->m_desired_version - << " parent=" << to_hex_short(parent_script) - << " key=" << to_hex_short(weight_key) - << " bits=0x" << std::hex << obj->m_bits << std::dec; - // For V36 shares, log merged_addresses status - if constexpr (requires { obj->m_merged_addresses; }) { - LOG_INFO << "[DOGE-TIER] merged_addrs=" << obj->m_merged_addresses.size() - << " ver=" << obj->version; - for (const auto& entry : obj->m_merged_addresses) { - LOG_INFO << "[DOGE-TIER] chain_id=" << entry.m_chain_id - << " script_len=" << entry.m_script.m_data.size() - << " script=" << to_hex_short(entry.m_script.m_data); - } - } - } - } - // Tier 3: unconvertible — skip, weight redistributed - // p2pool: return (1, {}, 0, 0) — share counts but zero weight - if (weight_key.empty()) return; - // Only set total_weight/donation for convertible scripts - // (matching p2pool MergedWeightsSkipList.get_delta) - delta.total_weight = att * 65535; - delta.total_donation_weight = att * static_cast(obj->m_donation); - delta.weights[weight_key] = att * static_cast(65535 - obj->m_donation); - }); - return delta; + // F2: unified merged-weight delta, per-chain path. + return merged_weights_delta_for_hash(hash, chain_id); }, make_previous_fn() )