Skip to content

dash(oracle): embedded ORACLE-SHADOW validator + graduation gate (first slice) [DRAFT]#793

Draft
frstrtr wants to merge 2 commits into
masterfrom
dash/embedded-oracle-shadow
Draft

dash(oracle): embedded ORACLE-SHADOW validator + graduation gate (first slice) [DRAFT]#793
frstrtr wants to merge 2 commits into
masterfrom
dash/embedded-oracle-shadow

Conversation

@frstrtr

@frstrtr frstrtr commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Review fixes landed (commit 8ac2ee3)

  • REQUIRED — false-graduation vector fixed: NonEmptyTemplate now classifies on the embedded side only (!e.empty_template) — dashd's near-always-non-empty template can no longer tick the class while the embedded arm serves empty templates.
  • (a/b) dashd-side rpc-error: + proposal-mode tip-race reasons (inconclusive/not-best-prevblk/stale) now VOID the sample (no count, no streak reset) instead of false-REJECT.
  • (c) ledger identity commit key = git describe --tags --always --dirty; no-git/dev fallbacks are never-inherit.
  • (d) creditPool INVARIANT base now = the connected block N-1's committed value (getblock v2 → tx[0].cbTx.creditPoolBalance), not dashd's previous template projection.
  • (e) the compare runs on a dedicated coalescing worker thread, all dashd RPCs outside the ledger mutex — a hung dashd cannot stall tip processing or block /embedded_oracle; worker joined before node_coin_state/rpc unwind.
  • (f) the 11-KAT test_dash_embedded_oracle_shadow is folded into the already-CI-allowlisted test_dash_embedded_gbt target (20 tests green) — no build.yml workflow edit needed.

Rebuilt clean; serve path still untouched (consensus-neutral).


Summary

First working slice of the DASH Embedded ORACLE-SHADOW VALIDATOR — the operator's directive toward a fully daemonless c2pool, safely. Run c2pool ALONGSIDE a Dash Core daemon and use dashd as a continuous realtime oracle: on every new tip, verify the daemonless embedded DIP4 CbTx/template arm against what dashd would produce, catching consensus bugs throughout the alpha, plus an objective graduation criterion that says when it is safe to disable the external dashd.

OBSERVE-only / consensus-neutral. The serve/consensus/share path is untouched — the shadow is a separate new_tip subscriber that only reads the SAME embedded build path (NodeCoinState::select_work) + the SAME dashd RPC closure the serve arm holds. Empty diff-grep over work_source.cpp, coinbase_builder.hpp, share_check.hpp, pplns.hpp, node_coin_state.hpp, embedded_gbt.hpp, coin_state_maintainer.hpp.

Design doc: frstrtr/the docs/c2pool-dash-embedded-oracle-shadow-validator.md (committed). Implements the five §6 conditions of c2pool-dash-oracle-shadow-adversarial-critique.md.

What the first slice does

  • --embedded-oracle-shadow mode (+ --oracle-graduation-blocks N, --oracle-class-coverage K). Arms when the coin-P2P feed is present (--coin-p2p-connect).
  • Per-block VERDICT = dashd getblocktemplate{mode:proposal} of the fully-assembled embedded block (NodeRPC::propose_block_hex) — TestBlockValidity, mempool-independent. This is the authoritative pass/fail (the oracle the soaks used, which the naive field-only draft dropped). A served height counts CLEAN only when the proposal is ACCEPTED.
  • Per-block DIAGNOSIS = regime-aware field compare vs dashd getblocktemplate:
    • EQUALITY (counts): nHeight, CbTx version, bits, mintime, payee IDENTITY, platform burn amount, normalized subsidy (coinbasevalue − Σfees), MN/quorum roots on empty templates.
    • INVARIANT (counts on violation): creditPool normalized-base (committed − platformReward − ownΔ == creditPool(N−1) per side) — the exact check that caught the seed bug 3×.
    • CONSTRAINT (range): bestCL* — sig-equality only when the two nodes' CL heights coincide.
    • informational (NEVER counts): raw coinbasevalue, MN/payee amounts, tx set — the separate-mempool false-positives.
  • Divergence handling: [EMB-ORACLE] DIVERGENCE height=<h> class=<c> field=<f> … structured logs + a persistent JSONL ledger under the data-dir.
  • Graduation ledger + /embedded_oracle JSON endpoint: GRADUATED ⇔ N consecutive served+proposal-accepted clean blocks AND ≥K served coverage of each class (dkg_window, superblock, post_restart_cold, quorum_rotation, credit_pool_transition, non_empty_template) AND ≥1 reorg AND serve-rate floor AND not-revoked. Selection-bias fix: fall-closed classes are recorded and declared as residual serve-gaps (disabling dashd removes the fallback those classes rely on). Verdict gives the exact still_needed + residual_serve_gaps breakdown.
  • Alignment on (height, prev_hash); ledger hard-keyed to {c2pool_commit, comparator_version, dashd_version, net} (a new binary does not inherit an old streak); revocable by chain-drift sentinels (unknown CbTx version / payload parse fail).

