A server that stops answering without closing the socket left a running
sync waiting forever. Requests now fail with TimeoutException after 30 s
(10 s for the handshake, both configurable in ConnectAsync) and the
connection is marked dead so the app reconnects. See ADR 0034.
After a protocol error (e.g. a malformed line) the read loop ended but
the client still reported IsConnected and later requests waited forever
for a response that could never arrive. The read loop now marks the
connection closed before failing pending requests, and RequestAsync
throws IOException on a closed connection. Adds regression tests and a
raw-line helper to the fake server.
DispatchLine's ArrayPool-copy path (for a JSON-RPC line spanning
multiple PipeReader segments) only ran for small responses in existing
tests, which stay single-segment. A 256 KB fake response forces the
multi-segment branch and verifies the payload still arrives intact.
ElectrumClient, WalletSynchronizer, TransactionInspector, CertificatePinStore
and peer discovery went from 0% coverage to close to full coverage, all
against the fake ElectrumX server:
- ElectrumClient: request/response correlation under load, server errors,
notifications, disconnection, cancellation, SSL handshake with TOFU pinning.
- WalletSynchronizer: gap-limit scanning, UTXO/history reconstruction,
unconfirmed/immature balances, busy-retry, disk-cache reuse, and the
security-critical path where a lying server fails Merkle verification and
the sync must abort.
- TransactionInspector: fee calculation from resolved inputs, mine/theirs
attribution, RBF flag, coinbase handling, degraded paths when a previous
transaction can't be resolved.
- CertificatePinStore: TOFU pin/match/mismatch/reset, corrupted-file fallback.
- ServerRegistry: peer discovery and persistence via DiscoverAsync.
- UpdateChecker: release-tag parsing and semver comparison.