Repository navigation
[Bug]: Continue on Let your contacts pay you does not finish #861
Description
What happened?
After importing a Pubky Ring profile with 61 contacts, Continue on "Let your contacts pay you" stays busy for a long time. It does finish. The session log shows why it takes that long.
The follow list itself was immediate: Discovered 61 contacts from pubky.app at 15:04:53 UTC. Seven profile lookups then failed with transport_error / fetch profile, were retried, and fell back to placeholders by 15:06:07 UTC. Imported 61 new contacts is at 15:06:14 UTC, so the save after those lookups was about 8 seconds.
From 15:06:19 UTC through 15:55:48 UTC the log records 236 Failed to refresh Paykit receiver paths warnings. All are transport_error / list_paykit_receiver_paths: list Paykit receiver apps send failed. They are only seven pubkys, about 33–34 failures each, and they are the same seven whose profile fetch failed. The export still contains those retries. That is the long Continue, not the import save.
Expected behavior
Continue finishes promptly after the contacts are saved, with visible progress while any remaining work runs. A contact whose profile lookup already failed should not be retried for the rest of the session.
Steps to Reproduce
- Use Pubky Ring with an identity that follows 61 pubkys, and a Bitkit wallet that has not saved those contacts yet.
- In Bitkit, adopt that Ring identity and wait until the import preview lists the contacts.
- Import the contacts.
- On "Let your contacts pay you", tap Continue.
- Continue stays busy for many minutes, then finishes.
Logs / Screenshots / Recordings
From bitkit_foreground_2026-10-01_14-23-02.log (timestamps are UTC):
- 15:04:53
Discovered 61 contacts from pubky.app - 15:05:00–15:06:07 seven profile lookups fail with
transport_error/fetch profileand become placeholders - 15:06:14
Imported 61 new contacts - 15:06:19–15:55:48 two hundred thirty-six
Failed to refresh Paykit receiver paths, alllist_paykit_receiver_paths: list Paykit receiver apps send failed, only those same seven pubkys
2026-10-01-pay-contacts-continue-hang.log
Bitkit Version
Merged pull request #851 at b7ce9849. App version on the device TBD.
Device / OS
TBD.
Reproducibility
Often (>50%)
Additional context
- Same 61-contact Ring import as on Android. The reporter later saw Continue finish. This log still has the receiver-path retries at 15:55 UTC.
- The preview wait is the seven serialized
fetch profilefailures (15:04:53 to 15:06:07 UTC). That part is what fix: speed up pubky profile loading #854 and fix: speed up pubky profile loading bitkit-android#1399 change. This report is the laterlist_paykit_receiver_pathsretries, which those PRs do not cover. - fix: speed up pubky profile loading #854's 4.6 s figure is a spinner on the Ring row until Pay Contacts opens, not this Continue button.
- Environment: staging Pubky. Log timestamps are UTC.
- Twin: Android issue filed in the same batch and linked after both numbers exist.
Activity
piotr-iohk commentedon Oct 1, 2026
Twin: synonymdev/bitkit-android#1406
ben-kaufman commentedon Oct 1, 2026
The logs show repeated receiver-path lookup failures after the contacts have been saved. Could you retest with #856? It removes receiver-folder discovery, including the lookup producing these warnings. Continue still waits for contact link preparation, so we need to confirm whether that resolves the delay. If it persists, please include logs covering the Continue tap through completion or failure.
piotr-iohk commentedon Oct 2, 2026
Checked on #856 similar to synonymdev/bitkit-android#1406 (comment)
Continue on Let your contacts pay you never seems to finish.
bitkit_logs_2026-10-02_08-36-26.zip
Jasonvdb commentedon Oct 2, 2026
@ben-kaufman, here are iOS measurements for this issue from the same 61-follow test, in case they help with #856. Setup: simulator, staging network, piotr's Pubky Ring follow list, PR #854 at 9d0a526.
Continue (tap → Profile): 104–106 s. Nearly all of it is one publication walk of 102–104 s:
Delete Profile with 61 contacts: 645 s.
removeSavedContactswaiting for the publication lock; it then held the lock for only 0.17 s.What #854 is changing. The per-visit walks come from #854. Opening Contacts reassigned
contacts, which published the saved-contacts event even when no contact was added or removed, and Import published it twice. I'm changing #854 so the event is published only when the saved keys change. That should leave Delete waiting for at most about one walk.#856's area. The walk itself, and the first-come-first-served lock that Delete can't jump, belong there. Two ideas:
The 7 failing follows each fail in about 5–6 s. The Swift bindings never call
rust_future_cancel, so a read that has started holds its limiter slot until it finishes, or until the 30 s Rust timeout.ben-kaufman commentedon Oct 2, 2026
Addressed the blocking contact-preparation path in #856, commit 27e921a; matching Android changes are in eaf6a092b.
Continue now finishes public setup and starts private preparation in the background. Repeated preparations are coalesced, established links are reused, and unavailable lookups have a five-minute cooldown. Link preparation no longer holds the publication lock for the entire contact walk; cleanup invalidates the remaining work, and removed contacts are checked again before publication.
236 focused iOS tests passed, including stalled lookup/cleanup, removed contacts and duplicate preparation. An in-flight SDK call can still take time, and public setup still needs the network. Please retest the 61-contact fixture on the new head; keeping this issue open until that device run confirms the Continue and Delete timings.
ben-kaufman commentedon Oct 3, 2026
Updated #856 at c334a6f and tested a local build with Paykit #171:
Full scope and timings. These are local one-contact tests, not a reproduction of the affected staging identity. They do not close the 61-contact import, request-delivery, withdrawal or backup-stall reports. The latest Android request payment also logged success without the transaction appearing on Bitcoin/Electrum; the broadcast-outcome dependency remains #844 / Android #1384, not part of the SDK polling fix.
392 focused tests passed, and current CI unit/integration tests are green. The remaining staging checks stay open.
piotr-iohk commentedon Oct 6, 2026
Retested on iOS commit 90799f2 on 6 October 2026: the Continue stall did not reproduce in this run.
Setup: iPhone 17 simulator, iOS 26.0, Bitkit 2.5.0 (197), staging Pubky / regtest. Imported the newly created Ring identity Stag3, with no old Paykit data and 62 followed contacts.
All 62 contacts were verified in the contact list. Home, Contacts and Profile remained usable afterwards. The profile was retained; deletion was not tested.
One sample. Timings run from automation tap start to polling detection of the destination, include automation overhead, and exclude pauses between steps. These measure visible import completion; full background Paykit preparation completion was not verified. This is a focused import retest, not full Paykit performance sign-off.
piotr-iohk commentedon Oct 6, 2026
ios-stag3-profile-delete-logs.zip
Follow-up observation from deleting the same Stag3 profile after the successful 62-contact import above, on iOS 90799f2, iPhone 17 simulator / iOS 26.0, staging Pubky / regtest.
Deletion eventually completed, but took approximately 13½ minutes.
The 13m33s measurement is confirmation tap start to logged session removal; the exact UI navigation timestamp was not captured. Background contact preparation overlapped the start of deletion, so this is not a confirmed idle deletion baseline. Remote cleanup was not independently verified. No restart, retry or disconnect was used.
This is a separate deletion observation; the successful import retest above still stands. We can move the slow deletion into a separate ticket if preferred.
ben-kaufman commentedon Oct 6, 2026
Thanks, we missed this bulk profile-deletion flow in the optimization work. It still cleans up contacts one by one. We're fixing it now and will retest with the same 62-contact setup.
ben-kaufman commentedon Oct 6, 2026
Updated #856 in 32fcc1d and Android #1401 in fe364a5cd to published Paykit rc64.
Profile deletion now stops scheduling contact preparation, withdraws once, and blocks/removes eligible contacts in one SDK transaction. Local contact and address-assignment cleanup is batched too. Active subscriptions, busy leases and public contact markers are retained rather than bypassed.
Tested on fresh staging identities with 62 unregistered contacts while background preparation was active:
Both returned to the signed-out profile screen. This is not the same contact fixture as Stag3/Stag4, and the remaining sign-out phase is not yet split into its individual waits, so I wouldn't call the whole performance issue closed.
The original iOS logs also contain two roughly five-minute gaps, one following an unconfirmed write. We cannot prove their cause from those logs. The safety waits are unchanged; these new runs did not encounter them. Please retest the original 62-follow setup on the updated heads, both after preparation settles and immediately after import.
piotr-iohk commentedon Oct 6, 2026
Retest of the immediately-after-import / background preparation active condition from the rc64 fix note, on 32fcc1d.
Fresh Stag6 (
pubky8czkjoykx6jccwcnknaptg5nxuwjyf5kyfxx3bn3hqweycnxftxy) followed the exact original 62-contact Stag3/Stag4 list, verified against public follow records with no missing or extra entries. First Bitkit import for this identity. Device: iPhone 17 simulator / iOS 26.0, staging Pubky, regtest, E2E hooks. Used the user-built, already-installed app; its debug binary matches localaut/Bitkit.app(SHA256d929b71f80c66bab459e21783d4ee630e2a79149d531f6b7e4f187f7f618dcad). Revision attribution is from the user's installed-head confirmation and matching local checkout; the binary has no independently verified embedded commit.UI timings are polling-based observed upper bounds, including automation latency. The three import durations exclude pauses between steps. Deletion navigation was batched immediately after Continue completed; there was no wait for preparation to settle.
The active-preparation condition occurred: confirmation started at 18:52:34.098 UTC; contact preparation logged at 18:52:33.121 before it and 18:52:36.549 after it. The latter was the last contact-preparation entry in the captured window. Logs confirmed Imported 62 new contacts at 18:52:01.333, Deleted all contacts at 18:52:40.933, and Deleted paykit_session at 18:52:48.707. Withdrawal and contact-cleanup start/end boundaries were not individually logged, so the 6.835s milestone includes everything before contact deletion completed.
Returned to the signed-out Ring chooser without a retry, restart or user intervention. Contacts showed the empty Dynamic Contacts intro afterward; wallet remained usable with 100,000 sats Savings / 0 Spending and the Your Name placeholder.
Preparation produced missing Noise-authorization warnings and one authorization-fetch transport warning before deletion, but no import/deletion failure was observed. One incoming-request refresh warning after sign-out reported that shared state requires an active session; it did not block completion.
This is a substantial observed improvement over the earlier Stag3 run (~13m33s to session deletion), but one simulator sample, not a controlled benchmark or full PR sign-off. Both platforms were tested on the same host. Remote cleanup, retained public markers, subscription/busy-lease safety cases were not independently verified; the settled-preparation variant remains to test. Full logs and UI/timing evidence are preserved locally for attachment.
piotr-iohk commentedon Oct 6, 2026
ios-stag6-reimport-logs.zip
Attempted the after-preparation-settles retest on 32fcc1d using the same Stag6 / original 62 follows after its successful immediate-deletion run. This attempt is blocked by re-import, so there is no settled deletion measurement.
Same already-installed staging/regtest/E2E build and iPhone17/iOS26 simulator; no rebuild, reset or reinstall. Stag6 remained on iOS only; Android used its separate Stag7 identity.
Failed to restore peer block after contact save failed: Transport(code: "transport_error", context: "write encrypted Pubky shared state could not be confirmed")(
PubkyService.swift:793).No retry, cancellation, app restart or deletion was performed. The app was left running; logs and the final pending screenshot were preserved.
The log establishes a contact-save/restore failure inside the operation; the outer UI operation was still pending. Code restores previously blocked peers through serial
unblockPeercalls beforesaveContacts; on error it serially re-blocks peers before rethrowing (PubkyService.swift:772–799). This path is relevant to re-import after deletion, but does not prove the root cause of the delay or failed write.This is a reused-identity scenario, distinct from the fresh first-import sample. Settled preparation and deletion remain untested. A fresh identity can isolate the requested settled-deletion condition while this re-import behavior is investigated separately.
ben-kaufman commentedon Oct 6, 2026
The matching re-import fix is pushed in f79cfcb on #856, using published Paykit rc65. Saving the selected contacts and unblocking their peers is now one atomic operation, with no per-peer rollback writes. The original uncertain-write safety wait is unchanged. Please repeat the same-identity 62-contact import on this build; the 277 focused tests and local SDK checks do not establish that the staging stall is resolved. The settled-deletion repeat remains open too.
piotr-iohk commentedon Oct 7, 2026
iOS staging retest on 5dd730b / Paykit 0.1.0-rc68, using Stag6 and the same original 62 followed contacts. iPhone 17 simulator / iOS 26.0.
Three imports and two deletions completed. The previous same-identity re-import stall did not recur in these samples. Strictly settled deletion was not reached: background retries/preparation remained active, and the delayed deletion was performed after the explicitly agreed extra five-minute wait.
Every attempt logged Imported 62 new contacts. No import retry, restart or reinstall was used during this cycle. The app was installed by the tester before this run; this is not an in-place-upgrade result.
Deletion durations start at Yes, Delete. Immediate confirmation was 2.183s after the profile became available, at 07:34:39.112 UTC. Preparation logged before and after it. Delayed confirmation was 08:00:54.841 UTC, 22m53.211s after the second import's 62-contact log.
Delayed-test condition: a read-only debugger inspection found the main preparation task idle, no active link-preparation entries, but a retry task with 62 pending peers. After an additional five minutes (07:54:37–07:59:58 UTC), the main task was still idle and the retry task still existed. Preparation subsequently logged at 08:00:50.864 and 08:00:58.468, across confirmation. Thus this is a successful delayed deletion with background overlap, not a pass for “all preparation/retries settled.”
Contacts were empty in the UI after the immediate deletion. Both deletions logged Deleted all contacts and Deleted paykit_session. After delayed deletion, navigating to Contacts displayed Ring onboarding rather than ContactsIntro, so that empty-list assertion was not established through the UI. The wallet remained usable at 100,000 sats Savings / 0 Spending, and Stag6 is imported again at the end.
Warnings observed: two remote-profile fetch failures per preview produced placeholders; preparation repeatedly reported missing signed Paykit Noise authorizations, some authorization-fetch transport failures, and endpoint queue/publication failures. Those did not prevent the measured imports or deletions. Successful contact import does not establish private payment-link readiness. The initial Edit tap before delayed deletion had no effect; a second tap succeeded before the deletion timer.
Evidence/limits: timestamped DSL results, app logs, preparation probes and screenshots are preserved. UI times are polling-based upper bounds. Installed debug-dylib SHA-256 61fad272498c3a38812f17282d2a7e853fdfceac46d01e8bad001d3ef0ee4484 matches the tester's local artifact; revision provenance is the tester's latest build and matching local HEAD, rather than an embedded commit marker. LLDB inspections briefly paused the app before timing, used field-offset/memory reads only, called no app methods and wrote no app state; the debugger was detached before deletion. These are individual simulator observations, not a matched benchmark or full PR sign-off. Remote marker/lease/subscription safety and a truly settled deletion remain unverified.
This follows the rc64 re-import stall and atomic contact-restore fix note. The import regression was not reproduced here; remaining background preparation/performance can be tracked separately if appropriate.
Evidence: iOS full-cycle logs, timings and screenshots.
The missing-authorization retries may be expected for contacts without Paykit enabled; whether the continued preparation behavior is expected remains to be clarified.
ben-kaufman commentedon Oct 7, 2026
Thanks, this confirms the same-identity re-import stall did not recur with the original 62-contact fixture on rc68, including deletion during preparation. I am keeping the settled/idle case open: the retry queue was still populated and preparation resumed. Missing signed authorizations can be expected for contacts that have not enabled Paykit, but that does not establish that the continued retries or their cost are acceptable. These results verify import/deletion, not private-link readiness or overall performance.
ovitrif commentedon Oct 14, 2026
Moved to 2.7.0. The contact-save stall is fixed (synonymdev/bitkit-android#1395), and RC1 testing showed contact payments working; what remains is the slow import preview and profile timeouts, which are not yet attributed to the app or to staging, so this does not block 2.6.0.