Skip to content
Closed
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: 12 additions & 15 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 "coin/share_weight_decay.hpp" // SSOT: half_life/decay_per_share (#450, get_decayed_cumulative_weights)
#include <impl/dgb/coin/desired_version_tally.hpp> // SSOT: accumulate_version_weights / accumulate_version_counts (#429 follow-on)
#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)
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;
// V36 decay constants sourced from coin/share_weight_decay.hpp SSOT (#450).
static constexpr uint64_t DECAY_PRECISION = dgb::coin::weight_decay::DECAY_PRECISION;
static constexpr uint64_t DECAY_SCALE = dgb::coin::weight_decay::DECAY_SCALE;

// 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,10 @@ 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;
static constexpr uint64_t DECAY_PRECISION = dgb::coin::weight_decay::DECAY_PRECISION;
static constexpr uint64_t DECAY_SCALE = dgb::coin::weight_decay::DECAY_SCALE;

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);
uint64_t decay_per = dgb::coin::weight_decay::decay_per_share(PoolConfig::chain_length());

CumulativeWeights result;
int32_t share_count = 0;
Expand Down Expand Up @@ -1804,12 +1802,11 @@ 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;
static constexpr uint64_t DECAY_PRECISION = dgb::coin::weight_decay::DECAY_PRECISION;
static constexpr uint64_t DECAY_SCALE = dgb::coin::weight_decay::DECAY_SCALE;

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);
uint32_t half_life = dgb::coin::weight_decay::half_life(PoolConfig::chain_length());
uint64_t decay_per = dgb::coin::weight_decay::decay_per_share(PoolConfig::chain_length());

int32_t share_count = 0;
uint64_t decay_fp = DECAY_SCALE;
Expand Down
84 changes: 84 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,87 @@ 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 VALUE-INVARIANCE KAT -- proves the share_tracker.hpp rewire onto
// the SSOT is byte-identical to the prior open-coded arithmetic.
//
// Each of the three rewired sites (init_decay_table, get_v36_decayed_
// cumulative_weights, dump_v36_pplns_walk) previously computed:
// half_life = max(chain_length / 4, 1)
// decay_per = SCALE - (SCALE * 693147) / (1e6 * half_life)
// decay_fp_{d+1} = mul128_shift(decay_fp_d, decay_per, 40)
// decayed_att = (att * decay_fp) >> 40
// inline. The rewire routes half_life/decay_per/constants through the SSOT
// while leaving the mul128_shift decay_fp walk + the uint288 hot-path
// widening open-coded (the consensus/wire bytes). This KAT re-evaluates
// the FULL decayed-cumulative-weight vector via the OLD literal expressions
// and via the SSOT helpers over a depth + chain-length sweep and asserts
// equality -- the proof the merge needs.
//
// mul128_shift is reproduced verbatim from share_tracker.hpp; it is the
// SAME function on both arms (unchanged by the rewire), so it cancels and
// the comparison isolates exactly what moved into the SSOT.
namespace {
inline uint64_t mul128_shift_ref(uint64_t a, uint64_t b, unsigned shift) {
return static_cast<uint64_t>((static_cast<__uint128_t>(a) * b) >> shift);
}

// OLD open-coded constants (pre-rewire literals).
constexpr uint64_t OLD_SCALE = uint64_t(1) << 40;
constexpr uint64_t OLD_LN2 = 693147;
inline uint32_t old_half_life(uint32_t cl) { return std::max(cl / 4, uint32_t(1)); }
inline uint64_t old_decay_per(uint32_t cl) {
return OLD_SCALE - (OLD_SCALE * OLD_LN2) / (uint64_t(1000000) * old_half_life(cl));
}
} // namespace

TEST(DgbWeightDecay, RewireConstantsValueInvariant) {
for (uint32_t cl : {8640u, 2880u, 3600u, 720u, 100u, 8u, 4u, 3u, 1u, 0u}) {
EXPECT_EQ(old_half_life(cl), wd::half_life(cl)) << "half_life cl=" << cl;
EXPECT_EQ(old_decay_per(cl), wd::decay_per_share(cl)) << "decay_per cl=" << cl;
EXPECT_EQ(wd::DECAY_SCALE, OLD_SCALE);
EXPECT_EQ(wd::DECAY_PRECISION, 40u);
}
}

// Full decayed-cumulative-weight vector: old expressions vs SSOT helpers.
TEST(DgbWeightDecay, DecayedCumulativeWeightVectorInvariant) {
for (uint32_t cl : {8640u, 2880u, 720u, 4u}) {
const uint64_t old_per = old_decay_per(cl);
const uint64_t new_per = wd::decay_per_share(cl);
ASSERT_EQ(old_per, new_per);

uint64_t old_fp = OLD_SCALE;
uint64_t new_fp = wd::DECAY_SCALE;
uint64_t old_cum_addr = 0, new_cum_addr = 0;
uint64_t old_cum_total = 0, new_cum_total = 0;

const int depths = 64;
for (int d = 0; d < depths; ++d) {
for (uint64_t att : {uint64_t(65535), uint64_t(1000), uint64_t(7), uint64_t(0)}) {
for (uint32_t don : {0u, 1u, 100u, 65535u}) {
uint64_t old_dec = (att * old_fp) >> 40;
uint64_t old_addr = old_dec * (65535 - don);
uint64_t old_don = old_dec * don;
uint64_t new_dec = wd::decayed_attempts(att, new_fp);
uint64_t new_addr = new_dec * (65535 - don);
uint64_t new_don = new_dec * don;

EXPECT_EQ(old_dec, new_dec) << "cl=" << cl << " d=" << d << " att=" << att;
EXPECT_EQ(old_addr, new_addr);
EXPECT_EQ(old_don, new_don);

old_cum_addr += old_addr; new_cum_addr += new_addr;
old_cum_total += old_addr + old_don;
new_cum_total += new_addr + new_don;
}
}
old_fp = mul128_shift_ref(old_fp, old_per, 40);
new_fp = mul128_shift_ref(new_fp, new_per, 40);
ASSERT_EQ(old_fp, new_fp) << "decay_fp drift cl=" << cl << " d=" << d;
}
EXPECT_EQ(old_cum_addr, new_cum_addr) << "cumulative addr weight cl=" << cl;
EXPECT_EQ(old_cum_total, new_cum_total) << "cumulative total weight cl=" << cl;
}
}
Loading