Skip to content

Commit 20fb437

Browse files
authored
Merge pull request #556 from frstrtr/dgb/phaseB-retarget-conformance-kat
dgb: fenced non-genesis share-retarget conformance KAT (emergency-decay + ±10% clamp, oracle-pinned)
2 parents 062183e + c5151f6 commit 20fb437

3 files changed

Lines changed: 206 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ jobs:
9494
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 \
9595
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 \
9696
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
97-
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 \
97+
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 \
9898
-j8
9999
100100
- name: Run tests
@@ -237,7 +237,7 @@ jobs:
237237
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 \
238238
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 \
239239
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
240-
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 \
240+
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 \
241241
v37_test \
242242
-j8
243243

src/impl/dgb/test/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,21 @@ if (BUILD_TESTING AND GTest_FOUND)
678678
dgb_coin pool sharechain)
679679
gtest_add_tests(dgb_share_bits_oracle_pin_test "" AUTO)
680680

681+
# dgb_share_target_retarget_test: FENCED non-circular KAT pinning the
682+
# NON-genesis (deep-chain, acc_height >= TARGET_LOOKBEHIND) branch of
683+
# ShareTracker::compute_share_target -- Step-3 emergency time-based decay +
684+
# Step-4 +/-10% clamp -- to goldens derived from an INDEPENDENT Python
685+
# reference of the oracle formula (not from the SUT). Companion to
686+
# dgb_share_target_genesis_test (which covers the genesis branch); links the
687+
# same proven set. MUST also be in the build.yml --target allowlist (#143).
688+
add_executable(dgb_share_target_retarget_test share_target_retarget_test.cpp)
689+
target_link_libraries(dgb_share_target_retarget_test PRIVATE
690+
GTest::gtest_main GTest::gtest
691+
core dgb
692+
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage
693+
dgb_coin pool sharechain)
694+
gtest_add_tests(dgb_share_target_retarget_test "" AUTO)
695+
681696
# dgb_g1_oracle_byte_parity_test: FENCED greenlight-gate G1 KAT. Pins the
682697
# assembled make_coin_params() net/consensus constants AND bucket-1
683698
# isolation primitives (PREFIX/IDENTIFIER) against oracle-transcribed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// dgb::ShareTracker::compute_share_target — NON-genesis (seeded sharechain)
2+
// branch KAT: Step-3 emergency time-based decay + Step-4 ±10% clamp.
3+
//
4+
// FENCED conformance test (no production code touched). The genesis/unknown-prev
5+
// branch is already pinned by share_target_genesis_test.cpp; the deep-chain
6+
// retarget path (acc_height >= TARGET_LOOKBEHIND) had NO direct coverage. This
7+
// slice closes that gap and is the consensus-bearing half of compute_share_target.
8+
//
9+
// NON-CIRCULAR: every golden below is derived from an INDEPENDENT Python
10+
// reference of the oracle formula (a throwaway script that re-implements
11+
// target_utils.hpp bits_to_target / target_to_bits_upper_bound and the
12+
// share_tracker.hpp Step-3/Step-4 arithmetic in big-ints), then transcribed as a
13+
// literal. The test does NOT recompute any golden by calling compute_share_target
14+
// or chain:: encoders on a SUT-derived intermediate — it asserts the SUT output
15+
// equals the pre-computed oracle literal.
16+
//
17+
// Oracle SSOT: frstrtr/p2pool-dgb-scrypt bitcoin/data.py generate_transaction
18+
// (pre_target derive -> emergency decay -> ±10% clamp) with MAX_TARGET from
19+
// networks/digibyte.py = 2**256//2**20 - 1 (2^236 - 1); SHARE_PERIOD=15 =>
20+
// emergency_threshold = 300s, half_life = 150s.
21+
//
22+
// DETERMINISM: the seeded chain uses HUGE inter-share timestamp gaps so the
23+
// pool-attempts-per-second estimate drives pre_target to MAX_TARGET; the ±10%
24+
// clamp then pins pre_target to the `hi` boundary, making max_bits depend ONLY
25+
// on clamp_ref (the quantity under test) and not on the exact APS magnitude.
26+
// desired_timestamp is measured against the TIP share's timestamp and controls
27+
// ONLY the decay, independent of the gaps used for APS.
28+
//
29+
// CAVEAT (documented, not pinned): the SUT's Step-3 `eased` is a uint256, so
30+
// `prev_max_target << halvings` silently WRAPS once the result reaches 2^256
31+
// (here halvings >= 25 for the chosen prev_max_bits). An ideal big-int oracle
32+
// saturates instead. This KAT deliberately exercises the saturation guard at the
33+
// FIRST crossing (halvings=5, C4) where the SUT and a faithful big-int agree; it
34+
// does NOT assert the extreme-decay (halvings>=25) regime, which is a known
35+
// SUT/oracle divergence surfaced separately.
36+
//
37+
// MUST appear in BOTH the ctest registration (this dir CMakeLists.txt) AND the
38+
// build.yml --target allowlist, or it becomes a #143-style NOT_BUILT sentinel
39+
// that reds master.
40+
41+
#include <impl/dgb/share_tracker.hpp>
42+
#include <impl/dgb/config_pool.hpp>
43+
#include <core/target_utils.hpp>
44+
#include <core/uint256.hpp>
45+
46+
#include <gtest/gtest.h>
47+
48+
#include <cstdint>
49+
#include <cstdio>
50+
#include <string>
51+
52+
namespace {
53+
54+
// prev (tip) share max_bits used for every case: target_to_bits_upper_bound of
55+
// MAX_TARGET/30 (= 0x1e008888). bits_to_target(0x1e008888) = M, the clamp_ref
56+
// base the decay scales. Oracle-verified: M*11//10 < MAX_TARGET, so the un-
57+
// decayed and one/interp-decayed `hi` bounds all stay below MAX_TARGET (i.e.
58+
// they exercise a real clamp, not the saturation guard) — except C4 by design.
59+
constexpr uint32_t kPrevMaxBits = 0x1e008888u;
60+
61+
// TIP share timestamp. desired_timestamp is offset from THIS value to control
62+
// the emergency decay (Step 3). The inter-share gaps below are a separate axis.
63+
constexpr uint32_t kTipTs = 2000000000u;
64+
65+
// Build a hex string for a 64-hex-char (uint256) hash from a small index, so the
66+
// 101 seeded shares get distinct, deterministic hashes.
67+
std::string hash_hex(uint32_t i) {
68+
char buf[65];
69+
// zero-padded 64-hex; put the index in the low bytes.
70+
std::snprintf(buf, sizeof(buf),
71+
"%040x%024x", 0u, i);
72+
return std::string(buf);
73+
}
74+
75+
// Seed a linked chain of `count` shares (genesis at index 0). Each share's
76+
// timestamp is base_ts + i*gap (so the APS window timespan = 99*gap). All shares
77+
// carry m_max_bits = kPrevMaxBits; the TIP (last) share's timestamp is forced to
78+
// kTipTs so desired_timestamp deltas are well-defined regardless of `gap`.
79+
// Returns the tip hash. acc_height after seeding `count` shares is count-1.
80+
uint256 seed_chain(dgb::ShareTracker& tracker, uint32_t count, uint32_t gap) {
81+
uint256 prev; // null for genesis
82+
uint256 tip;
83+
for (uint32_t i = 0; i < count; ++i) {
84+
auto* s = new dgb::MergedMiningShare();
85+
s->m_hash.SetHex(hash_hex(i + 1)); // +1 so index 0 isn't the null hash
86+
if (i == 0) s->m_prev_hash.SetNull();
87+
else s->m_prev_hash = prev;
88+
s->m_bits = kPrevMaxBits;
89+
s->m_max_bits = kPrevMaxBits;
90+
// huge gaps -> tiny APS -> pre_target saturates to MAX_TARGET.
91+
s->m_timestamp = kTipTs - (count - 1 - i) * gap;
92+
dgb::ShareType st; st = s;
93+
tracker.add(st);
94+
prev = s->m_hash;
95+
tip = s->m_hash;
96+
}
97+
return tip;
98+
}
99+
100+
// Seed 101 shares (acc_height = 100 = TARGET_LOOKBEHIND) so the non-genesis,
101+
// deep-enough retarget branch is taken.
102+
constexpr uint32_t kCount = 101u;
103+
// HUGE gap so APS ~ 0 -> pre_target = MAX_TARGET -> ±10% clamp pins to `hi`.
104+
constexpr uint32_t kHiGap = 1000000u;
105+
106+
// Assert the seeded chain actually reached the deep (non-genesis) branch: a
107+
// genesis-branch call (null prev) would return max_bits = tubu(MAX_TARGET) =
108+
// 0x1e0fffff; the seeded hi-clamp cases below return a strictly different,
109+
// clamp-derived max_bits, proving the deep path was exercised.
110+
void expect_deep_branch_distinct(uint32_t got_max_bits) {
111+
EXPECT_NE(got_max_bits, 0x1e0fffffu)
112+
<< "got the genesis/MAX_TARGET max_bits — deep branch was NOT exercised";
113+
}
114+
115+
// ─── C1: no decay (t <= 300), hi clamp ──────────────────────────────────────
116+
// t=100 <= emergency_threshold => clamp_ref = M => hi = M*11//10.
117+
// Oracle: max_bits = target_to_bits_upper_bound(M*11//10) = 0x1e00962f.
118+
TEST(DgbShareTargetRetarget, C1_NoDecayHiClamp) {
119+
dgb::ShareTracker tracker;
120+
auto tip = seed_chain(tracker, kCount, kHiGap);
121+
const uint256 MAX_TARGET = dgb::PoolConfig::max_target();
122+
auto st = tracker.compute_share_target(tip, kTipTs + 100u, MAX_TARGET);
123+
EXPECT_EQ(st.max_bits, 0x1e00962fu);
124+
// desired_target = MAX_TARGET -> clipped down to pre_target3 -> bits==max_bits.
125+
EXPECT_EQ(st.bits, 0x1e00962fu);
126+
expect_deep_branch_distinct(st.max_bits);
127+
}
128+
129+
// ─── C2: one halving, no remainder ──────────────────────────────────────────
130+
// t=450 => excess=150, halvings=1, rem=0 => clamp_ref = 2M => hi = 2M*11//10.
131+
// Oracle: max_bits = 0x1e012c5e.
132+
TEST(DgbShareTargetRetarget, C2_DecayOneHalvingNoRemainder) {
133+
dgb::ShareTracker tracker;
134+
auto tip = seed_chain(tracker, kCount, kHiGap);
135+
const uint256 MAX_TARGET = dgb::PoolConfig::max_target();
136+
auto st = tracker.compute_share_target(tip, kTipTs + 450u, MAX_TARGET);
137+
EXPECT_EQ(st.max_bits, 0x1e012c5eu);
138+
EXPECT_EQ(st.bits, 0x1e012c5eu);
139+
expect_deep_branch_distinct(st.max_bits);
140+
}
141+
142+
// ─── C3: linear interpolation of the fractional halving ─────────────────────
143+
// t=525 => excess=225, halvings=1, rem=75 => clamp_ref = 2M*(150+75)//150 = 3M
144+
// => hi = 3M*11//10. Oracle: max_bits = 0x1e01c28d.
145+
TEST(DgbShareTargetRetarget, C3_DecayInterpolation) {
146+
dgb::ShareTracker tracker;
147+
auto tip = seed_chain(tracker, kCount, kHiGap);
148+
const uint256 MAX_TARGET = dgb::PoolConfig::max_target();
149+
auto st = tracker.compute_share_target(tip, kTipTs + 525u, MAX_TARGET);
150+
EXPECT_EQ(st.max_bits, 0x1e01c28du);
151+
EXPECT_EQ(st.bits, 0x1e01c28du);
152+
expect_deep_branch_distinct(st.max_bits);
153+
}
154+
155+
// ─── C4: decay saturates clamp_ref to MAX_TARGET ────────────────────────────
156+
// t = 300 + 150*5 = 1050 => excess=750, halvings=5, rem=0 => eased = M << 5,
157+
// which exceeds MAX_TARGET => clamp_ref = MAX_TARGET => hi = MAX_TARGET =>
158+
// max_bits = tubu(MAX_TARGET) = 0x1e0fffff. halvings=5 is the FIRST shift that
159+
// crosses MAX_TARGET; it is also well within uint256, so this case pins the
160+
// saturation guard WITHOUT relying on the eased<<halvings overflow regime (see
161+
// the file-level caveat: the SUT shift is a uint256, so prev_max_target<<halvings
162+
// silently wraps once it reaches 2^256, i.e. halvings>=25 for this M — that
163+
// extreme-decay regime is a known SUT/oracle divergence and is NOT pinned here).
164+
// (deep-branch-distinct sanity intentionally skipped: the SATURATED clamp_ref
165+
// legitimately equals the genesis max_bits — the point of the case; path
166+
// coverage is proven by C1–C3 + C5.)
167+
TEST(DgbShareTargetRetarget, C4_DecaySaturatesToMax) {
168+
dgb::ShareTracker tracker;
169+
auto tip = seed_chain(tracker, kCount, kHiGap);
170+
const uint256 MAX_TARGET = dgb::PoolConfig::max_target();
171+
auto st = tracker.compute_share_target(tip, kTipTs + 1050u, MAX_TARGET);
172+
EXPECT_EQ(st.max_bits, 0x1e0fffffu);
173+
EXPECT_EQ(st.bits, 0x1e0fffffu);
174+
}
175+
176+
// ─── C5: lo clamp (high APS drives pre_target below the lower bound) ─────────
177+
// Small inter-share gaps (1s) => high APS => pre_target collapses far below
178+
// clamp_ref*9//10; with t<=300 (no decay) clamp_ref = M so lo = M*9//10 and
179+
// pre_target2 is pinned UP to lo. Oracle: max_bits = tubu(M*9//10) = 0x1d7ae0cc.
180+
TEST(DgbShareTargetRetarget, C5_LoClamp) {
181+
dgb::ShareTracker tracker;
182+
auto tip = seed_chain(tracker, kCount, /*gap=*/1u);
183+
const uint256 MAX_TARGET = dgb::PoolConfig::max_target();
184+
auto st = tracker.compute_share_target(tip, kTipTs + 100u, MAX_TARGET);
185+
EXPECT_EQ(st.max_bits, 0x1d7ae0ccu);
186+
expect_deep_branch_distinct(st.max_bits);
187+
}
188+
189+
} // namespace

0 commit comments

Comments
 (0)