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 dgb_emergency_decay_saturation_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_target_retarget_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 dgb_emergency_decay_saturation_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_target_retarget_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
15 changes: 15 additions & 0 deletions src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,21 @@ if (BUILD_TESTING AND GTest_FOUND)
dgb_coin pool sharechain)
gtest_add_tests(dgb_share_bits_oracle_pin_test "" AUTO)

# dgb_share_target_retarget_test: FENCED non-circular KAT pinning the
# NON-genesis (deep-chain, acc_height >= TARGET_LOOKBEHIND) branch of
# ShareTracker::compute_share_target -- Step-3 emergency time-based decay +
# Step-4 +/-10% clamp -- to goldens derived from an INDEPENDENT Python
# reference of the oracle formula (not from the SUT). Companion to
# dgb_share_target_genesis_test (which covers the genesis branch); links the
# same proven set. MUST also be in the build.yml --target allowlist (#143).
add_executable(dgb_share_target_retarget_test share_target_retarget_test.cpp)
target_link_libraries(dgb_share_target_retarget_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_share_target_retarget_test "" AUTO)

# dgb_g1_oracle_byte_parity_test: FENCED greenlight-gate G1 KAT. Pins the
# assembled make_coin_params() net/consensus constants AND bucket-1
# isolation primitives (PREFIX/IDENTIFIER) against oracle-transcribed
Expand Down
189 changes: 189 additions & 0 deletions src/impl/dgb/test/share_target_retarget_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// dgb::ShareTracker::compute_share_target — NON-genesis (seeded sharechain)
// branch KAT: Step-3 emergency time-based decay + Step-4 ±10% clamp.
//
// FENCED conformance test (no production code touched). The genesis/unknown-prev
// branch is already pinned by share_target_genesis_test.cpp; the deep-chain
// retarget path (acc_height >= TARGET_LOOKBEHIND) had NO direct coverage. This
// slice closes that gap and is the consensus-bearing half of compute_share_target.
//
// NON-CIRCULAR: every golden below is derived from an INDEPENDENT Python
// reference of the oracle formula (a throwaway script that re-implements
// target_utils.hpp bits_to_target / target_to_bits_upper_bound and the
// share_tracker.hpp Step-3/Step-4 arithmetic in big-ints), then transcribed as a
// literal. The test does NOT recompute any golden by calling compute_share_target
// or chain:: encoders on a SUT-derived intermediate — it asserts the SUT output
// equals the pre-computed oracle literal.
//
// Oracle SSOT: frstrtr/p2pool-dgb-scrypt bitcoin/data.py generate_transaction
// (pre_target derive -> emergency decay -> ±10% clamp) with MAX_TARGET from
// networks/digibyte.py = 2**256//2**20 - 1 (2^236 - 1); SHARE_PERIOD=15 =>
// emergency_threshold = 300s, half_life = 150s.
//
// DETERMINISM: the seeded chain uses HUGE inter-share timestamp gaps so the
// pool-attempts-per-second estimate drives pre_target to MAX_TARGET; the ±10%
// clamp then pins pre_target to the `hi` boundary, making max_bits depend ONLY
// on clamp_ref (the quantity under test) and not on the exact APS magnitude.
// desired_timestamp is measured against the TIP share's timestamp and controls
// ONLY the decay, independent of the gaps used for APS.
//
// CAVEAT (documented, not pinned): the SUT's Step-3 `eased` is a uint256, so
// `prev_max_target << halvings` silently WRAPS once the result reaches 2^256
// (here halvings >= 25 for the chosen prev_max_bits). An ideal big-int oracle
// saturates instead. This KAT deliberately exercises the saturation guard at the
// FIRST crossing (halvings=5, C4) where the SUT and a faithful big-int agree; it
// does NOT assert the extreme-decay (halvings>=25) regime, which is a known
// SUT/oracle divergence surfaced separately.
//
// 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/share_tracker.hpp>
#include <impl/dgb/config_pool.hpp>
#include <core/target_utils.hpp>
#include <core/uint256.hpp>

#include <gtest/gtest.h>

#include <cstdint>
#include <cstdio>
#include <string>

