Skip to content

Commit ae3bb52

Browse files
committed
dgb(phase-b): delegate get_desired_version_{weights,counts} into tally SSOT
Rewire ShareTracker::get_desired_version_weights() (the CONSENSUS 60%-by-work version-switch gate input) and its flat-count diagnostic sibling get_desired_version_counts() to delegate the per-version accumulation into the desired_version_tally SSOT introduced in #429 (accumulate_version_weights / accumulate_version_counts). The chain-walk and the lookbehind clamp stay inline in ShareTracker (the clamp is the separate chain_walk_window SSOT); only the map accumulation moves. Shares with an absent ShareIndex are skipped exactly as before, so the resulting map is byte-identical. Value-identity is proven NON-CIRCULARLY by a new KAT (DesiredVersionTallyDelegationByteIdentity) over chains of 2/4/8 shares whose expected maps are derived from first principles (production chain::target_to_average_attempts over the input bits, plus a hand-counted flat tally) rather than from the SSOT under test, plus a lookbehind-clamp check; and by the pre-existing real-chain weight tests (DesiredVersionWeightsByAttempts, WeightWalkValueInvarianceBattery, AutoRatchet*) which now route through the delegated accessor and remain green. Stacked on #429 (dgb/desired-version-tally-ssot).
1 parent 1094551 commit ae3bb52

2 files changed

Lines changed: 106 additions & 14 deletions

File tree

src/impl/dgb/share_tracker.hpp

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ inline uint64_t mul128_shift(uint64_t a, uint64_t b, unsigned shift) {
2525
#include <impl/nmc/coin/aux_id.hpp> // nmc::coin::NMC_AUXPOW_CHAIN_ID (v37 bucket-2)
2626
#include "share_check.hpp"
2727
#include "think_p1_walk_bounds.hpp" // SSOT: think_p1_walk_count / think_p1_in_pruning_zone
28+
#include <impl/dgb/coin/desired_version_tally.hpp> // SSOT: accumulate_version_weights / accumulate_version_counts (#429 follow-on)
2829
#include "config_pool.hpp"
2930

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

2118-
auto view = chain.get_chain(share_hash, actual);
2119-
for (auto [hash, data] : view)
2123+
std::vector<uint64_t> window;
2124+
for (auto [hash, data] : chain.get_chain(share_hash, actual))
21202125
{
21212126
uint64_t dv = 0;
21222127
data.share.invoke([&](auto* obj) { dv = obj->m_desired_version; });
2123-
counts[dv]++;
2128+
window.push_back(dv);
21242129
}
2125-
return counts;
2130+
return dgb::accumulate_version_counts(window);
21262131
}
21272132

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

2153-
auto view = chain.get_chain(share_hash, actual);
2154-
for (auto [hash, data] : view)
2163+
std::vector<dgb::VersionWork> window;
2164+
for (auto [hash, data] : chain.get_chain(share_hash, actual))
21552165
{
21562166
uint64_t dv = 0;
21572167
data.share.invoke([&](auto* obj) { dv = obj->m_desired_version; });
21582168
auto* idx = chain.get_index(hash);
21592169
if (idx)
2160-
weights[dv] = weights[dv] + idx->work;
2170+
window.push_back({dv, idx->work});
21612171
}
2162-
return weights;
2172+
return dgb::accumulate_version_weights(window);
21632173
}
21642174

21652175
// -- Merged mining: per-chain PPLNS weights --

src/impl/dgb/test/share_test.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,88 @@ TEST(DGB_share_test, WeightWalkValueInvarianceBattery)
864864
}
865865
}
866866

