fix(pairing): default relay to the transport's nostrrelay, not nostrclient proxy
Some checks failed
ci.yml / fix(pairing): default relay to the transport's nostrrelay, not nostrclient proxy (pull_request) Failing after 0s

The nostrclient endpoint is a subscription MULTIPLEXER, not a full relay: its
router forwards a client's EVENT upstream but never returns an OK ack (see
nostrclient/router.py). A transport client that awaits OK on publish therefore
times out ("publish timed out"), so kind-21000 RPCs never complete — verified
on the Sintra: connect succeeded but list_wallets hung, and switching to the
nostrrelay endpoint made the whole flow work (wallet, balance, availability).

default_relay_endpoint now derives from settings.nostr_transport_relays — the
relay the transport actually listens on: use it as-is when already
machine-reachable, or re-home its path on lnbits_baseurl when it's a co-located
loopback relay (the bundled nostrrelay). Validation/localhost-reject and the
pair-dialog pre-fill/hint carry over; wording updated to "transport relay".

227 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-07-02 00:43:54 +02:00
commit 0bb9939822
6 changed files with 89 additions and 49 deletions

View file

@ -35,7 +35,9 @@ from ..pairing import (
)
_BASEURL = "https://lnbits.example.com/"
_NOSTRCLIENT_ENDPOINT = "wss://lnbits.example.com/nostrclient/api/v1/relay"
_TRANSPORT_RELAY = "ws://localhost:5001/nostrrelay/test"
# baseurl host (wss://lnbits.example.com) + transport relay path (/nostrrelay/test)
_DEFAULT_RELAY = "wss://lnbits.example.com/nostrrelay/test"
_NOW = datetime(2026, 6, 16, tzinfo=timezone.utc)
_SPIRE_HEX = "522a4538f1df96508d9ee8b14072344dd4a566acfe03c25a92a39179c6fca891"
@ -53,11 +55,14 @@ def _set_transport_pubkey():
# lnbits_npub in the seed (bitspire#70). Set it for every test; restore after.
prev = settings.nostr_transport_public_key
prev_base = settings.lnbits_baseurl
prev_relays = settings.nostr_transport_relays
settings.nostr_transport_public_key = _LNBITS_HEX
settings.lnbits_baseurl = _BASEURL
settings.nostr_transport_relays = [_TRANSPORT_RELAY]
yield
settings.nostr_transport_public_key = prev
settings.lnbits_baseurl = prev_base
settings.nostr_transport_relays = prev_relays
@pytest.fixture(autouse=True)
@ -327,8 +332,8 @@ def test_missing_passphrase_raises():
)
def test_relays_default_to_nostrclient_endpoint():
# No relays given → derived from lnbits_baseurl → nostrclient proxy endpoint.
def test_relays_default_to_transport_relay():
# No relays given → derived from the transport relay, re-homed on baseurl.
result = asyncio.run(
pair_spire(
_machine(),
@ -336,34 +341,51 @@ def test_relays_default_to_nostrclient_endpoint():
keystore_passphrase=_PASSPHRASE,
)
)
assert _decode_seed(result.seed_url)["relays"] == [_NOSTRCLIENT_ENDPOINT]
assert _decode_seed(result.seed_url)["relays"] == [_DEFAULT_RELAY]
def test_default_relay_endpoint_scheme_map():
prev = settings.lnbits_baseurl
def test_default_relay_endpoint_rehomes_loopback_transport_relay():
prev_base = settings.lnbits_baseurl
prev_relays = settings.nostr_transport_relays
try:
# loopback transport relay → re-homed on the base URL host
settings.nostr_transport_relays = ["ws://localhost:5001/nostrrelay/test"]
settings.lnbits_baseurl = "http://192.168.0.32:5001"
assert default_relay_endpoint() == "ws://192.168.0.32:5001/nostrclient/api/v1/relay"
assert default_relay_endpoint() == "ws://192.168.0.32:5001/nostrrelay/test"
settings.lnbits_baseurl = "https://lnbits.example.com/"
assert default_relay_endpoint() == _NOSTRCLIENT_ENDPOINT
assert default_relay_endpoint() == _DEFAULT_RELAY
# loopback relay but no base URL → can't re-home → None
settings.lnbits_baseurl = ""
assert default_relay_endpoint() is None
finally:
settings.lnbits_baseurl = prev
settings.lnbits_baseurl = prev_base
settings.nostr_transport_relays = prev_relays
def test_no_relays_and_no_baseurl_raises():
prev = settings.lnbits_baseurl
def test_default_relay_endpoint_passes_through_reachable_transport_relay():
prev = settings.nostr_transport_relays
try:
settings.lnbits_baseurl = ""
with pytest.raises(PairingError, match="cannot derive"):
# already remote-reachable → used as-is, base URL irrelevant
settings.nostr_transport_relays = ["wss://relay.aiolabs.dev"]
assert default_relay_endpoint() == "wss://relay.aiolabs.dev"
settings.nostr_transport_relays = []
assert default_relay_endpoint() is None
finally:
settings.nostr_transport_relays = prev
def test_no_relays_and_no_transport_relay_raises():
prev = settings.nostr_transport_relays
try:
settings.nostr_transport_relays = []
with pytest.raises(PairingError, match="none could be derived"):
asyncio.run(
pair_spire(
_machine(), admin_client=FakeBunker(), keystore_passphrase=_PASSPHRASE
)
)
finally:
settings.lnbits_baseurl = prev
settings.nostr_transport_relays = prev
@pytest.mark.parametrize(