fix(pairing): retry get_key_tokens past a transient empty result
Some checks failed
ci.yml / fix(pairing): retry get_key_tokens past a transient empty result (pull_request) Failing after 0s

pair_spire failed with "bunker returned no tokens after create_new_token" when
the nsecbunkerd was briefly slow — get_key_tokens listed nothing in the few ms
after create_new_token before the write landed. Observed live: a nip44_decrypt
timeout, then a 502 on pair, then the identical call 25s later succeeding.

Retry get_key_tokens up to 5x with a 0.4s backoff before giving up, so pairing
survives a sluggish bunker instead of flaking. Tests cover the transient-empty
recovery and the exhausted-attempts failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-07-02 00:29:08 +02:00
commit 765b07737b
2 changed files with 68 additions and 1 deletions

View file

@ -248,6 +248,50 @@ def test_malformed_token_raises():
_pair(bunker)
def test_get_key_tokens_retries_past_a_transient_empty(monkeypatch):
# A slow bunker can list no tokens right after create_new_token; retry.
import spirekeeper.pairing as pairing_mod
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.get_key_tokens
calls = {"n": 0}
async def flaky(key_name):
calls["n"] += 1
return [] if calls["n"] == 1 else await real(key_name)
bunker.get_key_tokens = flaky
result = _pair(bunker)
assert calls["n"] == 2 # empty once, then the token
assert result.spire_npub == _SPIRE_NPUB
def test_pair_raises_when_tokens_stay_empty(monkeypatch):
import spirekeeper.pairing as pairing_mod
async def _no_sleep(*_a, **_k):
pass
monkeypatch.setattr(pairing_mod.asyncio, "sleep", _no_sleep)
bunker = FakeBunker()
calls = {"n": 0}
async def always_empty(key_name):
calls["n"] += 1
return []
bunker.get_key_tokens = always_empty
with pytest.raises(PairingError, match="no tokens"):
_pair(bunker)
assert calls["n"] == 5 # exhausted all attempts
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