867+
// ── #429 follow-on: ShareTracker delegation byte-identity over {2,4,8} ───────
868+
// #429 lifted the per-version tally accumulation into the desired_version_tally
869+
// SSOT (accumulate_version_weights / accumulate_version_counts) WITHOUT rewiring
870+
// ShareTracker. This slice rewires get_desired_version_weights() (the CONSENSUS
871+
// 60%-by-work switch gate input) and its flat-count diagnostic sibling
872+
// get_desired_version_counts() to delegate into that SSOT, keeping the
873+
// chain-walk + lookbehind clamp inline. This KAT proves the rewire is
874+
// value-identical NON-CIRCULARLY: the expected maps are derived from first
875+
// principles (production chain::target_to_average_attempts over the known input
876+
// bits, and a hand counted flat tally) — NOT from the SSOT under test — over
877+
// chains of exactly 2, 4 and 8 shares (the {2,4,8} anchors).
878+
TEST(DGB_share_test, DesiredVersionTallyDelegationByteIdentity)
879+
{
880+
struct In { uint64_t dv; uint32_t bits; };
881+
882+
auto run = [](const std::vector<In>& shares, const char* tag) {
883+
dgb::ShareTracker tracker;
884+
std::vector<uint256> hashes;
885+
for (size_t i = 0; i < shares.size(); ++i) {
886+
char buf[16]; std::snprintf(buf, sizeof(buf), "%zx", 0x4290 + i);
887+
uint256 h = hx(buf);
888+
auto* sh = new dgb::MergedMiningShare();
889+
sh->m_hash = h;
890+
if (i == 0) sh->m_prev_hash.SetNull(); else sh->m_prev_hash = hashes.back();
891+
sh->m_desired_version = shares[i].dv;
892+
sh->m_bits = shares[i].bits;
893+
sh->m_max_bits = shares[i].bits;
894+
dgb::ShareType st; st = sh;
895+
tracker.add(st);
896+
hashes.push_back(h);
897+
}
898+
const uint256& tip = hashes.back();
899+
900+
// Expected, hand-derived from first principles (not via the SSOT):
901+
// counts = flat occurrence per desired_version
902+
// weights = sum of production work per desired_version
903+
std::map<uint64_t, int32_t> want_counts;
904+
std::map<uint64_t, uint288> want_weights;
905+
for (const auto& in : shares) {
906+
want_counts[in.dv] += 1;
907+
const uint288 w = chain::target_to_average_attempts(chain::bits_to_target(in.bits));
908+
want_weights[in.dv] = want_weights[in.dv] + w;
909+
}
910+
911+
const auto got_counts = tracker.get_desired_version_counts(tip, 1000);
912+
const auto got_weights = tracker.get_desired_version_weights(tip, 1000);
913+
EXPECT_EQ(got_counts, want_counts) << tag << " counts (flat, diag)";
914+
EXPECT_EQ(got_weights, want_weights) << tag << " weights (consensus gate)";
915+
};
916+
917+
// 2 shares: split versions, equal work => flat-count and weight agree on keys
918+
run({{36, 0x1e0fffff}, {35, 0x1e0fffff}}, "anchor-2");
919+
920+
// 4 shares: 3x dv=36 (easy) vs 1x dv=35 (hard) — the exact case where a flat
921+
// count (36 leads 3:1) inverts under work-weighting (35 outweighs).
922+
run({{36, 0x1e0fffff}, {36, 0x1e0fffff}, {35, 0x1d00ffff}, {36, 0x1e0fffff}}, "anchor-4");
923+
924+
// 8 shares: three versions, mixed difficulty, repeated keys.
925+
run({{35, 0x1d00ffff}, {36, 0x1e0fffff}, {36, 0x1e07ffff}, {37, 0x1e0fffff},
926+
{35, 0x1e0fffff}, {36, 0x1d00ffff}, {37, 0x1e07ffff}, {36, 0x1e0fffff}},
927+
"anchor-8");
928+
929+
// Lookbehind clamp stays inline & correct: a window shorter than the chain
930+
// tallies ONLY the clamped tail, and a height/<=0 guard yields an empty map.
931+
{
932+
dgb::ShareTracker tracker;
933+
uint256 g = hx("7a0"), a = hx("7a1"), b = hx("7a2");
934+
auto mk = [&](const uint256& h, const uint256* ph, uint64_t dv) {
935+
auto* sh = new dgb::MergedMiningShare();
936+
sh->m_hash = h;
937+
if (ph) sh->m_prev_hash = *ph; else sh->m_prev_hash.SetNull();
938+
sh->m_desired_version = dv; sh->m_bits = 0x1e0fffff; sh->m_max_bits = 0x1e0fffff;
939+
dgb::ShareType st; st = sh; tracker.add(st);
940+
};
941+
mk(g, nullptr, 35); mk(a, &g, 36); mk(b, &a, 36);
942+
EXPECT_TRUE(tracker.get_desired_version_counts(b, 0).empty()) << "clamp<=0 empty";
943+
EXPECT_TRUE(tracker.get_desired_version_weights(b, 0).empty()) << "clamp<=0 empty";
944+
auto c2 = tracker.get_desired_version_counts(b, 2); // tail of 2 => both dv=36
945+
ASSERT_EQ(c2.size(), 1u); EXPECT_EQ(c2.at(36u), 2);
946+
}
947+
}
948+
867949
// ============================================================================
868950
// WorkRefHashAssembler — prospective-work RefHashParams assembler KAT.
869951
//

0 commit comments

Comments
 (0)