Skip to content

dash(mint 3/3): run-loop share minting — ShareAccept -> tracker insert + broadcast + PPLNS weights#750

Merged
frstrtr merged 6 commits into
masterfrom
feat/dash-mint-runloop
Jul 19, 2026
Merged

dash(mint 3/3): run-loop share minting — ShareAccept -> tracker insert + broadcast + PPLNS weights#750
frstrtr merged 6 commits into
masterfrom
feat/dash-mint-runloop

Conversation

@frstrtr

@frstrtr frstrtr commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Slice 3/3 of the DASH sharechain-MINT campaign: the run-loop wiring that makes c2pool-dash --run actually MINT legacy v16 shares onto the p2pool-dash sharechain — ShareAccept -> producer rebuild -> tracker insert -> peer broadcast, with best-share election and the PPLNS window bound into the coinbase. Enables PPLNS payouts and is the prerequisite for joining the live p2pool-dash network.

Re-verified gap delta (vs the earlier assessment)

  • Slice 2 was NOT on master. share_producer_bind.hpp (+ its 5 KATs) sat unmerged on dash/s8-share-producer-mint-bind with no PR. The two commits are included here (cherry-picked, unchanged) — this PR stacks on them.
  • (b) think(): the tracker half already existedShareTracker::think() (P1 verify walk / P2 budgeted verify / scoring) is fully implemented on master. What was missing was the NODE-side driver: run_think(), m_best_share_hash publication, the pending-adds drain, and any caller. All added.
  • (a) mint seam, (c) broadcast/forward leg, (d) storage construction, (e) PPLNS weights binding — all still missing on master; all built here.
  • Two additional blocking gaps found during re-verification, both fixed:
    • the DASH node NEVER SENT its own version message (no connected() override / send_version) — a p2pool-dash peer drops the link on handshake timeout, so reception alone could not hold a session; send_ping was also a stub;
    • nothing set tracker.m_coin_params (X11 pow_func) — reception verify would run with an empty pow function and reject every share.

What was built

Byte-parity architecture (the ban-safety keystone): when the producer seam is bound, the stratum coinbase served to miners IS the producer share gentx (producer::build_gentx), split verbatim around the zeroed nonce64 slot. The mint-time rebuild (build_mint_share, slice 2) re-runs the identical deterministic producer walks (all anchored backward from the frozen prev_share_hash), so the rebuilt share reproduces the solved header bytes exactly — ONE gentx serializer, byte-parity by construction rather than by parallel-implementation parity. Fail-closed gates stack: frozen-job registry miss (ref_hash recovered from the coinb1 tail), the slice-2 X11 identity gate, and a new explicit pow <= committed m_bits target guard so a stratum share-bits race can never mint a share peers would ban over.

  • src/impl/dash/mint_runloop.hpp — build_producer_job, FrozenJobRegistry, mint_from_inputs (ban-safety gate), elect_best_share (verified-accumulated-work-first; KAT-able pure function), pplns_weights_for (ORACLE window: grandparent start, data.py:181), fee policy (below).
  • src/impl/dash/node.{hpp,cpp} — run_think + election + snapshot publication; broadcast_share/send_shares (message_shares codec + remember_tx/forget_tx tx forwarding); connected()/send_version (+ real ping); SharechainStorage construction + reload + verified-mark/removal persistence + batch share persist; add_local_share; register_template_txs.
  • src/impl/dash/stratum/work_source.{hpp,cpp} — ProducerJobFn seam (byte-unchanged fallback path when unbound); MintShareInputs extended with ref_hash / last_txout_nonce / tx_data.
  • src/c2pool/main_dash.cpp — full --run wiring: coin-params + storage init, best-share fn, producer-job fn (per-job cache to keep notify bytes stable + the registry within its window), mint fn, PPLNS weights fn, work-refresh on best change, initial + 15 s think tick, shutdown flush.

Fee flags (README design, LTC sharechain-lane port): --give-author (default 0.1%) -> the share's donation u16 (oracle dev-fee channel; PPLNS weights decay by att*(65535-donation); the donation output is ALWAYS emitted by the gentx — --give-author 0 keeps the dust-marker output present). -f/--fee + --node-owner-address -> the consensus-safe probabilistic identity substitution (~fee% of minted shares carry the owner's pubkey_hash; identical coinbases on every peer). --redistribute fee|donate for non-P2PKH miner credentials (donate = donation=65535, the pubkey-hash-keyed DASH form of 100%-to-donation); the LTC weighted pplns|boost redistribution engine is NOT yet ported — those decline fail-closed (the pre-existing behavior), reported as a follow-up. The pre-v36 donation script stays dash::DONATION_SCRIPT (what the live v16 network verifies); the v36 COMBINED-script migration surface (compute_dash_payouts version gate) is untouched — when the crossing slice lands, producer::build_gentx needs the same share-version gate (flagged, not silently wrong: this slice mints v16 only by construction).