Scope of the graduation claim

GRADUATED == chain-state / CbTx-validity equivalence over the served domain (reward-safe daemonless block construction). Soundness rests on the embedded arm's independent chain-state derivation (own SPV/mnlistdiff/quorum/credit-pool pipeline), NOT peer diversity — DASH has no CoinPeerManager. Full network-standalone daemonless operation additionally needs the separate network-layer gate (independent mempool/relay/propagation/peers); the verdict says so.

Build + tests

  • c2pool-dash builds; --selftest passes; --help shows the flag.
  • test_dash_embedded_oracle_shadow — 11 hermetic KATs (green) pinning the regime model, the fee-difference-is-not-a-divergence keystone, the creditPool invariant, the bestCL constraint, and the graduation predicate (+ serve-rate floor, fall-closed serve-gap declaration, revocation).

Follow-ups (scoped in the design doc)

  • Embedded selector must EXCLUDE special-tx types (1–4, 6, 8, 9) until their state machines exist (§1.1 latent bug — a consensus-path change, separate).
  • Nonzero-fee pinning of the platform-reward / MN-amount models (all soaks ran fees=0).
  • Confirm-before-count settle re-sample; full mainnet DKG-window table; phase-diverse restart automation; live-CLSIG getdata leg.
  • CoinPeerManager port to DASH — the network-layer gate.

@frstrtr
frstrtr force-pushed the dash/embedded-oracle-shadow branch from 7dbb8ad to e0bc3fc Compare July 21, 2026 06:29
…duation gate (#793)

Cross-checks the daemonless embedded CbTx/block against dashd getblocktemplate{mode:proposal} per tip; regime-aware field-compare diagnosis; persistent coverage ledger + /embedded_oracle GRADUATED verdict. Observe-only, consensus-neutral.
@frstrtr
frstrtr force-pushed the dash/embedded-oracle-shadow branch from e0bc3fc to f371f7d Compare July 21, 2026 06:32
…d-side class + folded nits

Addresses the PR #793 review (CHANGES-REQUIRED).

REQUIRED FIX — false-graduation vector: the NonEmptyTemplate coverage class
classified on `!e.empty_template || !d.empty_template`, so DASHD's near-always
non-empty template ticked the class while the embedded arm served only empty
templates — accruing coverage without ever exercising the embedded tx-selection/
fee-split/special-tx path the class exists to force-sample (where the latent
special-tx bug lives). Now classified on the EMBEDDED side only (`!e.empty_template`).

Folded review nits:
- (a) a dashd-side `rpc-error:` proposal result VOIDs the sample (not REJECTED) —
  matches propose_block_hex never-throw semantics, no false streak reset.
- (b) proposal-mode tip-race reasons (inconclusive/not-best-prevblk/stale) VOID
  too (bounded settle-resample is a scoped follow-up). is_void_proposal_reason().
- (c) ledger identity: `git describe --tags --always --dirty` so a dirty rebuild
  cannot inherit a tag-exact clean streak; no-git/dev fallbacks are never-inherit.
- (d) creditPool INVARIANT base now comes from the CONNECTED block N-1's committed
  value (getblock verbosity 2 -> tx[0].cbTx.creditPoolBalance), not dashd's
  previous TEMPLATE projection — fewer false VIOLs on real asset-lock chains.
- (e) the compare (2-3 dashd RPCs + file writes) moved OFF the tip thread onto a
  dedicated coalescing worker thread; all RPCs run outside the ledger mutex so a
  hung dashd can neither stall tip processing nor block /embedded_oracle. Worker
  joined (oracle_shadow.reset) before node_coin_state/rpc unwind.
- (f) the 11-KAT test_dash_embedded_oracle_shadow is FOLDED into the already-CI-
  allowlisted test_dash_embedded_gbt target (no build.yml workflow edit the bot
  cannot make); 20 tests green.

Consensus-neutral: serve/consensus/share path unchanged; the shadow only reads.
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