Skip to content

[Bug]: Continue on Let your contacts pay you does not finish #861

Open

Description

@piotr-iohk

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

  1. Use Pubky Ring with an identity that follows 61 pubkys, and a Bitkit wallet that has not saved those contacts yet.
  2. In Bitkit, adopt that Ring identity and wait until the import preview lists the contacts.
  3. Import the contacts.
  4. On "Let your contacts pay you", tap Continue.
  5. 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 profile and 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, all list_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 profile failures (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 later list_paykit_receiver_paths retries, 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

  1. piotr-iohk commented on Oct 1, 2026

    @piotr-iohk
    CollaboratorAuthor
  2. added theissue type on Oct 1, 2026
  3. ben-kaufman commented on Oct 1, 2026

    @ben-kaufman
    Contributor

    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.

  4. added this to the 2.6.0 milestone on Oct 2, 2026
  5. piotr-iohk commented on Oct 2, 2026

    @piotr-iohk
    CollaboratorAuthor

    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

  6. Jasonvdb commented on Oct 2, 2026

    @Jasonvdb
    Contributor

    @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:

    • Per-contact loop, 82.5–83.5 s:
      • Receiver-path discovery: 45–47 s, including about 14 s waiting for the SDK lock to read each contact record.
      • Receiver-path selection reads: 28–29 s.
      • Linking 4 receiver paths, for the 3 contacts that use Paykit: about 8 s.
      • Building endpoints: 0.6 s.
    • After the loop: payment list sync 1.8 s, then message drain 18 s (4 link retries, plus process and receive twice).
    • Duplicate walks: Import published the saved-contacts event twice. That started 2 extra link-preparation walks over the same 61 contacts, which ran alongside Continue and competed for read slots and the SDK lock.

    Delete Profile with 61 contacts: 645 s.

    • Lock wait: 639 s of that was removeSavedContacts waiting for the publication lock; it then held the lock for only 0.17 s.
    • Queued ahead of it: the end of a payment request polling walk, a contact-sync walk (90 s) and 5 prepare walks of 99–113 s each, one per Contacts visit.
    • Actual work: removing the 61 contacts in the SDK took 2 s.
    • After removal: the queued walks kept running over the removed, blocked contacts, logging 857 "is blocked" warnings.

    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:

    • A walk could drop keys that are no longer saved once it gets the lock.
    • Only one pending walk could be kept.

    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.

  7. ben-kaufman commented on Oct 2, 2026

    @ben-kaufman
    Contributor

    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.

  8. ben-kaufman commented on Oct 3, 2026

    @ben-kaufman
    Contributor

    Updated #856 at c334a6f and tested a local build with Paykit #171:

    • Public request-capability discovery runs eight contacts at a time, outside the SDK mutation queue. Intake and discovery no longer wait for all-contact preparation. Retry drains only process the affected peers.
    • The warm request-recipient list appeared within 0.6 seconds. Sharing OFF completed in 3.1 seconds on the final SDK build (1.9 seconds in the earlier local run).
    • Paykit [Bug]: Activity: no activity after sending lightning payment #171 adds bounded concurrent inbox probes/outbound peers and removes redundant shared-state reads. This is a separate SDK improvement to consume after merge/release; feat: share paykit state across apps #856 still uses rc59.

    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.

  9. piotr-iohk commented on Oct 6, 2026

    @piotr-iohk
    CollaboratorAuthor

    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.

    Step Observed time
    Select Ring identity → profile/contact preview 18.7s
    Import All → Let your contacts pay you 1.8s
    Continue → profile screen 13.7s

    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.

  10. piotr-iohk commented on Oct 6, 2026

    @piotr-iohk
    CollaboratorAuthor

    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.

    • Contact cleanup finished after 13m21s, with a warning that one contact deletion failed because an encrypted Pubky shared-state write could not be confirmed. The flow continued with best-effort cleanup.
    • Paykit session removal was logged after 13m33s from confirming “Yes, Delete”.
    • The Edit Profile screen remained disabled with a spinner throughout the 10m33s observation window, with no success or failure dialog during that time. Later verification confirmed that both Profile and Contacts opened the Ring identity chooser.

    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.

  11. ben-kaufman commented on Oct 6, 2026

    @ben-kaufman
    Contributor

    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.

  12. ben-kaufman commented on Oct 6, 2026

    @ben-kaufman
    Contributor

    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:

    Phase iOS Android
    Withdrawal, including waiting for active preparation 9.2s 18.8s
    Contact cleanup 4.2s 5.4s
    Remaining profile/public cleanup and sign-out 20.8s 24.4s
    Total 34.3s 48.5s

    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.

  13. piotr-iohk commented on Oct 6, 2026

    @piotr-iohk
    CollaboratorAuthor

    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 local aut/Bitkit.app (SHA256 d929b71f80c66bab459e21783d4ee630e2a79149d531f6b7e4f187f7f618dcad). Revision attribution is from the user's installed-head confirmation and matching local checkout; the binary has no independently verified embedded commit.

    Measurement Observed result
    Select Ring identity → import preview (62 friends) 19.138s
    Import All → enabled Continue 1.875s
    Continue → profile/Edit available 12.876s
    Profile available → Yes, Delete confirmation 3.353s
    Delete confirmation → all contacts deleted (log) 6.835s
    Delete confirmation → Paykit session deleted (log) 14.609s
    Delete confirmation → signed-out profile screen detected 15.932s

    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.

  14. piotr-iohk commented on Oct 6, 2026

    @piotr-iohk
    CollaboratorAuthor

    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.

    • Ring selection → preview: 15.475s, with 62 friends.
    • Import All tapped at 19:01:35.681 UTC.
    • At 19:07:17.956 UTC (5m42.275s after the tap), the log reported:
      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).
    • Final UI inspection at 19:12:08.866 UTC, 10m33.185s after Import All, still showed the spinner with Import All/Select disabled. No Continue, re-import completion or final user-facing import error was observed during this window. The 10m33s value is a pending-duration lower bound, not completion latency.

    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 unblockPeer calls before saveContacts; 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.

  15. ben-kaufman commented on Oct 6, 2026

    @ben-kaufman
    Contributor

    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.

  16. piotr-iohk commented on Oct 7, 2026

    @piotr-iohk
    CollaboratorAuthor

    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.

    Import Ring selection → 62-contact preview Import All → Continue Continue → profile
    Reuse Stag6 after the earlier rc64 deletion/failure 16.440s 1.830s 9.215s
    Re-import after the immediate rc68 deletion 15.890s 1.806s 8.993s
    Re-import after the delayed rc68 deletion 16.078s 1.358s 9.530s

    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 Contacts removed, log Paykit session removed, log Profile onboarding observed
    Immediately after import, preparation active 6.495s 15.859s 17.068s
    Delayed, retries/preparation still pending 6.227s 14.200s 15.324s

    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.

  17. ben-kaufman commented on Oct 7, 2026

    @ben-kaufman
    Contributor

    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.

  18. modified the milestones: 2.6.0, 2.7.0 on Oct 14, 2026
  19. ovitrif commented on Oct 14, 2026

    @ovitrif
    Collaborator

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions