Skip to content

ci: build test_dgb_subsidy in both Linux test jobs (fix master-red NOT_BUILT) - #143

Merged
frstrtr merged 1 commit into
masterfrom
dgb/ci-build-test-dgb-subsidy
Jun 17, 2026
Merged

ci: build test_dgb_subsidy in both Linux test jobs (fix master-red NOT_BUILT)#143
frstrtr merged 1 commit into
masterfrom
dgb/ci-build-test-dgb-subsidy

Conversation

@frstrtr

@frstrtr frstrtr commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Problem

master CI is red on the Linux x86_64 and Linux x86_64 (AsAN+UBSan) jobs. The Run tests step exits 8 with:

36 - test_dgb_subsidy_NOT_BUILT (Not Run)
99% tests passed, 1 tests failed out of 616

Confirmed on the #138 merge run (27717734612) too, so it is pre-existing on master, not introduced by any open DGB PR.

Root cause

test_dgb_subsidy is registered in test/CMakeLists.txt via gtest_discover_tests with DISCOVERY_MODE PRE_TEST, but it was never added to the explicit cmake --build --target allowlist in the build.yml "Build tests" (Linux) and "Build c2pool + tests" (AsAN) steps. The executable is therefore absent at ctest time, CTest registers the *_NOT_BUILT sentinel, and the step fails. This regression landed when #137 added the test without wiring it into the CI allowlist.

Fix

Add test_dgb_subsidy to both target lists (2-line diff). The target builds clean and both subsidy oracle tests pass locally:

[  PASSED  ] 2 tests. (DgbSubsidy.MatchesOracleVectors, DgbSubsidy.NeverBelowCoinFloor)

DGB-domain (completes the #137 landing). GPG-signed. HOLD merge -- integrator merges on operator tap.

…T_BUILT)

test_dgb_subsidy is registered in test/CMakeLists.txt via
gtest_discover_tests but was never added to the explicit --target
allowlist in the build.yml Build tests / Build c2pool+tests steps, so
the executable is absent at ctest time and CTest registers the
test_dgb_subsidy_NOT_BUILT sentinel -> Run tests fails (exit 8) on both
the Linux x86_64 and Linux x86_64 (AsAN+UBSan) jobs. This has left
master CI red since #137 landed the test. Add the target to both
allowlists. Target builds clean and the 2 subsidy oracle tests pass.
@frstrtr
frstrtr merged commit bb7ef22 into master Jun 17, 2026
16 checks passed
frstrtr added a commit that referenced this pull request Jun 18, 2026
Register the three standalone M3 regression guards
(rpc_request_test / softfork_check_test / genesis_check_test) as ctest
targets in src/impl/dgb/test/CMakeLists.txt and add them to BOTH
build.yml --target allowlists. Header-only: each links only GTest +
nlohmann_json (no OBJECT lib), so they stay buildable while the
external-daemon transport rpc.cpp wiring remains deferred (Option-B).

Avoids the #143 NOT_BUILT trap: a gtest target present in CMake but
absent from the build.yml allowlist becomes a NOT_BUILT sentinel that
reds master. Both lists, always.

23/23 guard assertions pass (rpc-request 8, softfork 8, genesis 7).
frstrtr added a commit that referenced this pull request Jun 18, 2026
Replace the header_chain.hpp namespace stub with the validate() body that
drives the Scrypt-only chain: per-header disposition (Scrypt validate /
accept-by-continuity / reject), work-neutral accounting, and the Scrypt-only
DigiShield retarget-window assembly (averaged target + actual timespan).

Work-neutrality SSOT: cumulative work advances iff header_credits_work(), the
same predicate scrypt_window_ancestors() consults for the retarget window, so
the work-accounting and retarget paths cannot drift. Continuity headers extend
the chain but credit zero work and are excluded from the retarget window.

