Skip to content

Commit d5ddb71

Browse files
authored
bch(share): port canonical 60% work-weighted version-switch boundary; drop 95% flat-count punish (#326)
Replace the non-canonical 95%-flat-count should_punish_version gate in share_check with the canonical 60% PPLNS-work-weighted boundary switch (p2pool data.py check() 1396-1414), matching the BTC F10/(b) port and the LTC/DGB removals. Add ShareTracker::get_desired_version_weights — per- desired-version WORK tally (idx->work), mirroring get_desired_version_counts (data.py:2651) — NOT a flat share count. A version BOUNDARY (share.version != parent.version) is valid only when the new version holds >= 60% of weighted desired-version support in the window [CHAIN_LENGTH*9/10, CHAIN_LENGTH] behind the parent; one-step downgrade is allowed (AutoRatchet deactivation), larger jumps rejected. Couples the accept gate to the work-weighted mint guard so an activated node cannot mint a boundary share its peers reject. BCH-fenced (src/impl/bch only); standalone SHA256d parent, no aux dimension. v36-native bucket-2 standardization. Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
1 parent 164331b commit d5ddb71

2 files changed

Lines changed: 85 additions & 16 deletions

File tree

src/impl/bch/share_check.hpp

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,31 +1722,72 @@ bool share_check(const ShareT& share,
17221722
if (share.m_timestamp > now + 600)
17231723
throw std::invalid_argument("share timestamp is too far in the future");
17241724

1725-
// 2. Version counting — AutoRatchet upgrade enforcement
1726-
// p2pool data.py:1400-1414: version switch validation only at BOUNDARIES
1727-
// (when share.VERSION != parent.VERSION). Shares that match their parent's
1728-
// version are always valid — they were correct when created.
1729-
// Previous code rejected ALL v35 shares retroactively after 95% activation.
1730-
{
1731-
auto lookbehind = static_cast<int32_t>(PoolConfig::chain_length());
1732-
if (tracker.chain.contains(share.m_hash) &&
1733-
!share.m_prev_hash.IsNull() && tracker.chain.contains(share.m_prev_hash))
1725+
// 2. Version switch enforcement — canonical 60% weighted switch rule.
1726+
// p2pool data.py check() (lines 1396-1414): a version BOUNDARY
1727+
// (share.version != parent.version) is only valid when, in the sampling
1728+
// window [CHAIN_LENGTH*9/10, CHAIN_LENGTH] behind the PARENT, the new
1729+
// version holds >= 60% of the PPLNS-WEIGHTED desired-version counts.
1730+
// The weight is target_to_average_attempts per share — get_desired_version_weights,
1731+
// matching canonical get_desired_version_counts (data.py:2651) — NOT a flat count.
1732+
// F10/(b): the prior non-canonical 95%-flat-count should_punish_version
1733+
// punish is removed; the canonical 60% switch rule is now the ONLY version
1734+
// gate. AutoRatchet stays work-weighted (#290) and does not gate peer
1735+
// shares here. This couples the accept gate to the mint guard so an
1736+
// activated node can never mint a boundary share its peers reject.
1737+
{
1738+
auto chain_length = static_cast<int32_t>(PoolConfig::chain_length());
1739+
if (!share.m_prev_hash.IsNull() && tracker.chain.contains(share.m_prev_hash))
17341740
{
1735-
// Get parent's share version
1741+
// Parent's share version (the type the incoming share must legally follow)
17361742
int64_t parent_version = 0;
17371743
tracker.chain.get_share(share.m_prev_hash).invoke([&](auto* obj) {
17381744
parent_version = std::remove_pointer_t<decltype(obj)>::version;
17391745
});
1746+
int64_t share_ver = share.version;
17401747

1741-
// Only enforce version obsolescence at version BOUNDARIES
1742-
if (share.version != parent_version)
1748+
auto prev_height = tracker.chain.get_height(share.m_prev_hash);
1749+
if (prev_height >= chain_length)
17431750
{
1744-
auto height = tracker.chain.get_height(share.m_hash);
1745-
if (height >= lookbehind)
1751+
if (share_ver == parent_version)
17461752
{
1747-
if (tracker.should_punish_version(share.m_hash, share.version, lookbehind))
1748-
throw std::invalid_argument("share version too old — newer version has 95%+ activation");
1753+
// same version — always valid (correct when created)
17491754
}
1755+
else if (share_ver == parent_version + 1)
1756+
{
1757+
// Upgrade by one version: requires >= 60% weighted support in
1758+
// window [CHAIN_LENGTH*9/10, CHAIN_LENGTH] behind the parent.
1759+
uint32_t window_start = (static_cast<uint32_t>(chain_length) * 9) / 10;
1760+
uint32_t window_size = static_cast<uint32_t>(chain_length) / 10;
1761+
auto ancestor = tracker.chain.get_nth_parent_key(share.m_prev_hash, window_start);
1762+
auto weights = tracker.get_desired_version_weights(
1763+
ancestor, static_cast<int32_t>(window_size));
1764+
1765+
uint288 new_ver_weight; // weight of shares desiring exactly share_ver
1766+
uint288 total_weight;
1767+
for (auto& [ver, w] : weights)
1768+
{
1769+
total_weight = total_weight + w;
1770+
if (static_cast<int64_t>(ver) == share_ver)
1771+
new_ver_weight = new_ver_weight + w;
1772+
}
1773+
// Canonical: counts.get(self.VERSION,0) < sum(counts)*60//100
1774+
if (new_ver_weight * uint32_t(100) < total_weight * uint32_t(60))
1775+
throw std::invalid_argument("switch without enough hash power upgraded");
1776+
}
1777+
else if (parent_version == share_ver + 1)
1778+
{
1779+
// Downgrade by one (AutoRatchet deactivation: V35 may follow V36)
1780+
}
1781+
else
1782+
{
1783+
throw std::invalid_argument("invalid version jump from "
1784+
+ std::to_string(parent_version) + " to " + std::to_string(share_ver));
1785+
}
1786+
}
1787+
else if (share_ver == parent_version + 1)
1788+
{
1789+
// Not enough history for an upgrade boundary
1790+
throw std::invalid_argument("switch without enough history");
17501791
}
17511792
}
17521793
}

src/impl/bch/share_tracker.hpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,34 @@ class ShareTracker
686686

687687
return attempts / uint288(time_span);
688688
}
689+
690+
// -- AutoRatchet: PPLNS-weighted desired-version tally (canonical) --
691+
// Mirrors p2pool get_desired_version_counts (data.py:2651): walk `lookbehind`
692+
// shares back from share_hash and accumulate per-desired-version WORK weight
693+
// (idx->work = target_to_average_attempts), NOT a flat share count. Consumed by
694+
// share_check's 60% weighted version-switch boundary gate. BCH is a standalone
695+
// SHA256d parent — no merged/aux dimension here.
696+
std::map<uint64_t, uint288> get_desired_version_weights(const uint256& share_hash, int32_t lookbehind)
697+
{
698+
std::map<uint64_t, uint288> weights;
699+
if (!chain.contains(share_hash))
700+
return weights;
701+
auto height = chain.get_height(share_hash);
702+
auto actual = std::min(lookbehind, height);
703+
if (actual <= 0)
704+
return weights;
705+
706+
auto view = chain.get_chain(share_hash, actual);
707+
for (auto [hash, data] : view)
708+
{
709+
uint64_t dv = 0;
710+
data.share.invoke([&](auto* obj) { dv = obj->m_desired_version; });
711+
auto* idx = chain.get_index(hash);
712+
if (idx)
713+
weights[dv] = weights[dv] + idx->work;
714+
}
715+
return weights;
716+
}
689717
};
690718

691719
} // namespace bch

0 commit comments

Comments
 (0)