Skip to content

dash(share-producer): prospective share_info assembly, ref_hash/hash_link producers, retarget + KATs (mint campaign 1/3)#734

Merged
frstrtr merged 6 commits into
masterfrom
feat/dash-share-producer
Jul 18, 2026
Merged

dash(share-producer): prospective share_info assembly, ref_hash/hash_link producers, retarget + KATs (mint campaign 1/3)#734
frstrtr merged 6 commits into
masterfrom
feat/dash-share-producer

Conversation

@frstrtr

@frstrtr frstrtr commented Jul 17, 2026

Copy link
Copy Markdown
Owner

⚠️ Scope correction (2026-07-18) — read before labeling/tapping

This PR is NOT header-only / SAFE-ADDITIVE. Actual diff: +2932/-175 across 16 files, two payloads:

  1. DASH mint-slice (primary, additive + KAT-gated): src/impl/dash/{share,share_check,share_producer}.hpp + impl/dash/stratum/* + 3 KATs. Wires nothing into --run (that is slices 2-3).
  2. Bundled dash: stratum serves no work — DASHWorkSource 4c/4d skeleton (v0.2.2 field report); [LTC] mistag in core #732 coin-tag fix (cross-core, mechanical, wire-neutral): adds coin_symbol to src/core/stratum_types.hpp, uses it in src/core/stratum_server.cpp (replaces hardcoded "[LTC]" with runtime tag, neutral "[Stratum]" fallback), and sets it per-coin in main_ltc.cpp + impl/btc + impl/dgb + impl/dash work sources. Zero wire-byte change; all six coin smokes green.

The #732 fix was folded in to give the DASH stratum path correct self-tagging; it is intentional, not a shared-base ride-along. It crosses src/core + 3 coin trees, so per-coin-isolation review applies — it clears only because it is uniform/mechanical/wire-neutral. If the operator prefers a clean split, I will extract #732 to its own PR and rebase this one; otherwise it ships as reviewed.


Scope — mint campaign slice 1/3 (producer-side share construction + oracle goldens)

c2pool-dash's stratum serves work (#733) but cannot MINT shares onto the live pre-v36 p2pool-dash sharechain — no producer-side share machinery existed in src/impl/dash/. This slice builds it, KAT-gated, wiring NOTHING into --run or stratum (that is slices 2-3: node legs + DASHWorkSource::set_mint_share_fn binding).

New: src/impl/dash/share_producer.hpp (dash::producer, header-only, mirrors the DGB mint-campaign layering — run_loop_mint.hpp / work_ref_hash.hpp pure-core + thin chain wrappers):

  1. Prospective share_info assembly at job timegenerate_prospective_share_info(chain, params, ProducerJobInputs): prev hash / share nonce / subsidy / donation / packed_payments carry-through, assemble_tx_refs (tx forwarding over the past min(height,100) shares, first-seen dedup, [share_count, tx_count] pairs — data.py:147-170), compute_far_share_hash (99-ancestor walk, data.py:235), oracle retarget compute_share_target[_pure] (data.py:135-145: APS on the min_work basis → 2^256//(SP·aps)-1 → ±10% clamp on prev max_target[0, MAX_TARGET] → truncating compact), timestamp clip (prev+1 .. prev+2·SP-1), absheight/abswork accumulation mod 2^32/2^128.
  2. ref_hash producerserialize_share_info (exact v16 ref preimage layout) + compute_ref_hash (identifier ‖ share_info → sha256d → ref merkle link). Byte-equal to the verifier recompute in share_check.hpp (golden-pinned).
  3. hash_link midstate producerprefix_to_hash_link (data.py:34-37): SHA256 midstate over the gentx prefix (packed_gentx[:-payload_varstr-32-8-4]), inverse of check_hash_link, with the oracle's endswith(const_ending) assertion.
  4. DashShare build + MANDATORY self-verifybuild_share: oracle cumulative-weights window → full gentx byte assembly (build_gentx, oracle dash tx_type: int16 version+type, coinbase input, worker‖payments‖donation‖OP_RETURN(ref_hash‖nonce64) outputs, VarStr extra_payload) → hash_link → merkle_link (calculate_merkle_link_index0, drift-fenced against coinbase::merkle_branches_raw) → non-optional self-check: (a) verifier check_hash_link fold must reproduce the produced gentx txid, (b) full share_init_verify pass; any mismatch throws before a share could ever reach the wire.
  5. KATstest/test_dash_share_producer.cpp (25 tests in the target, all green), compiled into the EXISTING allowlisted test_dash_share_hash_link target (CI build.yml allowlist untouched — token workflow-scope pattern):
    • (a) producer ref stream/ref_hash goldens (fixture F1, 179-byte preimage pinned hex) + built shares pass share_init_verify/verify_share;
    • (b) wire round-trip byte-identical (DashFormatter + RawShare + load_share), outer-payload framing pinned;
    • (c) retarget KATs at 5 chain positions (genesis, short, deep-uniform→MAX clamp, fast-pool→9/10 clamp 0x1c1ccccb, slow-pool→11/10 clamp 0x1c233332; truncating-compact discriminators);
    • (d) absheight/abswork accumulation incl. wrap at 2^32/2^128;
    • (e) hash_link producer→checker round trip swept across SHA256 block boundaries (131 prefix lengths).

One consensus-bearing verifier fix (review closely)

share_check.hpp::share_init_verify outer coinbase_payload framing. Oracle (data.py:277-289): the outer contents['coinbase_payload'] VALUE is pack.VarStrType().pack(raw_payload) — i.e. the field value already carries ONE compactsize prefix — and Share.__init__ (data.py:346-348) appends that value VERBATIM to the check_hash_link data. On the wire the PossiblyNone(b'', VarStr) layer adds one MORE prefix, so DashFormatter's single strip leaves m_data == [compactsize][raw] for a live oracle share. #412 correctly put the compactsize into the hash_link data, but implemented it by re-VarStr-ing m_data — a SECOND prefix on top of the one m_data already carries, diverging gentx_hash from the oracle on every real DIP4 CbTx share (i.e. every modern mainnet-template share, in BOTH directions: accepting live shares and having ours accepted). Fixed to a verbatim append; empty-branch (b"", no 0x00) preserved; test_dash_share_hash_link.cpp test 2 updated to discriminate all three framings (verbatim/double-prefix/bare-raw). The producer fills m_data = VarStr(raw) accordingly and the F2 KAT pins the wire tail 00 05 04 de ad be ef (double prefix on wire).

Oracle-tracing method

Goldens were derived by hand-tracing github.com/frstrtr/p2pool-dash master (p2pool/data.py, p2pool/dash/data.py, p2pool/util/pack.py, p2pool/networks/dash.py) with exact big-integer arithmetic (CPython ints/hashlib for digests) — the oracle PROCESS was not run. Byte layouts were hand-assembled from the pack-type definitions and pinned as hex; retarget/weights constants are exact integer traces of the formulas.

PROVISIONAL goldens (need oracle-replay confirmation)

  1. Outer coinbase_payload double-VarStr wire framing (the fix above + F2 wire-tail pin). Highest-value replay item: capture one live CbTx share from a p2pool-dash peer and confirm m_data post-strip is [compactsize][raw]. The in-repo tests exercise the full serialize→parse→verify pipeline but the wire layout itself is a source trace.
  2. far_share_hash at exactly height == 99 → None/null (DistanceSkipList boundary hand-trace; <99 → None and ≥100 → nth(prev,99) are unambiguous).
  3. Retarget compacts 0x1c1ccccb / 0x1c00f5c2 / 0x1c233332 / 0x1c088880 (exact traces of FloatingInteger.from_target_upper_bound + math.clip; mantissa-truncation matches the in-tree target_to_bits_upper_bound).
  4. Partial-inclusion weight constants (0x733373330000, 0xccc8ccc8000) — exact trace of WeightsSkipList.apply_delta division order.
  5. F1/F2 ref-stream/ref_hash/gentx/txid digests (CPython hashlib over hand-assembled layouts; non-circular vs the C++ but not process-replayed).

Ban-risk hotspots (hardest-to-pin semantics — review focus)

  • Outer-payload framing (above) — consensus-bearing on every real share.
  • PPLNS weights window START: oracle starts at previous_share.previous_share_hash (the GRANDPARENT, data.py:181) with max(0, min(height, RCL)-1) shares; the in-tree generate_share_transaction walks from prev itself — off by one vs the oracle (invisible on homogeneous chains, wrong payouts on heterogeneous ones). Producer implements the oracle window (KAT OracleWindowStartsAtGrandparent). Traced consumers: generate_share_transaction currently has ZERO dash callers (the accept path is share_init_verify + the version-transition gate), so nothing in-tree rejects the producer's shares over this — but that reconstructor should be conformed to the oracle (window start, unconditional-vs-emitted payment_total, donation-entry merge) before it is ever wired into an accept path. Follow-up flagged, deliberately NOT bundled into this slice.
  • Self-verify scope: build_share's mandatory self-check proves serialization self-consistency (the committed hash_link/ref_hash/merkle folds reproduce exactly what a receiving verifier recomputes from the share bytes); it structurally cannot prove amount-level oracle parity, since no in-tree accept-path component reconstructs the coinbase from chain state. Amount parity is carried by the KAT layer + the oracle replay of the PROVISIONAL list. Stated explicitly in the header.
  • Retarget source: ShareTracker::compute_share_target carries a v36 emergency time-decay ABSENT from the p2pool-dash oracle; using it to mint would fork us off under slow-pool conditions. The mint path must use dash::producer::compute_share_target (slices 2-3 must bind stratum share_bits to it too).
  • worker_payout rule: oracle subtracts only payments actually EMITTED as outputs (valid payee, amount>0, data.py:191-218); the verifier subtracts all packed_payments unconditionally. Producer implements the oracle rule; divergence only bites on invalid/empty payees in GBT.
  • tx-refs semantics: 1+i share_count offset and first-seen dedup across the 100-share walk (share_count < 110 receive-side assert bounds it).

Runtime wiring

Nothing is wired into --run, stratum, or the node legs. c2pool-dash --selftest passes; all 57 dash test binaries pass locally (Release, -j4); the change set is dash-fenced (no shared/core files touched).

Base: branched from fix/dash-stratum-work-serving because #733 is not yet merged; the PR is opened against that branch so the diff shows only slice-1 commits — please retarget to master after #733 lands.

frstrtr added 6 commits July 18, 2026 00:07
…wait tag

The coin-agnostic core hardcoded "[LTC]" in the send_notify_work
no-template warning, so a DASH binary logged LTC and sent the operator
diagnosing the wrong coin (#732 secondary defect). StratumConfig now
carries a runtime coin_symbol -- LTC sets it in main_ltc.cpp, BTC/DGB in
their work-source ctors (DASH follows in the 4c/4d commit) -- with a
neutral [Stratum] fallback when unset. No coin name remains hardcoded in
src/core.
Stage 4c (template serving): bridge the armed get_work() seam (#726,
embedded coin-state when seeded / retained dashd GBT fallback -- duality
preserved) into the template trio the coin-agnostic session consumes.
DashWorkData is cached under template_mutex_ keyed on work_generation_ +
a 30s staleness TTL (5s negative cache; tip-move bumps the generation).
get_current_work_template() emits exactly the fields send_notify_work()
reads with LTC/BTC byte conventions; merkle branches ride the existing
merkle_branches_raw/_hex SSOT; build_connection_coinbase() goes through
the verifier-shared compute_dash_payouts -> coinbase::build ->
split_coinb SSOTs (coinb1/coinb2 around the 8-byte nonce64 extranonce
slot), so the coinbase a miner hashes is byte-identical to the verifier
split by construction. Genesis case (fresh pool, empty sharechain): the
connecting miner's P2PKH script carries the full worker payout + the
GBT-mandated masternode/superblock outputs + donation tail; the PPLNS
multi-output path rides the new set_pplns_weights_fn seam through the
same SSOT arm. Ctor pins set_difficulty_multiplier to 1.0 (X11 =
standard diff-1 scale) -- the scrypt 65536 default inflated the
advertised difficulty 65536x (the wire=32.768 in the field report).

Stage 4d (submit scoring): mining_submit() reassembles the coinbase,
folds the frozen branches, serializes the 80-byte header via the
block_producer SSOT, runs the selftest-pinned X11, and classifies
tighten-first: WonBlock -> full block in the exact --mine-block
serialization -> submit_block_fn_ dual-path dispatcher (a won block
reaching no sink screams, never drops); ShareAccept -> new
set_mint_share_fn seam (accept-for-vardiff + loud log while unbound);
else low-difficulty reject. compute_share_difficulty() is the same
reconstruction ending in diff1/x11(header) so vardiff engages.

Test gate (the miss that let v0.2.2 ship): test_dash_stratum_work_source
(already in the build.yml allowlist + ctest registration) now carries
the 4c/4d KATs -- fixed-DashWorkData template trio with pinned
encodings, set-gap honesty, branch fold vs compute_merkle_root, coinbase
reassembly byte-identical to the SSOT build with pinned value splits,
X11 difficulty KAT, and deterministic mining_submit classification
(share accept / low-diff reject / won-block bytes / mint-seam routing).

Verified: 19/19 KATs green; c2pool-dash --selftest green; live smoke
(--run --stratum vs a stubbed GBT endpoint) serves mining.notify with a
real job on subscribe -- the #732 field failure mode is gone.
…the field value verbatim

Oracle data.py:277-289: the outer contents['coinbase_payload'] VALUE is
pack.VarStrType().pack(raw_payload) — it already carries one compactsize
prefix — and Share.__init__ (data.py:346-348) appends that value VERBATIM
to the check_hash_link data. On the wire the PossiblyNone(b'', VarStr)
layer adds one more prefix, so DashFormatter's single strip leaves
m_data == [compactsize][raw] for a live oracle share.

The previous fix (#412) correctly established that the compactsize
belongs in the hash_link data, but implemented it by re-VarStr-ing
m_data — a SECOND prefix on top of the one m_data already carries —
diverging gentx_hash from the oracle on every real DIP4 CbTx share
(every modern mainnet-template share, in both accept directions).

Append m_data verbatim; preserve the empty branch (oracle appends b'',
not 0x00). Update the end-to-end anchor in test_dash_share_hash_link to
discriminate all three framings (verbatim / double-prefix / bare-raw)
and document the field-value convention on DashShare.
…link producers, oracle retarget

Producer-side share construction for the pre-v36 v16 lineage (mint
campaign slice 1/3), hand-traced from the p2pool-dash oracle
(p2pool/data.py generate_transaction/get_share/get_ref_hash/
prefix_to_hash_link, p2pool/dash/data.py FloatingInteger + tx_type,
p2pool/networks/dash.py):

- generate_prospective_share_info: share_data carry-through, tx
  forwarding refs over the past min(height,100) shares (first-seen
  dedup, [share_count, tx_count] pairs), far_share_hash 99-ancestor
  walk, timestamp clip (prev+1 .. prev+2*SP-1), absheight/abswork
  accumulation mod 2^32 / 2^128.
- compute_share_target[_pure]: the ORACLE retarget only — APS on the
  min_work basis, 2^256//(SP*aps)-1, +-10% clamp on prev max_target,
  [0, MAX_TARGET] clamp, truncating compact. Deliberately NOT the
  tracker's compute_share_target, which carries a v36 emergency decay
  absent from the dash oracle.
- get_cumulative_weights: the oracle PPLNS window — starts at the
  GRANDPARENT (prev.prev, data.py:181) with max(0,min(height,RCL)-1)
  shares and the oracle's exact partial-inclusion division order.
- serialize_share_info + compute_ref_hash: v16 ref preimage, byte-equal
  to the verifier recompute.
- build_gentx: full oracle coinbase bytes (int16 version+type, worker ||
  payments || donation || OP_RETURN(ref_hash||nonce64), VarStr
  extra_payload) + the hash_link prefix cut; worker_payout subtracts
  only payments actually emitted (oracle rule).
- prefix_to_hash_link: SHA256 midstate producer, inverse of
  check_hash_link, with the oracle endswith(const_ending) assertion.
- build_share: full DashShare assembly with a MANDATORY self-verify —
  the verifier hash_link fold must reproduce the produced gentx txid and
  the share must pass share_init_verify before it could reach the wire.

Nothing is wired into --run or stratum; slices 2-3 bind this to the
node legs and DASHWorkSource::set_mint_share_fn.
…rget pins, wire round-trip

25 tests in the existing allowlisted test_dash_share_hash_link target
(CI build.yml allowlist untouched):
(a) ref stream/ref_hash goldens (179-byte F1 preimage pinned hex) and
    built shares passing share_init_verify / verify_share;
(b) wire round-trip byte-identical through DashFormatter + RawShare +
    load_share, with the oracle outer-payload double-prefix framing
    pinned on the wire tail;
(c) retarget KATs at five chain positions (genesis, short chain,
    deep-uniform MAX clamp, fast-pool 9/10 clamp 0x1c1ccccb, slow-pool
    11/10 clamp 0x1c233332) — exact big-int traces of the oracle
    formula, truncating-compact discriminators;
(d) absheight/abswork accumulation incl. wrap at 2^32 / 2^128;
(e) hash_link producer->checker round trip swept across SHA256 block
    boundaries, plus a drift fence pinning the producer merkle walk to
    coinbase::merkle_branches_raw.

Goldens derived by hand-tracing the oracle source with exact big-int
arithmetic; PROVISIONAL values are marked in-file and listed in the PR
body for oracle-replay confirmation.
…-clip saturation, self-verify scope note

- pool_attempts_per_second: enforce height >= dist at the helper boundary
  so no future caller can receive a silently-wrong span from the skip
  list's exhaust fallback (the oracle only reaches this with the
  TARGET_LOOKBEHIND gate already satisfied).
- clip_timestamp: compute the clip window in 64-bit and saturate at
  2^32-1 — the oracle uses unbounded ints, so a wrapped uint32 window
  would be a silent divergence on a bogus far-future prev timestamp.
- build_share: document the exact scope of the mandatory self-verify
  (serialization self-consistency; amount-level oracle parity is carried
  by the KAT layer + oracle replay of the PROVISIONAL goldens).
@frstrtr
frstrtr changed the base branch from fix/dash-stratum-work-serving to master July 17, 2026 22:18
@frstrtr frstrtr closed this Jul 17, 2026
@frstrtr frstrtr reopened this Jul 17, 2026
@frstrtr
frstrtr merged commit a2eb89e into master Jul 18, 2026
30 checks passed
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