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
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:
dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test nmc_mempool_name_test nmc_block_broadcast_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test \
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_naughty_propagation_test v37_test \
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test v37_test \
-j$(nproc)

- name: Run tests
Expand Down Expand Up @@ -216,7 +216,7 @@ jobs:
dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test nmc_mempool_name_test nmc_block_broadcast_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test \
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_naughty_propagation_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
v37_test \
-j$(nproc)

Expand Down
54 changes: 54 additions & 0 deletions src/impl/dgb/coin/share_weight_decay.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#pragma once
// V36 PPLNS exponential weight-decay parameters -- SSOT.
//
// Conforms to frstrtr/p2pool-merged-v36 p2pool/data.py
// get_decayed_cumulative_weights (the V36-native PPLNS path):
// half_life = max(net.CHAIN_LENGTH // 4, 1)
// decay_per = SCALE - (SCALE * 693147) // (1_000_000 * half_life)
// decayed_att = (att * decay_fp) >> 40 # 40-bit fixed point
// where SCALE = 1 << 40, 693147 = ln(2) * 1e6 truncated. The fixed-point
// form is consensus-deterministic (no floating point).
//
// V36-NATIVE shared structure (3-bucket rule, bucket 2): the decayed PPLNS
// weighting is the V36 reward-distribution shape, distinct from the older
// p2pool-dgb-scrypt flat WeightsSkipList path (which has no decay and stays
// the live baseline during the crossing window). Standardize cross-coin
// toward this shape for the V37 unified migration.
//
// FENCED: header-only, share_tracker.hpp NOT yet rewired. The three open-coded
// copies of this arithmetic --
// init_decay_table() (decay table precompute)
// get_v36_decayed_cumulative_weights() (hot-path PPLNS weights)
// the [PARENT-PPLNS] diagnostic dump (GENTX-mismatch debug only)
// -- delegate onto this SSOT in a byte-identity follow-on PR.
#include <algorithm>
#include <cstdint>

namespace dgb::coin::weight_decay {

// 40-bit fixed-point scale (1.0 == DECAY_SCALE). Consensus-deterministic.
inline constexpr uint64_t DECAY_PRECISION = 40;
inline constexpr uint64_t DECAY_SCALE = uint64_t(1) << DECAY_PRECISION;
// ln(2) * 1e6, truncated for integer arithmetic.
inline constexpr uint64_t LN2_MICRO = 693147;

// half_life = max(chain_length / 4, 1) (oracle: max(CHAIN_LENGTH // 4, 1)).
// Guard keeps the divisor non-zero for tiny chains.
inline constexpr uint32_t half_life(uint32_t chain_length) {
return std::max(chain_length / 4u, uint32_t(1));
}

// Per-share decay multiplier in fixed point:
// 2^(-1/H) ~= 1 - ln(2)/H -> SCALE - SCALE*693147 / (1e6 * H)
inline constexpr uint64_t decay_per_share(uint32_t chain_length) {
return DECAY_SCALE
- (DECAY_SCALE * LN2_MICRO) / (uint64_t(1000000) * half_life(chain_length));
}

// Apply the accumulated depth-decay factor to a share attempts count:
// decayed_att = (att * decay_fp) >> 40
inline constexpr uint64_t decayed_attempts(uint64_t att, uint64_t decay_fp) {
return (att * decay_fp) >> DECAY_PRECISION;
}

} // namespace dgb::coin::weight_decay
12 changes: 12 additions & 0 deletions src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,18 @@ if (BUILD_TESTING AND GTest_FOUND)
GTest::gtest_main GTest::gtest
core c2pool_hashrate c2pool_merged_mining c2pool_payout c2pool_storage)
gtest_add_tests(dgb_pool_attempts_per_second_test "" AUTO)
# dgb_share_weight_decay_test: FENCED, additive KAT pinning the V36 PPLNS
# exponential weight-decay rate arithmetic (half_life = CHAIN_LENGTH/4,
# decay_per = SCALE - SCALE*693147/(1e6*half_life), decayed_att =
# (att*decay_fp)>>40) lifted into coin/share_weight_decay.hpp vs the
# frstrtr/p2pool-merged-v36 data.py get_decayed_cumulative_weights oracle.
# Header-only constexpr (no core types); share_tracker.hpp NOT yet rewired
# (the 3-copy delegation is the byte-identity follow-on). MUST also be in
# the build.yml --target allowlist (#143 NOT_BUILT trap).
add_executable(dgb_share_weight_decay_test share_weight_decay_test.cpp)
target_link_libraries(dgb_share_weight_decay_test PRIVATE
GTest::gtest_main GTest::gtest)
gtest_add_tests(dgb_share_weight_decay_test "" AUTO)
# dgb_binomial_conf_interval_test: FENCED, additive KAT pinning the pure
# Wilson-score confidence-interval arithmetic of p2pool util/math.py:133
# binomial_conf_interval (+ ierf/erf A&S-7.1.26 helpers) lifted into
Expand Down
87 changes: 87 additions & 0 deletions src/impl/dgb/test/share_weight_decay_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// dgb::coin::weight_decay -- V36 PPLNS exponential weight-decay KAT.
//
// FENCED conformance test (no production code touched). Pins the decay-rate
// arithmetic lifted into coin/share_weight_decay.hpp against the
// frstrtr/p2pool-merged-v36 oracle p2pool/data.py get_decayed_cumulative_weights:
// half_life = max(net.CHAIN_LENGTH // 4, 1)
// decay_per = SCALE - (SCALE * 693147) // (1_000_000 * half_life)
// decayed_att = (att * decay_fp) >> 40
// SCALE = 1 << 40, 693147 = ln(2) * 1e6 truncated.
//
// All expected values are HAND-DERIVED by independently evaluating the oracle
// Python expression above (run as a separate reference, not by calling the
// helper under test), so the test is NON-CIRCULAR. The CL goldens cover the
// V36 DGB chain length (CHAIN_LENGTH = 24*60*60//10 = 8640), the older
// p2pool-dgb-scrypt baseline (12*60*60//15 = 2880), and the tiny-chain
// half_life=1 guard.
//
// MUST appear in BOTH the ctest registration (this dir CMakeLists.txt) AND the
// build.yml --target allowlist, or it becomes a #143-style NOT_BUILT sentinel
// that reds master.

