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