Repository navigation
v0.6.1
📄 Canonical release notes:
docs/release-notes/0.6.1.md
satd 0.6.1
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
-reindexcontinues 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_SUCCESSset, 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
WatchAddRejectedevent (#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, withoutstream: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 aWatchAddRejectedevent on the
Watchstream (watch_add_rejectedon WebSocket) naming the reason and the
refused items; both SDKs decode it, andResilientWatchretries 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 atstreamwsmaxsubscriptions. The event is an additive schema
change (NodeEventtag 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 anOP_CHECKMULTISIGafter another signature opcode in P2SH and
P2WSH scripts lower than Core. Parents admitted bysubmitpackagewithout a
fee check now carry their real sigop cost (#937). - The Rust script engine treats 0x80, 0x81 and 0x83–0x86 as
OP_SUCCESSxin
tapscript, as Core does, instead of failing the leaf. This affected
-consensus=rust,cpp-shadowandsatd-chainstate-repair; the default
rust-shadowmode 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 malformednBitscount 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:
-blocknotifydid not run, peers were not told, and Electrum, Esplora SSE,
streaming and ZMQhashblocksubscribers 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. Theheaderstip leaves the failed
branch, and a chain with more work below it is activated at once (#957). - A block passed to
submitblockbefore 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
-maxaheadwindow. 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). nobanand 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.getpeerinforeports onion-inbound peers asonion
(#944).- Address announcements are rate-limited per peer as in Core, with
addr_processedandaddr_rate_limitedingetpeerinfo. Dials in flight
count against the outbound limit, manual connections sit outside it, and
setbanof 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 loweringmaxmempoolon a config reload
trims the pool at once (#942). getmempoolentry,getrawmempool,getmempoolinfo,gettxoutand
sendrawtransactionanswer 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.datis 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_historyorder, 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 emptywitness
arrays, and/fee-estimatesomits 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
--electrumrequesttimeoutand--esplorarequesttimeouttake effect (#947).
Indexes
- The address, spend and compact block filter indexes do not serve during or
after an unfinishedbackfillindex. 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 blockfilterrun no longer leaves a broken BIP 157 filter
header chain, wheregetblockfilter,getcfheadersandgetcfcheckpt
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
-reindexcontinues 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
-reindexscan 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-chainstateor
-upgradechainstatestops 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
-reindexno 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 dataonly 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 withRUST_LOG=warn, so the log carried warnings only
(#930).
Configuration
bitcoin.conftreats#anywhere on a line as a comment start, as Core
does, and refuses anrpcpasswordline containing#. Trace logs no longer
include RPC headers or parameters, and the Tor control cookie read is
bounded (#940).
RPC
getrawaddrmankeeps 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).submitblockanswersduplicate-invalidand throws -22 or -25,
getblockfilteruses Core's errors, and a failed RPC password is answered
after 250 ms (#955).
Dependencies
rustlsis 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 anrpcpasswordcontaining#(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
-reindexthat 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.
Anrpcpasswordline 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). gettxoutincludes the mempool by default (include_mempool=true).
getmempoolinfo.bytesis total vsize, not serialized size
(satd_mempool_bytesand the-maxmempoolbudget still count serialized
bytes), andloadedis false until the saved mempool has finished loading.
sendrawtransactionrefuses a negative, unreadable or ≥ 1 BTC/kvB
maxfeerateormaxburnamountinstead of ignoring it (#946).submitblockthrows 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 ingetnetworkinfoand PSBT RPCs with 8 decimals; clients that
compared these as strings may see different text (#955).- Esplora
/fee-estimatesomits targets the node has no estimate for and
answers{}during initial sync./block/:hash/txs/:start_indexanswers 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). --electrumrequesttimeoutand--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
backfillindexrun, or after one that stopped part-way until a re-run
completes;NODE_COMPACT_FILTERSis withdrawn meanwhile. A redundant run on
a complete index also stops serving until it completes (#958). - A node with
-blockfilterindexon whichbackfillindex blockfilterhas
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 warningrewrote filter headersnames
the first height that was wrong. Until the check finishes, the old headers
are served (#963). - Misbehaving
nobanand 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.confkeys, on-disk
layout, Electrum/Esplora surfaces) are governed by
STABILITY_POLICY.md. satd follows
semantic versioning; while in the
0.xpre-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