Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions docs/sdk/sdk-parity-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"platform_wallet_manager_create_with_persistence_capabilities": "packages/rs-platform-wallet-ffi/src/manager.rs",
"platform_wallet_manager_persistence_capabilities": "packages/rs-platform-wallet-ffi/src/manager.rs",
"platform_wallet_manager_spv_rescan_filters": "packages/rs-platform-wallet-ffi/src/spv.rs",
"platform_wallet_manager_sync_fault_detected": "packages/rs-platform-wallet-ffi/src/manager.rs",
"platform_wallet_resume_identity_with_existing_asset_lock_signer": "packages/rs-platform-wallet-ffi/src/identity_registration_funded_with_signer.rs",
"platform_wallet_sync_contested_dpns_names": "packages/rs-platform-wallet-ffi/src/dpns.rs",
"platform_wallet_token_purchase": "packages/rs-platform-wallet-ffi/src/tokens/purchase.rs",
Expand Down Expand Up @@ -811,6 +812,63 @@
}
]
},
{
"id": "persistence.sync_fault_latch",
"title": "Watermark-freeze sync fault latch is host-visible",
"area": "recovery",
"shared_apis": [
"platform_wallet_manager_sync_fault_detected"
],
"required_persistence_capabilities": [],
"hosts": {
"swift": {
"sdk": "supported",
"example_app": "not-applicable",
"restart": "not_applicable",
"reason": null
},
"kotlin": {
"sdk": "supported",
"example_app": "not-applicable",
"restart": "not_applicable",
"reason": null
}
},
"verification": [
{
"host": "swift",
"kind": "unit",
"file": "packages/swift-sdk/SwiftTests/SwiftDashSDKTests/SyncFaultDetectedTests.swift",
"id": "testSyncFaultDetectedRequiresConfiguredManager",
"command": "cd packages/swift-sdk && xcodebuild test -scheme SwiftDashSDK -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -only-testing:SwiftDashSDKTests/SyncFaultDetectedTests",
"covers_restart": false
},
{
"host": "kotlin",
"kind": "device",
"file": "packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/FfiSmokeTest.kt",
"id": "syncFaultDetectedSymbolBindsAndRejectsInvalidHandle",
"command": "cd packages/kotlin-sdk && ./gradlew :sdk:connectedDebugAndroidTest",
"covers_restart": false
},
{
"host": "shared",
"kind": "unit",
"file": "packages/rs-platform-wallet/src/changeset/core_bridge.rs",
"id": "lagged_broadcast_freezes_and_strips_subsequent_watermark",
"command": "cargo test -p platform-wallet lagged_broadcast_freezes_and_strips_subsequent_watermark",
"covers_restart": false
},
{
"host": "shared",
"kind": "unit",
"file": "packages/rs-platform-wallet/src/changeset/core_bridge.rs",
"id": "rejected_store_freezes_wallet_and_strips_watermark",
"command": "cargo test -p platform-wallet rejected_store_freezes_wallet_and_strips_watermark",
"covers_restart": false
}
]
},
{
"id": "network.masternode_discovery",
"title": "Canonical masternode endpoint discovery",
Expand Down
15 changes: 8 additions & 7 deletions packages/kotlin-sdk/PARITY_SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
# Kotlin/Swift executable parity summary

Audit baseline: `PR #3999 @ 6dbc72a54df72d26eb9c4a014b425d2b95134e4e`
Capabilities tracked: **22**
Capabilities tracked: **23**

## Status counts

| Host | Surface | Supported | Partial | Unsupported | Not applicable |
| --- | --- | ---: | ---: | ---: | ---: |
| Swift | SDK | 12 | 8 | 1 | 1 |
| Swift | Example app | 4 | 12 | 1 | 5 |
| Kotlin | SDK | 10 | 12 | 0 | 0 |
| Kotlin | Example app | 5 | 12 | 0 | 5 |
| Swift | SDK | 13 | 8 | 1 | 1 |
| Swift | Example app | 4 | 12 | 1 | 6 |
| Kotlin | SDK | 11 | 12 | 0 | 0 |
| Kotlin | Example app | 5 | 12 | 0 | 6 |

## Restart coverage

| Host | Tested | Required | Not applicable |
| --- | ---: | ---: | ---: |
| Swift | 0 | 7 | 15 |
| Kotlin | 4 | 6 | 12 |
| Swift | 0 | 7 | 16 |
| Kotlin | 4 | 6 | 13 |

## Capability status

Expand All @@ -34,6 +34,7 @@ Capabilities tracked: **22**
| `identity.asset_lock_resume` | supported / partial / required | partial / partial / required |
| `core.compact_filter_rescan` | supported / partial / not_applicable | partial / partial / not_applicable |
| `dpns.contested_names_by_identity` | supported / partial / not_applicable | partial / partial / required |
| `persistence.sync_fault_latch` | supported / not-applicable / not_applicable | supported / not-applicable / not_applicable |
| `network.masternode_discovery` | partial / not-applicable / not_applicable | partial / not-applicable / not_applicable |
| `identity.platform_address_auto_funding` | partial / partial / not_applicable | partial / partial / not_applicable |
| `tokens.authorization_decisions` | partial / partial / not_applicable | partial / partial / not_applicable |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.dashfoundation.dashsdk
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.Dispatchers
import org.dashfoundation.dashsdk.ffi.DashSDKException
import org.dashfoundation.dashsdk.ffi.NativeLoader
import org.dashfoundation.dashsdk.ffi.SdkNative
import org.dashfoundation.dashsdk.ffi.SignerNative
Expand All @@ -13,6 +14,7 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -84,6 +86,22 @@ class FfiSmokeTest {
assertEquals("compact recoverable ECDSA signature", 65, signature!!.size)
}

/**
* The #4069 watermark-freeze fault latch surfaced by PR #4314: the
* `syncFaultDetected` JNI symbol binds, and an invalid manager handle
* surfaces as the mapped [DashSDKException] rather than a crash. The
* latch behavior itself (freeze on dropped events / rejected store) is
* covered by the shared Rust tests in
* `packages/rs-platform-wallet/src/changeset/core_bridge.rs`.
*/
@Test
fun syncFaultDetectedSymbolBindsAndRejectsInvalidHandle() {
NativeLoader.ensureLoaded()
assertThrows(DashSDKException::class.java) {
WalletManagerNative.syncFaultDetected(0L)
}
}

/**
* Rust↔Kotlin persistence-descriptor lockstep. The trampolines resolve
* each bridge method by (name, descriptor) only when its slot first
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,28 @@ extension PlatformWalletManager {
return running
}

/// Whether the native manager has frozen its durable sync watermark this
/// session (dashpay/platform#4069). `true` means the wallet-event adapter
/// dropped record-bearing events, or a persistence `store()` was rejected,
/// so the persisted `syncedHeight` is deliberately held behind the chain
/// tip and a rescan is pending on the next launch. Poll this to surface a
/// hard "verification failed / rescan pending" state instead of leaving
/// the fault visible only in the error logs.
///
/// The flag latches for this native manager's lifetime: once `true` it stays
/// `true` until the manager is destroyed.
public func syncFaultDetected() throws -> Bool {
guard isConfigured, handle != NULL_HANDLE else {
throw PlatformWalletError.invalidHandle(
"PlatformWalletManager not configured"
)
}

var detected = false
try platform_wallet_manager_sync_fault_detected(handle, &detected).check()
return detected
}

public func isPlatformAddressSyncing() throws -> Bool {
guard isConfigured, handle != NULL_HANDLE else {
throw PlatformWalletError.invalidHandle(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SyncFaultDetectedTests.swift
// SwiftDashSDKTests
//
// Unit tests for the `PlatformWalletManager.syncFaultDetected()` FFI bridge.

import XCTest
@testable import SwiftDashSDK

/// Coverage for the Swift side of the watermark-freeze fault latch
/// (dashpay/platform#4069), exported over the C ABI by PR #4314 as
/// `platform_wallet_manager_sync_fault_detected`.
///
/// `syncFaultDetected()` is a pure bridge: guard the handle, call the export,
/// return the out-param. The latch's actual semantics — the persisted
/// `syncedHeight` being deliberately held behind the chain tip after the
/// wallet-event adapter drops record-bearing events or a persistence `store()`
/// is rejected, and the flag latching `true` for the process lifetime — live in
/// Rust and are covered by the shared tests in
/// `packages/rs-platform-wallet/src/changeset/core_bridge.rs`. What is Swift's
/// to get right is the unconfigured-handle guard, which must throw rather than
/// hand `NULL_HANDLE` across the FFI boundary.
///
/// `PlatformWalletManager` is main-actor isolated, so the suite runs on the
/// main actor.
@MainActor
final class SyncFaultDetectedTests: XCTestCase {

/// A bare `PlatformWalletManager()` has never been configured: `handle` is
/// `NULL_HANDLE` and `isConfigured` is false, so the wrapper must throw
/// `PlatformWalletError.invalidHandle` instead of calling the export with
/// a null handle.
func testSyncFaultDetectedRequiresConfiguredManager() {
let manager = PlatformWalletManager()
XCTAssertFalse(manager.isConfigured, "a bare manager is unconfigured")

XCTAssertThrowsError(try manager.syncFaultDetected()) { error in
XCTAssertTrue(error is PlatformWalletError)
if case PlatformWalletError.invalidHandle = error {
// Expected error
} else {
XCTFail("Expected invalidHandle error, got \(error)")
}
}
}
}
Loading