fix(#9): close race between create_new_key and NIP-46 connect #10
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "issue-9-fix-create-new-key-race"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #9.
Summary
Two-layer fix for the race where a fresh client chaining
create_new_key+ NIP-46connecton the same target key would time out — the bunker had no subscription registered for the new key by the time the connect event arrived at the relay.Repro before this PR
Surfaced by
aiolabs/lnbitsPR #33's eager-bind chain, which publishes a NIP-46connectevent in the same HTTP round-trip ascreate_new_key. Pre-PR-#33 lnbits deferred the connect to firstsign_event(minutes-to-hours after provisioning), so the race window was hidden.On bohm regtest with
aiolabs/lnbitsPR #33 + this branch reverted:Bunker logs: zero
REQUEST_IN method=connecton kind-24133 in the same window. The connect event reached the relay (lnbits/nostrrelay extension logged_handle_eventon the right kind), but the bunker's NIP-46 listener wasn't subscribed yet.What changed
Layer 1 —
src/daemon/run.tsloadNsecandunlockKeywere synchronous and fire-and-forgot the asyncstartKeypromise.create_new_key.ts:35already awaitedloadNsec, but the await was a no-op against a sync return. Promoted both toasyncand properly awaitedstartKey. This was necessary but not sufficient —backend.start()itself was still returning before the relay registered the subscription.Same shape change for
unlockKey.Layer 2 —
src/daemon/backend/index.tsNDKNip46Backend.start()(parent class, from@nostr-dev-kit/ndk@2.8.1):this.ndk.subscribe(...)returns immediately — theNDKSubscriptionqueues a REQ on the relay connection but the relay's EOSE acknowledgement hasn't arrived yet. Any caller that publishes a NIP-46 event in the immediate window afterstart()returns races against the relay registering this subscription.This is the actual race-closer. Layer 1's await alone wasn't enough because
start()was still returning before the relay registered the subscription.Overrode
start()in ourBackendsubclass to await EOSE before resolving:By the time
start()resolves, the relay has confirmed it has the bunker's subscription on file and will route matching kind-24133 events to it.Verification
End-to-end on bohm regtest stack with
aiolabs/lnbitsPR #33 (b1694186) + this PR (65a6966):http://192.168.0.32:5181/login) completes cleanly in ~3s end-to-end.NsecBunkerTimeoutError, noToken already redeemed.Related
aiolabs/lnbits#32/ PR #33 — the chain that surfaced this~/dev/coordination/log.mdentries2026-05-30T07:00Zthrough2026-05-30T10:30Zdocumenting the debugging arc.Test plan
create_new_key→connectloop run repeatedly (e.g. 100 iterations) without artificial sleep. Worth adding to the test suite as a regression guard. Out of scope for this PR.🤖 Generated with Claude Code