Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["dash", "dash-network", "dash-network-ffi", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-ffi", "dash-spv"]
members = ["dash", "dash-network", "dash-network-ffi", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-ffi", "dash-spv", "dash-spv-ffi"]
resolver = "2"

[workspace.package]
Expand Down
4 changes: 2 additions & 2 deletions dash-network-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ mod tests {
assert_eq!(devnet_info.magic(), 0xCEFFCAE2);

let regtest_info = NetworkInfo::new(Network::Regtest);
assert_eq!(regtest_info.magic(), 0xDAB5BFFA);
assert_eq!(regtest_info.magic(), 0xDCB7C1FC);
}

#[test]
Expand All @@ -132,7 +132,7 @@ mod tests {
assert!(NetworkInfo::from_magic(0xBD6B0CBF).is_ok());
assert!(NetworkInfo::from_magic(0xFFCAE2CE).is_ok());
assert!(NetworkInfo::from_magic(0xCEFFCAE2).is_ok());
assert!(NetworkInfo::from_magic(0xDAB5BFFA).is_ok());
assert!(NetworkInfo::from_magic(0xDCB7C1FC).is_ok());

// Invalid magic bytes
assert!(matches!(NetworkInfo::from_magic(0x12345678), Err(NetworkError::InvalidMagic)));
Expand Down
6 changes: 3 additions & 3 deletions dash-network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl Network {
0xBD6B0CBF => Some(Network::Dash),
0xFFCAE2CE => Some(Network::Testnet),
0xCEFFCAE2 => Some(Network::Devnet),
0xDAB5BFFA => Some(Network::Regtest),
0xDCB7C1FC => Some(Network::Regtest),
_ => None,
}
}
Expand Down Expand Up @@ -114,15 +114,15 @@ mod tests {
assert_eq!(Network::Dash.magic(), 0xBD6B0CBF);
assert_eq!(Network::Testnet.magic(), 0xFFCAE2CE);
assert_eq!(Network::Devnet.magic(), 0xCEFFCAE2);
assert_eq!(Network::Regtest.magic(), 0xDAB5BFFA);
assert_eq!(Network::Regtest.magic(), 0xDCB7C1FC);
}

#[test]
fn test_network_from_magic() {
assert_eq!(Network::from_magic(0xBD6B0CBF), Some(Network::Dash));
assert_eq!(Network::from_magic(0xFFCAE2CE), Some(Network::Testnet));
assert_eq!(Network::from_magic(0xCEFFCAE2), Some(Network::Devnet));
assert_eq!(Network::from_magic(0xDAB5BFFA), Some(Network::Regtest));
assert_eq!(Network::from_magic(0xDCB7C1FC), Some(Network::Regtest));
assert_eq!(Network::from_magic(0x12345678), None);
}

Expand Down
31 changes: 31 additions & 0 deletions dash-spv-ffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "dash-spv-ffi"
version = "0.1.0"
edition = "2021"
authors = ["Dash Core Developers"]
license = "MIT"
description = "FFI bindings for the Dash SPV client"
repository = "https://github.com/dashpay/rust-dashcore"

[lib]
name = "dash_spv_ffi"
crate-type = ["cdylib", "staticlib", "rlib"]

Comment thread
QuantumExplorer marked this conversation as resolved.
Resolved
[dependencies]
dash-spv = { path = "../dash-spv" }
dashcore = { path = "../dash", package = "dashcore" }
libc = "0.2"
once_cell = "1.19"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
log = "0.4"
hex = "0.4"

[dev-dependencies]
tempfile = "3.8"
serial_test = "3.0"
env_logger = "0.10"

[build-dependencies]
cbindgen = "0.26"
120 changes: 120 additions & 0 deletions dash-spv-ffi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Dash SPV FFI

This crate provides C-compatible FFI bindings for the Dash SPV client library.

## Features

- Complete FFI wrapper for DashSpvClient
- Configuration management
- Wallet operations (watch addresses, balance queries, UTXO management)
- Async operation support via callbacks
- Comprehensive error handling
- Memory-safe abstractions

## Building

```bash
cargo build --release
```

This will generate:
- Static library: `target/release/libdash_spv_ffi.a`
- Dynamic library: `target/release/libdash_spv_ffi.so` (or `.dylib` on macOS)
- C header: `include/dash_spv_ffi.h`

## Usage

See `examples/basic_usage.c` for a simple example of using the FFI bindings.

### Basic Example

```c
#include "dash_spv_ffi.h"

// Initialize logging
dash_spv_ffi_init_logging("info");

// Create configuration
FFIClientConfig* config = dash_spv_ffi_config_testnet();
dash_spv_ffi_config_set_data_dir(config, "/path/to/data");

// Create client
FFIDashSpvClient* client = dash_spv_ffi_client_new(config);
if (client == NULL) {
const char* error = dash_spv_ffi_get_last_error();
// Handle error
}

// Start the client
if (dash_spv_ffi_client_start(client) != 0) {
// Handle error
}

// ... use the client ...

// Clean up
dash_spv_ffi_client_destroy(client);
dash_spv_ffi_config_destroy(config);
```

## API Documentation

### Configuration

- `dash_spv_ffi_config_new(network)` - Create new config
- `dash_spv_ffi_config_mainnet()` - Create mainnet config
- `dash_spv_ffi_config_testnet()` - Create testnet config
- `dash_spv_ffi_config_set_data_dir(config, path)` - Set data directory
- `dash_spv_ffi_config_set_validation_mode(config, mode)` - Set validation mode
- `dash_spv_ffi_config_set_max_peers(config, max)` - Set maximum peers
- `dash_spv_ffi_config_add_peer(config, addr)` - Add a peer address
- `dash_spv_ffi_config_destroy(config)` - Free config memory

### Client Operations

- `dash_spv_ffi_client_new(config)` - Create new client
- `dash_spv_ffi_client_start(client)` - Start the client
- `dash_spv_ffi_client_stop(client)` - Stop the client
- `dash_spv_ffi_client_sync_to_tip(client, callbacks)` - Sync to chain tip
- `dash_spv_ffi_client_get_sync_progress(client)` - Get sync progress
- `dash_spv_ffi_client_get_stats(client)` - Get client statistics
- `dash_spv_ffi_client_destroy(client)` - Free client memory

### Wallet Operations

- `dash_spv_ffi_client_add_watch_item(client, item)` - Add address/script to watch
- `dash_spv_ffi_client_remove_watch_item(client, item)` - Remove watch item
- `dash_spv_ffi_client_get_address_balance(client, address)` - Get address balance
- `dash_spv_ffi_client_get_utxos(client)` - Get all UTXOs
- `dash_spv_ffi_client_get_utxos_for_address(client, address)` - Get UTXOs for address

### Watch Items

- `dash_spv_ffi_watch_item_address(address)` - Create address watch item
- `dash_spv_ffi_watch_item_script(script_hex)` - Create script watch item
- `dash_spv_ffi_watch_item_outpoint(txid, vout)` - Create outpoint watch item
- `dash_spv_ffi_watch_item_destroy(item)` - Free watch item memory

### Error Handling

- `dash_spv_ffi_get_last_error()` - Get last error message
- `dash_spv_ffi_clear_error()` - Clear last error

### Memory Management

All created objects must be explicitly destroyed:
- Config: `dash_spv_ffi_config_destroy()`
- Client: `dash_spv_ffi_client_destroy()`
- Progress: `dash_spv_ffi_sync_progress_destroy()`
- Stats: `dash_spv_ffi_spv_stats_destroy()`
- Balance: `dash_spv_ffi_balance_destroy()`
- Arrays: `dash_spv_ffi_array_destroy()`
- Strings: `dash_spv_ffi_string_destroy()`

## Thread Safety

The FFI bindings are thread-safe. The client uses internal synchronization to ensure safe concurrent access.

## License

MIT
19 changes: 19 additions & 0 deletions dash-spv-ffi/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::env;
use std::path::PathBuf;

fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let output_path = PathBuf::from(&crate_dir).join("include");

std::fs::create_dir_all(&output_path).unwrap();

let config = cbindgen::Config::default();

cbindgen::Builder::new()
.with_crate(crate_dir)
.with_config(config)
.with_language(cbindgen::Language::C)
.generate()
.expect("Unable to generate bindings")
.write_to_file(output_path.join("dash_spv_ffi.h"));
}
37 changes: 37 additions & 0 deletions dash-spv-ffi/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# cbindgen configuration for dash-spv-ffi

language = "C"
header = "/* dash-spv-ffi C bindings - Auto-generated by cbindgen */"
include_guard = "DASH_SPV_FFI_H"
autogen_warning = "/* Warning: This file is auto-generated by cbindgen. Do not modify manually. */"
include_version = true
namespace = "dash_spv_ffi"
cpp_compat = true

[export]
include = ["FFI"]
exclude = []
prefix = "dash_spv_ffi_"

[export.rename]
"FFINetwork" = "DashSpvNetwork"
"FFIValidationMode" = "DashSpvValidationMode"
"FFIErrorCode" = "DashSpvErrorCode"
"FFIWatchItemType" = "DashSpvWatchItemType"

[fn]
prefix = ""
postfix = ""

[struct]
rename_fields = "None"

[enum]
rename_variants = "None"

[parse]
parse_deps = false
include = []

[macro_expansion]
bitflags = false
42 changes: 42 additions & 0 deletions dash-spv-ffi/examples/basic_usage.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <stdio.h>
#include <stdlib.h>
#include "../include/dash_spv_ffi.h"

int main() {
// Initialize logging
if (dash_spv_ffi_init_logging("info") != 0) {
fprintf(stderr, "Failed to initialize logging\n");
return 1;
}

// Create a configuration for testnet
FFIClientConfig* config = dash_spv_ffi_config_testnet();
if (config == NULL) {
fprintf(stderr, "Failed to create config\n");
return 1;
}

// Set data directory
if (dash_spv_ffi_config_set_data_dir(config, "/tmp/dash-spv-test") != 0) {
fprintf(stderr, "Failed to set data dir\n");
dash_spv_ffi_config_destroy(config);
return 1;
}

// Create the client
FFIDashSpvClient* client = dash_spv_ffi_client_new(config);
if (client == NULL) {
const char* error = dash_spv_ffi_get_last_error();
fprintf(stderr, "Failed to create client: %s\n", error);
dash_spv_ffi_config_destroy(config);
return 1;
}

printf("Successfully created Dash SPV client!\n");

// Clean up
dash_spv_ffi_client_destroy(client);
dash_spv_ffi_config_destroy(config);

return 0;
}
Loading