Widen extranonce2 to 8 bytes and enforce the width on submit - #40
Open
rsantacroce wants to merge 1 commit into
Open
Widen extranonce2 to 8 bytes and enforce the width on submit#40rsantacroce wants to merge 1 commit into
rsantacroce wants to merge 1 commit into
Conversation
extranonce2 was 4 bytes, hardcoded in two places: the literal handed
back as the third element of the mining.subscribe result, and the
/*en2*/ 4 passed into stratum_job_new. Both now come from
STRATUM_EXTRANONCE2_SIZE in stratum.h, and the value is 8.
The reason is not search space. At 4 bytes a single connection already
gets 2^80 headers per job once nonce and version rolling are counted,
which no hashrate exhausts and which jobs rotate out from under anyway.
The reason is subdivision: a stratum proxy in front of the pool splits
the extranonce2 it is given into a downstream-miner id plus the
downstream miner's own extranonce2. At 4 bytes a proxy spending 3 on
addressing leaves its miners one byte, and some firmware will not run
that narrow. At 8 it can spend 3 and still hand down the conventional 4.
Widening it exposed a hole that was already there. handle_submit took
whatever extranonce2 length the miner sent and spliced it into a cb1
whose scriptSig length varint had been fixed at render time from
en1_size + en2_size. A mismatched width produces a coinbase whose
declared scriptSig length disagrees with the bytes following it — an
invalid transaction whose header still hashes like a valid one, so the
share is credited and only a found block reveals the problem, by being
rejected. Before this change that took a misbehaving miner; after it,
any miner that ignores mining.subscribe and assumes the classic 4 hits
it. So submit now rejects on length ("wrong extranonce2 size") instead
of silently accepting work that could never become a block.
Also add the 100-byte scriptSig cap check to coinbase_build_split. The
coinbasetxn path has always had it; the from-scratch path computed
script_sig_len and never checked it. Not reachable through config today
(a 5-byte height push plus a 76-byte tag plus 12 extranonce bytes is
93), but it is the guard that keeps a future widening from emitting a
coinbase that only fails at the network.
Tests cover the advertised size, rejection of a too-narrow and a
too-wide extranonce2, acceptance at the exact width, agreement between
the scriptSig varint and the assembled coinbase at the production
width, that a wrong-width splice does not parse as a valid coinbase,
and the cap check. The regtest walkthrough against the enforcer was not
run — no local bitcoind/enforcer.
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.
extranonce2was 4 bytes, hardcoded in two places: the literal handed back as the third element of themining.subscriberesult, and the/*en2*/ 4passed intostratum_job_new. Both now come fromSTRATUM_EXTRANONCE2_SIZEinstratum.h, and the value is 8.Why
Not search space. At 4 bytes a single connection already gets 2^80 headers per job once nonce and version rolling are counted — a 1 EH/s farm would need about two weeks to exhaust one job, and jobs rotate every template. Capacity was never the constraint.
The reason is subdivision. A stratum proxy in front of the pool splits the
extranonce2it is given into a downstream-miner id (high bytes) plus the downstream miner's ownextranonce2(low bytes). At 4 bytes a proxy spending 3 on addressing leaves its miners a single byte — 256 values — and some firmware refuses to run that narrow. At 8 it can spend 3 and still hand down the conventional 4, so downstream miners see an ordinary pool.8 rather than 7 because it is the same cost, satisfies any "at least 7" requirement, and 4/8 is the split proxies and firmware already expect. It is one constant if that turns out to be wrong.
The hole this exposed
handle_submittook whateverextranonce2length the miner sent and spliced it into acb1whose scriptSig length varint had been fixed at render time fromen1_size + en2_size. A mismatched width produces a coinbase whose declared scriptSig length disagrees with the bytes following it — an invalid transaction whose header still hashes like a valid one. The share gets credited; the problem only surfaces when a found block is rejected by the network.Before this change that took a misbehaving miner. After it, any miner that ignores
mining.subscribeand assumes the classic 4 hits it. So submit now rejects on length (wrong extranonce2 size), with anon_rejectcall so it lands in the rejects ledger and a log line naming the mismatch, rather than silently accepting work that could never become a block.Also
The 100-byte scriptSig cap check is added to
coinbase_build_split. Thecoinbasetxnpath has always had it; the from-scratch path computedscript_sig_lenand never checked it. To be precise about what this is: not reachable through config today — a 5-byte height push plus the 76-byte maximum tag plus 12 extranonce bytes is 93. It is the guard that keeps a future widening from emitting a coinbase that only fails at the network.Docs updated:
NONCE_AND_SHARES.md,README.md,docs/simplepool.html. While inNONCE_AND_SHARES.mdI also replaced the staleseq ^ now_ms()extranonce1 snippet — it still documented the collision behavior that was removed, in the same section that explains the extranonce layout.Testing
Clean build under
-Wall -Wextra -Werror -Wpedantic. Full suite green:test_stratum75 passed,test_share91,test_bitcoind56,test_coinbase/test_store/test_broadcast/test_thunderall passing.Six new tests: the advertised size, a too-narrow
extranonce2rejected, a too-wide one rejected, the exact width still accepted, the scriptSig varint agreeing with the assembled coinbase at production width, a wrong-width splice failing to parse as a valid coinbase, and the cap check firing.Test fixtures now build jobs from the constants rather than a hardcoded 4, so a future width change that misses a call site fails in the tests instead of on a found block.
Not verified: the regtest walkthrough against the enforcer — no local
bitcoind/bip300301_enforcer. Thecoinbasetxnpath is the live one, since templates come from the enforcer, and it is covered by unit tests but not end to end. Worth running before this merges.scripts/regtest/cpuminer.jsneeds no changes — it already sizes its sweep offen2_sizefrom the subscribe reply, so it exercises the new width as-is.Deploying this
Miners renegotiate the width on
mining.subscribe, so a restart is enough for well-behaved clients. Anything that hardcodes 4 will start gettingwrong extranonce2 sizerejects instead of silently mining invalid work — visible in the rejects ledger and the logs, which is the point, but worth watching the reject rate right after the first restart.