Skip to content

fix: decouple payout authorization from sync - #794

Merged
ovitrif merged 2 commits into
masterfrom
codex/locks-staging-e2e-20260924
Sep 29, 2026
Merged

ovitrif merged 2 commits into
masterfrom
codex/locks-staging-e2e-20260924

Conversation

@ben-kaufman

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

Copy link
Copy Markdown
Contributor

Closes #810

This PR prevents a wallet-sync timeout from aborting payout authorization. Bitkit registers the watch-only account and prepares its receiving addresses before sending authorization; existing periodic synchronization retrieves its transaction history.

Counterpart: Android PR.

Description

  • Removes the full-wallet network sync from authorization preparation so an Electrum timeout cannot roll back the new payout account at that step.
  • Preserves local account registration, address revelation, durable authorization state and rollback on preparation failures.
  • Updates the marketplace journey to keep the seller active on a separate device and verify periodic payout detection without a foreground-triggered sync.

Out of Scope

  • Payout reconnect: the separate HTTP 422 account/xpub mismatch between Bitkit and Paykit Server.
  • Wallet synchronization: startup reconciliation and explicit refresh behavior.

Design

N/A — no UI changes.

Preview

N/A — no visual changes.

QA Notes

Journeys

  • updated wallet-leg.xml — authorization succeeds and the seller sees the payout while continuously active on its own device, without a foreground transition or manual refresh. Capture lifecycle and sync logs to verify the interval.

Manual Tests

  • regression: make Electrum synchronization fail while Pubky/Paykit authorization services remain reachable → approve a fresh payout setup → Authorization Successful appears; after restoring Electrum, received payouts synchronize — selective Electrum fault injection not in Capabilities.

Observed on staging: the original build timed out syncing an existing account and removed the newly added account. With this fix, local registration/revelation took 0.65 seconds and native authorization succeeded. The subsequent server reconnect still returned HTTP 422. A deterministic Electrum outage was not re-injected, and the complete updated marketplace journey has not been replayed on this head.

Automated Checks

  • ran WatchOnlyAccountServiceTests.swift and PubkyAuthApprovalSheetTests.swift — 50 tests passed using an isolated iOS simulator with the E2E network/regtest build; these mock account tracking, so they do not directly reproduce the native Electrum timeout.
  • ran the E2E network/regtest simulator build — compiled the changed native integration used in the staging retry.

@greptile-apps

greptile-apps Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

The PR appears safe to merge, but the updated journey does not independently verify periodic payout detection.

Findings

  1. P2 Foreground Sync Masks Coverage ▶

Summary

The PR removes the immediate full-wallet sync from watch-only payout authorization, leaving account registration and address revelation in place before approval.

  • Documents that transaction history is fetched later by synchronization.
  • Extends the marketplace journey to check the seller’s payout, though its foreground transition does not isolate periodic synchronization.

Reviews (1) · Last reviewed commit: "fix: decouple payout authorization from ..."

Comment thread journeys/pubky-marketplace/wallet-leg.xml Outdated
@ovitrif ovitrif added this to the 2.6.0 milestone Sep 24, 2026
@jvsena42
jvsena42 requested review from a team, jvsena42 and pwltr and removed request for a team September 25, 2026 16:22

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No findings.

Checked (ldk-node 0.7.0-rc.66):

  • setWatchOnlyAccountTracking still adds the account if absent, reveals up to watchOnlyAccountHighestPreRevealedAddressIndex, and removes the account and rethrows if the reveal fails. Only the post-reveal syncWallets() is gone.
  • Background sync runs every 10s (Env.swift) and picks up accounts added at runtime. A derived account's first sync in a process is a full scan, and a failed scan is retried on the next tick.
  • WatchOnlyAccountClaimCodec.encode sends only the xpub, so no address that depends on sync state reaches the counterparty.
  • beginSetupAuthorization persists Authorizing with tracking enabled under lifecycleCoordinator.withLock. reconcileTracking takes the same lock and only removes accounts that are managed but not desired. After a crash mid-approval, the account reloads on the next start.
  • The failure paths in PubkyService.approveAuthRequest are unchanged: cancel unless the companion approval was delivered, then markActive.
  • Reachable only with a stored Pubky identity. v2.5.0 already has the sync, so upgrading users only gain the fix.

Parity with synonymdev/bitkit-android#1338: same change. The only difference is that this side carries no test update; the existing tests mock tracking and never covered the sync.

@pwltr pwltr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed 186c99e1de204f044632e10c662cd0bf98f77627, tracing payout authorization through account tracking, persisted authorization state, rollback and subsequent synchronization.

No actionable code findings. Local account registration and address revelation remain in place; the full-wallet syncWallets() call is removed from authorization preparation. The journey's earlier foreground-sync coverage concern is addressed in the current source.

Executed CI checks, including local E2E, pass. The author reports 50 focused simulator tests passing, but those tests mock account tracking and do not reproduce the native Electrum timeout.

The complete revised wallet-leg.xml journey and a deterministic Electrum-only outage have not been replayed on this head. Device verification of authorization during that outage and later periodic payout detection remains pending.

@piotr-iohk piotr-iohk left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

QA reviewed on 186c99e.

QA review

Reviewed the full PR diff against its merge base, at 186c99e.

No new actionable code findings.

