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 @@ -94,7 +94,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 nmc_host_dualpath_test nmc_fallback_path_conformance_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_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test dgb_aux_dual_target_select_test dgb_aux_broadcast_path_election_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_share_bits_oracle_pin_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 dgb_hash_format_parity_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_share_bits_oracle_pin_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 dgb_hash_format_parity_test dgb_emergency_decay_saturation_test v37_test \
-j8

- name: Run tests
Expand Down Expand Up @@ -237,7 +237,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 nmc_host_dualpath_test nmc_fallback_path_conformance_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_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test dgb_aux_dual_target_select_test dgb_aux_broadcast_path_election_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_share_bits_oracle_pin_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 dgb_hash_format_parity_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_share_bits_oracle_pin_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 dgb_hash_format_parity_test dgb_emergency_decay_saturation_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
v37_test \
-j8

Expand Down
24 changes: 14 additions & 10 deletions src/impl/dgb/share_tracker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1528,19 +1528,23 @@ class ShareTracker
auto excess = time_since_share - emergency_threshold;
auto halvings = excess / half_life;
auto remainder = excess % half_life;
// 2^halvings with linear interpolation for fractional part
uint256 eased = prev_max_target;
if (halvings < 256)
eased <<= halvings;
else
eased = MAX_TARGET;
// Linear interpolation: eased = eased * (half_life + remainder) / half_life
// Step 3 (emergency ease): 2^halvings with linear interpolation.
// Shift in the wider uint288 accumulator, saturating at
// MAX_TARGET as we go so no uint256- OR uint288-representable
// wrap can occur. The old uint256 `eased <<= halvings` wrapped
// mod 2^256, collapsing the eased target to a hard value and
// stalling the DAA stall-path (~50-68min death-spiral hole).
uint288 eased_288;
eased_288.SetHex(eased.GetHex());
eased_288 = eased_288 * static_cast<uint32_t>(half_life + remainder);
eased_288 = eased_288 / static_cast<uint32_t>(half_life);
eased_288.SetHex(prev_max_target.GetHex());
uint288 max_288;
max_288.SetHex(MAX_TARGET.GetHex());
for (uint64_t i = 0; i < static_cast<uint64_t>(halvings) && i < 288 && eased_288 <= max_288; ++i)
eased_288 <<= 1;
if (eased_288 > max_288)
eased_288 = max_288;
// Linear interpolation: eased = eased * (half_life + remainder) / half_life
eased_288 = eased_288 * static_cast<uint32_t>(half_life + remainder);
eased_288 = eased_288 / static_cast<uint32_t>(half_life);
if (eased_288 > max_288)
clamp_ref_target = MAX_TARGET;
else
Expand Down
15 changes: 15 additions & 0 deletions src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,21 @@ if (BUILD_TESTING AND GTest_FOUND)
target_link_libraries(dgb_share_weight_decay_test PRIVATE
GTest::gtest_main GTest::gtest)
gtest_add_tests(dgb_share_weight_decay_test "" AUTO)
# dgb_emergency_decay_saturation_test: FENCED, additive KAT pinning the
# share_tracker.hpp Step-3 emergency time-based decay (death-spiral
# prevention) eased target == min(prev_max_target * 2^halvings, MAX_TARGET)
# against an arbitrary-precision oracle (right-shift threshold form, no wrap
# at 2^256 OR 2^288). Proves (a) the load-bearing h=21/57.5min fails-now/
# passes-after boundary vector and (b) full-sweep NEW==ORACLE identity. Uses
# core uint256/uint288 -> links core only (no OBJECT lib). MUST also be in
# the build.yml --target allowlist (#143 NOT_BUILT trap).
add_executable(dgb_emergency_decay_saturation_test emergency_decay_saturation_test.cpp)
target_link_libraries(dgb_emergency_decay_saturation_test PRIVATE
GTest::gtest_main GTest::gtest
core dgb
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage
dgb_coin pool sharechain)
gtest_add_tests(dgb_emergency_decay_saturation_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
230 changes: 230 additions & 0 deletions src/impl/dgb/test/emergency_decay_saturation_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// ---------------------------------------------------------------------------
// dgb emergency-decay (Step 3) saturating-shift regression guard.
//
// Pins the DGB sharechain emergency time-based decay ("death spiral
// prevention") at share_tracker.hpp Step 3 against its oracle. The eased
// target doubles once per half_life past the emergency threshold; the
// canonical retarget is the arbitrary-precision saturating value
//
// eased = min(prev_max_target * 2^halvings, MAX_TARGET)
//
// with NO wrap at any bound. The shipped fix performs the shift incrementally
// in the wider uint288 accumulator, saturating to MAX_TARGET as it goes, so
// neither a uint256 wrap (at 2^256) NOR a single-shot uint288 wrap (at 2^288,
// reachable for halvings >= 53) can collapse the eased target.
//
// The pre-fix shape did `uint256 eased <<= halvings`, which truncates mod
// 2^256: a prev target whose mantissa shifts past bit 256 wraps back to a
// SUB-MAX value, so the DAA stops easing and the chain death-spirals. The
// observed stall hole sat at ~50-68 min since last share.
//
// ORACLE NON-CIRCULARITY: oracle() below never shifts a value that could
// overflow -- it tests prev <= (MAX >> halvings) FIRST (a right-shift, which
// cannot overflow) and only then performs the in-range left-shift. A proxy
// that instead did `uint288 << halvings` would itself wrap at 2^288 and
// fabricate false mismatches; this guard MUST keep the right-shift threshold
// form. (Proxy gotcha baked in per the boundary-divergence evidence,
// sha256 5d5c9452..., ~/dgb-emergency-decay-evidence/.)
//
// Links ONLY core (uint256/uint288 big-int) + gtest -- no dgb OBJECT lib /
// transport, matching the pure-math scope of the Step-3 computation.
// ---------------------------------------------------------------------------

#include <cstdint>
#include <string>
#include <vector>

#include <gtest/gtest.h>

#include <core/uint256.hpp>

namespace {

// DGB Scrypt MAX_TARGET (powLimit), bits_to_target upper bound.
uint256 MAXT()
{
uint256 t;
t.SetHex("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
return t;
}

uint256 H(const std::string& s)
{
uint256 t;
t.SetHex(s);
return t;
}

// TRUE oracle: arbitrary-precision min(prev * 2^h, MAX). No wrap at any bound.
// prev<<h <= MAX iff prev <= (MAX >> h); the right-shift can never overflow, so
// the threshold test is exact and the surviving left-shift is in-range.
uint256 oracle(const uint256& prev, uint32_t h)
{
uint256 mx = MAXT();
if (h >= 256)
return prev.IsNull() ? prev : mx;
uint256 thr = mx;
thr >>= h;
if (prev > thr)
return mx;
uint288 f;
f.SetHex(prev.GetHex());
f <<= h;
uint256 r;
r.SetHex(f.GetHex());
return r;
}

// NEW shape: byte-for-byte the share_tracker.hpp Step-3 emergency-ease body
// (incremental uint288 saturating shift + linear interpolation), lifted as a
// pure function so the consensus math is provable in isolation.
uint256 new_shape(const uint256& prev, uint32_t h, uint32_t HL, uint32_t rem)
{
uint288 x;
x.SetHex(prev.GetHex());
uint288 m;
m.SetHex(MAXT().GetHex());
for (uint64_t i = 0; i < static_cast<uint64_t>(h) && i < 288 && x <= m; ++i)
x <<= 1;
if (x > m)
x = m;
x = x * static_cast<uint32_t>(HL + rem);
x = x / static_cast<uint32_t>(HL);
if (x > m)
return MAXT();
uint256 r;
r.SetHex(x.GetHex());
return r;
}

// OLD shape: the pre-fix uint256 `eased <<= halvings`, kept ONLY to demonstrate
// the fails-now divergence the fix repairs. Never shipped past the fix.
uint256 old_shape(const uint256& prev, uint32_t h, uint32_t HL, uint32_t rem)
{
uint256 e = prev;
if (h < 256)
e <<= h;
else
e = MAXT();
uint288 x;
x.SetHex(e.GetHex());
uint288 m;
m.SetHex(MAXT().GetHex());
x = x * static_cast<uint32_t>(HL + rem);
x = x / static_cast<uint32_t>(HL);
if (x > m)
return MAXT();
uint256 r;
r.SetHex(x.GetHex());
return r;
}

constexpr uint32_t HL = 150; // half_life = SHARE_PERIOD(15) * 10

// 5 representative prev_max_target vectors: a sparse hi-bit + lo-bit pattern
// that wraps under uint256 truncation, a mid-density, two dense, and the unit.
const char* const PREVS[] = {
"0000080000000000000000000000000000000000000000000000000000000001", // bit235 + bit0
"0000000000000fa1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f6071829",
"00000000000000000ddccbbaa9988776655443322110ffeeddccbbaa99887766",
"00000000003fedcba9876543210fedcba9876543210fedcba9876543210fedcb",
"0000000000000000000000000000000000000000000000000000000000000001",
};

// minutes-since-last-share for a given halving count h:
// threshold = SHARE_PERIOD*20 = 300s, half_life = SHARE_PERIOD*10 = 150s
// t = (300 + h*HL) / 60 minutes
double t_minutes(uint32_t h) { return (300.0 + static_cast<double>(h) * HL) / 60.0; }

} // namespace

// (a) LOAD-BEARING fails-now / passes-after vector.
// prev = bit235 + bit0, h = 21 (t = 57.5 min, dead-center of the observed
// 50-68 min stall hole). The pre-fix uint256 shift wraps bit235<<21 -> bit256
// -> 0 (mod 2^256), collapsing the eased target FAR below the saturating
// oracle; the shipped saturating shift returns MAX_TARGET == oracle.
TEST(EmergencyDecaySaturation, LoadBearingBoundaryVector_h21_57min)
{
const uint256 prev = H(PREVS[0]); // bit235 + bit0
const uint32_t h = 21;
ASSERT_DOUBLE_EQ(57.5, t_minutes(h)); // dead-center of the stall hole

const uint256 want = oracle(prev, h);
EXPECT_EQ(want, MAXT()) << "prev<<21 overflows MAX -> oracle saturates";

// fails-now: the pre-fix wrapping shape DIVERGES from the oracle here.
EXPECT_NE(old_shape(prev, h, HL, 0), want)
<< "pre-fix uint256 <<= wraps mod 2^256 and death-spirals";

// passes-after: the shipped saturating shift MATCHES the oracle.
EXPECT_EQ(new_shape(prev, h, HL, 0), want)
<< "saturating uint288 shift == min(prev*2^h, MAX)";
}

// (b) Full-sweep NEW == ORACLE behavioral identity, exact boundary (rem == 0),
// every prev vector x every halving count 0..255. Zero mismatch proves the fix
// is the oracle, not merely "closer".
TEST(EmergencyDecaySaturation, NewEqualsOracleIdentity_rem0_sweep)
{
int mismatch = 0;
for (const char* hx : PREVS)
{
const uint256 prev = H(hx);
for (uint32_t h = 0; h <= 255; ++h)
if (new_shape(prev, h, HL, 0) != oracle(prev, h))
++mismatch;
}
EXPECT_EQ(0, mismatch) << "NEW must equal oracle across full h-sweep";
}

// (b') Interpolated identity incl. the fractional remainder term:
// new == min( min(prev*2^h, MAX) * (HL+rem)/HL , MAX )
// across prev x h0..60 x rem. Pins the linear-interpolation tail too.
TEST(EmergencyDecaySaturation, NewEqualsOracleIdentity_interpolated_sweep)
{
int mismatch = 0;
for (const char* hx : PREVS)
{
const uint256 prev = H(hx);
for (uint32_t h = 0; h <= 60; ++h)
for (uint32_t rem = 0; rem < HL; rem += 29)
{
uint256 base = oracle(prev, h);
uint288 b;
b.SetHex(base.GetHex());
uint288 m;
m.SetHex(MAXT().GetHex());
b = b * static_cast<uint32_t>(HL + rem);
b = b / static_cast<uint32_t>(HL);
uint256 want;
if (b > m)
want = MAXT();
else
want.SetHex(b.GetHex());
if (new_shape(prev, h, HL, rem) != want)
++mismatch;
}
}
EXPECT_EQ(0, mismatch) << "interpolated NEW must equal oracle";
}

// (c) Divergence catalogue: there EXISTS a non-empty set of death-spiral cases
// where the pre-fix shape is wrong and the fix is right. Guards against a
// future "simplification" that silently reintroduces the wrap.
TEST(EmergencyDecaySaturation, OldShapeDeathSpiralCasesExist)
{
int total = 0;
for (const char* hx : PREVS)
{
const uint256 prev = H(hx);
for (uint32_t h = 0; h <= 255; ++h)
{
const uint256 o = oracle(prev, h);
const bool old_ok = (old_shape(prev, h, HL, 0) == o);
const bool new_ok = (new_shape(prev, h, HL, 0) == o);
if (!old_ok && new_ok)
++total;
}
}
EXPECT_GT(total, 0) << "the wrap bug must be demonstrably reachable";
}
Loading