STOPPED on (honest gaps, loudly logged — not half-wired)

  • Desired-share DOWNLOAD leg (p2pool node.py download loop): think()'s desired set is logged, not requested. Reception can serve peers (handle_get_share), but filling OUR gaps beyond what peers push rides a later slice — required for a cold-start sync against the live network.
  • Peer ban list + rejected-share tracking (btc m_ban_list/m_rejected_share_hashes): bad-share peers are logged, not banned.
  • pplns/boost redistribute engine — LTC's weighted redistribution not yet ported (declines fail-closed).
  • clean_tracker/prune driver on the DASH node (tracker-side machinery exists; the periodic prune caller is a later slice — bounded exposure while chains are short).

KATs (11 new, in the existing allowlisted test_dash_share_hash_link target; 40/40 green)

Full pipeline with REAL X11 nonce searches: ShareAccept mints a share that passes the in-tree verifier and whose hash IS the solved header PoW; message_shares codec round-trip byte-identical + peer-side re-verify to the same hash; tracker insert + get_shares walk; heaviest-verified-head election (+ refusal with peers-but-no-verified-chain); PPLNS oracle-window pin (grandparent start, tip's miner excluded, hand-computed weights); ban-safety decline of an above-target solve (isolated to the target gate via mainnet params); registry fail-closed + FIFO bound; donation u16 mapping; fee-roll/redistribute identity resolution; the combined --fee 1 --give-author 0 split (owner identity + donation 0 + donation output PRESENT); oracle weight decay att*(65535-donation).

Verification

  • test_dash_share_hash_link 40/40 (slice-1 + slice-2 + slice-3 KATs); all 17 affected dash test targets rebuilt + green (node, tracker, work_source x4, stratum x4, p2p_node, peer, messages x2, reception wire, auto_ratchet, min_protocol_gate, conformance 56/56).
  • c2pool-dash --selftest OK; 20 s --run --testnet smoke: listener + stratum up, think tick live, clean shutdown with persistence flush.
  • Fenced to src/impl/dash/ + main_dash.cpp + dash test files; no core/other-coin source edits; dashd-RPC fallback untouched.

Byte-parity confidence vs the oracle

The producer core (slice 1) is hand-traced from frstrtr/p2pool-dash with oracle-pinned KATs; slice 3 adds no second serializer — the mint reuses the producer end-to-end and self-verifies through the in-tree verifier plus the X11 identity + target gates, so a diverging share CANNOT reach the tracker or the wire (it declines instead). What KATs cannot prove is live-oracle ACCEPTANCE semantics (e.g. tx-forwarding expectations of a real peer, timestamp/donation edge tolerances) — the next step is a controlled soak against a p2pool-dash testnet peer (available on request) BEFORE pointing at mainnet: connect, mint on the live tip, and confirm the oracle keeps the session + tracks our share.

Remaining gap to a live p2pool-dash JOIN

  1. Controlled-peer soak (above) — the acceptance evidence gate.
  2. The desired-share download leg (cold-start sync).
  3. Outbound dial machinery exists via bootstrap addrs; addnode/connect reach the addr store and inbound handshakes now complete both ways — verify a sustained outbound session against the controlled peer.
  4. Ban list + clean_tracker driver (robustness under long uptime).

frstrtr and others added 6 commits July 19, 2026 11:10
…>build_share adapter scaffold (WIP, KAT+build next)
…dapter + 5 KATs

Implements make_producer_mint_fn / build_mint_share: maps a stratum-found
solution (DASHWorkSource::MintShareInputs) plus the per-job context frozen at
job assembly (FrozenMintJob) into a fully-built, self-verified DashShare and
returns its X11 share hash. parse_min_header_80 (BlockHeaderType slice) +
pubkey_hash_from_p2pkh (inverse of pubkey_hash_to_script2), both fail-closed.
Integrity gate: rebuilt share m_hash must reproduce the solved header pow_hash
or the mint is declined (null). Header-only, impl/dash only, nothing in
src/core; KAT-gated, not wired into --run (that is slice 3/3). dashd RPC
fallback untouched. Compiled into the existing allowlisted test target so the
build.yml allowlist is unchanged. 5/5 KATs green; direct-build parity pinned.
… frozen-job registry, ban-safety mint gate

mint_runloop.hpp: the wiring layer between the producer machinery (slices
1-2) and the live --run pool.

- build_producer_job: job-time producer pass — prospective share_info off
  the live chain, oracle-window cumulative weights (grandparent start,
  data.py:181), ref_hash, and the FULL share gentx serialized with the
  nonce64 slot zeroed. The stratum coinbase IS this gentx (split around the
  slot), so the solved bytes are byte-identical to what the mint-time
  rebuild reproduces — byte-parity by construction, ONE gentx serializer
  (producer::build_gentx), no parallel-implementation parity to drift.
- FrozenJobRegistry: bounded ref_hash -> FrozenMintJob store; ref_hash
  commits to the entire prospective share_info, so a miss is a fail-closed
  decline, never a guess.
- mint_from_inputs: slice-2 build_mint_share (X11 identity gate) PLUS the
  explicit pow<=committed-target guard — a stratum share-bits race can
  never mint a share that fails its own m_bits target on peers (the
  PeerMisbehavingError BAN line).
- elect_best_share: verified-work-first election (think() best when on the
  verified chain; else heaviest verified head by ACCUMULATED work via the
  tracker view; ZERO with peers-but-no-verified-chain; raw-head genesis
  bootstrap). Pure function so the policy is KAT-able.
- pplns_weights_for: oracle-window tracker walk for the non-producer
  coinbase seam (uniform-shift u64 normalization; ref ZERO by contract —
  a solve on the fallback path can never mint).
- MintFeePolicy/resolve_mint_identity/donation_percent_to_u16: README flag
  port (LTC sharechain-lane semantics) — --give-author -> share donation
  field (oracle dev-fee channel; donation output stays ALWAYS emitted);
  --fee/--node-owner-address -> consensus-safe probabilistic identity
  substitution; --redistribute fee/donate for non-P2PKH credentials
  (donate = donation=65535, the pubkey-hash-keyed DASH form of 100%-to-
  donation); pplns/boost redistribution engine not yet ported (declines,
  the pre-existing fail-closed behavior).

work_source: ProducerJobFn seam (build_connection_coinbase serves the
producer gentx verbatim when bound, publishes the committed share bits via
set_share_target; byte-unchanged fallback otherwise); MintShareInputs now
carries ref_hash (recovered from the coinb1 tail), last_txout_nonce
(LE u64 of en1||en2) and the job's frozen tx_data.

share_producer_bind: FrozenMintJob.payout_script_override — the mint-time
rebuild uses the identity the job was BUILT with (fee substitution would
otherwise fail the X11 identity gate on every substituted solve).

Fenced to src/impl/dash/; dashd-RPC fallback untouched.
…oadcast leg, LevelDB persistence, version-handshake send

Ports of the btc::NodeImpl prod reference reconciled to the DASH pool-node
(the tracker's think() was already fully implemented; this is the NODE-side
driver + the send legs that were missing):

- run_think(): compute-thread think() under the exclusive tracker lock
  (bootstrap full-verify on an empty verified chain), publishes
  m_best_share_hash + the lock-free snapshot, flushes verified marks to
  LevelDB, IO-phase drains deferred share batches, fires
  on_best_share_changed (stratum work refresh) and re-broadcasts the new
  head. Serialized by m_think_running; budget-exhaustion continuation
  re-posts. bad_peer_addresses are LOGGED (no ban list on the DASH node
  yet) and the desired-share download leg is loudly logged as a later
  slice — both honest gaps, not silent drops.
- best_share_hash()/advertised_best_share(): election via the
  mint_runloop elect_best_share policy; advertised variant is inline
  (vtable-safe for the link-deferred KAT targets) and feeds send_version.
- broadcast_share()/send_shares(): the missing FORWARD leg — walk up to 5
  un-broadcast ancestors, remember_tx the txs the peer lacks (from
  m_known_txs), send via the message_shares wire codec (round-trip pinned
  byte-identical by KAT), forget_tx after. try_to_lock throughout (IO
  thread never blocks on the compute thread).
- connected()/send_version(): the node now OPENS the handshake on every
  connection (p2pool sends version from both sides; a silent side gets
  dropped by the remote handshake timeout) and advertises its best share
  so peers pull our chain. send_ping is now a real keep-alive.
- init_storage()/load_persisted_shares()/flush_verified_to_leveldb()/
  shutdown_persistence(): SharechainStorage construction under the
  per-net name, verified-hash + removal persistence hooks, height-ordered
  reload with known_verified pre-population and window pruning (btc
  parity: 8-byte version prefix + packed contents).
- add_verified_shares(): now persists each new batch atomically and
  triggers run_think() (p2pool: set_best_share after every batch).
- add_local_share(): the mint insertion — non-blocking exclusive lock
  (busy => decline, fail-closed), tracker add + inline attempt_verify +
  persist, then broadcast + rescore.
- register_template_txs(): current template txs -> m_known_txs (bounded
  by one template) so share relay can serve remember_tx for a minted
  share's new_transaction_hashes.

Fenced to src/impl/dash/.
… broadcast + PPLNS weights + fee flags

main_dash run_node now actually MINTS onto the sharechain:

- tracker coin-params set at startup (X11 pow_func — reception verify was
  running with an empty default) + init_storage(net_subdir) (LevelDB
  sharechain persistence + reload under the per-net dir).
- set_best_share_hash_fn -> the node's verified-work-first election, so
  new jobs build on the live tip.
- set_producer_job_fn: tracker-guarded (try-lock, degrade-on-busy)
  producer job build with a per-(prev, payout, template) 30 s cache
  (stable job bytes across the 1 s notify cadence — keeps the session
  payload cache effective and the frozen-job registry within its
  window), fee-policy identity resolution (one roll per job build, the
  p2pool per-get_work fee roll), and template-tx registration for relay.
- set_mint_share_fn: registry lookup by the coinbase's OP_RETURN
  commitment -> deterministic producer rebuild (X11 identity +
  pow<=target ban-safety gates) -> add_local_share (tracker insert +
  persist + broadcast + rescore). Every decline is loud; nothing mints
  on a miss.
- set_pplns_weights_fn: oracle-window tracker walk for the non-producer
  coinbase path (pays the live PPLNS window even when the producer seam
  degrades).
- on_best_share_changed -> stratum work-generation bump; initial
  run_think + 15 s keep-fresh tick; shutdown_persistence on exit.
- README fee flags (LTC-path port): --give-author/--dev-donation
  (default 0.1% -> donation field 66; the donation output is ALWAYS
  emitted by the gentx, 0% keeps the dust-marker semantic), -f/--fee +
  --node-owner-address (consensus-safe probabilistic node-owner
  identity substitution), --redistribute pplns|fee|boost|donate
  (broken-credential policy; weighted pplns/boost engine is a later
  port and declines fail-closed).

The minted shares are the LEGACY v16 DashShare lineage the live
p2pool-dash network speaks (min-proto 1700); the v36 crossing stays a
separate later step. dashd-RPC fallback untouched.
…11 solves

11 KATs into the existing allowlisted test_dash_share_hash_link target
(no build.yml change):

- ShareAcceptMintsVerifiedShareIntoTracker: producer job -> coinb1/coinb2
  split -> miner coinbase reassembly -> REAL X11 nonce search -> mint;
  the share hash IS the solved header PoW, the frozen-job registry keys
  on the ref recovered from the coinb1 tail, the share passes the
  in-tree verifier (build_share mandatory self-verify), inserts into the
  tracker and appears in the get_shares chain walk.
- MintedShareWireRoundTripByteIdentical: message_shares payload codec
  round-trip byte-identical + the reloaded share re-verifies to the SAME
  X11 hash through share_init_verify (the exact peer-side check).
- BestShareElectionPicksHeaviestVerifiedHead: heaviest ACCUMULATED-work
  verified head wins across forks; think-best on the verified chain is
  authoritative; peers-but-no-verified-chain refuses (ZERO).
- PplnsWeightsMatchOracleWindow: oracle grandparent-start window pin —
  the tip's own miner excluded, earlier miners' equal weights + total
  hand-verified; ref ZERO on the fallback path.
- DeclinesSolveBelowCommittedTarget: the ban-safety gate — a
  structurally-consistent solve above the committed share target is
  DECLINED (isolated to the pow<=target discriminator via mainnet
  params).
- FrozenJobRegistryFailClosedAndBounded: unknown-ref decline + FIFO cap.
- DonationPercentToU16Mapping / ResolveMintIdentityFeeAndRedistribute /
  CombinedFeeSplitOwnerSubstitutionWithZeroDevFee /
  DonationFieldDecaysPplnsWeight: the fee-flag port pins — --give-author
  u16 mapping (0.1->66, 0.5->328), fee-roll substitution + redistribute
  fee/donate/pplns outcomes, the combined --fee 1 --give-author 0 split
  (owner identity + donation field 0 + donation output PRESENT in the
  solved coinbase bytes), and the oracle weight decay
  att*(65535-donation) with the donation weight att*donation.
@frstrtr
frstrtr merged commit ae89a78 into master Jul 19, 2026
26 checks passed
@frstrtr
frstrtr deleted the feat/dash-mint-runloop branch July 19, 2026 09:28
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