diff --git a/.gitignore b/.gitignore index 62f41ed..7ec88ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ -/target +target +build +Cargo.lock *.h -/main -/Cargo.lock +main +/local.properties +.gradle +wallet_db diff --git a/Cargo.toml b/Cargo.toml index 3fab519..98e4d9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -crate-type = ["staticlib"] +crate-type = ["cdylib"] [dependencies] bdk = { version = "^0.7", features = ["all-keys"] } diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..7286dea --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +buildscript { + ext.kotlin_version = '1.5.10' + repositories { + //google() + mavenCentral() + } + dependencies { + //classpath 'com.android.tools.build:gradle:3.6.4' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + //google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/build.sh b/build.sh index 0cd81cf..d80153a 100755 --- a/build.sh +++ b/build.sh @@ -1,3 +1,9 @@ cargo build cargo test --features c-headers -- generate_headers -cc main.c -o main -L target/debug -l bdk_ffi -l pthread -l dl -l m \ No newline at end of file +cc main.c -o main -L target/debug -l bdk_ffi -l pthread -l dl -l m +./main + +# jvm +mkdir -p jvm/build/jniLibs/x86_64_linux +cp target/debug/libbdk_ffi.so jvm/build/jniLibs/x86_64_linux +export LD_LIBRARY_PATH=`pwd`/target/debug diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..23339e0 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/jvm/build.gradle b/jvm/build.gradle new file mode 100644 index 0000000..c9ab9a8 --- /dev/null +++ b/jvm/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' // version '1.3.71' + id 'java-library' + id 'maven-publish' +} + +test { + systemProperty "java.library.path", file("${buildDir}/jniLibs/x86_64_linux").absolutePath + environment "LD_LIBRARY_PATH", file("${buildDir}/jniLibs/x86_64_linux").absolutePath +// testLogging { +// events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" +// } +} + +task buildRust(type: Exec) { + workingDir '../' + commandLine './build.sh' +} + +dependencies { + implementation platform('org.jetbrains.kotlin:kotlin-bom') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.10" + implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.+" + implementation "net.java.dev.jna:jna:5.8.0" + + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.jetbrains.kotlin:kotlin-test-junit' +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'org.bitcoindevkit.bdkjni' + artifactId = 'bdk-jvm-debug' + version = '0.2.1-dev' + + from components.java + } + } +} \ No newline at end of file diff --git a/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt b/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt new file mode 100644 index 0000000..aef6e69 --- /dev/null +++ b/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt @@ -0,0 +1,43 @@ +package org.bitcoindevkit.bdkjni + +import com.sun.jna.Native +import com.sun.jna.NativeLong +import org.junit.Test + +/** + * Library test, which will execute on linux host. + * + */ +class LibTest { + + private val lib: Lib = Native.load("bdk_ffi", Lib::class.java) + + @Test + fun print_string() { + lib.print_string("hello print string") + } + + @Test + fun concat_print_free_string() { + val concat = lib.concat_string("hello", "concat") + lib.print_string(concat) + lib.free_string(concat) + } + + @Test + fun print_free_config() { + val config = Config_t() + config.name = "test" + config.count = NativeLong(101) + lib.print_config(config) + lib.free_config(config) + } + + @Test + fun new_print_free_config() { + println("Long max value = ${Long.MAX_VALUE}") + val config = lib.new_config("test test", NativeLong(Long.MAX_VALUE)) + lib.print_config(config) + lib.free_config(config) + } +} diff --git a/main.c b/main.c index 33522fd..6d133e4 100644 --- a/main.c +++ b/main.c @@ -4,40 +4,52 @@ int main (int argc, char const * const argv[]) { - + // test print_string + print_string("hello 123"); + + // test concat_string char const * string1 = "string1"; char const * string2 = "string2"; char * string3 = concat_string(string1, string2); - - print_string(string3); print_string(string3); free_string(string3); - //free_string(string3); - - //Point_t a = new_point(84,45); - //Point_t b = new_point(0,39); - //Point_t m = mid_point(a, b); - //print_point(m); + // verify free_string after free_string fails + ////free_string(string3); - char const * name = "test_wallet"; - char const * desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; - char const * change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; + // test print_config with c created config + Config_t config1 = { .name = "test", .count = 101 }; + print_config(&config1); - //printf("wallet name: %s\n", name); - //printf("descriptor: %s\n", desc); - //printf("change descriptor: %s\n", change); - WalletPtr_t * wallet = new_wallet(name, desc, change); + // test new_config + Config_t * config2 = new_config("test test", 202); + print_config(config2); - sync_wallet(&wallet); - sync_wallet(&wallet); + // test free_config + free_config(config2); + // verify print_config after free_config fails (invalid data) + ////print_config(config2); + // verify free_config after free_config fails (double free detected, core dumped) + ////free_config(config2); - char const * address1 = new_address(&wallet); - printf("address1: %s\n", address1); - char const * address2 = new_address(&wallet); - printf("address: %s\n", address2); + //char const * name = "test_wallet"; + //char const * desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; + //char const * change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; + + ////printf("wallet name: %s\n", name); + ////printf("descriptor: %s\n", desc); + ////printf("change descriptor: %s\n", change); + //WalletPtr_t * wallet = new_wallet(name, desc, change); + + //sync_wallet(&wallet); + //sync_wallet(&wallet); + + //char const * address1 = new_address(&wallet); + //printf("address1: %s\n", address1); + //char const * address2 = new_address(&wallet); + //printf("address: %s\n", address2); //free_wallet(wallet); - //sync_wallet(&wallet); + ////sync_wallet(&wallet); return EXIT_SUCCESS; } diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..f14b0a7 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'bdk_ffi' + +include 'jvm' diff --git a/src/lib.rs b/src/lib.rs index fd4c76a..714fe9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,11 +11,17 @@ use bdk::wallet::AddressIndex::New; use safer_ffi::char_p::{char_p_ref, char_p_boxed}; use safer_ffi::boxed::Box; +#[ffi_export] +fn print_string (string: char_p_ref) +{ + println!("{}", string); +} + /// Concatenate two input UTF-8 (_e.g._, ASCII) strings. /// -/// \remark The returned string must be freed with `rust_free_string` +/// The returned string must be freed with `rust_free_string` #[ffi_export] -fn concat_string<'a>(fst: char_p_ref<'a>, snd: char_p_ref<'a>) +fn concat_string(fst: char_p_ref, snd: char_p_ref) -> char_p_boxed { let fst = fst.to_str(); // : &'_ str @@ -24,13 +30,7 @@ fn concat_string<'a>(fst: char_p_ref<'a>, snd: char_p_ref<'a>) ccat } -#[ffi_export] -fn print_string (string: char_p_ref) -{ - println!("{}", string); -} - -/// Frees a Rust-allocated string. +/// Frees a Rust-allocated string #[ffi_export] fn free_string (string: char_p_boxed) { @@ -40,105 +40,97 @@ fn free_string (string: char_p_boxed) /// A `struct` usable from both Rust and C #[derive_ReprC] #[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct Point { - x: f64, - y: f64, +#[derive(Debug, Clone)] +pub struct Config { + name: char_p_boxed, + count: i64 } -/* Export a Rust function to the C world. */ -/// Returns the middle point of `[a, b]`. +/// Debug print a Point #[ffi_export] -fn mid_point(a: Point, b: Point) -> Point { - Point { - x: (a.x + b.x) / 2., - y: (a.y + b.y) / 2., - } +fn print_config(config: &Config) { + println!("{:?}", config); } -/// Pretty-prints a point using Rust's formatting logic. +/// Create a new Config #[ffi_export] -fn print_point(point: Point) { - println!("{:?}", point); +fn new_config(name: char_p_ref, count: i64) -> Box { + let name = name.to_string().try_into().unwrap(); + Box::new(Config { name, count }) } #[ffi_export] -fn new_point(x: f64, y: f64) -> Point { - Point { x, y } +fn free_config(config: Box) { + drop(config) } -//#[ffi_export] -//fn free_point(point: Point) { -// drop(point) +//#[derive_ReprC] +//#[ReprC::opaque] +//pub struct WalletPtr { +// raw: Wallet, //} -#[derive_ReprC] -#[ReprC::opaque] -pub struct WalletPtr { - raw: Wallet, -} +//impl From> for WalletPtr { +// fn from(wallet: Wallet) -> Self { +// WalletPtr { +// raw: wallet, +// } +// } +//} -impl From> for WalletPtr { - fn from(wallet: Wallet) -> Self { - WalletPtr { - raw: wallet, - } - } -} +//#[ffi_export] +//fn new_wallet<'a>( +// name: char_p_ref<'a>, +// descriptor: char_p_ref<'a>, +// change_descriptor: Option>, +//) -> Box { +// let name = name.to_string(); +// let descriptor = descriptor.to_string(); +// let change_descriptor = change_descriptor.map(|s| s.to_string()); +// +// let database = sled::open("./wallet_db").unwrap(); +// let tree = database.open_tree(name.clone()).unwrap(); +// +// let descriptor: &str = descriptor.as_str(); +// let change_descriptor: Option<&str> = change_descriptor.as_deref(); +// +// let electrum_url = "ssl://electrum.blockstream.info:60002"; +// let client = Client::new(&electrum_url).unwrap(); +// +// let wallet = Wallet::new( +// descriptor, +// change_descriptor, +// Testnet, +// tree, +// ElectrumBlockchain::from(client), +// ) +// .unwrap(); +// +// Box::new(WalletPtr::from(wallet)) +//} -#[ffi_export] -fn new_wallet<'a>( - name: char_p_ref<'a>, - descriptor: char_p_ref<'a>, - change_descriptor: Option>, -) -> Box { - let name = name.to_string(); - let descriptor = descriptor.to_string(); - let change_descriptor = change_descriptor.map(|s| s.to_string()); +//#[ffi_export] +//fn sync_wallet( wallet: &Box) { +// println!("before sync"); +// let _r = wallet.raw.sync(log_progress(), Some(100)); +// println!("after sync"); +//} - let database = sled::open("./wallet_db").unwrap(); - let tree = database.open_tree(name.clone()).unwrap(); +//#[ffi_export] +//fn new_address( wallet: &Box) -> char_p_boxed { +// println!("before new_address"); +// let new_address = wallet.raw.get_address(New); +// println!("after new_address: {:?}", new_address); +// let new_address = new_address.unwrap(); +// let new_address = new_address.to_string(); +// println!("new address: ${}", new_address); +// new_address.try_into().unwrap() +//} - let descriptor: &str = descriptor.as_str(); - let change_descriptor: Option<&str> = change_descriptor.as_deref(); - - let electrum_url = "ssl://electrum.blockstream.info:60002"; - let client = Client::new(&electrum_url).unwrap(); - - let wallet = Wallet::new( - descriptor, - change_descriptor, - Testnet, - tree, - ElectrumBlockchain::from(client), - ) - .unwrap(); - - Box::new(WalletPtr::from(wallet)) -} - -#[ffi_export] -fn sync_wallet( wallet: &Box) { - println!("before sync"); - let _r = wallet.raw.sync(log_progress(), Some(100)); - println!("after sync"); -} - -#[ffi_export] -fn new_address( wallet: &Box) -> char_p_boxed { - println!("before new_address"); - let new_address = wallet.raw.get_address(New); - println!("after new_address: {:?}", new_address); - let new_address = new_address.unwrap(); - let new_address = new_address.to_string(); - println!("new address: ${}", new_address); - new_address.try_into().unwrap() -} - -#[ffi_export] -fn free_wallet( wallet: Box) { - drop(wallet) -} +//#[ffi_export] +//fn free_wallet( wallet: Box) { +// drop(wallet) +//} /// The following test function is necessary for the header generation. #[::safer_ffi::cfg_headers]