Skip to content

Comments

fix(swift-sdk): minimally unblock iOS CI build plumbing#3143

Closed
thepastaclaw wants to merge 4 commits intodashpay:v3.1-devfrom
thepastaclaw:fix/ios-ci-minimal-unblock
Closed

fix(swift-sdk): minimally unblock iOS CI build plumbing#3143
thepastaclaw wants to merge 4 commits intodashpay:v3.1-devfrom
thepastaclaw:fix/ios-ci-minimal-unblock

Conversation

@thepastaclaw
Copy link
Contributor

@thepastaclaw thepastaclaw commented Feb 21, 2026

Summary

Minimal CI-unblock PR for systemic iOS build failures.

This intentionally includes only build plumbing changes (no Swift API/behavior changes).

What changed

packages/rs-sdk-ffi/build_ios.sh

  • Added SPV_TARGET_DIR (workspace root target dir: ../rust-dashcore/target)
  • Fixed BUILD_ARCH=x86 to build/use x86_64-apple-ios SPV sim lib
  • Fixed BUILD_ARCH=universal to lipo SPV sim arm64 + x86_64 before simulator merge
  • Standardized merged output naming to remain librs_sdk_ffi.a (temp + mv), avoiding combined-name drift

packages/swift-sdk/build_ios.sh

  • Replaced fragile nm | grep pipes with temp-file symbol checks to avoid SIGPIPE/pipefail false negatives

