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
27 changes: 10 additions & 17 deletions src/impl/dgb/share_tracker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ inline uint64_t mul128_shift(uint64_t a, uint64_t b, unsigned shift) {
#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 <impl/dgb/coin/share_weight_decay.hpp> // SSOT: weight-decay constants/half_life/decay_per_share (#450 rewire)
#include "coin/pool_attempts_per_second.hpp" // SSOT: compute_pool_attempts_per_second (#407 follow-on)
#include "coin/tail_score_endpoints.hpp" // SSOT: score_* endpoint arithmetic (ShareTracker::score chain-walk, #411 follow-on)
#include "coin/chain_walk_window.hpp" // SSOT: lookbehind clamp (#423 diag follow-on); consensus get_desired_version_weights stays inline
Expand Down Expand Up @@ -159,9 +160,9 @@ struct PPLNSEntry {

class DensePPLNSWindow {
public:
static constexpr uint64_t DECAY_PRECISION = 40;
static constexpr uint64_t DECAY_SCALE = uint64_t(1) << DECAY_PRECISION;
static constexpr uint64_t LN2_MICRO = 693147;
static constexpr uint64_t DECAY_PRECISION = dgb::coin::weight_decay::DECAY_PRECISION;
static constexpr uint64_t DECAY_SCALE = dgb::coin::weight_decay::DECAY_SCALE;
static constexpr uint64_t LN2_MICRO = dgb::coin::weight_decay::LN2_MICRO;

// Precomputed decay table: decay_table[d] = iterative decay factor at depth d.
// Computed once, reused for every PPLNS walk. Thread-safe after init.
Expand All @@ -172,8 +173,7 @@ class DensePPLNSWindow {
static void init_decay_table(uint32_t chain_len) {
if (s_table_initialized && s_decay_table.size() == chain_len)
return;
uint32_t half_life = std::max(chain_len / 4, uint32_t(1));
s_decay_per = DECAY_SCALE - (DECAY_SCALE * LN2_MICRO) / (uint64_t(1000000) * half_life);
s_decay_per = dgb::coin::weight_decay::decay_per_share(chain_len);
s_decay_table.resize(chain_len);
s_decay_table[0] = DECAY_SCALE;
for (uint32_t d = 1; d < chain_len; ++d)
Expand Down Expand Up @@ -1729,12 +1729,8 @@ class ShareTracker
&& m_decayed_cache_desired == desired_weight)
return m_decayed_cache_result;

static constexpr uint64_t DECAY_PRECISION = 40;
static constexpr uint64_t DECAY_SCALE = uint64_t(1) << DECAY_PRECISION;
static constexpr uint64_t LN2_MICRO = 693147;

uint32_t half_life = std::max(PoolConfig::chain_length() / 4, uint32_t(1));
uint64_t decay_per = DECAY_SCALE - (DECAY_SCALE * LN2_MICRO) / (uint64_t(1000000) * half_life);
using namespace dgb::coin::weight_decay; // SSOT decay constants + helpers (#450)
uint64_t decay_per = decay_per_share(PoolConfig::chain_length());

CumulativeWeights result;
int32_t share_count = 0;
Expand Down Expand Up @@ -1804,12 +1800,9 @@ class ShareTracker
return;
}

static constexpr uint64_t DECAY_PRECISION = 40;
static constexpr uint64_t DECAY_SCALE = uint64_t(1) << DECAY_PRECISION;
static constexpr uint64_t LN2_MICRO = 693147;

uint32_t half_life = std::max(PoolConfig::chain_length() / 4, uint32_t(1));
uint64_t decay_per = DECAY_SCALE - (DECAY_SCALE * LN2_MICRO) / (uint64_t(1000000) * half_life);
using namespace dgb::coin::weight_decay; // SSOT decay constants + helpers (#450)
uint32_t half_life = dgb::coin::weight_decay::half_life(PoolConfig::chain_length());
uint64_t decay_per = decay_per_share(PoolConfig::chain_length());

int32_t share_count = 0;
uint64_t decay_fp = DECAY_SCALE;
Expand Down
85 changes: 85 additions & 0 deletions src/impl/dgb/test/share_weight_decay_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,88 @@ TEST(DgbWeightDecay, DecayedAttemptsZeroAndIdentity) {
EXPECT_EQ(wd::decayed_attempts(0, wd::DECAY_SCALE), uint64_t(0));
EXPECT_EQ(wd::decayed_attempts(12345, wd::DECAY_SCALE), uint64_t(12345)); // x1.0
}

// ---- #450 consumer-rewire byte-identity invariance ----------------------
//
// Pins the share_tracker.hpp rewire (init_decay_table /
// get_v36_decayed_cumulative_weights / dump_v36_pplns_walk) onto this SSOT.
// The OLD path below recomputes decay_per and the iterative decay table with
// the open-coded literals (1<<40, 693147, 1e6, max(CL/4,1)) exactly as the
// three sites did before #450; the SSOT path sources the rate from
// wd::decay_per_share / wd::DECAY_SCALE / wd::DECAY_PRECISION. Both run the
// SAME unsigned __int128 mul-shift used by the production mul128_shift /
// uint288 hot path, so this proves the rewire is value-invariant
// (byte-for-byte) across the full decayed-cumulative-weight vector. The OLD
// path is independent of the helper under test -> NON-CIRCULAR.

TEST(DgbWeightDecay, DecayedCumulativeWeightVectorInvariance) {
using uint128 = unsigned __int128;
const uint32_t CL = 8640; // V36 DGB chain length

// --- OLD open-coded decay_per (literals, pre-#450) ---
const uint64_t old_half_life = std::max(CL / 4u, 1u);
const uint64_t old_decay_per =
(uint64_t(1) << 40)
- ((uint64_t(1) << 40) * 693147ull) / (1000000ull * old_half_life);

// --- SSOT decay_per ---
const uint64_t ssot_decay_per = wd::decay_per_share(CL);

ASSERT_EQ(ssot_decay_per, old_decay_per);

// Synthetic share window: distinct attempts + donation per depth.
const std::vector<uint64_t> att = {
1000ull, 999983ull, 2500000ull, 65535ull,
1ull, 123456789ull, 42ull, 7777777ull,
};
const std::vector<uint64_t> don = { 0, 100, 32767, 65535, 1, 12345, 200, 65534 };
const size_t N = att.size();

// --- OLD path: iterative decay table via uint128 mul-shift ---
std::vector<uint64_t> old_table(N);
old_table[0] = (uint64_t(1) << 40); // DECAY_SCALE
for (size_t d = 1; d < N; ++d)
old_table[d] =
(uint64_t)((uint128(old_table[d-1]) * old_decay_per) >> 40);

// --- SSOT path: same uint128 mul-shift, SSOT-sourced constants/rate ---
std::vector<uint64_t> ssot_table(N);
ssot_table[0] = wd::DECAY_SCALE;
for (size_t d = 1; d < N; ++d)
ssot_table[d] = (uint64_t)(
(uint128(ssot_table[d-1]) * ssot_decay_per) >> wd::DECAY_PRECISION);

// Decay-table vectors must be element-by-element identical.
ASSERT_EQ(old_table.size(), ssot_table.size());
for (size_t d = 0; d < N; ++d)
EXPECT_EQ(old_table[d], ssot_table[d]) << "decay_table depth " << d;

// Per-depth decayed attempts + addr/donation split + running cumulative
// totals, mirroring the consumer's uint288 hot path (replicated with
// uint128 here; the SSOT scalar decayed_attempts is uint64-only so it is
// intentionally NOT used for the att*table widening).
uint128 old_cum_total = 0, old_cum_don = 0;
uint128 ssot_cum_total = 0, ssot_cum_don = 0;
for (size_t d = 0; d < N; ++d) {
const uint128 old_dec = (uint128(att[d]) * old_table[d]) >> 40;
const uint128 ssot_dec =
(uint128(att[d]) * ssot_table[d]) >> wd::DECAY_PRECISION;
EXPECT_EQ((uint64_t)old_dec, (uint64_t)ssot_dec) << "decayed_att depth " << d;

const uint128 old_addr = old_dec * uint128(65535 - don[d]);
const uint128 old_donw = old_dec * uint128(don[d]);
const uint128 ssot_addr = ssot_dec * uint128(65535 - don[d]);
const uint128 ssot_donw = ssot_dec * uint128(don[d]);
EXPECT_EQ((uint64_t)old_addr, (uint64_t)ssot_addr) << "addr_w depth " << d;
EXPECT_EQ((uint64_t)old_donw, (uint64_t)ssot_donw) << "don_w depth " << d;

old_cum_total += old_addr + old_donw;
old_cum_don += old_donw;
ssot_cum_total += ssot_addr + ssot_donw;
ssot_cum_don += ssot_donw;
EXPECT_EQ((uint64_t)old_cum_total, (uint64_t)ssot_cum_total)
<< "cum_total depth " << d;
EXPECT_EQ((uint64_t)old_cum_don, (uint64_t)ssot_cum_don)
<< "cum_don depth " << d;
}
}
Loading