fix(pairing): retry all bunker admin RPCs past transient timeouts (#38)
Some checks failed
ci.yml / fix(pairing): retry all bunker admin RPCs past transient timeouts (#38) (pull_request) Failing after 0s
Some checks failed
ci.yml / fix(pairing): retry all bunker admin RPCs past transient timeouts (#38) (pull_request) Failing after 0s
The get_key_tokens retry only covered an empty token list; a transient NsecBunkerTimeoutError on any admin RPC still failed pairing with a 502 (seen live on aio-demo: create_new_key and get_key_tokens both 15s-timed-out, then a manual retry succeeded). Generalise to `_bunker_retry`, wrapping every admin call in the pair_spire chain (create_new_key, ensure_policy, create_new_token, get_key_tokens): a NsecBunkerTimeoutError (and, for get_key_tokens, an empty list) is transient → retry with backoff; a NsecBunkerRpcError rejection or misconfig is terminal → fail fast. create_new_key is replace-by-name and ensure_policy reconciles idempotently, so retrying on timeout is safe. Also: _validate_relay now rejects a host-less ws:// (mint was laxer than the consumer's parseSpireSeed), and stale "nostrclient" comments in the pair UI + a test are corrected to "transport relay". 229 tests pass (incl. timeout-retry + fail-fast-rejection coverage). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0bb9939822
commit
2b90590104
4 changed files with 93 additions and 32 deletions
|
|
@ -297,6 +297,55 @@ def test_pair_raises_when_tokens_stay_empty(monkeypatch):
|
|||
assert calls["n"] == 5 # exhausted all attempts
|
||||
|
||||
|
||||
def test_create_new_key_retries_past_transient_timeout(monkeypatch):
|
||||
# A bunker timeout on any admin RPC (here create_new_key) is transient (#38).
|
||||
import spirekeeper.pairing as pairing_mod
|
||||
from lnbits.core.services.nsec_bunker import NsecBunkerTimeoutError
|
||||
|
||||
async def _no_sleep(*_a, **_k):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(pairing_mod.asyncio, "sleep", _no_sleep)
|
||||
|
||||
bunker = FakeBunker(token_secret="s") # pragma: allowlist secret
|
||||
real = bunker.create_new_key
|
||||
calls = {"n": 0}
|
||||
|
||||
async def flaky(name, passphrase):
|
||||
calls["n"] += 1
|
||||
if calls["n"] == 1:
|
||||
raise NsecBunkerTimeoutError("no response for 'create_new_key' within 15.0s")
|
||||
return await real(name, passphrase)
|
||||
|
||||
bunker.create_new_key = flaky
|
||||
result = _pair(bunker)
|
||||
assert calls["n"] == 2 # timed out once, then succeeded
|
||||
assert result.spire_npub == _SPIRE_NPUB
|
||||
|
||||
|
||||
def test_bunker_rejection_fails_fast_without_retry(monkeypatch):
|
||||
# A real rejection (NsecBunkerRpcError) is terminal — no retry.
|
||||
import spirekeeper.pairing as pairing_mod
|
||||
from lnbits.core.services.nsec_bunker import NsecBunkerRpcError
|
||||
|
||||
async def _no_sleep(*_a, **_k):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(pairing_mod.asyncio, "sleep", _no_sleep)
|
||||
|
||||
bunker = FakeBunker()
|
||||
calls = {"n": 0}
|
||||
|
||||
async def rejecting(name, passphrase):
|
||||
calls["n"] += 1
|
||||
raise NsecBunkerRpcError("policy rejected")
|
||||
|
||||
bunker.create_new_key = rejecting
|
||||
with pytest.raises(PairingError, match="bunker admin RPC failed"):
|
||||
_pair(bunker)
|
||||
assert calls["n"] == 1 # terminal — not retried
|
||||
|
||||
|
||||
def test_bunker_relay_defaults_to_spire_event_relay():
|
||||
"""No explicit bunker_relay -> the relay baked into bunker_url is the spire's
|
||||
own public event relay (relays[0]), NOT lnbits's internal bunker URL. This
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue