fix: clean up deleted private contacts - #1372
Conversation
There was a problem hiding this comment.
detekt found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
|
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
jvsena42
left a comment
There was a problem hiding this comment.
Two MEDIUM and one LOW inline. Paykit is now on by default on master (#1359), unreleased; findings 1 and 2 also affect opted-in users on released builds.
Checked and clean:
ensurePubkyPrefix()matchesPaykitPublicKeys.normalize, soblockPeerhits the stored peer record.- Backup notification is preserved via
withStateRevisionTracking. blockPeerfailing aborts beforeremoveContact, and a retry re-blocks idempotently.- Only explicit add/import paths unblock, and the new
check(restorePrivateConnection || existing != null)stops background savers recreating a deleted contact. - All
_contactswriters bump the revision. - Both pay paths gate on blocked peers in
accept()before sending. - Per-identity state is guarded by
isCurrentState.
jvsena42
left a comment
There was a problem hiding this comment.
Re-checked 50799d9.
Open:
- The subscription thread has a MEDIUM follow-up, posted as a reply: the
ActiveSubscriptionrefusal is wrapped byServiceQueueand never matches, so Delete silently does nothing. It also has a LOW on fixed-term subscriptions. - One LOW inline: a failed re-add leaves peers unblocked.
Resolved: the test/journey history thread.
Checked and clean:
- The subscription guard runs before withdrawal and blocking, so nothing is hidden, and re-add after cancel does not revive periods.
- Private-list withdrawal runs before
blockPeerunderoperationMutex. - The
loadContactsreload loop keeps the mutex and completes only on a fresh snapshot. - Payment authorization: Lightning checks after channel readiness and cleans up proof/metadata on denial; on-chain checks after
markOnchainPaymentStartedwithout marking the payment uncertain; HW checks the cached path too;accept()re-checks under the repo mutex with no deadlock. No path blocks a legitimate payment except failing closed on an SDK read error.
There was a problem hiding this comment.
Advice: ✅ Approve
Review: diff 18 files.
Pair PR synonymdev/bitkit-ios#830: equivalent.
Findings:
1 inline (1 LOW)
QA:
Tests queued.
Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)
There was a problem hiding this comment.
Advice: ✅ Approve
Interim test report for the review; the rest resumes on its own.
QA:
No test ran: 548 unit tests failed: missing Conscrypt JNI and unsupported Robolectric runtime on Linux aarch64.
0 of 2 ran; the other 2 could not run on our setup, so this stays Advice.
Tests J1, J2 ⏭️
Test J1
Not verified yet: 548 unit tests failed: missing Conscrypt JNI and unsupported Robolectric runtime on Linux aarch64.
log
LightningNodeServiceTest > non-pending payment skips notification FAILED
java.lang.UnsatisfiedLinkError: no conscrypt_openjdk_jni-linux-aarch_64 in java.library.path
at org.conscrypt.NativeLibraryUtil.loadLibrary(NativeLibraryUtil.java:54)
LightningNodeServiceTest > foreground service timeout stops service before node stop completes FAILED
java.lang.AssertionError: The Robolectric native runtime is not supported on Linux (aarch64)
3018 tests completed, 548 failed
Test J2
Not verified yet: 548 unit tests failed: missing Conscrypt JNI and unsupported Robolectric runtime on Linux aarch64.
Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)
jvsena42
left a comment
There was a problem hiding this comment.
Re-checked 627936b and merge 3bc4481. The silent-Delete MEDIUM and the re-add rollback are resolved. The fixed-term refusal wording was declined here; iOS took the end-date option, and ovi-reviewer already raised that parity gap.
One LOW inline: hardware retries skip the peer check, the twin of the accepted iOS fix, with a fix shape that keeps an already-attempted signed tx.
jvsena42
left a comment
There was a problem hiding this comment.
Re-checked 5114d34 and merge cb4b300. No findings; the HW retry thread is resolved.
Checked and clean:
- A denial after an attempt returns before any proof cleanup or sheet hide, so the dispose never drops the tx.
- A denial leaves
isBroadcastUnresolvedfalse, andisSigningresets infinally, so the sheet stays dismissable. - The merge's conflict resolutions in PR-owned files do not change behaviour.
Pre-existing, out of scope: Back after an uncertain attempt still fails the started proof.
There was a problem hiding this comment.
Verdict: ✅ Approve
Tests for the review: both journeys passed.
QA:
Tested on two Android 15 emulators.
Tests J1, J2 ✅
Test J1
Deleting a contact stops incoming private Payment Requests until the user adds the contact again.
J1-1.mp4 | J1-2.mp4 |
![]() | ![]() | ![]() |
Test J2
An active subscription must end before its contact can be deleted.
J2-setup.mp4 | J2.mp4 | J2-post.mp4 |
![]() | ![]() | ![]() | ![]() | ![]() | ![]() |
Tip
Tests J1, J2 worth a journey
Test J1
- Send a 5,000-sat Before deletion request from REQUESTER to PAYER2.
- Verify FROM REQUESTER and close the confirmation without paying.
- Delete REQUESTER from payer Contacts and confirm absence immediately and after refresh.
- Verify Before deletion is absent from payer Payment Requests.
- Send After deletion from REQUESTER and wait two Home polling intervals on payer.
- Verify no confirmation or payable request appears.
- Force-stop and relaunch payer, wait two polling intervals, and verify Payment Requests remains empty.
- Explicitly re-add REQUESTER on payer and keep both apps foreground until linked.
- Send After readd and verify its confirmation shows REQUESTER, then close without paying.
Test J2
- With Journey Sub active, open REQUESTER in payer Contacts, select Delete Contact, and confirm.
- Observe the active-subscription error toast for a 30-second timeline and verify REQUESTER remains saved.
- Open Subscriptions and verify Journey Sub remains Active.
- Open Journey Sub, swipe to cancel, and verify its status is Expired.
- Return to REQUESTER, select Delete Contact again, and confirm.
- Verify empty Contacts after deletion.
- Explicitly re-add REQUESTER's Pubky key and keep both apps foreground.
- Return Home and wait two foreground request polling intervals.
- Verify Journey Sub remains Expired and no subscription payment confirmation or request appears.
Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)









