Skip to content

Commit bf5c4d3

Browse files
committed
dgb: M3 §7b ingest-gate scrypt(header) PoW-satisfaction check
CheckProofOfWork second half: the VALIDATE_SCRYPT path now rejects a header whose scrypt(header) digest exceeds its declared target (hash > target). Half 1 (target <= pow_limit) already landed; this closes the missing PoW-satisfaction guard that was a placeholder. HeaderSample gains a uint64 pow_hash proxy (mirrors ltc coin/header_chain hash field). Context-free check, run after the pow_limit ceiling and before the contextual nBits-equality gate, mirroring DigiByte Core if (UintToArith256(hash) > bnTarget) return false. pow_hash == 0 is the standalone default (trivially satisfies any target) so every existing vector is unchanged; the embedded-daemon port fills the real digest into the same field shape. +1 vector folded INTO header_chain_test (no new gtest target, #143 trap avoided): rejects pow_hash > target, accepts == and < boundary, and confirms a continuity header short-circuits before the PoW check. header_chain_test 20/20 PASS, build EXIT=0.
1 parent c6742b7 commit bf5c4d3

2 files changed

Lines changed: 63 additions & 7 deletions

File tree

src/impl/dgb/coin/header_chain.hpp

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,18 @@ using ::dgb::coin::u256;
6262
// Minimal header sample the Scrypt-only retarget body consumes. The embedded
6363
// DigiByte Core port (M3+) carries the full CBlockHeader; the validate() +
6464
// retarget path needs only the algo bits (disposition + work-credit), the
65-
// timestamp (timespan), and the expanded difficulty target (averaging).
66-
// `target` is a fixed-width proxy for the standalone CI guard; the per-algo
67-
// DigiShield slice swaps arith_uint256 in with the SAME field shape, so the
68-
// averaging/timespan assembly below is unchanged when the real width lands.
65+
// timestamp (timespan), the expanded difficulty target (averaging), and the
66+
// scrypt(header) PoW digest (`pow_hash`, the CheckProofOfWork hash <= target
67+
// check). `target` and `pow_hash` are fixed-width proxies for the standalone CI
68+
// guard; the per-algo DigiShield slice swaps arith_uint256 in with the SAME
69+
// field shape, so the averaging/timespan assembly below is unchanged when the
70+
// real width lands. pow_hash == 0 is the proxy default for "scrypt(header) not
71+
// evaluated here" (trivially satisfies any target); the daemon port fills it.
6972
struct HeaderSample {
7073
int32_t n_version = 0;
7174
int64_t n_time = 0;
7275
uint64_t target = 0; // expanded PoW target (smaller == more work)
76+
uint64_t pow_hash = 0; // scrypt(header) digest; hash <= target == valid PoW
7377
};
7478

7579
// Outcome of validating + ingesting one header.
@@ -195,9 +199,8 @@ class HeaderChain {
195199
case HeaderDisposition::VALIDATE_SCRYPT:
196200
{
197201
// PoW validate path. A zero target is not a validatable Scrypt
198-
// header (the embedded daemon's full scrypt(header) <= target
199-
// check lands with the daemon port; the structural guard here is
200-
// that a malformed target never credits work).
202+
// header (the structural guard here is that a malformed target
203+
// never credits work).
201204
if (h.target == 0)
202205
return IngestResult::REJECTED;
203206

@@ -213,6 +216,19 @@ class HeaderChain {
213216
if (m_ds_params.pow_limit != 0 && h.target > m_ds_params.pow_limit)
214217
return IngestResult::REJECTED;
215218

219+
// PoW satisfaction (DigiByte Core CheckProofOfWork second half):
220+
// the header's scrypt(header) digest must SATISFY its declared
221+
// target -- hash <= target. A header claiming work it does not meet
222+
// is consensus-invalid. This is the CONTEXT-FREE PoW check (mirrors
223+
// `if (UintToArith256(hash) > bnTarget) return false`), run before
224+
// the contextual nBits-equality gate below. pow_hash == 0 is the
225+
// standalone proxy for "scrypt(header) not evaluated here" and
226+
// trivially satisfies any target, so the chain-helper tests stay
227+
// unconstrained; the embedded-daemon port fills pow_hash with the
228+
// real Scrypt digest and this gate goes live with the SAME shape.
229+
if (h.pow_hash > h.target)
230+
return IngestResult::REJECTED;
231+
216232
// Consensus retarget gate: the declared target must equal the
217233
// DigiShield next-target computed over the Scrypt-only window
218234
// ending at the CURRENT tip (assembled BEFORE h is appended).

src/impl/dgb/test/header_chain_test.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,46 @@ TEST(HeaderChainValidate, IngestGateRejectsTargetAbovePowLimit)
272272
IngestResult::VALIDATED_SCRYPT);
273273
}
274274

275+
// Ingest-path PoW satisfaction (DigiByte Core CheckProofOfWork second half):
276+
// a Scrypt header whose scrypt(header) digest is NUMERICALLY GREATER than its
277+
// declared target does not meet the work it claims and is consensus-invalid --
278+
// rejected without mutating the chain, independent of the retarget/ceiling
279+
// gates. Mirrors `if (UintToArith256(hash) > bnTarget) return false`. The
280+
// pow_hash field defaults to 0 (every brace-init vector above), which trivially
281+
// satisfies any target, so all chain-helper tests run unchanged; here we set it
282+
// explicitly to drive the gate. A default-ctor chain leaves the retarget gate
283+
// and pow_limit ceiling unconfigured, so ONLY this PoW check can fire.
284+
TEST(HeaderChainValidate, IngestGateRejectsInsufficientScryptPoW)
285+
{
286+
HeaderChain hc; // gate + ceiling unconfigured: only the PoW check can fire
287+
288+
// pow_hash strictly ABOVE the declared target: the header does not satisfy
289+
// its own difficulty. REJECT, no work credited, chain not extended.
290+
HeaderSample weak{SCRYPT, 1000, 100};
291+
weak.pow_hash = 101;
292+
EXPECT_EQ(hc.validate_and_append(weak), IngestResult::REJECTED);
293+
EXPECT_EQ(hc.size(), 0u);
294+
EXPECT_EQ(hc.cumulative_work(), 0u);
295+
296+
// pow_hash == target is the boundary: hash <= target satisfies the work.
297+
HeaderSample exact{SCRYPT, 1000, 100};
298+
exact.pow_hash = 100;
299+
EXPECT_EQ(hc.validate_and_append(exact), IngestResult::VALIDATED_SCRYPT);
300+
301+
// pow_hash strictly below target also satisfies (more work than required).
302+
HeaderSample strong{SCRYPT, 1075, 100};
303+
strong.pow_hash = 1;
304+
EXPECT_EQ(hc.validate_and_append(strong), IngestResult::VALIDATED_SCRYPT);
305+
EXPECT_EQ(hc.size(), 2u);
306+
307+
// A continuity (non-Scrypt) header never reaches the PoW check: its
308+
// disposition short-circuits to ACCEPT_BY_CONTINUITY even with a huge
309+
// pow_hash, confirming the gate is Scrypt-path only.
310+
HeaderSample cont{SHA256D, 1090, 100};
311+
cont.pow_hash = UINT64_MAX;
312+
EXPECT_EQ(hc.validate_and_append(cont), IngestResult::ACCEPTED_CONTINUITY);
313+
}
314+
275315
// ---------------------------------------------------------------------------
276316
// 256-BIT BOUNDARY (M3 §7b — arith_uint256 swap). Proves, not assumes, that
277317
// the DigiShield damped multiply runs at TRUE 256-bit width: a uint64/__int128

0 commit comments

Comments
 (0)