feat(swift-sdk): add Core wallet balance diagnostics - #4580
Open
llbartekll wants to merge 11 commits into
Open
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue being fixed or feature implemented
Adds read-only diagnostics for a support case where a CoinJoin sweep appeared spent locally but no corresponding transaction or AssetLock was found on-chain. It also adds regression coverage for CoinJoin-funded transactions with owned BIP44 change, the persistence behavior addressed by #4438.
What was done?
core_restore_buffer_snapshotduring wallet restore. It reuses rows already fetched for restore and performs no additional SwiftData history scan or Rust FFI query.emitCoreWalletDiagnostics(for:)API. Full SwiftData, Rust-memory, AssetLock, shielded-store, owned-output, and DB-to-memory diagnostics run only when the host explicitly requests apre_exportsnapshot.shutdown()raises a cancellation flag that both the queue-confined database pass (between fetch stages) and the Rust reads check.ModelContexton the persistence queue: it sees only committed state (never a changeset's pending rows) and is dropped with the pass, so the up-to-110k objects it registers do not stay resident. If it returns nothing, the Rust half still runs and its diffs are markeddatabase_snapshot_available=false.core_store_open_resultwith SQLite main/WAL/SHM sizes, duration,migration_path, and an accurate store-open outcome without claiming that a migration ran. Events emitted before the file sink exists (the store open runs in the host'sinit()) are buffered and replayed intoswift/run.logon install, so they reach the exported artifact.core_rescan_requestedlogging: what was asked and whether the FFI accepted it (accepted/failed/invalid_wallet_id), with no claim about whether a rewind happened — that would need the filter-scan checkpoint, which is neither the core wallet's synced height nor readable without a blocking Rust-lock call on the main actor.PersistentTxoaudit manual/export-only, with Rust analysis off MainActor. The export holds the persistence serial queue for its duration (documented onemitCoreWalletDiagnostics(for:)); aboveCoreDiagnosticRowLimits(100k TXO rows / 10k transaction rows table-wide) it narrows to the wallet's own rows and declines the exact audit withaudit_incomplete=true, reason=tables_too_large_for_exact_auditrather than truncating and misclassifying. A paged variant that lifts the ceilings is swift-sdk: paged Core wallet diagnostics export that keeps the exact #4438 classification #4607.DashModelContainer.open(_:)public. One production behaviour change: when the stagedDashMigrationPlanrejects a store with Cocoa 134504 ("unknown model version" — what every dev.1 store hits until the remaining V1/V2 shapes are frozen, seeDashSchemaFrozenModels.swift),openretries with inferred lightweight migration instead of letting the host crash at launch. The decision is made on the store's metadata, not the error (SwiftData surfaces the checksum failure as an opaqueloadIssueModelContainer):classifyStore(at:)returns a verdict, and the fallback runs only fordriftedRegisteredVersion. A store from a newer build — an unregistered version identifier, or an entity this schema lacks — isnewerThanRegisteredand is rethrown, because inferred migration would otherwise trim it silently; so are unreadable stores and stores that match a registered version but failed anyway (a future customMigrationStage). The verdict is logged asstore_verdict. Same entity names with a kept identifier are then compared hash-by-hash against the declared version's model: the fallback runs only if every disagreeing entity carries exactly the hashknownDriftedEntityHasheslists for it — the dev.1 shapes of the two entities changed in place since V1, pinned byte-for-byte to the fixture by a test. A hash is a function of the shape, so a newer build's version of any entity, those two included, is refused asunexpected_entity_drift; there is no same-name-unknown-shape residual. The fallback answers exactly the store the fixture proves.Dev1StoreUpgradeTestspins the fallback on the dev.1 fixture, the refusal of a corrupt store and of a store with an extra@Model, and that the migrated store reopens through the staged path afterwards.ModelContainerand does not callcreate; it needs to adoptDashModelContainer.open(_:)in a follow-up to receive the store-open telemetry and the fallback. Until then only SwiftExampleApp emits them.How Has This Been Tested?
build-for-testing: passed.git diff --check: passed.Breaking Changes
None. Public additions: the nonthrowing read-only API
emitCoreWalletDiagnostics(for:)andDashModelContainer.open(_:). The only behaviour change is the narrowly-scoped migration fallback described above, which acts only on a path that previously threw.Checklist:
For repository code-owners and collaborators only
Summary by CodeRabbit
New Features
Bug Fixes
Tests