feat(spv): verify the chain identity of a server on connect

After the handshake the wallet checks server.features genesis_hash and,
when the profile has checkpoints, the header at the highest checkpoint
not above the server's tip (mainnet shares Bitcoin's genesis, so the
checkpoint is what tells the chains apart). A server on another chain is
refused with WrongChainException and the next candidate is tried. Used by
the GUI, peer discovery, the CLI and the server probe. See ADR 0036.
This commit is contained in:
davide committed 2026-11-03 14:01:34 +01:00
1 parent 77f00d1b4e
commit 8bcee27c23
13 files changed
+341 -6

No files matched your search

@@ -170,6 +170,7 @@ public sealed class CliTests : IDisposable
}
await using var server = new FakeElectrumServer();
server.Handle("server.features", _ => new { genesis_hash = ChainProfiles.Regtest.GenesisHash });
server.Handle("blockchain.headers.subscribe", _ => new { height = 300, hex = Header(uint256.One, 300) });
server.Handle("blockchain.scripthash.subscribe", _ => null);
server.Handle("blockchain.scripthash.get_history", p =>
@@ -198,4 +199,18 @@ public sealed class CliTests : IDisposable
Assert.Equal(txid, tx.Inputs[0].PrevOut.Hash);
Assert.Equal(0, server.CallCount("blockchain.transaction.broadcast")); // nothing broadcast without --broadcast
}
[Fact]
public async Task Sync_refuses_a_server_on_another_chain()
{
await using var server = new FakeElectrumServer(); // default features: mainnet genesis
await Cli("restore", Mnemonic, "--net", "regtest", "--file", WalletFile());
var (exit, _, err) = await Cli("sync", "--server", $"{server.Host}:{server.Port}",
"--net", "regtest", "--file", WalletFile());
Assert.Equal(1, exit);
Assert.Contains("not on the expected chain", err);
Assert.Equal(0, server.CallCount("blockchain.headers.subscribe")); // rejected before syncing
}
}