rpc: invalidateblock reports success when a block can't be disconnected #36546

issue praveenperera opened this issue on October 15, 2026
  1. praveenperera commented at 4:16 PM on October 15, 2026: none

    Is there an existing issue for this?

    • I have searched the existing issues

    Current behaviour

    If Chainstate::InvalidateBlock fails part way through, the invalidateblock RPC still returns null as if it succeeded.

    The RPC helper ignores the return value of InvalidateBlock and only throws when state is invalid (src/rpc/blockchain.cpp#L1765-L1773):

    chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
    
    if (state.IsValid()) {
        chainman.ActiveChainstate().ActivateBestChain(state);
    }
    
    if (!state.IsValid()) {
        throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
    }
    

    DisconnectTip returns false without touching state when DisconnectBlock fails, for example when ReadBlockUndo hits a checksum mismatch or a missing rev*.dat file (src/validation.cpp#L2956-L2959). InvalidateBlock then returns false from inside its disconnect loop (src/validation.cpp#L3591-L3594).

    When that happens:

    • every block above the failing one has already been disconnected and marked BLOCK_FAILED_VALID
    • the tip is left at the block whose undo data couldn't be read
    • the block passed to invalidateblock is not marked invalid
    • the RPC returns null, and the only trace is a DisconnectBlock ... failed line in debug.log

    The same silent success happens for the genesis block, where InvalidateBlock returns false early with a valid state (src/validation.cpp#L3536-L3537).

    The same DisconnectTip failure during a normal reorg is fatal: ActivateBestChainStep aborts the node with "Failed to disconnect block" (#15305). Through invalidateblock, the user is told it worked.

    How I ran into this

    I run a mainnet Bitcoin node. The mempool.space backend running against it kept failing with "Undo data expected but can't be read", and bitcoind logged ReadBlockUndo: Checksum mismatch for rev05123.dat every few seconds. getblockstats failed for six consecutive blocks, 913132 to 913137. Their undo records had been written in September 2025, and 576 KiB of the file in the middle of them had been replaced with zeros, most likely a lost write after an unclean shutdown.

    A full -reindex would have taken days, so I looked at invalidateblock 913132 followed by reconsiderblock as a shortcut. Reading the code showed it couldn't work. The rollback would disconnect about 58,400 blocks, stop at 913137 because that block's undo data can't be read, and then return null. Even if it had got through, reconsiderblock wouldn't have repaired anything, because WriteBlockUndo skips blocks that already have an undo position. I didn't run it.

    I repaired the node another way, by rebuilding the six undo records from -txindex and writing them back in place. But nothing in the RPC's response would have told me the rollback stopped part way, which is why I'm filing this.

    Expected behaviour

    invalidateblock returns an error when the requested block was not invalidated. The error should say that the rollback stopped early and at which block, so the user knows the chain is partly rolled back. For the genesis block, it should return a clear "cannot invalidate genesis block" error.

    Steps to reproduce

    I found this by reading the code and haven't run these steps against master yet. On regtest:

    1. Start a node and mine 10 blocks with generatetoaddress. Each block contains only a coinbase transaction.
    2. Stop the node. Corrupt the undo record for block 5 by flipping one byte of its checksum in blocks/rev00000.dat. Each coinbase-only undo record is 41 bytes: 4 magic, 4 size, 1 byte for the empty CBlockUndo, and a 32-byte checksum. Genesis has no undo record, so block 5's checksum ends at offset (5 - 1) * 41 + 40 = 204.
    3. Start the node and run invalidateblock <hash of block 3>.

    From the code paths above, the RPC returns null, getblockcount returns 5, getchaintips does not show block 3 as invalid, and debug.log gets a DisconnectTip(): DisconnectBlock <hash> failed line. invalidateblock <genesis hash> also returns null.

    Relevant log output

    From the mainnet node, repeated every few seconds while the mempool.space backend retried:

    [error] ReadBlockUndo: Checksum mismatch at FlatFilePos(nFile=5123, nPos=1035211)
    

    How did you obtain Bitcoin Core

    Not applicable, found by reading the source

    What version of Bitcoin Core are you using?

    Not applicable. The code references above are from master at 649bc0a645eb127a9b1e6d078f8d129be6380c87.

    Operating system and version

    Not relevant

    Machine specifications

    Not relevant

  2. maflcko added the label Bug on Oct 16, 2026
  3. maflcko added the label RPC/REST/ZMQ on Oct 16, 2026
  4. maflcko added the label Data corruption on Oct 16, 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-26 03:51 UTC

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