Repository navigation
sv2: ProposeTemplate — validate externally proposed templates (sv2-spec#239) - #137
average-gary wants to merge 4 commits into
Conversation
Bitcoin Core v32 looks up mempool transactions by wtxid (mining.capnp @9, bitcoin/bitcoin#34020). ProposeTemplate needs it to resolve a declared wtxid list, and submitBlock() to broadcast the resulting block. Neither exists on v31, so the mock v31 node now throws for submitBlock() too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
That's an interesting idea. I'll study the spec proposal and your code here later, but I'm definitely open to adding support. I'm also fine with an initial version that doesn't use OTOH implementing a mirror mempool does sound tedious. If you think anything is missing from bitcoin/bitcoin#35671, please leave a comment there as well. |
b9b4898 to
09b1483
Compare
|
Thanks. On the mirror mempool: this version doesn't need one. It resolves the declared wtxids with Fable 5.1 assisted but hooman reviewed. |
ProposeTemplate (0x77) carries the DeclareMiningJob fields a node needs to assemble a candidate block: version, the coinbase split around the extranonce and the wtxid list. The replies are Success (0x79) and Error (0x7a). Transactions the node lacks are exchanged with the Job Declaration Protocol's layouts under Template Distribution Protocol numbers: ProvideMissingTransactions (0x78) asks for them by position and ProvideMissingTransactions.Success (0x7b) supplies them. Coinbase() rebuilds the coinbase with a zero extranonce of the length implied by the prefix, which no check short of the merkle root depends on, and refuses a scriptSig length consensus would reject. See stratum-mining/sv2-spec#239 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sages A client that intends to send ProposeTemplate sets bit 0 of SetupConnection.flags. Start() now takes the flags the server accepts, and SetupConnection.Success echoes the accepted ones. ProposeTemplate or ProvideMissingTransactions.Success on a connection that did not negotiate the flag is a protocol violation and disconnects, like any other out-of-order setup message. Such a client has no reason to send CoinbaseOutputConstraints, so RequestTransactionData no longer requires it when the flag was negotiated. The template provider does not advertise the flag yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The checks that need no node call run on the networking thread when ProposeTemplate arrives: list bounds, duplicate wtxids, the coinbase prefix and suffix (bad-cb-decode), a request_id that is still in use (duplicate-request-id) and the per-client in-flight bound. The request is then queued for a worker thread, so that the node round trips don't hold up SubmitSolution and the other clients' messages. The worker answers job-validation-unavailable during initial block download, looks up the wtxids in the node's mempool (getTransactionsByWitnessID) and reports what is missing by position in ProvideMissingTransactions. The request stays pending, for 30 seconds and at most 8 per client, until ProvideMissingTransactions.Success supplies exactly those transactions (else bad-missing-tx; an unknown or expired id gets unknown-request-id). With every transaction known, the worker assembles the block with the client's coinbase and version on the node's tip and runs checkBlock() without the proof-of-work and merkle root checks. The tip's hash, nBits and nTime come from an empty template, the only way the mining interface exposes them. A validated block is cached under a template_id like our own templates, wrapped in a BlockTemplate whose submitSolution() calls submitBlock(), so SubmitSolution, RequestTransactionData and pruning are unchanged. The tip it was checked on becomes the best known prevhash, so a client that receives no templates does not see its proposal pruned right away. The flag is only advertised when the node is Bitcoin Core v32 or later, since v31 has neither the wtxid lookup nor submitBlock(). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
09b1483 to
9042473
Compare
Draft, because the wire format is still being discussed in sv2-spec discussion #239 (RFC: a TDP message so a JDS can validate a Custom Job through a Template Provider). This is the sv2-tp side of that proposal, opened so the discussion has a concrete Core-IPC implementation to look at. It now follows the shape the thread converged on (2026-10-08) and will track whatever moves from here.
What it does
A TDP client that sets
SetupConnection.flagsbit 0 (REQUIRES_JOB_VALIDATION, echoed inSetupConnection.Success) can sendProposeTemplate(0x77) carrying theDeclareMiningJobsubset:version,coinbase_tx_prefix,coinbase_tx_suffix,wtxid_list,excess_data. There is notransaction_list; missing transactions use the JDP 6.4.7/6.4.8 layouts under TDP numbers. The TP:duplicate-wtxid), coinbase prefix/suffix decode incl. a consensus-valid scriptSig length (bad-cb-decode), arequest_idstill in use by this client (duplicate-request-id), and the per-client in-flight bound;ThreadSv2ProposalHandler, one per TP, FIFO across clients). The worker answersjob-validation-unavailablewhileisInitialBlockDownload()is true, before any lookup; otherwise resolves the wtxids withgetTransactionsByWitnessID(Core v32) and, for anything unknown, repliesProvideMissingTransactions(0x78) with 0-indexed positions and keeps the request pending per client, keyed byrequest_id: at mostMAX_PENDING_PROPOSALS(8, oldest evicted) forPENDING_PROPOSAL_TIMEOUT(30 s);ProvideMissingTransactions.Success(0x7b) resumes a pending request. Each supplied transaction must hash to a wtxid at one of the requested positions and all requested positions must be covered (bad-missing-tx), checked before any node call; an unknown or expiredrequest_idgetsunknown-request-id;scriptSiglength from the prefix minus bytes present; assumes the extranonce is the scriptSig tail), assembles aCBlockon the tip, and callscheckBlock(check_merkle_root=false, check_pow=false);BlockTemplatein the existing template cache (same tip-change prune as our own templates) and repliesProposeTemplate.Success(0x79) withtemplate_id, the tipprev_hashandfees, so a normalSubmitSolutionlater submits it viasubmitBlockwith the miner's coinbase; otherwiseProposeTemplate.Error(0x7a) with Core's reject reason.Replies from the worker are queued under the same locks
SendWork/SubmitSolutionuse. Each client may haveMAX_PROPOSALS_IN_FLIGHT(4) proposals queued or being validated; further ones getjob-validation-unavailable. On a v31 node the flag is refused (no wtxid lookup, nosubmitBlock).Commits
ipc: add getTransactionsByWitnessID to the mining interface— capnp@9as in Core v32, the null-transaction decode hook from Core'scommon-types.h, andMockMiningsupport.sv2: add ProposeTemplate messages— structs, serializers (incl. 0x78/0x7b),Coinbase()assembly, message tests.sv2: negotiate REQUIRES_JOB_VALIDATION and forward job validation messages—Sv2Connmansupported flags, Success echoes accepted flags, dispatch of 0x77 and 0x7b;RequestTransactionDatano longer requiresCoinbaseOutputConstraintson such a connection.sv2: validate proposed templates and track them for SubmitSolution— the synchronous prechecks, the worker thread, the pending map, theBlockTemplateadapter and the tests below.Things Core IPC can't provide today, and how the patch copes
checkBlockreturns only reason/debug/result, and theTxCollection.makeTemplatetemplates in mining: add TxCollection to bandwidth-efficiently validate external block templates bitcoin/bitcoin#35671 throw ongetTxFees.ProposeTemplate.Success.feesis therefore sent as 0 (unknown) until Core exposes it;bad-cb-amountalready bounds the coinbase at subsidy + fees.createNewBlock(useMempool=false, cooldown=false)and copieshashPrevBlock/nBits/nTime. That is an extracs_mainround trip per request;TxCollection.makeTemplate(prevhash, coinbase)would remove it, and the adapter is shape-compatible with what it returns.Still open in #239
.Error, or keeps answering withProposeTemplate.Erroras here (unknown-request-id,bad-missing-tx).job-validation-unavailablehere) and the pending-map overflow rule (oldest evicted here).Not done
The fuzz target and the functional scenario. Happy to add them before this leaves draft, or to park it until #35671 lands if you'd rather build on
TxCollection.Testing
Builds and
ctestpasses (7/7 suites, 10 new cases) on macOS with Homebrew cmake/capnp/boost. New coverage: message round-trips incl. 0x7b; missing →ProvideMissingTransactions→.Success→ProposeTemplate.Success→SubmitSolutionwith an extranonce that differs from the placeholder (submitted block carries the miner's coinbase and a recomputed merkle root);RequestTransactionDataon a proposed id;duplicate-request-id,unknown-request-id, expiry and the 8-entry eviction via mock time;duplicate-wtxid;bad-missing-txfor a wrong transaction, one at an unrequested position and an incomplete set;bad-cb-decodethrough the handler; the IBD gate (no lookup, nocreateNewBlock); prune after a tip change and no premature prune for a client without templates; and a deterministic off-thread test using aMockMininglatch oncheckBlock(RequestTransactionDataanswered while a proposal is blocked in the node; bound, FIFO order andduplicate-request-idfor a queued id). The SRI integration matrix was not run.A native-TP implementation of the same messages lives in rbitcoin: https://github.com/average-gary/rbitcoin/tree/sv2/job-validation
🤖 Generated with Claude Code