Skip to content
Merged
Show file tree
Hide file tree
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
38 changes: 24 additions & 14 deletions src/impl/dgb/share_tracker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ inline uint64_t mul128_shift(uint64_t a, uint64_t b, unsigned shift) {
#include "share_check.hpp"
#include "think_p1_walk_bounds.hpp" // SSOT: think_p1_walk_count / think_p1_in_pruning_zone
#include "coin/naughty_propagation.hpp" // SSOT: propagate_naughty_from_parent (data.py:543-549)
#include <impl/dgb/coin/desired_version_tally.hpp> // SSOT: accumulate_version_weights / accumulate_version_counts (#429 follow-on)
#include "config_pool.hpp"

#include <core/target_utils.hpp>
Expand Down Expand Up @@ -2110,22 +2111,26 @@ class ShareTracker
// Python ref: tracker.get_desired_version_counts(...)
std::map<uint64_t, int32_t> get_desired_version_counts(const uint256& share_hash, int32_t lookbehind)
{
std::map<uint64_t, int32_t> counts;
// Chain-walk + lookbehind clamp stay inline (the clamp is the separate
// chain_walk_window SSOT); ONLY the per-version accumulation delegates to
// dgb::accumulate_version_counts (desired_version_tally SSOT, #429). The
// window vector below is the exact (desired_version) sequence the prior
// inline counts[dv]++ loop read, so the result is byte-identical.
if (!chain.contains(share_hash))
return counts;
return {};
auto height = chain.get_height(share_hash);
auto actual = std::min(lookbehind, height);
if (actual <= 0)
return counts;
return {};

auto view = chain.get_chain(share_hash, actual);
for (auto [hash, data] : view)
std::vector<uint64_t> window;
for (auto [hash, data] : chain.get_chain(share_hash, actual))
{
uint64_t dv = 0;
data.share.invoke([&](auto* obj) { dv = obj->m_desired_version; });
counts[dv]++;
window.push_back(dv);
}
return counts;
return dgb::accumulate_version_counts(window);
}

// -- PPLNS-weighted version counting for the consensus 60% switch rule --
Expand All @@ -2145,24 +2150,29 @@ class ShareTracker
// Weight = ShareIndex::work (share.hpp).
std::map<uint64_t, uint288> get_desired_version_weights(const uint256& share_hash, int32_t lookbehind)
{
std::map<uint64_t, uint288> weights;
// Chain-walk + lookbehind clamp stay inline; ONLY the per-version work
// accumulation delegates to dgb::accumulate_version_weights (SSOT, #429).
// Shares whose ShareIndex is absent are skipped exactly as before (they
// never touched weights[dv]), so the delegated map is byte-identical to
// the prior inline weights[dv] = weights[dv] + idx->work loop -- the
// CONSENSUS 60%-by-work switch gate input is unchanged.
if (!chain.contains(share_hash))
return weights;
return {};
auto height = chain.get_height(share_hash);
auto actual = std::min(lookbehind, height);
if (actual <= 0)
return weights;
return {};

auto view = chain.get_chain(share_hash, actual);
for (auto [hash, data] : view)
std::vector<dgb::VersionWork> window;
for (auto [hash, data] : chain.get_chain(share_hash, actual))
{
uint64_t dv = 0;
data.share.invoke([&](auto* obj) { dv = obj->m_desired_version; });
auto* idx = chain.get_index(hash);
if (idx)
weights[dv] = weights[dv] + idx->work;
window.push_back({dv, idx->work});
}
return weights;
return dgb::accumulate_version_weights(window);
}

// -- Merged mining: per-chain PPLNS weights --
Expand Down
82 changes: 82 additions & 0 deletions src/impl/dgb/test/share_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,88 @@ TEST(DGB_share_test, WeightWalkValueInvarianceBattery)
}
}

