Skip to content

Conversation

@PastaPastaPasta
Copy link
Member

@PastaPastaPasta PastaPastaPasta commented Aug 27, 2025

Summary by CodeRabbit

  • New Features

    • Full wallet FFI: create/import/list wallets, query balances/UTXOs, generate receive/change addresses, monitor addresses, and record/send transactions.
    • New config-from-raw-network constructor and explicit API to clear the last error.
    • Address validation utility and safe string-array destruction utility.
  • Refactor

    • Retrieval APIs now return heap-allocated arrays (caller frees); FFI config/network layouts adjusted.
  • Tests

    • Tests updated, gated, and many marked ignored pending mock/test rewrites.
  • Chores

    • Removed outdated examples and static peer reputation data.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 27, 2025

Warning

Rate limit exceeded

@PastaPastaPasta has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 6 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbit 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 4529dcd and 8c84caf.

📒 Files selected for processing (12)
  • hashes/src/hash160.rs (1 hunks)
  • hashes/src/hash_x11.rs (1 hunks)
  • hashes/src/ripemd160.rs (1 hunks)
  • hashes/src/sha1.rs (1 hunks)
  • hashes/src/sha256.rs (1 hunks)
  • hashes/src/sha256d.rs (1 hunks)
  • hashes/src/sha256t.rs (1 hunks)
  • hashes/src/sha512.rs (1 hunks)
  • hashes/src/sha512_256.rs (1 hunks)
  • hashes/src/siphash24.rs (1 hunks)
  • hashes/src/util.rs (1 hunks)
  • rpc-integration-test/src/main.rs (0 hunks)

Walkthrough

FFI array layout and ownership were changed (elem_size/elem_align; arrays now returned as heap pointers). Client/config FFI signatures adjusted and ClientConfig aliased. Large wallet FFI surface added. Tests and mocks updated/gated; examples and peer reputation JSONs removed; assorted small refactors and lint fixes applied.

Changes

Cohort / File(s) Summary
FFI headers & types
dash-spv-ffi/include/dash_spv_ffi.h, swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h, dash-spv-ffi/src/types.rs, dash-spv-ffi/src/config.rs
Added elem_size/elem_align to FFIArray; retrieval APIs now return struct FFIArray *; added dash_spv_ffi_string_array_destroy and dash_spv_ffi_clear_error; FFIClientConfig aliased to ClientConfig; FFIClientConfig marked #[repr(transparent)].
FFI client wrappers
dash-spv-ffi/src/client.rs
Many getters now return *mut FFIArray (heap-allocated); added null-safety checks; logging moved to tracing; simplified validation patterns; error-paths return boxed empty arrays.
FFI wallet API (new)
dash-spv-ffi/src/wallet.rs
Large new wallet FFI: watch items, balances, UTXOs, transactions, address generation, wallet create/import/list, mempool queries, many FFI types and destroyers; address validation and error reporting via FFI.
FFI tests & callsites
dash-spv-ffi/tests/*, dash-spv-ffi/tests/c_tests/test_advanced.c, swift-dash-core-sdk/.../dash_spv_ffi.h
Tests and C examples updated for pointer-returning arrays (FFIArray*), adjusted destroy calls, import cleanups, underscore-renamed locals, and updated config constructor usage.
Core config & enums
dash-spv/src/client/config.rs, dash-network/src/lib.rs
ClientConfig annotated #[repr(C)]; Network enum annotated #[repr(u8)].
Client / block processing
dash-spv/src/client/block_processor.rs, dash-spv/src/client/mod.rs
Removed unused per-address balance helper; minor binding/import cleanup; added #[allow(dead_code)] to mempool update helper.
Sync / filter plumbing
dash-spv/src/sync/filters.rs, dash-spv/src/sync/sequential/mod.rs
Marked unused parameters with underscores; added allow(dead_code) to a helper; no control-flow changes.
Tests gating & scaffolding
dash-spv/Cargo.toml, dash-spv/tests/*, dash-spv/src/mempool_filter.rs
Added feature skip_mock_implementation_incomplete; converted many cfg gates to feature + per-test #[ignore]; widened/rewrote mocks and test scaffolding; explicit generics in tests.
Removals
dash-spv/examples/reorg_demo.rs, dash-spv/peer_reputation.json, dash-spv-ffi/peer_reputation.json
Removed reorg demo example and static peer reputation JSONs.
Error / misc changes
dash-spv/src/error.rs, dash/src/blockdata/script/mod.rs, rpc-client/src/lib.rs
Implemented Clone for StorageError; adjusted Error::source() for BitcoinConsensus variant; moved unused-lint to crate level.
Examples & deps
dash/examples/ecdsa-psbt.rs, dash/examples/taproot-psbt.rs, dash/Cargo.toml
Switched PSBT/BIP32 imports to key_wallet crate; adjusted taproot hash handling; added key-wallet dev-dependency.
Minor test cleanups
dash-spv/src/*_test.rs, dash-spv/tests/*
Removed unused imports/vars, renamed bindings to _, deleted unused helpers; purely housekeeping.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as C/Swift Caller
  participant F as FFI (C ABI)
  participant R as Rust Core

  rect rgb(245,250,255)
  note right of F: Retrieval APIs now return heap pointers
  C->>F: dash_spv_ffi_client_get_utxos(client)
  F->>R: fetch_utxos(client)
  R-->>F: Vec<Utxo>
  F->>F: allocate Boxed FFIArray { data,len,cap,elem_size,elem_align }
  F-->>C: *FFIArray (ownership transfers)
  C->>F: dash_spv_ffi_array_destroy(arr)
  F->>F: free buffer using elem_size/elem_align and drop FFIArray
  F-->>C: (freed)
  end
Loading
sequenceDiagram
  autonumber
  participant C as C/Swift Caller
  participant F as FFI Wallet
  participant R as Wallet Manager

  rect rgb(245,255,245)
  note right of F: New wallet FFI surface
  C->>F: dash_spv_ffi_wallet_create_from_mnemonic(...)
  F->>R: create_wallet(...)
  R-->>F: wallet_id (String)
  F->>F: convert to FFIString
  F-->>C: *FFIString (owned)
  C->>F: dash_spv_ffi_wallet_get_receive_address(client,wid,...)
  F->>R: next_receive_address(...)
  R-->>F: address + account_type_used
  F->>F: allocate FFIAddressGenerationResult
  F-->>C: *FFIAddressGenerationResult
  C->>F: dash_spv_ffi_address_generation_result_destroy(ptr)
  F->>F: free inner FFIString and result struct
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • QuantumExplorer
  • ogabrielides

Poem

A rabbit taps keys with delight,
Arrays hop to heap in the night,
Wallets whisper cross the C sea,
Strings and addresses set free.
Carrots, structs, and alignment—whee! 🥕

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/resolve-various-warnings-and-issues

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai bot changed the title @coderabbitai FFIArray metadata, pointer-based getters; wallet mempool API update Aug 27, 2025
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: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (10)
dash-spv/src/client/mod.rs (1)

2162-2173: Fix: unresolved Version/CompactTarget here (imports appear later in the function)

These identifiers are used before the use dashcore::{ block::{Header as BlockHeader, Version}, pow::CompactTarget } appears at Line 2241, which will not compile. Either fully-qualify here or move the use above Line 2162.

Apply one of these diffs to fix immediately:

-                    let checkpoint_header = dashcore::block::Header {
-                        version: Version::from_consensus(536870912),
+                    let checkpoint_header = dashcore::block::Header {
+                        version: dashcore::block::Version::from_consensus(536870912),
                         prev_blockhash: checkpoint.prev_blockhash,
                         merkle_root: checkpoint
                             .merkle_root
                             .map(|h| dashcore::TxMerkleNode::from_byte_array(*h.as_byte_array()))
                             .unwrap_or_else(dashcore::TxMerkleNode::all_zeros),
                         time: checkpoint.timestamp,
-                        bits: CompactTarget::from_consensus(
+                        bits: dashcore::pow::CompactTarget::from_consensus(
                             checkpoint.target.to_compact_lossy().to_consensus(),
                         ),
                         nonce: checkpoint.nonce,
                     };

Or move the use dashcore::{ block::{Header as BlockHeader, Version}, pow::CompactTarget } to just before Line 2162.

dash-spv-ffi/src/types.rs (1)

486-494: Ownership mismatch in raw_tx destroy docs vs implementation

Docs say the caller allocates raw_tx before passing to Rust, but the implementation frees it via Vec::from_raw_parts, which assumes Rust allocation. Clarify that raw_tx is allocated by Rust before returning to C and must be freed by this function; otherwise this is UB.

Update the doc comment accordingly and, if needed, add a sibling API for freeing C-allocated buffers.

dash-spv-ffi/tests/integration/test_full_workflow.rs (2)

483-486: Fix pointer semantics and free nested strings

Here you deref the pointer and call the generic destroy, which mismatches the new pointer-return API and leaks inner strings.

Apply:

-                if !watched.is_null() {
-                    println!("Watched addresses persisted: {} addresses", (*watched).len);
-                    dash_spv_ffi_array_destroy(*watched);
-                }
+                if !watched.is_null() {
+                    println!("Watched addresses persisted: {} addresses", (*watched).len);
+                    dash_spv_ffi_string_array_destroy(watched);
+                }

1-492: Ensure correct destruction of FFI string arrays

To avoid leaking the inner C strings (and to match the intended destructor), every call to destroy a FFIArray returned by dash_spv_ffi_client_get_watched_addresses (and similarly get_watched_scripts) must use dash_spv_ffi_string_array_destroy, not the generic dash_spv_ffi_array_destroy. Please update the following locations:

• dash-spv-ffi/tests/integration/test_full_workflow.rs
– Thread-5 “Get watched addresses” block (around line 351):
diff - if !addresses.is_null() { - dash_spv_ffi_array_destroy(addresses); - } + if !addresses.is_null() { + dash_spv_ffi_string_array_destroy(addresses); + }
– Persistence check (around line 485):
diff - if !watched.is_null() { - println!(…); - dash_spv_ffi_array_destroy(*watched); - } + if !watched.is_null() { + println!(…); + dash_spv_ffi_string_array_destroy(watched); + }

• dash-spv-ffi/tests/c_tests/test_integration.c (line 200):

-     dash_spv_ffi_array_destroy(*watched);
+     dash_spv_ffi_string_array_destroy(watched);

• dash-spv-ffi/tests/unit/test_wallet_operations.rs (lines 932–934):

- dash_spv_ffi_array_destroy(addresses);
+ dash_spv_ffi_string_array_destroy(addresses);

• Audit any other calls to get_watched_addresses and get_watched_scripts to ensure that all string‐based arrays are torn down with dash_spv_ffi_string_array_destroy.

swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h (1)

149-158: Header out-of-date: FFIEventCallbacks is missing new callbacks.

Rust side uses on_compact_filter_matched and on_wallet_transaction. Missing fields here break layout when Swift/C passes the struct.

Add to the struct (order must match Rust):

 typedef struct FFIEventCallbacks {
   BlockCallback on_block;
   TransactionCallback on_transaction;
   BalanceCallback on_balance_update;
   MempoolTransactionCallback on_mempool_transaction_added;
   MempoolConfirmedCallback on_mempool_transaction_confirmed;
   MempoolRemovedCallback on_mempool_transaction_removed;
+  void (*on_compact_filter_matched)(const uint8_t (*block_hash)[32],
+                                    const char *matched_scripts,
+                                    const char *wallet_id,
+                                    void *user_data);
+  void (*on_wallet_transaction)(const char *wallet_id,
+                                uint32_t account_index,
+                                const uint8_t (*txid)[32],
+                                bool confirmed,
+                                int64_t amount,
+                                const char *addresses,
+                                uint32_t block_height,
+                                bool is_ours,
+                                void *user_data);
   void *user_data;
 } FFIEventCallbacks;

Re-run header sync to guarantee ABI parity.

dash-spv-ffi/src/wallet.rs (2)

480-526: Decode error handling: don’t use unwrap_or_default() for wallet_id.

unwrap_or_default() masks invalid-hex vs wrong-length. Use explicit match and set last_error accordingly.

-    let bytes = hex::decode(wallet_id_hex).unwrap_or_default();
-    if bytes.len() != 32 {
-        set_last_error("Wallet ID must be 32 bytes hex");
-        return std::ptr::null_mut();
-    }
+    let bytes = match hex::decode(wallet_id_hex) {
+        Ok(b) => b,
+        Err(e) => {
+            set_last_error(&format!("Invalid hex wallet ID: {}", e));
+            return std::ptr::null_mut();
+        }
+    };
+    if bytes.len() != 32 {
+        set_last_error("Wallet ID must be 32 bytes hex");
+        return std::ptr::null_mut();
+    }

Apply same fix in dash_spv_ffi_wallet_get_balance().


812-841: wallet_list returns FFIString array; ensure proper cleanup path.*

Same concern as above: expose and require dash_spv_ffi_string_array_destroy; otherwise strings (and possibly the struct) leak.

dash-spv-ffi/tests/unit/test_wallet_operations.rs (1)

1-1182: FFI getter coverage confirmed; missing dedicated UTXO-array destroyer

I verified that all pointer-returning FFI getters—dash_spv_ffi_client_get_utxos, get_watched_addresses, and get_watched_scripts—are exercised in both unit and integration tests, and their returned arrays/strings are properly destroyed with the existing generic dash_spv_ffi_array_destroy and dash_spv_ffi_string_array_destroy calls. However, I did not find a dedicated UTXO-array destroy function (dash_spv_ffi_utxo_array_destroy) declared or implemented, which is needed to mirror the pattern used for other typed FFI returns.

Please add the following:

  • In dash-spv-ffi/include/dash_spv_ffi.h, declare:
    /// Frees an array of UTXO structs returned by the FFI
    void dash_spv_ffi_utxo_array_destroy(FFIArray* utxos);
  • In dash-spv-ffi/src/client.rs, implement the extern C wrapper, e.g.:
    #[no_mangle]
    pub unsafe extern "C" fn dash_spv_ffi_utxo_array_destroy(utxos: *mut FFIArray) {
        dash_spv_ffi_array_destroy(utxos);
    }
  • Add a unit test (e.g. in tests/unit/test_memory_management.rs) that allocates a fake FFIArray of UTXOs, calls dash_spv_ffi_utxo_array_destroy, and asserts no leaks/crashes, analogous to the existing array-destroy tests.

This will ensure consistency with other typed FFI destroyers and prevent memory‐management footguns at the UTXO boundary.

dash-spv-ffi/include/dash_spv_ffi.h (2)

209-216: FFIArray destroy semantics are incomplete for pointer-returning APIs — add “owned” destroyers and document

  • Current doc says “call dash_spv_ffi_array_destroy,” but that only frees the buffer; heap-allocated FFIArray structs (returned by many APIs in this PR) are leaked.
  • Add owned variants and recommend them for pointer-returning getters.

Header additions:

 typedef struct FFIArray {
   void *data;
   uintptr_t len;
   uintptr_t capacity;
   uintptr_t elem_size;
   uintptr_t elem_align;
 } FFIArray;

 void dash_spv_ffi_array_destroy(struct FFIArray *arr);
+/**
+ * Destroy an owned (heap-allocated) FFIArray returned by pointer-returning APIs:
+ * - Deep-frees the vector buffer (like dash_spv_ffi_array_destroy)
+ * - Frees the FFIArray struct itself
+ * Safe to pass NULL.
+ */
+void dash_spv_ffi_array_destroy_owned(struct FFIArray *arr);

 /**
  * Destroy an array of FFIString pointers (Vec<*mut FFIString>) and their contents.
  */
 void dash_spv_ffi_string_array_destroy(struct FFIArray *arr);
+/**
+ * Owned variant that also frees the FFIArray struct.
+ * Safe to pass NULL.
+ */
+void dash_spv_ffi_string_array_destroy_owned(struct FFIArray *arr);

Also add typed UTXO array destroyers:

+/**
+ * Destroy an FFIArray of FFIUtxo elements:
+ * - Calls dash_spv_ffi_utxo_destroy on each element
+ * - Frees the vector buffer; owned variant also frees the struct
+ */
+void dash_spv_ffi_utxo_array_destroy(struct FFIArray *arr);
+void dash_spv_ffi_utxo_array_destroy_owned(struct FFIArray *arr);

Also applies to: 660-671


792-801: wallet_list docs mismatch implementation: elements are FFIString pointers, not inline FFIString values

Rust returns Vec<*mut FFIString>; callers must use dash_spv_ffi_string_array_destroy(_owned).

- * Pointer to FFIArray of FFIString objects containing hex-encoded WalletIds
+ * Pointer to FFIArray of pointers to FFIString (FFIString*) containing hex-encoded WalletIds.
+ * Free with dash_spv_ffi_string_array_destroy_owned().
🧹 Nitpick comments (33)
dash-spv/tests/block_download_test.rs (1)

