dash: emit the canonical p2pool coinbase marker so explorers attribute our blocks (+ wire --coinbase-text) - #901
Merged
Conversation
Block explorers attribute blocks to a pool by coinbase text.
chainz.cryptoid.info/dash/extraction.dws?30.htm registers this pool as
"P2Pool-DASH" and has no knowledge of the string "c2pool", so the blocks
c2pool won for the p2pool-dash sharechain (2511241, 2511303) were credited
to nobody. c2pool was writing scriptSig `03c751266332706f6f6c` = BIP34
height push + bare "c2pool".
DASH now defaults to the oracle COINBASEEXT payload plus an implementation
tag:
mainnet 03c75126 2f5032506f6f6c2d444153482f6332706f6f6c2f
= push3 height || "/P2Pool-DASH/c2pool/" (24 bytes)
testnet 03f07e0e 2f5032506f6f6c2d74444153482f6332706f6f6c2f
= push3 height || "/P2Pool-tDASH/c2pool/" (25 bytes)
Sourced from the coin SSOT (config_pool.hpp COINBASEEXT_HEX, transcribed
verbatim from p2pool-dash networks/dash.py:11 / dash_testnet.py:11 with the
push opcode; coinbaseext_text() strips that opcode so the emitted value is
plain text an operator can override).
--coinbase-text was a documented, implemented feature (README "Coinbase
structure", src/core/coinbase_builder.hpp) that main_ltc.cpp wires and
main_dash.cpp never did: it was absent from the DASH binary's --help and
unusable on that lane. It is now parsed, bounded by
c2pool::MAX_OPERATOR_TEXT_SOLO (64 B; DASH has no merged-mining commitment
sharing the budget), resolved once before any coinbase is built, and echoed
at startup.
The override lives on the coin SSOT rather than being threaded through each
call because DASH builds the scriptSig in TWO places -- coinbase::build()
for the stratum job / block coinbase and mint::build_producer_job() for
share_data['coinbase']. A one-byte divergence between them would make the
node self-reject its own shares. Both now go through
dash::coinbase::build_coinbase_scriptsig(), and a KAT pins their equality.
Not consensus-bearing. COINBASEEXT appears nowhere in the oracle's data.py;
the scriptSig travels as share_info.share_data.coinbase (VarStr, 2..100 B)
and Share.check() re-derives the gentx from the RECEIVED share's own
coinbase field. Framing is preserved and tested: the BIP34 height push stays
first and cannot be reached by the 100-byte cap (worst-case 5-byte push +
64-byte operator max), and the stratum extranonce2 slot does not move --
DASH's extranonce2 is the 8-byte nonce64 in the OP_RETURN, and coinb1/coinb2
split at an offset derived from the tx tail.
Other lanes are unchanged and reported, not touched: LTC writes /c2pool/,
BTC /c2pool-btc/, BCH /c2pool-bch/, DGB writes an empty scriptSig on the
stratum path. Only p2pool-dash defines a COINBASEEXT among the oracles
available here.
README updated: the config table no longer claims a single /c2pool/ default,
and a per-lane operator-text table plus the attribution rationale were added.
Tests fold into the existing allowlisted test_dash_coinbase_parity target
(9 new cases, no new add_executable): mainnet + testnet scriptSig KATs, the
pre-change bytes as a negative control, SSOT/oracle-constant pins, the
--coinbase-text override, the 100-byte bound with BIP34 intact, end-to-end
bytes inside the serialized coinbase with the coinb1/coinb2 split checked,
and mint-vs-block scriptSig equality.
frstrtr
marked this pull request as ready for review
July 26, 2026 17:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Block explorers attribute blocks to a pool by coinbase text.
chainz.cryptoid.info/dash/extraction.dws?30.htm
registers this pool as
P2Pool-DASHand has no knowledge of the stringc2pool.c2pool was writing scriptSig
03c751266332706f6f6c= BIP34 height push + barec2pool(verified on our own live mainnet block 2511303). We are ~87% of the p2pool-dash pool's
hashrate and the two blocks won on 2026-07-24 (2511241, 2511303) are credited to nobody.
Second, smaller problem found on the way:
--coinbase-textis an existing, documentedfeature (
README.md"Coinbase structure",src/core/coinbase_builder.hpp) thatmain_ltc.cppwires andmain_dash.cppnever did — it did not appear in the DASHbinary's
--helpat all, and the README claimed a/c2pool/default that the DASH lanedid not even use.
What this changes (DASH only)
Default coinbase text, sourced from the coin SSOT:
03c751262f5032506f6f6c2d444153482f6332706f6f6c2f/P2Pool-DASH/c2pool/(24 B)03f07e0e2f5032506f6f6c2d74444153482f6332706f6f6c2f/P2Pool-tDASH/c2pool/(25 B)src/impl/dash/config_pool.hppnow carriesCOINBASEEXT_HEX/TESTNET_COINBASEEXT_HEXtranscribed verbatim from the oracle(
p2pool-dash networks/dash.py:11,dash_testnet.py:11), push opcode included, so theSSOT pins the oracle constant exactly.
coinbaseext_text()strips that opcode; what weemit is plain text, because the slot is operator-overridable and a bare
0x0Dcontrolbyte would vanish the moment anyone set
--coinbase-text. Attribution is unaffected —explorers key on the ASCII, and
/P2Pool-DASH/is byte-identical to what a canonicalp2pool-dash node renders.
--coinbase-textwired intomain_dash.cpp, mirroring the LTC pattern: parsed,bounded by
c2pool::MAX_OPERATOR_TEXT_SOLO(64 B — DASH has no merged-mining commitmentsharing the 100-byte budget), resolved once before any coinbase is built, echoed at
startup, and present in
--help.One scriptSig SSOT. DASH built the scriptSig in two places —
coinbase::build()(stratum job / block coinbase) and
mint::build_producer_job()(share_data['coinbase']).A one-byte divergence between them makes the node self-reject its own shares. Both now
call
dash::coinbase::build_coinbase_scriptsig(), and a KAT pins their equality. This isalso why the override lives on the SSOT rather than being threaded per-call.
Consensus safety
Verified before touching bytes, not assumed:
share_info.share_data.coinbase(VarStrType,2 <= len <= 100,data.py:315).Share.check()callsgenerate_transaction(tracker, SELF.share_info['share_data'], ...)— it feeds the received share's own coinbase field back in, so the re-derived gentx
contains whatever bytes the sender wrote.
COINBASEEXTappears nowhere in theoracle's
data.py; grep puts it only innetworks/*.pyand the stratum assembly atwork.py:339. c2pool's mirrorshare_check.hpp::generate_share_transactionlikewisedoes
tx << share.m_coinbase.share_info != self.share_infois satisfied for the same reason —share_dataispassed through, not rebuilt.
prefix_to_hash_link()takes a SHA-256 midstate overthe gentx prefix plus
extra_data= trailing partial 64-byte block minus theconst_ending;check_hash_link()reconstructs it fromhash_link.length % 64. Bothsides derive the split from the actual prefix length, and dash's
hash_link_typecarries
extra_dataas aVarStrprecisely so a variable-length prefix works (theupstream
assert not extra_datais commented out in the oracle for this reason).c2pool— matching no networkconstant — and canonical p2pool-dash peers accept its shares with zero bans across
thousands of shares and two dashd-confirmed mainnet blocks.
Framing (not consensus, but breaking it breaks mining)
ContextualCheckBlockcompares the scriptSig prefix toCScript() << nHeight.Worst-case push is 5 bytes;
--coinbase-textis capped at 64; 5 + 64 = 69 < 100.Tested with a 400-byte text: result is exactly 100 bytes with the height push intact.
nonce64inside the OP_RETURN output, not the scriptSig.
coinb1/coinb2split atnonce64_offset, whichbuild()derives from the tx tail(
total - payload - 4 - 8). A longer scriptSig lengthenscoinb1only; the advertisedextranonce2_size(8) andcoinb2are unchanged. Asserted in the KAT.Other lanes — reported, deliberately not changed
/c2pool/src/core/web_server.cpp(756, 1103) + twice inmerged/merged_mining.cpp/c2pool-btc/src/impl/btc/stratum/work_source.cpp:430/c2pool-bch/src/impl/bch/stratum/work_source.cpp:415main_dgb.cpp:861-868never setsain.coinbase_scriptc2pool->/P2Pool-DASH/c2pool/Adjacent findings, filed here rather than fixed:
BIP34 height push. That looks like a real bug, not a cosmetic gap.
block_json.hpp:99andbch/coinbase_commitment.hpp:198both look for/c2pool/).p2pool-dashdefines aCOINBASEEXTamong the oracles available on this box;jtoomim's
networks/bitcoin.pyhas none (checked directly). No p2pool-ltc / -dgb / -bchcheckout exists locally, so their canonical markers are unverified.
Tests
Folded into the existing allowlisted
test_dash_coinbase_paritytarget — no newadd_executable. 9 new cases; per #895, each was confirmed to actually execute (gtest[ RUN ]/[ OK ]lines, not a bare ctest tick):Negative control run: flipping one nibble of
COINBASEEXT_HEX(
...444153482F->...444153483F) and rebuilding fails 5 of the 9 with the expectedbyte diff (
"...444153483f..."vs"...444153482f...") andfind(...) == nposon themarker — so the KATs discriminate rather than tautologically pass. Reverted.
Regression sweep: all 41
test_dash_*binaries built and run — 0 failures.test_dash_stratum_work_source.cpp:389had hardcoded"c2pool"in itsstratum-vs-SSOT byte-identity comparison and was updated to read the SSOT (pinning the
literal there would break the test the moment an operator sets
--coinbase-text, which isthe whole point of the flag).
--helpon the built binary:Bound enforced:
--coinbase-textwith 65 bytes prints[args] --coinbase-text too long: 65 bytes (max 64)and exits 2; 64 bytes is accepted.Could not verify / deliberately out of scope
main_dash.cppmaps--regtestonto
testnet = true— DASH has no third sharechain profile — so a separate/P2Pool-rDASH/would need a three-way network selector across the whole lane. Noted inthe SSOT comment. Also: the oracle's own
dash_regtest.py:11constant0F2F5032506F6F6C2D724441534828is malformed — it declares push-15 but carries 14bytes, and ends
0x28(where/was clearly meant (/P2Pool-rDASH(). Notreproduced. (
dash_custom_example.py:25has the same class of bug: push-13 declared,10 bytes present.)
"Coming soon... :)" for its block table, so I could confirm the pool is registered as
P2Pool-DASHbut not observe its matching rule directly. The argument for/P2Pool-DASH/is that it is byte-identical to what canonical p2pool-dash nodes write.those chains.
pool.yaml coinbase_textis not wired on DASH —main_dash.cpphas no YAML configloader at all (0 hits for
cfg[/Fileconfig). CLI only. The README table row keeps theYAML key because it is accurate for LTC.
evidence is KATs plus the structural argument above.
Conflict surface
main_dash.cpphelp-text block (~line 245) overlaps dash: wire the embedded arm so --run can actually take it (#738) #891's@@ -238,6 +239,15 @@hunk —trivial textual conflict, adjacent additions.
test/test_dash_stratum_work_source.cppedit is at ~389; dash: wire the embedded arm so --run can actually take it (#738) #891 appends at 1646+. No overlap.src/impl/dash/stratum/work_source.cppedit is at 695; coins: mint the block-winning share onto the sharechain (dash/btc/bch/dgb) #888's dash hunks start at 816(
mining_submit). No overlap.DRAFT — do not merge.