Skip to content

DashSync migration part 1#1

Closed
podkovyrin wants to merge 8 commits intodevelopfrom
feature/dashsync-migration-part1
Closed

DashSync migration part 1#1
podkovyrin wants to merge 8 commits intodevelopfrom
feature/dashsync-migration-part1

Conversation

@podkovyrin
Copy link
Copy Markdown
Contributor

No description provided.

@podkovyrin podkovyrin closed this Nov 13, 2018
@podkovyrin podkovyrin deleted the feature/dashsync-migration-part1 branch November 13, 2018 09:18
podkovyrin added a commit that referenced this pull request Aug 28, 2020
bfoss765 added a commit that referenced this pull request Nov 13, 2025
…rized commits

Updated both CLAUDE.md and AI-DEVELOPMENT-GUIDE.md with much stronger and clearer
language about the critical requirement to NEVER commit or push without explicit
user permission.

Changes include:
- Added prominent visual markers (emojis, bold text) to catch attention
- Declared this the #1 rule and #1 user complaint
- Provided concrete examples of violations vs correct behavior
- Listed specific phrases that grant permission
- Made it absolutely clear there are NO EXCEPTIONS to this rule

This addresses the recurring issue where AI assistants commit changes
without waiting for user permission.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Apr 8, 2026
The SDK's `ModelContainerHelper.createContainer()` is unusable in
dashwallet-ios for two compounding reasons that the SDK author never
hit because `SwiftExampleApp/` has neither App Group nor iCloud
entitlements:

1. **CloudKit auto-validation rejects the SDK's schema.** dashwallet-ios
   has both an App Group entitlement (Watch app + Today extension data
   sharing) and an iCloud entitlement. SwiftData's default
   `cloudKitDatabase: .automatic` and `groupContainer: .automatic`
   auto-detect both and try to register the container as a CloudKit
   store under the App Group container. The validator then enumerates
   ~120 violations across the SDK's 10 `@Model` types — non-optional
   attributes without defaults, non-optional relationships, missing
   relationship inverses (PersistentIdentity↔PersistentTokenBalance),
   and ubiquitous `@Attribute(.unique)` constraints which CloudKit
   doesn't support at all. The whole container init throws
   `SwiftDataError #1` (loadIssueModelContainer).

2. **Threading.** Even with the CloudKit issue worked around, the SDK
   helper's runtime schema registration only succeeds reliably when
   called from main on iOS 17. The SDK example app uses a
   `@MainActor`-isolated `UnifiedAppState.init()` to enforce this
   (`SwiftExampleApp/UnifiedAppState.swift:64`). Our migrator,
   coordinator, wallet creator, and wallet wiper all dispatched to
   `DispatchQueue.global(qos: .userInitiated)` and called the helper
   off-main, which would race even if CloudKit weren't an issue.

Fix: introduce `SwiftDashSDKContainer.swift`, a shared
`@objc(DWSwiftDashSDKContainer)` singleton that owns a
`ModelContainer` constructed once on the main thread. The container is
built directly via `ModelConfiguration(...)` with explicit
`cloudKitDatabase: .none`, an explicit store URL under
`Library/Application Support/SwiftDashSDKLocal/HDWallet.store`, and a
minimal `Schema([HDWallet.self])` — bypassing the SDK helper entirely.
The 9 platform `Persistent*` models are unused by dashwallet-ios so
omitting them from the schema is harmless.

`AppDelegate.application:didFinishLaunchingWithOptions:` calls
`[DWSwiftDashSDKContainer warmUp]` BEFORE the seed migrator, the SPV
coordinator, the wallet creator, and the wallet wiper. The warmUp is
synchronous, runs on main (precondition-enforced), and creates the
container exactly once per app launch.

All four downstream callers now read `SwiftDashSDKContainer.modelContainer`
from their background queues and construct their own background
`ModelContext` against it, which Apple documents as the supported
pattern. Reads from background queues are safe because the
`dispatch_async` calls that schedule the background work establish a
happens-before edge with the warmUp write. Each caller handles the
nil case (warmUp failed on main) by logging a clear error and either
rolling back any partial state (migrator/creator) or refusing to
proceed (coordinator/wiper).

Also bumps the SPV coordinator's seed-migrator polling timeout from
30s to 120s — covers slow simulator boot + the migrator's heavy work
+ a generous buffer for first-launch SwiftData store creation.

Verified end-to-end: imported a wallet via the recover flow (which
calls `WalletCreator.importWallet`), the wallet creator wrote an
HDWallet record to the new sandbox store, and the SPV coordinator
read it back and started syncing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Apr 8, 2026
…ause

The fix in 0b70276 is structurally correct, but the explanatory
comments around it were written from an early hypothesis that turned
out to be wrong. An exploration agent's research suggested the SDK's
ModelContainerHelper.createContainer() was racing schema registration
when invoked from a background queue on iOS 17, and the SDK's example
app uses a @MainActor-isolated init for the same reason. I implemented
the shared-container fix on that basis.

Two rounds of testing later we discovered the actual root cause from
a CoreData log dump: dashwallet-ios has both an iCloud entitlement and
an App Group entitlement. SwiftData's defaults `cloudKitDatabase:
.automatic` and `groupContainer: .automatic` auto-detect both, register
the SDK's 10-model schema as a CloudKit-backed store under the App
Group container, and then fail validation because the SDK's
`Persistent*` models violate ~120 CloudKit rules (non-optional
attributes without defaults, unique constraints, missing relationship
inverses, non-optional relationships).

The bypass — minimal `Schema([HDWallet.self])`, explicit
`cloudKitDatabase: .none`, explicit store URL under
Library/Application Support/SwiftDashSDKLocal — works end-to-end and
is verified by an actual SPV sync running against a wallet imported
via the recover flow. But the comments still tell the iOS-17 threading
story.

Cleanup, six edits across five files plus one numeric constant:

  - AppDelegate.m: reframe the warmUp block comment from "races
    schema registration on iOS 17" → "fails CloudKit validation on
    iCloud + App Group entitled apps".

  - SwiftDashSDKContainer.swift: fix the file-header bullet that
    claimed we set `groupContainer: .none` (we don't — the explicit
    `url:` overrides any group-container default, which is correct
    behavior, but the comment lied about the mechanism). Soften the
    `warmUp()` doc to say the main-thread requirement is defensive
    practice matching the SDK example app, not load-bearing for
    SwiftData. The Thread.isMainThread precondition stays — it's free
    belt-and-suspenders against any future SwiftData regression.

  - SwiftDashSDKKeyMigrator.swift,
    SwiftDashSDKWalletCreator.swift,
    SwiftDashSDKWalletWiper.swift: drop the "throws SwiftDataError #1
    on iOS 17" attribution from each background-queue context comment.
    Replace with "the SDK helper fails CloudKit validation on entitled
    apps; see SwiftDashSDKContainer.swift for the full rationale".

  - SwiftDashSDKKeyMigrator.swift, SwiftDashSDKSPVCoordinator.swift:
    drop "main-thread warmUp must have failed" from error logs and
    the user-visible error string. warmUp() can fail for filesystem
    or schema reasons too, not just threading. New phrasing is
    "warmUp() failed; check Console.app for `📦 SDKBOX` logs".

  - SwiftDashSDKSPVCoordinator.swift: revert
    `seedMigratorWaitTimeout` from 120.0 → 30.0. The bump to 120s
    came in during the diagnostic round with a rationale ("buffer
    for first-launch SwiftData store creation") that's now obsolete
    because the store is created upfront in `warmUp()` BEFORE the
    coordinator's polling loop even starts. The migrator's heavy
    work is ~300-500 ms; 30 s was always plenty.

Pure documentation hygiene plus one numeric constant. Zero behavior
changes. Both dashwallet and dashpay targets build clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Apr 9, 2026
M6 stopped DashSync's SPV but left BalanceModel reading from
DSWallet.balance, which is now frozen at whatever DashSync had cached
the moment M6 ran. This commit unfreezes the home screen balance by
sourcing it from SwiftDashSDK via a new SwiftDashSDKWalletState
singleton.

A new SwiftDashSDKWalletState class is the right home for wallet-side
@published state: SPV chain sync (handled by SwiftDashSDKSPVCoordinator)
and wallet state (balance, transactions, addresses) are different
concerns even though the FFI couples their event delivery. Future
follow-ups for transactions (#6), addresses (#1), and identities (#16)
will live alongside `balance` in this class instead of bloating the
SPV coordinator.

- Add SwiftDashSDKWalletState singleton with @published var balance,
  applyBalance(_:), seedInitialBalance(walletManager:walletId:), and
  clearBalance() methods. Holds the WalletBalance struct (4 UInt64
  fields, with `total` and `spendable` computed). 💰 WALLET :: log tag.
- SwiftDashSDKSPVCoordinator: WalletEventsHandler.onBalanceUpdated
  becomes a thin forwarder to SwiftDashSDKWalletState.shared.applyBalance.
  performStart calls SwiftDashSDKWalletState.shared.seedInitialBalance
  after walletManager.importWallet succeeds. Coordinator no longer
  owns any wallet-side @published state.
- BalanceModel subscribes to SwiftDashSDKWalletState.shared.\$balance
  via Combine and reads from .balance?.total instead of
  DWEnvironment.sharedInstance().currentWallet.balance.
- SwiftDashSDKWalletWiper calls SwiftDashSDKWalletState.shared.clearBalance()
  after the SwiftData wipe so post-wipe state doesn't show the
  previous wallet's balance.
- Register the new file in both dashwallet and dashpay targets in
  project.pbxproj (UUID family A5D5DD000000000000010C/D/E).

Other DashSync balance consumers (BalanceNotifier, SendAmountModel,
DWPhoneWCSessionManager, DashPay/CrowdNode/CoinJoin/DashSpendPay) still
read from DSWallet and remain stale. Each gets its own follow-up commit.
Aim of this commit is the smallest change that fixes the most visible
part of the M6 regression — the home screen number — with the right
architectural shape so #6 and beyond can build on it cleanly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Apr 17, 2026
Migrates the receive-address read path from DSAccount.receiveAddress
to WalletManager.getReceiveAddress(walletId:) via the new thin shim
SwiftDashSDKReceiveAddressReader, mirroring the AddressValidator
pattern. Eleven production call sites switched in one cut, with
DWReceiveModel re-fetching on transactionsDidChange so the displayed
address advances as SPV catches up after first launch post-migration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

2 participants