Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 105 additions & 153 deletions src/impl/btc/share_tracker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class ShareT>
chain::WeightsDelta merged_weights_delta(ShareT* obj, std::optional<uint32_t> 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<unsigned char> 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<uint32_t>(obj->m_donation);
delta.weights[weight_key] = att * static_cast<uint32_t>(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<uint32_t> 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
Expand Down Expand Up @@ -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<uint32_t>(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<uint32_t>(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)
);
Expand Down Expand Up @@ -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<uint32_t>(obj->m_donation);
delta.weights[raw_script] = att * static_cast<uint32_t>(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()
);
Expand All @@ -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<unsigned char> 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<unsigned char>& 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<uint32_t>(obj->m_donation);
delta.weights[weight_key] = att * static_cast<uint32_t>(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()
)
Expand Down
Loading