rpc: Assertion 'block_template' failed in `getblocktemplate` (during shutdown or startup) #34262

issue dergoegge opened this issue on January 12, 2026
  1. dergoegge commented at 1:55 PM on January 12, 2026: member

    The CHECK_NONFATAL check here may fail when getblocktemplate is called during shutdown or during startup.

    rpc/mining.cpp:875 auto getblocktemplate()::(anonymous class)::operator()(const RPCHelpMan &, const JSONRPCRequest &) const: Assertion `block_template' failed.
    

    See attached debug logs of two examples.

    getblocktemplate_during_startup.log getblocktemplate_during_shutdown.log

    This was found with a test running on Antithesis.

  2. dergoegge renamed this:
    rpc: Assertion `block_template' failed in `getblocktemplate` (during shutdown or startup)
    rpc: Assertion 'block_template' failed in `getblocktemplate` (during shutdown or startup)
    on Jan 12, 2026
  3. maflcko added the label RPC/REST/ZMQ on Jan 12, 2026
  4. maflcko added the label Mining on Jan 12, 2026
  5. maflcko added the label interfaces on Jan 12, 2026
  6. maflcko removed the label interfaces on Jan 12, 2026
  7. DevPatel-11 commented at 2:04 PM on January 17, 2026: none

    I'd like to work on this. Looking at the code now.

  8. DevPatel-11 commented at 6:14 AM on January 19, 2026: none

    I tried reproducing this locally by spamming getblocktemplate calls during startup and shutdown, but couldn't trigger the race condition (given the timing window).

    Looking at the debug logs:

    • Shutdown case: "Shutdown in progress..." message appears right before the assertion failure
    • Startup case: Assertion fails while node is still initializing (network threads just starting)

    Both show Assertion 'block_template' failed, which means createNewBlock() returned nullptr when the mining subsystem wasn't available.

    The issue is that CHECK_NONFATAL assumes this should never happen, but nullptr is actually a valid return during these states. The fix is to replace the assertion with a proper null check and return an RPC error instead.

    I'll open a PR with the fix.

  9. chriszeng1010 commented at 2:57 AM on August 18, 2026: none

    Looks like this issue is closed due to LLM usage. Should we still work on this?

  10. sedited commented at 6:30 AM on August 18, 2026: contributor

    The pull requests in question were not just closed because they were using LLMs, but because they didn't fix the problem and the authors didn't understand the fixes they were applying. It would still be good to address this issue properly.

  11. chriszeng1010 commented at 4:26 PM on August 18, 2026: none

    I'll take a crack at this.

  12. chriszeng1010 commented at 7:30 PM on August 18, 2026: none

    I'm able to reproduce the error by setting up a test node with a funded wallet and running getblocktemplate on the 1st thread with a longpoll ID on a second terminal to display result. And then, I ran Cli stop after sending funds on the first terminal triggers a 2nd thread to run the shut down process.

    This happens on half of the attempts that I've tried. The other half exists normally with error -9 (Shut down)

    $ ./build/bin/bitcoin-cli -regtest -datadir=/tmp/gbtrepro getblocktemplate '{"rules":["segwit"],"longpollid":"4d9ad53b6b0cdda5d3e2f46ca3065053068dc7095d713034d355f237a783457c4"}' error code: -1 error message: Internal bug detected: block_template ./rpc/mining.cpp:910 (getblocktemplate()::<lambda(const RPCMethod&, const JSONRPCRequest&)>) Bitcoin Core v31.99.0-311847fffa21 Please report this issue here: https://github.com/bitcoin/bitcoin/issues

  13. chriszeng1010 commented at 11:17 PM on August 18, 2026: none

    There's a race condition during shutdown.

    Thread A sleeps here on condition after longpoll to call getblocktemplate

    std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt)
    {
        ......
            kernel_notifications.m_tip_block_cv.wait_until(lock, deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
                return Assume(kernel_notifications.TipBlock()) != current_tip || chainman.m_interrupt || interrupt;
            });
        .....
    }
    

    Thread B notifies shutdown and wakes up Thread A running the following:

    
    void Interrupt(NodeContext& node)
    {
    #if HAVE_SYSTEM
        ShutdownNotify(*node.args);
    #endif
        // Wake any threads that may be waiting for the tip to change.
        if (node.notifications) WITH_LOCK(node.notifications->m_tip_block_mutex, node.notifications->m_tip_block_cv.notify_all());
        InterruptHTTPServer();
        InterruptHTTPRPC();
        InterruptRPC();
        ......
    }
    
    

    Thread A Wakes up, continues, and returns {}

    std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt)
    {
        ....
            if (chainman.m_interrupt || interrupt) {
                interrupt = false;
                return {};
            }
        }
        ....
    }
    
    

    And then it exits

    std::optional<BlockRef> maybe_tip{miner.waitTipChanged(hashWatchedChain, checktxtime)};
    // Node is shutting down
     if (!maybe_tip) break;
    
    ..........
    
    if (!IsRPCRunning())
                throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
    

    Mean while on Thread B its 3 lines away from InterruptRPC()

    
    // wake up everyone here
    if (node.notifications) WITH_LOCK(node.notifications->m_tip_block_mutex, node.notifications->m_tip_block_cv.notify_all());
    InterruptHTTPServer();
    InterruptHTTPRPC();
    //interrupt here
    InterruptRPC();
    
    

    If B is faster and makes it in time, A shuts down correctly.

    Otherwise, A keeps going, hits here

    block_template = miner.createNewBlock({}, /*cooldown=*/false);
    

    And then end up in here and returns empty {}

    if (chainman.m_interrupt || interrupt) {
         interrupt = false;
         return {};
      }
    

    And failing here

    CHECK_NONFATAL(block_template);

  14. chriszeng1010 commented at 11:27 PM on August 18, 2026: none

    I will also check startup path.

  15. chriszeng1010 commented at 6:36 PM on August 20, 2026: none

    The startup failure path is the same as shutdown failure path triggered by the same condition chainman.m_interrupt || interrupt caused by the shutdown flag.


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-08-26 20:51 UTC

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