Skip to content

dash: wire the W4 quorum-member resolver into the MN diff-store parallel fold engine (stacked on #1261) - #1262

Closed
frstrtr wants to merge 1 commit into
masterfrom
dash/mn-diff-store-quorum-resolver
Closed

dash: wire the W4 quorum-member resolver into the MN diff-store parallel fold engine (stacked on #1261)#1262
frstrtr wants to merge 1 commit into
masterfrom
dash/mn-diff-store-quorum-resolver

Conversation

@frstrtr

@frstrtr frstrtr commented Aug 17, 2026

Copy link
Copy Markdown
Owner

What

Wire the already-ported W4 quorum-member resolver + W1 PoSePunish loop into the parallel MN diff-store fold engine on the cold MN-CKPT bridge (main_dash.cpp). The store-writer engine (mn_bridge_fold_engine) was stood up with no set_members_fn, so at the first punishing qfcommit it hit the fold's fail-closed:

block carries a non-null qfcommit (llmqType=5 ...) but no quorum-member resolver is installed — PoSe punishes cannot be folded, failing closed

This PR mirrors the existing main replay-fold wiring onto the store engine: a second QuorumReplayEngine seeded at the same full-state anchor and a second ReplayQuorumBridge (whose ctor installs set_members_fn), with observe()/after_fold() driven around each fold_block from the store's block feed and prime_at_anchor() called after the deferred full-state seed lands. Stacked on #1261.

No new algorithm: the dashd HandleQuorumCommitment PoSe-punish loop (replay_fold_engine.hpp) and the rotated/non-rotated member derivation (replay_quorum_engine.hpp, dashd CalcQuorumMembers/GetAllQuorumMembers analog) were already ported, self-checked, and KAT-covered. This is the wiring.

Reward-safety (by construction)

A wrong member set / wrong penalty → folded root diverges from the block's committed merkleRootMNListfold_block poisons (HARD STOP) → the writer never sinks the row → store stays empty → callers fall through to the network path. Never a bad mint. Fail-closed stays the last resort.

KAT — red / green / reward-safe (all pass, BLS=REAL)

  • RED: DashReplayQuorumSeam.Height2513130FailsClosedWithoutAResolver, DashReplayFoldReal.QfcommitWithoutMemberResolverFailsClosed
  • GREEN: DashReplayQuorumSeam.Height2513130FoldsThroughDerivedMembership (folds, punishes 4, reproduces merkleRootMNList byte-exact), DashReplayFoldReal.PoSePunishAndCaseDBan2516412 (real ban-flip reproduces root)
  • REWARD-SAFE: DashReplayFoldSynthetic.RootMismatchIsAHardStopWithNamedHeight, DashMnDiffStore.FinalRootMismatchRefuses
  • 18/18 in the run above.

vm905 cold daemonless MEASUREMENT (honest partial — NOT serve-ready)

Binary g1b329dc3, /proc/exe md5 == built, BLS=REAL, flags --embedded-mainnet --embedded-null-arm --embedded-utxo --coin-p2p-discover, dashd ABSENT (no --coin-rpc).

The wiring works and is proven live: the base fail-closed no quorum-member resolver is installed is GONE (0 occurrences). The resolver is installed, seeded, consulted; the anchor full-state seed reproduces the committed merkleRootMNList and the engine folds forward.

But the cold store still caps at h=2513130 — the error changed to:

resolver has no member set for llmqType=5 quorumHash=0000000000000025 — failing closed

Root cause (structural, separate from the resolver wiring): the first post-anchor punishing commitment is a rotated (DIP-24) llmqType=5 quorum whose cycle base is 2513088, which is below the engine's self-contained point self_contained_from(5, 2513000) ≈ 2513952. An anchor-seeded engine cannot self-derive a rotated cycle whose quarter-snapshot work blocks (2513080 / 2512792 / 2512504 / 2512216, three pre-anchor) predate the anchor — unlike the full --replay-bulk path that starts at height 1 and observes them. The cold daemonless path seeds no pre-anchor cycle snapshots / work lists (0 pre-anchor header hashes at store-setup), so cycle 2513088 is underivable and the fold fails closed at h=2513130 — the same store cap as base #1261.

Consequence: store covers 2513000→2513130 only; the MN-CKPT payee lane falls back to the capped network getmnlistd probe and hits the pre-existing payee-desync fail-closed at h=2517703 (BAN-STATE PROBE 8/8 exhausted; that is #149 territory, not this change). Final [EMBED-STATUS] arm=would-decline cause=not-populated populated=0 have_mn=0 bridge=failed-closed. Fold rate 21.5 blk/s. Not serve-ready.

The resolver wiring is a correct, necessary, reward-safe prerequisite — it removes the "no resolver installed" fail-closed and folds every cycle the anchor-seeded engine can self-derive (cycle base ≳ 2513952 forward). The remaining blocker to move the cold store cap past 2513130 is a companion pre-anchor rotated-cycle seed for the store engine (the qsnapshot/work-list seed the main path takes from a capture, or an earlier anchor) — a separate piece, NOT a resolver defect.

Guardrails

NON-MONEY vm905 only; hotel untouched; dashd ABSENT; author frstrtr; zero AI attribution; DRAFT, do not merge.

…lel fold engine

The cold MN-CKPT bridge stands up a parallel DmlFoldEngine (full
ReplayMNState) to write the MN diff/snapshot store, seeded at the
full-state anchor. It was created WITHOUT a quorum-member resolver, so
its fold FAILED CLOSED at the first punishing qfcommit (llmqType=5 @
h=2513130, ~130 past the anchor): the PoSe-punish loop had no ordered
member list to index against the commitment validMembers bitset and
returned "no quorum-member resolver is installed -- PoSe punishes cannot
be folded, failing closed". The store therefore covered only ~130
heights and the ban-state probe / on-demand fold fell back to the capped
network getmnlistd path (payee-desync).

Mirror the main replay-fold path exactly: stand up a second
QuorumReplayEngine seeded at the same anchor and a second
ReplayQuorumBridge whose ctor installs set_members_fn onto the store
engine, seed the pre-anchor header hashes, prime_at_anchor after the
deferred full-state seed lands, and drive observe()/after_fold() from
the store block feed around each fold_block. The engine self-derives
rotated + non-rotated quorum member sets from the same replayed blocks
(dashd CalcQuorumMembers/GetAllQuorumMembers analog), self-checked
against each block committed merkleRootQuorums, with no qrinfo/P2P
dependency. The already-ported HandleQuorumCommitment PoSe-punish loop
then applies the dashd-exact punishes so the folded list re-hashes to
the committed merkleRootMNList past the qfcommit and the store covers
heights to tip.

Reward-safe by construction: a wrong member set / wrong penalty makes
the folded root diverge from the block committed merkleRootMNList, the
fold poisons (HARD STOP), the writer never sinks the row, the store
stays empty, and callers fall through to the network path -- never a bad
mint. Fail-closed stays the last resort.
@frstrtr
frstrtr force-pushed the dash/mn-diff-store-quorum-resolver branch from 1b329dc to f480ffe Compare August 18, 2026 11:43
@frstrtr
frstrtr changed the base branch from dash/mn-diff-store-fullstate-seed to master August 18, 2026 11:43
@frstrtr frstrtr closed this Aug 18, 2026
@frstrtr frstrtr reopened this Aug 18, 2026
@frstrtr

frstrtr commented Aug 20, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #1293 (combined fetch+fold onion). This branch's head commit is present in #1293 by exact SHA (verified via triage). Closing to reconcile the draft backlog; the branch is preserved (not deleted) — reopen if #1293 changes course.

@frstrtr frstrtr closed this Aug 20, 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