namespace {

// prev (tip) share max_bits used for every case: target_to_bits_upper_bound of
// MAX_TARGET/30 (= 0x1e008888). bits_to_target(0x1e008888) = M, the clamp_ref
// base the decay scales. Oracle-verified: M*11//10 < MAX_TARGET, so the un-
// decayed and one/interp-decayed `hi` bounds all stay below MAX_TARGET (i.e.
// they exercise a real clamp, not the saturation guard) — except C4 by design.
constexpr uint32_t kPrevMaxBits = 0x1e008888u;

// TIP share timestamp. desired_timestamp is offset from THIS value to control
// the emergency decay (Step 3). The inter-share gaps below are a separate axis.
constexpr uint32_t kTipTs = 2000000000u;

// Build a hex string for a 64-hex-char (uint256) hash from a small index, so the
// 101 seeded shares get distinct, deterministic hashes.
std::string hash_hex(uint32_t i) {
char buf[65];
// zero-padded 64-hex; put the index in the low bytes.
std::snprintf(buf, sizeof(buf),
"%040x%024x", 0u, i);
return std::string(buf);
}

// Seed a linked chain of `count` shares (genesis at index 0). Each share's
// timestamp is base_ts + i*gap (so the APS window timespan = 99*gap). All shares
// carry m_max_bits = kPrevMaxBits; the TIP (last) share's timestamp is forced to
// kTipTs so desired_timestamp deltas are well-defined regardless of `gap`.
// Returns the tip hash. acc_height after seeding `count` shares is count-1.
uint256 seed_chain(dgb::ShareTracker& tracker, uint32_t count, uint32_t gap) {
uint256 prev; // null for genesis
uint256 tip;
for (uint32_t i = 0; i < count; ++i) {
auto* s = new dgb::MergedMiningShare();
s->m_hash.SetHex(hash_hex(i + 1)); // +1 so index 0 isn't the null hash
if (i == 0) s->m_prev_hash.SetNull();
else s->m_prev_hash = prev;
s->m_bits = kPrevMaxBits;
s->m_max_bits = kPrevMaxBits;
// huge gaps -> tiny APS -> pre_target saturates to MAX_TARGET.
s->m_timestamp = kTipTs - (count - 1 - i) * gap;
dgb::ShareType st; st = s;
tracker.add(st);
prev = s->m_hash;
tip = s->m_hash;
}
return tip;
}

// Seed 101 shares (acc_height = 100 = TARGET_LOOKBEHIND) so the non-genesis,
// deep-enough retarget branch is taken.
constexpr uint32_t kCount = 101u;
// HUGE gap so APS ~ 0 -> pre_target = MAX_TARGET -> ±10% clamp pins to `hi`.
constexpr uint32_t kHiGap = 1000000u;

// Assert the seeded chain actually reached the deep (non-genesis) branch: a
// genesis-branch call (null prev) would return max_bits = tubu(MAX_TARGET) =
// 0x1e0fffff; the seeded hi-clamp cases below return a strictly different,
// clamp-derived max_bits, proving the deep path was exercised.
void expect_deep_branch_distinct(uint32_t got_max_bits) {
EXPECT_NE(got_max_bits, 0x1e0fffffu)
<< "got the genesis/MAX_TARGET max_bits — deep branch was NOT exercised";
}

// ─── C1: no decay (t <= 300), hi clamp ──────────────────────────────────────
// t=100 <= emergency_threshold => clamp_ref = M => hi = M*11//10.
// Oracle: max_bits = target_to_bits_upper_bound(M*11//10) = 0x1e00962f.
TEST(DgbShareTargetRetarget, C1_NoDecayHiClamp) {
dgb::ShareTracker tracker;
auto tip = seed_chain(tracker, kCount, kHiGap);
const uint256 MAX_TARGET = dgb::PoolConfig::max_target();
auto st = tracker.compute_share_target(tip, kTipTs + 100u, MAX_TARGET);
EXPECT_EQ(st.max_bits, 0x1e00962fu);
// desired_target = MAX_TARGET -> clipped down to pre_target3 -> bits==max_bits.
EXPECT_EQ(st.bits, 0x1e00962fu);
expect_deep_branch_distinct(st.max_bits);
}

// ─── C2: one halving, no remainder ──────────────────────────────────────────
// t=450 => excess=150, halvings=1, rem=0 => clamp_ref = 2M => hi = 2M*11//10.
// Oracle: max_bits = 0x1e012c5e.
TEST(DgbShareTargetRetarget, C2_DecayOneHalvingNoRemainder) {
dgb::ShareTracker tracker;
auto tip = seed_chain(tracker, kCount, kHiGap);
const uint256 MAX_TARGET = dgb::PoolConfig::max_target();
auto st = tracker.compute_share_target(tip, kTipTs + 450u, MAX_TARGET);
EXPECT_EQ(st.max_bits, 0x1e012c5eu);
EXPECT_EQ(st.bits, 0x1e012c5eu);
expect_deep_branch_distinct(st.max_bits);
}

// ─── C3: linear interpolation of the fractional halving ─────────────────────
// t=525 => excess=225, halvings=1, rem=75 => clamp_ref = 2M*(150+75)//150 = 3M
// => hi = 3M*11//10. Oracle: max_bits = 0x1e01c28d.
TEST(DgbShareTargetRetarget, C3_DecayInterpolation) {
dgb::ShareTracker tracker;
auto tip = seed_chain(tracker, kCount, kHiGap);
const uint256 MAX_TARGET = dgb::PoolConfig::max_target();
auto st = tracker.compute_share_target(tip, kTipTs + 525u, MAX_TARGET);
EXPECT_EQ(st.max_bits, 0x1e01c28du);
EXPECT_EQ(st.bits, 0x1e01c28du);
expect_deep_branch_distinct(st.max_bits);
}

// ─── C4: decay saturates clamp_ref to MAX_TARGET ────────────────────────────
// t = 300 + 150*5 = 1050 => excess=750, halvings=5, rem=0 => eased = M << 5,
// which exceeds MAX_TARGET => clamp_ref = MAX_TARGET => hi = MAX_TARGET =>
// max_bits = tubu(MAX_TARGET) = 0x1e0fffff. halvings=5 is the FIRST shift that
// crosses MAX_TARGET; it is also well within uint256, so this case pins the
// saturation guard WITHOUT relying on the eased<<halvings overflow regime (see
// the file-level caveat: the SUT shift is a uint256, so prev_max_target<<halvings
// silently wraps once it reaches 2^256, i.e. halvings>=25 for this M — that
// extreme-decay regime is a known SUT/oracle divergence and is NOT pinned here).
// (deep-branch-distinct sanity intentionally skipped: the SATURATED clamp_ref
// legitimately equals the genesis max_bits — the point of the case; path
// coverage is proven by C1–C3 + C5.)
TEST(DgbShareTargetRetarget, C4_DecaySaturatesToMax) {
dgb::ShareTracker tracker;
auto tip = seed_chain(tracker, kCount, kHiGap);
const uint256 MAX_TARGET = dgb::PoolConfig::max_target();
auto st = tracker.compute_share_target(tip, kTipTs + 1050u, MAX_TARGET);
EXPECT_EQ(st.max_bits, 0x1e0fffffu);
EXPECT_EQ(st.bits, 0x1e0fffffu);
}

// ─── C5: lo clamp (high APS drives pre_target below the lower bound) ─────────
// Small inter-share gaps (1s) => high APS => pre_target collapses far below
// clamp_ref*9//10; with t<=300 (no decay) clamp_ref = M so lo = M*9//10 and
// pre_target2 is pinned UP to lo. Oracle: max_bits = tubu(M*9//10) = 0x1d7ae0cc.
TEST(DgbShareTargetRetarget, C5_LoClamp) {
dgb::ShareTracker tracker;
auto tip = seed_chain(tracker, kCount, /*gap=*/1u);
const uint256 MAX_TARGET = dgb::PoolConfig::max_target();
auto st = tracker.compute_share_target(tip, kTipTs + 100u, MAX_TARGET);
EXPECT_EQ(st.max_bits, 0x1d7ae0ccu);
expect_deep_branch_distinct(st.max_bits);
}

} // namespace
Loading