Skip to content

dash: v36 share type (Phase A) — define + parse, dormant, byte-standardized#775

Merged
frstrtr merged 1 commit into
masterfrom
dash/v36-share-type-phaseA
Jul 21, 2026
Merged

dash: v36 share type (Phase A) — define + parse, dormant, byte-standardized#775
frstrtr merged 1 commit into
masterfrom
dash/v36-share-type-phaseA

Conversation

@frstrtr

@frstrtr frstrtr commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Phase A of the DASH 1700 → v36 transition — the v36 SHARE TYPE

DRAFT for the integrator's merge-tap. Consensus-bearing (defines a share format). Do not merge without review of the flagged divergence in §2.

Decided context (operator, 2026-07-20): DASH does the full 1700→v36 transition, phased. v36 protocol version = 3600. #774 wired the AutoRatchet (dormant); DASH had ONLY a permanent type-16 DashShare and no v36 share type, so the ratchet could not activate. This PR creates that v36 share type. Scope is Phase A ONLY — messaging is Phase B, activation (the ratchet flip) is Phase C; neither is done here.

1. Standardized shape — byte-parity reference is BCH v36 (the non-segwit precedent)

The v36 formatter emits Optional(segwit_data) only if constexpr (is_segwit_activated(version)). That is true for LTC/DGB/BTC (they carry a segwit slot) but false for BCH (SEGWIT_ACTIVATION_VERSION == 0). DASH is non-segwit (X11), like BCH, so the correct byte-parity reference is the BCH v36 MergedMiningShare shape = the ltc/dgb/btc v36 layout MINUS segwit_data. Omitting segwit_data is the established non-segwit convention, not a divergence.

Standardized prefix (min_header .. message_data) is byte-identical to BCH v36: VarInt version; v36 address (pubkey_hash + pubkey_type); VarInt subsidy; donation; stale_info; desired_version VarInt = the version-vote the ratchet reads; merged_addresses (empty/inert); far/max_bits/bits/timestamp/absheight; AbsworkV36Format abswork; merged_coinbase_info + merged_payout_hash (empty/zero inert); ref_merkle_link; last_txout_nonce; V36HashLinkType hash_link; merkle_link; message_data (empty, Phase A). Merged-mining fields are carried for structural uniformity but always EMPTY/INERT (DASH is standalone X11, no AuxPoW child) — exactly as a non-merged coin populates them. "DensePPLNS weights" are derived by the tracker from bits/donation/pubkey_hash, not a wire field.

The v36 shared types are added as byte-identical copies in a new dash::v36 namespace (the established per-coin duplication pattern — the only shared symbol is core::version_gate::is_v36_active).

2. ⚠️ FLAGGED DIVERGENCE — the DASH suffix (integrator/operator review)

DASH must pay masternodes/superblocks/platform and carry a DIP4 CbTx payload — consensus data with no cross-coin equivalent. It rides as a DASH-specific suffix after message_data, keeping the entire standardized prefix byte-identical: coinbase_payload, payment_amount, packed_payments, coinbase_payload_outer. Folding it into the shared merged_* fields was rejected (invents a DASH-only encoding, breaks parity). Please confirm this suffix approach vs. a pure no-suffix shape (which would break p2p verifiability for peers without dashd state). Baked now (dormant + reversible) so the wire format is frozen once rather than changed again at Phase C.

3. Dormancy / backward-compat (1700 byte-unchanged)

The live dash::ShareType = ShareVariants<DashFormatter, DashShare> is unchanged — its concrete consumers (share_init_verify(const DashShare&), node.cpp:64) would not compile against a second variant member; that guarding is Phase B/C. A separate dormant dash::V36ShareType proves version-36 dispatch. DashFormatter::Read/Write branch on ShareT::version, so the v16 body is instantiated only for DashShare (byte-unchanged). current_share_version stays 16 — nothing mints a v36 share, and the #774 hard-cut warning is respected.

