Skip to content

Commit 60e22ef

Browse files
committed
dgb: M3 §7b wire DigiShield next-target into HeaderChain ingest gate
validate_and_append now enforces the DigiShield-computed next target on the VALIDATE_SCRYPT path: the declared Scrypt target must EQUAL the next-target computed over the Scrypt-only retarget window ending at the current tip (assembled before the header is appended), the nBits-style consensus rule. A configured HeaderChain(DigiShieldParams, window) arms the gate; a default- constructed chain leaves target_timespan 0 so digishield_next_target() returns the 0 sentinel and the gate is a no-op (existing retarget/work-accounting tests run unconstrained, unchanged). A rejected header never mutates the chain (size + cumulative work preserved). +1 ingest-gate test vector (window 1, deterministic 7/8 next-target): wrong target REJECTED with chain unchanged, exact target VALIDATED_SCRYPT crediting work, continuity header bypasses the gate. Added INTO header_chain_test (no new gtest target -> #143 NOT_BUILT trap avoided). 13/13 PASS, build EXIT=0.
1 parent 9a48bdf commit 60e22ef

2 files changed

Lines changed: 68 additions & 0 deletions

File tree

src/impl/dgb/coin/header_chain.hpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,17 @@ inline uint64_t digishield_next_target(const RetargetWindow& rw,
157157
// Append order is oldest..newest; the retarget walk reads nearest-first.
158158
class HeaderChain {
159159
public:
160+
HeaderChain() = default;
161+
162+
// Configure the consensus retarget gate: the DigiShield params + the
163+
// Scrypt-only window depth validate_and_append enforces declared targets
164+
// against. Default-constructed chains leave target_timespan == 0, which
165+
// makes digishield_next_target() return 0 and the gate a no-op -- the
166+
// retarget-window/work-accounting tests run unconstrained, exactly as
167+
// before this slice.
168+
HeaderChain(DigiShieldParams ds, std::size_t retarget_window)
169+
: m_ds_params(ds), m_retarget_window(retarget_window) {}
170+
160171
// Validate one header and, unless rejected, append it to the chain.
161172
// Work-neutrality SSOT: cumulative work advances iff header_credits_work()
162173
// — the SAME predicate scrypt_window_ancestors() consults for the retarget
@@ -171,15 +182,32 @@ class HeaderChain {
171182
return IngestResult::REJECTED;
172183

173184
case HeaderDisposition::VALIDATE_SCRYPT:
185+
{
174186
// PoW validate path. A zero target is not a validatable Scrypt
175187
// header (the embedded daemon's full scrypt(header) <= target
176188
// check lands with the daemon port; the structural guard here is
177189
// that a malformed target never credits work).
178190
if (h.target == 0)
179191
return IngestResult::REJECTED;
192+
193+
// Consensus retarget gate: the declared target must equal the
194+
// DigiShield next-target computed over the Scrypt-only window
195+
// ending at the CURRENT tip (assembled BEFORE h is appended).
196+
// expected == 0 means no Scrypt ancestor is in range yet
197+
// (genesis/bootstrap) or the gate is unconfigured -- no retarget
198+
// constraint is enforceable, so the header passes on its
199+
// structural (non-zero) target alone. nBits-style exact match
200+
// mirrors Bitcoin/DigiByte consensus: the header carries the
201+
// required next-work value, not merely a target it satisfies.
202+
const RetargetWindow rw = next_retarget_window(m_retarget_window);
203+
const uint64_t expected = digishield_next_target(rw, m_ds_params);
204+
if (expected != 0 && h.target != expected)
205+
return IngestResult::REJECTED;
206+
180207
m_chain.push_back(h);
181208
m_cumulative_work += work_from_target(h.target); // credited
182209
return IngestResult::VALIDATED_SCRYPT;
210+
}
183211

184212
case HeaderDisposition::ACCEPT_BY_CONTINUITY:
185213
default:
@@ -237,6 +265,8 @@ class HeaderChain {
237265
return target == 0 ? 0 : (UINT64_MAX / target);
238266
}
239267

268+
DigiShieldParams m_ds_params{}; // retarget gate params
269+
std::size_t m_retarget_window = 0; // Scrypt window depth
240270
std::vector<HeaderSample> m_chain; // oldest .. newest
241271
uint64_t m_cumulative_work = 0;
242272
};

src/impl/dgb/test/header_chain_test.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,41 @@ TEST(DigiShieldRetarget, ConsumesLiveScryptOnlyWindow)
199199
ASSERT_TRUE(rw.sufficient);
200200
EXPECT_EQ(digishield_next_target(rw, DigiShieldParams{225, 0}), 100u);
201201
}
202+
203+
// ---------------------------------------------------------------------------
204+
// Ingest-path retarget gate: validate_and_append must demand the declared
205+
// Scrypt target EQUAL the DigiShield next-target computed over the Scrypt-only
206+
// window ending at the current tip (nBits-style exact consensus match). A
207+
// default-constructed chain leaves the gate unconfigured (target_timespan 0 ->
208+
// expected 0), so every test above runs unconstrained; this one configures it.
209+
//
210+
// window depth 1, nominal 80. Window(1) over a lone Scrypt tip has
211+
// actual_timespan 0 (front == back) -> damped = 80 + (0-80)/8 = 70, above the
212+
// floor rail 60 -> next target = avg * 70/80 = avg * 7/8, deterministically.
213+
// ---------------------------------------------------------------------------
214+
TEST(HeaderChainValidate, IngestGateEnforcesDigiShieldNextTarget)
215+
{
216+
HeaderChain hc(DigiShieldParams{80, 0}, /*retarget_window=*/1);
217+
218+
// Seed: empty window -> gate no-op, any non-zero target seeds the chain.
219+
ASSERT_EQ(hc.validate_and_append({SCRYPT, 1000, 4096}),
220+
IngestResult::VALIDATED_SCRYPT);
221+
222+
// Required next target = 4096 * 7/8 = 3584. A mismatch is consensus-invalid
223+
// and must REJECT without mutating the chain (size + work unchanged).
224+
const std::size_t size_before = hc.size();
225+
const uint64_t work_before = hc.cumulative_work();
226+
EXPECT_EQ(hc.validate_and_append({SCRYPT, 1080, 4096}),
227+
IngestResult::REJECTED);
228+
EXPECT_EQ(hc.size(), size_before);
229+
EXPECT_EQ(hc.cumulative_work(), work_before);
230+
231+
// The exact DigiShield target is accepted and credits work.
232+
EXPECT_EQ(hc.validate_and_append({SCRYPT, 1080, 3584}),
233+
IngestResult::VALIDATED_SCRYPT);
234+
EXPECT_GT(hc.cumulative_work(), work_before);
235+
236+
// Continuity headers bypass the retarget gate (work-neutral, no nBits).
237+
EXPECT_EQ(hc.validate_and_append({SHA256D, 1090, 1}),
238+
IngestResult::ACCEPTED_CONTINUITY);
239+
}

0 commit comments

Comments
 (0)