Skip to content

Commit eebf1da

Browse files
committed
ltc(KAT): pin chain-walk lookbehind window clamp+guard via real ShareTracker
Additive, fenced KAT (no production code touched). Pins the pure-integer window idiom governing every backward sharechain accessor: actual = min(lookbehind, height); if (actual <= 0) -> empty result open-coded inline in four share_tracker.hpp accessors (get_average_stale_prop, get_stale_counts, get_desired_version_counts, get_desired_version_weights). Drives the clamp+guard through the REAL ltc::ShareTracker chain-walk API over a dense (H, lookbehind) matrix; the realized walk length is asserted against an independent inline min() oracle. The clamp feeds the check()-phase 60% work-weighted v36 switch gate and the #288 AutoRatchet VOTING tail guard. Registered into the share_test target so it cannot become a #143 NOT_BUILT sentinel. 5/5 PASS on Linux x86_64.
1 parent e3a0483 commit eebf1da

2 files changed

Lines changed: 219 additions & 1 deletion

File tree

src/impl/ltc/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
if (BUILD_TESTING AND GTest_FOUND)
2-
add_executable(share_test share_test.cpp f11_donation_invariance_test.cpp emergency_decay_overflow_test.cpp wirecompat_runtime_test.cpp desired_version_tally_test.cpp auto_ratchet_sim_test.cpp auto_ratchet_tail_guard_test.cpp)
2+
add_executable(share_test share_test.cpp f11_donation_invariance_test.cpp emergency_decay_overflow_test.cpp wirecompat_runtime_test.cpp desired_version_tally_test.cpp auto_ratchet_sim_test.cpp auto_ratchet_tail_guard_test.cpp chain_walk_window_test.cpp)
33
target_link_libraries(share_test PRIVATE
44
GTest::gtest_main GTest::gtest
55
core ltc
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// LTC lookbehind chain-walk WINDOW clamp + activation guard -- KAT, exercised
3+
// through LTC's REAL ShareTracker API.
4+
//
5+
// FENCED, additive (no production code touched this slice). Pins the pure
6+
// integer window idiom that governs every backward sharechain accessor:
7+
// actual = min(lookbehind, height); if (actual <= 0) -> empty result
8+
//
9+
// Unlike dgb -- which lifts the clamp into a header (chain_walk_window.hpp) and
10+
// KATs the free functions -- LTC is the V36 reference impl and does NOT
11+
// modularize this guard: the identical three-line pattern is open-coded inline
12+
// in FOUR share_tracker.hpp accessors:
13+
// auto height = chain.get_height(share_hash); // depth of the head
14+
// auto actual = std::min(lookbehind, height); // clamp
15+
// if (actual <= 0) return <empty>; // guard
16+
// auto view = chain.get_chain(share_hash, actual); // walk
17+
// get_average_stale_prop (share_tracker.hpp:2091)
18+
// get_stale_counts (share_tracker.hpp:2114)
19+
// get_desired_version_counts (share_tracker.hpp:2156)
20+
// get_desired_version_weights (share_tracker.hpp:2185)
21+
//
22+
// APPROACH (mirrors the LTC desired_version_tally precedent, commit 0c8a83a3):
23+
// LTC has no header to lift, and this KAT must NOT touch production code, so it
24+
// drives the clamp+guard through the real chain-walk API rather than a lifted
25+
// oracle. It builds a resolved ltc::ShareTracker of ltc::MergedMiningShare (V36)
26+
// of a KNOWN depth H, then observes the REALIZED window -- the number of shares
27+
// get_desired_version_counts actually walks -- across a dense (H, lookbehind)
28+
// matrix. All shares carry one desired_version, so the single tally bucket's
29+
// count IS the realized walk length. The realized window is asserted equal to an
30+
// INDEPENDENT inline oracle min(lookbehind, H) computed from the construction
31+
// depth H and the loop variable -- NOT read from the code under test -- and the
32+
// empty/non-empty result is asserted to track the (actual > 0) guard.
33+
//
34+
// Why H == the share count: get_height returns get_delta_to_last().height, which
35+
// accumulates 1 per contained ancestor from the head to the null-parent chain
36+
// end, so a chain of H shares (share 0 has a null parent) reports height H. The
37+
// high-lookbehind matrix cells (lookbehind >> H) self-validate this: they observe
38+
// exactly H walked, proving height == H and available == H before the sub-H cells
39+
// exercise the clamp. This is the non-circular basis for the min() oracle.
40+
//
41+
// The clamp feeds two consensus-bearing consumers via get_desired_version_weights
42+
// -- the check()-phase 60% work-weighted v36 switch gate (share_check step 2) and
43+
// the #288 AutoRatchet VOTING tail guard -- so a drifted window would re-scope the
44+
// accept gate. share_tracker.hpp is NOT rewired here. This target joins the
45+
// existing `share_test` executable (already on the build.yml --target allowlist),
46+
// so it cannot become a #143 NOT_BUILT sentinel.
47+
48+
#include <cstddef>
49+
#include <cstdint>
50+
#include <cstdio>
51+
#include <algorithm>
52+
#include <map>
53+
#include <string>
54+
#include <vector>
55+
56+
#include <gtest/gtest.h>
57+
58+
#include <core/uint256.hpp>
59+
#include <impl/ltc/share.hpp>
60+
#include <impl/ltc/share_tracker.hpp>
61+
62+
namespace {
63+
64+
// A short hex tail -> uint256 (zero-padded to 64 nibbles), matching the LTC
65+
// desired_version_tally / dgb share_test hx() convention.
66+
uint256 hx(const std::string& tail) {
67+
uint256 v;
68+
v.SetHex(std::string(64 - tail.size(), '0') + tail);
69+
return v;
70+
}
71+
72+
// Build a resolved ltc::ShareTracker with `count` uniform V36 shares (share i's
73+
// parent is i-1; share 0 has a null parent = resolved chain genesis) and return
74+
// the tip hash. Every share carries the same desired_version so the single tally
75+
// bucket's count equals the realized walk length. Distinct `salt` gives disjoint
76+
// hash spaces across the matrix so no two trackers collide (each tracker is
77+
// independent, but keeping hashes disjoint is defensive).
78+
uint256 build_uniform_chain(ltc::ShareTracker& tracker, int32_t count,
79+
uint64_t dv, uint32_t bits, size_t salt) {
80+
uint256 prev;
81+
prev.SetNull();
82+
uint256 tip;
83+
tip.SetNull();
84+
for (int32_t i = 0; i < count; ++i) {
85+
char buf[24];
86+
std::snprintf(buf, sizeof(buf), "%zx",
87+
static_cast<size_t>((salt << 16) + 0x1c70 + i));
88+
uint256 h = hx(buf);
89+
auto* sh = new ltc::MergedMiningShare();
90+
sh->m_hash = h;
91+
if (i == 0) sh->m_prev_hash.SetNull(); else sh->m_prev_hash = prev;
92+
sh->m_desired_version = dv;
93+
sh->m_bits = bits;
94+
sh->m_max_bits = bits;
95+
ltc::ShareType st;
96+
st = sh;
97+
tracker.add(st);
98+
prev = h;
99+
tip = h;
100+
}
101+
return tip;
102+
}
103+
104+
// Realized window = number of shares the backward walk actually tallied. All
105+
// shares share one desired_version, so the map has at most one bucket whose
106+
// count IS the walk length; sum defensively.
107+
int32_t realized_window(ltc::ShareTracker& tracker, const uint256& tip,
108+
int32_t lookbehind) {
109+
int32_t total = 0;
110+
for (const auto& [dv, n] : tracker.get_desired_version_counts(tip, lookbehind))
111+
total += n;
112+
return total;
113+
}
114+
115+
// The walk runs (non-empty result) iff the realized window is positive.
116+
bool walk_ran(ltc::ShareTracker& tracker, const uint256& tip,
117+
int32_t lookbehind) {
118+
return !tracker.get_desired_version_counts(tip, lookbehind).empty();
119+
}
120+
121+
constexpr uint64_t kDv = 36;
122+
constexpr uint32_t kBits = 0x1e0fffff;
123+
124+
} // namespace
125+
126+
// --- realized window = min(lookbehind, height) ------------------------------
127+
TEST(LtcChainWalkWindow, ClampsToHeight) {
128+
// A head 6 shares deep.
129+
ltc::ShareTracker t6;
130+
const uint256 tip6 = build_uniform_chain(t6, 6, kDv, kBits, 1);
131+
// lookbehind below height -> identity (the ask is satisfiable).
132+
EXPECT_EQ(realized_window(t6, tip6, 3), 3);
133+
// lookbehind above height -> clamped to what the chain can yield.
134+
EXPECT_EQ(realized_window(t6, tip6, 720), 6);
135+
// exact boundary: equal -> that value.
136+
EXPECT_EQ(realized_window(t6, tip6, 6), 6);
137+
138+
// One-deep chain: at most one ancestor to walk.
139+
ltc::ShareTracker t1;
140+
const uint256 tip1 = build_uniform_chain(t1, 1, kDv, kBits, 2);
141+
EXPECT_EQ(realized_window(t1, tip1, 720), 1);
142+
EXPECT_EQ(realized_window(t1, tip1, 1), 1);
143+
}
144+
145+
// --- degenerate / defensive inputs ------------------------------------------
146+
TEST(LtcChainWalkWindow, NonPositiveInputs) {
147+
// Genesis head (no such share present) -> zero window: chain.contains()==false
148+
// is the height-0 analog (a present head is always >=1 deep), empty map.
149+
ltc::ShareTracker t;
150+
EXPECT_EQ(realized_window(t, hx("deadbeef"), 720), 0);
151+
EXPECT_FALSE(walk_ran(t, hx("deadbeef"), 720));
152+
153+
ltc::ShareTracker t3;
154+
const uint256 tip3 = build_uniform_chain(t3, 3, kDv, kBits, 3);
155+
// zero lookbehind -> min(0,height)=0 -> actual<=0 -> zero window.
156+
EXPECT_EQ(realized_window(t3, tip3, 0), 0);
157+
EXPECT_FALSE(walk_ran(t3, tip3, 0));
158+
// negative lookbehind -> min(-5,height)=-5 -> actual<=0 -> zero window.
159+
EXPECT_EQ(realized_window(t3, tip3, -5), 0);
160+
EXPECT_FALSE(walk_ran(t3, tip3, -5));
161+
}
162+
163+
// --- activation guard: actual > 0 -------------------------------------------
164+
TEST(LtcChainWalkWindow, ActivationGuard) {
165+
ltc::ShareTracker t;
166+
const uint256 tip = build_uniform_chain(t, 4, kDv, kBits, 4);
167+
// The accessors early-return the empty result when actual <= 0.
168+
EXPECT_FALSE(walk_ran(t, tip, 0));
169+
EXPECT_FALSE(walk_ran(t, tip, -3));
170+
// Absent tip is also empty (contains() guard, the height-0 analog).
171+
EXPECT_FALSE(walk_ran(t, hx("cafe"), 720));
172+
// A positive realized window runs.
173+
EXPECT_TRUE(walk_ran(t, tip, 1));
174+
EXPECT_TRUE(walk_ran(t, tip, 720));
175+
}
176+
177+
// --- composite: the realized window is walked iff it is positive -------------
178+
TEST(LtcChainWalkWindow, ClampThenActivateComposite) {
179+
// Present head, lookbehind 0: clamp to 0, guard fails -> no walk.
180+
ltc::ShareTracker t8;
181+
const uint256 tip8 = build_uniform_chain(t8, 8, kDv, kBits, 5);
182+
EXPECT_FALSE(walk_ran(t8, tip8, 0));
183+
184+
// One-deep: clamp to 1, guard passes -> walk one ancestor.
185+
ltc::ShareTracker t1;
186+
const uint256 tip1 = build_uniform_chain(t1, 1, kDv, kBits, 6);
187+
EXPECT_TRUE(walk_ran(t1, tip1, 720));
188+
EXPECT_EQ(realized_window(t1, tip1, 720), 1);
189+
190+
// Deep chain, real lookbehind: clamp to 8, walk 8.
191+
EXPECT_TRUE(walk_ran(t8, tip8, 720));
192+
EXPECT_EQ(realized_window(t8, tip8, 720), 8);
193+
}
194+
195+
// --- NON-CIRCULAR: realized window == inline min()+guard over a dense matrix --
196+
// Re-implements the exact clamp+guard pattern from the four share_tracker.hpp
197+
// accessors WITHOUT reading their `actual`, then proves the OBSERVABLE realized
198+
// window matches it across a dense (height, lookbehind) grid. The high-lookbehind
199+
// cells (lookbehind >> height) self-validate height == the construction depth,
200+
// giving the min() oracle a non-circular basis.
201+
TEST(LtcChainWalkWindow, RealizedWindowMatchesInlineClampMatrix) {
202+
for (int32_t height = 1; height <= 8; ++height) {
203+
ltc::ShareTracker t;
204+
const uint256 tip =
205+
build_uniform_chain(t, height, kDv, kBits, 100 + height);
206+
for (int32_t lookbehind = -2; lookbehind <= 12; ++lookbehind) {
207+
// verbatim pre-lift inline (see share_tracker.hpp:2155-2160):
208+
const int32_t inline_actual = std::min(lookbehind, height);
209+
const bool inline_runs = !(inline_actual <= 0);
210+
211+
EXPECT_EQ(realized_window(t, tip, lookbehind),
212+
inline_runs ? inline_actual : 0)
213+
<< "height=" << height << " lookbehind=" << lookbehind;
214+
EXPECT_EQ(walk_ran(t, tip, lookbehind), inline_runs)
215+
<< "height=" << height << " lookbehind=" << lookbehind;
216+
}
217+
}
218+
}

0 commit comments

Comments
 (0)