4. KATs — MANDATORY (bundled into the already-allowlisted test_dash_share_hash_link, no build.yml change → avoids the #769 new-target trap)

  • Byte-parity: standardized prefix == BCH v36 field order/encoding + frozen golden hex sentinel (prefix + full wire).
  • Round-trip: pack → RawShare(type 36)load_v36_share → field equality → re-pack byte-identical (real dispatch).
  • Merged-inert: empty merged vectors → 0x00; zero payout hash → 32 zero bytes; empty message_data → 0x00.
  • Version-vote: desired_version rides as CompactSize VarInt.
  • Backward-compat: v16 round-trips through the live ShareType::load(16) unchanged; v16 vs v36 distinct.

Results: test_dash_share_hash_link 50/50 pass; c2pool-dash builds; test_dash_conformance (56), test_dash_share_tracker (9), test_dash_share_messages (9) unchanged.

5. Phase B / C build on this

  • B (messaging): populate/consume message_data; extend version_negotiation tallies to count DashV36Share.m_desired_version (currently guarded to DashShare only — correct while dormant).
  • C (activation): promote DashV36Share into the live variant + guard the DashShare-concrete consumers; add a v36 accept path (X11 PoW + hash_link + merkle + the suffix coinbase reconstruction); feed apply_min_protocol_ratchet_decision with target_floor = 3600; only then consider current_share_version → 36 behind the 95% weighted activation.

Design note staged to frstrtr/the docs/dash-v36-share-type-phaseA.md (not pushed).

…rdized

Adds the DASH v36 share type (wire-type 36, dash::DashV36Share) so the #774
AutoRatchet has a v36 share to activate. PHASE A only: the type is DEFINED,
PARSEABLE, and round-trips through the real ShareVariants dispatch, but stays
DORMANT — nothing mints it (current_share_version stays 16) and the live 1700
accept/mint/store/send path is byte-unchanged. Messaging is Phase B; the
activation flip is Phase C.

Standardized shape: the share's standardized prefix (min_header..message_data)
is byte-identical to the cross-coin non-segwit v36 shape. The reference is the
BCH v36 MergedMiningShare layout — i.e. the ltc/dgb/btc v36 layout MINUS the
Optional(segwit_data) slot — because DASH, like BCH, is non-segwit
(is_segwit_activated(36) is false). Omitting segwit_data is the established
non-segwit convention, not a divergence. Merged-mining fields
(merged_addresses / merged_coinbase_info / merged_payout_hash) are carried for
structural uniformity but always EMPTY/INERT (DASH is standalone X11, no AuxPoW
child), exactly as a non-merged coin populates them.

The one deliberate, FLAGGED divergence is a DASH-specific suffix after
message_data — coinbase_payload, payment_amount, packed_payments,
coinbase_payload_outer — carrying masternode / DIP4-CbTx consensus data that has
no cross-coin equivalent, so Phase C can reconstruct and verify a real DASH
coinbase from the share.

Coexistence is kept surgical: the live dash::ShareType variant is unchanged (its
concrete consumers, e.g. share_init_verify(const DashShare&), would not compile
against a second variant member — that guarding is Phase B/C). A separate dormant
dash::V36ShareType proves version-36 dispatch. DashFormatter::Read/Write branch on
ShareT::version, so the v16 body is instantiated only for DashShare (byte-unchanged).

KATs in test/test_dash_v36_share.cpp (bundled into the already-allowlisted
test_dash_share_hash_link target — no build.yml change, avoids the #769 trap):
byte-parity of the standardized prefix vs the BCH v36 order + frozen golden;
round-trip through the version-36 dispatch; merged-fields-inert convention;
version-vote VarInt; and v16 backward-compat (unchanged + distinct).
test_dash_share_hash_link 50/50 pass; c2pool-dash builds; test_dash_conformance
(56), test_dash_share_tracker (9), test_dash_share_messages (9) unchanged.
@frstrtr
frstrtr marked this pull request as ready for review July 21, 2026 00:50
@frstrtr
frstrtr merged commit d4d45ac into master Jul 21, 2026
26 checks passed
frstrtr pushed a commit that referenced this pull request Jul 21, 2026
…reward-safe --embedded-mainnet (default OFF)

Rebased onto master (#781 io-decouple + #773/#777/#775/#782). Integrates the
embedded DASH template arm into the decoupled tip-poll/rpc_pool architecture WHILE
preserving both: #781's refresh_executor/resource_template_now split AND every
v0.2.4 embedded guard. Default behaviour is UNCHANGED and reward-safe — with
--embedded-mainnet OFF (the default) the template stays arm=dashd-fallback, the
path the hotel runs, byte-for-byte.

Wires the vendored SML/QuorumManager/CCbTx machinery into the live embedded
template so the daemonless arm assembles a real DIP-0004 type-5 CCbTx, proven
byte-identical to a real dashd getblocktemplate from the raw mnlistdiff wire
(merkleRootMNList + merkleRootQuorums recomputed from wire; bestCL; creditPool
accrual). The mainnet gate lifts behind the explicit --embedded-mainnet opt-in.

Consensus guards (all fail-closed to the reward-safe dashd fallback):
- merkleRootQuorums: hash ALL active commitments (no rotated per-index dedup) —
  byte-identical to dashd (109-quorum from-wire KAT).
- H-1: a malformed quorum tail wipes + full-resyncs (never papered over by a
  later incremental; base-continuity tightened).
- DKG mining-phase heights (is_dkg_commitment_window over every enabled llmqType)
  and superblock heights: refuse embedded.
- bestCL freshness: committed ChainLock must be within one block of the tip.
- creditPool: INDEPENDENT seed-height gate (the seed cbTx's own nHeight must equal
  the tip) — catches a seed one block behind that value/hash self-checks miss;
  pre-emit value re-check + serve-time cache re-validation for the build-vs-serve
  skew; GBT-xcheck backstop cross-checks the embedded creditPool vs dashd.
- C-3: the embedded block excludes all DIP special txs (1-4,6,8,9) — their
  own-block state effects are not modelled in the committed CbTx (Phase-2).
- pre-emit HARD GATE re-validates the built CbTx (both roots + all height-class +
  bestCL + creditPool checks) before serve; discards to fallback on any failure.

Reward-safety posture: --embedded-mainnet is default OFF (unconfigured mainnet
nodes are byte-unchanged dashd-fallback). The embedded-ON daemonless seed-lag
(arm falls back rather than serving stale at some heights) is deferred as a
separate goal; ON is reward-safe (worst case = fallback, never a bad block).

Tests (dash embedded suite): node_coin_state 15, coin_state_maintainer 11,
stratum_work_source 38, embedded_gbt 10, mnlistdiff_root_parity 4, byte_parity 4,
quorum_root 11, mempool 19, node_reception_wire 21, header_chain 8. c2pool-dash
builds+links against the #781 io-decoupled main_dash.
frstrtr pushed a commit that referenced this pull request Jul 21, 2026
…reward-safe --embedded-mainnet (default OFF)

Rebased onto master (#781 io-decouple + #773/#777/#775/#782). Integrates the
embedded DASH template arm into the decoupled tip-poll/rpc_pool architecture WHILE
preserving both: #781's refresh_executor/resource_template_now split AND every
v0.2.4 embedded guard. Default behaviour is UNCHANGED and reward-safe — with
--embedded-mainnet OFF (the default) the template stays arm=dashd-fallback, the
path the hotel runs, byte-for-byte.

Wires the vendored SML/QuorumManager/CCbTx machinery into the live embedded
template so the daemonless arm assembles a real DIP-0004 type-5 CCbTx, proven
byte-identical to a real dashd getblocktemplate from the raw mnlistdiff wire
(merkleRootMNList + merkleRootQuorums recomputed from wire; bestCL; creditPool
accrual). The mainnet gate lifts behind the explicit --embedded-mainnet opt-in.

Consensus guards (all fail-closed to the reward-safe dashd fallback):
- merkleRootQuorums: hash ALL active commitments (no rotated per-index dedup) —
  byte-identical to dashd (109-quorum from-wire KAT).
- H-1: a malformed quorum tail wipes + full-resyncs (never papered over by a
  later incremental; base-continuity tightened).
- DKG mining-phase heights (is_dkg_commitment_window over every enabled llmqType)
  and superblock heights: refuse embedded.
- bestCL freshness: committed ChainLock must be within one block of the tip.
- creditPool: INDEPENDENT seed-height gate (the seed cbTx's own nHeight must equal
  the tip) — catches a seed one block behind that value/hash self-checks miss;
  pre-emit value re-check + serve-time cache re-validation for the build-vs-serve
  skew; GBT-xcheck backstop cross-checks the embedded creditPool vs dashd.
- C-3: the embedded block excludes all DIP special txs (1-4,6,8,9) — their
  own-block state effects are not modelled in the committed CbTx (Phase-2).
- pre-emit HARD GATE re-validates the built CbTx (both roots + all height-class +
  bestCL + creditPool checks) before serve; discards to fallback on any failure.

Reward-safety posture: --embedded-mainnet is default OFF (unconfigured mainnet
nodes are byte-unchanged dashd-fallback). The embedded-ON daemonless seed-lag
(arm falls back rather than serving stale at some heights) is deferred as a
separate goal; ON is reward-safe (worst case = fallback, never a bad block).

Tests (dash embedded suite): node_coin_state 15, coin_state_maintainer 11,
stratum_work_source 38, embedded_gbt 10, mnlistdiff_root_parity 4, byte_parity 4,
quorum_root 11, mempool 19, node_reception_wire 21, header_chain 8. c2pool-dash
builds+links against the #781 io-decoupled main_dash.

CI-fold: build.yml "Build tests" allowlists test_dash_embedded_gbt but not
the two byte-parity executables, so CTest registered them (gtest_add_tests AUTO)
while CI never built them -> ctest "Not Run" exit 8 (Linux x86_64 red), and the
from-wire merkleRoot + real-dashd CbTx round-trip proofs ran in NO CI job.
Folded test_dash_embedded_cbtx_byte_parity.cpp + test_dash_mnlistdiff_root_parity.cpp
INTO the test_dash_embedded_gbt executable (distinct TEST suites; DASH_FIXTURE_DIR
moved to that target) and removed the standalone add_executable/gtest_add_tests
registrations. test_dash_embedded_gbt 10 -> 18 cases (byte-parity 4 + from-wire 4),
all green; no unbuilt-executable CTest registrations remain.
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