diff --git a/bdk_ffi_test.c b/bdk_ffi_test.c index 7c2d492..83ed17d 100644 --- a/bdk_ffi_test.c +++ b/bdk_ffi_test.c @@ -1,65 +1,36 @@ #include #include #include +#include #include "bdk_ffi.h" 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); - free_string(string3); - // verify free_string after free_string fails - ////free_string(string3); - - // test print_config with c created config - Config_t config1 = { .name = "test", .count = 101 }; - print_config(&config1); - - // test new_config - Config_t * config2 = new_config("test test", 202); - print_config(config2); - - // 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 * name = "test_wallet"; char const * desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; char const * change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; - //char const * change = NULL; // test new_wallet { WalletPtr_t * wallet = new_wallet(name, desc, change); + assert(wallet != NULL); // test sync_wallet - sync_wallet(wallet); - printf("after sync_wallet\n"); - sync_wallet(wallet); - printf("after sync_wallet\n"); + sync_wallet(wallet); // test new_address char * address1 = new_address(wallet); - printf("address1: %s\n", address1); + //printf("address1: %s\n", address1); + assert( 0 == strcmp(address1,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp")); free_string(address1); - assert(address1 != NULL); + char * address2 = new_address(wallet); - printf("address2: %s\n", address2); - assert(address2 != NULL); + //printf("address2: %s\n", address2); + assert(0 == strcmp(address2,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf")); free_string(address2); // test free_wallet free_wallet(wallet); - printf("after free_wallet\n"); // test free_wallet NULL doesn't crash free_wallet(NULL); diff --git a/jvm/build.gradle b/jvm/build.gradle index f06e84c..e544c88 100644 --- a/jvm/build.gradle +++ b/jvm/build.gradle @@ -30,7 +30,7 @@ dependencies { publishing { publications { maven(MavenPublication) { - groupId = 'org.bitcoindevkit.bdkjni' + groupId = 'org.bitcoindevkit.bdkffi' artifactId = 'bdk-jvm-debug' version = '0.2.1-dev' diff --git a/jvm/src/main/java/org/bitcoindevkit/bdkffi/Lib.kt b/jvm/src/main/java/org/bitcoindevkit/bdkffi/Lib.kt new file mode 100644 index 0000000..0be82d5 --- /dev/null +++ b/jvm/src/main/java/org/bitcoindevkit/bdkffi/Lib.kt @@ -0,0 +1,36 @@ +package org.bitcoindevkit.bdkjni + +import com.sun.jna.* +import com.sun.jna.ptr.PointerByReference + +interface Lib : Library { + + // typedef struct WalletPtr WalletPtr_t; + class WalletPtr_t : PointerType { + constructor(): super() + constructor(pointer: Pointer): super(pointer) + } + + // void free_string ( + // char * string); + fun free_string(string: String) + + // WalletPtr_t * new_wallet ( + // char const * name, + // char const * descriptor, + // char const * change_descriptor); + fun new_wallet(name: String, descriptor: String, changeDescriptor: String?): WalletPtr_t + + // void sync_wallet ( + // WalletPtr_t * const * wallet); + //fun sync_wallet(wallet: WalletPtr_t) + fun sync_wallet(wallet: WalletPtr_t) + + // char * new_address ( + // WalletPtr_t * const * wallet); + fun new_address(wallet: WalletPtr_t): String + + // void free_wallet ( + // WalletPtr_t * wallet); + fun free_wallet(wallet: WalletPtr_t) +} diff --git a/jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt b/jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt deleted file mode 100644 index 033a42f..0000000 --- a/jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt +++ /dev/null @@ -1,73 +0,0 @@ -package org.bitcoindevkit.bdkjni - -import com.sun.jna.* -import com.sun.jna.ptr.PointerByReference - -// typedef struct { -// -// char * name; -// -// int32_t count; -// -// } Config_t; -@Structure.FieldOrder("name", "count") -class Config_t : Structure() { - @JvmField - var name: String? = null - @JvmField - var count: NativeLong? = null -} - -// typedef struct WalletPtr WalletPtr_t; -class WalletPtr_t : PointerType { - constructor(): super() - constructor(pointer: Pointer): super(pointer) -} - -interface Lib : Library { - - // void print_string ( - // char const * string); - fun print_string(name: String) - - // char * concat_string ( - // char const * fst, - // char const * snd); - fun concat_string(fst: String, snd: String): String - - // void free_string ( - // char * string); - fun free_string(string: String) - - // void print_config ( - // Config_t const * config); - fun print_config(config: Config_t) - - // Config_t new_config ( - // char * name, - // int32_t count); - fun new_config(name: String, count: NativeLong): Config_t - - // void free_config ( - // Config_t * config); - fun free_config(config: Config_t) - - // WalletPtr_t * new_wallet ( - // char const * name, - // char const * descriptor, - // char const * change_descriptor); - fun new_wallet(name: String, descriptor: String, changeDescriptor: String?): WalletPtr_t - - // void sync_wallet ( - // WalletPtr_t * const * wallet); - //fun sync_wallet(wallet: WalletPtr_t) - fun sync_wallet(wallet: WalletPtr_t) - - // char * new_address ( - // WalletPtr_t * const * wallet); - fun new_address(wallet: WalletPtr_t): String - - // void free_wallet ( - // WalletPtr_t * wallet); - fun free_wallet(wallet: WalletPtr_t) -} diff --git a/jvm/src/test/java/org/bitcoindevkit/bdkffi/LibTest.kt b/jvm/src/test/java/org/bitcoindevkit/bdkffi/LibTest.kt new file mode 100644 index 0000000..cf8b469 --- /dev/null +++ b/jvm/src/test/java/org/bitcoindevkit/bdkffi/LibTest.kt @@ -0,0 +1,40 @@ +package org.bitcoindevkit.bdkjni + +import com.sun.jna.Native +import com.sun.jna.NativeLong +import org.junit.Test +import kotlin.test.assertEquals + +/** + * Library test, which will execute on linux host. + * + */ +class LibTest { + + private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) + + @Test + fun new_sync_free_wallet() { + val name = "test_wallet" + val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" + + val wallet = bdkFfi.new_wallet(name, desc, change) + bdkFfi.sync_wallet(wallet) + bdkFfi.free_wallet(wallet) + } + + @Test + fun new_newaddress_wallet() { + val name = "test_wallet" + val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" + + val wallet = bdkFfi.new_wallet(name, desc, change) + val address = bdkFfi.new_address(wallet) + //println("address created from kotlin: $address") + assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") + bdkFfi.free_string(address) + bdkFfi.free_wallet(wallet) + } +} diff --git a/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt b/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt deleted file mode 100644 index 3c32f94..0000000 --- a/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -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) - } - - @Test - fun new_sync_free_wallet() { - val name = "test_wallet" - val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" - - val wallet = lib.new_wallet(name, desc, change) - lib.sync_wallet(wallet) - lib.free_wallet(wallet) - } - - @Test - fun new_newaddress_wallet() { - val name = "test_wallet" - val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" - - val wallet = lib.new_wallet(name, desc, change) - val address = lib.new_address(wallet) - println("address created from kotlin: $address") - lib.free_wallet(wallet) - } -} diff --git a/src/lib.rs b/src/lib.rs index 7776e47..75143b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,131 +1,6 @@ #![deny(unsafe_code)] /* No `unsafe` needed! */ -use ::safer_ffi::prelude::*; -use bdk::bitcoin::network::constants::Network::Testnet; -use bdk::blockchain::{ElectrumBlockchain, log_progress}; -use bdk::electrum_client::Client; -use bdk::sled; -use bdk::sled::Tree; -use bdk::Wallet; -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. -/// -/// The returned string must be freed with `rust_free_string` -#[ffi_export] -fn concat_string(fst: char_p_ref, snd: char_p_ref) - -> char_p_boxed -{ - let fst = fst.to_str(); // : &'_ str - let snd = snd.to_str(); // : &'_ str - let ccat = format!("{}{}", fst, snd).try_into().unwrap(); - ccat -} - -/// Frees a Rust-allocated string -#[ffi_export] -fn free_string (string: char_p_boxed) -{ - drop(string) -} - -/// A `struct` usable from both Rust and C -#[derive_ReprC] -#[repr(C)] -#[derive(Debug, Clone)] -pub struct Config { - name: char_p_boxed, - count: i64 -} - -/// Debug print a Point -#[ffi_export] -fn print_config(config: &Config) { - println!("{:?}", config); -} - -/// Create a new Config -#[ffi_export] -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 free_config(config: Box) { - drop(config) -} - -#[derive_ReprC] -#[ReprC::opaque] -pub struct WalletPtr { - raw: Wallet, -} - -impl From> for WalletPtr { - fn from(wallet: Wallet) -> Self { - WalletPtr { - raw: wallet, - } - } -} - -#[ffi_export] -fn new_wallet( - name: char_p_ref, - descriptor: char_p_ref, - 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(); - println!("created wallet"); - Box::new(WalletPtr::from(wallet)) -} - -#[ffi_export] -fn sync_wallet( wallet: &WalletPtr) { - let _r = wallet.raw.sync(log_progress(), Some(100)); -} - -#[ffi_export] -fn new_address( wallet: &WalletPtr) -> char_p_boxed { - let new_address = wallet.raw.get_address(New); - let new_address = new_address.unwrap(); - let new_address = new_address.to_string(); - new_address.try_into().unwrap() -} - -#[ffi_export] -fn free_wallet( wallet: Option>) { - drop(wallet) -} +mod wallet; /// The following test function is necessary for the header generation. #[::safer_ffi::cfg_headers] diff --git a/src/wallet.rs b/src/wallet.rs new file mode 100644 index 0000000..1a9156b --- /dev/null +++ b/src/wallet.rs @@ -0,0 +1,78 @@ +use ::safer_ffi::prelude::*; +use bdk::bitcoin::network::constants::Network::Testnet; +use bdk::blockchain::{ + log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain, + ElectrumBlockchainConfig, +}; +use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase}; +use bdk::wallet::AddressIndex::New; +use bdk::Wallet; +use safer_ffi::boxed::Box; +use safer_ffi::char_p::{char_p_boxed, char_p_ref}; + +#[derive_ReprC] +#[ReprC::opaque] +pub struct WalletPtr { + raw: Wallet, +} + +impl From> for WalletPtr { + fn from(wallet: Wallet) -> Self { + WalletPtr { raw: wallet } + } +} + +#[ffi_export] +fn new_wallet( + name: char_p_ref, + descriptor: char_p_ref, + change_descriptor: Option, +) -> Box { + let network = Testnet; + let _name = name.to_string(); + let descriptor = descriptor.to_string(); + let change_descriptor = change_descriptor.map(|s| s.to_string()); + + let electrum_config = AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { + url: "ssl://electrum.blockstream.info:60002".to_string(), + socks5: None, + retry: 5, + timeout: None, + }); + let blockchain_config = electrum_config; + let client = AnyBlockchain::from_config(&blockchain_config).unwrap(); + + let database_config = AnyDatabaseConfig::Memory(()); + let database = AnyDatabase::from_config(&database_config).unwrap(); + + let descriptor: &str = descriptor.as_str(); + let change_descriptor: Option<&str> = change_descriptor.as_deref(); + + let wallet = Wallet::new(descriptor, change_descriptor, network, database, client).unwrap(); + + Box::new(WalletPtr::from(wallet)) +} + +#[ffi_export] +fn sync_wallet(wallet: &WalletPtr) { + let _r = wallet.raw.sync(log_progress(), Some(100)); +} + +#[ffi_export] +fn new_address(wallet: &WalletPtr) -> char_p_boxed { + let new_address = wallet.raw.get_address(New); + let new_address = new_address.unwrap(); + let new_address = new_address.to_string(); + new_address.try_into().unwrap() +} + +/// Frees a Rust-allocated string +#[ffi_export] +fn free_string(string: char_p_boxed) { + drop(string) +} + +#[ffi_export] +fn free_wallet(wallet: Option>) { + drop(wallet) +}