mining: add precious option to IPC block submission #35300

pull w0xlt wants to merge 7 commits into bitcoin:master from w0xlt:ipc-submit-block-precious changing 19 files +651 −101
  1. w0xlt commented at 3:23 AM on May 16, 2026: contributor

    This PR adds a precious flag to the IPC Mining submitBlock and submitSolution methods, as suggested in #34644 (review). Built on top #34644.

    With this change, IPC mining clients can submit and prefer a block in one IPC call, instead of submitting it over IPC and then using the preciousblock RPC separately.

    By default, same-work side blocks keep the existing behavior: submitBlock reports duplicate/inconclusive cases as failure with a reason, while submitSolution continues to return true when ProcessNewBlock accepts or already knows the block.

    Includes unit and IPC functional coverage for default behavior, precious reorgs, active-tip no-op submissions, and invalid-block rejection.

  2. DrahtBot added the label Mining on May 16, 2026
  3. DrahtBot commented at 3:23 AM on May 16, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Concept ACK Sjors, pablomartin4btc, ViniciusCestarii, johnnyasantoss

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36097 (mining: replace interrupt methods with cancellation arguments by xyzconstant)
    • #36074 (scripted-diff: [test] Add util/check.h includes for assertions by maflcko)
    • #35675 (mining: add block template manager by ismaelsadeeq)
    • #35671 (mining: add TxCollection to bandwidth-efficiently validate external block templates by Sjors)
    • #35646 (RFC: Separate out runtime errors from BlockValidationState using util::Expected by yuvicc)
    • #35581 (node: add block template manager and track waitNext fee inflow by ismaelsadeeq)
    • #33922 (mining: add getMemoryLoad() and track template non-mempool memory footprint by Sjors)
    • #29700 (kernel, refactor: return error status on all fatal errors by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  4. w0xlt marked this as a draft on May 16, 2026
  5. w0xlt force-pushed on May 16, 2026
  6. DrahtBot added the label CI failed on May 16, 2026
  7. DrahtBot commented at 4:02 AM on May 16, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task test ancestor commits: https://github.com/bitcoin/bitcoin/actions/runs/25951528963/job/76290253269</sub> <sub>LLM reason (✨ experimental): CI failed due to a C++ build error: src/node/miner.cpp would not compile (clang: passing CBlockIndex* where const CBlockIndex& is required in chainman.ActiveChain().Contains).</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  8. DrahtBot removed the label CI failed on May 16, 2026
  9. Sjors commented at 9:39 AM on May 16, 2026: member

    Maybe do this after #34672?

  10. DrahtBot added the label Needs rebase on May 17, 2026
  11. w0xlt force-pushed on Jul 24, 2026
  12. DrahtBot removed the label Needs rebase on Jul 24, 2026
  13. in src/test/miner_tests.cpp:1029 in a1f8966316
    1024 | +    BOOST_REQUIRE_EQUAL(reason, "");
    1025 | +    BOOST_REQUIRE_EQUAL(debug, "");
    1026 | +    BOOST_REQUIRE_EQUAL(ActiveTipHash(m_node), side_block.GetHash());
    1027 | +
    1028 | +    // Re-submitting the active tip with precious=true is a no-op reorg, but
    1029 | +    // the block is validated/connected so submitBlock should report success.
    


    Sjors commented at 12:47 PM on July 24, 2026:

    In a1f8966316dcd0807d0ee612327d4a822c89177f test: cover precious mining submissions: mmm, I don't think precious should change the behavior if the block is duplicate, so just return duplicate?

    Similarly, if we already had it, but it wasn't the tip, and we then switch the tip over, duplicate still seems like the correct response.


    w0xlt commented at 8:19 PM on September 22, 2026:

    Done. Thanks.

  14. in src/test/miner_tests.cpp:1040 in a1f8966316
    1035 | +    BOOST_REQUIRE_EQUAL(ActiveTipHash(m_node), side_block.GetHash());
    1036 | +
    1037 | +    // Submitting a brand-new same-work block with precious=true on the first
    1038 | +    // try (no prior precious=false store) should still reorg to it. This
    1039 | +    // exercises the path where AcceptBlock stores a fresh block and
    1040 | +    // PreciousBlock immediately promotes it.
    


    Sjors commented at 12:48 PM on July 24, 2026:

    In a1f8966316dcd0807d0ee612327d4a822c89177f test: cover precious mining submissions: this seems to be the main use case, so maybe cover it at the start of the test?

    In general this test is massive, so maybe split it?


    w0xlt commented at 8:19 PM on September 22, 2026:

    Done. Thanks.

  15. in src/test/miner_tests.cpp:1145 in a1f8966316
    1140 | +    BOOST_REQUIRE_EQUAL(reason, "bad-txnmrklroot");
    1141 | +    BOOST_REQUIRE_EQUAL(debug, "hashMerkleRoot mismatch");
    1142 | +    BOOST_REQUIRE_EQUAL(ActiveTipHash(m_node), tip_before_invalid);
    1143 | +}
    1144 | +
    1145 | +BOOST_AUTO_TEST_CASE(SubmitSolution_precious)
    


    Sjors commented at 12:50 PM on July 24, 2026:

    In a1f8966316dcd0807d0ee612327d4a822c89177f test: cover precious mining submissions: since we expect more or less the same behavior as SubmitBlock, perhaps it's better to interleave the test? (and then have separate tests for each scenario, rather than for each method)


    w0xlt commented at 8:20 PM on September 22, 2026:

    Done. Thanks.

  16. Sjors commented at 12:59 PM on July 24, 2026: member

    Concept ACK

    There's some game theory miners should contemplate before using this though:

    It's clearly rational to prefer your own block for p2p relay[^0], but it's less obvious that you should mine the next block on it.

    That might be a sunk-cost fallacy. If you saw the other block first, it's a good assumption that the rest of the network also saw it first. If the majority of hash rate is mining on the other side, and if you (the miner, not the pool) can't tip that majority over, it may be a waste of hash power.

    In a Job Declaration scenario, the main criterion for which block to build on should be the side of the fork the pool picked. Unfortunately afaik the sv2 protocol doesn't currently communicate that (cc @plebhash).

    [^0]: which could be achieved with some special handling where we briefly switch tip to validate, broadcast and then switch back to the first-seen tip.

  17. in test/functional/interface_ipc_mining.py:133 in 95f62bce86
     129 | @@ -126,8 +130,29 @@ async def build_candidate_block(self, template, ctx, extra_nonce=b""):
     130 |          block.hashMerkleRoot = block.calc_merkle_root()
     131 |          return block
     132 |  
     133 | -    async def assert_submit_block(self, mining, ctx, block, *, result, reason="", debug=""):
     134 | -        submit = await mining.submitBlock(ctx, block.serialize())
     135 | +    def make_block_variant(self, block, coinbase, extra_nonce):
    


    pablomartin4btc commented at 8:55 PM on July 24, 2026:

    nit: make_block_variant, assert_chain_tip, and assert_submit_block none of them access self — all should be @staticmethod


    w0xlt commented at 8:21 PM on September 22, 2026:

    Done. Thanks.

  18. in src/rpc/blockchain.cpp:1712 in 95f62bce86 outdated
    1708 | @@ -1709,7 +1709,8 @@ static RPCMethod preciousblock()
    1709 |      }
    1710 |  
    1711 |      BlockValidationState state;
    1712 | -    chainman.ActiveChainstate().PreciousBlock(state, pblockindex);
    1713 | +    bool connected{false};
    


    pablomartin4btc commented at 9:05 PM on July 24, 2026:

    nit: connected is silently unused. A [[maybe_unused]] attribute or a brief comment (// RPC callers don't need to distinguish connected vs not) would help the next reader I think...


    w0xlt commented at 8:21 PM on September 22, 2026:

    Done. Thanks.

  19. in src/interfaces/mining.h:78 in 95f62bce86
      73 | @@ -72,9 +74,10 @@ class BlockTemplate
      74 |       *       is only one byte long, so the coinbase scriptSig needs at least
      75 |       *       one additional byte of data to avoid bad-cb-length.
      76 |       *
      77 | -     * @returns true if the block was accepted as a new block
      78 | +     * @returns true if the block was accepted as a new block. With precious,
      79 | +     *          an already-known block may return true if it is connected.
    


    pablomartin4btc commented at 9:29 PM on July 24, 2026:

    nit: perhaps worth clarifying that this includes blocks that are ancestors of the active tip, not just the tip itself, so callers don't assume result=true implies a reorg occurred? (If I understand it correctly)


    w0xlt commented at 8:21 PM on September 22, 2026:

    Done. Thanks.

  20. in src/interfaces/mining.h:187 in 95f62bce86
     187 | +     * @returns           true if the block was accepted. By default, returns
     188 |       *                    false and sets reason if the block is a duplicate or
     189 | -     *                    the validation result is inconclusive.
     190 | +     *                    the validation result is inconclusive. With precious,
     191 | +     *                    an already-known block may return true if it is
     192 | +     *                    validated/connected.
    


    pablomartin4btc commented at 9:29 PM on July 24, 2026:

    nit: same as the above.


    w0xlt commented at 8:21 PM on September 22, 2026:

    Done. Thanks.

  21. pablomartin4btc commented at 9:33 PM on July 24, 2026: member

    Concept ACK

    Adding precious=true to submitBlock and submitSolution, allows IPC mining clients atomically submit and prefer a same-work block in one go instead of a separate preciousblock RPC call.

    The ActivateBestChain_ refactor is a clean prerequisite (so in commit 2 PreciousBlock can call it directly).

    Left some nits (none blocking).

  22. DrahtBot added the label Needs rebase on Aug 14, 2026
  23. w0xlt force-pushed on Sep 22, 2026
  24. w0xlt marked this as ready for review on Sep 22, 2026
  25. w0xlt commented at 8:22 PM on September 22, 2026: contributor

    Rebased. All suggestions addressed. Ready for review.

  26. DrahtBot removed the label Needs rebase on Sep 22, 2026
  27. DrahtBot added the label Needs rebase on Sep 24, 2026
  28. ViniciusCestarii commented at 5:27 PM on September 25, 2026: contributor

    Concept ACK

    Reviewed with @johnnyasantoss.

    That might be a sunk-cost fallacy. If you saw the other block first, it's a good assumption that the rest of the network also saw it first. If the majority of hash rate is mining on the other side, and if you (the miner, not the pool) can't tip that majority over, it may be a waste of hash power.

    There's no issue with the majority of the hash rate on the other side because a miner (or pool) is always mining against the rest of the network hash rate. So the odds of finding the next block are the same on either side, only the payout differs: 2 blocks on your own tip vs 1 on the other.

  29. johnnyasantoss commented at 5:59 PM on September 25, 2026: none

    Concept ACK 22aeff71e81186c714a4f7529e9922f5155e2de4

    I've reviewed the code but I'm not that confident in my cpp skills so I'll refrain from commenting it.


    That might be a sunk-cost fallacy. If you saw the other block first, it's a good assumption that the rest of the network also saw it first. If the majority of hash rate is mining on the other side, and if you (the miner, not the pool) can't tip that majority over, it may be a waste of hash power.

    cc @Sjors @0xB10C

    Ran some thought experiments together with @ViniciusCestarii to understand whether precious=true always would be ideal. Thinking with context from this tweet^1 regarding deep reorg viabtc vs. antpool^2.

    Seems it would be extremely unlikely to lose reward from working on a template on top of your own past work. Given that if the network finds a block the miner would revert to it and switch the template to the new best chain.

    <img width="1130" height="892" alt="image" src="https://github.com/user-attachments/assets/25267aa8-5f99-46dc-8f75-ba7194883ca4" />

    The miner would have to find an alternative block in the same timing as the rest of the network and then find another subsequent block on right after the network extends the other chain(!) to then get reorg'd and lose work. This seems like a black swan event. Chances of finding a block are related to hashrate and not prevhash so there's no waste in doubling down on your template AFAICT. Your odds at prevhash 0A and 0B are the same. Electricity spent trying to find 1A and finding 1B would be the same regardless if the network sends you 1B, but on the other side reward finding 1A is roughly twice of that working on 1B.

    maybe there's something we are not grasping, but intuitively precious=true makes a lot of sense.

  30. 0xB10C commented at 9:45 PM on September 29, 2026: contributor

    I don't have the full context of the PR, but I agree with @johnnyasantoss @ViniciusCestarii here. If you don't mine on your own block, the maximum you can win is the next block. If you mine on your own block, you can win the last and the next block. Chances of finding the next block don't change, in both cases it's equal to your share of network hashrate.

  31. fanquake referenced this in commit 66776840be on Oct 3, 2026
  32. refactor: extract ActivateBestChain_ helper
    Move the lock-held body of ActivateBestChain() into an internal helper without changing behavior. This allows callers that already hold m_chainstate_mutex to run activation without releasing and reacquiring it.
    302006b8b4
  33. validation: report active-chain membership from PreciousBlock
    Hold m_chainstate_mutex while marking a block precious, activating the
    best chain, and capturing whether the block is in the active chain.
    Use ActivateBestChain_ so this sequence does not release and reacquire
    the lock.
    
    Return this status through the connected output so mining submissions
    can inspect the result without racing another chain activation.
    
    Adapt the preciousblock RPC and document why it ignores connected,
    as suggested by pablomartin4btc.
    64e5010615
  34. mining: add precious IPC block submission option
    Add a precious flag to the Mining IPC submitBlock and submitSolution
    methods so callers can prefer a submitted block over competing blocks
    with the same work.
    
    Use the connected result from PreciousBlock to determine whether a new
    block entered the active chain.
    
    Preserve false with reason="duplicate" for already-known blocks, including
    when precious changes the active tip, as suggested by Sjors. Report
    captured validation failures ahead of the duplicate fallback.
    
    Clarify that a successfully submitted new block may be an ancestor of
    the active tip and that success does not imply a reorganization,
    addressing pablomartin4btc's review suggestion.
    
    Follow the explicit-error versioning pattern discussed by Sjors and
    ryanofsky: use new Cap'n Proto ordinals for the updated methods and
    retain the old signatures as deprecated entry points that direct
    clients to update. This prevents older servers from silently ignoring
    the precious argument.
    0cd41ab4aa
  35. test: cover precious mining submissions
    Exercise submitBlock and submitSolution together in five independent
    regtest scenarios, with fresh chain state for each method. Cover first
    submission of a new same-work precious block, duplicate submissions,
    lower-work blocks, activation failures, and invalid blocks.
    
    Following Sjors's review, put the first-submission scenario at the start
    and share scenario assertions across both APIs. Check that known blocks
    remain duplicates even when making them precious changes the active tip.
    
    Check the deprecated submitSolution error message as well as the
    exception type, following DrahtBot's review suggestion.
    071374f047
  36. test: deduplicate proof-of-work grinding cea8805989
  37. test: cover precious IPC mining submissions
    Exercise precious submission through both IPC methods, including the
    default behavior for side blocks with the same work, first submissions,
    duplicate activation, and invalid-block rejection.
    
    Check that already-known blocks report duplicate even when precious
    changes the active tip, as requested by Sjors. Make the three stateless
    test helpers static methods, as suggested by pablomartin4btc.
    
    Check explicit update errors from both deprecated submission methods,
    following the versioning pattern discussed by Sjors and ryanofsky.
    
    Name the precious boolean arguments in the IPC calls, as suggested by
    DrahtBot, so the tests make the requested behavior explicit.
    7f88605e60
  38. doc: add IPC block submission release note
    Describe the precious option, acceptance of new blocks, and validation
    results for IPC mining clients. Document that duplicate submissions
    retain reason="duplicate" even when precious changes the active tip,
    consistent with Sjors's review.
    
    Document the new method ordinals, regenerated bindings, and explicit
    update errors for clients calling the deprecated methods.
    cac6117bfa
  39. w0xlt force-pushed on Oct 8, 2026
  40. DrahtBot removed the label Needs rebase on Oct 9, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-11 08:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me