pair_spire: retry create_new_key (and other bunker admin RPCs) on transient timeout #38

Open
opened 2026-07-02 08:43:29 +00:00 by padreug · 1 comment
Owner

Summary

pair_spire makes a chain of nsecbunkerd admin RPCs over NIP-46: create_new_keyensure_policy/get_policiescreate_new_tokenget_key_tokens. When the bunker is briefly slow/overloaded, any of these can time out and surface as a PairingError → 502, failing the whole pairing.

PR #37 added a retry for get_key_tokens (the empty-list race after create_new_token). This follow-up is to extend that resilience to the RPCs that can time out — first and foremost create_new_key.

Observed (bitspire-#70 session, regtest)

Two distinct transient bunker failures during on-hardware pairing testing:

  1. bunker returned no tokens after create_new_tokenget_key_tokens raced empty; the identical call ~25s later succeeded. Fixed in #37 (_get_key_tokens_with_retry, 5× / 0.4s backoff).
  2. bunker admin RPC failed during pairing: no response for 'create_new_key' within 15.0screate_new_key timed out; a plain retry of the mint immediately succeeded. Not yet handled.

Both correlate with the nsecbunkerd being momentarily unresponsive (also seen as no NIP-46 response for 'nip44_decrypt' within 15.0s in the logs around the same time).

Proposal

Wrap the transient-timeout-prone bunker admin calls in pair_spire with a small retry/backoff, so a sluggish bunker doesn't fail an otherwise-valid pairing. At minimum create_new_key; consider create_new_token too.

  • A NsecBunkerError whose message matches the timeout shape (no response for '<method>' within Ns) is transient → retry with backoff (mirror the get_key_tokens helper: a few attempts, ~0.4s backoff).
  • A NsecBunkerNotConfiguredError, or a non-timeout NsecBunkerError (rejection / bad policy), is terminal → fail fast as today.
  • Keep it idempotent-friendly: create_new_key is replace-by-name on the bunker side, so re-issuing on a timeout is safe.

A single small helper (e.g. _bunker_call_with_retry(coro_factory, *, transient_re=...)) reused across the admin calls would keep it tidy, or a decorator on the RPC wrappers.

Out of scope / note

The underlying nsecbunkerd slowness in regtest may be load from heavy test-session churn; this is about making pairing tolerate a slow bunker, not fixing the bunker itself. If the timeouts show up in production under normal load, that's a separate nsecbunkerd perf issue.

Code refs

  • pairing.pypair_spire (the admin RPC chain), _get_key_tokens_with_retry (the existing pattern to generalize).

Related: bitspire-#70, PR #37.

## Summary `pair_spire` makes a chain of nsecbunkerd admin RPCs over NIP-46: `create_new_key` → `ensure_policy`/`get_policies` → `create_new_token` → `get_key_tokens`. When the bunker is briefly slow/overloaded, any of these can time out and surface as a `PairingError` → 502, failing the whole pairing. PR #37 added a retry for **`get_key_tokens`** (the empty-list race after `create_new_token`). This follow-up is to extend that resilience to the RPCs that can *time out* — first and foremost `create_new_key`. ## Observed (bitspire-#70 session, regtest) Two distinct transient bunker failures during on-hardware pairing testing: 1. `bunker returned no tokens after create_new_token` — `get_key_tokens` raced empty; the identical call ~25s later succeeded. **Fixed** in #37 (`_get_key_tokens_with_retry`, 5× / 0.4s backoff). 2. `bunker admin RPC failed during pairing: no response for 'create_new_key' within 15.0s` — `create_new_key` timed out; a plain retry of the mint immediately succeeded. **Not yet handled.** Both correlate with the nsecbunkerd being momentarily unresponsive (also seen as `no NIP-46 response for 'nip44_decrypt' within 15.0s` in the logs around the same time). ## Proposal Wrap the transient-timeout-prone bunker admin calls in `pair_spire` with a small retry/backoff, so a sluggish bunker doesn't fail an otherwise-valid pairing. At minimum `create_new_key`; consider `create_new_token` too. - A `NsecBunkerError` whose message matches the timeout shape (`no response for '<method>' within Ns`) is **transient** → retry with backoff (mirror the `get_key_tokens` helper: a few attempts, ~0.4s backoff). - A `NsecBunkerNotConfiguredError`, or a non-timeout `NsecBunkerError` (rejection / bad policy), is **terminal** → fail fast as today. - Keep it idempotent-friendly: `create_new_key` is replace-by-name on the bunker side, so re-issuing on a timeout is safe. A single small helper (e.g. `_bunker_call_with_retry(coro_factory, *, transient_re=...)`) reused across the admin calls would keep it tidy, or a decorator on the RPC wrappers. ## Out of scope / note The underlying nsecbunkerd slowness in regtest may be load from heavy test-session churn; this is about making pairing *tolerate* a slow bunker, not fixing the bunker itself. If the timeouts show up in production under normal load, that's a separate nsecbunkerd perf issue. ## Code refs - `pairing.py` — `pair_spire` (the admin RPC chain), `_get_key_tokens_with_retry` (the existing pattern to generalize). Related: bitspire-#70, PR #37.
Author
Owner

Fix implemented in PR #37 (commit 2b90590). Rather than a get_key_tokens-only helper, generalised to _bunker_retry, which now wraps every admin RPC in the pair_spire chain (create_new_key, ensure_policy, create_new_token, get_key_tokens):

  • NsecBunkerTimeoutError (the no response for '<method>' within Ns case) → transient, retried with backoff.
  • empty token list (get_key_tokens race) → transient, retried (retry_empty=True).
  • NsecBunkerRpcError (real rejection) / NsecBunkerNotConfiguredError → terminal, fail fast.

create_new_key is replace-by-name and ensure_policy reconciles idempotently, so re-issuing on a timeout is safe. Added test_create_new_key_retries_past_transient_timeout + test_bunker_rejection_fails_fast_without_retry (229 tests pass).

Confirmed reproduction that motivated the broadened scope: on aio-demo, create_new_key and get_key_tokens both 15s-timed-out (502), and a manual retry succeeded — exactly what this now auto-recovers.

Leaving open until PR #37 merges and the fix reaches the deployed instance (per the deploy-not-commit close convention).

Fix implemented in PR #37 (commit `2b90590`). Rather than a `get_key_tokens`-only helper, generalised to `_bunker_retry`, which now wraps **every** admin RPC in the `pair_spire` chain (`create_new_key`, `ensure_policy`, `create_new_token`, `get_key_tokens`): - `NsecBunkerTimeoutError` (the `no response for '<method>' within Ns` case) → transient, retried with backoff. - empty token list (get_key_tokens race) → transient, retried (`retry_empty=True`). - `NsecBunkerRpcError` (real rejection) / `NsecBunkerNotConfiguredError` → terminal, fail fast. `create_new_key` is replace-by-name and `ensure_policy` reconciles idempotently, so re-issuing on a timeout is safe. Added `test_create_new_key_retries_past_transient_timeout` + `test_bunker_rejection_fails_fast_without_retry` (229 tests pass). Confirmed reproduction that motivated the broadened scope: on aio-demo, `create_new_key` and `get_key_tokens` both 15s-timed-out (502), and a manual retry succeeded — exactly what this now auto-recovers. Leaving open until PR #37 merges and the fix reaches the deployed instance (per the deploy-not-commit close convention).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/spirekeeper#38
No description provided.