Rationale
Verifying block templates (no PoW)
Stratum v2 allows miners to generate their own block template. Pools may wish (or need) to verify these templates. This typically involves comparing mempools, asking miners to providing missing transactions and then reconstructing the proposed block.1 This is not sufficient to ensure a proposed block is actually valid. In some schemes miners could take advantage of incomplete validation2.
The Stratum Reference Implementation (SRI), currently the only Stratum v2 implementation, collects all missing mempool transactions, but does not yet fully verify the block.3 There’s currently no way for it to do so, short of re-implementing Bitcoin Core validation code.
(although SRI could use this PR, the Template Provider role doesn’t need it, so this is not part of #31098)
Verifying weak blocks (reduced PoW)
Stratum v2 decentralises block template generation, but still hinge on a centralised entity to approve these templates.
There used to be a decentralised mining protocol P2Pool4 which relied on (what we would today call) weak blocks. In such a system all participants perform verification and there is no privileged pool operator (that can reject a template).
P2Pool shares form a “sharechain” with each share referencing the previous share’s hash. Each share contains a standard Bitcoin block header, some P2Pool-specific data that is used to compute the generation transaction (total subsidy, payout script of this share, a nonce, the previous share’s hash, and the current target for shares), and a Merkle branch linking that generation transaction to the block header’s Merkle hash.
IIUC only the header and coinbase of these shares / weak blocks were distributed amongst all pool participants and verified. This was sufficient at the time because fees were negligible, so payouts could simply be distributed based on work.
However, if P2Pool were to be resurrected today5, it would need to account for fees in a similar manner as PPLNS2 above. In that case the share chain should include the full weak blocks6. The client software would need a way to verify those weak blocks.
A similar argument applies to Braidpool.
Enter checkblock
and its IPC counterpart checkBlock()
Given a serialised block, it performs the same checks as submitblock
, but without updating the block index, chainstate, etc. The proof-of-work check can be bypassed entirely, for the first use of verifying block templates. Alternatively a custom target
can be provided for the use case of weak blocks.
When called with check_pow=false
the checkblock
RPC is (almost) the same as getblocktemplate
in proposal
mode, and indeed they both call checkBlock()
on the Mining interface.
Implementation details:
Builds on:
The ChainstateManager::CheckNewBlock()
implementation is based on ProcessNewBlock()
, but refactored in a number of ways (see https://github.com/Sjors/bitcoin/pull/75 for previous incarnations):
- it drops the use of a
StateCatcher
- it requires building (directly) on the tip
- if calls
CheckWeakProofOfWork
before the other checks - instead of
AcceptBlock()
it performs a (documented) subset of that function - it creates a throwaway
CCoinsViewCache
on top of the tip on which it connects the block (needed for full transaction verification) - it updates validation caches (and does not delete them). This should make subsequent validation (of a real block) faster if it contains overlapping transactions that are not in our mempool.
This CheckNewBlock()
method is then called from the Mining interface createBlock()
method. This in turn is called from the RPC checkblock
code, which is a simplified clone of submitblock
.
This method ended up being very similar to TestBlockValidity
, so the last commit replaces the latter entirely (renaming CheckNewBlock
).
This PR contains a simple unit test and a more elaborate functional test.
Potential followups:
- if the block contains “better” transactions, (optionally) add them to our mempool 5
- keep track of non-standard transactions7
- allow rollback (one or two blocks) for pools to verify stale work / uncles
-
https://github.com/stratum-mining/sv2-spec/blob/main/06-Job-Declaration-Protocol.md ↩︎
-
https://delvingbitcoin.org/t/pplns-with-job-declaration/1099/45?u=sjors ↩︎ ↩︎
-
https://github.com/stratum-mining/stratum/blob/v1.1.0/roles/jd-server/src/lib/job_declarator/message_handler.rs#L196 ↩︎
-
this improves compact block relay once the real block comes in ↩︎ ↩︎
-
The share chain client software could use compact block encoding to reduce its orphan / uncle rate. To make that easier, we could provide a
reconstructblock
RPC (reconstructBlock
IPC) that takes the header and short ids, fills in aCBlock
from the mempool (and jail) and returns a list of missing items. The share chain client then goes and fetches missing items and calls the method again. It then passes the complete block tocheckblock
. This avoids the need for others to fully implement compact block relay. Note that even if we implemented p2p weak block relay, it would not be useful for share chain clients, because they need to relay additional pool-specific data. ↩︎ -
https://delvingbitcoin.org/t/second-look-at-weak-blocks/805 ↩︎