Skip to content

fix: recover interrupted paykit sessions - #1339

Open
ben-kaufman wants to merge 5 commits into
masterfrom
fix/paykit-clock-recovery
Open

ben-kaufman wants to merge 5 commits into
masterfrom
fix/paykit-clock-recovery

Conversation

@ben-kaufman

@ben-kaufman ben-kaufman commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

A failed Paykit session restore after connection loss or a device clock change could make an existing profile appear missing and discard contacts. This PR preserves saved identity data, retries recovery when connectivity returns or the app resumes, and corrects retry and billing-reminder timing.

Fixes #1344.

Counterpart: iOS PR.

Related: #1334 also changes the persisted-identity lookup as part of backup protection; that overlapping hunk needs reconciling when both PRs merge.

Description

  • Automatically retries saved-session restoration on reconnect and foreground from any screen, coalescing concurrent attempts. Recovery cannot overwrite an active authorization or reinstate a session after teardown.
  • Reads the saved identity without restoring its old grant, and preserves unreadable SDK state so a failed reconnect cannot treat existing contacts as a fresh identity.
  • Retains cached profile/contact overrides after restoration fails and limits failed Ring-auth cleanup to a newly installed session, preserving the previous or unreadable session.
  • Keeps Pubky signup disabled while an existing identity is saved, including when its credentials are temporarily unreadable.
  • Clears cached profile/contact overrides on a confirmed identity change while preserving same-identity recovery and legacy backup caches.
  • Resumes identity publication after a backward clock correction instead of waiting for an old future timestamp.
  • Rechecks the billing date when a subscription notification worker runs, retrying if the period is still in the future.

Out of Scope

  • Pubky grant and payment-expiry validation: real expiration and authorization checks remain enforced; no backend clock-skew bypass.
  • Notification delivery precision: WorkManager remains best effort and can delay reminders through OS scheduling or retry backoff.
  • Wallet backup/restore protection: tracked separately in fix: preserve paykit payment state #1334. The reported missing Bitcoin wallet remains unconfirmed and is not claimed fixed here.

Design

N/A — no UI changes.

Preview

Android offline-start/reconnect recordings were captured locally: the original build lost the profile name and did not recover; the fixed build retained the name and contact and restored the same identity about 1.7 seconds after reconnecting. Live clock-change recording remains pending.

QA Notes

Journeys

N/A — not drivable; see Manual Tests.

Manual Tests

Live grant-session clock-change E2E has not been rerun. Android offline cold-start/reconnect and contact preservation were verified on a disposable regtest emulator. The full procedure is in paykit-clock-changes.md.

  • regression: with a saved profile/contact and valid local or Ring session, cold-start offline, then reconnect or foreground from the contact/profile screen → cached data stays present and the same identity recovers without signing out or reauthorization; an expired/revoked grant still needs authorization — network fault injection not in Capabilities.
  • regression: start Ring authorization, sign out or reset while recovery is pending → automatic recovery cannot replace the authorization or resurrect deleted credentials — network fault injection not in Capabilities.
  • regression: use fresh test wallets with a saved contact, covering local-secret and Ring sessions → move the device clock a month ahead, attempt recovery, correct it and retry; repeat with a backward change → saved contacts/profile remain available and private payments recover, with reauthorization if the grant expired — isolated device-clock control not in Capabilities.
  • regression: after failed restoration, open the profile button and reauthorize with Ring without signing out → the same identity keeps its cache; a different identity cannot display the old name/avatar or contact labels even when its profile is unavailable — device-clock fault injection not in Capabilities.
  • regression: change only the timezone between America/New_York, Pacific/Kiritimati and Pacific/Pago_Pago, including a daylight-saving boundary → authentication and UTC billing boundaries stay unchanged — OS timezone and date control not in Capabilities.
  • regression: schedule a subscription reminder and defer an unavailable payment request, then change the clock → no payment-due reminder before its billing boundary, and retries recover after time is corrected — device-clock fault injection not in Capabilities.

Automated Checks

  • updated PaykitSdkServiceTest.kt — identity lookup failures stop activation without deleting saved state or credentials; activation tests cover same/different owners, normalized keys, legacy backup caches, and cache-reset failure.
  • updated PubkyAuthHandlerRegistrarTest.kt and AppViewModelSendFlowTest.kt — saved/unreadable identities do not advertise signup; reconnect and foreground trigger recovery.
  • updated PubkyRepoTest.kt — automatic retry after failure, unreadable credentials, queued wipe, active Ring authorization, and cancelled completion racing retry; restoration preserves profile data for retry; failed Ring auth cleans up only a session installed by that attempt, including unreadable ownership and cancellation coverage; identity switches discard stale in-memory profile/contact data and reset the contact-load marker.
  • updated PubkyIdentityRepublishTest.kt — clock rollback retries publication and then resumes throttling.
  • updated PaykitSubscriptionNotificationSchedulerTest.kt — a worker running before the billing boundary defers its reminder.
  • updated PaykitSubscriptionTest.kt — timezone and DST changes preserve UTC billing boundaries.
  • ran local Gradle compile, full unit tests and Detekt with a command-scoped init script excluding stale Maven Local artifacts — all 2,911 tests passed across 192 suites, with no failures or skips. Detekt has no findings in changed files and 15 existing findings in untouched files. Final cleanup also passed all 110 PubkyRepo tests, compile and APK build. No host or funded test-device clock was changed.

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Regtest APK

Built from 5bc92b8 (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

@greptile-apps

greptile-apps Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

The PR should not merge until preserved Pubky cache data is isolated when recovery proceeds with a different identity.

Findings

  1. P1 Cached identity data crosses accounts ▶

Summary

The PR preserves Paykit session and cached Pubky data across failed recovery, retries identity publication after clock rollback, and prevents subscription reminders from posting before their billing boundary.

  • Adds recovery, timing, and timezone tests plus a manual clock-change journey.
  • The preserved Pubky cache needs an identity boundary before a different account can authenticate.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Identity A restoration fails] --> B[Global Pubky cache retained]
  B --> C[Identity B authenticates through Ring]
  C --> D[B contacts loaded]
  B --> D
  D --> E[A contact overrides may affect B]
Loading

Reviews (1) · Last reviewed commit: "fix: recover paykit after clock changes"

}
is InitResult.RestorationFailed -> {
clearAuthenticatedState()
clearAuthenticatedState(clearCachedProfile = false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cached identity data crosses accounts If identity A’s saved session fails to restore and the user then authorizes identity B through Ring, this branch keeps A’s cached data. The cache is not separated by identity, and B’s authentication does not clear it. As a result, A’s saved contact overrides can change how B’s contacts appear, and A’s name or image can appear while B’s profile is unavailable. Keep the cache for a retry with the same identity, but clear or separate it when the identity changes.

Knowledge Base Used: Pubky identity and profile

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9890462. Activation now clears the cached profile and contact overrides when the saved SDK owner changes. Same-identity recovery keeps them. A missing SDK owner also preserves restored legacy backup overrides.

Ring completion clears stale in-memory profile/contacts and resets the contact-load marker before loading the new identity. Regression coverage includes same/different owners, normalized keys, legacy backups, and cache-reset failure. All 2,903 Android tests passed.

Applied the matching cache fix and corrected the profile recovery route on iOS #796 too.

@ben-kaufman ben-kaufman changed the title fix: recover paykit after clock changes fix: recover interrupted paykit sessions Sep 25, 2026
@ovitrif ovitrif removed this from the 2.6.0 milestone Sep 25, 2026

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Pubky profile falls back to "Your Name" after Shop Paykit setup

2 participants