#include <impl/dgb/coin/share_weight_decay.hpp>

#include <gtest/gtest.h>

namespace wd = dgb::coin::weight_decay;

// ---- fixed-point constants match the oracle exactly ---------------------

TEST(DgbWeightDecay, FixedPointConstants) {
EXPECT_EQ(wd::DECAY_PRECISION, 40u);
EXPECT_EQ(wd::DECAY_SCALE, uint64_t(1) << 40);
EXPECT_EQ(wd::DECAY_SCALE, uint64_t(1099511627776));
EXPECT_EQ(wd::LN2_MICRO, 693147u);
}

// ---- half_life = max(CHAIN_LENGTH // 4, 1) ------------------------------

TEST(DgbWeightDecay, HalfLifeMatchesOracle) {
EXPECT_EQ(wd::half_life(8640), 2160u); // V36 DGB: 8640 // 4
EXPECT_EQ(wd::half_life(2880), 720u); // baseline: 2880 // 4
EXPECT_EQ(wd::half_life(3600), 900u);
EXPECT_EQ(wd::half_life(720), 180u);
}

TEST(DgbWeightDecay, HalfLifeTinyChainGuard) {
// chain_length // 4 == 0 must clamp to 1 (oracle max(.., 1)).
EXPECT_EQ(wd::half_life(4), 1u);
EXPECT_EQ(wd::half_life(3), 1u);
EXPECT_EQ(wd::half_life(1), 1u);
EXPECT_EQ(wd::half_life(0), 1u);
}

// ---- decay_per = SCALE - SCALE*693147 // (1e6 * half_life) ---------------
// Goldens computed independently from the oracle expression.

TEST(DgbWeightDecay, DecayPerShareMatchesOracle) {
EXPECT_EQ(wd::decay_per_share(8640), uint64_t(1099158792968)); // hl=2160 (V36 DGB)
EXPECT_EQ(wd::decay_per_share(2880), uint64_t(1098453123351)); // hl=720 (baseline)
EXPECT_EQ(wd::decay_per_share(3600), uint64_t(1098664824236)); // hl=900
EXPECT_EQ(wd::decay_per_share(720), uint64_t(1095277610075)); // hl=180
EXPECT_EQ(wd::decay_per_share(4), uint64_t(337388441518)); // hl=1 (guard)
}

// ---- decayed_att = (att * decay_fp) >> 40 -------------------------------
// The per-depth decay_fp accumulation (decay_fp_{n+1} = decay_fp_n * decay_per)
// is a 80-bit product done with mul128_shift in the caller's walk -- that
// 128-bit step lives in share_tracker.hpp, NOT in this SSOT (which exposes only
// the rate constants + the final attempts shift). So the decay_fp values below
// are supplied as DIRECT oracle literals (computed in Python bignums from
// decay_fp_{n+1} = (decay_fp_n * decay_per) >> 40 with CL=8640,
// decay_per=1099158792968), and decayed_attempts is checked against each. The
// att*decay_fp product fits uint64 here because att (65535) is small.

TEST(DgbWeightDecay, DecayedAttemptsAtOracleDepthFactors) {
const uint64_t att = 65535; // target_to_average_attempts placeholder
// depth 0..3 decay_fp factors for the V36 DGB chain (CL=8640):
EXPECT_EQ(wd::decayed_attempts(att, uint64_t(1099511627776)), uint64_t(65535)); // depth 0: 1.0
EXPECT_EQ(wd::decayed_attempts(att, uint64_t(1099158792968)), uint64_t(65513)); // depth 1
EXPECT_EQ(wd::decayed_attempts(att, uint64_t(1098806071385)), uint64_t(65492)); // depth 2
EXPECT_EQ(wd::decayed_attempts(att, uint64_t(1098453462991)), uint64_t(65471)); // depth 3
}

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
}
Loading