Skip to content

v0.6.1

Choose a tag to compare

@github-actions github-actions released this 15 Oct 19:02
· 231 commits to master since this release
v0.6.1
fef5294

📄 Canonical release notes: docs/release-notes/0.6.1.md

satd 0.6.1

Released 2026-10-15 · git tag v0.6.1 · Changelog

satd 0.6.1 is a patch release on the 0.6 line, cut from the release/0.6
branch. It fixes bugs in 0.6.0: reindex and shutdown handling, initial sync
stalls, header and block validation that differed from Bitcoin Core, P2P
limits and peer handling, mempool behavior and RPC answers, Electrum and
Esplora, and the indexes. It is a drop-in upgrade from 0.6.0 with no reindex.
Read the upgrade notes for the few changes an operator or
client can notice.

Highlights

  • An interrupted -reindex continues where it stopped, on any start,
    instead of starting over from genesis (#906).
  • A stop signal stops a rebuild cleanly. SIGTERM or SIGINT during a
    reindex or chainstate upgrade flushes and exits 0, also as PID 1 in a
    container (#907).
  • Initial sync no longer waits on one slow peer for up to a minute, and
    no longer stalls with a full download window (#909, #953).
  • Sigops, the tapscript OP_SUCCESS set, header sync and invalid-block
    handling now match Bitcoin Core
    (#937, #933, #954, #957).
  • A refused streaming watch add is reported to the client with a
    WatchAddRejected event (#899).
  • Electrum desktop no longer reconnects in a loop on addresses with
    pending transactions (#938).

Streaming Consumption API

  • A watch add the server refused (over the token's quota or rate limit, over
    a per-connection cap, without stream:watch, or malformed) was logged on
    the node and dropped, so a client could believe it was watching addresses
    it was not. The server now answers with a WatchAddRejected event on the
    Watch stream (watch_add_rejected on WebSocket) naming the reason and the
    refused items; both SDKs decode it, and ResilientWatch retries a
    rate-limited add and stops re-sending other refused items on reconnect
    (#899).
  • A silent-payment add that only changes labels no longer spends a rate-limit
    token, and on WebSocket an add that only re-asserts watched items is no
    longer shed at streamwsmaxsubscriptions. The event is an additive schema
    change (NodeEvent tag 32): older SDKs ignore it (#899).

Consensus and validation

  • Signature operations are counted exactly as Bitcoin Core counts them, for
    the block sigop limit, mempool policy and block templates. The old counter
    scored an OP_CHECKMULTISIG after another signature opcode in P2SH and
    P2WSH scripts lower than Core. Parents admitted by submitpackage without a
    fee check now carry their real sigop cost (#937).
  • The Rust script engine treats 0x80, 0x81 and 0x83–0x86 as OP_SUCCESSx in
    tapscript, as Core does, instead of failing the leaf. This affected
    -consensus=rust, cpp-shadow and satd-chainstate-repair; the default
    rust-shadow mode decided with libbitcoinconsensus (#933).
  • A block body that fails the context-free block checks no longer marks the
    block invalid, since the body may not be the block its header names. An
    empty body is checked against the header's merkle root first (#936).
  • On signet the signed coinbase commitment is rebuilt exactly as Core does. A
    block missing from a median-time-past window stops connecting with a storage
    error instead of using fewer blocks, and malformed nBits count as zero
    work (#948).

Chain, sync and reindex

  • A block that reached the tip through the stored-tail drain, such as one that
    arrived before its parent, was connected without a chain event:
    -blocknotify did not run, peers were not told, and Electrum, Esplora SSE,
    streaming and ZMQ hashblock subscribers missed it. The drain now reports
    each block it connects (#900).
  • satd stops, as Core does, when it cannot read a coin from the chainstate,
    instead of treating the coin as spent and recording a valid block as
    invalid (#945).
  • Header sync continues from the last header a peer sent, so a node whose best
    header is on a competing branch keeps syncing. The retarget start is found
    on the block's own branch, and targets above the proof-of-work limit are
    refused (#954).
  • A block that fails to connect from storage is marked invalid instead of
    being retried by the IBD connector. The headers tip leaves the failed
    branch, and a chain with more work below it is activated at once (#957).
  • A block passed to submitblock before its parent is now indexed as a
    header, as Core does: it becomes the best header and connects as soon as
    its parent does, where it stayed unconnected until another block arrived
    (#922).

Initial block download

  • The network loop's periodic pass, which takes a block back from a peer that
    has held it for 15 seconds and asks another, hardly ran while blocks were
    arriving. The block the sync needed next could wait up to a minute on one
    slow peer. The pass now runs every half second (#909).
  • Initial sync no longer stalls with a full -maxahead window. Re-sent blocks
    do not count against it, a local write failure does not penalize the
    sending peer, and block downloads go only to peers that advertise block
    service (#953).

P2P

  • A block that arrives before its parent is kept only if it was requested and
    within fixed bounds: 4 per peer, 32 MB in all, 5 minutes (#941).
  • Core's limits on P2P message size and entry counts apply. Block replies are
    queued about 1 MB per peer, a peer that takes no data for 20 minutes is
    disconnected, user agents are capped at 256 bytes and sanitized, and BIP 157
    requests Core refuses disconnect the peer (#939).
  • noban and manual peers are never disconnected or banned for misbehaviour.
    Local and onion-inbound peers are disconnected but not banned, so 127.0.0.1
    is no longer banned. getpeerinfo reports onion-inbound peers as onion
    (#944).
  • Address announcements are rate-limited per peer as in Core, with
    addr_processed and addr_rate_limited in getpeerinfo. Dials in flight
    count against the outbound limit, manual connections sit outside it, and
    setban of an onion host disconnects it (#952).
  • Automatic outbound connections take at most one peer per network group
    (/16, /32, or ASN with -asmap), as Core does. The extra outbound peers
    opened during initial sync (up to 64) are disconnected one every 10 seconds
    once the node has caught up, until 8 remain (#894, #895).

Mempool

  • A full mempool checks an incoming transaction's scripts before evicting
    anything for it, and never evicts its parent. Eviction costs time in
    proportion to what it removes, and lowering maxmempool on a config reload
    trims the pool at once (#942).
  • getmempoolentry, getrawmempool, getmempoolinfo, gettxout and
    sendrawtransaction answer as Core does. A restart keeps entry times and
    loads a large mempool in linear time (#946).
  • A restart re-admits the whole saved mempool. mempool.dat is written
    parents first, as Core writes it, and a file saved by 0.6.0 is reordered on
    load; before, a child saved in the same second as its parent could be tried
    first and dropped with its descendants (#949).

Electrum and Esplora

  • The Electrum scripthash status hashes the history in get_history order, as
    the protocol defines. Electrum desktop no longer reconnects in a loop on
    addresses with pending transactions (#938).
  • Esplora honours mempool.space's ?after_txid= paging, answers 404 or 400
    for out-of-range or misaligned block-txs pages, drops empty witness
    arrays, and /fee-estimates omits targets with no estimate (#943).
  • Long Electrum and Esplora address-history reads run off the API runtime's
    worker threads, so one slow request no longer stalls the others, and
    --electrumrequesttimeout and --esplorarequesttimeout take effect (#947).

Indexes

  • The address, spend and compact block filter indexes do not serve during or
    after an unfinished backfillindex. A reorg during a backfill no longer
    deletes rows the active chain wrote, and a read during a flush waits for it
    (#958).
  • A backfillindex blockfilter run no longer leaves a broken BIP 157 filter
    header chain, where getblockfilter, getcfheaders and getcfcheckpt
    served headers chained off zeros from some height on. The filters themselves
    were correct. On first start, 0.6.1 checks and repairs the stored header
    chain once (#963).

Operator

  • An interrupted full -reindex continues from the height it had flushed, on
    any start, instead of starting over. satd scans the block files again,
    checks the blocks it had replayed against them, and connects the rest,
    downloading nothing. A reindex that cannot be continued runs again from
    genesis on any start, where 0.6.0 refused every start but --reindex
    (#906).
  • The -reindex scan reads only block headers, at most 4 KiB per block,
    instead of reading every block file whole (about 700 GB on mainnet) (#916).
  • SIGTERM or SIGINT during -reindex, -reindex-chainstate or
    -upgradechainstate stops the replay: satd flushes what it had connected,
    keeps the rebuild marker and exits 0, within -maxshutdownsecs, also as
    PID 1 of a container with no init. A shutdown logs each wait before it
    starts (#907).
  • A full -reindex no longer appends a second copy of the genesis block to
    the last block file, which on a node switched from Bitcoin Core is one of
    Core's files (#931).
  • Initial sync logs Connector stuck waiting for block data only after a
    20-second wait on one block, then once a minute, with the time waited,
    instead of at WARN for nearly every block (#904).
  • StartOS package: the service log shows satd's INFO lines. StartOS
    starts services with RUST_LOG=warn, so the log carried warnings only
    (#930).

Configuration

  • bitcoin.conf treats # anywhere on a line as a comment start, as Core
    does, and refuses an rpcpassword line containing #. Trace logs no longer
    include RPC headers or parameters, and the Tor control cookie read is
    bounded (#940).

RPC

  • getrawaddrman keeps each address in the bucket Core's formulas give it.
    Two addresses that derived the same slot could put the second in the next
    bucket (#923).
  • RPC numbers use Core's formatting (16 significant digits, 8-decimal fee
    amounts). submitblock answers duplicate-invalid and throws -22 or -25,
    getblockfilter uses Core's errors, and a failed RPC password is answered
    after 250 ms (#955).

Dependencies

  • rustls is updated to 0.23.46 for RUSTSEC-2026-0346, -0347, -0348 and
    -0349. None of them applies to satd's configuration (#966).

Upgrade notes

  • Drop-in upgrade from 0.6.0: no reindex. The only configuration change is
    for an rpcpassword containing # (below).
  • A client that treated silence after a watch add as success should handle
    WatchAddRejected; one built on a 0.6.0 SDK keeps working and ignores the
    event.
  • A full -reindex that 0.6.0 left unfinished runs again from genesis on the
    first 0.6.1 start, with or without --reindex. Reindexes 0.6.1 starts are
    continued.
  • bitcoin.conf: a # anywhere on a line now starts a comment, as in Core.
    An rpcpassword line containing #, including a trailing comment such as
    rpcpassword=pw # old, is refused at startup; change the password or move
    the comment, or use -rpcauth (#940).
  • satd now stops, as Core does, when it cannot read a coin from the
    chainstate. A restart replays the blocks after the last flush; if the stop
    recurs, check the disk and run -reindex-chainstate (#945).
  • gettxout includes the mempool by default (include_mempool=true).
    getmempoolinfo.bytes is total vsize, not serialized size
    (satd_mempool_bytes and the -maxmempool budget still count serialized
    bytes), and loaded is false until the saved mempool has finished loading.
    sendrawtransaction refuses a negative, unreadable or ≥ 1 BTC/kvB
    maxfeerate or maxburnamount instead of ignoring it (#946).
  • submitblock throws error -22 for undecodable hex and -25 for an internal
    error instead of returning a reject string. Difficulty, network hash rate
    and verification progress are printed with 16 significant digits, and fee
    fields in getnetworkinfo and PSBT RPCs with 8 decimals; clients that
    compared these as strings may see different text (#955).
  • Esplora /fee-estimates omits targets the node has no estimate for and
    answers {} during initial sync. /block/:hash/txs/:start_index answers 404
    past the end of the block and 400 for a start that is not a multiple of 25,
    and an unknown ?after_txid= cursor answers 422 (#943).
  • --electrumrequesttimeout and --esplorarequesttimeout (30 seconds by
    default) now take effect: an Electrum request past it gets an error and its
    connection is closed, and an Esplora address route answers 503. Raise them
    if clients read very long address histories (#947).
  • The address, spend and compact block filter indexes do not serve during a
    backfillindex run, or after one that stopped part-way until a re-run
    completes; NODE_COMPACT_FILTERS is withdrawn meanwhile. A redundant run on
    a complete index also stops serving until it completes (#958).
  • A node with -blockfilterindex on which backfillindex blockfilter has
    ever run checks its stored filter header chain once on its first 0.6.1
    start and rewrites the headers a 0.6.0 backfill left chained off zeros. The
    check reads every stored filter once (a few minutes on mainnet) in the
    background. No action is needed; the warning rewrote filter headers names
    the first height that was wrong. Until the check finishes, the old headers
    are served (#963).
  • Misbehaving noban and manual peers are never disconnected or banned. Peers
    on local addresses and peers that come in through the onion service are
    disconnected but not banned (#944).

About this release

satd is free software under the MIT License.

  • Stability & compatibility — Tier 1 surfaces (Bitcoin Core-compatible
    JSON-RPC wire shape, CLI flag names/defaults, bitcoin.conf keys, on-disk
    layout, Electrum/Esplora surfaces) are governed by
    STABILITY_POLICY.md. satd follows
    semantic versioning; while in the
    0.x pre-1.0 line, deprecations may be accelerated (see the policy).
  • Security & disclosure — report vulnerabilities per
    SECURITY.md. Consensus-affecting bugs are treated as
    P0 with same-day acknowledgement.
  • Verifying this release — tarballs are signed with minisign, container
    images with cosign (keyless OIDC), and git tags with SSH signatures (no GPG).
    The key matrix and verification steps are in
    SECURITY.md.
  • Intentional differences from Bitcoin Core — catalogued in
    CORE_DIFFERENCES.md.
  • Operating satd — observability, the full flag matrix, tuning, and the
    downstream packaging contract live in the
    Operator Manual.

What's Changed

  • Correct the streaming limit docs and stale SDK and proto comments by @bkeroack in #898
  • Report a refused watch add in-band (WatchAddRejected) by @bkeroack in #899
  • Report blocks the stored-tail drain connects as chain events (#900) by @bkeroack in #912
  • e2e: fix the Go watch-set loader reconnect flake by @bkeroack in #919
  • release: fix the notes step's quoting on release/0.6 (from #901) by @bkeroack in #918
  • ci: run Tests and Lint on pushes to release branches by @bkeroack in #913
  • p2p: run the manager loop's maintenance on time under load (#909) by @bkeroack in #917
  • IBD: warn that the connector is stuck only after a 20 s wait (#904) by @bkeroack in #911
  • Stop a startup block replay cleanly on SIGTERM, also as PID 1 (#907) by @bkeroack in #914
  • Continue an interrupted -reindex from the block files on any start (#906) by @bkeroack in #915
  • addrman: keep a colliding address in its derived bucket (getrawaddrman, test flake) by @bkeroack in #923
  • Read only block headers in the -reindex scan (#916) by @bkeroack in #927
  • Index the genesis record a full -reindex finds in blk00000.dat (#931) by @bkeroack in #932
  • StartOS package: run satd with RUST_LOG=info by @bkeroack in #930
  • Check a block's merkle root first, and don't keep a body-check failure as an invalid mark by @bkeroack in #936
  • Bound the unknown-parent block buffer by @bkeroack in #941
  • Stop on a coin read error instead of treating the coin as missing by @bkeroack in #945
  • Count sigops exactly as Bitcoin Core does by @bkeroack in #937
  • Electrum: hash scripthash status in history order by @bkeroack in #938
  • p2p: apply Core's message size and count limits, and bound queued sends per peer by @bkeroack in #939
  • consensus: use Core's exact OP_SUCCESS set in the Rust interpreter by @bkeroack in #933
  • Local hardening: bounded Tor cookie read, Core's bitcoin.conf comments, no request dumps in trace logs by @bkeroack in #940
  • Esplora: page /txs with after_txid, 404 past a block's last page, omit empty witness and unknown fee targets by @bkeroack in #943
  • Electrum/Esplora: run index reads off the runtime workers, make request timeouts fire, page /txs/chain from the cursor by @bkeroack in #947
  • p2p: don't ban local, inbound-onion or manual peers for misbehaviour (Core parity) by @bkeroack in #944
  • p2p: bound learned addresses and outbound dials; manual peers outside the automatic cap by @bkeroack in #952
  • IBD: count only requested blocks in the download window by @bkeroack in #953
  • Header sync: continue from the peer's last header; derive the retarget start from the branch by @bkeroack in #954
  • mempool: verify scripts before evicting, and make eviction linear by @bkeroack in #942
  • mempool RPCs: match Bitcoin Core's getmempoolentry, getmempoolinfo, gettxout and sendrawtransaction by @bkeroack in #946
  • RPC: match Bitcoin Core's number formatting, submitblock, getblockfilter and failed-auth answers by @bkeroack in #955
  • Signet commitment rewrite matches Core byte for byte; MTP needs a full window by @bkeroack in #948
  • Retry the best valid chain after a failed connect, and recalculate the header tip by @bkeroack in #957
  • Index serving gate, backfill reorg safety, flush window and coin-free blocks by @bkeroack in #958
  • Release notes: 0.6.1 fixes from release/0.6 by @bkeroack in #959
  • mempool: save and reload parents before their children by @bkeroack in #960
  • chain: a block submitted before its parent connects with it (#922) by @bkeroack in #961
  • net: one automatic outbound peer per network group; trim outbound peers after IBD (#894, #895) by @bkeroack in #962
  • index: filter tail catch-up during IBD keeps the filter header chain by @bkeroack in #963
  • deps: rustls 0.23.46 by @bkeroack in #966
  • chore(release): cut 0.6.1 by @bkeroack in #965

Full Changelog: v0.6.0...v0.6.1