-
Notifications
You must be signed in to change notification settings - Fork 3
fix: FFIArray metadata, pointer-based getters; wallet mempool API update #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: FFIArray metadata, pointer-based getters; wallet mempool API update #119
Conversation
|
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 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. 📒 Files selected for processing (12)
WalkthroughFFI 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
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
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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: unresolvedVersion/CompactTargethere (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 theuseabove 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 implementationDocs say the caller allocates
raw_txbefore passing to Rust, but the implementation frees it viaVec::from_raw_parts, which assumes Rust allocation. Clarify thatraw_txis 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 stringsHere 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 arraysTo avoid leaking the inner C strings (and to match the intended destructor), every call to destroy a
FFIArrayreturned bydash_spv_ffi_client_get_watched_addresses(and similarlyget_watched_scripts) must usedash_spv_ffi_string_array_destroy, not the genericdash_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_addressesandget_watched_scriptsto ensure that all string‐based arrays are torn down withdash_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 destroyerI verified that all pointer-returning FFI getters—
dash_spv_ffi_client_get_utxos,get_watched_addresses, andget_watched_scripts—are exercised in both unit and integration tests, and their returned arrays/strings are properly destroyed with the existing genericdash_spv_ffi_array_destroyanddash_spv_ffi_string_array_destroycalls. 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 fakeFFIArrayof UTXOs, callsdash_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 valuesRust 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 testsThe 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
ignoreattribute 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-teststo 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-gatedThis 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] everywhereSame 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 ignoreGood 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 ignoreThe 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 setBoth 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 amountsNegatives 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_requestedshould not be incremented here; you already incrementblocks_processedafter 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: Unboundedprocessed_blockscan grow without limitIf 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 APIsElsewhere (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 usingelem_sizeto catch misuse.
I can draft those helpers if you confirm the intended shapes.
506-514: Addresses array destroy mirrors Rust allocation assumptionSame 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_relevantonly 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
scriptandwatched_outpointare 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_manageris 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 addressesThis “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 sleepsThe cooldown test uses real
tokio::time::sleep. If feasible, expose an injectable clock or usetokio::time::pause/advanceto make this deterministic and faster.dash-spv-ffi/tests/unit/test_memory_management.rs (1)
41-67: Consider adding a string-array lifecycle testTo cover
dash_spv_ffi_string_array_destroy, add a test that buildsFFIArrayofFFIStringand 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 pointersFuture 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 nestedunsafeblocks inside an enclosingunsafescopeYou 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 semanticsThese 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_ownedTo avoid leaking when persistence is implemented to return strings.
| 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); | ||
|
|
There was a problem hiding this comment.
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 || trueLength 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.
| 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); | ||
|
|
There was a problem hiding this comment.
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().
| 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); | ||
| } |
There was a problem hiding this comment.
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.
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.
| 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.
| 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); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
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:
- Add Rust “owned” destroyers that also free the FFIArray struct.
- Add a typed destroyer for arrays of FFIUtxo that deep-destroys elements.
- 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 gracefullyAdd 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.
| // 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); |
There was a problem hiding this comment.
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
| 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this 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_dataIf the client could invoke callbacks during destruction or on background threads, freeing
user_datafirst risks UAF. Free it afterdash_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 ResultThis 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 hereRepeat 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” destroyersAPIs 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, andDurationaren’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: Fixtest_chainlock_validation_without_masternode_engine: update client constructor and suppress unused-variable warningThe 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 usesnetwork, 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 insteadReplace 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/scriptsThese 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 arraysdash_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 destroyersUpdate 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 scaffoldLeftover 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 hashesCurrent [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
NetworkUncheckedin 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 u8andimpl core::convert::TryFrom<u8> for Networkto centralize conversions.dash-spv-ffi/src/config.rs (1)
36-50: Consider parity constructors for convenience (devnet/regtest).You have
config_mainnet/config_testnet; addingconfig_devnet/config_regtestimproves API symmetry. If you want to reduce surface area instead, consider relying solely ondash_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].pathdefaults to "src/lib.rs". Consider removing to reduce noise.[lib] name = "dash_spv" -path = "src/lib.rs"
69-71: Feature flag LGTM; naming nitFeature 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 logicThe file is compiled only with the feature enabled. Given tests are also
#[ignore], you now need both--featuresand-- --ignoredto run them. If that’s intentional, fine; otherwise consider a single mechanism (feature or ignore) to reduce friction.
145-145: Consolidate conditional ignoringYou 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 intobasedirectlyLeaving
wallet_managerimmutable is correct. Consider exposing thin pass-through methods onSPVWalletManager(e.g.,wallet_count(),total_balance()) instead of reaching intobaseto keep encapsulation.dash-spv/src/client/block_processor_test.rs (1)
301-322: Avoid flaky sleep; await the oneshot response insteadReplace 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 HashSetInput 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 destroyerThe 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 clientisn’t needed.networkis 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; unusednetwork.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: unusednetwork; 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 unnecessarymut.
- Update constructor args.
- Drop
mutonclient.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
newsignature 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.
📒 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.rsdash-spv/examples/filter_sync.rsdash-spv/tests/wallet_integration_test.rsdash-spv/src/sync/validation_test.rsdash-spv-ffi/src/config.rsdash-spv-ffi/tests/test_mempool_tracking.rsdash-network/src/lib.rsdash-spv/tests/chainlock_simple_test.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/test_wallet.rsdash-spv/tests/error_handling_test.rsdash-spv/src/client/block_processor_test.rsdash-spv/src/client/config_test.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-spv/src/mempool_filter.rsdash-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.rsdash-spv/examples/filter_sync.rsdash-spv/tests/wallet_integration_test.rsdash-spv/src/sync/validation_test.rsdash-spv-ffi/src/config.rsdash-spv-ffi/tests/test_mempool_tracking.rsdash-network/src/lib.rsdash-spv/tests/chainlock_simple_test.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/test_wallet.rsdash-spv/Cargo.tomldash-spv/tests/error_handling_test.rsdash-spv/src/client/block_processor_test.rsdash-spv/src/client/config_test.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-spv/src/mempool_filter.rsdash-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.rsdash-spv/tests/wallet_integration_test.rsdash-spv-ffi/tests/test_mempool_tracking.rsdash-spv/tests/chainlock_simple_test.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/test_wallet.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-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.rsdash-spv/tests/wallet_integration_test.rsdash-spv/tests/chainlock_simple_test.rsdash-spv/tests/error_handling_test.rsdash-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.rsdash-spv/examples/filter_sync.rsdash-spv/tests/wallet_integration_test.rsdash-spv/src/sync/validation_test.rsdash-network/src/lib.rsdash-spv/tests/chainlock_simple_test.rsdash-spv/tests/error_handling_test.rsdash-spv/src/client/block_processor_test.rsdash-spv/src/client/config_test.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-spv/src/mempool_filter.rs
dash-spv/**/*.rs
📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)
dash-spv/**/*.rs: Enforce Rust formatting viacargo fmt --checkon all Rust source files
All code must be clippy-clean: runcargo clippy --all-targets --all-features -- -D warnings
Files:
dash-spv/tests/chainlock_validation_test.rsdash-spv/examples/filter_sync.rsdash-spv/tests/wallet_integration_test.rsdash-spv/src/sync/validation_test.rsdash-spv/tests/chainlock_simple_test.rsdash-spv/tests/error_handling_test.rsdash-spv/src/client/block_processor_test.rsdash-spv/src/client/config_test.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-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.rsdash-spv/tests/wallet_integration_test.rsdash-spv/tests/chainlock_simple_test.rsdash-spv/tests/error_handling_test.rsdash-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.rsdash-spv-ffi/tests/test_mempool_tracking.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/test_wallet.rsdash-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.rsdash-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-versionfield 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.rsdash-spv/examples/filter_sync.rsdash-spv/tests/wallet_integration_test.rsdash-spv/src/sync/validation_test.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/test_wallet.rsdash-spv/Cargo.tomldash-spv/tests/error_handling_test.rsdash-spv/src/client/block_processor_test.rsdash-spv/src/client/config_test.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-spv/src/mempool_filter.rsdash-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.rsdash-spv/tests/error_handling_test.rsdash-spv/src/client/config_test.rsdash-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.rsdash-spv-ffi/tests/test_mempool_tracking.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/test_wallet.rsdash-spv/Cargo.tomldash-spv/tests/error_handling_test.rsdash-spv/src/mempool_filter.rsdash-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.rsdash-spv/tests/wallet_integration_test.rsdash-spv/src/sync/validation_test.rsdash-spv-ffi/tests/test_wallet.rsdash-spv/Cargo.tomldash-spv/tests/error_handling_test.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-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.rsdash-spv/tests/wallet_integration_test.rsdash-spv/src/sync/validation_test.rsdash-spv-ffi/tests/test_mempool_tracking.rsdash-spv/tests/chainlock_simple_test.rsdash-spv-ffi/tests/test_wallet.rsdash-spv/Cargo.tomldash-spv/tests/error_handling_test.rsdash-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.rsdash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-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.rsdash-spv/tests/wallet_integration_test.rsdash-spv-ffi/tests/test_wallet.rsdash-spv/tests/error_handling_test.rsdash-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.rsdash-spv-ffi/tests/test_mempool_tracking.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/test_wallet.rsdash-spv/Cargo.tomldash-spv-ffi/tests/unit/test_wallet_operations.rsdash-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.rsdash-spv-ffi/tests/test_mempool_tracking.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/test_wallet.rsdash-spv/Cargo.tomldash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/unit/test_wallet_operations.rsdash-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.rsdash-spv-ffi/tests/test_mempool_tracking.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-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.rsdash-spv-ffi/tests/test_mempool_tracking.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/test_wallet.rsdash-spv-ffi/tests/unit/test_wallet_operations.rsdash-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.rsdash-spv/src/mempool_filter.rsdash-spv-ffi/tests/unit/test_wallet_operations.rsdash-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.rsdash-spv/Cargo.tomldash-spv-ffi/tests/unit/test_wallet_operations.rsdash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/tests/unit/test_wallet_operations.rsdash-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.rsdash-spv-ffi/tests/unit/test_wallet_operations.rsdash-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.rsdash-spv-ffi/tests/unit/test_wallet_operations.rsdash-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.rsdash-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
Networkhere 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
FFIUtxoviaFrom<key_wallet::Utxo>and then call the FFI destructor. This is okay only ifdash_spv_ffi_utxo_destroyis 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
ClientConfigfor pointer passing across FFI.dash-spv/src/client/config_test.rs (1)
7-7: Import cleanup LGTM.Switching to
dashcore::Networkonly is correct here.dash-spv-ffi/tests/test_mempool_tracking.rs (2)
2-2: Import narrowing LGTMRestricting import to
CStringis 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*_destroyfunction.dash-spv/tests/wallet_integration_test.rs (1)
11-11: Import cleanup LGTMUsing
dashcore::Networkdirectly is clearer.dash-spv/src/client/block_processor_test.rs (2)
22-27: MockWallet::new() signature change looks correctConstructor now aligns with the simplified mock (no Network). Call sites updated accordingly.
83-95: setup_processor(): wallet wiring LGTMSwitched 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 changesEnsure 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 LGTMCompiles 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 prematureThe repository only provides:
dash_spv_ffi_array_destroy(…)dash_spv_ffi_string_array_destroy(…)No
*_destroy_ownedvariants 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.rsand 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_destroyordash_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 forclientis correct here.
update_chainlock_validation()and subsequent calls take&self; no need formut. Looks good.
| typedef ClientConfig FFIClientConfig; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| /** | ||
| * 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); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| /** | |
| * 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")] |
There was a problem hiding this comment.
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.
There was a problem hiding this 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 || !xis 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
MockWalletindash-spv/src/mempool_filter.rscurrently defines two fields and three accessors that aren’t referenced by any active tests:
- Fields
networkandutxosare only initialized, never read.- Accessors
network(),watched_addresses(), andutxos()are never called in this file’s non-ignored tests.To keep CI green under
-D warnings, you can:
- Rename the unused fields to
_networkand_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 || truePlease run CI to verify. If you later implement real uses for
network()orutxos(), you can remove theallow(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.
📒 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 viacargo fmt --checkon all Rust source files
All code must be clippy-clean: runcargo 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.
| 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); | ||
| } |
There was a problem hiding this comment.
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\(' -SLength 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 abitcoin_hashes::Hash—> replace with.into_inner().secp256k1::SecretKey::from_byte_array(&bytes)—> replace withsecp256k1::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 otherdash/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.
| 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.
There was a problem hiding this 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 deterministicThe 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 collisionsUnscoped 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 122To 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-*-ffiheaders). You can also configure cbindgen to auto-prefix variants:[enum_style] prefix = true style = "upper_camel"– this will emit
FFIAccountTypeBip44(or adjust to exactlyFFIAccountType_BIP44via 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.awaitYou’re currently locking
innerwithinner.lock().unwrap()and immediately calling.awaiton the RwLock returned byspv_client.wallet(), which holds theMutexGuardacross the.await. This can lead to deadlocks and makes the future non-Send.Please refactor each
run_asyncclosure indash-spv-ffi/src/wallet.rsso that you:
- Acquire and immediately clone out the
spv_client(or the minimal data you need).- Drop the
MutexGuardbefore 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 requestedConstructors 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 testsIf 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 nitPreallocate 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 printlnsInside 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 generationConstructing 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 behaviorThese 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
zeroizecrate is available, consider wiping the temporaryprivate_key_bytesafter constructingSecretKeyto 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
datato 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_shato useinto_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 keephash.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 missingsha256::Hash::into_inner().This line is fine conceptually but won’t compile until
sha256::Hashimplementsinto_inner(). Prefer addinginto_inner()tosha256::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.
📒 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.rsdash/src/pow.rskey-wallet/src/derivation.rsdash/src/signer.rshashes/src/hash160.rsdash/src/sign_message.rshashes/src/sha256d.rsdash/src/sml/quorum_entry/validation.rsdash/src/bip158.rsrpc-json/src/lib.rsdash/src/sml/quorum_entry/quorum_modifier_type.rsdash/src/blockdata/constants.rshashes/src/sha512_256.rshashes/src/hash_x11.rsdash/src/merkle_tree/block.rsdash/src/crypto/key.rskey-wallet/src/wallet/mod.rskey-wallet/src/derivation_slip10.rskey-wallet/src/wallet/root_extended_keys.rskey-wallet/src/psbt/mod.rshashes/src/sha256t.rsdash/src/hash_types.rsdash/src/sml/masternode_list/peer_addresses.rshashes/src/util.rsdash/src/crypto/sighash.rsdash/src/sml/masternode_list_entry/mod.rsdash/src/sml/masternode_list_engine/message_request_verification.rsdash/src/taproot.rsdash/src/sml/masternode_list_entry/qualified_masternode_list_entry.rshashes/src/sha256.rsdash-spv/tests/filter_header_verification_test.rshashes/src/sha512.rsdash-spv-ffi/tests/unit/test_wallet_operations.rsdash-spv-ffi/src/wallet.rsdash-spv/src/mempool_filter.rsdash-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.rsdash/src/pow.rskey-wallet/src/derivation.rsdash/src/signer.rshashes/src/hash160.rsdash/src/sign_message.rshashes/src/sha256d.rsdash/src/sml/quorum_entry/validation.rsdash/src/bip158.rsrpc-json/src/lib.rsdash/src/sml/quorum_entry/quorum_modifier_type.rsdash/src/blockdata/constants.rshashes/src/sha512_256.rshashes/src/hash_x11.rsdash/src/merkle_tree/block.rsdash/src/crypto/key.rskey-wallet/src/wallet/mod.rskey-wallet/src/derivation_slip10.rskey-wallet/src/wallet/root_extended_keys.rskey-wallet/src/psbt/mod.rshashes/src/sha256t.rsdash/src/hash_types.rsdash/src/sml/masternode_list/peer_addresses.rshashes/src/util.rsdash/src/crypto/sighash.rsdash/src/sml/masternode_list_entry/mod.rsdash/src/sml/masternode_list_engine/message_request_verification.rsdash/src/taproot.rsdash/src/sml/masternode_list_entry/qualified_masternode_list_entry.rshashes/src/sha256.rsdash-spv/tests/filter_header_verification_test.rshashes/src/sha512.rsdash-spv-ffi/tests/unit/test_wallet_operations.rsdash-spv-ffi/src/wallet.rsdash-spv/src/mempool_filter.rsdash-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.rskey-wallet/src/derivation.rskey-wallet/src/wallet/mod.rskey-wallet/src/derivation_slip10.rskey-wallet/src/wallet/root_extended_keys.rskey-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.rsdash-spv/tests/filter_header_verification_test.rsdash-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.rsdash-spv-ffi/tests/unit/test_wallet_operations.rsdash-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 viacargo fmt --checkon all Rust source files
All code must be clippy-clean: runcargo clippy --all-targets --all-features -- -D warnings
Files:
dash-spv/tests/filter_header_verification_test.rsdash-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.rsdash-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.rsdash-spv-ffi/src/wallet.rsdash-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.rsdash/src/sml/masternode_list_entry/mod.rsdash/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.rskey-wallet/src/wallet/root_extended_keys.rsdash-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.rsdash-spv-ffi/tests/unit/test_wallet_operations.rsdash-spv/src/mempool_filter.rsdash-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.rsdash-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.rsdash-spv-ffi/src/wallet.rsdash-spv/src/mempool_filter.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-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.rsdash-spv-ffi/src/wallet.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-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.rsdash-spv/src/mempool_filter.rsdash-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.rsdash-spv-ffi/src/wallet.rsdash-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.rsdash-spv-ffi/src/wallet.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-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.rsdash-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.rsdash-spv-ffi/src/wallet.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-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.rsdash-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.rsdash-spv-ffi/src/wallet.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-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.rsdash-spv/src/mempool_filter.rsdash-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.rsdash-spv-ffi/src/wallet.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-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.rsdash-spv/src/mempool_filter.rsdash-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.rsdash-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.rsdash-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.rsdash-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)
dash-spv-ffi/include/dash_spv_ffi.h
Outdated
| /** | ||
| * 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; | ||
|
|
There was a problem hiding this comment.
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
| /// 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); | ||
| } | ||
|
|
There was a problem hiding this comment.
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*\)' -C2Length 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/srcLength 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 || trueLength 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.
| 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); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| // 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| // 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")] |
There was a problem hiding this comment.
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.tomlLength 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.tomlLength 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' -tmdLength 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.
hashes/src/sha256.rs
Outdated
| debug_assert_eq!(e.length % BLOCK_SIZE, 0); | ||
|
|
||
| Hash(e.midstate().to_byte_array()) | ||
| Hash(e.midstate().into_inner()) |
There was a problem hiding this comment.
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.rsLength 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.rsOnce 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.
ff95937 to
e893309
Compare
- 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
e893309 to
e1fea31
Compare
There was a problem hiding this 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: Alignsend_pingreturn type withNetworkManagertrait
Indash-spv/tests/error_handling_test.rsupdate the mock’s signature fromNetworkResult<()>toNetworkResult<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) andscript_pubkey(Box::into_raw) inwallet_utxo_to_ffi, but there’s no typed array destroyer to free those for arrays returned byget_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 withdash_spv_ffi_utxo_array_destroy.
1428-1473: Consistency: return an empty FFIArray on bad input instead of NULLOther getters return an owned empty array when inputs are NULL/invalid. Align
get_address_historyto 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
Implementdash_spv_ffi_utxo_destroy(utxo: FFIUtxo)to free all nested fields, anddash_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 genericdash_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 LayoutCurrent 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 querySame 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 removedClientConfig 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 APIExpose 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 testCompiling 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 cfgSame 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 cleanupThe 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 NULLMatch 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 abovePrefer 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.
📒 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.rsdash-spv-ffi/src/types.rsdash-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.rsdash-spv/src/sync/validation_test.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv/tests/error_handling_test.rsdash-spv-ffi/src/types.rsdash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/src/types.rsdash-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.rsdash-spv/src/sync/validation_test.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv/tests/error_handling_test.rsdash-spv-ffi/src/types.rsdash-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.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-spv/tests/error_handling_test.rs
dash-spv/**/*.rs
📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)
dash-spv/**/*.rs: Enforce Rust formatting viacargo fmt --checkon all Rust source files
All code must be clippy-clean: runcargo clippy --all-targets --all-features -- -D warnings
Files:
dash-spv/src/sync/validation_test.rsdash-spv/tests/edge_case_filter_sync_test.rsdash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-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.rsdash-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.rsdash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/src/types.rsdash-spv-ffi/src/client.rsdash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/src/types.rsdash-spv-ffi/src/client.rsdash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/src/types.rsdash-spv-ffi/src/client.rsdash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/src/types.rsdash-spv-ffi/src/client.rsdash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-spv-ffi/src/types.rsdash-spv-ffi/src/client.rsdash-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.rsdash-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.rsdash-spv-ffi/tests/unit/test_error_handling.rsdash-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.rsdash-spv/tests/error_handling_test.rsdash-spv-ffi/src/types.rsdash-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.rsdash-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.rsdash-spv-ffi/src/types.rsdash-spv-ffi/src/client.rsdash-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.rsdash-spv-ffi/src/types.rsdash-spv-ffi/src/client.rsdash-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.rsdash-spv-ffi/src/types.rsdash-spv-ffi/src/client.rsdash-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.rsdash-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.rsdash-spv-ffi/src/types.rsdash-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.rsdash-spv-ffi/src/client.rsdash-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.rsdash-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.rsdash-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.rsdash-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 usessend_message
FilterSyncManager invokesnetwork.send_message(see dash-spv/src/sync/sequential/mod.rs:1841 and dash-spv/src/sync/filters.rs:903), soMockNetworkManager.send_messagewill populatesent_messagescorrectly—no need to interceptget_message_sender.Likely an incorrect or invalid review comment.
dash-spv-ffi/src/types.rs (2)
17-18: Good: string length derived from finalized CStringPrevents length/NUL mismatches.
292-294: Good: record element size/alignment in FFIArrayThis 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 testsCalling 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 hexClear error reporting on invalid hex or too-short scripts.
Also applies to: 1400-1402
616-657: Good: switch to tracing and clearer sync diagnosticsNon-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 arraysCallers 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 managementLearnt 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 boundariesLearnt 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_freeLearnt 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 borrowLearnt 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_freeLearnt 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)
| #[repr(transparent)] | ||
| pub struct FFIClientConfig { | ||
| inner: ClientConfig, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| #[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.
| /// 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); | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
| /// 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
There was a problem hiding this 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, andAmount.-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, andinitial_utxoare 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 unusedpkto 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 unusedpk.- 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 ignoresFile-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 cleanNetwork 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_managerThese 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 usedAlso applies to: 29-29
525-531: Avoid returning a fresh channel per get_message_sender() callCreating 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 importskey-walletandkey-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.
📒 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.rsdash-spv/tests/instantsend_integration_test.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/filter_header_verification_test.rsdash-spv/tests/block_download_test.rsdash-spv-ffi/src/wallet.rsdash-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.rsdash-spv/tests/instantsend_integration_test.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/filter_header_verification_test.rsdash-spv/tests/block_download_test.rsdash-spv-ffi/src/wallet.rsdash-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.rsdash-spv/tests/instantsend_integration_test.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/filter_header_verification_test.rsdash-spv/tests/block_download_test.rsdash-spv/tests/error_recovery_integration_test.rs
dash-spv/**/*.rs
📄 CodeRabbit inference engine (dash-spv/CLAUDE.md)
dash-spv/**/*.rs: Enforce Rust formatting viacargo fmt --checkon all Rust source files
All code must be clippy-clean: runcargo clippy --all-targets --all-features -- -D warnings
Files:
dash-spv/src/error.rsdash-spv/tests/instantsend_integration_test.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/filter_header_verification_test.rsdash-spv/tests/block_download_test.rsdash-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.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/filter_header_verification_test.rsdash-spv/tests/block_download_test.rsdash-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.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/filter_header_verification_test.rsdash-spv/tests/block_download_test.rsdash-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.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/filter_header_verification_test.rsdash-spv/tests/block_download_test.rsdash-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.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-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.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-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.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-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.rsdash-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.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/filter_header_verification_test.rsdash-spv/tests/block_download_test.rsdash-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.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/filter_header_verification_test.rsdash-spv/tests/block_download_test.rsdash-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.rsdash-spv/tests/cfheader_gap_test.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/block_download_test.rsdash-spv-ffi/src/wallet.rsdash-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.rsdash-spv/tests/error_handling_test.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/filter_header_verification_test.rsdash-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.rsdash-spv/tests/chainlock_validation_test.rsdash-spv/tests/block_download_test.rsdash-spv/tests/error_recovery_integration_test.rsdash-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.rsdash-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.rsdash-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.rsdash-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.rsdash-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.rsdash-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.rsdash-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.rsdash-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)
| 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() | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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*.
| #[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() | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| network::{MultiPeerNetworkManager, NetworkManager}, | ||
| storage::{MemoryStorageManager, StorageManager}, | ||
| sync::filters::FilterSyncManager, | ||
| }; |
There was a problem hiding this comment.
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.
| // Note: current_height method doesn't exist on SyncPhase | ||
| // assert!(restart_height < phase.current_height()); | ||
| eprintln!( |
There was a problem hiding this comment.
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.
| // 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.
| // 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(); | ||
|
|
There was a problem hiding this comment.
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.
| // 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()'
There was a problem hiding this 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.
📒 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: Addrust-version = "1.89"to dash-spv-ffi/Cargo.toml
Thedash-spv-ffi/Cargo.tomllacks arust-versionfield; please addrust-version = "1.89"to match the MSRV declared in
dash-spv/Cargo.tomland 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.89Learnt 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 functionLearnt 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 filesLearnt 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 boundariesLearnt 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] |
There was a problem hiding this comment.
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, orhandle_errorare also annotated with#[serial]. - Add a final cleanup to
test_handle_errorto 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/testsLength of output: 27741
Enforce hermetic cleanup and serialize all error-API tests
- Add
dash_spv_ffi_clear_error();at the end oftest_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 callsset_last_error,clear_last_error/dash_spv_ffi_clear_error,dash_spv_ffi_get_last_errororhandle_errorwith#[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.
8c84caf to
4529dcd
Compare
Summary by CodeRabbit
New Features
Refactor
Tests
Chores