Refuse an Electrum server that is not on the PLM chain (B-99, ELC-30)

PLM shares Bitcoin's genesis and powLimit, so a Bitcoin server passed every
header check. connect() now asks server.features and requires PLM's genesis.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-18 00:32:24 +02:00
1 parent 1433813ec7
commit 2c0e71a74c
4 files changed
+72 -2

No files matched your search

+37
View File
@@ -325,12 +325,30 @@ class _PinWriter(FakeWriter):
self.closed = True
def _answer_features(reader, writer, result=None):
"""Make `writer` answer `server.features` the moment it is asked: a reply fed up front
would be read before its request is registered, and dropped."""
from app.wallet.plm_network import GENESIS_HASH
original = writer.write
def write(data):
original(data)
sent = json.loads(data.decode())
if sent["method"] == "server.features":
reply = {"genesis_hash": GENESIS_HASH} if result is None else result
reader.feed_data((json.dumps({"id": sent["id"], "result": reply}) + "\n").encode())
writer.write = write
async def _connect_with_pin(monkeypatch, pin, der):
import hashlib # noqa: F401
writer = _PinWriter(der)
reader = asyncio.StreamReader()
reader.feed_data(b'{"id": 1, "result": ["fake", "1.4"]}\n')
_answer_features(reader, writer)
async def fake_open_connection(host, port, **kwargs):
return reader, writer
@@ -413,3 +431,22 @@ async def test_only_a_server_error_object_is_an_explicit_refusal():
silent._read_task.cancel()
finally:
client_module._REQUEST_TIMEOUT_SECONDS = original
async def test_a_server_on_another_chain_is_refused_at_the_handshake(monkeypatch):
"""ELC-30: Bitcoin's genesis (or no genesis at all) fails like an unreachable server."""
import pytest
bitcoin = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26e"
for result in ({"genesis_hash": bitcoin}, {}):
writer = FakeWriter()
reader = asyncio.StreamReader()
reader.feed_data(b'{"id": 1, "result": ["fake", "1.4"]}\n')
_answer_features(reader, writer, result)
async def fake_open_connection(host, port, reader=reader, writer=writer, **kwargs):
return reader, writer
monkeypatch.setattr(asyncio, "open_connection", fake_open_connection)
with pytest.raises(ElectrumError, match="not on the PLM chain"):
await ElectrumClient("localhost", 1).connect()