beginSetupAuthorization still registers the watch-only account and reveals receive indexes through 999 before the claim is sent. syncWallets() is no longer called on that path, so an Electrum timeout there would no longer remove the new account. A failed address reveal still removes an account this step just added. WatchOnlyAccountClaimCodec.encode sends the account index and xpub only, so the claim does not depend on sync state. In ldk-node 0.7.0-rc.66, background on-chain sync (interval 10s) reads derived accounts on each tick. A derived account's first successful scan is a full scan with the configured stop gap of 1000; a failed scan leaves it unmarked, so a later tick retries it. Aggregate on-chain balance includes that account, and the wallet refreshes on sync completion, balance change, and on-chain receive events, so savings (ActivitySavings) and home activity update after that sync rather than during authorization. Foreground wallet.sync() runs when the scene becomes active and on pull-to-refresh, not on a timer while the app stays in the foreground. The updated wallet-leg.xml keeps the seller on Home and awake until the payout appears, which addresses the earlier foreground-sync coverage note.

Unchanged, and out of scope in this PR: startup and foreground LightningService.sync() still call syncWallets() after reconciliation returns. That call is outside the add/rollback path, so an Electrum timeout there fails the sync and leaves the new account in place. It can still delay a concurrent authorization waiting on the LDK queue.

The same sync removal and wallet-leg.xml update are in bitkit-android#1338 at b3fb622. iOS keeps id where Android uses testTag. Android also asserts in unit tests that syncWallets() is not called; these iOS tests mock tracking above LightningService and do not execute this path.

CI tests, integration tests, and local e2e on this revision passed. The PR reports 50 focused simulator tests passing. I inspected WatchOnlyAccountServiceTests and did not re-run them; they mock account tracking and do not reproduce the native Electrum timeout. The runtime checks are the updated journeys/pubky-marketplace/wallet-leg.xml and the Electrum-failure authorization case in the PR. No Appium spec covers this marketplace leg.

Device testing: not performed in this review.

Ready for device testing.

@ovitrif ovitrif removed this from the 2.6.0 milestone Sep 28, 2026
@ovitrif
ovitrif self-requested a review September 28, 2026 13:56

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Advice: ✅ Approve


Review: diff 5 files.
Equivalent to synonymdev/bitkit-android#1338.

Audit:
Awaits QA.

Coverage:
QA: waits for the other reviewers' approval, or @ovi-reviewer test


Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner) · wrong <why> (owner)

@ovitrif ovitrif mentioned this pull request Sep 28, 2026
3 tasks
@ovitrif

ovitrif commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

@ovi-reviewer test

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Advice: ✅ Approve


Review: diff 5 files.
Pair PR synonymdev/bitkit-android#1338: equivalent.

Audit:
Awaits QA.

QA:
Tested on two iOS 26.5 simulators (iPhone 17 Pro), regtest

Test 1 ⚠️ not verified: The Electrum settings screen would not save an unreachable host, so the selective outage test could not be completed.

evidence
1.mp4

Test J1 ⚠️ not verified: The seller account did not provide the xpub or spending authority proof needed for J1.

evidence
J1.mp4

Warning

The seller account lacked the proof needed for J1, and a same-chain marketplace purchase could not be completed. The Electrum settings screen would not save an unreachable host, leaving the selective outage and payout recovery in Test 1 unverified.


Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner) · wrong <why> (owner)

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Advice: ✅ Approve


Review: diff 5 files.
Pair PR synonymdev/bitkit-android#1338: equivalent.

Audit:
Awaits QA.

QA:
Tested on two iOS 26.5 simulators (iPhone 17 Pro), regtest

Test 1 ⚠️ not verified: Same-chain payout was not produced.

evidence
1.mp4
log
ERROR: Incremental sync of on-chain wallet failed: Connection refused (os error 61)
INFO: Sync completed: onchainWallet at height 202513

Test J1 ⚠️ not verified: The fixture did not provide proof of the seller account.

evidence
J1.mp4
log
INFO: Registered derived on-chain wallet account OnchainWalletAccount { address_type: NativeSegwit, account_index: 1 }
DEBUG: Hiding sheet pubkyAuthApproval reason: PubkyAuthApprovalSheet.swift:222 successContent

Note

“Authorization Successful” remained visible for 30 seconds while Electrum connections failed. On-chain sync completed after the connection was restored.

Warning

No payout was produced on the app's regtest chain, and the fixture provided no seller xpub or proof of spending authority. Payout receipt and the remaining marketplace purchase steps remain unverified.


Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner) · wrong <why> (owner)

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Verified locally (iOS seller + Android buyer, payments-env regtest)

Cross-device marketplace purchase against this PR’s payout path:

  • Seller: iOS Bitkit (this PR build), Pubky profile + contact with buyer, Paykit creator setup already done
  • Buyer: Android Bitkit, Pubky profile + contact with seller, funded
  • Fixture: Locks content lock + proof-bundle with locks creator = Bitkit seller = Paykit recipient (Locks /connect approved via CLI as the seller identity; Bitkit UI still rejects Locks’ legacy signin connect)
  • Buyer received Payment Request → Swipe To Pay 15 000 sats → Bitcoin Sent
  • Seller kept foreground/active (no restart / manual refresh)
  • Seller showed Received Bitcoin · ₿15 000, then Home savings 17 000 (prior 2 000 + payout) with activity Received +15 000
  • Paykit invoice ended confirmed (3 confs after mine)

Confirms periodic payout detection after authorization, without needing a sync during auth prep.

Counterpart run also exercised Android #1338 as the buyer.

@piotr-iohk piotr-iohk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

QA pass — periodic payout path verified locally (iOS seller / Android buyer) on payments-env regtest.

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

utAck

@ovitrif
ovitrif merged commit da96a92 into master Sep 29, 2026
14 checks passed
@ovitrif
ovitrif deleted the codex/locks-staging-e2e-20260924 branch September 29, 2026 15:22
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: a wallet sync timeout aborts payout authorization

5 participants