Repository navigation
fix: defer proof delivery after durable handoff - #1458
ben-kaufman wants to merge 1 commit into
Conversation
|
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
talosmachina
left a comment
There was a problem hiding this comment.
No findings. Bumps paykit to rc73, moves the contact and payment-request display refreshes onto the single observeState() transaction, and lets proven Lightning, on-chain and hardware completions return once the proof is persisted, leaving delivery to reconcile(). Reviewed 268293a, full tier. Reasoned from the code and CI; per the repo's agent rules this branch is not built here.
What I checked, and 4 candidates I ruled out
Read in full: the diff of all 17 files; persistAndSubmit, reconcile, reconcileProof, reconcileLightningProof, reconcileOnchainProof, reconcileHardwareOnchainProof, submitReady, completeOnchainPayment and paymentRequestStateChanges in PaykitPaymentProofRepo.kt; refresh and fetchRequestSnapshot in PaykitPaymentRequestRepo.kt; loadContacts, currentSignIn and isCurrent in PubkyRepo.kt; observeState, withPaykitKey and refreshPaykitKey in PaykitSdkService.kt; observe_state and state_observation_in_transaction in pubky/paykit-rs#183.
Call sites traced: completeLightningPayment (handlePaymentSuccessful), completeOnchainPayment (completeOnchainPaymentProofInBackground, completeRecoveredOnchainPayment), the hardware original-payment observer, every reconcile() caller (refreshIncomingPaykitPaymentRequests, the not-dispatched send error, BackupRepo).
CI: CI (build, unit tests), build-local, lint, detekt and Greptile green at this head; the e2e-tests-local shards were still pending when I read them.
Ruled out
- A persisted proof that nothing delivers: the persist bumps
PaykitPaymentProofStore.backupStateVersion,paymentRequestStateChangesseesproofDatamove from null to the preimage or txid, and the observer now passesreconcileProofs = true, soreconcile()reachessubmitReady. That covers the one-time on-chain path too, wherecompleteOnchainPaymentProofInBackgroundonly refreshes whenbillingPeriod != null. The Lightning path also gets the FULL refresh right aftercompleteLightningPayment. - Reconcile looping on its own writes:
submitReadyremoving the delivered proof changes the observed state once, thendistinctUntilChangedholds; an undeliverable proof is not re-persisted byreconcile(), so offline does not spin. Two concurrent reconciles are covered by thealreadyQueuedcheck underoperationMutex. - Changed meaning of
completedfor the Shop follow-up:persistAndSubmitalready returned true on a successful persist before this change, socompleteAcceptedShopFollowupfires on the same condition; only the wait forsubmitReadyis gone, and the persist-failure branch still delivers immediately. - Contacts on a session without the Paykit secret:
observe_staterequires the identity secret, while the oldcontact_records()needed only an initialized identity, so a restored session whose local key cannot be read (a Ring-adopted identity while the Ring provider fails the trust check) now fails the contacts load where it used to succeed. Warm contacts are kept and the trigger is a transient provider failure, so I ruled it out, narrowly.
Merge confidence: 4/5, the contacts candidate above was ruled out only narrowly and the e2e shards had not finished.
This PR:
Related work: SDK #183, iOS #914, and Server #78. This consumer pins the published rc73 prerelease.
Description
Proof persistence failure retains the existing immediate-delivery fallback. If neither persistence nor delivery succeeds, the unresolved follow-up and payment guard remain. Unknown payment outcomes remain unresolved; per-request execution claims, save-before-send ordering, exact proof/ciphertext handling, and duplicate-payment prevention are unchanged. Cancellation retains native-operation ownership until the native call completes; refreshes retain runtime, identity, sign-in, and key-generation guards.
The normal display request-snapshot read phase changes from three getter calls (identity validation, request list, and linked peers) to one SDK observation transaction. Message intake, authoritative action reads, and checkpoint-driven rereads remain separate and unchanged. Contact-only loading already used one transaction; reading a full observation adds projection/filtering work, so no contact speedup is claimed. Existing warm StateFlows, coalescing, and persistence are reused, with no new cache or cold-start instant-display promise. Observation failures preserve existing warm display; cold failures do not claim a successful refresh.
Out of Scope
Design
N/A — no UI changes.
Preview
QA Notes
Journeys
N/A — not drivable; see Manual Tests.
The payment-request journey README documents these fault-driven checks. Matching iOS coverage belongs to the iOS consumer change.
Manual Tests
Automated Checks
PaykitPaymentProofRepoTest.kt- verifies local completion after successful durable writes during blocked submission, exact-proof delivery after reconstruction, failure fallback, and identity/reset isolation.PaykitPaymentRequestRepoTest.kt- verifies readable warm flows during blocked refresh, identity clearing, warm-state retention on snapshot failure, and honest cold failure.PubkyRepoTest.kt- verifies failed observations preserve warm contacts and stale results from an ended same-identity sign-in stay hidden.PaykitSdkServiceTest.kt- verifies observation identity validation, error propagation, queued runtime replacement, and cancellation without overlapping native operations.PaykitKeyGenerationTest.kt- verifies observation key generation matches the active key.PaykitPaymentRequestRepoSubscriptionTest.kt,ContactSaveSessionChangeTest.kt, andAppViewModelSendFlowTest.kt- retains request/contact action coverage and verifies proof reconciliation remains lifecycle-owned and coalesced during active payment submission.PubkyRepoTest.kt- verifies cold contact failure ends loading without marking a successful observation.44cc0ad08e6689c5a820d399d49ad67f2d69f8ff3039f322107525fa97411725.One fresh combined source correctness and quality review and bounded integration follow-up are complete. Controlled payment/restart fault checks above remain incomplete, not passed.
Remote Measurements and Payment QA
Accepted, but immediate bookkeeping failed withWinning transaction fee is unavailablebefore this PR's changed deferred-delivery branch. The activity/fee path is byte-identical to base. The fixture omitted the normal post-Accepted wallet synchronization and periodic recovery, so it does not prove a persistent production blocker or explain the reported stuck-wallet incident. No successful payment-completion speedup was measured. The separate validated fee-retention fix is fix: retain prepared transaction fees #1459; unknown-outcome and duplicate-payment guards remain unchanged.