Commit Graph

4 Commits

Author SHA1 Message Date
davide 3460e53b4f feat(spv): verify Merkle proofs progressively, gate spendability on it
Balance/history now render as soon as tx downloads finish instead of
blocking on every historical Merkle proof, critical for mobile where
proof-checking can take much longer than the download itself. Proofs
continue to be checked in the background and each tx's Verified flag
catches up progressively; header ranges are now fetched in batches
(blockchain.block.headers) instead of one call per header to keep this
fast over high-latency links.

Coin selection (UtxoSpendability.IsSpendable) refuses to spend a UTXO
until its Merkle proof is actually checked, regardless of confirmation
count, so a server that fabricates a confirmed balance can get it
displayed early but never spent before the forgery is caught. The disk
cache only ever persists the fully-verified end state of a sync.

UI surfaces the new PendingVerificationSats/SpendableSats split with a
"verifying..." badge, and the sync save now runs off the UI thread to
avoid freezing on slower hardware.
2026-07-17 19:43:18 +02:00
davide db5b65ca0d test(spv): cover checkpoint-anchoring memoization and the non-generic retry path
Two branches of WalletSynchronizer had no coverage: the early-return in
AnchorToCheckpointAsync when a range is already memoized (a second sync
with a tx below the previously-anchored height must not re-walk the
header chain), and the void RetryOnBusyAsync overload used by the
transaction-download tasks (only the generic <T> overload, exercised by
get_history, had a test).
2026-07-07 16:24:11 +02:00
davide e349a80ddc feat(spv): anchor header trust to hardcoded mainnet checkpoints
ChainProfiles.Mainnet.Checkpoints was an empty array with a "populate
before release" TODO, and even BlockHeaderInfo.MatchesCheckpoint()/
IsValidChild() — the methods meant to enforce it — were never called
anywhere in WalletSynchronizer. So on this LWMA chain, where PoW can't
be recomputed client-side (SkipPowValidation), a malicious or
eclipsing server could hand back any internally-consistent 80-byte
header for a Merkle proof: nothing tied it to the real Palladium
chain.

Populated Mainnet.Checkpoints with 24 real [height, hash, bits]
checkpoints (every 20,000 blocks + one near tip), pulled via RPC from
a fully-synced palladiumd. Testnet/Regtest are left empty (no node
available to verify against, and WalletSynchronizer already treats a
missing checkpoint as a no-op, so this is safe, just unanchored).

Added WalletSynchronizer.AnchorToCheckpointAsync: for every header
used in a Merkle proof, downloads the intervening headers back to the
nearest checkpoint at or below that height and verifies an unbroken
prev-hash chain terminating in the checkpoint's exact hash, finally
wiring up the previously-dead MatchesCheckpoint/IsValidChild. Verified
ranges are memoized in-memory per sync session to avoid re-walking.

Updated SECURITY.md and USERGUIDE.md to describe what is actually
enforced now (previously the guide deliberately avoided promising
checkpoint anchoring, since the array was empty).
2026-07-07 15:12:17 +02:00
davide 448f7dc65d test: cover network/SPV layer end to end (was previously untested)
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.
2026-07-02 23:21:01 +02:00