THIRD INVARIANT under test: a continuity header inside the nominal window is
proven skipped from both avg_target and actual_timespan (a naive all-headers
window would corrupt both). The damped DigiShield/MultiShield arith_uint256
multiply layers on RetargetWindow in the following slice.

Guard header_chain_test wired into the ctest foreach AND both build.yml
--target allowlists (the #143 NOT_BUILT lesson). 6/6 PASS, build EXIT=0.
frstrtr added a commit that referenced this pull request Jun 18, 2026
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.
frstrtr added a commit that referenced this pull request Jun 18, 2026
Isolates the consensus quirk flagged at header_chain.hpp DIGISHIELD INSERTION
POINT: the Scrypt difficulty window must walk Scrypt-algo ancestors ONLY and
skip continuity (non-Scrypt) headers; folding a continuity header into the
window corrupts the Scrypt retarget and breaks the THIRD INVARIANT work-
neutrality. Adds header_credits_work() as the single SSOT predicate the
validate() work-accounting and the retarget walk both consult, so they cannot
drift. Header-only (deps: dgb_block_algo.hpp + std), so it links into the
standalone GTest guard with no dgb OBJECT lib.

- coin/dgb_digishield.hpp: scrypt_window_ancestors(), header_credits_work()
- test/digishield_walk_test.cpp: 6 guards (zero-bits Scrypt, interleave skip,
  leading-continuity run, exhausted chain, zero window, work predicate)
- wired into BOTH ctest foreach AND both build.yml --target allowlists (#143)

Per-algo DigiShield target math layers on top in the following slice.
frstrtr added a commit that referenced this pull request Jun 18, 2026
Replace the header_chain.hpp namespace stub with the validate() body that
drives the Scrypt-only chain: per-header disposition (Scrypt validate /
accept-by-continuity / reject), work-neutral accounting, and the Scrypt-only
DigiShield retarget-window assembly (averaged target + actual timespan).

Work-neutrality SSOT: cumulative work advances iff header_credits_work(), the
same predicate scrypt_window_ancestors() consults for the retarget window, so
the work-accounting and retarget paths cannot drift. Continuity headers extend
the chain but credit zero work and are excluded from the retarget window.

THIRD INVARIANT under test: a continuity header inside the nominal window is
proven skipped from both avg_target and actual_timespan (a naive all-headers
window would corrupt both). The damped DigiShield/MultiShield arith_uint256
multiply layers on RetargetWindow in the following slice.

Guard header_chain_test wired into the ctest foreach AND both build.yml
--target allowlists (the #143 NOT_BUILT lesson). 6/6 PASS, build EXIT=0.
frstrtr added a commit that referenced this pull request Jun 18, 2026
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.
frstrtr added a commit that referenced this pull request Jun 18, 2026
VALIDATE_SCRYPT path now rejects a declared Scrypt target EASIER than
pow_limit (target > pow_limit), mirroring DigiByte Core CheckProofOfWork.
This fires on the bootstrap/empty-window path (expected == 0) where the
nBits-style retarget-equality gate is a no-op, closing the hole where a
sub-minimum-difficulty header could seed/extend the chain during a stall.
pow_limit == 0 keeps the gate unconfigured (legacy default-ctor chains
unconstrained). +1 vector folded into header_chain_test (no new gtest
target; #143 NOT_BUILT trap avoided). 19/19 PASS, build EXIT=0.
frstrtr added a commit that referenced this pull request Jun 18, 2026
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.
frstrtr added a commit that referenced this pull request Jun 18, 2026
…7d) (#153)

* dgb: M3 §7b DigiShield Scrypt-only ancestor walk + work-credit predicate

Isolates the consensus quirk flagged at header_chain.hpp DIGISHIELD INSERTION
POINT: the Scrypt difficulty window must walk Scrypt-algo ancestors ONLY and
skip continuity (non-Scrypt) headers; folding a continuity header into the
window corrupts the Scrypt retarget and breaks the THIRD INVARIANT work-
neutrality. Adds header_credits_work() as the single SSOT predicate the
validate() work-accounting and the retarget walk both consult, so they cannot
drift. Header-only (deps: dgb_block_algo.hpp + std), so it links into the
standalone GTest guard with no dgb OBJECT lib.

- coin/dgb_digishield.hpp: scrypt_window_ancestors(), header_credits_work()
- test/digishield_walk_test.cpp: 6 guards (zero-bits Scrypt, interleave skip,
  leading-continuity run, exhausted chain, zero window, work predicate)
- wired into BOTH ctest foreach AND both build.yml --target allowlists (#143)

Per-algo DigiShield target math layers on top in the following slice.

* dgb: M3 §7b HeaderChain Scrypt-only validate() + retarget-window body

Replace the header_chain.hpp namespace stub with the validate() body that
drives the Scrypt-only chain: per-header disposition (Scrypt validate /
accept-by-continuity / reject), work-neutral accounting, and the Scrypt-only
DigiShield retarget-window assembly (averaged target + actual timespan).

Work-neutrality SSOT: cumulative work advances iff header_credits_work(), the
same predicate scrypt_window_ancestors() consults for the retarget window, so
the work-accounting and retarget paths cannot drift. Continuity headers extend
the chain but credit zero work and are excluded from the retarget window.

THIRD INVARIANT under test: a continuity header inside the nominal window is
proven skipped from both avg_target and actual_timespan (a naive all-headers
window would corrupt both). The damped DigiShield/MultiShield arith_uint256
multiply layers on RetargetWindow in the following slice.

Guard header_chain_test wired into the ctest foreach AND both build.yml
--target allowlists (the #143 NOT_BUILT lesson). 6/6 PASS, build EXIT=0.

* dgb: M3 §7b per-algo DigiShield damped retarget multiply

digishield_next_target() + DigiShieldParams in coin/header_chain.hpp:
consumes the Scrypt-only RetargetWindow (avg_target + actual_timespan over
Scrypt ancestors only) and produces the next expanded target via DigiByte
DigiShield v3: amplitude filter damped = nominal + (actual-nominal)/8, clamp
to [3/4, 3/2] nominal rails, bnNew = avg*damped/nominal, capped to pow_limit.
__int128 intermediate (proxy-overflow safe); arith_uint256 swaps in later with
the same field shape. Returns 0 on empty window / degenerate nominal so the
caller keeps the prior target.

header_chain_test: +6 DigiShield cases -- nominal (target unchanged), both
clamp rails (ceiling 3/2, floor 3/4 via negative timespan), pow_limit floor,
empty-window/zero-nominal safety, and end-to-end over the live continuity-
skipped window. 12/12 pass, build EXIT=0. Header-only; reuses the already-
wired header_chain_test guard (no new ctest/build.yml target).

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

* dgb: M3 §7b DigiShield damped multiply at true 256-bit width

Swap the uint64/__int128 retarget intermediate for a header-only 256-bit
unsigned (coin/dgb_arith256.hpp) reproducing arith_uint256 multiply-then-
divide with 256-bit overflow truncation -- the consensus arithmetic of
DigiByte Core CalculateNextWorkRequired. For uint64-range avg_target the
path is bit-identical to the prior intermediate, so the existing 13
vectors are unchanged; +5 boundary vectors prove the divergence above
2^64 and the 256-bit truncation near pow_limit that an __int128 proxy
could not reproduce. Header-only, single-coin (src/impl/dgb only), tests
added into header_chain_test (no new gtest target).

* dgb: M3 §7b ingest-gate pow_limit minimum-difficulty ceiling

VALIDATE_SCRYPT path now rejects a declared Scrypt target EASIER than
pow_limit (target > pow_limit), mirroring DigiByte Core CheckProofOfWork.
This fires on the bootstrap/empty-window path (expected == 0) where the
nBits-style retarget-equality gate is a no-op, closing the hole where a
sub-minimum-difficulty header could seed/extend the chain during a stall.
pow_limit == 0 keeps the gate unconfigured (legacy default-ctor chains
unconstrained). +1 vector folded into header_chain_test (no new gtest
target; #143 NOT_BUILT trap avoided). 19/19 PASS, build EXIT=0.

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

* dgb: M3 §7b HeaderSample/retarget field-shape swap to arith_uint256 (u256)

Swap HeaderSample::target/pow_hash, RetargetWindow::avg_target,
DigiShieldParams::pow_limit and the digishield_next_target() return from
uint64 proxies to the full-width u256 (coin/dgb_arith256.hpp). An implicit
uint64 widening ctor keeps every uint64-range vector byte-identical, so the
ingest PoW-satisfaction + minimum-difficulty ceiling checks and the
DigiShield averaging now run at true arith_uint256 width through
validate_and_append. Adds u256 operator+= for the window target-sum average.

+1 ingest-path boundary vector folded into header_chain_test (no new gtest
target): a >2^64 target/digest decides acceptance the same way arith_uint256
would and diverges from a low64-only proxy in both directions. 21/21 PASS.

Confined to src/impl/dgb/; no shared base / bitcoin_family / CMake / build.yml
touch.

---------

Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
frstrtr added a commit that referenced this pull request Jun 18, 2026
Add the daemon-independent, Scrypt-only-walk-safe half of header time
validation: a Scrypt header's nTime must be STRICTLY GREATER than the median
timestamp of the tip and its (up to) 10 nearest ancestors (DigiByte Core
ContextualCheckBlockHeader "time-too-old"). This is the monotonicity floor
the demoted nBits re-derivation gate used to imply, recovered without any
difficulty recompute -- it reads only timestamps already in the local header
chain, so it is safe on a Scrypt-only walk and needs no V4-MultiShield
multi-algo window.

median_time_past() walks ALL appended headers regardless of algo, mirroring
DGB Core's GetMedianTimePast over the block index: continuity (non-Scrypt)
headers DO contribute their timestamps to the median even though their PoW is
never validated and they are never rejected on the gate (accept-by-continuity
is preserved). Genesis (empty chain) returns INT64_MIN -> unconstrained. A
rejection never mutates the chain (checked before push_back).

+4 tests INTO header_chain_test (no new gtest target -> no build.yml
allowlist change, #143 trap avoided): genesis-unconstrained, strict-greater
rejection at/below median, continuity timestamps feed the median, continuity
header itself bypasses the gate. header_chain_test 25/25 PASS, build EXIT=0.
frstrtr added a commit that referenced this pull request Jun 18, 2026
Pin the embedded-primary / external-digibyted-RPC-fallback contract of
dgb::coin::CoinNode (core::coin::ICoinNode) that the won-block dispatch
relies on: web_server submits a solved block via
m_coin_node->submit_block_hex(), which forwards to NodeRPC::submitblock
(the external-daemon fallback that MUST persist alongside embedded).

Three invariants, mirroring impl/bch/test/coin_node_seam_test.cpp:
  1. no source        -> empty WorkView, no throw
  2. embedded, no rpc -> WorkView sourced from embedded
  3. no rpc sink      -> submit_block_hex == false (!m_rpc guard, no crash)

The live submitblock call + m_on_block_found tracker callback are wired
by the DGB run-loop standup (main_dgb.cpp is selftest-only today); that
is the remaining #82 slice and is out of scope here (rpc=nullptr).

Registered in test/CMakeLists.txt AND both build.yml --target allowlists
(avoids the #143 NOT_BUILT-sentinel trap). 3/3 PASS, build EXIT=0.
frstrtr added a commit that referenced this pull request Jun 18, 2026
Add coin/block_broadcast.hpp -- broadcast_won_block(), the DGB dual-path
won-block dispatcher the run-loop wires tracker().m_on_block_found to. Fires
BOTH sinks per the broadcaster-gate rule: embedded P2P relay (PRIMARY, supplied
as a sink fn until NodeP2P/EmbeddedDaemon lands) AND external digibyted
submitblock via the CoinNode seam (FALLBACK, fired ALWAYS, ignore_failure=true
so a duplicate after a P2P accept never masks the win). landed_first records the
race winner; with neither sink live the call is a safe no-op (any()=false, no
throw, null seam never dereferenced). 1:1 contract mirror of bch
broadcast_won_block (90a3553); decoupled via std::function P2P sink + ICoinNode*
so it is build-verified now. DGB-Scrypt standalone parent -- no merged-coinbase
leg in the default build. Complements the #163 RPC-fallback seam half.

dgb_block_broadcast_test: 5-case dispatch contract (no-sink no-op, p2p-only,
rpc-only, dual-path always-fires-fallback, fallback-no-ack-does-not-mask-p2p)
over a fake ICoinNode -- 5/5 PASS, build EXIT=0. Registered in test/CMakeLists.txt
AND both build.yml --target allowlists (#143 NOT_BUILT trap avoided).

p2pool-merged-v36 surface: NONE (block dispatch only). Per-coin isolation:
src/impl/dgb/coin/ only.
frstrtr added a commit that referenced this pull request Jun 18, 2026
make_on_block_found turns a winning share hash into a broadcast: reconstruct
share->block via an injected WonBlockReconstructor, then fire broadcast_won_block
down both paths (P2P primary + external digibyted submitblock fallback). The
reconstructor is injected so the run-loop dispatch contract is build-verified and
run-tested now, before the faithful as_block reassembly is ported.

won_block_dispatch_test drives the handler end-to-end: known share fires both
paths, unknown/unassemblable share broadcasts nothing, RPC-only still reaches the
network, missing reconstructor is a safe no-op. 4/4 PASS. Registered in
test/CMakeLists.txt and both build.yml --target allowlists (avoids the #143
NOT_BUILT trap). src/impl/dgb/ only; no p2pool-merged-v36 surface.
frstrtr added a commit that referenced this pull request Jun 18, 2026
…-trip KAT

Factor the p2pool non-witness coinbase (gentx) wire layout out of
generate_share_transaction() (share_check.hpp:933, the verification SSOT)
into coin/gentx_coinbase.hpp::assemble_gentx_coinbase() so emission and
verification cannot diverge on a byte. Pure: takes already-built
scripts/amounts (no tracker/share-template dependency) so it is directly
KAT-able; this is the single builder that create_local_share() and the
won-block reconstruction body will consume in follow-up slices.

KAT vectors are GROUND-TRUTH from the canonical oracle
frstrtr/p2pool-dgb-scrypt (util/pack.py byte logic + bitcoin/data.py
tx_id_type; donation script 4104ffd0...ac), not self-generated, so a PASS
proves build -> non-witness serialize -> double-SHA256 == oracle bytes/txid,
not mere self-consistency. Covers the no-segwit and witness-commitment-first
layouts; the only DGB<->BCH divergence is the gated segwit predicate at
serialize time. dgb_gentx_coinbase_test 3/3 PASS, build EXIT=0; target wired
into ctest + BOTH build.yml --target allowlists (#143 NOT_BUILT trap avoided).

Scope: factor + KAT only. The won-block-reaches-network wiring (m_on_block_found
and submit_block_hex) is fix #82 and stays separate.
frstrtr added a commit that referenced this pull request Jun 18, 2026
…its SSOT framing

Round-trip equivalence KAT (op_return ref_hash is share-derived, cannot pin a
fixed oracle vector): generate_share_transaction(share) txid == verbatim
re-derivation through the SAME assemble_gentx_coinbase SSOT. Proves the #172
collapse behaviorally, not structurally. 3/3 PASS, txid 9e2b78d5. Wired into
ctest + BOTH build.yml allowlists in this commit (#143 NOT_BUILT trap).
frstrtr added a commit that referenced this pull request Jun 18, 2026
…-trip KAT (#171)

Factor the p2pool non-witness coinbase (gentx) wire layout out of
generate_share_transaction() (share_check.hpp:933, the verification SSOT)
into coin/gentx_coinbase.hpp::assemble_gentx_coinbase() so emission and
verification cannot diverge on a byte. Pure: takes already-built
scripts/amounts (no tracker/share-template dependency) so it is directly
KAT-able; this is the single builder that create_local_share() and the
won-block reconstruction body will consume in follow-up slices.

KAT vectors are GROUND-TRUTH from the canonical oracle
frstrtr/p2pool-dgb-scrypt (util/pack.py byte logic + bitcoin/data.py
tx_id_type; donation script 4104ffd0...ac), not self-generated, so a PASS
proves build -> non-witness serialize -> double-SHA256 == oracle bytes/txid,
not mere self-consistency. Covers the no-segwit and witness-commitment-first
layouts; the only DGB<->BCH divergence is the gated segwit predicate at
serialize time. dgb_gentx_coinbase_test 3/3 PASS, build EXIT=0; target wired
into ctest + BOTH build.yml --target allowlists (#143 NOT_BUILT trap avoided).

Scope: factor + KAT only. The won-block-reaches-network wiring (m_on_block_found
and submit_block_hex) is fix #82 and stays separate.

Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
frstrtr added a commit that referenced this pull request Jun 23, 2026
…lar KAT

Lift the open-coded think() Phase-5 best-share resolution from
share_tracker.hpp into a header-only SSOT (think_p5_best_share_punish.hpp):
walk back off a naughty decorated head, dive to the deepest non-naughty
descendant (skipping naughty children, 20-gen bound), and report the
punishment of the share finally landed on, per p2pool data.py:2142-2166.

FENCED/additive: dgb tree only, share_tracker.hpp NOT yet rewired (the
byte-identity delegation is the follow-on). Pure graph traversal, consensus
value semantics unchanged. KAT 6/6 with hand-derived non-circular fake-chain
anchors; registered in CMake and both build.yml --target allowlist sites
(#143 NOT_BUILT guard).
frstrtr added a commit that referenced this pull request Jun 23, 2026
…rcular KAT

Lift the inline want/can/get walk-bound arithmetic from share_tracker.hpp
think() Phase 2 into a header-only SSOT (think_p2_walk_bounds.hpp), sibling
to the Phase-1 think_p1_walk_bounds.hpp landed in #353. Pins the three
integer decisions per verified head against the p2pool data.py:2098-2103
oracle:
  want = max(CHAIN_LENGTH - head_height, 0)
  can  = last_height                          if unrooted
       = max(last_height - 1 - CHAIN_LENGTH, 0) otherwise
  get  = min(want, can)

FENCED, additive: dgb/ tree-local only, header-only, share_tracker.hpp is
NOT yet rewired (byte-identity delegation is the follow-on). Consensus-
neutral pure arithmetic. KAT (4/4) hand-derives expectations from the
oracle formula, not the subject. Registered in both the dir CMakeLists.txt
and both build.yml --target allowlists (avoids #143 NOT_BUILT sentinel).
frstrtr added a commit that referenced this pull request Jun 23, 2026
The missing composition between leg-2's faithful won-block reconstruction
and a node-B ProcessNewBlock ACCEPT. finalize_won_block_pow takes a
reconstructed block (header[0..79] with merkle_root already set -- merkle
FIRST, integrator-pinned ordering) and grinds the nonce at [76..79] THROUGH
grind_won_nonce -> scrypt_pow_hash (the #286 digest CALL SSOT) until the
DGB-Scrypt PoW satisfies the parent target, splices the winning nonce back,
and regenerates the RPC-fallback hex in lockstep with the P2P-arm bytes.
This closes the "high-hash, proof of work failed" rejection: the forced-won
seam reached node B but carried no real PoW.

SSOT call-through, never a bypass hash -- a finalized block is by
construction one node B's own Scrypt validation accepts. Fail-closed
(nullopt) on sub-80-byte input or no-nonce-in-budget, mirroring
make_reconstruct_closure's never-a-partial-block posture.

Standalone-guard discipline: depends ONLY on nonce_grinder.hpp (real
btclibs scrypt + header-only u256); no core, no dgb OBJECT lib, no
util/HexStr (local lowercase encoder, byte-identical to HexStr). Test links
the SAME _dgb_scrypt_tus set as dgb_nonce_grinder_test.

+5 KATs (dgb_won_block_finalize_test, 5/5 green): finalized header
pow<=target via the same SSOT; winning nonce spliced LE into [76..79]; hex
== bytes; ONLY the 4 nonce bytes mutate (merkle + tx tail untouched);
fail-closed on a 79-byte input. Registered in BOTH build.yml --target
allowlists (#143 trap).

Fenced: src/impl/dgb/** + build.yml allowlist only. p2pool-merged-v36
surface: NONE. DGB-Scrypt standalone parent in the V36 default build.
frstrtr added a commit that referenced this pull request Jun 23, 2026
Add coin/submit_classify.hpp: the single place mining_submit places a
submission Scrypt pow_hash into one of three outcome classes. Tighten-first
ladder (block target checked before share target, so a won block -- a superset
of an accepted share -- wins the classification):

  pow_hash <= block_target  -> WonBlock     (submit_block_fn_ broadcast)
  pow_hash <= share_target  -> ShareAccept  (try_mint_share sharechain mint)
  otherwise                 -> Reject

Inclusive at BOTH boundaries via the same pow<=target == !(pow>target) gate the
header_chain satisfaction check and the nonce grinder run, matching DigiByte
Core CheckProofOfWork and p2pool-merged-v36 share-accept -- keeps c2pool-dgb
bit-compatible with the daemon and the Python reference.

Header-only, depends only on dgb_arith256.hpp (u256): no scrypt, no core, no
dgb OBJECT lib -- a pure decision over three integers. 7/7 KAT pins the ladder,
both inclusive boundaries, the zero-hash superset case, and a safety invariant
that an inverted (malformed) target pair can only mis-Reject, never spurious
WonBlock. Registered in both build.yml --target allowlists (#143 trap).

mining_submit still returns the stage-4d low-difficulty stub -- no behavior
change. The header-reconstruct -> scrypt_pow_hash -> classify -> dispatch wiring
that makes this the live-invocation point is the following slice.

Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
frstrtr added a commit that referenced this pull request Jun 23, 2026
…e won-block reconstructor other_txs

The won-block reconstructor (make_reconstruct_closure_from_template) frames the
broadcast block as [gentx] ++ other_txs, where other_txs is the captured GBT
template's non-coinbase set. make_mempool_tx_source already shapes that template
(transactions[] of {data,txid,hash,fee}); the reconstructor consumes deserialized
MutableTransaction. This adds the missing deserialize wire between them:

  * deserialize_template_other_txs(json) -- pure transactions[] -> txs[] SSOT;
    throws on malformed data (bad hex/trailing bytes) so the closure fails the
    won block CLOSED rather than broadcasting a half-decoded tx set.
  * make_template_other_txs_fn(captured_template_txs_fn) -- adapts a per-share
    captured-transactions[] provider into the template_other_txs_fn the run-loop
    installs (per-job capture provider is the run-loop integration, #271).

dgb_template_other_txs_test (5/5): round-trips the PRODUCTION make_mempool_tx_source
output byte-faithfully, proves the funded template txs land in the reconstructed
block ([gentx]++other_txs byte-identical to reconstruct_won_block_from_template),
and proves a bad provider fails closed. Fenced to src/impl/dgb/; no p2pool-merged-v36
surface (decodes the conformant GBT shape back into the in-memory tx). Both
build.yml allowlists updated (#143 NOT_BUILT trap).
This was referenced Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant