Skip to content

dash(coinbase): include masternode payee output in stratum-served coinbase (fix bad-cb-payee won-block reject)#746

Closed
frstrtr wants to merge 1 commit into
masterfrom
fix/dash-coinbase-masternode-payee
Closed

dash(coinbase): include masternode payee output in stratum-served coinbase (fix bad-cb-payee won-block reject)#746
frstrtr wants to merge 1 commit into
masterfrom
fix/dash-coinbase-masternode-payee

Conversation

@frstrtr

@frstrtr frstrtr commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Release-blocking bug: won DASH blocks rejected bad-cb-payee

c2pool-dash could build a coinbase that dashd rejects with bad-cb-payee when a block is found — the won block (and its reward) was silently lost. This gates the DASH release.

Hex-confirmed root cause (live testnet dashd, GBT proposal mode, 2026-07-19)

compute_dash_payouts silently dropped any GBT-mandated payment (masternode / superblock / platform burn) whose payee string failed decode_payee_script (if (pm_script.empty()) continue;), and normalize_payment discarded dashd's GBT-provided raw "script" scriptPubKey bytes whenever a non-empty base58 payee was present — so there was no byte-faithful fallback. The coinbase then lacked a consensus-REQUIRED output and dashd rejected every won block with bad-cb-payee.

Deterministic field trigger: net-mode/daemon-chain mismatch. A mainnet-mode binary against a testnet dashd applies address_version=76 to the y... testnet masternode payee → base58 decode fails → the masternode output vanishes. NodeRPC::check() passed this mismatch silently (it only probed the mainnet-genesis direction).

Evidence (same daemon, same height-1517409 template, byte diff):

  • testnet mode: coinbase = worker ‖ platform-burn 6a (66966830) ‖ MN payee 76a91464f2b2b84f62d68a2cd7f7f5fb2b5aa75ef716d788ac (111611384) ‖ donation ‖ OP_RETURN ‖ CbTx → dashd proposal verdict null (valid)
  • mainnet mode: identical except the MN payee output is MISSING (amount folded into donation) → dashd proposal verdict bad-cb-payee

Falsified alternates (per the parallel investigation): the fresh-template stratum coinbase on the right net is dashd-valid (proposal-accepted twice + a live won block ACCEPTED, below); template+coinbase+txs are frozen as ONE atomic DashWorkData snapshot per job, so intra-height GBT drift cannot desync payee vs body; tip-move staleness produces prev-based rejects (observed live: bad-chainlock), never bad-cb-payee.

Fix (fenced to src/impl/dash/ + tests)

  1. coin/rpc_data.hppPackedPayment gains a script field; normalize_payment ALWAYS preserves the raw GBT scriptPubKey bytes alongside the payee string (payee-string semantics unchanged → share-wire compatible; the sharechain PackedPayment is a distinct struct).
  2. coinbase_builder.hppcompute_dash_payouts never drops: (a) decode the payee string as before; (b) on failure, emit the GBT script bytes verbatim with a LOUD warning; (c) if neither is usable, throw — the stratum path serves no job and screams, instead of letting a miner burn hashrate on a block dashd must reject.
  3. coin/rpc.cpp — chain-identity guard in check(): mainnet mode requires chain=="main"; --testnet rejects chain=="main" (accepts test/regtest/devnet for the tuned-net harness posture). Wrong-net now fails loudly at connect.
  4. coin/embedded_gbt.hpp — embedded arm also carries raw scriptPayout bytes for the same fallback.

KATs (existing allowlisted targets — no new test target, no build.yml change)

test_dash_cb_payee (12/12): raw-script preservation; right-net coinbase contains burn + MN payee with exact GBT script+amount (real captured testnet GBT entries); wrong-net falls back to GBT script verbatim (the pre-fix vanishing-output case); undecodable-without-script throws; GOLDEN — byte-for-byte full coinbase for the real height-1517409 template, the exact serialization dashd accepted live via proposal mode.
test_dash_coinbase_parity (11/11): updated to the never-drop rule + new UndecodablePayeeThrowsNeverDrops.

Verification

  • test_dash_cb_payee 12/12, test_dash_coinbase_parity 11/11, test_dash_stratum_work_source 19/19, g3_assembled 9/9, block_producer 9/9, embedded_gbt 9/9, coinbase_muldiv, donation_combined, get_work, work_source, node_coin_state, rpc_request all green; --selftest OK.
  • Live testnet (192.168.86.52): fixed binary's stratum-served coinbase accepted by dashd proposal mode (verdict null).
  • Wrong-net now refuses at connect: Chain mismatch: dashd reports chain='test' but c2pool-dash is in MAINNET mode...
  • Reward-safe gate PROVEN LIVE: fixed builder X11-mined and submitted a real testnet block — submitblock ACCEPTED, tip advanced to height 1517418 with our hash 0000007a8b36efe807cbc0ea37172d3267b93978a375c1c247045bb009efaa5b; on-chain coinbase carries the platform burn (0.66966830), the masternode payee yVXDAM73Tg6A44Bm3qduXsMCYxzuqBCT48 (1.11611384), donation, OP_RETURN ref slot, and the full CbTx payload. (Two prior wins during the run were rejected bad-chainlock — natural stale-tip races while CPU-mining, not payee failures.)