201-201: Avoid double-disabling tests (cfg + #[ignore]).

This file already has a module-level cfg gate; the added #[ignore]s are redundant and add maintenance overhead. Recommend removing the per-test #[ignore] lines and relying on the file-level cfg, or drop the cfg and keep a single #[ignore] approach consistently across the repo (preferred for “cargo test -- --ignored” usability).

Apply this diff if you choose to keep the module-level cfg and drop redundant per-test ignores:

-#[ignore = "mock implementation incomplete"]
-#[ignore = "mock implementation incomplete"]
-#[ignore = "mock implementation incomplete"]
-#[ignore = "mock implementation incomplete"]
-#[ignore = "mock implementation incomplete"]
-#[ignore = "mock implementation incomplete"]
-#[ignore = "mock implementation incomplete"]
-#[ignore = "mock implementation incomplete"]

Also applies to: 212-212, 249-249, 302-302, 318-318, 368-368, 389-389, 420-420

dash-spv/tests/edge_case_filter_sync_test.rs (1)

145-145: Consolidate test gating.

Same redundancy here: top-of-file cfg plus per-test #[ignore]. Pick one mechanism; suggest removing the per-test #[ignore]s for consistency and simpler re-enablement.

-#[ignore = "mock implementation incomplete"]
-#[ignore = "mock implementation incomplete"]
-#[ignore = "mock implementation incomplete"]

Also applies to: 187-187, 248-248

dash-spv/tests/rollback_test.rs (2)

17-20: Use a single #[ignore] with a clear reason.

Two #[ignore] attributes on the same test are redundant. Keep the specific one (“rollback_to_height not implemented…”) and drop the generic one.

-#[ignore = "mock implementation incomplete"]
 #[tokio::test]
 #[ignore = "rollback_to_height not implemented in StorageManager trait"]
 async fn test_disk_storage_rollback() -> Result<(), Box<dyn std::error::Error>> {

77-80: Same here: consolidate #[ignore].

Prefer one #[ignore] with the specific reason.

-#[ignore = "mock implementation incomplete"]
 #[tokio::test]
 #[ignore = "rollback_to_height not implemented in StorageManager trait"]
 async fn test_disk_storage_rollback_filter_headers() -> Result<(), Box<dyn std::error::Error>> {
dash-spv/tests/integration_real_node_test.rs (1)

65-65: Enable feature-gated ignores for real-node integration tests

The tests already bail out early when no Dash Core node is found—hard-ignoring them entirely prevents catching regressions in the skip logic. Instead, gate the ignore attribute behind a feature so that:

  • By default (in CI and normal local runs) the tests remain ignored.
  • Developers can opt in via --features real-node-tests to exercise the availability check and the test body.

Apply the following replacement at each occurrence (lines 65, 108, 183, 334, 397, 470, 536 in dash-spv/tests/integration_real_node_test.rs):

-#[ignore = "requires local Dash Core node"]
+#[cfg_attr(not(feature = "real-node-tests"), ignore = "requires local Dash Core node")]

Optional bonus: add an explicit env-var check for clearer failures/skips:

if std::env::var("RUN_REAL_NODE_TESTS").ok().as_deref() != Some("1") {
    eprintln!("skipping real-node test; set RUN_REAL_NODE_TESTS=1 to enable");
    return;
}

Occurrences to update:

  • dash-spv/tests/integration_real_node_test.rs:65
  • dash-spv/tests/integration_real_node_test.rs:108
  • dash-spv/tests/integration_real_node_test.rs:183
  • dash-spv/tests/integration_real_node_test.rs:334
  • dash-spv/tests/integration_real_node_test.rs:397
  • dash-spv/tests/integration_real_node_test.rs:470
  • dash-spv/tests/integration_real_node_test.rs:536
dash-spv/tests/chainlock_validation_test.rs (1)

175-175: Redundant per-test #[ignore] — file already compile-gated

This module is behind #![cfg(skip_mock_implementation_incomplete)]; adding #[ignore] on each test is duplicative. Drop the per-test ignores to reduce maintenance noise. If you still want default-skip behavior when the cfg is enabled, gate the ignore behind a feature.

Example fix:

-#[ignore = "mock implementation incomplete"]
+#[cfg_attr(not(feature = "mock-tests"), ignore = "mock implementation incomplete")]

Also applies to: 225-225, 284-284, 341-341, 396-396

dash-spv/tests/error_recovery_integration_test.rs (1)

122-122: Unify test gating: use a single mechanism instead of blanket #[ignore] everywhere

Same pattern as other files: the crate-level #![cfg(skip_mock_implementation_incomplete)] already prevents these from compiling unless opted-in. Prefer a feature-gated ignore to keep them runnable when explicitly enabled.

Apply to each shown line:

-#[ignore = "mock implementation incomplete"]
+#[cfg_attr(not(feature = "mock-tests"), ignore = "mock implementation incomplete")]

Also applies to: 224-224, 273-273, 328-328, 405-405

dash-spv/tests/instantsend_integration_test.rs (1)

1-1: Consistent gating for InstantSend tests; prefer feature-gated ignore

Good to isolate these until the wallet API stabilizes. Replace blanket #[ignore] with a feature-gated version so contributors can run them with --features instantsend-tests.

-#![cfg(skip_mock_implementation_incomplete)]
+#![cfg(skip_mock_implementation_incomplete)]
 // keep the file-level cfg if desired

-// These tests are currently ignored until they can be properly updated.
+// These tests are skipped by default; enable with `--features instantsend-tests`.

-#[ignore = "instantsend tests not yet updated"]
+#[cfg_attr(not(feature = "instantsend-tests"), ignore = "instantsend tests not yet updated")]

Also applies to: 13-13, 98-98

dash-spv/tests/error_handling_test.rs (1)

1-1: Deduplicate gating: convert repeated #[ignore] to a feature-gated ignore

The file is already under #![cfg(skip_mock_implementation_incomplete)]. Replacing repeated ignores with a feature-gated form makes it easy to run the suite locally without editing code.

Batch change pattern:

-#[ignore = "mock implementation incomplete"]
+#[cfg_attr(not(feature = "mock-tests"), ignore = "mock implementation incomplete")]

Optional follow-up: document the features in Cargo.toml:

  • mock-tests: enables mocked suites
  • real-node-tests: enables live-node integration tests
  • instantsend-tests: enables InstantSend suites

Also applies to: 5-5, 540-540, 556-556, 587-587, 627-627, 645-645, 677-677, 695-695, 727-727, 743-743, 855-855, 889-889, 923-923, 1068-1068, 1112-1112, 1188-1188

dash-spv/src/client/mod.rs (2)

431-442: Mempool filter initialized with empty address set

Both here and in start(), the filter is built with an empty HashSet. Until wallet integration wires monitored addresses in, filter matching will never trigger. Consider gating enablement until addresses are available or plumb a minimal wallet query now.

Would you like a small PR to fetch monitored addresses from WalletInterface (behind a feature-flag) and populate this set?


515-524: Balance clamping hides outgoing pending amounts

Negatives are clamped to 0, so a pending spend will not be reflected. If the API expects signed deltas, return signed values; if not, at least document that outgoing mempool debits aren’t surfaced.

Proposed change (if unsigned API must remain, expose signed alongside it):

-        let pending_sats = if pending < 0 { 0 } else { pending as u64 };
-        let pending_instant_sats = if pending_instant < 0 { 0 } else { pending_instant as u64 };
+        // Outgoing (negative) pending is represented in separate signed fields for clarity
+        let pending_sats = pending.max(0) as u64;
+        let pending_instant_sats = pending_instant.max(0) as u64;

And add signed fields or a doc comment stating negatives are dropped.

dash-spv/src/client/block_processor.rs (2)

604-608: Stat bug: incrementing blocks_requested during chain-state update

blocks_requested should not be incremented here; you already increment blocks_processed after successful processing. This double counts.

Apply:

-            {
-                let mut stats = self.stats.write().await;
-                stats.blocks_requested += 1;
-            }
+            // No stat change here; blocks_processed is updated on successful processing.

121-127: Unbounded processed_blocks can grow without limit

If many blocks are processed over time, this set will grow indefinitely. Consider a bounded LRU/TTL or remove the duplicate check if not needed.

I can provide a small ring-buffer implementation if helpful.

dash-spv-ffi/src/types.rs (2)

361-380: *String array destroy assumes Vec<mut FFIString>; be consistent with other APIs

Elsewhere (e.g., FFIUnconfirmedTransaction.addresses) we use arrays of FFIString-by-value, not pointers. Mixing both shapes is error-prone for C callers.

  • Either standardize on arrays of FFIString-by-value and drop this function, or rename it (e.g., dash_spv_ffi_string_ptr_array_destroy) and document clearly.
  • If keeping both, add a matching destroy for arrays of by-value FFIString stored in FFIArray (iterate values and call dash_spv_ffi_string_destroy), and add runtime asserts using elem_size to catch misuse.
    I can draft those helpers if you confirm the intended shapes.

506-514: Addresses array destroy mirrors Rust allocation assumption

Same as above: this frees memory as if it were a Rust-allocated Vec. Ensure all such arrays are produced by Rust. If any C code allocates them, this will be UB.

Document and enforce allocation provenance, or switch to per-element free + single libc::free.

dash-spv/src/mempool_filter.rs (5)

44-46: Avoid redundant collect in constructor

watched_addresses: watched_addresses.into_iter().collect() just rebuilds a HashSet. Assign directly.

Apply:

-            watched_addresses: watched_addresses.into_iter().collect(),
+            watched_addresses,

70-110: Inputs not considered for relevance (outgoing spends undetected)

is_transaction_relevant only inspects outputs. Outgoing transactions that spend our UTXOs won’t be marked relevant. If intentional for now, add a clarifying comment; otherwise, extend with input outpoint/script checks when wallet data is available.


478-486: Unused locals in tests cause -D warnings

script and watched_outpoint are never used; will warn. Prefix with underscore or remove.

Apply:

-        let script = addr.script_pubkey();
+        let _script = addr.script_pubkey();
-        let watched_outpoint = OutPoint {
+        let _watched_outpoint = OutPoint {

Also applies to: 513-521


341-358: Unused helper function triggers dead_code in tests

create_test_wallet_manager is unused. Either remove, prefix with _, or allow dead_code on the function.

Apply one:

-    fn create_test_wallet_manager(network: Network) -> SPVWalletManager {
+    #[allow(dead_code)]
+    fn create_test_wallet_manager(network: Network) -> SPVWalletManager {

505-549: Outpoint-focused test currently asserts only addresses

This “outpoint” test doesn’t wire watched outpoints into the filter and can’t fail meaningfully beyond address checks. Consider renaming or extending once wallet watch-by-outpoint is available.

dash-spv/tests/cfheader_gap_test.rs (1)

143-278: MockNetworkManager is adequate; consider time mocking to avoid sleeps

The cooldown test uses real tokio::time::sleep. If feasible, expose an injectable clock or use tokio::time::pause/advance to make this deterministic and faster.

dash-spv-ffi/tests/unit/test_memory_management.rs (1)

41-67: Consider adding a string-array lifecycle test

To cover dash_spv_ffi_string_array_destroy, add a test that builds FFIArray of FFIString and ensures all inner strings and buffer are freed without leaks or double-frees.

Also applies to: 144-172, 330-348

dash-spv-ffi/src/wallet.rs (1)

389-423: String arrays: return type requires a matching destroyer.

dash_spv_ffi_wallet_get_monitored_addresses returns FFIArray of FFIString*. Callers must use a typed destroyer (dash_spv_ffi_string_array_destroy) to free both contents and the array struct; document this in the header and tests.

I can add usage in tests and declare the function in headers if missing.

dash-spv-ffi/src/client.rs (6)

1021-1066: Pointer-based array return: align error-path semantics and cleanup.

  • On error, this returns an “empty boxed array” instead of null, while other APIs return null. Prefer consistent null-on-error + set_last_error.
  • Ensure consumers know which destroyer to call; for UTXO arrays, a typed destroyer is needed to free nested FFIStrings.

Minimal change:

-        Err(e) => {
-            set_last_error(&e.to_string());
-            Box::into_raw(Box::new(FFIArray { data: std::ptr::null_mut(), len: 0, capacity: 0, elem_size: 0, elem_align: 1, }))
-        }
+        Err(e) => {
+            set_last_error(&e.to_string());
+            std::ptr::null_mut()
+        }

1068-1159: Same as above for get_utxos_for_address.

Unify error returns to null and document the required destroyer for successful non-empty arrays.


1419-1462: Not-implemented functions should return null, not an empty array.

Returning an empty array conflates “not implemented” with “no results.” Return null and set_last_error for clarity.


1549-1564: Ditto: watched addresses – return null on unimplemented.

Keep semantics consistent across APIs.


1567-1582: Ditto: watched scripts – return null on unimplemented.

Consistency helps FFI consumers.


252-371: Callback locking may block reconfiguration.

Event loop locks event_callbacks across callback invocation. If a callback re-enters API that also locks event_callbacks, risk of deadlock exists. Consider cloning the callbacks struct before invocation or narrowing lock scope.

dash-spv-ffi/tests/unit/test_wallet_operations.rs (4)

185-191: Add explicit non-null assertions before dereferencing FFIArray pointers

Future changes may return null on hard errors; add assert!(!ptr.is_null()) before deref to avoid UB in tests.

-            assert_eq!((*utxos).len, 0);
+            assert!(!utxos.is_null());
+            assert_eq!((*utxos).len, 0);
-            assert!((*utxos).data.is_null());
+            assert!(!utxos.is_null());
+            assert!((*utxos).data.is_null());
-            assert_eq!(unsafe { (*history).len }, 0);
+            assert!(!history.is_null());
+            assert_eq!(unsafe { (*history).len }, 0);

Also applies to: 195-197, 212-217, 428-441, 868-871


480-489: Unnecessary nested unsafe blocks inside an enclosing unsafe scope

You can drop the inner unsafe { ... } to reduce noise.

-            for i in 0..unsafe { (*wallet_list).len } {
-                let string_ptr = unsafe { (*wallet_list).data as *const *const FFIString };
-                let ffi_string = unsafe { *string_ptr.add(i) };
+            for i in 0..(*wallet_list).len {
+                let string_ptr = (*wallet_list).data as *const *const FFIString;
+                let ffi_string = *string_ptr.add(i);

Also applies to: 934-939, 1148-1151


924-940: Future-proof watched addresses/scripts tests to string-array semantics

These functions are currently “not implemented” but will likely return arrays of FFIString pointers. Use string-array destroyers now to keep tests stable when implemented.

Also applies to: 1049-1054


1016-1060: Persistence test uses array_destroy — switch to string_array_destroy_owned

To avoid leaking when persistence is implemented to return strings.

Comment on lines +487 to 326
struct FFIArray *dash_spv_ffi_client_get_utxos(struct FFIDashSpvClient *client);

struct FFIArray dash_spv_ffi_client_get_utxos_for_address(struct FFIDashSpvClient *client,
const char *address);
struct FFIArray *dash_spv_ffi_client_get_utxos_for_address(struct FFIDashSpvClient *client,
const char *address);

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

API surface changes to pointer-returning arrays look coherent

Signatures align with Rust. Once owned/typed destroyers are added and documented, this is good.

Run to ensure Swift header sync is updated after regen:

Also applies to: 509-511, 535-537, 746-748


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Public header decls for new owned/typed destroyers:"
rg -n 'dash_spv_ffi_(array_destroy_owned|string_array_destroy_owned|utxo_array_destroy)' dash-spv-ffi/include/dash_spv_ffi.h || true

echo
echo "Swift SDK header sync check:"
# Expect these to exist after sync if SDK mirrors FFI header
rg -n 'dash_spv_ffi_(array_destroy_owned|string_array_destroy_owned|utxo_array_destroy)' swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h || true

Length of output: 501


Add missing owned/typed destroyers and sync Swift header

It looks like the new destroyer functions aren’t present in either the C public header or its Swift mirror. These are critical for FFI memory safety and must be added before merging.

Locations needing updates:

  • dash-spv-ffi/include/dash_spv_ffi.h
  • swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h

Suggested additions to dash-spv-ffi/include/dash_spv_ffi.h:

/// Releases ownership of an FFIArray returned from the FFI.
/// Caller must invoke this to avoid leaks.
void dash_spv_ffi_array_destroy_owned(struct FFIArray *array);

/// Releases ownership of a string-array returned from the FFI.
/// Caller must invoke this to avoid leaks.
void dash_spv_ffi_string_array_destroy_owned(struct FFIArray *array);

/// Releases ownership of a UTXO-array returned from the FFI.
/// Caller must invoke this to avoid leaks.
void dash_spv_ffi_utxo_array_destroy(struct FFIArray *array);

After adding these to the C header, regenerate and commit the synced header in the Swift SDK at:

swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h

so it mirrors the updated declarations.

🤖 Prompt for AI Agents
In dash-spv-ffi/include/dash_spv_ffi.h around lines 487-491 the new FFI
destroyer functions for owned/typed arrays are missing; add declarations for
dash_spv_ffi_array_destroy_owned(struct FFIArray *array),
dash_spv_ffi_string_array_destroy_owned(struct FFIArray *array), and
dash_spv_ffi_utxo_array_destroy(struct FFIArray *array) to the public C header,
then regenerate and commit the mirrored header at
swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h so the Swift SDK
matches the updated C declarations.

Comment on lines +518 to 356
struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);

struct FFIArray dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);
struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Clarify element type for watched addresses/scripts and required destructor

Document these as arrays of FFIString* and point to dash_spv_ffi_string_array_destroy_owned().

-struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);
+/* Returns an FFIArray of FFIString* (addresses). Free with dash_spv_ffi_string_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);
@@
-struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);
+/* Returns an FFIArray of FFIString* (hex scripts). Free with dash_spv_ffi_string_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);

Also applies to: 740-742

🤖 Prompt for AI Agents
In dash-spv-ffi/include/dash_spv_ffi.h around lines 518-521 (and similarly at
740-742), the declarations for dash_spv_ffi_client_get_watched_addresses and
dash_spv_ffi_client_get_watched_scripts lack documentation of the element type
and the required destructor; update the comments for these functions to state
they return an FFIArray of FFIString* (i.e., an array whose elements are
FFIString pointers) and explicitly instruct callers to free the returned array
with dash_spv_ffi_string_array_destroy_owned().

Comment on lines +52 to 57
FFIArray* utxos = dash_spv_ffi_client_get_address_utxos(client, test_addresses[0]);
if (utxos != NULL) {
// New wallet should have no UTXOs
TEST_ASSERT(utxos.len == 0);
dash_spv_ffi_array_destroy(&utxos);
TEST_ASSERT(utxos->len == 0);
dash_spv_ffi_array_destroy(utxos);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Typed array cleanup needed for nested data (UTXO arrays).

FFIUtxo contains FFIString fields. Freing only the buffer (even with struct free) will leak those inner strings. Provide a typed destroyer and require its use where applicable.

Add in dash-spv-ffi/src/types.rs:

#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_utxo_array_destroy(arr: *mut FFIArray) {
    if arr.is_null() { return; }
    let len = (*arr).len as usize;
    let data = (*arr).data as *mut crate::FFIUtxo;
    for i in 0..len {
        let utxo = data.add(i);
        crate::types::dash_spv_ffi_string_destroy((*utxo).txid);
        crate::types::dash_spv_ffi_string_destroy((*utxo).script_pubkey);
        crate::types::dash_spv_ffi_string_destroy((*utxo).address);
    }
    dash_spv_ffi_array_destroy(arr);
}

Then use this destroyer in C/Swift for UTXO arrays.

🤖 Prompt for AI Agents
In dash-spv-ffi/tests/c_tests/test_advanced.c around lines 52 to 57: the test
frees a returned UTXO array with the generic dash_spv_ffi_array_destroy which
leaks inner FFIString fields inside each FFIUtxo; add a typed destroy on the
Rust side (dash_spv_ffi_utxo_array_destroy) that iterates elements, calls the
existing dash_spv_ffi_string_destroy on txid, script_pubkey and address for each
entry, then calls dash_spv_ffi_array_destroy on the outer array, expose it with
no_mangle/extern "C", and update this C test to call
dash_spv_ffi_utxo_array_destroy(utxos) (with a null check) instead of
dash_spv_ffi_array_destroy so nested strings are properly freed.

⚠️ Potential issue

FFIArray leak risk: current destroyer doesn’t free the struct.

dash_spv_ffi_array_destroy only frees the vector buffer, not the FFIArray struct. Since APIs now return heap-allocated FFIArray*, the struct itself will leak. Fix on the Rust side by freeing the struct too (see types.rs change below). No test changes needed if the destroyer is fixed.

Apply in dash-spv-ffi/src/types.rs:

-pub unsafe extern "C" fn dash_spv_ffi_array_destroy(arr: *mut FFIArray) {
-    if !arr.is_null() {
-        // Only deallocate the vector buffer recorded in the struct; do not free the struct itself.
-        // This makes it safe to pass pointers to stack-allocated FFIArray values returned by-value.
-        if !(*arr).data.is_null() && (*arr).capacity > 0 {
-            use std::alloc::{dealloc, Layout};
-            let size = (*arr).elem_size.saturating_mul((*arr).capacity);
-            if size > 0 && (*arr).elem_align.is_power_of_two() && (*arr).elem_align > 0 {
-                let layout = Layout::from_size_align_unchecked(size, (*arr).elem_align);
-                unsafe { dealloc((*arr).data as *mut u8, layout) };
-            }
-        }
-    }
-}
+pub unsafe extern "C" fn dash_spv_ffi_array_destroy(arr: *mut FFIArray) {
+    if arr.is_null() {
+        return;
+    }
+    // Deallocate vector buffer
+    if !(*arr).data.is_null() && (*arr).capacity > 0 {
+        use std::alloc::{dealloc, Layout};
+        let size = (*arr).elem_size.saturating_mul((*arr).capacity);
+        if size > 0 && (*arr).elem_align.is_power_of_two() && (*arr).elem_align > 0 {
+            let layout = Layout::from_size_align_unchecked(size, (*arr).elem_align);
+            dealloc((*arr).data as *mut u8, layout);
+        }
+    }
+    // Free the FFIArray struct itself (heap-allocated by Rust)
+    drop(Box::from_raw(arr));
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FFIArray* utxos = dash_spv_ffi_client_get_address_utxos(client, test_addresses[0]);
if (utxos != NULL) {
// New wallet should have no UTXOs
TEST_ASSERT(utxos.len == 0);
dash_spv_ffi_array_destroy(&utxos);
TEST_ASSERT(utxos->len == 0);
dash_spv_ffi_array_destroy(utxos);
}
pub unsafe extern "C" fn dash_spv_ffi_array_destroy(arr: *mut FFIArray) {
if arr.is_null() {
return;
}
// Deallocate vector buffer
if !(*arr).data.is_null() && (*arr).capacity > 0 {
use std::alloc::{dealloc, Layout};
let size = (*arr).elem_size.saturating_mul((*arr).capacity);
if size > 0 && (*arr).elem_align.is_power_of_two() && (*arr).elem_align > 0 {
let layout = Layout::from_size_align_unchecked(size, (*arr).elem_align);
dealloc((*arr).data as *mut u8, layout);
}
}
// Free the FFIArray struct itself (heap-allocated by Rust)
drop(Box::from_raw(arr));
}
🤖 Prompt for AI Agents
In dash-spv-ffi/tests/c_tests/test_advanced.c around lines 52 to 57 the test
calls dash_spv_ffi_array_destroy(utxos) but the current destroyer only frees the
internal buffer and not the FFIArray struct itself, causing a heap leak; fix the
Rust side in dash-spv-ffi/src/types.rs by updating the destroy function to free
both the internal vector buffer and then deallocate/free the FFIArray struct
pointer (ensure proper ownership handling and null-checks before freeing) so
tests need no change.

Comment on lines 351 to 354
let addresses = dash_spv_ffi_client_get_watched_addresses(client);
if !addresses.is_null() {
dash_spv_ffi_array_destroy(*addresses);
dash_spv_ffi_array_destroy(addresses);
}
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

Use string-array destroy for watched addresses

dash_spv_ffi_client_get_watched_addresses returns an array of strings. Free with the dedicated string-array destroyer, not the generic one.

Apply:

-                                    dash_spv_ffi_array_destroy(addresses);
+                                    dash_spv_ffi_string_array_destroy(addresses);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let addresses = dash_spv_ffi_client_get_watched_addresses(client);
if !addresses.is_null() {
dash_spv_ffi_array_destroy(*addresses);
dash_spv_ffi_array_destroy(addresses);
}
let addresses = dash_spv_ffi_client_get_watched_addresses(client);
if !addresses.is_null() {
dash_spv_ffi_string_array_destroy(addresses);
}
🤖 Prompt for AI Agents
In dash-spv-ffi/tests/integration/test_full_workflow.rs around lines 351 to 354,
the watched addresses returned by dash_spv_ffi_client_get_watched_addresses are
a string array but are being freed with the generic dash_spv_ffi_array_destroy;
replace that call with the dedicated string-array destroyer
(dash_spv_ffi_string_array_destroy) while keeping the existing null check so the
returned pointer is correctly freed using the proper destructor.

Comment on lines 185 to 191
let utxos = dash_spv_ffi_client_get_address_utxos(client, addr.as_ptr());

// New wallet should have no UTXOs
assert_eq!(utxos.len, 0);
if !utxos.data.is_null() {
dash_spv_ffi_array_destroy(&mut utxos as *mut FFIArray);
}
assert_eq!((*utxos).len, 0);
// Properly destroy the array buffer
dash_spv_ffi_array_destroy(utxos);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Critical: arrays returned as FFIArray leak (and UTXO inner strings leak) — use typed “owned” destroyers*

  • dash_spv_ffi_array_destroy() only frees the Vec buffer, not the FFIArray struct; every call here leaks the Box.
  • For UTXO arrays, elements embed FFIString fields; freeing the buffer without walking elements leaks each inner CString. A typed array destroyer is required.

Action:

  1. Add Rust “owned” destroyers that also free the FFIArray struct.
  2. Add a typed destroyer for arrays of FFIUtxo that deep-destroys elements.
  3. Switch tests to use these.

Apply in tests (representative hunks):

@@
-            dash_spv_ffi_array_destroy(utxos);
+            dash_spv_ffi_utxo_array_destroy_owned(utxos);
@@
-            assert!((*utxos).data.is_null());
-            dash_spv_ffi_array_destroy(utxos);
+            assert!(!utxos.is_null());
+            assert!((*utxos).data.is_null());
+            dash_spv_ffi_array_destroy_owned(utxos);
@@
-            dash_spv_ffi_array_destroy(history);
+            dash_spv_ffi_array_destroy_owned(history);
@@
-            dash_spv_ffi_array_destroy(utxos);
+            dash_spv_ffi_utxo_array_destroy_owned(utxos);
@@
-                    if !utxos.is_null() {
-                        dash_spv_ffi_array_destroy(utxos);
-                    }
+                    if !utxos.is_null() {
+                        dash_spv_ffi_utxo_array_destroy_owned(utxos);
+                    }
@@
-            // Destroy the array - this should not crash
-            dash_spv_ffi_array_destroy(utxos);
+            // Destroy the array - this should not crash
+            dash_spv_ffi_utxo_array_destroy_owned(utxos);
@@
-            dash_spv_ffi_array_destroy(wallet_list);
+            dash_spv_ffi_string_array_destroy_owned(wallet_list);
@@
-            dash_spv_ffi_array_destroy(std::ptr::null_mut()); // Should be safe
+            dash_spv_ffi_array_destroy_owned(std::ptr::null_mut()); // Should be safe
@@
-                dash_spv_ffi_array_destroy(utxos);
+                dash_spv_ffi_utxo_array_destroy_owned(utxos);
@@
-                dash_spv_ffi_array_destroy(wallet_list);
+                dash_spv_ffi_string_array_destroy_owned(wallet_list);
@@
-            let null_result = dash_spv_ffi_wallet_get_utxos(client, invalid_wallet_id.as_ptr());
-            assert!(null_result.is_null()); // Should return null for invalid wallet ID
-            dash_spv_ffi_array_destroy(null_result); // Should handle null gracefully
+            let null_result = dash_spv_ffi_wallet_get_utxos(client, invalid_wallet_id.as_ptr());
+            assert!(null_result.is_null()); // Should return null for invalid wallet ID
+            dash_spv_ffi_utxo_array_destroy_owned(null_result); // Should handle null gracefully

Add in Rust (dash-spv-ffi/src/types.rs):

#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_array_destroy_owned(arr: *mut FFIArray) {
    if arr.is_null() { return; }
    dash_spv_ffi_array_destroy(arr);
    let _ = Box::from_raw(arr);
}

#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_string_array_destroy_owned(arr: *mut FFIArray) {
    dash_spv_ffi_string_array_destroy(arr);
    if !arr.is_null() { let _ = Box::from_raw(arr); }
}

Typed UTXO array destroyers:

#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_utxo_array_destroy(arr: *mut FFIArray) {
    if arr.is_null() { return; }
    if !(*arr).data.is_null() && (*arr).len > 0 {
        let slice = std::slice::from_raw_parts_mut((*arr).data as *mut crate::FFIUtxo, (*arr).len);
        for utxo in slice.iter_mut() {
            // Reuse element destroyer to free inner FFIStrings
            crate::dash_spv_ffi_utxo_destroy(utxo as *mut crate::FFIUtxo);
        }
    }
    dash_spv_ffi_array_destroy(arr);
}

#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_utxo_array_destroy_owned(arr: *mut FFIArray) {
    dash_spv_ffi_utxo_array_destroy(arr);
    if !arr.is_null() { let _ = Box::from_raw(arr); }
}

Expose in header accordingly (see header review).

Also applies to: 195-197, 212-217, 428-441, 868-871, 1115-1182

🤖 Prompt for AI Agents
In dash-spv-ffi/tests/unit/test_wallet_operations.rs around lines 185 to 191 the
test calls dash_spv_ffi_array_destroy on FFIArray* results which leaks the
FFIArray Box and, for UTXO arrays, the inner FFIString contents; add and use
"owned" destroyers that free the Box as well as typed deep-destroyers for
FFIUtxo arrays. Implement in dash-spv-ffi/src/types.rs: an owned generic array
destroyor that calls the existing array destroy and then Box::from_raw on the
pointer, a string-array owned destroyor, a dash_spv_ffi_utxo_array_destroy that
iterates elements and calls the utxo element destroyer to free inner strings
before destroying the buffer, plus an owned variant for the utxo array; update
the C header exports and replace calls in the test (and at other affected
locations: ~195-197, 212-217, 428-441, 868-871, 1115-1182) to use the typed
owned destroyers.

Comment on lines 473 to 499
// Test listing wallets with proper memory management
let wallet_list = dash_spv_ffi_wallet_list(client);
// Just ensure we can call the function without crashing
println!("Wallet list function called successfully");
// wallet_list is now a pointer to FFIArray (heap allocated)
assert!(!wallet_list.is_null());
// Should have 3 wallets
assert_eq!(unsafe { (*wallet_list).len }, 3);
// Each element should be a valid FFIString
for i in 0..unsafe { (*wallet_list).len } {
let string_ptr = unsafe { (*wallet_list).data as *const *const FFIString };
let ffi_string = unsafe { *string_ptr.add(i) };
assert!(!ffi_string.is_null());
// Verify the string is a valid hex wallet ID (64 characters)
let c_str = unsafe { CStr::from_ptr((*ffi_string).ptr) };
let wallet_id_str = c_str.to_str().unwrap();
assert_eq!(wallet_id_str.len(), 64);
assert!(wallet_id_str.chars().all(|c| c.is_ascii_hexdigit()));
}

// Clean up wallets only
// Clean up wallets
for wallet_id in wallet_ids {
if !wallet_id.is_null() {
let string_struct = unsafe { Box::from_raw(wallet_id) };
dash_spv_ffi_string_destroy(*string_struct);
}
}
// Skip array destruction for now to avoid memory corruption
// Properly destroy the wallet list array, including inner strings
dash_spv_ffi_string_array_destroy(wallet_list);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use string-array destroyer for arrays of FFIString pointers

wallet_list and monitored addresses/scripts are arrays of FFIString pointers per implementation; calling dash_spv_ffi_array_destroy() skips freeing inner strings and leaks. Use the string-array “owned” destroyer.

Apply in tests:

-            dash_spv_ffi_string_array_destroy(wallet_list);
+            dash_spv_ffi_string_array_destroy_owned(wallet_list)
-            dash_spv_ffi_string_array_destroy(addresses);
+            dash_spv_ffi_string_array_destroy_owned(addresses);
-            let addresses = dash_spv_ffi_client_get_watched_addresses(client);
-            assert_eq!(unsafe { (*addresses).len }, 0);
-            dash_spv_ffi_array_destroy(addresses);
+            let addresses = dash_spv_ffi_client_get_watched_addresses(client);
+            assert_eq!(unsafe { (*addresses).len }, 0);
+            dash_spv_ffi_string_array_destroy_owned(addresses);
-                let addresses = dash_spv_ffi_client_get_watched_addresses(client);
-                if !addresses.is_null() {
-                    dash_spv_ffi_array_destroy(addresses);
-                }
+                let addresses = dash_spv_ffi_client_get_watched_addresses(client);
+                if !addresses.is_null() {
+                    dash_spv_ffi_string_array_destroy_owned(addresses);
+                }
-            let wallet_list = dash_spv_ffi_wallet_list(client);
-            assert!(!wallet_list.is_null());
-            unsafe { assert_eq!((*wallet_list).len, 1); }
-            dash_spv_ffi_array_destroy(wallet_list);
+            let wallet_list = dash_spv_ffi_wallet_list(client);
+            assert!(!wallet_list.is_null());
+            unsafe { assert_eq!((*wallet_list).len, 1); }
+            dash_spv_ffi_string_array_destroy_owned(wallet_list);

Also switch watched scripts:

-            let scripts = dash_spv_ffi_client_get_watched_scripts(client);
-            assert_eq!(unsafe { (*scripts).len }, 0);
-            dash_spv_ffi_array_destroy(scripts);
+            let scripts = dash_spv_ffi_client_get_watched_scripts(client);
+            assert_eq!(unsafe { (*scripts).len }, 0);
+            dash_spv_ffi_string_array_destroy_owned(scripts);

Also applies to: 689-693, 1152-1166, 931-940, 1049-1054

Comment on lines +373 to 321
struct FFIArray *dash_spv_ffi_client_get_utxos(struct FFIDashSpvClient *client);

struct FFIArray dash_spv_ffi_client_get_utxos_for_address(struct FFIDashSpvClient *client,
struct FFIArray *dash_spv_ffi_client_get_utxos_for_address(struct FFIDashSpvClient *client,
const char *address);
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

Header/ABI mismatch: FFIArray is missing elem_size/elem_align.

Rust’s FFIArray now carries elem_size/elem_align and the destroyer relies on them. This header defines only data/len/capacity, which is ABI-unsafe.

Update the struct and re-sync headers (./sync-headers.sh):

-typedef struct FFIArray {
-  void *data;
-  uintptr_t len;
-  uintptr_t capacity;
-} FFIArray;
+typedef struct FFIArray {
+  void *data;
+  uintptr_t len;
+  uintptr_t capacity;
+  uintptr_t elem_size;
+  uintptr_t elem_align;
+} FFIArray;

Also applies to: 119-124

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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
dash-spv-ffi/tests/test_mempool_tracking.rs (1)

128-131: Avoid potential UAF: destroy client before freeing user_data

If the client could invoke callbacks during destruction or on background threads, freeing user_data first risks UAF. Free it after dash_spv_ffi_client_destroy.

-        // Clean up
-        let _ = Box::from_raw(test_callbacks_ptr);
-        dash_spv_ffi_client_destroy(client);
+        // Clean up: destroy client first, then free user_data to avoid UAF
+        dash_spv_ffi_client_destroy(client);
+        let _ = Box::from_raw(test_callbacks_ptr);
         dash_spv_ffi_config_destroy(config);
dash-spv/src/mempool_filter.rs (2)

79-83: Bug: Address::from_script returns Option, not Result

This pattern will never match and drops all outputs. Use Some(...) instead of Ok(...).

-            if let Ok(address) = Address::from_script(&output.script_pubkey, self.network) {
+            if let Some(address) = Address::from_script(&output.script_pubkey, self.network) {
                 addresses.insert(address.clone());

147-152: Same Option-vs-Result bug here

Repeat the fix for address extraction in process_transaction.

-            if let Ok(address) = Address::from_script(&output.script_pubkey, self.network) {
+            if let Some(address) = Address::from_script(&output.script_pubkey, self.network) {
                 if self.strategy == MempoolStrategy::FetchAll || self.is_address_watched(&address) {
                     addresses.push(address);
                 }
             }
dash-spv-ffi/include/dash_spv_ffi.h (1)

198-208: Doc mismatch: array destruction now requires “owned” destroyers

APIs return heap-allocated FFIArray*. dash_spv_ffi_array_destroy() frees only the buffer, not the struct; callers will leak without an “owned” variant and typed deep-destroyers.

- * - Calling `dash_spv_ffi_array_destroy` to properly deallocate the memory
+ * - For arrays returned by this API (heap-allocated), call the corresponding
+ *   "..._destroy_owned" function to free both the vector buffer and the FFIArray struct.
+ *   Use typed destroyers (e.g., dash_spv_ffi_string_array_destroy_owned, dash_spv_ffi_utxo_array_destroy_owned)
+ *   when elements contain nested allocations.
dash-spv/tests/chainlock_validation_test.rs (2)

10-25: Remove unused imports to keep clippy clean.

Result, StorageManager, Header, and Duration aren’t used; they’ll fail -D warnings.

Apply:

-use dash_spv::error::Result;
 use dash_spv::network::NetworkManager;
-use dash_spv::storage::{DiskStorageManager, StorageManager};
+use dash_spv::storage::DiskStorageManager;
-use dashcore::block::Header;
 use dashcore::blockdata::constants::genesis_block;
 use dashcore::sml::masternode_list_engine::MasternodeListEngine;
 use dashcore::Network;
 use dashcore::{BlockHash, ChainLock};
 use std::sync::Arc;
-use std::time::Duration;
 use tempfile::TempDir;
 use tokio::sync::RwLock;
 use tracing::{info, Level};

175-183: Fix test_chainlock_validation_without_masternode_engine: update client constructor and suppress unused-variable warning

The ignored test is still calling the old single-argument DashSpvClient::new(config) and will fail to compile now that the signature has changed to accept (config, network, storage, wallet). It also defines but never uses network, triggering a Clippy warning.

• In dash-spv/tests/chainlock_validation_test.rs, update every

let mut client = DashSpvClient::new(config).await.unwrap();

to

let mut client = DashSpvClient::new(config, network, storage_manager, wallet).await.unwrap();

(≈lines 196, 253, 307, 364, 419)

• Prefix the unused mock network variables to silence Clippy:

-    let network = Box::new(MockNetworkManager::new());
+    let _network = Box::new(MockNetworkManager::new());

(≈lines 186, 295, 352, 407)

♻️ Duplicate comments (11)
dash-spv/src/mempool_filter.rs (1)

256-290: Hardcoded/invalid addresses can panic; generate deterministic P2PKH instead

Replace placeholder strings with deterministic, network-valid P2PKH scripts to guarantee count and validity. This prevents indexing panics.

-    fn create_test_addresses(network: Network, count: usize) -> Vec<Address> {
-        let mut addresses = Vec::new();
-        // Use hardcoded addresses that are valid for the given network
-        let address_strings = match network {
-            Network::Dash => vec![
-                "XjbaGWaGnvEtuQAUoBgDxJWe8ZNv45upG2",
-                "Xan9iCVe1q5jYRDZ4VSMCtBjq2VyQA3Dge",
-                "XnC5y7Va2x8wF8v1J9J9J9J9J9J9J9J9J9",
-            ],
-            Network::Testnet => vec![
-                "yM7jWpY8jMgZ9a1b1b1b1b1b1b1b1b1b1b",
-                "yN8kXpZ9kNgA2c2c2c2c2c2c2c2c2c2c2c",
-                "yO9lYqA9lOhB3d3d3d3d3d3d3d3d3d3d3d",
-            ],
-            _ => vec![
-                "XjbaGWaGnvEtuQAUoBgDxJWe8ZNv45upG2",
-                "Xan9iCVe1q5jYRDZ4VSMCtBjq2VyQA3Dge",
-                "XnC5y7Va2x8wF8v1J9J9J9J9J9J9J9J9J9",
-            ],
-        };
-        for (i, addr_str) in address_strings.iter().enumerate() {
-            if i >= count {
-                break;
-            }
-            if let Ok(addr) = Address::from_str(addr_str) {
-                if let Ok(network_addr) = addr.require_network(network) {
-                    addresses.push(network_addr);
-                }
-            }
-        }
-        addresses
-    }
+    fn create_test_addresses(network: Network, count: usize) -> Vec<Address> {
+        (0..count)
+            .filter_map(|i| {
+                // 76a914 <20-byte hash> 88ac
+                let mut bytes = Vec::with_capacity(25);
+                bytes.extend_from_slice(&[0x76, 0xA9, 0x14]);
+                bytes.extend(std::iter::repeat(i as u8).take(20));
+                bytes.extend_from_slice(&[0x88, 0xAC]);
+                let script = ScriptBuf::from_bytes(bytes);
+                Address::from_script(&script, network)
+            })
+            .collect()
+    }
dash-spv-ffi/include/dash_spv_ffi.h (1)

518-521: Clarify element type and required destructor for watched-addresses/scripts

These return arrays of FFIString pointers. Document the element type and which destroyer to use.

-struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);
+/* Returns FFIArray of FFIString*; free with dash_spv_ffi_string_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);
@@
-struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);
+/* Returns FFIArray of FFIString*; free with dash_spv_ffi_string_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);
dash-spv-ffi/tests/unit/test_wallet_operations.rs (9)

185-191: Leak: use typed, owned destroyer for UTXO arrays

dash_spv_ffi_array_destroy() leaks the FFIArray struct and inner FFIString fields of elements.

-            assert_eq!((*utxos).len, 0);
-            // Properly destroy the array buffer
-            dash_spv_ffi_array_destroy(utxos);
+            assert_eq!((*utxos).len, 0);
+            // Deep-destroy elements and free the FFIArray struct
+            dash_spv_ffi_utxo_array_destroy_owned(utxos);

471-498: wallet_list is array of FFIString; use string-array owned destroyer*

-            // Properly destroy the wallet list array, including inner strings
-            dash_spv_ffi_string_array_destroy(wallet_list);
+            // Properly destroy the wallet list array, including inner strings and the FFIArray Box
+            dash_spv_ffi_string_array_destroy_owned(wallet_list);

929-937: Watched-addresses/scripts are arrays of FFIString; use owned string-array destroyer*

-            let addresses = dash_spv_ffi_client_get_watched_addresses(client);
-            assert_eq!((*addresses).len, 0);
-            dash_spv_ffi_array_destroy(addresses);
+            let addresses = dash_spv_ffi_client_get_watched_addresses(client);
+            assert_eq!((*addresses).len, 0);
+            dash_spv_ffi_string_array_destroy_owned(addresses);
@@
-            let scripts = dash_spv_ffi_client_get_watched_scripts(client);
-            assert_eq!((*scripts).len, 0);
-            dash_spv_ffi_array_destroy(scripts);
+            let scripts = dash_spv_ffi_client_get_watched_scripts(client);
+            assert_eq!((*scripts).len, 0);
+            dash_spv_ffi_string_array_destroy_owned(scripts);

689-693: Use owned string-array destroyer for monitored addresses

-            dash_spv_ffi_string_array_destroy(addresses);
+            dash_spv_ffi_string_array_destroy_owned(addresses);

1048-1052: Persistence test: switch to string-array owned destroyer

-                if !addresses.is_null() {
-                    dash_spv_ffi_array_destroy(addresses);
-                }
+                if !addresses.is_null() {
+                    dash_spv_ffi_string_array_destroy_owned(addresses);
+                }

865-869: Concurrent path: use owned UTXO destroyer

-                    if !utxos.is_null() {
-                        dash_spv_ffi_array_destroy(utxos);
-                    }
+                    if !utxos.is_null() {
+                        dash_spv_ffi_utxo_array_destroy_owned(utxos);
+                    }

428-441: UTXO arrays: deep-destroy and free Box

-            // Properly destroy the array to test memory management
-            dash_spv_ffi_array_destroy(utxos);
+            // Properly destroy the array to test memory management
+            dash_spv_ffi_utxo_array_destroy_owned(utxos);
@@
-                let string_struct = Box::from_raw(wallet_id);
-                dash_spv_ffi_string_destroy(*string_struct);
+                let string_struct = Box::from_raw(wallet_id);
+                dash_spv_ffi_string_destroy(*string_struct);

195-197: Leak on error path: use owned destroyer

-            assert!((*utxos).data.is_null());
-            dash_spv_ffi_array_destroy(utxos);
+            assert!((*utxos).data.is_null());
+            dash_spv_ffi_array_destroy_owned(utxos);

1113-1176: Memory-safety test should exercise owned/typed destroyers

Update to validate and use the correct destroyers, including null case.

-            let utxos = dash_spv_ffi_wallet_get_utxos(client, wallet_id_cstr.as_ptr());
-            assert!(!utxos.is_null());
-            assert_eq!((*utxos).len, 0); // New wallet should have no UTXOs
-                                         // Destroy the array - this should not crash
-            dash_spv_ffi_array_destroy(utxos);
+            let utxos = dash_spv_ffi_wallet_get_utxos(client, wallet_id_cstr.as_ptr());
+            assert!(!utxos.is_null());
+            assert_eq!((*utxos).len, 0); // New wallet should have no UTXOs
+            dash_spv_ffi_utxo_array_destroy_owned(utxos);
@@
-            // Test 2: wallet_list returns valid pointer and can be destroyed
+            // Test 2: wallet_list returns valid pointer and can be destroyed
             let wallet_list = dash_spv_ffi_wallet_list(client);
             assert!(!wallet_list.is_null());
             assert_eq!((*wallet_list).len, 1); // Should have 1 wallet
-                                               // Destroy the array - this should not crash
-            dash_spv_ffi_array_destroy(wallet_list);
+            dash_spv_ffi_string_array_destroy_owned(wallet_list);
@@
-            // Test 3: Double destroy should not crash (implementation should handle null checks)
-            dash_spv_ffi_array_destroy(std::ptr::null_mut()); // Should be safe
+            // Test 3: Double destroy should not crash (null-safe)
+            dash_spv_ffi_array_destroy_owned(std::ptr::null_mut());
@@
-                let utxos = dash_spv_ffi_wallet_get_utxos(client, wallet_id_cstr.as_ptr());
-                assert!(!utxos.is_null());
-                dash_spv_ffi_array_destroy(utxos);
+                let utxos = dash_spv_ffi_wallet_get_utxos(client, wallet_id_cstr.as_ptr());
+                assert!(!utxos.is_null());
+                dash_spv_ffi_utxo_array_destroy_owned(utxos);
@@
-                let wallet_list = dash_spv_ffi_wallet_list(client);
-                assert!(!wallet_list.is_null());
-                dash_spv_ffi_array_destroy(wallet_list);
+                let wallet_list = dash_spv_ffi_wallet_list(client);
+                assert!(!wallet_list.is_null());
+                dash_spv_ffi_string_array_destroy_owned(wallet_list);
@@
-            let null_result = dash_spv_ffi_wallet_get_utxos(client, invalid_wallet_id.as_ptr());
-            assert!(null_result.is_null()); // Should return null for invalid wallet ID
-            dash_spv_ffi_array_destroy(null_result); // Should handle null gracefully
+            let null_result = dash_spv_ffi_wallet_get_utxos(client, invalid_wallet_id.as_ptr());
+            assert!(null_result.is_null()); // Should return null for invalid wallet ID
+            dash_spv_ffi_utxo_array_destroy_owned(null_result); // Null-safe
🧹 Nitpick comments (18)
dash-spv/src/sync/validation_test.rs (2)

217-219: Turn TODO into a stable perf benchmark (Criterion) or remove the dead test scaffold

Leftover TODO in perf_tests adds no value and can bit-rot. Prefer moving perf to benches/ with Criterion to avoid flaky CI timing asserts; keep unit tests focused on correctness.

Example Criterion bench (outside this file):

// benches/validation.rs
use criterion::{criterion_group, criterion_main, Criterion, black_box};
use dash_spv::sync::chainlock_validation::{ChainLockValidationConfig, ChainLockValidator};

fn bench_validation(c: &mut Criterion) {
    let mut config = ChainLockValidationConfig::default();
    config.cache_size = 10_000;
    let validator = ChainLockValidator::new(config);

    c.bench_function("chainlock-cache-ops-10k", |b| {
        b.iter(|| {
            for i in 0..10_000u32 {
                black_box(i);
                // TODO: call inexpensive cache-touching path here
            }
        })
    });
}

criterion_group!(benches, bench_validation);
criterion_main!(benches);

233-233: Use unique 32-byte values and prevent optimization when simulating hashes

Current [i as u8; 32] collapses to 256 repeating values; also the optimizer may elide the loop. Feed a more unique 32-byte pattern and wrap in black_box.

Apply:

-            let _hash = BlockHash::from([i as u8; 32]);
+            let _hash = std::hint::black_box({
+                let mut a = [0u8; 32];
+                a[..4].copy_from_slice(&i.to_le_bytes());
+                BlockHash::from(a)
+            });
dash-spv/examples/filter_sync.rs (1)

20-22: Prefer checked address in examples.

Make the address explicitly checked to avoid propagating NetworkUnchecked in user code.

-    let watch_address = Address::<dashcore::address::NetworkUnchecked>::from_str(
-        "Xan9iCVe1q5jYRDZ4VSMCtBjq2VyQA3Dge",
-    )?;
+    let watch_address = Address::<dashcore::address::NetworkUnchecked>::from_str(
+        "Xan9iCVe1q5jYRDZ4VSMCtBjq2VyQA3Dge",
+    )?
+    .assume_checked();
dash-spv-ffi/tests/test_wallet.rs (1)

73-81: Mark placeholder test as ignored instead of passing.

Prevents false greens while keeping the stub.

-    #[test]
-    #[serial]
-    fn test_balance_conversion() {
-        // Skip this test for now - it has dependency issues
-        // This test would validate FFI balance conversion but requires
-        // proper Balance type imports which are complex to resolve
-        println!("Balance conversion test skipped - focus on new wallet functionality");
-        assert!(true);
-    }
+    #[test]
+    #[serial]
+    #[ignore = "dependency issues; wire Balance types and expectations"]
+    fn test_balance_conversion() {
+        // TODO: implement once Balance types are wired in
+    }
dash-network/src/lib.rs (1)

12-23: Stabilize ABI by giving explicit discriminants.

#[repr(u8)] is good for FFI, but assign explicit values to avoid accidental renumbering on variant reordering.

 #[repr(u8)]
 #[cfg_attr(feature = "bincode", derive(Encode, Decode))]
 pub enum Network {
-    /// Classic Dash Core Payment Chain
-    Dash,
-    /// Dash's testnet network.
-    Testnet,
-    /// Dash's devnet network.
-    Devnet,
-    /// Bitcoin's regtest network.
-    Regtest,
+    /// Classic Dash Core Payment Chain
+    Dash   = 0,
+    /// Dash's testnet network.
+    Testnet= 1,
+    /// Dash's devnet network.
+    Devnet = 2,
+    /// Bitcoin's regtest network.
+    Regtest= 3,
 }

Optionally, add impl From<Network> for u8 and impl core::convert::TryFrom<u8> for Network to centralize conversions.

dash-spv-ffi/src/config.rs (1)

36-50: Consider parity constructors for convenience (devnet/regtest).

You have config_mainnet/config_testnet; adding config_devnet/config_regtest improves API symmetry. If you want to reduce surface area instead, consider relying solely on dash_spv_ffi_config_new(FFINetwork).

Example additions:

#[no_mangle]
pub extern "C" fn dash_spv_ffi_config_devnet() -> *mut FFIClientConfig {
    Box::into_raw(Box::new(FFIClientConfig { inner: ClientConfig::devnet() }))
}

#[no_mangle]
pub extern "C" fn dash_spv_ffi_config_regtest() -> *mut FFIClientConfig {
    Box::into_raw(Box::new(FFIClientConfig { inner: ClientConfig::regtest() }))
}
dash-spv/Cargo.toml (2)

67-68: Unnecessary explicit lib path

[lib].path defaults to "src/lib.rs". Consider removing to reduce noise.

 [lib]
 name = "dash_spv"
-path = "src/lib.rs"
 

69-71: Feature flag LGTM; naming nit

Feature works as intended. Consider a positive name (e.g., mock_incomplete) to avoid double-negatives in cfg conditions.

dash-spv/tests/edge_case_filter_sync_test.rs (2)

6-6: Gating via feature is fine; consider simplifying ignore logic

The file is compiled only with the feature enabled. Given tests are also #[ignore], you now need both --features and -- --ignored to run them. If that’s intentional, fine; otherwise consider a single mechanism (feature or ignore) to reduce friction.


145-145: Consolidate conditional ignoring

You can make the ignore conditional to avoid redundancy with the file-level cfg.

-#[ignore = "mock implementation incomplete"]
+#[cfg_attr(feature = "skip_mock_implementation_incomplete", ignore = "mock implementation incomplete")]

Also applies to: 187-187, 248-248

dash-spv/tests/wallet_integration_test.rs (1)

66-77: Immutability change LGTM; avoid reaching into base directly

Leaving wallet_manager immutable is correct. Consider exposing thin pass-through methods on SPVWalletManager (e.g., wallet_count(), total_balance()) instead of reaching into base to keep encapsulation.

dash-spv/src/client/block_processor_test.rs (1)

301-322: Avoid flaky sleep; await the oneshot response instead

Replace the fixed sleep with awaiting response_rx to eliminate race/flakiness.

-        // Send mempool transaction task
-        let (response_tx, _response_rx) = oneshot::channel();
+        // Send mempool transaction task
+        let (response_tx, response_rx) = oneshot::channel();
@@
-        // Wait a bit for processing
-        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
+        // Wait for processing to complete
+        tokio::time::timeout(std::time::Duration::from_millis(200), response_rx)
+            .await
+            .expect("processor responded")
+            .expect("processor returned Ok");
dash-spv/src/mempool_filter.rs (1)

43-46: Unnecessary collect on HashSet

Input is already a HashSet. Avoid extra allocation.

-            watched_addresses: watched_addresses.into_iter().collect(),
+            watched_addresses,
dash-spv-ffi/include/dash_spv_ffi.h (1)

795-798: Correct wallet_list return docs to reflect pointer elements and owned destroyer

The implementation returns Vec<*mut FFIString>. Update docs and indicate the correct destroyer.

- * * Pointer to FFIArray of FFIString objects containing hex-encoded WalletIds
+ * * Pointer to FFIArray of FFIString* (array of pointers) containing hex-encoded WalletIds
+ *   Free with dash_spv_ffi_string_array_destroy_owned().
dash-spv/tests/chainlock_validation_test.rs (4)

225-233: Same issue: outdated ctor and unused/”unused mut”.

  • DashSpvClient::new(config) likely mismatches the current API.
  • mut client isn’t needed.
  • network is unused.

Apply:

-    let mut network = Box::new(MockNetworkManager::new());
+    let mut _network = Box::new(MockNetworkManager::new());
@@
-    let mut client = DashSpvClient::new(config).await.unwrap();
+    let client = DashSpvClient::new(/* update to match current signature */).await.unwrap();

See the verification script in the previous comment to extract the ctor signature and update accordingly.


284-295: Queue/process test: ctor likely needs updating; unused network.

Keep the test ignored, but ensure it compiles cleanly when enabled.

Apply:

-    let network = Box::new(MockNetworkManager::new());
+    let _network = Box::new(MockNetworkManager::new());
@@
-    let client = DashSpvClient::new(config).await.unwrap();
+    let client = DashSpvClient::new(/* update to match current signature */).await.unwrap();

341-361: Cache ops test: unused network; align ctor to current API.

Same pattern as above.

Apply:

-    let network = Box::new(MockNetworkManager::new());
+    let _network = Box::new(MockNetworkManager::new());
@@
-    let client = DashSpvClient::new(config).await.unwrap();
+    let client = DashSpvClient::new(/* update to match current signature */).await.unwrap();

396-446: Client update flow: outdated ctor and unnecessary mut.

  • Update constructor args.
  • Drop mut on client.

Apply:

-    let network = Box::new(MockNetworkManager::new());
+    let _network = Box::new(MockNetworkManager::new());
@@
-    let mut client = DashSpvClient::new(config).await.unwrap();
+    let client = DashSpvClient::new(/* update to match current signature */).await.unwrap();

Use the earlier script to confirm the new signature and adjust all call sites in this file in one pass.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e59ce84 and 5e015af.

📒 Files selected for processing (34)
  • dash-network/src/lib.rs (1 hunks)
  • dash-spv-ffi/include/dash_spv_ffi.h (12 hunks)
  • dash-spv-ffi/src/config.rs (1 hunks)
  • dash-spv-ffi/tests/test_event_callbacks.rs (4 hunks)
  • dash-spv-ffi/tests/test_mempool_tracking.rs (2 hunks)
  • dash-spv-ffi/tests/test_platform_integration.rs (1 hunks)
  • dash-spv-ffi/tests/test_wallet.rs (1 hunks)
  • dash-spv-ffi/tests/unit/test_async_operations.rs (0 hunks)
  • dash-spv-ffi/tests/unit/test_error_handling.rs (1 hunks)
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs (24 hunks)
  • dash-spv/Cargo.toml (1 hunks)
  • dash-spv/examples/filter_sync.rs (1 hunks)
  • dash-spv/src/chain/orphan_pool.rs (0 hunks)
  • dash-spv/src/chain/reorg.rs (0 hunks)
  • dash-spv/src/client/block_processor_test.rs (5 hunks)
  • dash-spv/src/client/config.rs (2 hunks)
  • dash-spv/src/client/config_test.rs (1 hunks)
  • dash-spv/src/mempool_filter.rs (11 hunks)
  • dash-spv/src/sync/sequential/recovery.rs (1 hunks)
  • dash-spv/src/sync/validation_test.rs (2 hunks)
  • dash-spv/src/validation/headers_test.rs (0 hunks)
  • dash-spv/tests/block_download_test.rs (10 hunks)
  • dash-spv/tests/chainlock_simple_test.rs (1 hunks)
  • dash-spv/tests/chainlock_validation_test.rs (6 hunks)
  • dash-spv/tests/edge_case_filter_sync_test.rs (4 hunks)
  • dash-spv/tests/error_handling_test.rs (21 hunks)
  • dash-spv/tests/error_recovery_integration_test.rs (6 hunks)
  • dash-spv/tests/filter_header_verification_test.rs (5 hunks)
  • dash-spv/tests/instantsend_integration_test.rs (3 hunks)
  • dash-spv/tests/multi_peer_test.rs (0 hunks)
  • dash-spv/tests/qrinfo_integration_test.rs (0 hunks)
  • dash-spv/tests/rollback_test.rs (3 hunks)
  • dash-spv/tests/smart_fetch_integration_test.rs (1 hunks)
  • dash-spv/tests/wallet_integration_test.rs (2 hunks)
💤 Files with no reviewable changes (6)
  • dash-spv/src/chain/reorg.rs
  • dash-spv/tests/qrinfo_integration_test.rs
  • dash-spv/tests/multi_peer_test.rs
  • dash-spv/src/chain/orphan_pool.rs
  • dash-spv/src/validation/headers_test.rs
  • dash-spv-ffi/tests/unit/test_async_operations.rs
✅ Files skipped from review due to trivial changes (3)
  • dash-spv/tests/smart_fetch_integration_test.rs
  • dash-spv/src/sync/sequential/recovery.rs
  • dash-spv-ffi/tests/test_platform_integration.rs
🚧 Files skipped from review as they are similar to previous changes (7)
  • dash-spv/tests/error_recovery_integration_test.rs
  • dash-spv/src/client/config.rs
  • dash-spv/tests/rollback_test.rs
  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv-ffi/tests/test_event_callbacks.rs
🧰 Additional context used
📓 Path-based instructions (12)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Use proper error types with the thiserror crate and propagate errors appropriately
Use the tokio runtime for async operations
Adhere to MSRV: code must compile on Rust 1.89 (avoid newer language/features)
Keep code formatted with rustfmt (cargo fmt)
Keep the codebase clippy-clean (clippy with -D warnings)

Files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/examples/filter_sync.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv/src/sync/validation_test.rs
  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-network/src/lib.rs
  • dash-spv/tests/chainlock_simple_test.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/src/client/block_processor_test.rs
  • dash-spv/src/client/config_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
{**/*.rs,**/Cargo.toml}

📄 CodeRabbit inference engine (CLAUDE.md)

Use feature flags and conditional compilation for optional features

Files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/examples/filter_sync.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv/src/sync/validation_test.rs
  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-network/src/lib.rs
  • dash-spv/tests/chainlock_simple_test.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv/Cargo.toml
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/src/client/block_processor_test.rs
  • dash-spv/src/client/config_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
**/tests/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/tests/**/*.rs: Write unit tests for new functionality
Use proptest for property-based testing where appropriate

Files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv/tests/chainlock_simple_test.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
{dash-network,dash-spv}/tests/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Provide integration tests for network operations

Files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv/tests/chainlock_simple_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
{dash-network,rpc-client,rpc-json,dash-spv}/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Always validate inputs from untrusted sources

Files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/examples/filter_sync.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv/src/sync/validation_test.rs
  • dash-network/src/lib.rs
  • dash-spv/tests/chainlock_simple_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/src/client/block_processor_test.rs
  • dash-spv/src/client/config_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv/src/mempool_filter.rs
dash-spv/**/*.rs

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

dash-spv/**/*.rs: Enforce Rust formatting via cargo fmt --check on all Rust source files
All code must be clippy-clean: run cargo clippy --all-targets --all-features -- -D warnings

Files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/examples/filter_sync.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv/src/sync/validation_test.rs
  • dash-spv/tests/chainlock_simple_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/src/client/block_processor_test.rs
  • dash-spv/src/client/config_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv/src/mempool_filter.rs
dash-spv/tests/**/*.rs

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

Place integration tests under the tests/ directory

Files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv/tests/chainlock_simple_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
dash-spv-ffi/src/**/*.rs

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

dash-spv-ffi/src/**/*.rs: FFI functions must be declared with #[no_mangle] extern "C"
Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management
Return Rust strings to C as *const c_char; allocate so the caller can free with dash_string_free
Accept input strings from C as *const c_char (borrowed; do not free inputs)
Functions returning pointers transfer ownership to the caller; functions taking pointers only borrow
Add cbindgen annotations for complex types so headers are generated correctly

Files:

  • dash-spv-ffi/src/config.rs
{dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}}

📄 CodeRabbit inference engine (CLAUDE.md)

Be especially careful with memory safety at FFI boundaries

Files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
dash-spv-ffi/tests/unit/**/*.rs

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

Add a corresponding Rust unit test for each new FFI function

Files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
dash-spv/**/Cargo.toml

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

Set MSRV to 1.89 using the rust-version field and keep the code compatible with Rust 1.89

Files:

  • dash-spv/Cargo.toml
dash-spv-ffi/include/dash_spv_ffi.h

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

The public C header is generated; ensure it reflects FFI changes after builds

Files:

  • dash-spv-ffi/include/dash_spv_ffi.h
🧠 Learnings (32)
📓 Common learnings
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/include/dash_spv_ffi.h : The public C header is generated; ensure it reflects FFI changes after builds
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : In C code, free all strings returned by the FFI using dash_string_free
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : Add a corresponding C test for each new FFI function
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/unit/**/*.rs : Add a corresponding Rust unit test for each new FFI function
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:47:37.438Z
Learning: Transaction IDs (txids) and block hashes in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Accept input strings from C as *const c_char (borrowed; do not free inputs)
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network,dash-spv}/tests/**/*.rs : Provide integration tests for network operations

Applied to files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/examples/filter_sync.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv/src/sync/validation_test.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv/Cargo.toml
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/src/client/block_processor_test.rs
  • dash-spv/src/client/config_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/tests/integration_real_node_test.rs : Integration tests that connect to a live Dash Core node must gracefully skip when the node at 127.0.0.1:9999 is unavailable

Applied to files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/src/client/config_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/unit/**/*.rs : Add a corresponding Rust unit test for each new FFI function

Applied to files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv/Cargo.toml
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/tests/**/*.rs : Place integration tests under the `tests/` directory

Applied to files:

  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv/src/sync/validation_test.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv/Cargo.toml
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv/src/mempool_filter.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/**/*.rs : All code must be clippy-clean: run `cargo clippy --all-targets --all-features -- -D warnings`

Applied to files:

  • dash-spv/examples/filter_sync.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv/src/sync/validation_test.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv/tests/chainlock_simple_test.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv/Cargo.toml
  • dash-spv/tests/error_handling_test.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
📚 Learning: 2025-08-16T04:15:57.225Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/Examples/DashHDWalletExample/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:57.225Z
Learning: Applies to swift-dash-core-sdk/Examples/DashHDWalletExample/**/*.swift : When accessing SPV functionality, use DashSDK public APIs instead of direct client access; add public wrapper methods to DashSDK if needed

Applied to files:

  • dash-spv/examples/filter_sync.rs
  • dash-spv/tests/wallet_integration_test.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/src/error.rs : Define and maintain domain-specific error types in `src/error.rs` (NetworkError, StorageError, SyncError, ValidationError, SpvError) and update them when adding new failure modes

Applied to files:

  • dash-spv/examples/filter_sync.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv/tests/error_handling_test.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/src/storage/**/*.rs : Provide both `MemoryStorageManager` and `DiskStorageManager` behind the `StorageManager` trait

Applied to files:

  • dash-spv/examples/filter_sync.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/src/client/block_processor_test.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)

Applied to files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv/Cargo.toml
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : FFI functions must be declared with #[no_mangle] extern "C"

Applied to files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv/Cargo.toml
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/include/dash_spv_ffi.h : The public C header is generated; ensure it reflects FFI changes after builds

Applied to files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Add cbindgen annotations for complex types so headers are generated correctly

Applied to files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv/Cargo.toml
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries

Applied to files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/test_wallet.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T16:01:37.609Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T16:01:37.609Z
Learning: The mempool tracking infrastructure (UnconfirmedTransaction, MempoolState, configuration, and mempool_filter.rs) is fully implemented and integrated in the Dash SPV client as of this PR, including client logic, FFI APIs, and tests.

Applied to files:

  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management

Applied to files:

  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv/Cargo.toml
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/callbacks.rs : Use C function pointers for async progress/completion callbacks; callbacks may be invoked from any thread

Applied to files:

  • dash-spv-ffi/tests/test_mempool_tracking.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Functions returning pointers transfer ownership to the caller; functions taking pointers only borrow

Applied to files:

  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Accept input strings from C as *const c_char (borrowed; do not free inputs)

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Return Rust strings to C as *const c_char; allocate so the caller can free with dash_string_free

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {key-wallet/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.swift} : Never log or expose private keys

Applied to files:

  • dash-spv-ffi/tests/test_wallet.rs
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {**/*.rs,**/Cargo.toml} : Use feature flags and conditional compilation for optional features

Applied to files:

  • dash-spv/Cargo.toml
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/**/Cargo.toml : Set MSRV to 1.89 using the `rust-version` field and keep the code compatible with Rust 1.89

Applied to files:

  • dash-spv/Cargo.toml
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/**/*.rs : Enforce Rust formatting via `cargo fmt --check` on all Rust source files

Applied to files:

  • dash-spv/Cargo.toml
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Test both mainnet and testnet configurations

Applied to files:

  • dash-spv/src/client/config_test.rs
📚 Learning: 2025-06-15T15:31:44.136Z
Learnt from: QuantumExplorer
PR: dashpay/rust-dashcore#74
File: key-wallet/src/address.rs:30-40
Timestamp: 2025-06-15T15:31:44.136Z
Learning: In the key-wallet crate, QuantumExplorer prefers keeping wildcard arms that default unknown Network variants to testnet prefixes rather than using exhaustive matches or panics. This fallback behavior is intentional.

Applied to files:

  • dash-spv/src/mempool_filter.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : In C code, free all strings returned by the FFI using dash_string_free

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-15T16:42:18.187Z
Learnt from: QuantumExplorer
PR: dashpay/rust-dashcore#74
File: key-wallet-ffi/src/lib_tests.rs:41-48
Timestamp: 2025-06-15T16:42:18.187Z
Learning: In key-wallet-ffi, the HDWallet::derive_xpriv method returns Result<String, KeyWalletError>, not an ExtPrivKey wrapper. When unwrapped, it yields a String that can have .is_empty() called on it.

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
📚 Learning: 2025-08-16T04:15:29.335Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:29.335Z
Learning: Applies to swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h : After updating dash-spv-ffi, synchronize the generated header into the Swift SDK using ./sync-headers.sh so Sources/DashSPVFFI/include/dash_spv_ffi.h stays up to date

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:15:29.335Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:29.335Z
Learning: Applies to swift-dash-core-sdk/Sources/**/FFIBridge.swift : Manage FFI pointers manually using RAII-style wrappers and defer blocks to ensure cleanup

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T15:47:37.438Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:47:37.438Z
Learning: Transaction IDs (txids) and block hashes in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T15:46:56.854Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:46:56.854Z
Learning: Transaction IDs (txids) in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
🧬 Code graph analysis (5)
dash-spv/tests/wallet_integration_test.rs (1)
key-wallet-manager/src/spv_wallet_manager.rs (1)
  • with_base (105-114)
dash-spv-ffi/tests/unit/test_error_handling.rs (1)
dash-spv-ffi/src/config.rs (1)
  • dash_spv_ffi_config_new (29-34)
dash-spv/src/mempool_filter.rs (1)
dash-spv/src/types.rs (2)
  • new (197-199)
  • new (893-913)
dash-spv-ffi/tests/unit/test_wallet_operations.rs (3)
dash-spv-ffi/src/client.rs (5)
  • dash_spv_ffi_client_get_address_utxos (1687-1695)
  • new (74-78)
  • dash_spv_ffi_client_get_address_history (1418-1462)
  • dash_spv_ffi_client_get_watched_addresses (1549-1564)
  • dash_spv_ffi_client_get_watched_scripts (1567-1582)
dash-spv-ffi/src/types.rs (8)
  • dash_spv_ffi_array_destroy (337-352)
  • new (15-23)
  • new (304-318)
  • from_ptr (25-30)
  • dash_spv_ffi_string_destroy (330-334)
  • dash_spv_ffi_string_array_destroy (361-380)
  • std (315-315)
  • std (316-316)
dash-spv-ffi/src/wallet.rs (5)
  • dash_spv_ffi_wallet_get_utxos (477-528)
  • dash_spv_ffi_wallet_list (812-842)
  • dash_spv_ffi_wallet_get_mempool_balance (1177-1248)
  • dash_spv_ffi_wallet_get_mempool_transaction_count (1263-1341)
  • dash_spv_ffi_wallet_create (747-802)
dash-spv-ffi/include/dash_spv_ffi.h (4)
dash-spv-ffi/src/client.rs (7)
  • dash_spv_ffi_client_new (164-221)
  • dash_spv_ffi_client_get_utxos (1021-1066)
  • dash_spv_ffi_client_get_utxos_for_address (1069-1159)
  • dash_spv_ffi_client_get_address_history (1418-1462)
  • dash_spv_ffi_client_get_watched_addresses (1549-1564)
  • dash_spv_ffi_client_get_watched_scripts (1567-1582)
  • dash_spv_ffi_client_get_address_utxos (1687-1695)
dash-spv-ffi/src/config.rs (23)
  • dash_spv_ffi_config_new (29-34)
  • dash_spv_ffi_config_mainnet (37-42)
  • dash_spv_ffi_config_testnet (45-50)
  • dash_spv_ffi_config_set_data_dir (53-71)
  • dash_spv_ffi_config_set_validation_mode (74-83)
  • dash_spv_ffi_config_set_max_peers (86-95)
  • dash_spv_ffi_config_add_peer (100-124)
  • dash_spv_ffi_config_set_user_agent (127-146)
  • dash_spv_ffi_config_set_relay_transactions (149-158)
  • dash_spv_ffi_config_set_filter_load (161-170)
  • dash_spv_ffi_config_get_network (173-182)
  • dash_spv_ffi_config_get_data_dir (185-203)
  • dash_spv_ffi_config_destroy (206-210)
  • dash_spv_ffi_config_set_mempool_tracking (225-234)
  • dash_spv_ffi_config_set_mempool_strategy (237-246)
  • dash_spv_ffi_config_set_max_mempool_transactions (249-258)
  • dash_spv_ffi_config_set_mempool_timeout (261-270)
  • dash_spv_ffi_config_set_fetch_mempool_transactions (273-282)
  • dash_spv_ffi_config_set_persist_mempool (285-294)
  • dash_spv_ffi_config_get_mempool_tracking (297-306)
  • dash_spv_ffi_config_get_mempool_strategy (309-318)
  • dash_spv_ffi_config_set_start_from_height (323-332)
  • dash_spv_ffi_config_set_wallet_creation_time (335-344)
dash-spv-ffi/src/types.rs (1)
  • dash_spv_ffi_string_array_destroy (361-380)
dash-spv-ffi/src/wallet.rs (2)
  • dash_spv_ffi_wallet_get_utxos (477-528)
  • dash_spv_ffi_wallet_list (812-842)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Integration Tests (stable, 22.0.0)
  • GitHub Check: RPC Tests (stable, true)
  • GitHub Check: Strict Warnings and Clippy Checks
  • GitHub Check: Clippy (Non-strict)
  • GitHub Check: SPV Components Tests
  • GitHub Check: fuzz (dash_deserialize_witness)
  • GitHub Check: fuzz (hashes_sha256)
  • GitHub Check: fuzz (dash_outpoint_string)
  • GitHub Check: fuzz (hashes_sha512)
  • GitHub Check: fuzz (dash_deserialize_block)
  • GitHub Check: fuzz (dash_script_bytes_to_asm_fmt)
  • GitHub Check: fuzz (hashes_sha512_256)
  • GitHub Check: fuzz (hashes_sha1)
  • GitHub Check: fuzz (hashes_ripemd160)
  • GitHub Check: fuzz (dash_deserialize_address)
  • GitHub Check: fuzz (dash_deserialize_script)
  • GitHub Check: fuzz (hashes_json)
  • GitHub Check: fuzz (dash_deser_net_msg)
  • GitHub Check: fuzz (dash_deserialize_amount)
  • GitHub Check: fuzz (hashes_cbor)
🔇 Additional comments (15)
dash-spv/examples/filter_sync.rs (1)

6-6: Import cleanup LGTM.

Dropping Network here is correct; no remaining uses in this example.

dash-spv-ffi/tests/test_wallet.rs (2)

3-6: Import cleanup LGTM.

Removing the unused key_wallet_manager import is fine.


123-125: Confirm destructor contract for locally-constructed FFIUtxo.

You create FFIUtxo via From<key_wallet::Utxo> and then call the FFI destructor. This is okay only if dash_spv_ffi_utxo_destroy is documented to accept Rust-allocated instances (not solely FFI-returned ones) and won’t double-free internal buffers vs. Drop. Please confirm.

dash-spv-ffi/src/config.rs (1)

23-26: repr(transparent) on FFIClientConfig is the right call.

This aligns the wrapper’s ABI with ClientConfig for pointer passing across FFI.

dash-spv/src/client/config_test.rs (1)

7-7: Import cleanup LGTM.

Switching to dashcore::Network only is correct here.

dash-spv-ffi/tests/test_mempool_tracking.rs (2)

2-2: Import narrowing LGTM

Restricting import to CString is cleaner and avoids unused-import lint noise.


168-172: Pointer deref is fine; keep destruction order consistent

let balance_data = *balance; is correct in this context; you destroy after use. Ensure all similar tests read fields before calling the *_destroy function.

dash-spv/tests/wallet_integration_test.rs (1)

11-11: Import cleanup LGTM

Using dashcore::Network directly is clearer.

dash-spv/src/client/block_processor_test.rs (2)

22-27: MockWallet::new() signature change looks correct

Constructor now aligns with the simplified mock (no Network). Call sites updated accordingly.


83-95: setup_processor(): wallet wiring LGTM

Switched to MockWallet::new() and preserved other wiring; no functional regressions apparent.

dash-spv-ffi/include/dash_spv_ffi.h (1)

600-602: Confirm Swift header is regenerated after these signature changes

Ensure the mirrored header in the Swift SDK is synced post-regeneration.

Would you like a script to diff the declarations between this header and swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h?

dash-spv/tests/error_handling_test.rs (1)

1-1: Feature-gating approach LGTM

Compiles this file only when the feature is enabled; paired with #[ignore] keeps CI green while retaining test code.

dash-spv-ffi/tests/unit/test_wallet_operations.rs (2)

600-606: Mempool API usage LGTM (network param removed)

Tests reflect new (client, wallet_id) signatures; behavior assertions remain valid.


1-1178: Destroy helpers not available – bulk replacement is premature

The repository only provides:

  • dash_spv_ffi_array_destroy(…)
  • dash_spv_ffi_string_array_destroy(…)

No *_destroy_owned variants exist for UTXO arrays, string arrays, or generic arrays, so an automated one-shot replace isn’t possible yet. You’ll first need to:

• Define and export the owned destroyers in dash-spv-ffi/src/types.rs and the C header (e.g.
dash_spv_ffi_utxo_array_destroy_owned(*mut FFIUtxoArray)
dash_spv_ffi_string_array_destroy_owned(*mut FFIStringArray)
dash_spv_ffi_array_destroy_owned(*mut FFIArray))

• Then update all tests to call the new functions instead of the raw dash_spv_ffi_array_destroy or dash_spv_ffi_string_array_destroy.

Until those helpers exist, please ignore the one-shot bulk-replace suggestion.

Likely an incorrect or invalid review comment.

dash-spv/tests/chainlock_simple_test.rs (1)

60-61: Immutable binding for client is correct here.

update_chainlock_validation() and subsequent calls take &self; no need for mut. Looks good.

Comment on lines +136 to +43
typedef ClientConfig FFIClientConfig;

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

C typedef is invalid: ClientConfig is unknown in C

Revert to an opaque forward-declared struct. Current typedef will break all C consumers.

-typedef ClientConfig FFIClientConfig;
+typedef struct FFIClientConfig FFIClientConfig;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
typedef ClientConfig FFIClientConfig;
++ b/dash-spv-ffi/include/dash_spv_ffi.h
@@ -136,1 +136,1 @@
typedef struct FFIClientConfig FFIClientConfig;
🤖 Prompt for AI Agents
In dash-spv-ffi/include/dash_spv_ffi.h around lines 136-137, the line "typedef
ClientConfig FFIClientConfig;" is invalid for C consumers because ClientConfig
is a C++ type and unknown in C; revert this to an opaque forward-declared struct
by declaring an incomplete struct type for FFIClientConfig and using that as the
typedef (i.e., expose only an opaque pointer-compatible type in the header),
ensuring the header contains no C++-only type names so C callers can compile.

Comment on lines +659 to +511
/**
* Destroy an array of FFIString pointers (Vec<*mut FFIString>) and their contents.
*
* This function:
* - Iterates the array elements as pointers to FFIString and destroys each via dash_spv_ffi_string_destroy
* - Frees the underlying vector buffer stored in FFIArray
* - Does not free the FFIArray struct itself (safe for both stack- and heap-allocated structs)
*/
void dash_spv_ffi_string_array_destroy(struct FFIArray *arr);

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

Missing owned and typed array destroyers in public header

Add owned variants and a typed UTXO array destroyer to prevent leaks of FFIArray and inner FFIString fields.

 void dash_spv_ffi_string_array_destroy(struct FFIArray *arr);
+
+// Owned/typed destroyers for heap-allocated arrays returned by the FFI
+void dash_spv_ffi_array_destroy_owned(struct FFIArray *arr);
+void dash_spv_ffi_string_array_destroy_owned(struct FFIArray *arr);
+void dash_spv_ffi_utxo_array_destroy(struct FFIArray *arr);
+void dash_spv_ffi_utxo_array_destroy_owned(struct FFIArray *arr);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Destroy an array of FFIString pointers (Vec<*mut FFIString>) and their contents.
*
* This function:
* - Iterates the array elements as pointers to FFIString and destroys each via dash_spv_ffi_string_destroy
* - Frees the underlying vector buffer stored in FFIArray
* - Does not free the FFIArray struct itself (safe for both stack- and heap-allocated structs)
*/
void dash_spv_ffi_string_array_destroy(struct FFIArray *arr);
/**
* Destroy an array of FFIString pointers (Vec<*mut FFIString>) and their contents.
*
* This function:
* - Iterates the array elements as pointers to FFIString and destroys each via dash_spv_ffi_string_destroy
* - Frees the underlying vector buffer stored in FFIArray
* - Does not free the FFIArray struct itself (safe for both stack- and heap-allocated structs)
*/
void dash_spv_ffi_string_array_destroy(struct FFIArray *arr);
// Owned/typed destroyers for heap-allocated arrays returned by the FFI
void dash_spv_ffi_array_destroy_owned(struct FFIArray *arr);
void dash_spv_ffi_string_array_destroy_owned(struct FFIArray *arr);
void dash_spv_ffi_utxo_array_destroy(struct FFIArray *arr);
void dash_spv_ffi_utxo_array_destroy_owned(struct FFIArray *arr);
🤖 Prompt for AI Agents
In dash-spv-ffi/include/dash_spv_ffi.h around lines 659 to 668, the public
header currently exposes dash_spv_ffi_string_array_destroy which frees the inner
FFIString elements and the vector buffer but does not free the FFIArray struct
itself; add two new declarations: an "owned" variant (e.g.,
dash_spv_ffi_string_array_destroy_owned) that performs the same work and also
frees the FFIArray struct pointer, and a typed UTXO array destroyer (e.g.,
dash_spv_ffi_utxo_array_destroy and dash_spv_ffi_utxo_array_destroy_owned) that
iterates and properly destroys each UTXO element's inner fields and frees the
vector buffer (and the struct for the owned variant) to prevent leaks of
FFIArray and inner FFIString/UTXO fields.

//! TODO: Re-enable once NetworkManager trait methods are fully implemented.
#![cfg(skip_mock_implementation_incomplete)]
#![cfg(feature = "skip_mock_implementation_incomplete")]
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Invert the cfg gate; current feature name compiles this file under --all-features and breaks clippy.

With cargo clippy run as --all-targets --all-features -D warnings, feature = "skip_mock_implementation_incomplete" will include this file, surfacing unused imports and outdated ctor calls. Flip the condition so setting the “skip” feature actually skips compiling this file.

Apply:

-#![cfg(feature = "skip_mock_implementation_incomplete")]
+#![cfg(not(feature = "skip_mock_implementation_incomplete"))]
🤖 Prompt for AI Agents
In dash-spv/tests/chainlock_validation_test.rs around line 6, the cfg gate
currently compiles this test when feature "skip_mock_implementation_incomplete"
is set which is the inverse of the intended behavior; change the attribute to
negate the feature (i.e., use cfg(not(feature =
"skip_mock_implementation_incomplete"))) so enabling the "skip" feature will
exclude this file from compilation and prevent the clippy warnings.

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 (5)
dash-spv/src/mempool_filter.rs (5)

486-514: Rename to reflect actual behavior (address-based relevance, not script).

The test exercises watched addresses only; rename for clarity and future maintainability.

Apply:

-async fn test_is_transaction_relevant_with_script() {
+async fn test_is_tx_relevant_with_watched_address() {
@@
-    // Transaction with watched script should be relevant
+    // Transaction to watched address should be relevant
@@
-    // Transaction without watched script should not be relevant
+    // Transaction to unwatched address should not be relevant

516-551: Rename to reflect actual behavior (inputs/outpoints aren’t checked yet).

This test verifies non-relevance when only an unrelated outpoint/input is present; name it accordingly.

Apply:

-async fn test_is_transaction_relevant_with_outpoint() {
+async fn test_is_tx_relevant_with_unwatched_outpoint() {

671-676: Replace tautological assert with a deterministic expectation.

x || !x is always true and may trigger lints. Without a mockable clock, pruning immediately after insertion should yield an empty set.

Apply:

-        assert!(pruned.is_empty() || !pruned.is_empty()); // Tautology, but shows the test ran
+        // Until time is mockable, nothing should expire immediately
+        assert!(pruned.is_empty());

301-331: Trim redundant cfg and noisy prints in helper.

Inside #[cfg(test)] mod tests, the extra #[cfg(test)] is redundant; println! adds noise to CI logs.

Apply:

-    #[cfg(test)]
     fn test_deterministic_addresses() {
@@
-        println!("Deterministic address generation test passed!");
-        println!("Dash addresses: {:?}", addresses1);
-        println!("Testnet addresses: {:?}", testnet_addresses);

224-227: Silence dead_code warnings in MockWallet (optional refactor)

The local MockWallet in dash-spv/src/mempool_filter.rs currently defines two fields and three accessors that aren’t referenced by any active tests:

  • Fields network and utxos are only initialized, never read.
  • Accessors network(), watched_addresses(), and utxos() are never called in this file’s non-ignored tests.

To keep CI green under -D warnings, you can:

  • Rename the unused fields to _network and _utxos (underscore prefix suppresses unused-field warnings).
  • Annotate each dead accessor with #[allow(dead_code)].

Suggested diff:

--- a/dash-spv/src/mempool_filter.rs
+++ b/dash-spv/src/mempool_filter.rs
@@ -223,7 +223,7 @@ struct MockWallet {
-    network: Network,
+    _network: Network,
     watched_addresses: HashSet<Address>,
-    utxos: HashSet<OutPoint>,
+    _utxos: HashSet<OutPoint>,
 }

 impl MockWallet {
@@ -237,9 +237,12 @@ impl MockWallet {
     fn add_watched_address(&mut self, address: Address) {
         self.watched_addresses.insert(address);
     }
+
+    #[allow(dead_code)]
     fn network(&self) -> &Network {
-        &self.network
+        &self._network
     }
+
+    #[allow(dead_code)]
     fn watched_addresses(&self) -> &HashSet<Address> {
         &self.watched_addresses
     }
@@ -247,7 +250,10 @@ impl MockWallet {
-    fn utxos(&self) -> &HashSet<OutPoint> {
-        &self.utxos
+    #[allow(dead_code)]
+    fn utxos(&self) -> &HashSet<OutPoint> {
+        &self._utxos
     }
 }

Then confirm no warnings remain:

rg -n 'warning:' -S dash-spv/src/mempool_filter.rs || true

Please run CI to verify. If you later implement real uses for network() or utxos(), you can remove the allow(dead_code) and restore the original names.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5e015af and 332a51e.

📒 Files selected for processing (1)
  • dash-spv/src/mempool_filter.rs (12 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Use proper error types with the thiserror crate and propagate errors appropriately
Use the tokio runtime for async operations
Adhere to MSRV: code must compile on Rust 1.89 (avoid newer language/features)
Keep code formatted with rustfmt (cargo fmt)
Keep the codebase clippy-clean (clippy with -D warnings)

Files:

  • dash-spv/src/mempool_filter.rs
{**/*.rs,**/Cargo.toml}

📄 CodeRabbit inference engine (CLAUDE.md)

Use feature flags and conditional compilation for optional features

Files:

  • dash-spv/src/mempool_filter.rs
{dash-network,rpc-client,rpc-json,dash-spv}/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Always validate inputs from untrusted sources

Files:

  • dash-spv/src/mempool_filter.rs
dash-spv/**/*.rs

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

dash-spv/**/*.rs: Enforce Rust formatting via cargo fmt --check on all Rust source files
All code must be clippy-clean: run cargo clippy --all-targets --all-features -- -D warnings

Files:

  • dash-spv/src/mempool_filter.rs
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/include/dash_spv_ffi.h : The public C header is generated; ensure it reflects FFI changes after builds
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : In C code, free all strings returned by the FFI using dash_string_free
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : Add a corresponding C test for each new FFI function
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/unit/**/*.rs : Add a corresponding Rust unit test for each new FFI function
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:47:37.438Z
Learning: Transaction IDs (txids) and block hashes in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.
📚 Learning: 2025-06-26T16:01:37.609Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T16:01:37.609Z
Learning: The mempool tracking infrastructure (UnconfirmedTransaction, MempoolState, configuration, and mempool_filter.rs) is fully implemented and integrated in the Dash SPV client as of this PR, including client logic, FFI APIs, and tests.

Applied to files:

  • dash-spv/src/mempool_filter.rs
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network,dash-spv}/tests/**/*.rs : Provide integration tests for network operations

Applied to files:

  • dash-spv/src/mempool_filter.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/tests/**/*.rs : Place integration tests under the `tests/` directory

Applied to files:

  • dash-spv/src/mempool_filter.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/unit/**/*.rs : Add a corresponding Rust unit test for each new FFI function

Applied to files:

  • dash-spv/src/mempool_filter.rs
📚 Learning: 2025-06-15T15:31:44.136Z
Learnt from: QuantumExplorer
PR: dashpay/rust-dashcore#74
File: key-wallet/src/address.rs:30-40
Timestamp: 2025-06-15T15:31:44.136Z
Learning: In the key-wallet crate, QuantumExplorer prefers keeping wildcard arms that default unknown Network variants to testnet prefixes rather than using exhaustive matches or panics. This fallback behavior is intentional.

Applied to files:

  • dash-spv/src/mempool_filter.rs
🧬 Code graph analysis (1)
dash-spv/src/mempool_filter.rs (4)
dash-spv/src/client/mod.rs (4)
  • network (418-420)
  • network (1219-1222)
  • new (174-237)
  • wallet (105-107)
dash-spv/src/client/message_handler.rs (1)
  • new (35-59)
dash-spv/src/types.rs (6)
  • new (197-199)
  • new (893-913)
  • default (63-79)
  • default (180-192)
  • default (436-438)
  • default (585-614)
dash-spv/src/bloom/manager.rs (2)
  • new (76-85)
  • default (31-40)
🪛 GitHub Actions: Continuous integration
dash-spv/src/mempool_filter.rs

[warning] 224-224: Fields network and utxos are never read.


[warning] 242-250: Methods network, watched_addresses, and utxos are never used.

Comment on lines +272 to +295
let secret_bytes = sha256::Hash::hash(&data).to_byte_array();

// Try to create secp256k1 SecretKey from the hash
match secp256k1::SecretKey::from_byte_array(&secret_bytes) {
Ok(secret_key) => {
// Create PrivateKey from SecretKey
let private_key = PrivateKey::new(secret_key, network);

// Create PublicKey from PrivateKey
let public_key = PublicKey::from_private_key(&secp, &private_key);

// Create P2PKH address from PublicKey
let address = Address::p2pkh(&public_key, network);

addresses.push(address);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Fix secp256k1 SecretKey construction (use from_slice; ensure MSRV compatibility).

SecretKey::from_byte_array isn’t a stable API; use from_slice. Prefer into_inner() for bitcoin_hashes portability.

Apply:

-            let secret_bytes = sha256::Hash::hash(&data).to_byte_array();
+            let secret_bytes = sha256::Hash::hash(&data).into_inner();
-
-            // Try to create secp256k1 SecretKey from the hash
-            match secp256k1::SecretKey::from_byte_array(&secret_bytes) {
+            // Try to create secp256k1 SecretKey from the hash
+            match secp256k1::SecretKey::from_slice(&secret_bytes) {
                 Ok(secret_key) => {

Run to ensure no other occurrences remain:


🏁 Script executed:

#!/bin/bash
rg -nP 'SecretKey::from_byte_array|to_byte_array\(' -S

Length of output: 9349


Update all SecretKey::from_byte_array and to_byte_array() usages to use from_slice and into_inner

The change in dash-spv/src/mempool_filter.rs is correct, but there are many other occurrences across the workspace that should be updated to maintain MSRV compatibility and avoid relying on the unstable from_byte_array API. Please apply analogous edits wherever you see:

  • .to_byte_array() on a bitcoin_hashes::Hash —> replace with .into_inner().
  • secp256k1::SecretKey::from_byte_array(&bytes) —> replace with secp256k1::SecretKey::from_slice(&bytes).

Locations to update (non-exhaustive):

  • key-wallet/src/wallet/root_extended_keys.rs:49, 172
  • key-wallet/src/psbt/mod.rs:1720
  • dash/src/signer.rs:137–138
  • dash/src/sign_message.rs:156, 256
  • dash-spv/src/mempool_filter.rs:272–275
  • rpc-json/src/lib.rs:3303, 3311
  • …and numerous hashes/src/* and other dash/src/* files

Example diff for mempool_filter.rs:

- let secret_bytes = sha256::Hash::hash(&data).to_byte_array();
+ let secret_bytes = sha256::Hash::hash(&data).into_inner();

- // Try to create secp256k1 SecretKey from the hash
- match secp256k1::SecretKey::from_byte_array(&secret_bytes) {
+ // Try to create secp256k1 SecretKey from the hash
+ match secp256k1::SecretKey::from_slice(&secret_bytes) {
     Ok(secret_key) => {
         …

• Please perform a workspace-wide search (e.g. rg -nP 'SecretKey::from_byte_array|to_byte_array\\(') and apply these replacements consistently.
• This refactoring is necessary to ensure a stable, MSRV-compatible API and avoid future breakage when from_byte_array is removed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let secret_bytes = sha256::Hash::hash(&data).to_byte_array();
// Try to create secp256k1 SecretKey from the hash
match secp256k1::SecretKey::from_byte_array(&secret_bytes) {
Ok(secret_key) => {
// Create PrivateKey from SecretKey
let private_key = PrivateKey::new(secret_key, network);
// Create PublicKey from PrivateKey
let public_key = PublicKey::from_private_key(&secp, &private_key);
// Create P2PKH address from PublicKey
let address = Address::p2pkh(&public_key, network);
addresses.push(address);
}
let secret_bytes = sha256::Hash::hash(&data).into_inner();
// Try to create secp256k1 SecretKey from the hash
match secp256k1::SecretKey::from_slice(&secret_bytes) {
Ok(secret_key) => {
// Create PrivateKey from SecretKey
let private_key = PrivateKey::new(secret_key, network);
// Create PublicKey from PrivateKey
let public_key = PublicKey::from_private_key(&secp, &private_key);
// Create P2PKH address from PublicKey
let address = Address::p2pkh(&public_key, network);
addresses.push(address);
}
🤖 Prompt for AI Agents
In dash-spv/src/mempool_filter.rs around lines 272 to 287, update the unstable
byte-array API usages to the stable slice/inner API: replace .to_byte_array() on
bitcoin_hashes::Hash with .into_inner() and replace
secp256k1::SecretKey::from_byte_array(&bytes) with
secp256k1::SecretKey::from_slice(&bytes); apply the same changes across the
workspace (e.g. key-wallet/src/wallet/root_extended_keys.rs,
key-wallet/src/psbt/mod.rs, dash/src/signer.rs, dash/src/sign_message.rs,
rpc-json/src/lib.rs and other hashes/src/* and dash/src/* locations) by running
a project-wide search for the patterns SecretKey::from_byte_array and
to_byte_array( and performing the corresponding replacements to ensure MSRV
compatibility.

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
dash-spv/src/mempool_filter.rs (1)

631-685: Flaky + clippy-breaking assertion; make prune_expired deterministic

The current assertion is a tautology and will trip clippy (nonminimal_bool). Also, without time control, the test is flaky. Sleep between inserting “old” and “recent” to create deterministic separation, then assert precisely.

         // Add an old transaction (will be expired)
         let old_tx = UnconfirmedTransaction::new(
@@
         let old_txid = old_tx.txid();
         state.transactions.insert(old_txid, old_tx);
 
-        // Manually set the first_seen time to be old
-        // TODO: Implement time manipulation for testing
-        // if let Some(tx) = state.transactions.get_mut(&old_txid) {
-        //     // This is a hack since we can't modify Instant directly
-        //     // In real tests, we'd use a time abstraction
-        // }
+        // Create a small, deterministic gap so pruning can differentiate “old” vs “recent”.
+        drop(state); // don't hold the lock across .await
+        tokio::time::sleep(Duration::from_millis(5)).await;
+        let mut state = mempool_state.write().await;
 
         // Add a recent transaction
         let recent_tx = UnconfirmedTransaction::new(
@@
         let recent_txid = recent_tx.txid();
         state.transactions.insert(recent_txid, recent_tx);
 
         drop(state);
 
-        // Prune with a very short timeout (this test is limited by Instant not being mockable)
+        // Prune: old should be removed, recent should remain.
         let pruned = filter.prune_expired(Duration::from_millis(1)).await;
 
-        // In a real test with time mocking, we'd verify that old transactions are pruned
-        // For now, just verify the method runs without panic
-        assert!(pruned.is_empty() || !pruned.is_empty()); // Tautology, but shows the test ran
+        assert!(pruned.contains(&old_txid));
+        assert!(!pruned.contains(&recent_txid));
dash-spv-ffi/include/dash_spv_ffi.h (1)

6-47: Prefix all unscoped enum variants to avoid C‐level name collisions

Unscoped C enums share a single global namespace, so repeating simple variant names will break C consumers with duplicate‐symbol errors. In dash-spv-ffi/include/dash_spv_ffi.h, we’ve confirmed multiple collisions:

• BIP44 appears in three different enums at lines 10, 53, and 72
• BIP32 appears in three different enums at lines 14, 57, and 76
• None appears in two different enums at lines 103 and 122

To fix this, all variants should be renamed or prefixed with their enum name. For example:

- typedef enum FFIAccountType {
-   BIP44 = 0,
-   BIP32 = 1,
+ typedef enum FFIAccountType {
+   FFIAccountType_BIP44 = 0,
+   FFIAccountType_BIP32 = 1,
    /* … */
 } FFIAccountType;

Repeat this pattern for every enum in this header (and any other dash-*-ffi headers). You can also configure cbindgen to auto-prefix variants:

[enum_style]
prefix = true
style = "upper_camel"

– this will emit FFIAccountTypeBip44 (or adjust to exactly FFIAccountType_BIP44 via your preferred style settings).

Please update all affected enums before merging to ensure the generated C header compiles cleanly.

dash-spv-ffi/src/wallet.rs (2)

556-606: UTXO array leaks nested strings; return pointer elements or provide a typed array destroy.

Current code builds FFIArray by value. dash_spv_ffi_array_destroy only frees the vector buffer; it won’t traverse elements to free FFIString members (txid, script_pubkey, address). Either:

  • Return FFIArray<*mut FFIUtxo> and add a dash_spv_ffi_utxo_array_destroy that iterates pointers and calls dash_spv_ffi_utxo_destroy; or
  • Keep by-value but add a destroy that iterates the slice as FFIUtxo and frees their FFIString fields before freeing the buffer.

Recommended (pointer elements) minimal change to this function:

@@
-            match wallet.base.wallet_utxos(&id) {
-                Ok(set) => {
-                    let ffi: Vec<crate::FFIUtxo> =
-                        set.into_iter().cloned().map(crate::FFIUtxo::from).collect();
-                    Ok(FFIArray::new(ffi))
-                }
+            match wallet.base.wallet_utxos(&id) {
+                Ok(set) => {
+                    let ffi: Vec<*mut crate::FFIUtxo> = set
+                        .into_iter()
+                        .cloned()
+                        .map(|u| Box::into_raw(Box::new(crate::FFIUtxo::from(u))))
+                        .collect();
+                    Ok(FFIArray::new(ffi))
+                }

And add (outside this hunk) a matching destroy:

#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_utxo_array_destroy(arr: *mut FFIArray) {
    if arr.is_null() { return; }
    if !(*arr).data.is_null() && (*arr).len > 0 {
        let slice = std::slice::from_raw_parts((*arr).data as *const *mut FFIUtxo, (*arr).len);
        for &p in slice.iter() {
            if !p.is_null() { dash_spv_ffi_utxo_destroy(p); }
        }
    }
    crate::types::dash_spv_ffi_array_destroy(arr);
    // If arrays are heap-allocated, consider calling dash_spv_ffi_owned_array_destroy instead.
}

479-494: Refactor MutexGuard usage to avoid holding across .await

You’re currently locking inner with inner.lock().unwrap() and immediately calling .await on the RwLock returned by spv_client.wallet(), which holds the MutexGuard across the .await. This can lead to deadlocks and makes the future non-Send.

Please refactor each run_async closure in dash-spv-ffi/src/wallet.rs so that you:

  • Acquire and immediately clone out the spv_client (or the minimal data you need).
  • Drop the MutexGuard before any .await.
  • Then perform your async operations on the cloned data.

Affected locations (all in dash-spv-ffi/src/wallet.rs):

  • Lines 479–494
  • Lines 533–546
  • Lines 771–804
  • Lines 847–873
  • Lines 967–996
  • Lines 1046–1073
  • Lines 1211–1241
  • Lines 1474–1498
  • Lines 1554–1589
  • Lines 1653–1688

Example refactor for one closure:

let result = client.run_async(|| async {
    // Scope to limit guard lifetime
    let spv_client_opt = {
        let guard = inner.lock().unwrap();
        (*guard).clone()  // Clone Arc or needed handle
    }; // guard dropped here

    if let Some(spv_client) = spv_client_opt {
        let wallet_handle = spv_client.wallet().clone();
        let wallet = wallet_handle.read().await;  // No MutexGuard held here
        let addresses = wallet.base.monitored_addresses(network.into());
        // ...
        Ok(crate::FFIArray::new(
            addresses.into_iter()
                .map(|addr| Box::into_raw(Box::new(FFIString::new(&addr.to_string()))))
                .collect(),
        ))
    } else {
        Err("Client not initialized".to_string())
    }
});

Please apply this pattern consistently at each of the locations listed above.

♻️ Duplicate comments (11)
dash-spv/src/mempool_filter.rs (1)

213-229: Naming fixed to snake_case as previously requested

Constructors now follow Rust conventions; this will keep clippy clean with -D warnings.

dash-spv-ffi/include/dash_spv_ffi.h (3)

136-137: C-incompatible typedef: ClientConfig is unknown to C; expose an opaque struct instead.

Change typedef to an opaque forward-declared struct to keep C compatibility.

-typedef ClientConfig FFIClientConfig;
+typedef struct FFIClientConfig FFIClientConfig;

Adjust cbindgen/Rust so it emits an incomplete struct rather than an alias.


536-540: Pointer-returning array getters require owned/typed destroyers; add and document them.

All these APIs now return heap-allocated FFIArray*. The current header only exposes dash_spv_ffi_array_destroy and dash_spv_ffi_string_array_destroy (non-owned), which leak the FFIArray struct and, for typed arrays, may leak inner fields.

  • Add owned destroyers and typed deep-destroyers.
  • Document which destroyer to use for each getter (e.g., string arrays vs UTXO arrays vs transaction records).
 // Array destroyers
 void dash_spv_ffi_array_destroy(struct FFIArray *arr);
-void dash_spv_ffi_string_array_destroy(struct FFIArray *arr);
+void dash_spv_ffi_string_array_destroy(struct FFIArray *arr);
+
+// Owned/typed destroyers for heap-allocated arrays returned by FFI
+void dash_spv_ffi_array_destroy_owned(struct FFIArray *arr);
+void dash_spv_ffi_string_array_destroy_owned(struct FFIArray *arr);
+void dash_spv_ffi_utxo_array_destroy(struct FFIArray *arr);
+void dash_spv_ffi_utxo_array_destroy_owned(struct FFIArray *arr);
+void dash_spv_ffi_transaction_record_array_destroy_owned(struct FFIArray *arr);

Also update docs:

-struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);
+/* Returns an FFIArray of FFIString*; free with dash_spv_ffi_string_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);
@@
-struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);
+/* Returns an FFIArray of FFIString*; free with dash_spv_ffi_string_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);
@@
-struct FFIArray *dash_spv_ffi_client_get_address_utxos(struct FFIDashSpvClient *client, const char *address);
+/* Returns an FFIArray of FFIUtxo; free with dash_spv_ffi_utxo_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_address_utxos(struct FFIDashSpvClient *client, const char *address);
@@
- * * Pointer to FFIArray of FFIString objects containing hex-encoded WalletIds
+ * * Pointer to FFIArray of FFIString* (heap strings); free with dash_spv_ffi_string_array_destroy_owned()

Also applies to: 558-560, 584-586, 879-880, 819-821, 567-570


716-725: Non-owned string array destroyer leaks the FFIArray struct for heap-allocated returns. Add “owned” variant.

Keep this non-owned function for by-value arrays, but add an owned variant and use it in docs/callers.

 void dash_spv_ffi_string_array_destroy(struct FFIArray *arr);
+void dash_spv_ffi_string_array_destroy_owned(struct FFIArray *arr);
dash-spv-ffi/tests/unit/test_wallet_operations.rs (7)

819-821: Document/test the correct destroyers for string arrays in header and tests.

Tests should mirror the public contract (“owned” string-array destroyer). Also update the public header docs accordingly.

Also applies to: 472-499, 931-939


185-191: Use typed owned destroyer for UTXO arrays to avoid leaks.

dash_spv_ffi_array_destroy frees only the Vec buffer; it leaks the FFIArray Box and inner FFIString fields in FFIUtxo. Use a typed owned destroyer.

-            assert_eq!((*utxos).len, 0);
-            // Properly destroy the array buffer
-            dash_spv_ffi_array_destroy(utxos);
+            assert_eq!((*utxos).len, 0);
+            // Deep-destroy elements, buffer, and the FFIArray struct
+            dash_spv_ffi_utxo_array_destroy_owned(utxos);
@@
-            assert!((*utxos).data.is_null());
-            dash_spv_ffi_array_destroy(utxos);
+            assert!((*utxos).data.is_null());
+            dash_spv_ffi_utxo_array_destroy_owned(utxos);

Also applies to: 193-197


686-692: Monitored addresses: use string-array owned destroyer.

Avoids leaking the FFIArray Box even for empty arrays.

-            dash_spv_ffi_string_array_destroy(addresses);
+            dash_spv_ffi_string_array_destroy_owned(addresses);

867-870: Concurrent path: utxo arrays must use typed owned destroyer.

Threads should call the deep/owned destroyer.

-                        dash_spv_ffi_array_destroy(utxos);
+                        dash_spv_ffi_utxo_array_destroy_owned(utxos);

429-440: wallet_get_utxos: switch to utxo owned destroyer.

Prevents leaks of element strings and the FFIArray Box.

-            dash_spv_ffi_array_destroy(utxos);
+            dash_spv_ffi_utxo_array_destroy_owned(utxos);

931-939: Watched addresses/scripts: use string-array owned destroyer.

Align with pointer-returning semantics.

-            dash_spv_ffi_array_destroy(addresses);
+            dash_spv_ffi_string_array_destroy_owned(addresses);
@@
-            dash_spv_ffi_array_destroy(scripts);
+            dash_spv_ffi_string_array_destroy_owned(scripts);

1114-1176: Array memory safety test must exercise owned/typed destroyers.

Update to use utxo/string owned variants; ensure null is accepted.

-            dash_spv_ffi_array_destroy(utxos);
+            dash_spv_ffi_utxo_array_destroy_owned(utxos);
@@
-            dash_spv_ffi_array_destroy(wallet_list);
+            dash_spv_ffi_string_array_destroy_owned(wallet_list);
@@
-            dash_spv_ffi_array_destroy(std::ptr::null_mut()); // Should be safe
+            dash_spv_ffi_array_destroy_owned(std::ptr::null_mut()); // Should be safe
@@
-                dash_spv_ffi_array_destroy(utxos);
+                dash_spv_ffi_utxo_array_destroy_owned(utxos);
@@
-                dash_spv_ffi_array_destroy(wallet_list);
+                dash_spv_ffi_string_array_destroy_owned(wallet_list);
@@
-            dash_spv_ffi_array_destroy(null_result); // Should handle null gracefully
+            dash_spv_ffi_utxo_array_destroy_owned(null_result); // Should handle null gracefully
🧹 Nitpick comments (19)
dash-spv/src/mempool_filter.rs (5)

250-261: Unused getters may trigger dead_code in tests

If these stay unused, either remove them or add allows to keep clippy/rustc quiet.

Apply one of:

 impl MockWallet {
@@
-    fn network(&self) -> &Network {
+    #[allow(dead_code)]
+    fn network(&self) -> &Network {
         &self.network
     }
@@
-    fn watched_addresses(&self) -> &HashSet<Address> {
+    #[allow(dead_code)]
+    fn watched_addresses(&self) -> &HashSet<Address> {
         &self.watched_addresses
     }
@@
-    fn utxos(&self) -> &HashSet<OutPoint> {
+    #[allow(dead_code)]
+    fn utxos(&self) -> &HashSet<OutPoint> {
         &self.utxos
     }

263-307: Deterministic address generation looks correct; small allocation nit

Preallocate capacity to avoid reallocations in tight loops.

-        let mut addresses = Vec::new();
+        let mut addresses = Vec::with_capacity(count);

308-340: Test helper noise: remove redundant cfg and printlns

Inside a #[cfg(test)] module, the extra cfg is redundant; printlns add noise under -D warnings in some setups.

-    #[cfg(test)]
     fn test_deterministic_addresses() {
@@
-        println!("Deterministic address generation test passed!");
-        println!("Dash addresses: {:?}", addresses1);
-        println!("Testnet addresses: {:?}", testnet_addresses);

528-558: Hex-script fallback is okay; consider simpler generation

Constructing a different P2PKH via script hex works, but you could just call create_test_addresses(network, 1) with a different index to avoid from_hex(). Optional.

-        let other_addr = {
-            // Create from script to ensure it's different from watched addresses
-            use dashcore::script::ScriptBuf;
-            let script =
-                ScriptBuf::from_hex("76a914123456789012345678901234567890123456789088ac").unwrap();
-            Address::from_script(&script, network).unwrap()
-        };
+        let other_addr = create_test_addresses(network, 3)[2].clone();

561-572: Follow-ups: tests for outgoing/incoming and FetchAll behavior

These TODOs are valuable; I can draft minimal versions using your helpers.

key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs (1)

387-389: into_inner() for BIP-69 input sorting — OK.

Semantics unchanged versus to_byte_array(); types are Copy so moves are harmless. Consider adding one unit test with non-uniform txid bytes to validate “reversed byte order” sorting behavior end-to-end (current tests use uniform bytes that don’t catch reversal mistakes).

Also applies to: 775-777

dash/src/sml/quorum_entry/validation.rs (1)

33-35: Avoid shadowing and a tiny copy here.

Rename the first variable to make intent clear; optional micro-nit.

Apply this diff:

-        let message = self.commitment_hash.into_inner();
-        let message = message.as_slice();
+        let message_bytes = self.commitment_hash.into_inner();
+        let message = message_bytes.as_slice();
key-wallet/src/wallet/root_extended_keys.rs (1)

48-50: Switch to SecretKey::from_slice is correct; preserves intended error mapping.

Good replacement and consistent with broader repo changes.

If the zeroize crate is available, consider wiping the temporary private_key_bytes after constructing SecretKey to minimize key material lifetime:

 let private_key = secp256k1::SecretKey::from_slice(&private_key_bytes).map_err(|e| {
     crate::error::Error::InvalidParameter(format!("Invalid private key: {}", e))
 })?;
+// Optional: clear temporary buffer (requires `use zeroize::Zeroize;`)
+private_key_bytes.zeroize();

Add once at top of the file:

use zeroize::Zeroize;
key-wallet/src/wallet/mod.rs (1)

99-99: into_inner() adoption is correct and idiomatic.

No functional change; consistent with hash newtype usage.

Minor: pre-allocate data to avoid one potential reallocation:

let mut data = Vec::with_capacity(33 + 32); // pubkey (33) + chain code (32)
dash/src/signer.rs (1)

137-139: into_inner() for hash byte extraction: LGTM; consider minor consistency and doc fix.

  • Consider updating double_sha to use into_inner() for consistency with this change.
  • Doc typo: “RIPEMD169” → “RIPEMD160”.

Proposed updates (outside this hunk):

/// calculates double sha256 on data
pub fn double_sha(payload: impl AsRef<[u8]>) -> Vec<u8> {
    sha256d::Hash::hash(payload.as_ref()).into_inner().to_vec()
}

/// calculates the RIPEMD160(SHA256(data))
pub fn ripemd160_sha256(data: &[u8]) -> Vec<u8> {
    let hash = sha256::Hash::hash(data).into_inner();
    ripemd160::Hash::hash(&hash).into_inner().to_vec()
}
dash/src/sml/masternode_list/peer_addresses.rs (1)

39-42: into_inner() usage is correct; consider pre-filtering and key-based sorting to cut hashing work.

  • Filter invalid masternodes before sorting to avoid unnecessary BLAKE3 computations.
  • Use sort_by_key with precomputed BLAKE3 keys to avoid recomputing inside the comparator.

This can notably reduce CPU for large lists while keeping determinism.

dash/src/sml/masternode_list_engine/message_request_verification.rs (1)

90-93: Prefer borrowing over consuming for request_id bytes.

Minor: using as_byte_array() avoids relying on Copy semantics and an unnecessary 32-byte copy.

-        let request_id_bytes = request_id.into_inner();
-        // Just copying the core implementation
-        let selection_hash_64 = u64::from_le_bytes(request_id_bytes[24..32].try_into().unwrap());
+        let request_id_bytes = request_id.as_byte_array();
+        // Just copying the core implementation
+        let selection_hash_64 =
+            u64::from_le_bytes(request_id_bytes[24..32].try_into().unwrap());
dash/src/hash_types.rs (3)

271-274: Avoid intermediate Vec allocations when building score input.

Use extend_from_slice on arrays returned by into_inner().

-                bytes.append(&mut confirmed_hash_hashed_with_pro_reg_tx.into_inner().to_vec());
+                bytes.extend_from_slice(&confirmed_hash_hashed_with_pro_reg_tx.into_inner());
             }
-            bytes.append(&mut modifier.into_inner().to_vec());
+            bytes.extend_from_slice(&modifier.into_inner());

336-337: Eliminate concat allocation in hash_members.

Build a fixed-size stack buffer instead of allocating.

-            Self::hash(&[pro_tx_hash.into_inner(), confirmed_hash.into_inner()].concat())
+            let (p, c) = (pro_tx_hash.into_inner(), confirmed_hash.into_inner());
+            let mut buf = [0u8; 64];
+            buf[..32].copy_from_slice(&p);
+            buf[32..].copy_from_slice(&c);
+            Self::hash(&buf)

344-345: Same allocation-free approach for optional variant.

Avoids heap use on the hot path.

-            confirmed_hash.map(|confirmed_hash| {
-                Self::hash(&[pro_tx_hash.into_inner(), confirmed_hash.into_inner()].concat())
-            })
+            confirmed_hash.map(|confirmed_hash| {
+                let (p, c) = (pro_tx_hash.into_inner(), confirmed_hash.into_inner());
+                let mut buf = [0u8; 64];
+                buf[..32].copy_from_slice(&p);
+                buf[32..].copy_from_slice(&c);
+                Self::hash(&buf)
+            })
hashes/src/sha256.rs (1)

582-582: Minor: avoid redundant slice+as_ref chain.

hash.into_inner()[..].as_ref() can just be &hash.into_inner()[..] (or keep hash.as_ref() if you don't need to consume it). Tiny clarity win.

-assert_eq!(hash.into_inner()[..].as_ref(), test.output.as_slice());
+assert_eq!(&hash.into_inner()[..], test.output.as_slice());
hashes/src/sha256t.rs (1)

93-93: Blocked by missing sha256::Hash::into_inner().

This line is fine conceptually but won’t compile until sha256::Hash implements into_inner(). Prefer adding into_inner() to sha256::Hash (in hashes/src/sha256.rs) rather than reverting here, to stay consistent with the project-wide migration.

If you prefer a stopgap until into_inner() lands, this keeps CI green:

-Hash::from_byte_array(sha256::Hash::from_engine(e).into_inner())
+Hash::from_byte_array(sha256::Hash::from_engine(e).to_byte_array())
dash-spv-ffi/tests/unit/test_error_handling.rs (1)

176-180: Good fix: avoid UB by calling dash_spv_ffi_config_new with a valid enum.

Consider adding a C test that exercises invalid raw values via dash_spv_ffi_config_new_raw(int32_t). I can draft it if helpful.

dash-spv-ffi/src/wallet.rs (1)

522-529: Improve error reporting for invalid wallet_id hex.

unwrap_or_default masks decode errors. Match on hex::decode to set a precise last_error like other functions.

-    let bytes = hex::decode(wallet_id_hex).unwrap_or_default();
-    if bytes.len() != 32 {
-        set_last_error("Wallet ID must be 32 bytes hex");
-        return std::ptr::null_mut();
-    }
+    let bytes = match hex::decode(wallet_id_hex) {
+        Ok(b) => b,
+        Err(e) => {
+            set_last_error(&format!("Invalid hex wallet ID: {}", e));
+            return std::ptr::null_mut();
+        }
+    };
+    if bytes.len() != 32 {
+        set_last_error("Wallet ID must be 32 bytes hex");
+        return std::ptr::null_mut();
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 332a51e and ff95937.

📒 Files selected for processing (38)
  • dash-spv-ffi/include/dash_spv_ffi.h (14 hunks)
  • dash-spv-ffi/src/wallet.rs (7 hunks)
  • dash-spv-ffi/tests/unit/test_error_handling.rs (1 hunks)
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs (24 hunks)
  • dash-spv/src/mempool_filter.rs (12 hunks)
  • dash-spv/src/sync/filters.rs (4 hunks)
  • dash-spv/tests/filter_header_verification_test.rs (6 hunks)
  • dash/src/bip158.rs (2 hunks)
  • dash/src/blockdata/constants.rs (1 hunks)
  • dash/src/crypto/key.rs (3 hunks)
  • dash/src/crypto/sighash.rs (3 hunks)
  • dash/src/hash_types.rs (3 hunks)
  • dash/src/merkle_tree/block.rs (4 hunks)
  • dash/src/pow.rs (2 hunks)
  • dash/src/sign_message.rs (2 hunks)
  • dash/src/signer.rs (1 hunks)
  • dash/src/sml/masternode_list/peer_addresses.rs (1 hunks)
  • dash/src/sml/masternode_list_engine/message_request_verification.rs (3 hunks)
  • dash/src/sml/masternode_list_entry/mod.rs (1 hunks)
  • dash/src/sml/masternode_list_entry/qualified_masternode_list_entry.rs (1 hunks)
  • dash/src/sml/quorum_entry/quorum_modifier_type.rs (1 hunks)
  • dash/src/sml/quorum_entry/validation.rs (1 hunks)
  • dash/src/taproot.rs (3 hunks)
  • hashes/src/hash160.rs (1 hunks)
  • hashes/src/hash_x11.rs (1 hunks)
  • hashes/src/sha256.rs (2 hunks)
  • hashes/src/sha256d.rs (1 hunks)
  • hashes/src/sha256t.rs (1 hunks)
  • hashes/src/sha512.rs (1 hunks)
  • hashes/src/sha512_256.rs (1 hunks)
  • hashes/src/util.rs (1 hunks)
  • key-wallet/src/derivation.rs (4 hunks)
  • key-wallet/src/derivation_slip10.rs (2 hunks)
  • key-wallet/src/psbt/mod.rs (3 hunks)
  • key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs (2 hunks)
  • key-wallet/src/wallet/mod.rs (1 hunks)
  • key-wallet/src/wallet/root_extended_keys.rs (2 hunks)
  • rpc-json/src/lib.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • dash-spv/src/sync/filters.rs
🧰 Additional context used
📓 Path-based instructions (12)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Use proper error types with the thiserror crate and propagate errors appropriately
Use the tokio runtime for async operations
Adhere to MSRV: code must compile on Rust 1.89 (avoid newer language/features)
Keep code formatted with rustfmt (cargo fmt)
Keep the codebase clippy-clean (clippy with -D warnings)

Files:

  • key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs
  • dash/src/pow.rs
  • key-wallet/src/derivation.rs
  • dash/src/signer.rs
  • hashes/src/hash160.rs
  • dash/src/sign_message.rs
  • hashes/src/sha256d.rs
  • dash/src/sml/quorum_entry/validation.rs
  • dash/src/bip158.rs
  • rpc-json/src/lib.rs
  • dash/src/sml/quorum_entry/quorum_modifier_type.rs
  • dash/src/blockdata/constants.rs
  • hashes/src/sha512_256.rs
  • hashes/src/hash_x11.rs
  • dash/src/merkle_tree/block.rs
  • dash/src/crypto/key.rs
  • key-wallet/src/wallet/mod.rs
  • key-wallet/src/derivation_slip10.rs
  • key-wallet/src/wallet/root_extended_keys.rs
  • key-wallet/src/psbt/mod.rs
  • hashes/src/sha256t.rs
  • dash/src/hash_types.rs
  • dash/src/sml/masternode_list/peer_addresses.rs
  • hashes/src/util.rs
  • dash/src/crypto/sighash.rs
  • dash/src/sml/masternode_list_entry/mod.rs
  • dash/src/sml/masternode_list_engine/message_request_verification.rs
  • dash/src/taproot.rs
  • dash/src/sml/masternode_list_entry/qualified_masternode_list_entry.rs
  • hashes/src/sha256.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • hashes/src/sha512.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
{**/*.rs,**/Cargo.toml}

📄 CodeRabbit inference engine (CLAUDE.md)

Use feature flags and conditional compilation for optional features

Files:

  • key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs
  • dash/src/pow.rs
  • key-wallet/src/derivation.rs
  • dash/src/signer.rs
  • hashes/src/hash160.rs
  • dash/src/sign_message.rs
  • hashes/src/sha256d.rs
  • dash/src/sml/quorum_entry/validation.rs
  • dash/src/bip158.rs
  • rpc-json/src/lib.rs
  • dash/src/sml/quorum_entry/quorum_modifier_type.rs
  • dash/src/blockdata/constants.rs
  • hashes/src/sha512_256.rs
  • hashes/src/hash_x11.rs
  • dash/src/merkle_tree/block.rs
  • dash/src/crypto/key.rs
  • key-wallet/src/wallet/mod.rs
  • key-wallet/src/derivation_slip10.rs
  • key-wallet/src/wallet/root_extended_keys.rs
  • key-wallet/src/psbt/mod.rs
  • hashes/src/sha256t.rs
  • dash/src/hash_types.rs
  • dash/src/sml/masternode_list/peer_addresses.rs
  • hashes/src/util.rs
  • dash/src/crypto/sighash.rs
  • dash/src/sml/masternode_list_entry/mod.rs
  • dash/src/sml/masternode_list_engine/message_request_verification.rs
  • dash/src/taproot.rs
  • dash/src/sml/masternode_list_entry/qualified_masternode_list_entry.rs
  • hashes/src/sha256.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • hashes/src/sha512.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
{key-wallet/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.swift}

📄 CodeRabbit inference engine (CLAUDE.md)

{key-wallet/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.swift}: Use secure random number generation for keys
Never log or expose private keys

Files:

  • key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs
  • key-wallet/src/derivation.rs
  • key-wallet/src/wallet/mod.rs
  • key-wallet/src/derivation_slip10.rs
  • key-wallet/src/wallet/root_extended_keys.rs
  • key-wallet/src/psbt/mod.rs
{dash-network,rpc-client,rpc-json,dash-spv}/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Always validate inputs from untrusted sources

Files:

  • rpc-json/src/lib.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/src/mempool_filter.rs
**/tests/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/tests/**/*.rs: Write unit tests for new functionality
Use proptest for property-based testing where appropriate

Files:

  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
{dash-network,dash-spv}/tests/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Provide integration tests for network operations

Files:

  • dash-spv/tests/filter_header_verification_test.rs
dash-spv/**/*.rs

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

dash-spv/**/*.rs: Enforce Rust formatting via cargo fmt --check on all Rust source files
All code must be clippy-clean: run cargo clippy --all-targets --all-features -- -D warnings

Files:

  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/src/mempool_filter.rs
dash-spv/tests/**/*.rs

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

Place integration tests under the tests/ directory

Files:

  • dash-spv/tests/filter_header_verification_test.rs
dash-spv-ffi/tests/unit/**/*.rs

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

Add a corresponding Rust unit test for each new FFI function

Files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
{dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}}

📄 CodeRabbit inference engine (CLAUDE.md)

Be especially careful with memory safety at FFI boundaries

Files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
dash-spv-ffi/src/**/*.rs

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

dash-spv-ffi/src/**/*.rs: FFI functions must be declared with #[no_mangle] extern "C"
Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management
Return Rust strings to C as *const c_char; allocate so the caller can free with dash_string_free
Accept input strings from C as *const c_char (borrowed; do not free inputs)
Functions returning pointers transfer ownership to the caller; functions taking pointers only borrow
Add cbindgen annotations for complex types so headers are generated correctly

Files:

  • dash-spv-ffi/src/wallet.rs
dash-spv-ffi/include/dash_spv_ffi.h

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

The public C header is generated; ensure it reflects FFI changes after builds

Files:

  • dash-spv-ffi/include/dash_spv_ffi.h
🧠 Learnings (29)
📓 Common learnings
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/include/dash_spv_ffi.h : The public C header is generated; ensure it reflects FFI changes after builds
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : In C code, free all strings returned by the FFI using dash_string_free
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : Add a corresponding C test for each new FFI function
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:47:37.438Z
Learning: Transaction IDs (txids) and block hashes in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/unit/**/*.rs : Add a corresponding Rust unit test for each new FFI function
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T16:01:37.609Z
Learning: The mempool tracking infrastructure (UnconfirmedTransaction, MempoolState, configuration, and mempool_filter.rs) is fully implemented and integrated in the Dash SPV client as of this PR, including client logic, FFI APIs, and tests.
📚 Learning: 2025-02-25T06:19:32.230Z
Learnt from: QuantumExplorer
PR: dashpay/rust-dashcore#51
File: dash/src/sml/masternode_list_entry/hash.rs:7-12
Timestamp: 2025-02-25T06:19:32.230Z
Learning: The `consensus_encode` method on `MasternodeListEntry` writing to a `Vec` buffer cannot fail, so using `.expect()` is appropriate rather than propagating the error with the `?` operator.

Applied to files:

  • dash/src/sml/quorum_entry/validation.rs
  • dash/src/sml/masternode_list_entry/mod.rs
  • dash/src/sml/masternode_list_entry/qualified_masternode_list_entry.rs
📚 Learning: 2025-06-15T16:42:18.187Z
Learnt from: QuantumExplorer
PR: dashpay/rust-dashcore#74
File: key-wallet-ffi/src/lib_tests.rs:41-48
Timestamp: 2025-06-15T16:42:18.187Z
Learning: In key-wallet-ffi, the HDWallet::derive_xpriv method returns Result<String, KeyWalletError>, not an ExtPrivKey wrapper. When unwrapped, it yields a String that can have .is_empty() called on it.

Applied to files:

  • key-wallet/src/derivation_slip10.rs
  • key-wallet/src/wallet/root_extended_keys.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
📚 Learning: 2025-02-27T05:39:16.767Z
Learnt from: QuantumExplorer
PR: dashpay/rust-dashcore#56
File: dash/src/sml/masternode_list_engine/message_request_verification.rs:91-91
Timestamp: 2025-02-27T05:39:16.767Z
Learning: In the Dash Rust codebase, unwrap() on try_into() for byte slices from hash types like QuorumSigningRequestId is safe because hash types guarantee fixed-size byte arrays (32 bytes), ensuring the slice operations and conversions will never fail.

Applied to files:

  • dash/src/sml/masternode_list_engine/message_request_verification.rs
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network,dash-spv}/tests/**/*.rs : Provide integration tests for network operations

Applied to files:

  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/tests/**/*.rs : Place integration tests under the `tests/` directory

Applied to files:

  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/src/mempool_filter.rs
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/unit/**/*.rs : Add a corresponding Rust unit test for each new FFI function

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Functions returning pointers transfer ownership to the caller; functions taking pointers only borrow

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/include/dash_spv_ffi.h : The public C header is generated; ensure it reflects FFI changes after builds

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : FFI functions must be declared with #[no_mangle] extern "C"

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : In C code, free all strings returned by the FFI using dash_string_free

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : Add a corresponding C test for each new FFI function

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Accept input strings from C as *const c_char (borrowed; do not free inputs)

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/**/*.rs : All code must be clippy-clean: run `cargo clippy --all-targets --all-features -- -D warnings`

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Return Rust strings to C as *const c_char; allocate so the caller can free with dash_string_free

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T16:01:37.609Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T16:01:37.609Z
Learning: The mempool tracking infrastructure (UnconfirmedTransaction, MempoolState, configuration, and mempool_filter.rs) is fully implemented and integrated in the Dash SPV client as of this PR, including client logic, FFI APIs, and tests.

Applied to files:

  • dash-spv-ffi/tests/unit/test_wallet_operations.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T15:47:37.438Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:47:37.438Z
Learning: Transaction IDs (txids) and block hashes in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.

Applied to files:

  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T15:46:56.854Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:46:56.854Z
Learning: Transaction IDs (txids) in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.

Applied to files:

  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-21T05:01:58.949Z
Learnt from: QuantumExplorer
PR: dashpay/rust-dashcore#108
File: key-wallet-ffi/src/wallet_manager.rs:270-318
Timestamp: 2025-08-21T05:01:58.949Z
Learning: In the key-wallet-ffi design, wallets retrieved from the wallet manager via lookup functions should return const pointers (*const FFIWallet) to enforce read-only access and prevent unintended modifications. The wallet manager should control wallet lifecycle and mutations through specific APIs rather than allowing external mutation of retrieved wallet references.

Applied to files:

  • dash-spv-ffi/src/wallet.rs
📚 Learning: 2025-06-15T15:31:44.136Z
Learnt from: QuantumExplorer
PR: dashpay/rust-dashcore#74
File: key-wallet/src/address.rs:30-40
Timestamp: 2025-06-15T15:31:44.136Z
Learning: In the key-wallet crate, QuantumExplorer prefers keeping wildcard arms that default unknown Network variants to testnet prefixes rather than using exhaustive matches or panics. This fallback behavior is intentional.

Applied to files:

  • dash-spv/src/mempool_filter.rs
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network,rpc-client,rpc-json,dash-spv}/**/*.rs : Always validate inputs from untrusted sources

Applied to files:

  • dash-spv/src/mempool_filter.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/src/error.rs : Define and maintain domain-specific error types in `src/error.rs` (NetworkError, StorageError, SyncError, ValidationError, SpvError) and update them when adding new failure modes

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Add cbindgen annotations for complex types so headers are generated correctly

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/tests/integration_real_node_test.rs : Integration tests that connect to a live Dash Core node must gracefully skip when the node at 127.0.0.1:9999 is unavailable

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
📚 Learning: 2025-08-16T04:15:29.335Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:29.335Z
Learning: Applies to swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h : After updating dash-spv-ffi, synchronize the generated header into the Swift SDK using ./sync-headers.sh so Sources/DashSPVFFI/include/dash_spv_ffi.h stays up to date

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:15:29.335Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:29.335Z
Learning: Applies to swift-dash-core-sdk/Sources/**/FFIBridge.swift : Manage FFI pointers manually using RAII-style wrappers and defer blocks to ensure cleanup

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
🧬 Code graph analysis (24)
dash/src/signer.rs (2)
hashes/src/sha256t.rs (1)
  • hash (83-85)
hashes/src/lib.rs (1)
  • hash (209-213)
hashes/src/hash160.rs (2)
hashes/src/hash_x11.rs (1)
  • into_inner (128-130)
hashes/src/sha256.rs (1)
  • test (524-583)
hashes/src/sha256d.rs (2)
hashes/src/hash_x11.rs (1)
  • into_inner (128-130)
hashes/src/sha256.rs (1)
  • test (524-583)
dash/src/bip158.rs (1)
dash/src/blockdata/block.rs (2)
  • block_hash (59-63)
  • block_hash (211-213)
rpc-json/src/lib.rs (2)
dash/src/taproot.rs (1)
  • into_inner (1242-1244)
hashes/src/hash_x11.rs (1)
  • into_inner (128-130)
dash/src/sml/quorum_entry/quorum_modifier_type.rs (1)
dash/src/blockdata/block.rs (2)
  • block_hash (59-63)
  • block_hash (211-213)
dash/src/blockdata/constants.rs (1)
dash/src/blockdata/block.rs (2)
  • block_hash (59-63)
  • block_hash (211-213)
hashes/src/sha512_256.rs (4)
hashes/src/hash_x11.rs (1)
  • into_inner (128-130)
hashes/src/hash160.rs (1)
  • test (53-103)
hashes/src/sha256.rs (1)
  • test (524-583)
hashes/src/sha512.rs (1)
  • test (275-348)
dash/src/merkle_tree/block.rs (3)
hashes/src/hash_x11.rs (1)
  • from_byte_array (107-109)
hashes/src/sha256.rs (1)
  • from_byte_array (162-164)
hashes/src/lib.rs (2)
  • from_byte_array (227-227)
  • hash (209-213)
key-wallet/src/wallet/root_extended_keys.rs (1)
dash/src/crypto/key.rs (2)
  • from_slice (286-303)
  • from_slice (395-398)
key-wallet/src/psbt/mod.rs (3)
hashes/src/hash_x11.rs (1)
  • from_slice (112-120)
dash/src/crypto/key.rs (2)
  • from_slice (286-303)
  • from_slice (395-398)
hashes/src/sha256.rs (1)
  • from_slice (167-175)
hashes/src/sha256t.rs (2)
hashes/src/sha256.rs (3)
  • from_byte_array (162-164)
  • from_engine (33-50)
  • from_engine (53-61)
hashes/src/lib.rs (2)
  • from_byte_array (227-227)
  • from_engine (200-200)
hashes/src/util.rs (1)
hashes/src/hash_x11.rs (1)
  • into_inner (128-130)
dash/src/crypto/sighash.rs (3)
dash/src/taproot.rs (2)
  • hash (854-856)
  • into_inner (1242-1244)
hashes/src/lib.rs (1)
  • hash (209-213)
hashes/src/hash_x11.rs (1)
  • into_inner (128-130)
dash/src/sml/masternode_list_engine/message_request_verification.rs (2)
dash/src/ephemerealdata/chain_lock.rs (2)
  • request_id (44-57)
  • sign_id (59-73)
dash/src/ephemerealdata/instant_lock.rs (2)
  • request_id (58-77)
  • sign_id (80-94)
dash/src/taproot.rs (3)
hashes/src/hash_x11.rs (3)
  • from_byte_array (107-109)
  • from_engine (44-46)
  • into_inner (128-130)
hashes/src/sha256.rs (3)
  • from_byte_array (162-164)
  • from_engine (33-50)
  • from_engine (53-61)
hashes/src/lib.rs (3)
  • from_byte_array (227-227)
  • from_engine (200-200)
  • hash (209-213)
hashes/src/sha256.rs (2)
hashes/src/hash_x11.rs (2)
  • from_engine (44-46)
  • into_inner (128-130)
hashes/src/sha256t.rs (2)
  • from_engine (90-94)
  • hash (83-85)
dash-spv/tests/filter_header_verification_test.rs (1)
hashes/src/sha256.rs (1)
  • from_byte_array (162-164)
hashes/src/sha512.rs (6)
hashes/src/lib.rs (1)
  • hash (209-213)
hashes/src/hash_x11.rs (1)
  • into_inner (128-130)
hashes/src/hash160.rs (1)
  • test (53-103)
hashes/src/sha256.rs (1)
  • test (524-583)
hashes/src/sha256d.rs (1)
  • test (48-95)
hashes/src/sha512_256.rs (1)
  • test (92-173)
dash-spv-ffi/tests/unit/test_wallet_operations.rs (4)
dash-spv-ffi/src/client.rs (6)
  • dash_spv_ffi_client_get_address_utxos (1687-1695)
  • new (74-78)
  • dash_spv_ffi_client_get_address_history (1418-1462)
  • dash_spv_ffi_client_get_watched_addresses (1549-1564)
  • dash_spv_ffi_client_get_watched_scripts (1567-1582)
  • dash_spv_ffi_client_destroy (1193-1227)
dash-spv-ffi/src/types.rs (8)
  • dash_spv_ffi_array_destroy (337-352)
  • new (15-23)
  • new (304-318)
  • from_ptr (25-30)
  • dash_spv_ffi_string_destroy (330-334)
  • dash_spv_ffi_string_array_destroy (361-380)
  • std (315-315)
  • std (316-316)
dash-spv-ffi/src/wallet.rs (6)
  • dash_spv_ffi_transaction_record_array_destroy (392-411)
  • dash_spv_ffi_wallet_get_utxos (557-608)
  • dash_spv_ffi_wallet_list (892-922)
  • dash_spv_ffi_wallet_get_mempool_balance (1257-1328)
  • dash_spv_ffi_wallet_get_mempool_transaction_count (1343-1421)
  • dash_spv_ffi_wallet_create (827-882)
dash-spv-ffi/src/config.rs (1)
  • dash_spv_ffi_config_destroy (206-210)
dash-spv-ffi/src/wallet.rs (2)
dash-spv-ffi/src/types.rs (7)
  • dash_spv_ffi_string_destroy (330-334)
  • std (315-315)
  • std (316-316)
  • dash_spv_ffi_array_destroy (337-352)
  • from_ptr (25-30)
  • new (15-23)
  • new (304-318)
dash-spv-ffi/src/error.rs (1)
  • set_last_error (26-31)
dash-spv/src/mempool_filter.rs (2)
dash-spv/src/types.rs (2)
  • new (197-199)
  • new (893-913)
dash/src/crypto/key.rs (4)
  • from_slice (286-303)
  • from_slice (395-398)
  • public_key (381-386)
  • from_private_key (306-311)
dash-spv-ffi/tests/unit/test_error_handling.rs (1)
dash-spv-ffi/src/config.rs (1)
  • dash_spv_ffi_config_new (29-34)
dash-spv-ffi/include/dash_spv_ffi.h (4)
dash-spv-ffi/src/client.rs (5)
  • dash_spv_ffi_client_new (164-221)
  • dash_spv_ffi_client_get_utxos (1021-1066)
  • dash_spv_ffi_client_get_utxos_for_address (1069-1159)
  • dash_spv_ffi_client_get_address_history (1418-1462)
  • txid (275-275)
dash-spv-ffi/src/config.rs (23)
  • dash_spv_ffi_config_new (29-34)
  • dash_spv_ffi_config_mainnet (37-42)
  • dash_spv_ffi_config_testnet (45-50)
  • dash_spv_ffi_config_set_data_dir (53-71)
  • dash_spv_ffi_config_set_validation_mode (74-83)
  • dash_spv_ffi_config_set_max_peers (86-95)
  • dash_spv_ffi_config_add_peer (100-124)
  • dash_spv_ffi_config_set_user_agent (127-146)
  • dash_spv_ffi_config_set_relay_transactions (149-158)
  • dash_spv_ffi_config_set_filter_load (161-170)
  • dash_spv_ffi_config_get_network (173-182)
  • dash_spv_ffi_config_get_data_dir (185-203)
  • dash_spv_ffi_config_destroy (206-210)
  • dash_spv_ffi_config_set_mempool_tracking (225-234)
  • dash_spv_ffi_config_set_mempool_strategy (237-246)
  • dash_spv_ffi_config_set_max_mempool_transactions (249-258)
  • dash_spv_ffi_config_set_mempool_timeout (261-270)
  • dash_spv_ffi_config_set_fetch_mempool_transactions (273-282)
  • dash_spv_ffi_config_set_persist_mempool (285-294)
  • dash_spv_ffi_config_get_mempool_tracking (297-306)
  • dash_spv_ffi_config_get_mempool_strategy (309-318)
  • dash_spv_ffi_config_set_start_from_height (323-332)
  • dash_spv_ffi_config_set_wallet_creation_time (335-344)
dash-spv-ffi/src/types.rs (1)
  • dash_spv_ffi_string_array_destroy (361-380)
dash-spv-ffi/src/wallet.rs (4)
  • dash_spv_ffi_transaction_record_destroy (368-378)
  • dash_spv_ffi_transaction_record_array_destroy (392-411)
  • dash_spv_ffi_wallet_get_utxos (557-608)
  • dash_spv_ffi_wallet_list (892-922)
🪛 GitHub Actions: Continuous integration
hashes/src/sha256t.rs

[error] 93-93: no method named into_inner found for struct sha256::Hash in the current scope. (Hash::from_byte_array(sha256::Hash::from_engine(e).into_inner()))

hashes/src/sha256.rs

[error] 49-49: no method named into_inner found for struct Midstate in the current scope. (Hash(e.midstate().into_inner()))

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: fuzz (hashes_ripemd160)
  • GitHub Check: fuzz (hashes_sha512_256)
  • GitHub Check: fuzz (hashes_sha512)
  • GitHub Check: fuzz (hashes_cbor)
  • GitHub Check: fuzz (hashes_sha1)
  • GitHub Check: fuzz (dash_deserialize_witness)
  • GitHub Check: fuzz (dash_deserialize_block)
  • GitHub Check: fuzz (hashes_json)
  • GitHub Check: fuzz (dash_deserialize_script)
  • GitHub Check: fuzz (dash_deser_net_msg)
  • GitHub Check: fuzz (dash_deserialize_address)

Comment on lines 347 to 395
/**
* FFI-safe representation of a TransactionRecord with owned data
*
* This structure contains owned data that must be properly cleaned up
* using dash_spv_ffi_transaction_record_destroy
*/
typedef struct FFITransactionRecord {
/**
* Transaction as hex string
*/
struct FFIString transaction_hex;
/**
* Transaction ID as hex string
*/
struct FFIString txid;
/**
* Block height (0 if unconfirmed)
*/
uint32_t height;
/**
* Whether this transaction is confirmed
*/
bool is_confirmed;
/**
* Block hash as hex string (empty if unconfirmed)
*/
struct FFIString block_hash;
/**
* Timestamp
*/
uint64_t timestamp;
/**
* Net amount for this account (positive = incoming, negative = outgoing)
*/
int64_t net_amount;
/**
* Fee paid (0 if not available)
*/
uint64_t fee;
/**
* Transaction label (empty if no label)
*/
struct FFIString label;
/**
* Whether this is our transaction
*/
bool is_ours;
} FFITransactionRecord;

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

TransactionRecord: good addition; expose owned array destroyer too.

Single-record and array destroyers are present; add an owned-array variant to free the FFIArray struct.

 void dash_spv_ffi_transaction_record_array_destroy(struct FFIArray *arr);
+void dash_spv_ffi_transaction_record_array_destroy_owned(struct FFIArray *arr);

Also applies to: 786-795, 796-810

Comment on lines +380 to +412
/// Destroy an array of FFITransactionRecord pointers and their contents.
///
/// This function:
/// - Iterates the array elements as pointers to FFITransactionRecord and destroys each via dash_spv_ffi_transaction_record_destroy
/// - Frees the underlying vector buffer stored in FFIArray
/// - Does not free the FFIArray struct itself (safe for both stack- and heap-allocated structs)
///
/// # Safety
/// * `arr` must be a valid pointer to an FFIArray containing FFITransactionRecord pointers
/// * Each element in the array must be a valid FFITransactionRecord pointer
/// * The array elements will be destroyed and should not be used after this call
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_transaction_record_array_destroy(arr: *mut FFIArray) {
if arr.is_null() {
return;
}

// Destroy each FFITransactionRecord pointed to by the array elements
if !(*arr).data.is_null() && (*arr).len > 0 {
let slice =
std::slice::from_raw_parts((*arr).data as *const *mut FFITransactionRecord, (*arr).len);
for &record_ptr in slice.iter() {
if !record_ptr.is_null() {
// Take ownership and destroy
dash_spv_ffi_transaction_record_destroy(record_ptr);
}
}
}

// Free the vector buffer itself
crate::types::dash_spv_ffi_array_destroy(arr);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Heap-allocated FFIArray wrapper is leaked; add an “owned array destroy” that also frees the struct.

This function frees elements and the vector buffer but not the FFIArray struct itself. Given this PR now returns heap-allocated FFIArray* (Box::into_raw), the wrapper leaks unless another API frees it. Provide a destroy that (1) destroys elements, (2) frees buffer, and (3) Box::from_raw(arr).

Search for an existing struct-freeing API:

If missing, add in types.rs (outside this hunk):

#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_owned_array_destroy(arr: *mut FFIArray) {
    if arr.is_null() { return; }
    dash_spv_ffi_array_destroy(arr); // frees buffer
    let _ = Box::from_raw(arr);      // frees wrapper
}

Then update docs for dash_spv_ffi_transaction_record_array_destroy to tell callers to use dash_spv_ffi_owned_array_destroy for arrays returned by-pointer.


🏁 Script executed:

#!/bin/bash
rg -nP 'extern "C"\s*fn\s+dash_.*ffi_.*array.*(free|destroy).*struct|Box::from_raw\(\s*arr\s*\)' -C2

Length of output: 102


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Locate the definition of dash_spv_ffi_array_destroy
rg -nP 'fn\s+dash_spv_ffi_array_destroy' -C2

# Search for any existing "owned array destroy" variants
rg -nP 'fn\s+dash_spv_ffi_.*owned_array_destroy' -C2

# Find all usages of Box::into_raw in the FFI code
rg -nP 'Box::into_raw' -C2 dash-spv-ffi/src

# Find all usages of Box::from_raw to see if any wrapper cleanup exists
rg -nP 'Box::from_raw' -C2 dash-spv-ffi/src

Length of output: 483


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "=== dash_spv_ffi_array_destroy definition ==="
rg -nP 'pub\s+unsafe\s+extern\s+"C"\s+fn\s+dash_spv_ffi_array_destroy' -C2 dash-spv-ffi/src/types.rs || true

echo "=== Any existing owned_array_destroy variants ==="
rg -nP 'pub\s+unsafe\s+extern\s+"C"\s+fn\s+dash_spv_ffi_.*owned_array_destroy' -C2 dash-spv-ffi/src || true

echo "=== Usages of Box::into_raw in FFI code ==="
rg -nP 'Box::into_raw' -C2 dash-spv-ffi/src || true

echo "=== Usages of Box::from_raw in FFI code ==="
rg -nP 'Box::from_raw' -C2 dash-spv-ffi/src || true

Length of output: 20143


Add wrapper‐freeing destroy for FFIArray to avoid leaking the array struct

The existing dash_spv_ffi_array_destroy in types.rs only frees the internal buffer but does not free the heap-allocated FFIArray wrapper (the Box), so any array returned via Box::into_raw will leak its wrapper. We need a new “owned” destroy that (1) tears down the elements/buffer and (2) drops the Box.

• In dash-spv-ffi/src/types.rs (after the existing dash_spv_ffi_array_destroy at around line 337):

/// Frees an FFIArray* returned by-pointer: destroys buffer and drops the Box wrapper.
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_owned_array_destroy(arr: *mut FFIArray) {
    if arr.is_null() { return; }
    // deallocate elements’ buffer
    dash_spv_ffi_array_destroy(arr);
    // drop the Box<FFIArray>
    let _ = Box::from_raw(arr);
}

• In dash-spv-ffi/src/wallet.rs (around lines 380–412), update the doc comment on dash_spv_ffi_transaction_record_array_destroy to clarify that it only frees elements+buffer and that callers should invoke dash_spv_ffi_owned_array_destroy when freeing an array returned by-pointer. For example:

 /// - Does not free the FFIArray struct itself
+/// - To free a heap-allocated FFIArray*, use `dash_spv_ffi_owned_array_destroy`

These changes ensure that every array returned via Box::into_raw can be fully reclaimed.

🤖 Prompt for AI Agents
In dash-spv-ffi/src/wallet.rs around lines 380–412, the transaction_record_array
destroy currently frees elements+buffer but does not free a heap-allocated
FFIArray wrapper returned via Box::into_raw; add a new owned-destroy in
dash-spv-ffi/src/types.rs (after the existing dash_spv_ffi_array_destroy around
line ~337) named dash_spv_ffi_owned_array_destroy which checks for null, calls
dash_spv_ffi_array_destroy(arr) to free the internal buffer, then drops the
wrapper with Box::from_raw(arr); then update the doc comment on
dash_spv_ffi_transaction_record_array_destroy to explicitly state it only
destroys elements+buffer and that callers who received an FFIArray via
Box::into_raw must call dash_spv_ffi_owned_array_destroy to fully free the
wrapper.

Comment on lines 212 to 218
let history = dash_spv_ffi_client_get_address_history(client, addr.as_ptr());

// New wallet should have no history
assert_eq!(history.len, 0);
if !history.data.is_null() {
dash_spv_ffi_array_destroy(&mut history as *mut FFIArray);
}
assert_eq!((*history).len, 0);
// Use the correct destroyer for owned transaction record arrays
dash_spv_ffi_transaction_record_array_destroy(history);

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

History arrays: add/use owned transaction-record array destroyer.

The non-owned variant leaks the FFIArray struct.

-            dash_spv_ffi_transaction_record_array_destroy(history);
+            dash_spv_ffi_transaction_record_array_destroy_owned(history);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let history = dash_spv_ffi_client_get_address_history(client, addr.as_ptr());
// New wallet should have no history
assert_eq!(history.len, 0);
if !history.data.is_null() {
dash_spv_ffi_array_destroy(&mut history as *mut FFIArray);
}
assert_eq!((*history).len, 0);
// Use the correct destroyer for owned transaction record arrays
dash_spv_ffi_transaction_record_array_destroy(history);
let history = dash_spv_ffi_client_get_address_history(client, addr.as_ptr());
// New wallet should have no history
assert_eq!((*history).len, 0);
// Use the correct destroyer for owned transaction record arrays
dash_spv_ffi_transaction_record_array_destroy_owned(history);
🤖 Prompt for AI Agents
In dash-spv-ffi/tests/unit/test_wallet_operations.rs around lines 212 to 218,
the test currently destroys address history with the non-owned destroyer which
leaks the FFIArray struct; replace the non-owned destroy call with the owned
array destructor (dash_spv_ffi_transaction_record_array_destroy) for any history
arrays returned as owned by the FFI, and ensure the test uses that owned
destroyer and not the non-owned variant so the underlying FFIArray and its
contents are freed.

Comment on lines 472 to 499
// Test listing wallets with proper memory management
let wallet_list = dash_spv_ffi_wallet_list(client);
// Just ensure we can call the function without crashing
println!("Wallet list function called successfully");
// wallet_list is now a pointer to FFIArray (heap allocated)
assert!(!wallet_list.is_null());
// Should have 3 wallets
assert_eq!((*wallet_list).len, 3);
// Each element should be a valid FFIString
for i in 0..(*wallet_list).len {
let string_ptr = (*wallet_list).data as *const *const FFIString;
let ffi_string = *string_ptr.add(i);
assert!(!ffi_string.is_null());
// Verify the string is a valid hex wallet ID (64 characters)
let c_str = CStr::from_ptr((*ffi_string).ptr);
let wallet_id_str = c_str.to_str().unwrap();
assert_eq!(wallet_id_str.len(), 64);
assert!(wallet_id_str.chars().all(|c| c.is_ascii_hexdigit()));
}

// Clean up wallets only
// Clean up wallets
for wallet_id in wallet_ids {
if !wallet_id.is_null() {
let string_struct = unsafe { Box::from_raw(wallet_id) };
let string_struct = Box::from_raw(wallet_id);
dash_spv_ffi_string_destroy(*string_struct);
}
}
// Skip array destruction for now to avoid memory corruption
// Properly destroy the wallet list array, including inner strings
dash_spv_ffi_string_array_destroy(wallet_list);
dash_spv_ffi_client_destroy(client);
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

wallet_list returns FFIString elements — use string-array owned destroyer.*

Ensures inner strings and FFIArray Box are freed.

-            dash_spv_ffi_string_array_destroy(wallet_list);
+            dash_spv_ffi_string_array_destroy_owned(wallet_list);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Test listing wallets with proper memory management
let wallet_list = dash_spv_ffi_wallet_list(client);
// Just ensure we can call the function without crashing
println!("Wallet list function called successfully");
// wallet_list is now a pointer to FFIArray (heap allocated)
assert!(!wallet_list.is_null());
// Should have 3 wallets
assert_eq!((*wallet_list).len, 3);
// Each element should be a valid FFIString
for i in 0..(*wallet_list).len {
let string_ptr = (*wallet_list).data as *const *const FFIString;
let ffi_string = *string_ptr.add(i);
assert!(!ffi_string.is_null());
// Verify the string is a valid hex wallet ID (64 characters)
let c_str = CStr::from_ptr((*ffi_string).ptr);
let wallet_id_str = c_str.to_str().unwrap();
assert_eq!(wallet_id_str.len(), 64);
assert!(wallet_id_str.chars().all(|c| c.is_ascii_hexdigit()));
}
// Clean up wallets only
// Clean up wallets
for wallet_id in wallet_ids {
if !wallet_id.is_null() {
let string_struct = unsafe { Box::from_raw(wallet_id) };
let string_struct = Box::from_raw(wallet_id);
dash_spv_ffi_string_destroy(*string_struct);
}
}
// Skip array destruction for now to avoid memory corruption
// Properly destroy the wallet list array, including inner strings
dash_spv_ffi_string_array_destroy(wallet_list);
dash_spv_ffi_client_destroy(client);
// Test listing wallets with proper memory management
let wallet_list = dash_spv_ffi_wallet_list(client);
// wallet_list is now a pointer to FFIArray (heap allocated)
assert!(!wallet_list.is_null());
// Should have 3 wallets
assert_eq!((*wallet_list).len, 3);
// Each element should be a valid FFIString
for i in 0..(*wallet_list).len {
let string_ptr = (*wallet_list).data as *const *const FFIString;
let ffi_string = *string_ptr.add(i);
assert!(!ffi_string.is_null());
// Verify the string is a valid hex wallet ID (64 characters)
let c_str = CStr::from_ptr((*ffi_string).ptr);
let wallet_id_str = c_str.to_str().unwrap();
assert_eq!(wallet_id_str.len(), 64);
assert!(wallet_id_str.chars().all(|c| c.is_ascii_hexdigit()));
}
// Clean up wallets
for wallet_id in wallet_ids {
if !wallet_id.is_null() {
let string_struct = Box::from_raw(wallet_id);
dash_spv_ffi_string_destroy(*string_struct);
}
}
// Properly destroy the wallet list array, including inner strings
dash_spv_ffi_string_array_destroy_owned(wallet_list);
dash_spv_ffi_client_destroy(client);
🤖 Prompt for AI Agents
In dash-spv-ffi/tests/unit/test_wallet_operations.rs around lines 472-499, the
test manually frees inner FFIString elements then calls
dash_spv_ffi_string_array_destroy, which risks double-free or incorrect
ownership handling because wallet_list contains FFIString* elements owned by the
array; remove the manual loop that Box::from_raw + dash_spv_ffi_string_destroy
on each wallet_id and instead call
dash_spv_ffi_string_array_destroy(wallet_list) (after null-checking wallet_list)
to let the array-owned destroyer free inner strings and the array Box, then
destroy the client.

//! TODO: Re-enable once NetworkManager trait methods are fully implemented.
#![cfg(skip_mock_implementation_incomplete)]
#![cfg(feature = "skip_mock_implementation_incomplete")]
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Feature-gate fix looks correct.

Switching to #[cfg(feature = "skip_mock_implementation_incomplete")] is the right way to gate tests behind a Cargo feature.


🏁 Script executed:

#!/bin/bash
# Ensure the feature exists and is documented.
rg -n '^features\s*=' -n dash-spv/Cargo.toml
rg -n 'skip_mock_implementation_incomplete' dash-spv/Cargo.toml

Length of output: 154


🏁 Script executed:

#!/bin/bash
# Verify the [features] section and the presence of the feature definition
rg -n '^\[features\]' dash-spv/Cargo.toml
rg -n 'skip_mock_implementation_incomplete' -n dash-spv/Cargo.toml

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Search for the feature mention in markdown documentation (README, docs folder, etc.)
rg -n 'skip_mock_implementation_incomplete' -tmd

Length of output: 48


Feature Gate Verified; Documentation Missing

The skip_mock_implementation_incomplete feature is correctly defined in dash-spv/Cargo.toml (line 71) and the test is properly gated with #[cfg(feature = "skip_mock_implementation_incomplete")]. However, I wasn’t able to find any documentation or mention of this feature in the project’s README or docs.

Please add a brief description of the skip_mock_implementation_incomplete feature—its purpose and usage—to the project documentation (e.g., in README.md or the docs folder).

🤖 Prompt for AI Agents
In dash-spv/tests/filter_header_verification_test.rs around line 6, the test is
gated by the feature flag skip_mock_implementation_incomplete but there is no
project documentation describing this feature; add a short entry to README.md
(or docs/) titled "skip_mock_implementation_incomplete" that states its purpose
(to skip tests relying on an incomplete mock implementation), when to enable it
(e.g., enabling the feature for CI or local runs when mocks are unfinished), how
to enable it (cargo test --features "skip_mock_implementation_incomplete"), and
any caveats or intended future work so developers understand usage and
rationale.

debug_assert_eq!(e.length % BLOCK_SIZE, 0);

Hash(e.midstate().to_byte_array())
Hash(e.midstate().into_inner())
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Build break: Midstate lacks into_inner() (and sha256::Hash may too).

e.midstate().into_inner() does not compile because Midstate in this module exposes to_byte_array(self) but no into_inner(self). This also ripples to other call sites (e.g., tests and sha256t.rs). Add into_inner() for both Midstate and sha256::Hash to align with the repo-wide migration and fix CI.

Apply these additions inside this file:

@@
 impl Hash {
+    #[inline]
+    pub fn into_inner(self) -> [u8; 32] {
+        self.0
+    }
@@
 impl Midstate {
@@
-    /// Unwraps the [`Midstate`] and returns the underlying byte array.
-    pub fn to_byte_array(self) -> [u8; 32] {
-        self.0
-    }
+    /// Unwraps the [`Midstate`] and returns the underlying byte array.
+    #[deprecated(since = "0.40.0", note = "Use into_inner() instead")]
+    pub fn to_byte_array(self) -> [u8; 32] { self.0 }
+
+    /// Consumes self and returns the inner bytes (preferred).
+    pub const fn into_inner(self) -> [u8; 32] { self.0 }

This keeps backward compatibility (via a deprecation) while unblocking all new into_inner() usages.

Also applies to: 54-54


🏁 Script executed:

#!/bin/bash
# Verify both methods now exist and CI errors are gone.
rg -nP 'impl\s+Hash\s*\{(?s).*?\binto_inner\s*\(' hashes/src/sha256.rs -n
rg -nP 'impl\s+Midstate\s*\{(?s).*?\binto_inner\s*\(' hashes/src/sha256.rs -n
# Spot-check the prior failing lines compile semantically.
rg -n 'midstate\(\)\.into_inner\(\)' hashes/src/sha256.rs
rg -n 'from_engine\(e\)\)\.into_inner\(\)' hashes/src/sha256t.rs

Length of output: 370


Missing into_inner() Methods in sha256.rs
The calls to e.midstate().into_inner() at lines 49 and 54 in hashes/src/sha256.rs are still failing because neither impl Hash nor impl Midstate provides an into_inner(self) method. You’ll need to add these to unblock the build and align with the repo-wide API migration.

• File: hashes/src/sha256.rs

  • Line 49: Hash(e.midstate().into_inner())
  • Line 54: let mut hash = e.midstate().into_inner();

Apply this patch to hashes/src/sha256.rs:

--- a/hashes/src/sha256.rs
+++ b/hashes/src/sha256.rs
@@ impl Hash {
     #[inline]
     pub fn from_engine(e: &Engine) -> Self {
-        Hash(e.midstate().into_inner())
+        Hash(e.midstate().into_inner())
     }
+
+    /// Consume the `Hash` and return its inner 32-byte array.
+    #[inline]
+    pub fn into_inner(self) -> [u8; 32] {
+        self.0
+    }
@@ impl Midstate {
-    /// Unwraps the [`Midstate`] and returns the underlying byte array.
-    pub fn to_byte_array(self) -> [u8; 32] {
-        self.0
-    }
+    /// Unwraps the [`Midstate`] and returns the underlying byte array.
+    #[deprecated(since = "0.40.0", note = "Use `into_inner()` instead")]
+    pub fn to_byte_array(self) -> [u8; 32] {
+        self.0
+    }
+
+    /// Consume the `Midstate` and return its inner 32-byte array.
+    #[inline]
+    pub const fn into_inner(self) -> [u8; 32] {
+        self.0
+    }

Verification Steps
After applying the above, confirm with:

rg -nP 'impl\s+Hash\s*\{' -n hashes/src/sha256.rs | rg into_inner
rg -nP 'impl\s+Midstate\s*\{' -n hashes/src/sha256.rs | rg into_inner
rg -n 'e.midstate().into_inner()' -n hashes/src/sha256.rs

Once these methods exist, the build errors at lines 49 and 54 will be resolved.

🧰 Tools
🪛 GitHub Actions: Continuous integration

[error] 49-49: no method named into_inner found for struct Midstate in the current scope. (Hash(e.midstate().into_inner()))

🤖 Prompt for AI Agents
In hashes/src/sha256.rs around lines 49 and 54 the calls to
e.midstate().into_inner() fail because neither impl Hash nor impl Midstate
implement an into_inner(self) method; add into_inner(self) to both impls so they
consume self and return the underlying inner value (e.g., the inner byte array
or fixed-size digest type) with the correct return type and visibility to match
the repo API migration; ensure the method names/signatures match other crates
(consume self, not &self), update any trait impls if needed, and run the
provided grep verification commands to confirm the new into_inner methods are
present and the callsites resolve.

@PastaPastaPasta PastaPastaPasta force-pushed the chore/resolve-various-warnings-and-issues branch from ff95937 to e893309 Compare August 27, 2025 21:52
- Remove unused FFIString import from client.rs
- Remove unused variables (shutdown_signal_clone, inner clones)
- Remove unused network parameters from FFI functions (BREAKING CHANGES):
  * dash_spv_ffi_wallet_get_mempool_balance()
  * dash_spv_ffi_wallet_get_mempool_transaction_count()

- Replace deprecated SyncError::SyncFailed with SyncError::Network
- Update C header declarations in dash_spv_ffi.h
- Update test files to match new function signatures

- Remove custom cfg conditions from test files:
  * skip_mock_implementation_incomplete
  * skip_instantsend_tests
  * skip_example
- Add #[ignore] attributes to incomplete tests with clear reasons
- Remove unexpected_cfgs lint configuration from Cargo.toml

- cfheader_gap_test.rs, error_handling_test.rs, edge_case_filter_sync_test.rs
- chainlock_validation_test.rs, rollback_test.rs, error_recovery_integration_test.rs
- filter_header_verification_test.rs, block_download_test.rs, instantsend_integration_test.rs
- examples/reorg_demo.rs

- Rewrite mempool filter tests to use wallet-generated addresses
- Replace hardcoded test addresses with SPVWalletManager integration
- Add create_test_addresses() helper function for wallet-compatible addresses
- Update test patterns to work with real wallet address generation

- FFI function signatures changed (network parameter removed)
- Requires updates to external code using dash-spv-ffi

- Zero compiler warnings across both crates
- 239 passing tests, 11 appropriately ignored
- Cleaner, more maintainable codebase using standard Rust practices
Remove the unused '_client' variable that was assigned but never used,
eliminating dead code and compiler warnings.
…spv-ffi: align FFI pointer semantics and tests\n - Return *mut FFIArray for address UTXOs/history and handle null clients by returning null\n - For invalid inputs, return empty FFIArray where tests expect safe deref/destroy\n - Return null for watched addresses/scripts on null client\n - Fix test_client deref/destroy of FFIArray pointers\n\n All dash-spv-ffi tests pass; dash-spv tests skip real-node cases.
…or mock-dependent tests; clean tests and examples; fix ffi test warnings; all tests pass for dash-spv and dash-spv-ffi
- Remove 27 unnecessary unsafe blocks in test files
- Fix FFI safety warnings by adding proper repr attributes
- Add #[repr(transparent)] to FFIClientConfig
- Add #[repr(C)] to ClientConfig
- Add #[repr(u8)] to Network enum
- Remove unused dead code (null_data_completion function, barrier field)
- Clean up unused imports and variables
- Update extern declarations to use *mut c_void for better FFI compatibility

All changes result in a clean build with zero warnings.
- Remove unused variables (temp_path, block_hash, script, outpoint)
- Remove dead functions (create_test_header, create_test_wallet_manager)
- Remove incomplete test functions and replace with TODO comments:
  * test_process_transaction_outgoing
  * test_process_transaction_incoming
  * test_process_transaction_fetch_all_strategy
  * test_validation_performance
- Fix unused fields in WatchItem enum (Script/Outpoint variants)
- Add accessor methods to MockWallet to make fields accessible
- Clean up unused imports (ValidationEngine, wallet-related imports)
- Comment out problematic test cases in incomplete implementations

All changes result in cleaner code with proper elimination of dead code
rather than just suppressing warnings with underscore prefixes.
- Replace placeholder address strings in create_test_addresses() with deterministically generated valid P2PKH addresses
- Use SHA256(fixed_seed + index) to generate secp256k1 SecretKey for each address
- Handle key-derivation errors by skipping invalid indices and continuing until count is reached
- Update test_address() and test_address2() functions to use new deterministic generator
- Add verification test to ensure deterministic generation works correctly
- Prevents indexing panics while maintaining test functionality
- Change WatchItem enum variants from unit types to proper types (ScriptBuf, OutPoint)
- Add snake_case constructor methods fn script() and fn outpoint()
- Update call sites to use new snake_case constructors
- Remove unused imports to eliminate warnings

Resolves clippy/rustc warnings for non-snake_case test helpers
- Add FFITransactionRecord FFI struct for transaction records with owned data
- Implement dash_spv_ffi_transaction_record_destroy for individual records
- Implement dash_spv_ffi_transaction_record_array_destroy for arrays of records
- Update test_get_address_history to use correct owned array destroyer
- Export new destroyer functions in FFI header

This fixes memory leaks when destroying address history arrays that contain
owned transaction record data with heap-allocated strings.
… using valid FFINetwork; move invalid raw-value testing to C-side
@PastaPastaPasta PastaPastaPasta force-pushed the chore/resolve-various-warnings-and-issues branch from e893309 to e1fea31 Compare August 28, 2025 15:56
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
dash-spv/tests/error_handling_test.rs (1)

556-586: Align send_ping return type with NetworkManager trait
In dash-spv/tests/error_handling_test.rs update the mock’s signature from NetworkResult<()> to NetworkResult<u64> and return the ping nonce:

-    async fn send_ping(&mut self) -> NetworkResult<()> {
-        self.send_message(dashcore::network::message::NetworkMessage::Ping(1234)).await
-    }
+    async fn send_ping(&mut self) -> NetworkResult<u64> {
+        self.send_message(dashcore::network::message::NetworkMessage::Ping(1234)).await?;
+        Ok(1234)
+    }
dash-spv-ffi/src/client.rs (3)

19-49: Critical: UTXO arrays leak nested allocations (address CString, script_pubkey buffer)

You allocate address (CString::into_raw) and script_pubkey (Box::into_raw) in wallet_utxo_to_ffi, but there’s no typed array destroyer to free those for arrays returned by get_utxos*. Add a typed destroyer that iterates elements, frees nested fields, then frees the array buffer and struct.

Add in this file:

+#[no_mangle]
+pub unsafe extern "C" fn dash_spv_ffi_utxo_array_destroy(arr: *mut FFIArray) {
+    if arr.is_null() {
+        return;
+    }
+    // Free nested fields in each element
+    if !(*arr).data.is_null() && (*arr).len > 0 {
+        let slice = std::slice::from_raw_parts_mut((*arr).data as *mut FFIUtxo, (*arr).len);
+        for utxo in slice {
+            if !utxo.address.is_null() {
+                let _ = CString::from_raw(utxo.address);
+                utxo.address = std::ptr::null_mut();
+            }
+            if !utxo.script_pubkey.is_null() && utxo.script_len > 0 {
+                let _ = Vec::from_raw_parts(utxo.script_pubkey, utxo.script_len, utxo.script_len);
+                utxo.script_pubkey = std::ptr::null_mut();
+                utxo.script_len = 0;
+            }
+        }
+    }
+    // Free buffer + struct
+    crate::types::dash_spv_ffi_array_destroy(arr);
+}

Expose this in the public header and document that arrays from get_utxos* must be destroyed with dash_spv_ffi_utxo_array_destroy.


1428-1473: Consistency: return an empty FFIArray on bad input instead of NULL

Other getters return an owned empty array when inputs are NULL/invalid. Align get_address_history to the same pattern for predictable cleanup.

 pub unsafe extern "C" fn dash_spv_ffi_client_get_address_history(
     client: *mut FFIDashSpvClient,
     address: *const c_char,
 ) -> *mut FFIArray {
-    if client.is_null() || address.is_null() {
-        return std::ptr::null_mut();
-    }
+    if client.is_null() || address.is_null() {
+        return Box::into_raw(Box::new(FFIArray { data: std::ptr::null_mut(), len: 0, capacity: 0, elem_size: 0, elem_align: 1 }));
+    }
@@
-        Err(e) => {
-            set_last_error(&format!("Invalid UTF-8 in address: {}", e));
-            return Box::into_raw(Box::new(FFIArray {
-                data: std::ptr::null_mut(),
-                len: 0,
-                capacity: 0,
-                elem_size: 0,
-                elem_align: 1,
-            }));
-        }
+        Err(e) => {
+            set_last_error(&format!("Invalid UTF-8 in address: {}", e));
+            return Box::into_raw(Box::new(FFIArray { data: std::ptr::null_mut(), len: 0, capacity: 0, elem_size: 0, elem_align: 1 }));
+        }
@@
-        Err(e) => {
-            set_last_error(&format!("Invalid address: {}", e));
-            return Box::into_raw(Box::new(FFIArray {
-                data: std::ptr::null_mut(),
-                len: 0,
-                capacity: 0,
-                elem_size: 0,
-                elem_align: 1,
-            }));
-        }
+        Err(e) => {
+            set_last_error(&format!("Invalid address: {}", e));
+            return Box::into_raw(Box::new(FFIArray { data: std::ptr::null_mut(), len: 0, capacity: 0, elem_size: 0, elem_align: 1 }));
+        }

1031-1077: Add typed destroyers for UTXO and UTXO array
Implement dash_spv_ffi_utxo_destroy(utxo: FFIUtxo) to free all nested fields, and dash_spv_ffi_utxo_array_destroy(arr: *mut FFIArray) to iterate each element calling that destroyer before deallocating the buffer. Update Rust/C headers and docs to direct callers to use these instead of the generic dash_spv_ffi_array_destroy. Add a C unit test that allocates and frees a small UTXO array, validated under Valgrind/ASan to catch leaks.

♻️ Duplicate comments (4)
dash-spv-ffi/src/types.rs (1)

336-352: Fix: FFIArray leak + unchecked Layout; free the struct and use checked Layout

Current destroy frees only the vector buffer and uses Layout::from_size_align_unchecked. It leaks the FFIArray struct and risks UB if elem_align is invalid. Free both the buffer and the struct and use checked Layout.

 #[no_mangle]
 pub unsafe extern "C" fn dash_spv_ffi_array_destroy(arr: *mut FFIArray) {
-    if !arr.is_null() {
-        // Only deallocate the vector buffer recorded in the struct; do not free the struct itself.
-        // This makes it safe to pass pointers to stack-allocated FFIArray values returned by-value.
-        if !(*arr).data.is_null() && (*arr).capacity > 0 {
-            // Deallocate the vector buffer using the original layout
-            use std::alloc::{dealloc, Layout};
-            let size = (*arr).elem_size.saturating_mul((*arr).capacity);
-            if size > 0 && (*arr).elem_align.is_power_of_two() && (*arr).elem_align > 0 {
-                // Safety: elem_size/elem_align were recorded from the original Vec<T>
-                let layout = Layout::from_size_align_unchecked(size, (*arr).elem_align);
-                unsafe { dealloc((*arr).data as *mut u8, layout) };
-            }
-        }
-    }
+    if arr.is_null() {
+        return;
+    }
+    // Take ownership so the struct itself is freed too.
+    let arr = Box::from_raw(arr);
+    if !arr.data.is_null() && arr.capacity > 0 && arr.elem_size > 0 {
+        use std::alloc::{dealloc, Layout};
+        let size = arr.elem_size.saturating_mul(arr.capacity);
+        if let Ok(layout) = Layout::from_size_align(size, arr.elem_align.max(1)) {
+            dealloc(arr.data as *mut u8, layout);
+        }
+    }
 }

Note: update any comments/docs that claim “does not free the struct”.

dash-spv-ffi/src/client.rs (1)

1080-1170: Same leak concern for address-scoped UTXO query

Same action item: require and expose dash_spv_ffi_utxo_array_destroy.

dash-spv-ffi/include/dash_spv_ffi.h (2)

42-43: Breaks C: typedef ClientConfig FFIClientConfig; must be removed

ClientConfig is unknown to C. Keep FFIClientConfig as an opaque forward-declared struct only.

-typedef ClientConfig FFIClientConfig;
+/* FFIClientConfig remains an opaque C type; do not alias to Rust types */

Also ensure Rust marks the wrapper as opaque for cbindgen (see config.rs comment).


502-511: Add missing owned/typed array destroyers to the public API

Expose destroyers that match the new pointer-return semantics and nested fields on UTXOs.

 void dash_spv_ffi_array_destroy(struct FFIArray *arr);
 
 /**
  * Destroy an array of FFIString pointers (Vec<*mut FFIString>) and their contents.
@@
  */
 void dash_spv_ffi_string_array_destroy(struct FFIArray *arr);
+/* Convenience alias for clarity; identical behavior. */
+void dash_spv_ffi_string_array_destroy_owned(struct FFIArray *arr);
+
+/* Destroy an array of FFIUTXO (struct-by-value), freeing nested fields, buffer, and struct. */
+void dash_spv_ffi_utxo_array_destroy(struct FFIArray *arr);
🧹 Nitpick comments (10)
dash-spv/src/sync/validation_test.rs (3)

217-219: Replace TODO with actionable plan or an ignored perf test skeleton.

Don’t leave placeholder TODOs in tests. Either (a) reference a tracking issue, or (b) add an ignored perf test skeleton now to avoid test debt.

Example skeleton to add (outside this range):

#[tokio::test]
#[ignore = "perf-only; requires large QRInfo fixture"]
async fn test_qrinfo_validation_perf() {
    // Setup large QRInfo fixture and engine
    // Run validation end-to-end
    // Assert elapsed <= agreed SLA
}

233-233: Use black_box instead of an unused binding in perf loop.

Prevents dead-code elimination and keeps intent clear.

Apply this diff:

-            let _hash = BlockHash::from([i as u8; 32]);
+            std::hint::black_box(BlockHash::from([i as u8; 32]));

1-2: Integration tests should live under tests/ per project convention.

File header says “Integration tests,” but these reside in src/. Move true integration tests under tests/ to align with repo guidance; keep unit tests inside src/.

dash-spv/tests/edge_case_filter_sync_test.rs (2)

6-6: Avoid compiling out the entire test file; prefer feature-gated ignores per test

Compiling the whole file only when the feature is enabled prevents catching compile-time drift. Recommend removing the crate-level cfg and using cfg_attr on each test to ignore them when the skip feature is set.

Apply:

-#![cfg(feature = "skip_mock_implementation_incomplete")]

And change each test’s ignore (see below comments) to:

-#[ignore = "mock implementation incomplete"]
+#[cfg_attr(feature = "skip_mock_implementation_incomplete", ignore = "mock implementation incomplete")]

145-185: Minor: tighten assertions and types for clarity

  • Prefer assert! over assert_eq!(…, false).
  • Make height type explicit to avoid inference surprises across edits.
-    let height = 100;
+    let height: u32 = 100;
...
-    assert!(result.is_ok());
-    assert_eq!(result.unwrap(), false, "Should not start sync when already at tip");
+    assert!(result.is_ok());
+    assert!(!result.unwrap(), "Should not start sync when already at tip");
dash-spv/tests/error_handling_test.rs (3)

1-1: Prefer per-test cfg_attr ignores over crate-level cfg

Same rationale as other file: keep tests compiling by default; ignore execution behind the feature.

-#![cfg(feature = "skip_mock_implementation_incomplete")]
+// Keep file compiled; ignore specific tests when skip feature is enabled (see per-test changes below).

Additionally, confirm feature defaults in dash-spv/Cargo.toml so CI doesn’t inadvertently drop compile coverage for this file.


540-556: Reduce duplication: feature-gated ignores can replace per-test #[ignore]

Rather than repeating #[ignore = "mock implementation incomplete"] on each test, gate them with cfg_attr tied to the same feature.

-#[ignore = "mock implementation incomplete"]
+#[cfg_attr(feature = "skip_mock_implementation_incomplete", ignore = "mock implementation incomplete")]

Apply to all tests in this file that currently use #[ignore = "..."].

I can mass-update these in a follow-up patch if you want.


1067-1111: Tempfile/disk I/O test: avoid flakiness and ensure cleanup

The corruption loop truncates the first .dat file found and then breaks. If headers sharding changes, this might miss files. Also ensure created dirs are within temp scope only.

-        for entry in entries.flatten() {
-            if entry.path().extension().map(|e| e == "dat").unwrap_or(false) {
-                // Truncate file to simulate corruption
-                let _ = std::fs::OpenOptions::new().write(true).truncate(true).open(entry.path());
-                break;
-            }
-        }
+        for entry in entries.flatten().filter(|e| e.path().extension().map(|ext| ext == "dat").unwrap_or(false)) {
+            let _ = std::fs::OpenOptions::new().write(true).truncate(true).open(entry.path());
+        }

This ensures at least one file gets corrupted even if naming/layout changes.

dash-spv-ffi/src/client.rs (2)

1559-1575: Consistency: also return empty arrays for watched lists when client is NULL

Match other getters for predictable caller cleanup.

 pub unsafe extern "C" fn dash_spv_ffi_client_get_watched_addresses(
     client: *mut FFIDashSpvClient,
 ) -> *mut FFIArray {
-    if client.is_null() {
-        return std::ptr::null_mut();
-    }
+    if client.is_null() {
+        return Box::into_raw(Box::new(FFIArray { data: std::ptr::null_mut(), len: 0, capacity: 0, elem_size: 0, elem_align: 1 }));
+    }
@@
 pub unsafe extern "C" fn dash_spv_ffi_client_get_watched_scripts(
     client: *mut FFIDashSpvClient,
 ) -> *mut FFIArray {
-    if client.is_null() {
-        return std::ptr::null_mut();
-    }
+    if client.is_null() {
+        return Box::into_raw(Box::new(FFIArray { data: std::ptr::null_mut(), len: 0, capacity: 0, elem_size: 0, elem_align: 1 }));
+    }

Also applies to: 1578-1593


1695-1703: Consistency: align address UTXO helper with the above

Prefer returning an owned empty array on NULL inputs for uniform API behavior.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ff95937 and e1fea31.

📒 Files selected for processing (48)
  • dash-network/src/lib.rs (1 hunks)
  • dash-spv-ffi/include/dash_spv_ffi.h (8 hunks)
  • dash-spv-ffi/peer_reputation.json (0 hunks)
  • dash-spv-ffi/src/client.rs (14 hunks)
  • dash-spv-ffi/src/config.rs (1 hunks)
  • dash-spv-ffi/src/types.rs (4 hunks)
  • dash-spv-ffi/tests/c_tests/test_advanced.c (1 hunks)
  • dash-spv-ffi/tests/integration/test_full_workflow.rs (1 hunks)
  • dash-spv-ffi/tests/test_client.rs (1 hunks)
  • dash-spv-ffi/tests/test_event_callbacks.rs (2 hunks)
  • dash-spv-ffi/tests/test_mempool_tracking.rs (1 hunks)
  • dash-spv-ffi/tests/test_platform_integration.rs (1 hunks)
  • dash-spv-ffi/tests/unit/test_async_operations.rs (0 hunks)
  • dash-spv-ffi/tests/unit/test_error_handling.rs (1 hunks)
  • dash-spv-ffi/tests/unit/test_memory_management.rs (1 hunks)
  • dash-spv/Cargo.toml (1 hunks)
  • dash-spv/examples/filter_sync.rs (1 hunks)
  • dash-spv/examples/reorg_demo.rs (0 hunks)
  • dash-spv/peer_reputation.json (0 hunks)
  • dash-spv/src/chain/orphan_pool.rs (0 hunks)
  • dash-spv/src/chain/reorg.rs (0 hunks)
  • dash-spv/src/client/block_processor.rs (2 hunks)
  • dash-spv/src/client/block_processor_test.rs (5 hunks)
  • dash-spv/src/client/config.rs (2 hunks)
  • dash-spv/src/client/config_test.rs (1 hunks)
  • dash-spv/src/client/mod.rs (2 hunks)
  • dash-spv/src/mempool_filter.rs (12 hunks)
  • dash-spv/src/sync/filters.rs (3 hunks)
  • dash-spv/src/sync/sequential/mod.rs (1 hunks)
  • dash-spv/src/sync/sequential/recovery.rs (1 hunks)
  • dash-spv/src/sync/validation_test.rs (2 hunks)
  • dash-spv/src/validation/headers_test.rs (0 hunks)
  • dash-spv/tests/block_download_test.rs (10 hunks)
  • dash-spv/tests/cfheader_gap_test.rs (6 hunks)
  • dash-spv/tests/chainlock_simple_test.rs (1 hunks)
  • dash-spv/tests/chainlock_validation_test.rs (6 hunks)
  • dash-spv/tests/edge_case_filter_sync_test.rs (4 hunks)
  • dash-spv/tests/error_handling_test.rs (21 hunks)
  • dash-spv/tests/error_recovery_integration_test.rs (6 hunks)
  • dash-spv/tests/filter_header_verification_test.rs (5 hunks)
  • dash-spv/tests/instantsend_integration_test.rs (3 hunks)
  • dash-spv/tests/integration_real_node_test.rs (7 hunks)
  • dash-spv/tests/multi_peer_test.rs (0 hunks)
  • dash-spv/tests/qrinfo_integration_test.rs (0 hunks)
  • dash-spv/tests/rollback_test.rs (3 hunks)
  • dash-spv/tests/smart_fetch_integration_test.rs (1 hunks)
  • dash-spv/tests/wallet_integration_test.rs (2 hunks)
  • swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h (4 hunks)
💤 Files with no reviewable changes (9)
  • dash-spv/src/chain/orphan_pool.rs
  • dash-spv/src/validation/headers_test.rs
  • dash-spv/tests/multi_peer_test.rs
  • dash-spv/tests/qrinfo_integration_test.rs
  • dash-spv/examples/reorg_demo.rs
  • dash-spv-ffi/tests/unit/test_async_operations.rs
  • dash-spv/src/chain/reorg.rs
  • dash-spv/peer_reputation.json
  • dash-spv-ffi/peer_reputation.json
🚧 Files skipped from review as they are similar to previous changes (31)
  • dash-spv/examples/filter_sync.rs
  • dash-spv-ffi/tests/test_platform_integration.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv/tests/chainlock_simple_test.rs
  • dash-spv/src/client/config_test.rs
  • dash-spv/Cargo.toml
  • dash-spv/src/client/mod.rs
  • dash-spv-ffi/tests/test_client.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-network/src/lib.rs
  • dash-spv/tests/error_recovery_integration_test.rs
  • dash-spv/src/client/config.rs
  • dash-spv/src/sync/sequential/mod.rs
  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv-ffi/tests/test_event_callbacks.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/integration_real_node_test.rs
  • dash-spv/src/client/block_processor_test.rs
  • dash-spv/tests/smart_fetch_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv-ffi/tests/unit/test_memory_management.rs
  • dash-spv/tests/wallet_integration_test.rs
  • dash-spv/src/mempool_filter.rs
  • dash-spv/src/sync/filters.rs
  • dash-spv-ffi/tests/integration/test_full_workflow.rs
  • dash-spv/src/client/block_processor.rs
  • dash-spv-ffi/tests/test_mempool_tracking.rs
  • dash-spv/src/sync/sequential/recovery.rs
  • dash-spv/tests/rollback_test.rs
  • swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h
  • dash-spv-ffi/tests/c_tests/test_advanced.c
🧰 Additional context used
📓 Path-based instructions (11)
dash-spv-ffi/src/**/*.rs

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

dash-spv-ffi/src/**/*.rs: FFI functions must be declared with #[no_mangle] extern "C"
Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management
Return Rust strings to C as *const c_char; allocate so the caller can free with dash_string_free
Accept input strings from C as *const c_char (borrowed; do not free inputs)
Functions returning pointers transfer ownership to the caller; functions taking pointers only borrow
Add cbindgen annotations for complex types so headers are generated correctly

Files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Use proper error types with the thiserror crate and propagate errors appropriately
Use the tokio runtime for async operations
Adhere to MSRV: code must compile on Rust 1.89 (avoid newer language/features)
Keep code formatted with rustfmt (cargo fmt)
Keep the codebase clippy-clean (clippy with -D warnings)

Files:

  • dash-spv-ffi/src/config.rs
  • dash-spv/src/sync/validation_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
{dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}}

📄 CodeRabbit inference engine (CLAUDE.md)

Be especially careful with memory safety at FFI boundaries

Files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
{**/*.rs,**/Cargo.toml}

📄 CodeRabbit inference engine (CLAUDE.md)

Use feature flags and conditional compilation for optional features

Files:

  • dash-spv-ffi/src/config.rs
  • dash-spv/src/sync/validation_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
{dash-network,rpc-client,rpc-json,dash-spv}/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Always validate inputs from untrusted sources

Files:

  • dash-spv/src/sync/validation_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv/tests/error_handling_test.rs
dash-spv/**/*.rs

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

dash-spv/**/*.rs: Enforce Rust formatting via cargo fmt --check on all Rust source files
All code must be clippy-clean: run cargo clippy --all-targets --all-features -- -D warnings

Files:

  • dash-spv/src/sync/validation_test.rs
  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv/tests/error_handling_test.rs
**/tests/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/tests/**/*.rs: Write unit tests for new functionality
Use proptest for property-based testing where appropriate

Files:

  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv/tests/error_handling_test.rs
{dash-network,dash-spv}/tests/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Provide integration tests for network operations

Files:

  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv/tests/error_handling_test.rs
dash-spv/tests/**/*.rs

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

Place integration tests under the tests/ directory

Files:

  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv/tests/error_handling_test.rs
dash-spv-ffi/tests/unit/**/*.rs

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

Add a corresponding Rust unit test for each new FFI function

Files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
dash-spv-ffi/include/dash_spv_ffi.h

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

The public C header is generated; ensure it reflects FFI changes after builds

Files:

  • dash-spv-ffi/include/dash_spv_ffi.h
🧠 Learnings (25)
📓 Common learnings
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:29.335Z
Learning: When adding new FFI functions in the Rust layer, implement them with #[no_mangle] extern "C" and proper cbindgen annotations
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)

Applied to files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : FFI functions must be declared with #[no_mangle] extern "C"

Applied to files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Add cbindgen annotations for complex types so headers are generated correctly

Applied to files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/include/dash_spv_ffi.h : The public C header is generated; ensure it reflects FFI changes after builds

Applied to files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries

Applied to files:

  • dash-spv-ffi/src/config.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/tests/**/*.rs : Place integration tests under the `tests/` directory

Applied to files:

  • dash-spv/src/sync/validation_test.rs
  • dash-spv/tests/error_handling_test.rs
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network,dash-spv}/tests/**/*.rs : Provide integration tests for network operations

Applied to files:

  • dash-spv/tests/edge_case_filter_sync_test.rs
  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv/tests/error_handling_test.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/unit/**/*.rs : Add a corresponding Rust unit test for each new FFI function

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/src/error.rs : Define and maintain domain-specific error types in `src/error.rs` (NetworkError, StorageError, SyncError, ValidationError, SpvError) and update them when adding new failure modes

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv/tests/error_handling_test.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Accept input strings from C as *const c_char (borrowed; do not free inputs)

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Return Rust strings to C as *const c_char; allocate so the caller can free with dash_string_free

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/tests/integration_real_node_test.rs : Integration tests that connect to a live Dash Core node must gracefully skip when the node at 127.0.0.1:9999 is unavailable

Applied to files:

  • dash-spv-ffi/tests/unit/test_error_handling.rs
  • dash-spv/tests/error_handling_test.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/**/*.rs : All code must be clippy-clean: run `cargo clippy --all-targets --all-features -- -D warnings`

Applied to files:

  • dash-spv/tests/error_handling_test.rs
  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/src/storage/**/*.rs : Provide both `MemoryStorageManager` and `DiskStorageManager` behind the `StorageManager` trait

Applied to files:

  • dash-spv/tests/error_handling_test.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Functions returning pointers transfer ownership to the caller; functions taking pointers only borrow

Applied to files:

  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/src/client.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:15:29.335Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:29.335Z
Learning: Applies to swift-dash-core-sdk/Sources/**/FFIBridge.swift : Manage FFI pointers manually using RAII-style wrappers and defer blocks to ensure cleanup

Applied to files:

  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : In C code, free all strings returned by the FFI using dash_string_free

Applied to files:

  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T15:47:37.438Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:47:37.438Z
Learning: Transaction IDs (txids) and block hashes in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.

Applied to files:

  • dash-spv-ffi/src/types.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:15:57.225Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/Examples/DashHDWalletExample/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:57.225Z
Learning: Applies to swift-dash-core-sdk/Examples/DashHDWalletExample/**/*.swift : When accessing SPV functionality, use DashSDK public APIs instead of direct client access; add public wrapper methods to DashSDK if needed

Applied to files:

  • dash-spv-ffi/src/client.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/callbacks.rs : Use C function pointers for async progress/completion callbacks; callbacks may be invoked from any thread

Applied to files:

  • dash-spv-ffi/src/client.rs
📚 Learning: 2025-08-16T04:15:29.335Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:29.335Z
Learning: Applies to swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h : After updating dash-spv-ffi, synchronize the generated header into the Swift SDK using ./sync-headers.sh so Sources/DashSPVFFI/include/dash_spv_ffi.h stays up to date

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T16:01:37.609Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T16:01:37.609Z
Learning: The mempool tracking infrastructure (UnconfirmedTransaction, MempoolState, configuration, and mempool_filter.rs) is fully implemented and integrated in the Dash SPV client as of this PR, including client logic, FFI APIs, and tests.

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T15:46:56.854Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:46:56.854Z
Learning: Transaction IDs (txids) in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
🧬 Code graph analysis (3)
dash-spv-ffi/tests/unit/test_error_handling.rs (1)
dash-spv-ffi/src/config.rs (1)
  • dash_spv_ffi_config_new (29-34)
dash-spv-ffi/src/client.rs (3)
dash-spv-ffi/src/platform_integration.rs (1)
  • error (22-28)
dash-spv-ffi/src/types.rs (5)
  • new (15-23)
  • new (304-318)
  • std (315-315)
  • std (316-316)
  • from_ptr (25-30)
dash-spv-ffi/src/error.rs (1)
  • set_last_error (26-31)
dash-spv-ffi/include/dash_spv_ffi.h (3)
dash-spv-ffi/src/client.rs (7)
  • dash_spv_ffi_client_new (208-265)
  • dash_spv_ffi_client_get_utxos (1031-1077)
  • dash_spv_ffi_client_get_utxos_for_address (1080-1170)
  • dash_spv_ffi_client_get_address_history (1429-1473)
  • dash_spv_ffi_client_get_watched_addresses (1560-1575)
  • dash_spv_ffi_client_get_watched_scripts (1578-1593)
  • dash_spv_ffi_client_get_address_utxos (1696-1704)
dash-spv-ffi/src/config.rs (7)
  • dash_spv_ffi_config_new (29-34)
  • dash_spv_ffi_config_mainnet (37-42)
  • dash_spv_ffi_config_testnet (45-50)
  • dash_spv_ffi_config_set_data_dir (53-71)
  • dash_spv_ffi_config_get_data_dir (185-203)
  • dash_spv_ffi_config_destroy (206-210)
  • dash_spv_ffi_config_set_mempool_tracking (225-234)
dash-spv-ffi/src/types.rs (1)
  • dash_spv_ffi_string_array_destroy (361-380)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: Key Wallet Components Tests
  • GitHub Check: fuzz (hashes_sha512)
  • GitHub Check: fuzz (hashes_sha512_256)
  • GitHub Check: Strict Warnings and Clippy Checks
  • GitHub Check: fuzz (hashes_sha1)
  • GitHub Check: fuzz (hashes_ripemd160)
  • GitHub Check: RPC Tests (stable, true)
  • GitHub Check: fuzz (dash_deser_net_msg)
  • GitHub Check: fuzz (hashes_sha256)
  • GitHub Check: fuzz (hashes_json)
  • GitHub Check: fuzz (dash_deserialize_script)
  • GitHub Check: fuzz (hashes_cbor)
  • GitHub Check: fuzz (dash_deserialize_address)
  • GitHub Check: fuzz (dash_deserialize_block)
  • GitHub Check: fuzz (dash_script_bytes_to_asm_fmt)
  • GitHub Check: fuzz (dash_deserialize_witness)
  • GitHub Check: fuzz (dash_outpoint_string)
  • GitHub Check: fuzz (dash_deserialize_amount)
🔇 Additional comments (7)
dash-spv/tests/edge_case_filter_sync_test.rs (1)

248-305: Message path is valid: FilterSyncManager uses send_message
FilterSyncManager invokes network.send_message (see dash-spv/src/sync/sequential/mod.rs:1841 and dash-spv/src/sync/filters.rs:903), so MockNetworkManager.send_message will populate sent_messages correctly—no need to intercept get_message_sender.

Likely an incorrect or invalid review comment.

dash-spv-ffi/src/types.rs (2)

17-18: Good: string length derived from finalized CString

Prevents length/NUL mismatches.


292-294: Good: record element size/alignment in FFIArray

This metadata is necessary for correct deallocation on the C side.

Also applies to: 315-317

dash-spv-ffi/tests/unit/test_error_handling.rs (1)

176-182: Fix validated: no more UB in Rust tests

Calling config_new with a real FFINetwork and destroying it is correct.

dash-spv-ffi/src/client.rs (2)

1364-1366: Good: early-return validation for script hex

Clear error reporting on invalid hex or too-short scripts.

Also applies to: 1400-1402


616-657: Good: switch to tracing and clearer sync diagnostics

Non-blocking logging improvements and error mapping look fine.

Also applies to: 649-652

dash-spv-ffi/include/dash_spv_ffi.h (1)

322-326: Document element types and required destroyers for pointer-returning arrays

Callers need to know how to free results and what the elements are.

-struct FFIArray *dash_spv_ffi_client_get_utxos(struct FFIDashSpvClient *client);
+/* Returns FFIArray of FFIUTXO (struct-by-value). Free with dash_spv_ffi_utxo_array_destroy(). */
+struct FFIArray *dash_spv_ffi_client_get_utxos(struct FFIDashSpvClient *client);
@@
-struct FFIArray *dash_spv_ffi_client_get_utxos_for_address(struct FFIDashSpvClient *client,
-                                                           const char *address);
+/* Returns FFIArray of FFIUTXO (struct-by-value). Free with dash_spv_ffi_utxo_array_destroy(). */
+struct FFIArray *dash_spv_ffi_client_get_utxos_for_address(struct FFIDashSpvClient *client,
+                                                           const char *address);
@@
-struct FFIArray *dash_spv_ffi_client_get_address_utxos(struct FFIDashSpvClient *client,
-                                                       const char *address);
+/* Returns FFIArray of FFIUTXO (struct-by-value). Free with dash_spv_ffi_utxo_array_destroy(). */
+struct FFIArray *dash_spv_ffi_client_get_address_utxos(struct FFIDashSpvClient *client,
+                                                       const char *address);
@@
-struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);
+/* Returns an FFIArray of FFIString* (addresses). Free with dash_spv_ffi_string_array_destroy(). */
+struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);
@@
-struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);
+/* Returns an FFIArray of FFIString* (hex scripts). Free with dash_spv_ffi_string_array_destroy(). */
+struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);

Also applies to: 353-356, 370-372

⛔ Skipped due to learnings
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : In C code, free all strings returned by the FFI using dash_string_free
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Functions returning pointers transfer ownership to the caller; functions taking pointers only borrow
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:47:37.438Z
Learning: Transaction IDs (txids) and block hashes in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Return Rust strings to C as *const c_char; allocate so the caller can free with dash_string_free
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:46:56.854Z
Learning: Transaction IDs (txids) in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Accept input strings from C as *const c_char (borrowed; do not free inputs)

Comment on lines +23 to 26
#[repr(transparent)]
pub struct FFIClientConfig {
inner: ClientConfig,
}
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

Header breakage: cbindgen emitted typedef ClientConfig FFIClientConfig (invalid in C)

With repr(transparent), cbindgen aliased to a Rust type unknown to C. Mark the wrapper opaque so the header exposes an incomplete struct instead of a typedef to ClientConfig.

-#[repr(transparent)]
+#[repr(transparent)]
+#[cbindgen::opaque]
 pub struct FFIClientConfig {
     inner: ClientConfig,
 }

Also ensure the generated header keeps only typedef struct FFIClientConfig FFIClientConfig; (see header comment below).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[repr(transparent)]
pub struct FFIClientConfig {
inner: ClientConfig,
}
#[repr(transparent)]
#[cbindgen::opaque]
pub struct FFIClientConfig {
inner: ClientConfig,
}
🤖 Prompt for AI Agents
In dash-spv-ffi/src/config.rs around lines 23–26, the FFI wrapper uses
#[repr(transparent)] which lets cbindgen emit a typedef to the Rust ClientConfig
(invalid C); replace the transparent wrapper with an opaque C representation so
the header exposes only an incomplete struct typedef. Concretely, change the
struct to use #[repr(C)] and remove the inner ClientConfig field (replace it
with a zero-sized private marker field like a private zero-length array or
pointer-sized private field) so cbindgen generates only "typedef struct
FFIClientConfig FFIClientConfig;" in the header; update any code that accessed
the inner field to use explicit conversion functions instead.

Comment on lines +354 to +381
/// Destroy an array of FFIString pointers (Vec<*mut FFIString>) and their contents.
///
/// This function:
/// - Iterates the array elements as pointers to FFIString and destroys each via dash_spv_ffi_string_destroy
/// - Frees the underlying vector buffer stored in FFIArray
/// - Does not free the FFIArray struct itself (safe for both stack- and heap-allocated structs)
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_string_array_destroy(arr: *mut FFIArray) {
if arr.is_null() {
return;
}

// Destroy each FFIString pointed to by the array elements
if !(*arr).data.is_null() && (*arr).len > 0 {
let slice = std::slice::from_raw_parts((*arr).data as *const *mut FFIString, (*arr).len);
for &ffi_string_ptr in slice.iter() {
if !ffi_string_ptr.is_null() {
// Take ownership and destroy
let boxed = Box::from_raw(ffi_string_ptr);
dash_spv_ffi_string_destroy(*boxed);
}
}
}

// Free the vector buffer itself
dash_spv_ffi_array_destroy(arr);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add owned string-array destroyer and validate element size

String arrays are returned as heap-allocated FFIArray*. Add an owned variant that also frees the FFIArray struct. Optionally assert elem_size matches pointer width to avoid UB if misused.

 #[no_mangle]
 pub unsafe extern "C" fn dash_spv_ffi_string_array_destroy(arr: *mut FFIArray) {
     if arr.is_null() {
         return;
     }
 
     // Destroy each FFIString pointed to by the array elements
     if !(*arr).data.is_null() && (*arr).len > 0 {
+        debug_assert_eq!((*arr).elem_size, std::mem::size_of::<*mut FFIString>());
         let slice = std::slice::from_raw_parts((*arr).data as *const *mut FFIString, (*arr).len);
         for &ffi_string_ptr in slice.iter() {
             if !ffi_string_ptr.is_null() {
                 // Take ownership and destroy
                 let boxed = Box::from_raw(ffi_string_ptr);
                 dash_spv_ffi_string_destroy(*boxed);
             }
         }
     }
 
-    // Free the vector buffer itself
-    dash_spv_ffi_array_destroy(arr);
+    // Free the vector buffer + struct
+    dash_spv_ffi_array_destroy(arr);
 }
+
+/// Owned variant for clarity; identical to dash_spv_ffi_string_array_destroy
+#[no_mangle]
+pub unsafe extern "C" fn dash_spv_ffi_string_array_destroy_owned(arr: *mut FFIArray) {
+    dash_spv_ffi_string_array_destroy(arr);
+}

Remember to declare the new symbol in the public header.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Destroy an array of FFIString pointers (Vec<*mut FFIString>) and their contents.
///
/// This function:
/// - Iterates the array elements as pointers to FFIString and destroys each via dash_spv_ffi_string_destroy
/// - Frees the underlying vector buffer stored in FFIArray
/// - Does not free the FFIArray struct itself (safe for both stack- and heap-allocated structs)
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_string_array_destroy(arr: *mut FFIArray) {
if arr.is_null() {
return;
}
// Destroy each FFIString pointed to by the array elements
if !(*arr).data.is_null() && (*arr).len > 0 {
let slice = std::slice::from_raw_parts((*arr).data as *const *mut FFIString, (*arr).len);
for &ffi_string_ptr in slice.iter() {
if !ffi_string_ptr.is_null() {
// Take ownership and destroy
let boxed = Box::from_raw(ffi_string_ptr);
dash_spv_ffi_string_destroy(*boxed);
}
}
}
// Free the vector buffer itself
dash_spv_ffi_array_destroy(arr);
}
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_string_array_destroy(arr: *mut FFIArray) {
if arr.is_null() {
return;
}
// Destroy each FFIString pointed to by the array elements
if !(*arr).data.is_null() && (*arr).len > 0 {
debug_assert_eq!((*arr).elem_size, std::mem::size_of::<*mut FFIString>());
let slice = std::slice::from_raw_parts((*arr).data as *const *mut FFIString, (*arr).len);
for &ffi_string_ptr in slice.iter() {
if !ffi_string_ptr.is_null() {
// Take ownership and destroy
let boxed = Box::from_raw(ffi_string_ptr);
dash_spv_ffi_string_destroy(*boxed);
}
}
}
- // Free the vector buffer itself
// Free the vector buffer + struct
dash_spv_ffi_array_destroy(arr);
}
/// Owned variant for clarity; identical to dash_spv_ffi_string_array_destroy
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_string_array_destroy_owned(arr: *mut FFIArray) {
dash_spv_ffi_string_array_destroy(arr);
}
🤖 Prompt for AI Agents
In dash-spv-ffi/src/types.rs around lines 354 to 381, add a second "owned"
destroyer function that takes ownership of the FFIArray pointer, validates the
element size matches the size of a pointer to FFIString (to avoid UB), destroys
each non-null FFIString element via the existing dash_spv_ffi_string_destroy,
frees the vector buffer, and then frees the FFIArray struct itself; keep the
existing non-owned dash_spv_ffi_string_array_destroy behavior unchanged, and
remember to add the new symbol declaration to the public header so callers can
use the owned variant.

- Fix instantsend_integration_test.rs compilation errors by commenting out deprecated method calls
- Add clear TODO comments explaining API changes needed for future updates
- Keep test ignored as it requires significant rewrite for new WalletInterface API
- Minor warning fixes across multiple test files
- Apply cargo fmt formatting
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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
dash-spv/tests/instantsend_integration_test.rs (4)

22-36: Clean up unused imports to keep clippy clean when this file is compiled.

Removes unused dash_spv::{client, network, storage}, dashcore_hashes, and Amount.

-use blsful::{Bls12381G2Impl, SecretKey};
-use dash_spv::{
-    client::{ClientConfig, DashSpvClient},
-    network::MultiPeerNetworkManager,
-    storage::MemoryStorageManager,
-};
-use dashcore::{
-    Address, Amount, InstantLock, Network, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid,
-    Witness,
-};
-use dashcore_hashes::{sha256d, Hash};
+use blsful::{Bls12381G2Impl, SecretKey};
+use dashcore::{
+    Address, InstantLock, Network, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness,
+};
 use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo;
 use key_wallet_manager::{
     spv_wallet_manager::SPVWalletManager, wallet_manager::WalletManager, Utxo,
 };

107-127: Silence unused variable warnings in the disabled section.

wallet, address, and initial_utxo are currently unused and will trip clippy when this feature is enabled.

-    let wallet = create_test_wallet();
-    let address = create_test_address();
+    let _wallet = create_test_wallet();
+    let _address = create_test_address();
@@
-    let mut initial_utxo = Utxo::new(
+    let mut _initial_utxo = Utxo::new(
@@
-    initial_utxo.is_confirmed = true;
+    _initial_utxo.is_confirmed = true;

145-148: Remove unused pk to keep clippy clean.

-    let sk = SecretKey::<Bls12381G2Impl>::random(&mut thread_rng());
-    let pk = sk.public_key();
+    let sk = SecretKey::<Bls12381G2Impl>::random(&mut thread_rng());

219-223: Remove second unused pk.

-    let sk = SecretKey::<Bls12381G2Impl>::random(&mut thread_rng());
-    let pk = sk.public_key();
+    let sk = SecretKey::<Bls12381G2Impl>::random(&mut thread_rng());
dash-spv-ffi/include/dash_spv_ffi.h (1)

88-105: Clarify ownership: arrays returned as pointers require an “owned” destroy.

Docs reference dash_spv_ffi_array_destroy, which frees only the buffer, not the FFIArray wrapper. Add and document dash_spv_ffi_array_destroy_owned for pointer-returned arrays.

  /**
   * FFI-safe array that transfers ownership of memory to the C caller.
   *
   * # Safety
   *
   * This struct represents memory that has been allocated by Rust but ownership
   * has been transferred to the C caller. The caller is responsible for:
   * - Not accessing the memory after it has been freed
-  * - Calling `dash_spv_ffi_array_destroy` to properly deallocate the memory
+  * - Calling `dash_spv_ffi_array_destroy` to free the element buffer when the
+  *   FFIArray is stack-allocated or embedded
+  * - Calling `dash_spv_ffi_array_destroy_owned` when the array itself was
+  *   heap-allocated and returned by pointer (frees buffer and the wrapper)
   * - Ensuring the data, len, and capacity fields remain consistent
   */
♻️ Duplicate comments (5)
dash-spv/tests/block_download_test.rs (1)

6-6: Avoid double-disabling; prefer per-test cfg_attr over module-level cfg.

Drop the crate-level cfg and gate each test with cfg_attr so they run by default.

Apply:

-#![cfg(feature = "skip_mock_implementation_incomplete")]

Also document the feature in README/docs (purpose, when/how to enable).

dash-spv/tests/chainlock_validation_test.rs (1)

6-6: Invert the cfg gate so the “skip” feature actually skips compiling this file under --all-features.

As discussed previously, with cargo clippy run as --all-targets --all-features -D warnings, the current gate includes this file and surfaces warnings. Flip it so enabling the skip feature excludes the file.

-#![cfg(feature = "skip_mock_implementation_incomplete")]
+#![cfg(not(feature = "skip_mock_implementation_incomplete"))]
dash-spv-ffi/include/dash_spv_ffi.h (3)

40-41: Invalid C typedef: ClientConfig is unknown to C; expose an opaque struct.

Replace typedef ClientConfig FFIClientConfig; with a forward-declared opaque C struct so C consumers compile.

-typedef ClientConfig FFIClientConfig;
+// Opaque handle for client configuration
+typedef struct FFIClientConfig FFIClientConfig;

320-324: Document element types and required destructor for returned arrays.

These APIs now return FFIArray*. Specify the element type and the correct destroy function so callers don’t leak memory.

-struct FFIArray *dash_spv_ffi_client_get_utxos(struct FFIDashSpvClient *client);
+/* Returns an FFIArray of FFIUtxo (by value). Free with dash_spv_ffi_utxo_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_utxos(struct FFIDashSpvClient *client);
@@
-struct FFIArray *dash_spv_ffi_client_get_utxos_for_address(struct FFIDashSpvClient *client,
-                                                           const char *address);
+/* Returns an FFIArray of FFIUtxo (by value). Free with dash_spv_ffi_utxo_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_utxos_for_address(struct FFIDashSpvClient *client,
+                                                           const char *address);
@@
-struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);
+/* Returns an FFIArray of FFIString* (addresses). Free with dash_spv_ffi_string_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_watched_addresses(struct FFIDashSpvClient *client);
@@
-struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);
+/* Returns an FFIArray of FFIString* (hex scripts). Free with dash_spv_ffi_string_array_destroy_owned(). */
+struct FFIArray *dash_spv_ffi_client_get_watched_scripts(struct FFIDashSpvClient *client);

Also applies to: 342-344, 351-354, 368-370


492-501: Add owned/typed array destroyers to prevent leaks of heap-allocated arrays.

Expose destroyers that free elements, buffer, and the FFIArray wrapper for pointer-returned arrays. Include a UTXO-typed destroyer.

 void dash_spv_ffi_array_destroy(struct FFIArray *arr);
 
 /**
  * Destroy an array of FFIString pointers (Vec<*mut FFIString>) and their contents.
@@
 void dash_spv_ffi_string_array_destroy(struct FFIArray *arr);
+
+// Owned variants: also free the FFIArray wrapper (for arrays returned by pointer)
+void dash_spv_ffi_array_destroy_owned(struct FFIArray *arr);
+void dash_spv_ffi_string_array_destroy_owned(struct FFIArray *arr);
+
+// Typed UTXO destroyers (elements are FFIUtxo by value with inner strings)
+void dash_spv_ffi_utxo_array_destroy(struct FFIArray *arr);
+void dash_spv_ffi_utxo_array_destroy_owned(struct FFIArray *arr);
🧹 Nitpick comments (23)
dash-spv/tests/error_recovery_integration_test.rs (4)

6-6: Clarify gating semantics; remove redundant per-test ignores

File-level cfg(feature = "skip_mock_implementation_incomplete") already disables the whole file unless explicitly enabled. Redundant #[ignore] on each test is unnecessary and hides the tests even when the feature is enabled.

Apply this diff to drop per-test ignores:

-#[ignore = "mock implementation incomplete"]
 #[tokio::test]
 async fn test_recovery_from_network_interruption_during_header_sync() {
-#[ignore = "mock implementation incomplete"]
 #[tokio::test]
 async fn test_recovery_from_storage_failure_during_sync() {
-#[ignore = "mock implementation incomplete"]
 #[tokio::test]
 async fn test_recovery_from_validation_errors() {
-#[ignore = "mock implementation incomplete"]
 #[tokio::test]
 async fn test_concurrent_error_recovery() {
-#[ignore = "mock implementation incomplete"]
 #[tokio::test]
 async fn test_recovery_statistics_tracking() {

Optional: consider renaming the feature to something positive like "enable_error_recovery_integration_tests" for clarity and flipping cfg accordingly.


19-19: Prune unused imports to keep clippy clean

Network appears unused here; keep only what's referenced.

-use dashcore::{block::Header as BlockHeader, hash_types::FilterHeader, BlockHash, Network, Txid};
+use dashcore::{block::Header as BlockHeader, hash_types::FilterHeader, BlockHash, Txid};

23-27: Drop unused imports from dash_spv and key_wallet_manager

These symbols are not used in this file and will trigger warnings during clippy --all-targets.

-use dash_spv::client::{ClientConfig, DashSpvClient};
+// (no direct client usage in this file)

@@
-use dash_spv::storage::{
-    sync_state::SyncCheckpoint, DiskStorageManager, MemoryStorage, StorageManager,
-};
+use dash_spv::storage::{sync_state::SyncCheckpoint, DiskStorageManager, StorageManager};

- use key_wallet_manager::Utxo;
+// key_wallet_manager::Utxo not used

Also applies to: 29-29


525-531: Avoid returning a fresh channel per get_message_sender() call

Creating a new channel each call breaks expectations for a stable sender handle. Prefer storing a Sender in the mock and cloning it here.

-    fn get_message_sender(
-        &self,
-    ) -> tokio::sync::mpsc::Sender<dashcore::network::message::NetworkMessage> {
-        let (_tx, _rx) = tokio::sync::mpsc::channel(1);
-        _tx
-    }
+    fn get_message_sender(
+        &self,
+    ) -> tokio::sync::mpsc::Sender<dashcore::network::message::NetworkMessage> {
+        self.tx.clone()
+    }

Additionally, add a Sender field to MockNetworkManager and initialize it in new():

// outside the selected range, supporting change
struct MockNetworkManager {
    messages_sent: usize,
    tx: tokio::sync::mpsc::Sender<dashcore::network::message::NetworkMessage>,
}

impl MockNetworkManager {
    fn new() -> Self {
        let (tx, _rx) = tokio::sync::mpsc::channel(1);
        Self { messages_sent: 0, tx }
    }
}
dash-spv/tests/instantsend_integration_test.rs (1)

83-102: InstantLock helper uses a dummy signature; fine for scaffolding, but mark clearly as non-cryptographic.

Consider a comment stating this bypasses real BLS verification and is only for structural tests.

dash-spv/tests/filter_header_verification_test.rs (2)

6-6: Avoid double-disabling: drop crate-level cfg; use per-test cfg_attr instead.

The file is compiled only when the feature is enabled and the tests are also #[ignore]-d, so they never run. Prefer keeping the module always compiled, and ignore tests only when the feature is set.

Apply:

-#![cfg(feature = "skip_mock_implementation_incomplete")]

Then switch each #[ignore] to cfg_attr as shown in comments below.


205-207: Gate with cfg_attr instead of unconditional #[ignore].

This keeps tests runnable locally by default and skips only when the feature is on.

Example (repeat for each test in this file):

-#[ignore = "mock implementation incomplete"]
+#[cfg_attr(
+  feature = "skip_mock_implementation_incomplete",
+  ignore = "mock implementation incomplete"
+)]
 #[tokio::test]
 async fn test_filter_header_verification_failure_reproduction() {

Also applies to: 359-361, 539-541, 635-637

dash-spv/tests/cfheader_gap_test.rs (3)

3-3: The file-level “ignored” note is fine, but prefer actionable gating.

Replace scattershot ignores with a consistent feature-gated #[cfg_attr] approach across the suite.


42-43: Use cfg_attr-based ignore.

Same rationale as other tests: runnable by default, skipped only when feature is on.

Example:

-#[ignore = "mock NetworkManager implementation incomplete"]
+#[cfg_attr(
+  feature = "skip_mock_implementation_incomplete",
+  ignore = "mock NetworkManager implementation incomplete"
+)]
 #[tokio::test]
 async fn test_cfheader_gap_detection_no_gap() {

Apply similarly to the other three tests.

Also applies to: 77-78, 115-116, 153-154


268-290: Don’t leave large blocks commented out; gate or stub instead.

Commented code rots and hides failures. Wrap these calls with a dedicated test-only feature or provide a minimal MultiPeer stub so assertions run.

Example pattern:

-// let result1 = filter_sync.maybe_restart_cfheader_sync_for_gap(&mut network, &mut storage).await;
-// assert!(result1.is_err(), "First restart attempt should fail with mock network");
+#[cfg(feature = "enable_cfheader_restart_path_in_tests")]
+{
+  let result1 = filter_sync
+    .maybe_restart_cfheader_sync_for_gap(&mut network, &mut storage)
+    .await;
+  assert!(result1.is_err(), "First restart attempt should fail with mock network");
+}

I can draft a minimal TestMultiPeerNetworkManager if you want to run these paths without bringing the full implementation.

dash-spv/tests/block_download_test.rs (2)

201-203: Replace unconditional #[ignore] with cfg_attr.

Keeps tests runnable locally; skips only when feature is set.

Patch each block like:

-#[ignore = "mock implementation incomplete"]
+#[cfg_attr(
+  feature = "skip_mock_implementation_incomplete",
+  ignore = "mock implementation incomplete"
+)]
 #[tokio::test]

Also applies to: 213-215, 251-253, 306-308, 323-325, 374-376, 403-405, 434-436


413-416: Unused variable ‘address’ triggers -D warnings.

Prefix with underscore to keep clippy clean, or remove if not needed.

-    let address = create_test_address();
+    let _address = create_test_address();
dash-spv/tests/chainlock_validation_test.rs (6)

188-195: Trim mut and silence unuseds to keep clippy clean when this file is compiled.

No mutation of storage/client here; also avoid unused variable warnings.

-    let mut storage = DiskStorageManager::new(storage_path).await.unwrap();
+    let storage = DiskStorageManager::new(storage_path).await.unwrap();
@@
-    let mut client = DashSpvClient::new(config, network, storage, wallet).await.unwrap();
+    let client = DashSpvClient::new(config, network, storage, wallet).await.unwrap();
@@
-    let chain_lock = create_test_chainlock(0, genesis.block_hash());
+    let _ = create_test_chainlock(0, genesis.block_hash());
@@
-    let chain_state = ChainState::new();
+    let _ = ChainState::new();

Also applies to: 206-206, 215-221


243-245: Same cleanups in “with_masternode_engine” test.

Drop unnecessary mut on storage and silence the unused ChainState; keep client mutable (used by update_chainlock_validation).

-    let mut storage = DiskStorageManager::new(storage_path).await.unwrap();
+    let storage = DiskStorageManager::new(storage_path).await.unwrap();
@@
-    let chain_state = ChainState::new();
+    let _ = ChainState::new();

Also applies to: 266-266, 286-291


303-305: Queue/process flow test: remove mut and unused ChainState.

-    let mut storage = DiskStorageManager::new(storage_path).await.unwrap();
+    let storage = DiskStorageManager::new(storage_path).await.unwrap();
@@
-    let chain_state = ChainState::new();
+    let _ = ChainState::new();

Also applies to: 321-321, 345-347


359-361: Manager cache ops test: remove mut; avoid unused storage/vars to prevent warnings.

-    let mut storage = DiskStorageManager::new(storage_path).await.unwrap();
+    let storage = DiskStorageManager::new(storage_path).await.unwrap();
@@
-    let storage = client.storage();
+    let _ = client.storage();
@@
-    let chain_lock = create_test_chainlock(0, genesis.block_hash());
-    let chain_state = ChainState::new();
+    let _ = create_test_chainlock(0, genesis.block_hash());
+    let _ = ChainState::new();

Also applies to: 377-390


419-426: Client update flow test: drop unnecessary mut on storage; keep client mutable.

-    let mut storage = DiskStorageManager::new(storage_path).await.unwrap();
+    let storage = DiskStorageManager::new(storage_path).await.unwrap();

Also applies to: 437-438


20-22: Move wallet crates to dev-dependencies or add feature gating
chainlock_validation_test.rs imports key-wallet and key-wallet-manager, but both are declared only under regular dependencies in dash-spv/Cargo.toml (lines 15–16). To avoid coupling test-only crates to production code, either move/add them under [dev-dependencies] or guard those tests behind a feature that CI enables.

dash-spv/src/error.rs (1)

115-128: Cloning Io errors drops original context; consider making this explicit or adjusting the variant.

The manual Clone reconstructs io::Error from kind + Display, losing OS error codes/causes. At minimum, document this behavior; optionally switch StorageError::Io to store (io::ErrorKind, String) so Clone is lossless and derivable.

Apply docs inline:

 impl Clone for StorageError {
     fn clone(&self) -> Self {
         match self {
+            // Note: Cloning `Io` recreates a new io::Error from kind + message,
+            // which drops original OS error codes/causes.
             StorageError::Corruption(s) => StorageError::Corruption(s.clone()),
             StorageError::NotFound(s) => StorageError::NotFound(s.clone()),
             StorageError::WriteFailed(s) => StorageError::WriteFailed(s.clone()),
             StorageError::ReadFailed(s) => StorageError::ReadFailed(s.clone()),
             StorageError::Io(err) => StorageError::Io(io::Error::new(err.kind(), err.to_string())),
             StorageError::Serialization(s) => StorageError::Serialization(s.clone()),
             StorageError::InconsistentState(s) => StorageError::InconsistentState(s.clone()),
             StorageError::LockPoisoned(s) => StorageError::LockPoisoned(s.clone()),
         }
     }
 }
dash-spv/tests/error_handling_test.rs (2)

17-42: Remove unused import to keep clippy clean.

use key_wallet_manager::Utxo; is unused and will trip -D warnings pipelines.

- use key_wallet_manager::Utxo;

166-171: Returned Sender is closed because the Receiver is dropped; retain the channel.

Dropping the receiver immediately yields a closed Sender. If any test uses it, sends will fail. Keep the receiver in the mock to maintain a live channel.

-    fn get_message_sender(&self) -> mpsc::Sender<dashcore::network::message::NetworkMessage> {
-        // Create a dummy channel for testing
-        let (_tx, _rx) = mpsc::channel(1);
-        _tx
-    }
+    fn get_message_sender(&self) -> mpsc::Sender<dashcore::network::message::NetworkMessage> {
+        // Keep the receiver alive to avoid returning a closed sender
+        static ONCE: std::sync::Once = std::sync::Once::new();
+        static mut CH: Option<(mpsc::Sender<_>, std::sync::Mutex<Option<mpsc::Receiver<_>>>)> = None;
+        ONCE.call_once(|| {
+            let (tx, rx) = mpsc::channel(1);
+            unsafe { CH = Some((tx, std::sync::Mutex::new(Some(rx)))) };
+        });
+        unsafe { CH.as_ref().unwrap().0.clone() }
+    }
dash-spv-ffi/src/wallet.rs (2)

139-153: FFIUtxo From: good, but mind string allocation costs.

Converting each field to strings is fine for FFI boundary; consider documenting that FFIUtxo must be freed via dash_spv_ffi_utxo_destroy (and array variant) to avoid leaks.


1438-1498: process_mempool_transaction result ignored; propagate and set error on failure.

If the future returns a Result, handle it and map to an error code; otherwise add a brief comment that it cannot fail.

-            spv_client
-                .wallet()
-                .write()
-                .await
-                .process_mempool_transaction(&transaction, network.into())
-                .await;
-            Ok(())
+            let res = spv_client
+                .wallet()
+                .write()
+                .await
+                .process_mempool_transaction(&transaction, network.into())
+                .await;
+            if let Err(e) = res {
+                return Err(e.to_string());
+            }
+            Ok(())
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e1fea31 and fcf86c0.

📒 Files selected for processing (11)
  • dash-spv-ffi/include/dash_spv_ffi.h (8 hunks)
  • dash-spv-ffi/src/wallet.rs (1 hunks)
  • dash-spv/src/error.rs (1 hunks)
  • dash-spv/tests/block_download_test.rs (9 hunks)
  • dash-spv/tests/cfheader_gap_test.rs (7 hunks)
  • dash-spv/tests/chainlock_validation_test.rs (18 hunks)
  • dash-spv/tests/edge_case_filter_sync_test.rs (4 hunks)
  • dash-spv/tests/error_handling_test.rs (25 hunks)
  • dash-spv/tests/error_recovery_integration_test.rs (13 hunks)
  • dash-spv/tests/filter_header_verification_test.rs (9 hunks)
  • dash-spv/tests/instantsend_integration_test.rs (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • dash-spv/tests/edge_case_filter_sync_test.rs
🧰 Additional context used
📓 Path-based instructions (11)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Use proper error types with the thiserror crate and propagate errors appropriately
Use the tokio runtime for async operations
Adhere to MSRV: code must compile on Rust 1.89 (avoid newer language/features)
Keep code formatted with rustfmt (cargo fmt)
Keep the codebase clippy-clean (clippy with -D warnings)

Files:

  • dash-spv/src/error.rs
  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv/tests/error_recovery_integration_test.rs
{**/*.rs,**/Cargo.toml}

📄 CodeRabbit inference engine (CLAUDE.md)

Use feature flags and conditional compilation for optional features

Files:

  • dash-spv/src/error.rs
  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv/tests/error_recovery_integration_test.rs
{dash-network,rpc-client,rpc-json,dash-spv}/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Always validate inputs from untrusted sources

Files:

  • dash-spv/src/error.rs
  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
dash-spv/**/*.rs

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

dash-spv/**/*.rs: Enforce Rust formatting via cargo fmt --check on all Rust source files
All code must be clippy-clean: run cargo clippy --all-targets --all-features -- -D warnings

Files:

  • dash-spv/src/error.rs
  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
dash-spv/src/error.rs

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

Define and maintain domain-specific error types in src/error.rs (NetworkError, StorageError, SyncError, ValidationError, SpvError) and update them when adding new failure modes

Files:

  • dash-spv/src/error.rs
**/tests/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/tests/**/*.rs: Write unit tests for new functionality
Use proptest for property-based testing where appropriate

Files:

  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
{dash-network,dash-spv}/tests/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Provide integration tests for network operations

Files:

  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
dash-spv/tests/**/*.rs

📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)

Place integration tests under the tests/ directory

Files:

  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
dash-spv-ffi/src/**/*.rs

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

dash-spv-ffi/src/**/*.rs: FFI functions must be declared with #[no_mangle] extern "C"
Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management
Return Rust strings to C as *const c_char; allocate so the caller can free with dash_string_free
Accept input strings from C as *const c_char (borrowed; do not free inputs)
Functions returning pointers transfer ownership to the caller; functions taking pointers only borrow
Add cbindgen annotations for complex types so headers are generated correctly

Files:

  • dash-spv-ffi/src/wallet.rs
{dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}}

📄 CodeRabbit inference engine (CLAUDE.md)

Be especially careful with memory safety at FFI boundaries

Files:

  • dash-spv-ffi/src/wallet.rs
dash-spv-ffi/include/dash_spv_ffi.h

📄 CodeRabbit inference engine (dash-spv-ffi/CLAUDE.md)

The public C header is generated; ensure it reflects FFI changes after builds

Files:

  • dash-spv-ffi/include/dash_spv_ffi.h
🧠 Learnings (29)
📓 Common learnings
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/src/error.rs : Define and maintain domain-specific error types in `src/error.rs` (NetworkError, StorageError, SyncError, ValidationError, SpvError) and update them when adding new failure modes

Applied to files:

  • dash-spv/src/error.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/src/storage/**/*.rs : Provide both `MemoryStorageManager` and `DiskStorageManager` behind the `StorageManager` trait

Applied to files:

  • dash-spv/src/error.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/**/*.rs : All code must be clippy-clean: run `cargo clippy --all-targets --all-features -- -D warnings`

Applied to files:

  • dash-spv/src/error.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T15:54:02.509Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:54:02.509Z
Learning: The `StorageManager` trait in `dash-spv/src/storage/mod.rs` uses `&mut self` methods but is also `Send + Sync`, and implementations often use interior mutability for concurrency. This can be confusing, so explicit documentation should clarify thread-safety expectations and the rationale for the API design.

Applied to files:

  • dash-spv/src/error.rs
  • dash-spv/tests/error_recovery_integration_test.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/tests/**/*.rs : Place integration tests under the `tests/` directory

Applied to files:

  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network,dash-spv}/tests/**/*.rs : Provide integration tests for network operations

Applied to files:

  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/unit/**/*.rs : Add a corresponding Rust unit test for each new FFI function

Applied to files:

  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/cfheader_gap_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/tests/integration_real_node_test.rs : Integration tests that connect to a live Dash Core node must gracefully skip when the node at 127.0.0.1:9999 is unavailable

Applied to files:

  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/error_handling_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/filter_header_verification_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
📚 Learning: 2025-06-26T16:01:37.609Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T16:01:37.609Z
Learning: The mempool tracking infrastructure (UnconfirmedTransaction, MempoolState, configuration, and mempool_filter.rs) is fully implemented and integrated in the Dash SPV client as of this PR, including client logic, FFI APIs, and tests.

Applied to files:

  • dash-spv/tests/instantsend_integration_test.rs
  • dash-spv/tests/chainlock_validation_test.rs
  • dash-spv/tests/block_download_test.rs
  • dash-spv/tests/error_recovery_integration_test.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T16:02:42.390Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T16:02:42.390Z
Learning: Passing exclusive mutable references to network and storage managers into the sync manager in async Rust can lead to borrow checker issues or runtime contention if concurrent access is needed elsewhere. It's advisable to document this architectural tradeoff and consider refactoring to interior mutability or message passing for shared access as the codebase evolves.

Applied to files:

  • dash-spv/tests/error_handling_test.rs
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {**/*.rs,**/Cargo.toml} : Use feature flags and conditional compilation for optional features

Applied to files:

  • dash-spv/tests/chainlock_validation_test.rs
📚 Learning: 2025-08-16T04:15:57.225Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/Examples/DashHDWalletExample/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:57.225Z
Learning: Applies to swift-dash-core-sdk/Examples/DashHDWalletExample/**/*.swift : When accessing SPV functionality, use DashSDK public APIs instead of direct client access; add public wrapper methods to DashSDK if needed

Applied to files:

  • dash-spv/tests/chainlock_validation_test.rs
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries

Applied to files:

  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)

Applied to files:

  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-21T05:01:58.949Z
Learnt from: QuantumExplorer
PR: dashpay/rust-dashcore#108
File: key-wallet-ffi/src/wallet_manager.rs:270-318
Timestamp: 2025-08-21T05:01:58.949Z
Learning: In the key-wallet-ffi design, wallets retrieved from the wallet manager via lookup functions should return const pointers (*const FFIWallet) to enforce read-only access and prevent unintended modifications. The wallet manager should control wallet lifecycle and mutations through specific APIs rather than allowing external mutation of retrieved wallet references.

Applied to files:

  • dash-spv-ffi/src/wallet.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Provide corresponding _destroy() functions for all FFI-owned types to enforce explicit memory management

Applied to files:

  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : FFI functions must be declared with #[no_mangle] extern "C"

Applied to files:

  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/include/dash_spv_ffi.h : The public C header is generated; ensure it reflects FFI changes after builds

Applied to files:

  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Functions returning pointers transfer ownership to the caller; functions taking pointers only borrow

Applied to files:

  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Accept input strings from C as *const c_char (borrowed; do not free inputs)

Applied to files:

  • dash-spv-ffi/src/wallet.rs
  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Use async/await throughout on the Tokio runtime

Applied to files:

  • dash-spv/tests/error_recovery_integration_test.rs
📚 Learning: 2025-08-16T04:15:29.335Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:29.335Z
Learning: Applies to swift-dash-core-sdk/Sources/DashSPVFFI/include/dash_spv_ffi.h : After updating dash-spv-ffi, synchronize the generated header into the Swift SDK using ./sync-headers.sh so Sources/DashSPVFFI/include/dash_spv_ffi.h stays up to date

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T15:47:37.438Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:47:37.438Z
Learning: Transaction IDs (txids) and block hashes in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:15:29.335Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: swift-dash-core-sdk/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:15:29.335Z
Learning: Applies to swift-dash-core-sdk/Sources/**/FFIBridge.swift : Manage FFI pointers manually using RAII-style wrappers and defer blocks to ensure cleanup

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/c_tests/**/*.c : In C code, free all strings returned by the FFI using dash_string_free

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Return Rust strings to C as *const c_char; allocate so the caller can free with dash_string_free

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Add cbindgen annotations for complex types so headers are generated correctly

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
📚 Learning: 2025-06-26T15:46:56.854Z
Learnt from: DCG-Claude
PR: dashpay/rust-dashcore#0
File: :0-0
Timestamp: 2025-06-26T15:46:56.854Z
Learning: Transaction IDs (txids) in Dash are always 32 bytes and should be passed as fixed-size byte arrays in FFI interfaces, not as C strings, to avoid inefficiency and encoding issues.

Applied to files:

  • dash-spv-ffi/include/dash_spv_ffi.h
🧬 Code graph analysis (9)
dash-spv/tests/instantsend_integration_test.rs (2)
dash-spv/src/client/block_processor_test.rs (2)
  • wallet (145-145)
  • wallet (327-327)
dash-spv/src/client/mod.rs (1)
  • wallet (105-107)
dash-spv/tests/cfheader_gap_test.rs (2)
dash-spv/src/client/mod.rs (3)
  • network (418-420)
  • network (1219-1222)
  • new (174-237)
dash-spv/src/sync/sequential/mod.rs (2)
  • new (86-113)
  • filter_sync (2036-2038)
dash-spv/tests/error_handling_test.rs (8)
dash-spv/src/storage/memory.rs (1)
  • shutdown (483-485)
dash-spv/tests/block_download_test.rs (10)
  • send_ping (109-111)
  • handle_ping (113-115)
  • handle_pong (117-119)
  • should_ping (121-123)
  • cleanup_old_pings (125-125)
  • get_message_sender (127-130)
  • get_peer_best_height (132-134)
  • has_peer_with_service (136-141)
  • get_peers_with_service (143-148)
  • update_peer_dsq_preference (154-159)
dash-spv/src/network/mod.rs (20)
  • send_ping (62-62)
  • send_ping (260-267)
  • handle_ping (65-65)
  • handle_ping (269-276)
  • handle_pong (68-68)
  • handle_pong (278-285)
  • should_ping (71-71)
  • should_ping (287-289)
  • cleanup_old_pings (74-74)
  • cleanup_old_pings (291-295)
  • get_message_sender (77-77)
  • get_message_sender (297-299)
  • get_peer_best_height (80-80)
  • get_peer_best_height (301-311)
  • has_peer_with_service (83-86)
  • has_peer_with_service (313-326)
  • get_peers_with_service (89-92)
  • get_peers_with_service (328-346)
  • update_peer_dsq_preference (106-106)
  • update_peer_dsq_preference (363-373)
dash-spv/src/network/multi_peer.rs (11)
  • send_ping (1085-1096)
  • handle_ping (1098-1101)
  • handle_pong (1103-1106)
  • should_ping (1108-1111)
  • cleanup_old_pings (1113-1115)
  • get_message_sender (1117-1151)
  • get_peer_best_height (1153-1200)
  • has_peer_with_service (1202-1221)
  • get_peers_with_service (1223-1243)
  • update_peer_dsq_preference (1256-1273)
  • shutdown (919-949)
dash-spv/src/network/mock.rs (10)
  • send_ping (175-177)
  • handle_ping (179-181)
  • handle_pong (183-185)
  • should_ping (187-189)
  • cleanup_old_pings (191-191)
  • get_message_sender (193-195)
  • get_peer_best_height (197-199)
  • has_peer_with_service (201-203)
  • get_peers_with_service (205-207)
  • update_peer_dsq_preference (218-221)
dash-spv/src/network/connection.rs (5)
  • send_ping (588-601)
  • handle_ping (604-611)
  • handle_pong (614-636)
  • should_ping (639-658)
  • cleanup_old_pings (661-677)
dash-spv/src/storage/disk.rs (3)
  • mpsc (155-155)
  • mpsc (156-156)
  • shutdown (1821-1835)
dash-spv/src/storage/mod.rs (1)
  • shutdown (266-266)
dash-spv/tests/chainlock_validation_test.rs (4)
dash-spv/src/client/mod.rs (7)
  • wallet (105-107)
  • storage (2379-2381)
  • new (174-237)
  • network (418-420)
  • network (1219-1222)
  • chainlock_manager (2374-2376)
  • chain_state (2351-2354)
dash-spv/src/storage/disk.rs (1)
  • new (135-249)
dash-spv/src/chain/chainlock_manager.rs (1)
  • new (53-62)
key-wallet-manager/src/spv_wallet_manager.rs (1)
  • with_base (105-114)
dash-spv/tests/filter_header_verification_test.rs (1)
dash-spv/src/sync/sequential/mod.rs (2)
  • filter_sync (2036-2038)
  • new (86-113)
dash-spv/tests/block_download_test.rs (4)
dash-spv/src/sync/sequential/mod.rs (2)
  • new (86-113)
  • filter_sync (2036-2038)
dash-spv/src/client/mod.rs (2)
  • new (174-237)
  • wallet (105-107)
dash-spv/src/sync/filters.rs (3)
  • new (139-173)
  • has_pending_downloads (2207-2209)
  • pending_download_count (2212-2214)
key-wallet-manager/src/spv_wallet_manager.rs (1)
  • with_base (105-114)
dash-spv-ffi/src/wallet.rs (2)
dash-spv-ffi/src/error.rs (1)
  • set_last_error (26-31)
dash-spv-ffi/src/types.rs (3)
  • from_ptr (25-30)
  • dash_spv_ffi_string_destroy (330-334)
  • dash_spv_ffi_array_destroy (337-352)
dash-spv/tests/error_recovery_integration_test.rs (5)
dash-spv/tests/error_handling_test.rs (51)
  • as_any (82-84)
  • disconnect (94-96)
  • is_connected (130-132)
  • peer_info (142-144)
  • send_ping (146-150)
  • handle_ping (152-154)
  • handle_pong (156-158)
  • should_ping (160-162)
  • cleanup_old_pings (164-164)
  • get_message_sender (166-170)
  • get_peer_best_height (172-174)
  • has_peer_with_service (176-181)
  • get_peers_with_service (183-188)
  • update_peer_dsq_preference (190-192)
  • peer_count (134-140)
  • connect (86-92)
  • as_any_mut (246-248)
  • store_headers (250-264)
  • load_headers (266-271)
  • get_header (273-284)
  • get_tip_height (286-291)
  • store_filter_headers (293-298)
  • load_filter_headers (300-308)
  • load_chain_state (350-355)
  • store_filter (357-362)
  • load_filter (364-369)
  • store_metadata (371-376)
  • load_metadata (378-383)
  • clear (385-390)
  • stats (392-400)
  • get_header_height_by_hash (402-410)
  • get_headers_batch (412-421)
  • store_masternode_state (324-332)
  • load_masternode_state (334-341)
  • store_sync_state (423-431)
  • load_sync_state (433-440)
  • clear_sync_state (442-447)
  • store_sync_checkpoint (449-458)
  • get_sync_checkpoints (460-469)
  • store_chain_lock (471-480)
  • load_chain_lock (482-487)
  • get_chain_locks (489-498)
  • store_instant_lock (500-509)
  • load_instant_lock (511-519)
  • store_mempool_transaction (521-530)
  • remove_mempool_transaction (532-537)
  • get_mempool_transaction (539-547)
  • get_all_mempool_transactions (549-556)
  • load_mempool_state (565-570)
  • clear_mempool (572-577)
  • shutdown (579-581)
dash-spv/src/network/mod.rs (32)
  • as_any (38-38)
  • as_any (186-188)
  • disconnect (44-44)
  • disconnect (214-220)
  • is_connected (53-53)
  • is_connected (240-242)
  • peer_info (59-59)
  • peer_info (252-258)
  • send_ping (62-62)
  • send_ping (260-267)
  • handle_ping (65-65)
  • handle_ping (269-276)
  • handle_pong (68-68)
  • handle_pong (278-285)
  • should_ping (71-71)
  • should_ping (287-289)
  • cleanup_old_pings (74-74)
  • cleanup_old_pings (291-295)
  • get_message_sender (77-77)
  • get_message_sender (297-299)
  • get_peer_best_height (80-80)
  • get_peer_best_height (301-311)
  • has_peer_with_service (83-86)
  • has_peer_with_service (313-326)
  • get_peers_with_service (89-92)
  • get_peers_with_service (328-346)
  • update_peer_dsq_preference (106-106)
  • update_peer_dsq_preference (363-373)
  • peer_count (56-56)
  • peer_count (244-250)
  • connect (41-41)
  • connect (190-212)
dash-spv/src/storage/memory.rs (41)
  • std (199-199)
  • std (200-200)
  • std (206-206)
  • std (213-213)
  • std (220-220)
  • std (220-220)
  • as_any_mut (47-49)
  • store_headers (51-89)
  • load_headers (91-100)
  • get_header (102-104)
  • get_tip_height (106-112)
  • store_filter_headers (114-119)
  • load_filter_headers (121-130)
  • load_chain_state (160-162)
  • store_filter (164-167)
  • load_filter (169-171)
  • store_metadata (173-176)
  • load_metadata (178-180)
  • clear (182-193)
  • stats (195-255)
  • get_header_height_by_hash (257-259)
  • get_headers_batch (261-279)
  • store_masternode_state (146-149)
  • load_masternode_state (151-153)
  • store_sync_state (283-296)
  • load_sync_state (298-308)
  • clear_sync_state (310-315)
  • store_sync_checkpoint (317-330)
  • get_sync_checkpoints (332-357)
  • store_chain_lock (359-372)
  • load_chain_lock (374-384)
  • get_chain_locks (386-408)
  • store_instant_lock (410-423)
  • load_instant_lock (425-438)
  • store_mempool_transaction (441-448)
  • remove_mempool_transaction (450-453)
  • get_mempool_transaction (455-460)
  • get_all_mempool_transactions (462-466)
  • load_mempool_state (473-475)
  • clear_mempool (477-481)
  • shutdown (483-485)
dash-spv/src/storage/disk.rs (37)
  • mpsc (155-155)
  • mpsc (156-156)
  • as_any_mut (955-957)
  • store_headers (958-1082)
  • load_headers (1084-1123)
  • get_header (1125-1171)
  • get_tip_height (1173-1175)
  • store_filter_headers (1177-1253)
  • load_filter_headers (1255-1301)
  • load_chain_state (1400-1439)
  • store_filter (1441-1445)
  • load_filter (1447-1455)
  • store_metadata (1457-1461)
  • load_metadata (1463-1471)
  • clear (1473-1494)
  • stats (1496-1527)
  • get_header_height_by_hash (1529-1534)
  • get_headers_batch (1536-1557)
  • store_masternode_state (1344-1352)
  • load_masternode_state (1354-1366)
  • store_sync_state (1561-1581)
  • load_sync_state (1583-1599)
  • clear_sync_state (1601-1608)
  • store_sync_checkpoint (1610-1626)
  • get_sync_checkpoints (1628-1667)
  • store_chain_lock (1669-1685)
  • load_chain_lock (1687-1700)
  • get_chain_locks (1702-1739)
  • store_instant_lock (1741-1757)
  • load_instant_lock (1759-1775)
  • store_mempool_transaction (1778-1785)
  • remove_mempool_transaction (1787-1790)
  • get_mempool_transaction (1792-1797)
  • get_all_mempool_transactions (1799-1803)
  • load_mempool_state (1810-1812)
  • clear_mempool (1814-1818)
  • shutdown (1821-1835)
dash-spv/src/storage/mod.rs (38)
  • as_any_mut (105-105)
  • as_any_mut (271-271)
  • as_any_mut (275-277)
  • store_headers (107-107)
  • load_headers (110-110)
  • get_header (31-31)
  • get_header (118-118)
  • get_tip_height (121-121)
  • store_filter_headers (124-124)
  • load_filter_headers (127-127)
  • load_chain_state (145-145)
  • store_filter (148-148)
  • load_filter (151-151)
  • store_metadata (154-154)
  • load_metadata (157-157)
  • clear (160-160)
  • stats (163-163)
  • get_header_height_by_hash (166-169)
  • get_headers_batch (173-177)
  • store_masternode_state (136-136)
  • load_masternode_state (139-139)
  • store_sync_state (182-182)
  • load_sync_state (185-185)
  • clear_sync_state (188-188)
  • store_sync_checkpoint (191-195)
  • get_sync_checkpoints (198-202)
  • store_chain_lock (205-209)
  • load_chain_lock (212-212)
  • get_chain_locks (215-219)
  • store_instant_lock (222-226)
  • load_instant_lock (229-232)
  • store_mempool_transaction (236-240)
  • remove_mempool_transaction (243-243)
  • get_mempool_transaction (246-249)
  • get_all_mempool_transactions (252-254)
  • load_mempool_state (260-260)
  • clear_mempool (263-263)
  • shutdown (266-266)
dash-spv-ffi/include/dash_spv_ffi.h (3)
dash-spv-ffi/src/client.rs (9)
  • dash_spv_ffi_client_new (208-265)
  • dash_spv_ffi_client_get_utxos (1031-1077)
  • dash_spv_ffi_client_get_utxos_for_address (1080-1170)
  • dash_spv_ffi_client_get_address_history (1429-1473)
  • dash_spv_ffi_client_get_transaction (1476-1501)
  • txid (319-319)
  • dash_spv_ffi_client_get_watched_addresses (1560-1575)
  • dash_spv_ffi_client_get_watched_scripts (1578-1593)
  • dash_spv_ffi_client_get_address_utxos (1696-1704)
dash-spv-ffi/src/config.rs (8)
  • dash_spv_ffi_config_new (29-34)
  • dash_spv_ffi_config_mainnet (37-42)
  • dash_spv_ffi_config_testnet (45-50)
  • dash_spv_ffi_config_set_data_dir (53-71)
  • dash_spv_ffi_config_destroy (206-210)
  • dash_spv_ffi_config_set_mempool_tracking (225-234)
  • dash_spv_ffi_config_set_mempool_strategy (237-246)
  • dash_spv_ffi_config_set_mempool_timeout (261-270)
dash-spv-ffi/src/types.rs (1)
  • dash_spv_ffi_string_array_destroy (361-380)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Integration Tests (stable, 22.1.3)
  • GitHub Check: Integration Tests (stable, 22.0.0)
  • GitHub Check: Clippy (Non-strict)
  • GitHub Check: Strict Warnings and Clippy Checks
  • GitHub Check: RPC Tests (stable, true)
  • GitHub Check: SPV Components Tests
  • GitHub Check: fuzz (hashes_json)
  • GitHub Check: fuzz (dash_deserialize_block)
  • GitHub Check: fuzz (hashes_sha512_256)
  • GitHub Check: fuzz (hashes_sha512)
  • GitHub Check: fuzz (dash_deserialize_address)
  • GitHub Check: fuzz (dash_deserialize_witness)
  • GitHub Check: fuzz (hashes_sha1)
  • GitHub Check: fuzz (hashes_sha256)
  • GitHub Check: fuzz (dash_script_bytes_to_asm_fmt)
  • GitHub Check: fuzz (hashes_ripemd160)
  • GitHub Check: fuzz (dash_deserialize_amount)
  • GitHub Check: fuzz (dash_outpoint_string)
  • GitHub Check: fuzz (dash_deserialize_script)
  • GitHub Check: fuzz (dash_deser_net_msg)

Comment on lines +470 to +503
pub unsafe extern "C" fn dash_spv_ffi_wallet_get_monitored_addresses(
client: *mut FFIDashSpvClient,
network: FFINetwork,
) -> *mut crate::FFIArray {
null_check!(client, std::ptr::null_mut());

let client = &(*client);
let inner = client.inner.clone();

let result = client.run_async(|| async {
let guard = inner.lock().unwrap();
if let Some(ref spv_client) = *guard {
let wallet_manager = &spv_client.wallet().read().await.base;
let addresses = wallet_manager.monitored_addresses(network.into());

let ffi_strings: Vec<*mut FFIString> = addresses
.into_iter()
.map(|addr| Box::into_raw(Box::new(FFIString::new(&addr.to_string()))))
.collect();

Ok(crate::FFIArray::new(ffi_strings))
} else {
Err("Client not initialized".to_string())
}
});

match result {
Ok(array) => Box::into_raw(Box::new(array)),
Err(e) => {
set_last_error(&e);
std::ptr::null_mut()
}
}
}
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

Returned arrays of FFIString need an owned string-array destroyer and header docs.*

These functions allocate FFIArray via Box::into_raw and fill it with FFIString*. Without an “owned” variant, the wrapper leaks. Add dash_spv_ffi_string_array_destroy_owned and reference it in the public header docs.

Additional changes outside this file (types.rs):

#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_string_array_destroy_owned(arr: *mut FFIArray) {
    if arr.is_null() { return; }
    dash_spv_ffi_string_array_destroy(arr);
    let _ = Box::from_raw(arr);
}

Also applies to: 892-923

🤖 Prompt for AI Agents
In dash-spv-ffi/src/wallet.rs around lines 470 to 503 (and also apply same fix
to lines ~892-923), the function returns a heap-allocated FFIArray
(Box::into_raw) containing FFIString* but there is no corresponding "owned"
destroyer exported to allow callers to free the Box as well as its contents; add
and export a no_mangle extern "C" function
dash_spv_ffi_string_array_destroy_owned that checks for null, calls the existing
dash_spv_ffi_string_array_destroy to free inner FFIString elements, then
reclaims the FFIArray Box via Box::from_raw to free the outer allocation, and
update the public header docs to reference this new owned destroyer for any
functions that return FFIArray*.

Comment on lines +556 to +608
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_wallet_get_utxos(
client: *mut FFIDashSpvClient,
wallet_id_ptr: *const c_char,
) -> *mut FFIArray {
null_check!(client, std::ptr::null_mut());
null_check!(wallet_id_ptr, std::ptr::null_mut());

let wallet_id_hex = match CStr::from_ptr(wallet_id_ptr).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(&format!("Invalid UTF-8 in wallet id: {}", e));
return std::ptr::null_mut();
}
};

let mut id: [u8; 32] = [0u8; 32];
let bytes = hex::decode(wallet_id_hex).unwrap_or_default();
if bytes.len() != 32 {
set_last_error("Wallet ID must be 32 bytes hex");
return std::ptr::null_mut();
}
id.copy_from_slice(&bytes);

let client = &(*client);
let inner = client.inner.clone();

let result: Result<FFIArray, String> = client.run_async(|| async {
let guard = inner.lock().unwrap();
if let Some(ref spv_client) = *guard {
let wallet = spv_client.wallet().clone();
let wallet = wallet.read().await;
match wallet.base.wallet_utxos(&id) {
Ok(set) => {
let ffi: Vec<crate::FFIUtxo> =
set.into_iter().cloned().map(crate::FFIUtxo::from).collect();
Ok(FFIArray::new(ffi))
}
Err(e) => Err(e.to_string()),
}
} else {
Err("Client not initialized".to_string())
}
});

match result {
Ok(arr) => Box::into_raw(Box::new(arr)),
Err(e) => {
set_last_error(&e);
std::ptr::null_mut()
}
}
}
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

UTXO arrays return FFIUtxo by value; add typed array destroyers to free inner strings.

Callers currently lack a safe way to free Vec. Provide utxo array destroyers that iterate elements, call dash_spv_ffi_utxo_destroy, then free the buffer (and wrapper for owned).

Additional changes outside this file (types.rs):

#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_utxo_array_destroy(arr: *mut FFIArray) {
    if arr.is_null() { return; }
    if !(*arr).data.is_null() && (*arr).len > 0 {
        let slice = std::slice::from_raw_parts((*arr).data as *const crate::FFIUtxo, (*arr).len);
        for utxo in slice {
            // Destroy inner strings for each by-value element
            crate::types::dash_spv_ffi_string_destroy(utxo.txid);
            crate::types::dash_spv_ffi_string_destroy(utxo.script_pubkey);
            crate::types::dash_spv_ffi_string_destroy(utxo.address);
        }
    }
    crate::types::dash_spv_ffi_array_destroy(arr);
}

#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_utxo_array_destroy_owned(arr: *mut FFIArray) {
    if arr.is_null() { return; }
    dash_spv_ffi_utxo_array_destroy(arr);
    let _ = Box::from_raw(arr);
}
🤖 Prompt for AI Agents
In dash-spv-ffi/src/wallet.rs around lines 556 to 608, the wallet_get_utxos FFI
returns an FFIArray of FFIUtxo by value but there are no typed destroyers to
free inner heap strings; add two functions in types.rs:
dash_spv_ffi_utxo_array_destroy and dash_spv_ffi_utxo_array_destroy_owned.
Implement dash_spv_ffi_utxo_array_destroy to no-op on null, otherwise if data is
non-null and len>0 create a slice from_raw_parts over (*arr).data as *const
FFIUtxo and iterate elements calling the existing string destroyors for each
element's txid, script_pubkey, and address, then call the generic
dash_spv_ffi_array_destroy(arr) to free the buffer; implement
dash_spv_ffi_utxo_array_destroy_owned to call dash_spv_ffi_utxo_array_destroy
then convert Box::from_raw(arr) to drop the wrapper.

Comment on lines +14 to 17
network::{MultiPeerNetworkManager, NetworkManager},
storage::{MemoryStorageManager, StorageManager},
sync::filters::FilterSyncManager,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Concrete MultiPeerNetworkManager in generics makes restart path untestable.

Tests can’t drive maybe_restart_cfheader_sync_for_gap without the concrete type. Consider abstracting the required API behind a trait used by FilterSyncManager so tests can use a minimal mock, or provide a tiny TestMultiPeerNetworkManager stub in tests.

Comment on lines +314 to 316
// Note: current_height method doesn't exist on SyncPhase
// assert!(restart_height < phase.current_height());
eprintln!(
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace commented assertion with an enum-pattern assertion

Assert against the concrete SyncPhase variant to validate restart_height semantics.

-                // Note: current_height method doesn't exist on SyncPhase
-                // assert!(restart_height < phase.current_height());
+                if let dash_spv::sync::sequential::phases::SyncPhase::DownloadingHeaders {
+                    current_height, ..
+                } = &phase
+                {
+                    assert!(
+                        restart_height < *current_height,
+                        "Restart height {} should be less than current height {}",
+                        restart_height,
+                        current_height
+                    );
+                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Note: current_height method doesn't exist on SyncPhase
// assert!(restart_height < phase.current_height());
eprintln!(
if let dash_spv::sync::sequential::phases::SyncPhase::DownloadingHeaders {
current_height, ..
} = &phase
{
assert!(
restart_height < *current_height,
"Restart height {} should be less than current height {}",
restart_height,
current_height
);
}
eprintln!(
🤖 Prompt for AI Agents
In dash-spv/tests/error_recovery_integration_test.rs around lines 314 to 316,
replace the commented-out assertion that referenced a non-existent
current_height method with a pattern match against the SyncPhase enum variant
that exposes the current height; match the phase value (e.g., SyncPhase::Syncing
{ current_height, .. }) and assert restart_height < current_height (or the
appropriate field name), handling other variants with a panic or appropriate
failure branch so the test fails if phase is not the expected variant.

Comment on lines 180 to 185
// Let's create a new UTXO that represents a payment *to* us, and then InstantLock it.
let wallet = create_test_wallet();
let address = create_test_address();
wallet.write().await.add_watched_address(address.clone()).await.unwrap();
// TODO: add_watched_address() method no longer exists
// wallet.write().await.add_watched_address(address.clone()).await.unwrap();

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Second wallet is also unused; rename to _wallet or remove.

-    let wallet = create_test_wallet();
+    let _wallet = create_test_wallet();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Let's create a new UTXO that represents a payment *to* us, and then InstantLock it.
let wallet = create_test_wallet();
let address = create_test_address();
wallet.write().await.add_watched_address(address.clone()).await.unwrap();
// TODO: add_watched_address() method no longer exists
// wallet.write().await.add_watched_address(address.clone()).await.unwrap();
// Let's create a new UTXO that represents a payment *to* us, and then InstantLock it.
let _wallet = create_test_wallet();
let address = create_test_address();
// TODO: add_watched_address() method no longer exists
// wallet.write().await.add_watched_address(address.clone()).await.unwrap();
🤖 Prompt for AI Agents
In dash-spv/tests/instantsend_integration_test.rs around lines 180 to 185, the
second declaration of wallet is unused; either rename it to _wallet to silence
the unused-variable warning or remove the declaration entirely. Update the line
that creates the wallet accordingly and run tests to ensure no unused-variable
compiler warnings remain.

- script::Error: avoid returning bitcoinconsensus::Error as source
- examples: use key-wallet for bip32/psbt imports
- examples: correct taproot Schnorr signing to use 32-byte sighash
- dev-deps: add key-wallet for example builds

cargo test -p dashcore --all-features: 554 passed, 0 failed
- Move #[allow(unused)] to correct position with crate attributes
- Add missing '!' to make it a proper crate-level attribute
- Resolves clippy compilation failure when warnings are treated as errors
- Add #[serial] attributes to error-handling tests to prevent race conditions
- Tests were failing due to concurrent access to global error state
- Ensures tests run sequentially to avoid interference between test cases
- Fixes CI failure: 'assertion failed: error_ptr.is_null()'
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 749805e and 4529dcd.

📒 Files selected for processing (2)
  • dash-spv-ffi/tests/test_error.rs (3 hunks)
  • dash-spv-ffi/tests/unit/test_error_handling.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • dash-spv-ffi/tests/unit/test_error_handling.rs
🧰 Additional context used
📓 Path-based instructions (4)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Use proper error types with the thiserror crate and propagate errors appropriately
Use the tokio runtime for async operations
Adhere to MSRV: code must compile on Rust 1.89 (avoid newer language/features)
Keep code formatted with rustfmt (cargo fmt)
Keep the codebase clippy-clean (clippy with -D warnings)

Files:

  • dash-spv-ffi/tests/test_error.rs
{dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}}

📄 CodeRabbit inference engine (CLAUDE.md)

Be especially careful with memory safety at FFI boundaries

Files:

  • dash-spv-ffi/tests/test_error.rs
{**/*.rs,**/Cargo.toml}

📄 CodeRabbit inference engine (CLAUDE.md)

Use feature flags and conditional compilation for optional features

Files:

  • dash-spv-ffi/tests/test_error.rs
**/tests/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/tests/**/*.rs: Write unit tests for new functionality
Use proptest for property-based testing where appropriate

Files:

  • dash-spv-ffi/tests/test_error.rs
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/unit/**/*.rs : Add a corresponding Rust unit test for each new FFI function

Applied to files:

  • dash-spv-ffi/tests/test_error.rs
📚 Learning: 2025-08-25T17:15:15.391Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network,dash-spv}/tests/**/*.rs : Provide integration tests for network operations

Applied to files:

  • dash-spv-ffi/tests/test_error.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/**/*.rs : All code must be clippy-clean: run `cargo clippy --all-targets --all-features -- -D warnings`

Applied to files:

  • dash-spv-ffi/tests/test_error.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)

Applied to files:

  • dash-spv-ffi/tests/test_error.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Accept input strings from C as *const c_char (borrowed; do not free inputs)

Applied to files:

  • dash-spv-ffi/tests/test_error.rs
📚 Learning: 2025-08-25T17:15:59.361Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/tests/**/*.rs : Place integration tests under the `tests/` directory

Applied to files:

  • dash-spv-ffi/tests/test_error.rs
📚 Learning: 2025-08-16T04:14:07.718Z
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Return Rust strings to C as *const c_char; allocate so the caller can free with dash_string_free

Applied to files:

  • dash-spv-ffi/tests/test_error.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: fuzz (dash_outpoint_string)
  • GitHub Check: fuzz (hashes_sha512)
  • GitHub Check: fuzz (hashes_ripemd160)
  • GitHub Check: fuzz (hashes_sha512_256)
  • GitHub Check: fuzz (dash_deserialize_witness)
  • GitHub Check: fuzz (hashes_json)
  • GitHub Check: fuzz (hashes_sha1)
  • GitHub Check: fuzz (hashes_cbor)
  • GitHub Check: fuzz (hashes_sha256)
  • GitHub Check: fuzz (dash_deserialize_block)
  • GitHub Check: fuzz (dash_script_bytes_to_asm_fmt)
  • GitHub Check: fuzz (dash_deserialize_amount)
  • GitHub Check: fuzz (dash_deserialize_script)
  • GitHub Check: fuzz (dash_deser_net_msg)
  • GitHub Check: fuzz (dash_deserialize_address)
  • GitHub Check: Strict Warnings and Clippy Checks
  • GitHub Check: Clippy (Non-strict)
  • GitHub Check: RPC Tests (stable, true)
  • GitHub Check: SPV Components Tests
  • GitHub Check: Core Components Tests
🔇 Additional comments (1)
dash-spv-ffi/tests/test_error.rs (1)

4-4: Add rust-version = "1.89" to dash-spv-ffi/Cargo.toml
The dash-spv-ffi/Cargo.toml lacks a rust-version field; please add

rust-version = "1.89"

to match the MSRV declared in dash-spv/Cargo.toml and ensure compatibility.

⛔ Skipped due to learnings
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/**/Cargo.toml : Set MSRV to 1.89 using the `rust-version` field and keep the code compatible with Rust 1.89
Learnt from: QuantumExplorer
PR: dashpay/rust-dashcore#108
File: key-wallet/src/gap_limit.rs:291-295
Timestamp: 2025-08-21T04:45:50.436Z
Learning: The rust-dashcore project uses Rust 1.89 as evidenced by rust-version = "1.89" in dash-spv/Cargo.toml. Modern Rust features like Option::is_none_or (stabilized in 1.82) can be used safely.
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to **/*.rs : Adhere to MSRV: code must compile on Rust 1.89 (avoid newer language/features)
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/**/*.rs : All code must be clippy-clean: run `cargo clippy --all-targets --all-features -- -D warnings`
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/tests/unit/**/*.rs : Add a corresponding Rust unit test for each new FFI function
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv/CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:59.361Z
Learning: Applies to dash-spv/**/*.rs : Enforce Rust formatting via `cargo fmt --check` on all Rust source files
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : FFI functions must be declared with #[no_mangle] extern "C"
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Expose complex Rust types to C as opaque pointers (e.g., FFIDashSpvClient*)
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-25T17:15:15.391Z
Learning: Applies to {dash-network-ffi/**/*.rs,dash-spv-ffi/**/*.rs,key-wallet-ffi/**/*.rs,swift-dash-core-sdk/**/*.{swift,m,mm,c,h}} : Be especially careful with memory safety at FFI boundaries
Learnt from: CR
PR: dashpay/rust-dashcore#0
File: dash-spv-ffi/CLAUDE.md:0-0
Timestamp: 2025-08-16T04:14:07.718Z
Learning: Applies to dash-spv-ffi/src/**/*.rs : Add cbindgen annotations for complex types so headers are generated correctly

use std::ffi::CStr;

#[test]
#[serial]
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Good call serializing tests that mutate global last-error state; ensure suite-wide consistency and hermetic cleanup.

  • Using #[serial] is necessary here to avoid cross-test races on the global error buffer.
  • Please verify that any other tests touching set_last_error, clear_last_error/dash_spv_ffi_clear_error, dash_spv_ffi_get_last_error, or handle_error are also annotated with #[serial].
  • Add a final cleanup to test_handle_error to avoid leaving residual global error state for subsequent tests.

To locate un-serialized tests that touch the error API:

Apply this small cleanup at the end of test_handle_error:

@@
         unsafe {
             let error_str = CStr::from_ptr(err_ptr).to_str().unwrap();
             assert_eq!(error_str, "Test error");
         }
+        // Ensure no residual error for subsequent tests
+        dash_spv_ffi_clear_error();
     }

Also applies to: 31-31, 47-47


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show calls to error API with nearby lines so you can confirm #[serial] presence
rg -nC3 'dash_spv_ffi_get_last_error|set_last_error|handle_error|dash_spv_ffi_clear_error|clear_last_error' dash-spv-ffi/tests

Length of output: 27741


Enforce hermetic cleanup and serialize all error-API tests

  • Add dash_spv_ffi_clear_error(); at the end of test_handle_error (dash-spv-ffi/tests/test_error.rs) to clear residual state:
@@ fn test_handle_error() {
     unsafe {
         let error_str = CStr::from_ptr(err_ptr).to_str().unwrap();
         assert_eq!(error_str, "Test error");
     }
+    dash_spv_ffi_clear_error();
 }
  • Annotate every #[test] that calls set_last_error, clear_last_error/dash_spv_ffi_clear_error, dash_spv_ffi_get_last_error or handle_error with #[serial] (e.g. in tests under dash-spv-ffi/tests/unit, test_client.rs, test_platform_integration_safety.rs, security/, integration/, performance/) to avoid cross-test races on the global error buffer.
🤖 Prompt for AI Agents
In dash-spv-ffi/tests/test_error.rs around line 8, add a call to
dash_spv_ffi_clear_error() at the end of the test_handle_error to ensure the
global error buffer is cleared after the test, and annotate every test that uses
set_last_error, clear_last_error/dash_spv_ffi_clear_error,
dash_spv_ffi_get_last_error or handle_error across the test suite
(dash-spv-ffi/tests/unit, test_client.rs, test_platform_integration_safety.rs,
security/, integration/, performance/) with #[serial] to serialize tests and
prevent cross-test races on the global error buffer.

@PastaPastaPasta PastaPastaPasta force-pushed the chore/resolve-various-warnings-and-issues branch from 8c84caf to 4529dcd Compare August 29, 2025 02:20
@QuantumExplorer QuantumExplorer changed the title FFIArray metadata, pointer-based getters; wallet mempool API update fix: FFIArray metadata, pointer-based getters; wallet mempool API update Aug 29, 2025
@QuantumExplorer QuantumExplorer merged commit 0100516 into v0.40-dev Aug 29, 2025
39 of 51 checks passed
@QuantumExplorer QuantumExplorer deleted the chore/resolve-various-warnings-and-issues branch August 29, 2025 03:59
@coderabbitai coderabbitai bot mentioned this pull request Aug 29, 2025
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.

3 participants