Closes #1380
This PR fixes private payments remaining available after a contact is deleted and contacts reappearing after a successful deletion.
iOS companion: synonymdev/bitkit-ios#830
Description
Out of Scope
Design
N/A — no design available. Adds an error message to the existing contact-deletion toast.
Preview
VIDEO_1
QA Notes
Journeys
new
delete-and-readd-contact.xml— deletion survives refresh/restart, private requests stop, and explicit readd reconnects.new
delete-contact-with-active-subscription.xml— deletion requires the subscription to end, and re-add does not revive a canceled subscription.Both journeys are mirrored on iOS and Android and have not been run locally.
Manual Tests
N/A
Automated Checks
PaykitSdkServiceTest.kt— receiver revocation, withdrawal before block including delivery failure, failed re-add retry, and subscription deletion guards for payer/payee, cancellation and expiry.PubkyRepoTest.kt— invalidated initial loads preserve unrelated contacts and cannot resurrect deleted ones.PaykitPaymentRequestRepoTest.kt— authorization is rechecked after mutex waits and history follows SDK blocked-peer visibility.AppViewModelSendFlowTest.kt— denied Lightning dispatch cleans up associated proofs even when initial preparation failed, and hardware preparation rechecks cached authorization.The earlier onchain end-to-end CI job failed before tests because Docker could not bind an occupied host port. The new two-wallet journeys have not been run.
Deletion can still wait for existing SDK operations or endpoint-withdrawal network timeouts. No end-to-end deletion latency claim is made.