…-cb-payee won-block reject)

Root cause (hex-confirmed live against testnet dashd 2026-07-19, GBT
proposal mode): compute_dash_payouts silently 'continue'd on any GBT
payment whose payee string failed decode_payee_script, and
normalize_payment discarded dashd's GBT-provided raw scriptPubKey bytes
whenever a non-empty base58 payee was present -- so there was no
byte-faithful fallback. The built coinbase then LACKED the masternode
payee output (a consensus-REQUIRED output), and dashd rejected every won
block with bad-cb-payee: silent, deterministic reward loss.

The deterministic field trigger: a net-mode/daemon-chain mismatch
(mainnet-mode binary against a testnet dashd applies address_version=76
to a 'y...' testnet payee -> base58 decode fails -> output dropped).
NodeRPC::check() previously passed that mismatch silently.

Falsified alternates (evidence): the fresh-template stratum coinbase on
the right net is dashd-valid (proposal verdict null, twice, and a live
won block ACCEPTED on testnet -- height 1517418,
0000007a8b36efe807cbc0ea37172d3267b93978a375c1c247045bb009efaa5b, with
the masternode payee + platform burn + CbTx payload on-chain); template
+ coinbase + tx set are frozen as ONE atomic snapshot per job, so
intra-height GBT drift cannot desync payee vs body; tip-move staleness
produces prev-based rejects (observed live: bad-chainlock), never
bad-cb-payee.

Fix (fenced to src/impl/dash/ + its tests):
* coin/rpc_data.hpp normalize_payment: ALWAYS preserve the raw GBT
  scriptPubKey bytes in PackedPayment.script alongside the payee string
  (payee-string semantics unchanged -- share-wire compatible; the
  sharechain PackedPayment is a distinct struct).
* coinbase_builder.hpp compute_dash_payouts: (a) payee string decode as
  before; (b) on decode failure fall back to the GBT script bytes
  VERBATIM with a LOUD warning; (c) if neither is usable, THROW --
  refuse to build a doomed coinbase (stratum serves no job and screams)
  instead of silently dropping a consensus-required output.
* coin/rpc.cpp NodeRPC::check(): chain-identity guard -- mainnet mode
  requires chain=='main'; --testnet rejects chain=='main' (accepts
  test/regtest/devnet for the tuned-net harness posture). Wrong-net now
  fails LOUDLY at connect instead of mining doomed work.
* coin/embedded_gbt.hpp: embedded arm also carries the raw scriptPayout
  bytes for the same byte-faithful fallback.

KATs (test_dash_cb_payee, existing allowlisted target; +
test_dash_coinbase_parity updated to the never-drop rule):
* normalize_payment preserves the raw script alongside a base58 payee;
* right-net coinbase contains burn + masternode payee (exact GBT
  script+amount, real captured testnet GBT entries);
* wrong-net params fall back to the GBT script verbatim (the pre-fix
  regression case: masternode output vanished);
* undecodable payee without script bytes throws (never drops);
* GOLDEN: byte-for-byte full coinbase for the real height-1517409
  testnet template, the exact serialization dashd accepted live via
  getblocktemplate proposal mode.

Verification: test_dash_cb_payee 12/12, test_dash_coinbase_parity 11/11,
test_dash_stratum_work_source 19/19, g3_assembled/block_producer/
embedded_gbt/coinbase_muldiv/donation_combined/get_work/work_source/
node_coin_state/rpc_request all green; --selftest OK; live testnet:
stratum-served coinbase proposal-accepted, won block submitblock
ACCEPTED, tip advanced with our hash.
frstrtr added a commit that referenced this pull request Jul 19, 2026
…live single-shot fetch (#748)

The Web-static verify gate fetched Chrome-for-Testing from Google's CDN
on every self-hosted run via a single @puppeteer/browsers install. A
transient outbound blip (ENETUNREACH/ETIMEDOUT, run 29675949544)
false-red the whole gate on PRs that never touch web/ (e.g. dash-fenced
#746/#747). Reuse an already-unpacked Chrome under ~/.cache/puppeteer
(warm path = no network) and bound-retry the cold fetch (3x, backoff).

Co-authored-by: ci-steward <frstrtr@users.noreply.github.com>
@frstrtr

frstrtr commented Jul 20, 2026

Copy link
Copy Markdown
Owner Author

Closing as superseded. The DASH won-block bad-cb-payee reward-safety issue was fixed and shipped via #751 (bind job to GBT snapshot + reconnect invalidation + submit-time payee guard), merged to master and released as v0.2.3.2 — and now proven in production (block 2507753 accepted by dashd with the correct masternode payee). This earlier approach is no longer needed.

@frstrtr frstrtr closed this Jul 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