Skip to content

v2026.9.4

Latest

Choose a tag to compare

@github-actions github-actions released this 04 Sep 10:02
· 13 commits to main since this release
v2026.9.4
893ba15

The second release, and the first that is an upgrade from
something.
Everything below is measured against v2026.8.27:
what changed since, and what it costs to move past it.
CHANGELOG.md's own v2026.9.4 section is the record of everything
that went into it.

Breaking changes

  • A data directory written by v2026.8.27 is refused (issue #569).
    A node upgrading past this change will not start against its
    existing one, and says so rather than reading it wrongly -- one
    line, wrapped here to fit the page:

    btclib_node.exceptions.IncompatibleStoreError: <data directory>
    holds a version 0 store, which this version (1) cannot read:
    delete the directory and sync again
    

    Delete the data directory and sync again; nothing outside it has to
    be touched, the blocks, the undo data, the chainstate and the log
    all living under it. There is no migration to run instead, and the
    reason is the change itself: COINBASE_MATURITY was enforced
    nowhere because the record could not express it, a stored output
    carrying no height for anything to ask how deep the coinbase that
    created it was. The record and the undo data now carry each coin's
    height and its coinbase bit, and both are on-disk formats a
    directory written by v2026.8.27 does not hold. Recovering the
    missing height means reading every block again in order, which is
    the sync under another name.

    The store now carries a schema version so that an old directory
    fails on the first read rather than misparsing a record deep into
    one. The released version wrote no such stamp, which is exactly what
    the refusal recognises.

    This is the first change to break an installation of v2026.8.27,
    that being the first release there was. CHANGELOG.md's own entry
    has what changed and why; this one is only what a user has to do.

  • Config(pruned=True) used to construct and do nothing; it now
    raises PruningNotImplementedError
    (issue #574). A caller relying
    on the old, silently-ignored value was getting a full node that
    wrote every block to disk regardless of what it asked for --
    pruned=False, the default, is unaffected. There is nothing to
    migrate: pass pruned=False, or drop the argument, until pruning
    itself is built (issue #601).

  • The rpc listener's default port is now Core's own, not the p2p
    port plus one
    (issue #605). v2026.8.27 listened on 8334, 18334,
    38334 and 18445 for mainnet, testnet, signet and regtest; it now
    listens on Core's 8332, 18332, 38332 and 18443, which is where
    bitcoin-cli and anything else written against Core looks. A client
    or a firewall rule pointed at an old default has to move; a caller
    passing rpc_port= explicitly is unaffected.

  • A storage fault while connecting a block used to be recorded as
    that block's own rejection, silently, and the node kept running; it
    now stops the node
    (issue #620). Where a node exits this way, do
    not restart it before reading the log for the exception and clearing
    whatever the store or disk reported -- restarting against the same
    fault reaches the same exit again.

  • A kill, a crash, or anything else that stops the node without
    going through a clean shutdown can now cost revalidating up to a few
    dozen of the most recently connected blocks the next time it starts

    (issue #586). A datadir this happens to is not corrupted and needs no
    repair: the node simply offers those blocks to itself again, the same
    way it would a block arriving for the first time, and the store never
    ends up holding a UTXO set, a block status or a filter more advanced
    than the other two. A clean stop -- SIGINT, SIGTERM, or the stop
    RPC -- is unaffected and loses nothing: the store is flushed before it
    closes either way, and this cost is only ever paid by the shutdown
    that skips that step.

  • A corrupted, node-owned utxo- record read back while accepting a
    transaction into the mempool used to be answered as that transaction's
    own refusal, and could get the peer that sent it discouraged; it now
    answers as this node's own fault instead
    (issue #631). Corrupted
    here means what the store's own checksum catches, raised as
    StoreCorruptionError (issue #641): sendrawtransaction answers an
    internal-error response rather than VERIFY_REJECTED/"Invalid signatures or script"; testmempoolaccept answers the same
    internal-error response for the whole batch, rather than the one
    entry's own reject-reason (issue #668); the peer-to-peer path no
    longer discourages a peer for exposing this node's own corrupted
    storage. Read the log for StoreCorruptionError rather than
    trusting any of them. A record whose bytes pass that checksum and
    still cannot be parsed is a different case, answered as absent (issue
    #650): the spend is refused as "Missing prevouts", exactly as a
    genuinely missing prevout is, and nothing is logged -- which is what
    Core's own CCoinsViewDB::GetCoin answers for the same record.

  • The store is now RocksDB, not sqlite3; a data directory written
    by an older release cannot be read
    (issues #637, #641). A node
    upgrading past this change refuses its existing directory rather than
    reading it wrongly:

    btclib_node.exceptions.IncompatibleStoreError: <data directory>
    holds a sqlite3 database, which this version cannot read: delete
    the directory and sync again
    

    Delete the data directory and sync again; there is no migration from
    the old store's own .sqlite file into RocksDB's own format. This is
    the same shape of refusal, and the same remedy, as the schema-version
    one above -- it is a second, independent reason the same directory
    can now be refused, not a repeat of it. What is gained for the cost:
    a bit flipped on disk is now caught at the read that meets it, the
    same per-block checksum Bitcoin Core's own LevelDB store already
    carries, where the old store answered a corrupted value silently and
    could lose a corrupted key without ever reading it at all.

  • scripts/chains/mainnet.py, testnet.py and signet.py are gone
    (issues #583, #581, #573). pip install btclib-node now puts
    btclib-node on PATH; run btclib-node in place of
    python scripts/chains/mainnet.py, btclib-node -testnet/-signet/
    -regtest in place of the other two, and btclib-node -h for every
    flag. btclib-node -conf=<file> reads an existing bitcoin.conf the
    way Core reads one.

  • Config(pruned=True) now builds a node and actually prunes,
    instead of raising PruningNotImplementedError
    (issue #601). A
    caller catching that exception around the constructor no longer has
    one to catch; pruned=False, the default, is unaffected. A pruned
    node keeps only the last 288 blocks and their undo data on disk and
    deletes the rest as the chain advances -- there is no -prune=<n>
    MiB target and no pruneblockchain RPC, only whether pruning is on.
    Nothing to migrate for an existing, unpruned data directory: pruning
    only ever removes data going forward from when it is first turned on.

  • -prune=<n> now matches Core's own manual/automatic split, instead
    of collapsing every nonzero <n> to the fixed depth above
    (issue
    #705). -prune=1 is manual pruning: nothing is deleted on its own any
    more, only the new pruneblockchain RPC deletes, and only when asked
    -- a node started with -prune=1 expecting the old automatic deletion
    now has to call that RPC itself, or use -prune=550 or higher for
    automatic pruning to a MiB target instead. -prune=<n> from 2 to
    549 now refuses to start rather than pruning to the fixed depth,
    Core's own wording: too small a target to actually run a node on.
    -prune=<n> at 550 or above is new: automatic pruning to roughly
    <n> MiB on disk, getblockchaininfo's own automatic_pruning and
    prune_target_size answering for it.

  • A mempool candidate is now also checked against Core's own relay
    policy, not only consensus
    (issue #810). v2026.8.27 held a
    transaction to getblockchaininfo's activation-gated consensus rules
    alone; a script only a standardness rule refuses -- CLEANSTACK
    among them -- now gets the transaction dropped rather than accepted,
    matching what a real Bitcoin Core peer would refuse to relay or mine
    in the first place. sendrawtransaction answers VERIFY_REJECTED /
    "Invalid signatures or script" for one, testmempoolaccept reports
    it not allowed, and the peer that relayed it over p2p is kept rather
    than discouraged -- Core's own policy is not to penalise a peer for
    forwarding a transaction only the non-mandatory rules refuse. Nothing
    to migrate: a transaction built to consensus rules alone and relying
    on this node's mempool to hold it despite failing standardness has to
    be built to a standard script instead, the same script a real network
    would have refused it over regardless.

Windows

  • pip install btclib-node now claims Windows (issue #430): its
    classifiers name it, and test.yml's gate checks the claim on every
    pull request.