// ── #429 follow-on: ShareTracker delegation byte-identity over {2,4,8} ───────
// #429 lifted the per-version tally accumulation into the desired_version_tally
// SSOT (accumulate_version_weights / accumulate_version_counts) WITHOUT rewiring
// ShareTracker. This slice rewires get_desired_version_weights() (the CONSENSUS
// 60%-by-work switch gate input) and its flat-count diagnostic sibling
// get_desired_version_counts() to delegate into that SSOT, keeping the
// chain-walk + lookbehind clamp inline. This KAT proves the rewire is
// value-identical NON-CIRCULARLY: the expected maps are derived from first
// principles (production chain::target_to_average_attempts over the known input
// bits, and a hand counted flat tally) — NOT from the SSOT under test — over
// chains of exactly 2, 4 and 8 shares (the {2,4,8} anchors).
TEST(DGB_share_test, DesiredVersionTallyDelegationByteIdentity)
{
struct In { uint64_t dv; uint32_t bits; };

auto run = [](const std::vector<In>& shares, const char* tag) {
dgb::ShareTracker tracker;
std::vector<uint256> hashes;
for (size_t i = 0; i < shares.size(); ++i) {
char buf[16]; std::snprintf(buf, sizeof(buf), "%zx", 0x4290 + i);
uint256 h = hx(buf);
auto* sh = new dgb::MergedMiningShare();
sh->m_hash = h;
if (i == 0) sh->m_prev_hash.SetNull(); else sh->m_prev_hash = hashes.back();
sh->m_desired_version = shares[i].dv;
sh->m_bits = shares[i].bits;
sh->m_max_bits = shares[i].bits;
dgb::ShareType st; st = sh;
tracker.add(st);
hashes.push_back(h);
}
const uint256& tip = hashes.back();

// Expected, hand-derived from first principles (not via the SSOT):
// counts = flat occurrence per desired_version
// weights = sum of production work per desired_version
std::map<uint64_t, int32_t> want_counts;
std::map<uint64_t, uint288> want_weights;
for (const auto& in : shares) {
want_counts[in.dv] += 1;
const uint288 w = chain::target_to_average_attempts(chain::bits_to_target(in.bits));
want_weights[in.dv] = want_weights[in.dv] + w;
}

const auto got_counts = tracker.get_desired_version_counts(tip, 1000);
const auto got_weights = tracker.get_desired_version_weights(tip, 1000);
EXPECT_EQ(got_counts, want_counts) << tag << " counts (flat, diag)";
EXPECT_EQ(got_weights, want_weights) << tag << " weights (consensus gate)";
};

// 2 shares: split versions, equal work => flat-count and weight agree on keys
run({{36, 0x1e0fffff}, {35, 0x1e0fffff}}, "anchor-2");

// 4 shares: 3x dv=36 (easy) vs 1x dv=35 (hard) — the exact case where a flat
// count (36 leads 3:1) inverts under work-weighting (35 outweighs).
run({{36, 0x1e0fffff}, {36, 0x1e0fffff}, {35, 0x1d00ffff}, {36, 0x1e0fffff}}, "anchor-4");

// 8 shares: three versions, mixed difficulty, repeated keys.
run({{35, 0x1d00ffff}, {36, 0x1e0fffff}, {36, 0x1e07ffff}, {37, 0x1e0fffff},
{35, 0x1e0fffff}, {36, 0x1d00ffff}, {37, 0x1e07ffff}, {36, 0x1e0fffff}},
"anchor-8");

// Lookbehind clamp stays inline & correct: a window shorter than the chain
// tallies ONLY the clamped tail, and a height/<=0 guard yields an empty map.
{
dgb::ShareTracker tracker;
uint256 g = hx("7a0"), a = hx("7a1"), b = hx("7a2");
auto mk = [&](const uint256& h, const uint256* ph, uint64_t dv) {
auto* sh = new dgb::MergedMiningShare();
sh->m_hash = h;
if (ph) sh->m_prev_hash = *ph; else sh->m_prev_hash.SetNull();
sh->m_desired_version = dv; sh->m_bits = 0x1e0fffff; sh->m_max_bits = 0x1e0fffff;
dgb::ShareType st; st = sh; tracker.add(st);
};
mk(g, nullptr, 35); mk(a, &g, 36); mk(b, &a, 36);
EXPECT_TRUE(tracker.get_desired_version_counts(b, 0).empty()) << "clamp<=0 empty";
EXPECT_TRUE(tracker.get_desired_version_weights(b, 0).empty()) << "clamp<=0 empty";
auto c2 = tracker.get_desired_version_counts(b, 2); // tail of 2 => both dv=36
ASSERT_EQ(c2.size(), 1u); EXPECT_EQ(c2.at(36u), 2);
}
}

// ============================================================================
// WorkRefHashAssembler — prospective-work RefHashParams assembler KAT.
//
Expand Down
Loading