Explicitly out of scope

  • No changes to packages/swift-sdk/Sources/**
  • No Swift API/deprecation/behavior adjustments
  • No lockfile/tooling churn
  • No app-level logic changes

Validation

  • bash -n packages/rs-sdk-ffi/build_ios.sh
  • bash -n packages/swift-sdk/build_ios.sh

Why this shape

This is a narrow CI-rescue patch so reviewers can approve quickly; follow-up refactors can be reviewed separately.

Summary by CodeRabbit

  • Chores
    • Improved iOS SDK build tooling for reliable device/simulator builds, toolchain-aware flows, and consistent artifact handling.
  • Refactor
    • Consolidated library merging and packaging to produce a single consistent SDK library across build modes.
  • Bug Fixes
    • Made symbol extraction more robust to avoid pipeline errors and handle missing optional libraries gracefully.
  • Documentation
    • Clarified nullable provider behaviors in public API docs.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Warning

Rate limit exceeded

@thepastaclaw has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 58 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Updates adjust iOS build scripts to centralize SPV target paths and robustly handle simulator/device merges and symbol extraction, and make the rs-sdk-ffi address provider vtable fields explicitly nullable via new public alias types.

Changes

Cohort / File(s) Summary
iOS FFI Build Script
packages/rs-sdk-ffi/build_ios.sh
Adds SPV_TARGET_DIR and replaces hard-coded paths; adds BUILD_ARCH branches (x86, universal) and explicit toolchain handling; creates temporary artifacts for merging and standardizes on librs_sdk_ffi.a for device/simulator before XCFramework creation.
iOS Swift SDK Build Script
packages/swift-sdk/build_ios.sh
Replaces direct nm piping with captured outputs (NM_MAIN_OUTPUT, NM_SPV_OUTPUT), conditions SPV symbol checks on library existence, and adds a cleanup trap to avoid SIGPIPE/portability issues.
rs-sdk-ffi Address Provider
packages/rs-sdk-ffi/src/address_sync/provider.rs
Introduces nullable alias types (NullableHasPendingFn, NullableGetHighestFoundIndexFn, NullableDestroyProviderFn) and updates AddressProviderVTable fields to use these aliases; preserves previous default behaviors when None.

Sequence Diagram(s)

(omitted — changes are configuration, scripting, and small API additions without a new multi-component sequential flow)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through paths and tiny quirks,

swapped targets, tamed the build-time works,
nullable holes now cozy and small,
temp files cleaned and symbols called,
I nibble bugs — this patch stands tall! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(swift-sdk): minimally unblock iOS CI build plumbing' directly matches the main changeset focus on fixing iOS CI build issues in build scripts, though it emphasizes swift-sdk while the changeset also includes significant rs-sdk-ffi changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/rs-sdk-ffi/build_ios.sh (1)

479-479: Pre-existing: eval on a space-delimited command string is fragile.

Not introduced by this PR, but eval $XCFRAMEWORK_CMD will break if any path component contains spaces or shell metacharacters. An array-based approach (XCFRAMEWORK_CMD=(xcodebuild …); "${XCFRAMEWORK_CMD[@]}") would be more robust. Fine to defer since CI paths are controlled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-sdk-ffi/build_ios.sh` at line 479, The script uses eval
$XCFRAMEWORK_CMD which is brittle with spaces or metacharacters; change
XCFRAMEWORK_CMD to be an array (e.g., XCFRAMEWORK_CMD=(xcodebuild ...)) and
invoke it with "${XCFRAMEWORK_CMD[@]}" instead of using eval so arguments are
preserved; update any places that build the command string (where
XCFRAMEWORK_CMD is assigned or appended) to push elements into the array rather
than concatenating into a single string.
packages/swift-sdk/build_ios.sh (3)

52-54: trap registered after both mktemp calls — minor temp-file leak on second mktemp failure

If mktemp on line 53 fails (under set -euo pipefail), the script exits before line 54 runs, leaving NM_MAIN_OUTPUT untracked. Negligible in practice, but easy to harden:

♻️ Proposed fix: initialise variables and set trap before allocating
+NM_MAIN_OUTPUT=""
+NM_SPV_OUTPUT=""
+trap 'rm -f "$NM_MAIN_OUTPUT" "$NM_SPV_OUTPUT"' EXIT
 NM_MAIN_OUTPUT="$(mktemp)"
 NM_SPV_OUTPUT="$(mktemp)"
-trap 'rm -f "$NM_MAIN_OUTPUT" "$NM_SPV_OUTPUT"' EXIT

rm -f "" is a no-op, so the trap fires safely even before the variables are populated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/build_ios.sh` around lines 52 - 54, Initialize
NM_MAIN_OUTPUT and NM_SPV_OUTPUT to empty strings and register the trap before
calling mktemp to avoid an untracked temp file if the second mktemp fails;
specifically, set NM_MAIN_OUTPUT="" and NM_SPV_OUTPUT="" then add the existing
trap 'rm -f "$NM_MAIN_OUTPUT" "$NM_SPV_OUTPUT"' and only afterward call
NM_MAIN_OUTPUT="$(mktemp)" and NM_SPV_OUTPUT="$(mktemp)" in build_ios.sh so the
trap always safely cleans up even if a mktemp call exits early.

62-62: Consider guarding on NM_SPV_OUTPUT content rather than LIB_SIM_SPV existence

[[ -f "$LIB_SIM_SPV" ]] here re-tests library existence rather than whether nm actually produced output. Using [[ -s "$NM_SPV_OUTPUT" ]] is semantically tighter — it directly asks "did nm capture anything?" and handles the case where nm ran but produced no output (the || true path).

♻️ Proposed change (lines 62 and 71)
-elif [[ -f "$LIB_SIM_SPV" ]] && "${SEARCH_CMD[@]}" "_dash_spv_ffi_config_add_peer" "$NM_SPV_OUTPUT" >/dev/null; then
+elif [[ -s "$NM_SPV_OUTPUT" ]] && "${SEARCH_CMD[@]}" "_dash_spv_ffi_config_add_peer" "$NM_SPV_OUTPUT" >/dev/null; then
-elif [[ -f "$LIB_SIM_SPV" ]] && "${SEARCH_CMD[@]}" "_dash_spv_ffi_config_set_restrict_to_configured_peers" "$NM_SPV_OUTPUT" >/dev/null; then
+elif [[ -s "$NM_SPV_OUTPUT" ]] && "${SEARCH_CMD[@]}" "_dash_spv_ffi_config_set_restrict_to_configured_peers" "$NM_SPV_OUTPUT" >/dev/null; then

Also applies to: 71-71

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/build_ios.sh` at line 62, Replace the file-existence guard
with a check that nm actually produced output: in the conditional that currently
uses [[ -f "$LIB_SIM_SPV" ]] alongside SEARCH_CMD searching for
"_dash_spv_ffi_config_add_peer" in $NM_SPV_OUTPUT, change the test to [[ -s
"$NM_SPV_OUTPUT" ]] so you assert that nm captured non-empty output before
running SEARCH_CMD; apply the same change to the analogous check later (the
other occurrence around the SEARCH_CMD usage) so both conditions guard on
NM_SPV_OUTPUT content rather than LIB_SIM_SPV presence.

60-76: Duplicate symbol-check blocks could be extracted into a helper function

Lines 60–67 and 69–76 are structurally identical. A small helper keeps future symbol additions DRY.

♻️ Proposed refactor
+check_symbol() {
+  local sym="$1"
+  if "${SEARCH_CMD[@]}" "$sym" "$NM_MAIN_OUTPUT" >/dev/null; then
+    return 0
+  elif [[ -s "$NM_SPV_OUTPUT" ]] && "${SEARCH_CMD[@]}" "$sym" "$NM_SPV_OUTPUT" >/dev/null; then
+    return 0
+  else
+    echo "❌ Missing symbol: ${sym#_} (in both main and spv libs)"
+    CHECK_OK=0
+  fi
+}
+
+check_symbol "_dash_spv_ffi_config_add_peer"
+check_symbol "_dash_spv_ffi_config_set_restrict_to_configured_peers"
-if "${SEARCH_CMD[@]}" "_dash_spv_ffi_config_add_peer" "$NM_MAIN_OUTPUT" >/dev/null; then
-  :
-elif [[ -f "$LIB_SIM_SPV" ]] && "${SEARCH_CMD[@]}" "_dash_spv_ffi_config_add_peer" "$NM_SPV_OUTPUT" >/dev/null; then
-  :
-else
-  echo "❌ Missing symbol: dash_spv_ffi_config_add_peer (in both main and spv libs)"
-  CHECK_OK=0
-fi
-
-if "${SEARCH_CMD[@]}" "_dash_spv_ffi_config_set_restrict_to_configured_peers" "$NM_MAIN_OUTPUT" >/dev/null; then
-  :
-elif [[ -f "$LIB_SIM_SPV" ]] && "${SEARCH_CMD[@]}" "_dash_spv_ffi_config_set_restrict_to_configured_peers" "$NM_SPV_OUTPUT" >/dev/null; then
-  :
-else
-  echo "❌ Missing symbol: dash_spv_ffi_config_set_restrict_to_configured_peers (in both main and spv libs)"
-  CHECK_OK=0
-fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/build_ios.sh` around lines 60 - 76, Extract the duplicated
symbol-check logic into a helper function (e.g., check_symbol) that takes a
symbol name, uses SEARCH_CMD with NM_MAIN_OUTPUT and, if LIB_SIM_SPV exists,
NM_SPV_OUTPUT, and on failure echoes the specific missing-symbol message and
sets CHECK_OK=0; then replace both blocks that check
"_dash_spv_ffi_config_add_peer" and
"_dash_spv_ffi_config_set_restrict_to_configured_peers" with calls to this
helper. Ensure the helper references SEARCH_CMD, NM_MAIN_OUTPUT, LIB_SIM_SPV,
NM_SPV_OUTPUT and updates CHECK_OK exactly as the duplicated blocks do.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/rs-sdk-ffi/build_ios.sh`:
- Around line 452-466: Inside the BUILD_ARCH="universal" branch where
ARM_SIM_SPV_LIB and X86_SIM_SPV_LIB are checked, add a diagnostic log when
exactly one of those files exists (and thus SIM_SPV_LIB stays unset) so the
merge is not silently skipped; detect presence of ARM_SIM_SPV_LIB and
X86_SIM_SPV_LIB, and if only one is found emit a warning message (including
which path is missing and the existing path) to stderr (e.g., via echo ... >&2)
so maintainers know the SPV sim slice wasn't created even though BUILD_ARCH is
universal.

---

Nitpick comments:
In `@packages/rs-sdk-ffi/build_ios.sh`:
- Line 479: The script uses eval $XCFRAMEWORK_CMD which is brittle with spaces
or metacharacters; change XCFRAMEWORK_CMD to be an array (e.g.,
XCFRAMEWORK_CMD=(xcodebuild ...)) and invoke it with "${XCFRAMEWORK_CMD[@]}"
instead of using eval so arguments are preserved; update any places that build
the command string (where XCFRAMEWORK_CMD is assigned or appended) to push
elements into the array rather than concatenating into a single string.

In `@packages/swift-sdk/build_ios.sh`:
- Around line 52-54: Initialize NM_MAIN_OUTPUT and NM_SPV_OUTPUT to empty
strings and register the trap before calling mktemp to avoid an untracked temp
file if the second mktemp fails; specifically, set NM_MAIN_OUTPUT="" and
NM_SPV_OUTPUT="" then add the existing trap 'rm -f "$NM_MAIN_OUTPUT"
"$NM_SPV_OUTPUT"' and only afterward call NM_MAIN_OUTPUT="$(mktemp)" and
NM_SPV_OUTPUT="$(mktemp)" in build_ios.sh so the trap always safely cleans up
even if a mktemp call exits early.
- Line 62: Replace the file-existence guard with a check that nm actually
produced output: in the conditional that currently uses [[ -f "$LIB_SIM_SPV" ]]
alongside SEARCH_CMD searching for "_dash_spv_ffi_config_add_peer" in
$NM_SPV_OUTPUT, change the test to [[ -s "$NM_SPV_OUTPUT" ]] so you assert that
nm captured non-empty output before running SEARCH_CMD; apply the same change to
the analogous check later (the other occurrence around the SEARCH_CMD usage) so
both conditions guard on NM_SPV_OUTPUT content rather than LIB_SIM_SPV presence.
- Around line 60-76: Extract the duplicated symbol-check logic into a helper
function (e.g., check_symbol) that takes a symbol name, uses SEARCH_CMD with
NM_MAIN_OUTPUT and, if LIB_SIM_SPV exists, NM_SPV_OUTPUT, and on failure echoes
the specific missing-symbol message and sets CHECK_OK=0; then replace both
blocks that check "_dash_spv_ffi_config_add_peer" and
"_dash_spv_ffi_config_set_restrict_to_configured_peers" with calls to this
helper. Ensure the helper references SEARCH_CMD, NM_MAIN_OUTPUT, LIB_SIM_SPV,
NM_SPV_OUTPUT and updates CHECK_OK exactly as the duplicated blocks do.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/rs-sdk-ffi/src/address_sync/provider.rs (1)

157-166: has_pending fallback allocates a full Vec just to test emptiness.

The None fallback calls self.pending_addresses(), which copies every address key into heap-allocated Vec<u8> before calling .is_empty(). Checking only the count field of the raw DashSDKPendingAddressList avoids all allocation.

♻️ Allocation-free fallback
     fn has_pending(&self) -> bool {
         unsafe {
             let vtable = &*self.ffi.vtable;
             if let Some(has_pending) = vtable.has_pending {
                 has_pending(self.ffi.context)
             } else {
-                // Default implementation
-                !self.pending_addresses().is_empty()
+                // Default: ask the C side without materialising keys
+                let list = (vtable.pending_addresses)(self.ffi.context);
+                !list.addresses.is_null() && list.count > 0
             }
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-sdk-ffi/src/address_sync/provider.rs` around lines 157 - 166, The
fallback in has_pending currently calls pending_addresses() which allocates a
Vec, so change the fallback to inspect the raw DashSDKPendingAddressList count
instead of building a Vec: add/use a small unsafe helper that invokes the
existing FFI function that returns a DashSDKPendingAddressList (or otherwise
obtains the raw pointer/struct), read its count field and return count > 0, and
ensure any FFI-owned list is properly freed if required; update has_pending (and
reference vtable.has_pending and DashSDKPendingAddressList) to use this
allocation-free count check instead of pending_addresses().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/rs-sdk-ffi/src/address_sync/provider.rs`:
- Around line 76-78: AddressProviderFFI never invokes its optional destroy
callback on drop, leaving FFI-side resources uncleaned; add a Drop impl for
AddressProviderFFI that, in drop, unsafely dereferences self.vtable, checks
vtable.destroy (the NullableDestroyProviderFn) for Some, and if present calls it
with self.context so the FFI-provided destructor runs during Rust cleanup.
- Around line 20-21: CallbackAddressProvider currently never invokes the destroy
callback and leaks resources; implement Drop for CallbackAddressProvider to
fetch the vtable from self.ffi, check vtable.destroy and call it with
self.ffi.context (unsafe block) to ensure C-side resources are freed when the
struct is dropped. Also change the has_pending fallback to avoid allocating a
full Vec by checking pending_addresses() for emptiness without cloning all
address payloads (use pending_addresses().is_empty() if available) or add an
is_empty() method to the AddressProvider trait and call that from has_pending to
avoid the Vec allocation; update references to has_pending, pending_addresses,
AddressProvider, CallbackAddressProvider, and the ffi.vtable.destroy field
accordingly.

---

Nitpick comments:
In `@packages/rs-sdk-ffi/src/address_sync/provider.rs`:
- Around line 157-166: The fallback in has_pending currently calls
pending_addresses() which allocates a Vec, so change the fallback to inspect the
raw DashSDKPendingAddressList count instead of building a Vec: add/use a small
unsafe helper that invokes the existing FFI function that returns a
DashSDKPendingAddressList (or otherwise obtains the raw pointer/struct), read
its count field and return count > 0, and ensure any FFI-owned list is properly
freed if required; update has_pending (and reference vtable.has_pending and
DashSDKPendingAddressList) to use this allocation-free count check instead of
pending_addresses().

Comment on lines 20 to 21
/// Nullable function pointer for checking if there are pending addresses
pub type NullableHasPendingFn = Option<HasPendingFn>;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n packages/rs-sdk-ffi/src/address_sync/provider.rs | head -100

Repository: dashpay/platform

Length of output: 4580


🏁 Script executed:

cat -n packages/rs-sdk-ffi/src/address_sync/provider.rs | sed -n '150,170p'

Repository: dashpay/platform

Length of output: 872


🏁 Script executed:

rg -A 5 "impl Drop for" packages/rs-sdk-ffi/src/address_sync/

Repository: dashpay/platform

Length of output: 42


🌐 Web query:

cbindgen type alias Option function pointer nullable typedef generation

💡 Result:

  • In Rust, function pointer types (extern "C" fn(...) -> ...) are non-nullable. To accept/hold a nullable C callback, you must use Option<extern "C" fn(...) -> ...>, which uses the nullable-pointer optimization and is ABI-compatible with a single pointer (i.e., C NULL maps to None). [1], [2]

  • In cbindgen, type aliases and fn() function-pointer types are supported, but the docs only explicitly call out Option<&T> / Option<&mut T> (not Option<fn(...)>), so a type NullableCb = Option<extern "C" fn(...)> alias may not generate the typedef you want depending on your setup/version. [3]

Practical pattern that works well with cbindgen:

pub type Callback = unsafe extern "C" fn(arg1: i32) -> i32;
// Use Option<Callback> at the FFI boundary where null is allowed:
pub type NullableCallback = Option<Callback>; // (may or may not be emitted as a typedef)

#[repr(C)]
pub struct Api {
    pub cb: NullableCallback, // nullable in C via NULL
}
  • Even if cbindgen doesn’t emit a separate typedef for NullableCallback, it can still emit Callback as a normal C function-pointer typedef (e.g. typedef int32_t (*Callback)(int32_t);), and the C side can pass NULL; Rust receives that as None via Option<Callback>. This matches Rust’s documented FFI model for nullable function pointers. [1], [2], [3]

Sources:
[1] Rust core docs: nullable function pointers via Option<fn()> (nullable-pointer optimization)
[2] Rust FFI docs: nullable pointer optimization, Option<extern "C" fn(...)> for nullable callbacks
[3] cbindgen docs: supported items include type and fn(); Option support is explicitly listed for references, not function pointers


Add Drop impl to call destroy callback and avoid resource leak.

The destroy field at line 77 is never invoked. If the C callback allocates resources, they will leak when CallbackAddressProvider is dropped. Implement Drop for CallbackAddressProvider to call the destroy callback if present:

impl<'a> Drop for CallbackAddressProvider<'a> {
    fn drop(&mut self) {
        unsafe {
            let vtable = &*self.ffi.vtable;
            if let Some(destroy) = vtable.destroy {
                destroy(self.ffi.context);
            }
        }
    }
}

Optimize has_pending fallback to avoid allocating a full Vec.

Lines 163-164 allocate a complete Vec<(u32, Vec<u8>)> just to call is_empty() when the has_pending callback is absent. Instead, check if pending_addresses() is empty without cloning all address data. If the underlying provider trait requires consuming the list, consider adding a non-allocating is_empty() check to the AddressProvider trait.

cbindgen nullable function pointer aliases:

The NullableXFn = Option<XFn> pattern at lines 20-21, 27-28, and 50-51 is idiomatic for FFI. Rust's Option<extern "C" fn(...)> is guaranteed null-pointer optimized and ABI-compatible. However, cbindgen's typedef generation for such Option<fn> aliases is not explicitly documented; verify your cbindgen configuration emits the expected typedef declarations if distinct C identifiers are required in your API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-sdk-ffi/src/address_sync/provider.rs` around lines 20 - 21,
CallbackAddressProvider currently never invokes the destroy callback and leaks
resources; implement Drop for CallbackAddressProvider to fetch the vtable from
self.ffi, check vtable.destroy and call it with self.ffi.context (unsafe block)
to ensure C-side resources are freed when the struct is dropped. Also change the
has_pending fallback to avoid allocating a full Vec by checking
pending_addresses() for emptiness without cloning all address payloads (use
pending_addresses().is_empty() if available) or add an is_empty() method to the
AddressProvider trait and call that from has_pending to avoid the Vec
allocation; update references to has_pending, pending_addresses,
AddressProvider, CallbackAddressProvider, and the ffi.vtable.destroy field
accordingly.

Comment on lines 76 to 78
/// Optional destructor for cleanup
pub destroy: Option<DestroyProviderFn>,
pub destroy: NullableDestroyProviderFn,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for Drop implementations for AddressProviderFFI and CallbackAddressProvider
rg -n "impl Drop for AddressProviderFFI|impl Drop for CallbackAddressProvider" --type rust -C 3

Repository: dashpay/platform

Length of output: 42


🏁 Script executed:

# Check the file under review to understand the structure
cat -n packages/rs-sdk-ffi/src/address_sync/provider.rs

Repository: dashpay/platform

Length of output: 10790


🏁 Script executed:

# Search more broadly for Drop implementations in the FFI codebase
rg -n "impl Drop" packages/rs-sdk-ffi/ --type rust

Repository: dashpay/platform

Length of output: 42


Implement Drop for AddressProviderFFI to ensure the destroy callback is invoked during cleanup.

Currently, AddressProviderFFI has no Drop implementation, so the optional destroy callback is never called. This leaves resource cleanup to the FFI consumer, which is error-prone. Add a Drop impl that safely calls destroy if provided:

impl Drop for AddressProviderFFI {
    fn drop(&mut self) {
        unsafe {
            let vtable = &*self.vtable;
            if let Some(destroy) = vtable.destroy {
                destroy(self.context);
            }
        }
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-sdk-ffi/src/address_sync/provider.rs` around lines 76 - 78,
AddressProviderFFI never invokes its optional destroy callback on drop, leaving
FFI-side resources uncleaned; add a Drop impl for AddressProviderFFI that, in
drop, unsafely dereferences self.vtable, checks vtable.destroy (the
NullableDestroyProviderFn) for Some, and if present calls it with self.context
so the FFI-provided destructor runs during Rust cleanup.

@thepastaclaw
Copy link
Contributor Author

CI triage note for Build Swift SDK and Example (no warnings) failure:\n\n- Failing run: https://github.com/dashpay/platform/actions/runs/22265453606/job/64410693152\n- The current error (cannot find type FFIDetailedSyncProgress / FFINetworks / FFISyncStage) is in Swift compile after XCFramework generation.\n- This appears pre-existing to this branch: generating dash_sdk_ffi.h on upstream/v3.1-dev currently does not export those FFI* symbols either, and this workflow is broadly red on unrelated PRs as well (e.g. https://github.com/dashpay/platform/actions/runs/22264485054).\n\nSo this check failure is not a regression uniquely introduced by PR #3143; this branch mostly surfaced an already-broken Swift SDK CI path after the earlier SPV-symbol gate.

@thepastaclaw
Copy link
Contributor Author

Addressed CodeRabbit feedback:

  • build_ios.sh partial universal SPV sim slice: fixed in commit 096ac558a.

    • Added an explicit warning to stderr when only one of aarch64-apple-ios-sim / x86_64-apple-ios SPV libs exists during BUILD_ARCH=universal.
  • ℹ️ destroy callback in address_sync/provider.rs: this is intentionally invoked in dash_sdk_address_provider_free(...) (see packages/rs-sdk-ffi/src/address_sync/mod.rs).

    • Adding Drop on CallbackAddressProvider or AddressProviderFFI would risk double-destroy and also break provider reuse across multiple sync calls.
  • ℹ️ has_pending fallback allocation: current fallback (!pending_addresses().is_empty()) only runs when has_pending callback is absent; it preserves backward compatibility and current AddressProvider trait shape.

If you still want this fallback optimized, we should do it as a separate API-shaping PR (new non-allocating callback/trait method), rather than inside this iOS CI plumbing PR.

@thepastaclaw
Copy link
Contributor Author

Triaged the Swift CI failure for this branch. This is not flaky and appears pre-existing/unrelated to this PR: the Swift package fails to compile because DashSDKFFI headers at the current pinned rust-dashcore revision do not expose FFIDetailedSyncProgress, FFINetworks, and FFIManagedAccountCollectionSummary (same deterministic errors across runs 22265218059/22265390864/22265453606/22265820164).\n\nThere is already an open fix PR for this area: #3107 (fix(swift-sdk): fix iOS XCFramework build and update Swift SDK for rust-dashcore changes).\n\nGiven scope rules (avoid patching unrelated pre-existing CI into this PR), I did not add workaround commits here.

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