From 8aa18fbf20fcf6ed0e9a94ded7f6e14e111c7006 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 8 Jun 2021 18:15:20 -0700 Subject: [PATCH 001/272] Test new, print, and free Config works --- .gitignore | 10 +- Cargo.toml | 2 +- build.gradle | 22 +++ build.sh | 8 +- gradle.properties | 21 +++ jvm/build.gradle | 40 ++++ .../java/org/bitcoindevkit/bdkjni/LibTest.kt | 43 +++++ main.c | 58 +++--- settings.gradle | 3 + src/lib.rs | 174 +++++++++--------- 10 files changed, 262 insertions(+), 119 deletions(-) create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 jvm/build.gradle create mode 100644 jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt create mode 100644 settings.gradle 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] From 8deb39ac761dc7742f7805329215acea043e2f72 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 9 Jun 2021 16:00:40 -0700 Subject: [PATCH 002/272] Add missing kotlin files --- .gitignore | 1 - .../main/java/org/bitcoindevkit/bdkjni/Lib.kt | 76 +++++++++++++++++++ .../java/org/bitcoindevkit/bdkjni/LibTest.kt | 25 ++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt diff --git a/.gitignore b/.gitignore index 7ec88ce..30226da 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ target build Cargo.lock *.h -main /local.properties .gradle wallet_db diff --git a/jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt b/jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt new file mode 100644 index 0000000..d1b7456 --- /dev/null +++ b/jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt @@ -0,0 +1,76 @@ +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("x", "y") +class Config_t : Structure() { + @JvmField + var name: String? = null + @JvmField + var count: NativeLong? = null + + override fun getFieldOrder() = listOf("name", "count") +} + +// typedef struct WalletPtr WalletPtr_t; +//class WalletPtr_t : PointerType() + +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_int ( + // int64_t number); + fun print_int(number: Int) + + // 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/bdkjni/LibTest.kt b/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt index aef6e69..6d935fe 100644 --- a/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt +++ b/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt @@ -2,6 +2,8 @@ package org.bitcoindevkit.bdkjni import com.sun.jna.Native import com.sun.jna.NativeLong +import com.sun.jna.Pointer +import com.sun.jna.ptr.PointerByReference import org.junit.Test /** @@ -40,4 +42,27 @@ class LibTest { 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) +// println("wallet created in kotlin: $wallet") +// 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 config = lib.new_config("test test", NativeLong(Long.MAX_VALUE)) +// lib.print_config(config) +// lib.free_config(config) +// } } From a5ad4cd0a5864831cc0594db42cac9cd5123bcd2 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 10 Jun 2021 13:40:58 -0700 Subject: [PATCH 003/272] Fix kotlin wallet struct access via JNA opaque pointer --- .gitignore | 1 + bdk_ffi_test.c | 72 +++++++++++ build.sh | 10 +- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58910 bytes gradle/wrapper/gradle-wrapper.properties | 5 + jvm/build.gradle | 6 +- .../main/java/org/bitcoindevkit/bdkjni/Lib.kt | 21 ++-- .../java/org/bitcoindevkit/bdkjni/LibTest.kt | 44 ++++--- main.c | 55 -------- src/lib.rs | 119 +++++++++--------- 10 files changed, 175 insertions(+), 158 deletions(-) create mode 100644 bdk_ffi_test.c create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100644 main.c diff --git a/.gitignore b/.gitignore index 30226da..9d55bb8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ Cargo.lock /local.properties .gradle wallet_db +bdk_ffi_test diff --git a/bdk_ffi_test.c b/bdk_ffi_test.c new file mode 100644 index 0000000..7c2d492 --- /dev/null +++ b/bdk_ffi_test.c @@ -0,0 +1,72 @@ +#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); + + // test sync_wallet + sync_wallet(wallet); + printf("after sync_wallet\n"); + sync_wallet(wallet); + printf("after sync_wallet\n"); + + // test new_address + char * address1 = new_address(wallet); + printf("address1: %s\n", address1); + free_string(address1); + assert(address1 != NULL); + char * address2 = new_address(wallet); + printf("address2: %s\n", address2); + assert(address2 != NULL); + free_string(address2); + + // test free_wallet + free_wallet(wallet); + printf("after free_wallet\n"); + + // test free_wallet NULL doesn't crash + free_wallet(NULL); + + // verify sync_wallet after sync_wallet fails (double free detected, core dumped) + ////sync_wallet(&wallet); + } + + return EXIT_SUCCESS; +} diff --git a/build.sh b/build.sh index d80153a..5e9eab6 100755 --- a/build.sh +++ b/build.sh @@ -1,9 +1,13 @@ +# rust 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 -./main +export LD_LIBRARY_PATH=`pwd`/target/debug + +# cc +cc bdk_ffi_test.c -o bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m +#valgrind --leak-check=full ./bdk_ffi_test +./bdk_ffi_test # 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/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..62d4c053550b91381bbd28b1afc82d634bf73a8a GIT binary patch literal 58910 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..622ab64 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/jvm/build.gradle b/jvm/build.gradle index c9ab9a8..f06e84c 100644 --- a/jvm/build.gradle +++ b/jvm/build.gradle @@ -7,9 +7,9 @@ plugins { 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" -// } + testLogging { + events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" + } } task buildRust(type: Exec) { diff --git a/jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt b/jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt index d1b7456..033a42f 100644 --- a/jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt +++ b/jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt @@ -10,18 +10,19 @@ import com.sun.jna.ptr.PointerByReference // int32_t count; // // } Config_t; -//@Structure.FieldOrder("x", "y") +@Structure.FieldOrder("name", "count") class Config_t : Structure() { @JvmField var name: String? = null @JvmField var count: NativeLong? = null - - override fun getFieldOrder() = listOf("name", "count") } // typedef struct WalletPtr WalletPtr_t; -//class WalletPtr_t : PointerType() +class WalletPtr_t : PointerType { + constructor(): super() + constructor(pointer: Pointer): super(pointer) +} interface Lib : Library { @@ -38,10 +39,6 @@ interface Lib : Library { // char * string); fun free_string(string: String) - // void print_int ( - // int64_t number); - fun print_int(number: Int) - // void print_config ( // Config_t const * config); fun print_config(config: Config_t) @@ -59,18 +56,18 @@ interface Lib : Library { // char const * name, // char const * descriptor, // char const * change_descriptor); - //fun new_wallet(name: String, descriptor: String, changeDescriptor: String?): WalletPtr_t + 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) + fun sync_wallet(wallet: WalletPtr_t) // char * new_address ( // WalletPtr_t * const * wallet); - //fun new_address(wallet: WalletPtr_t): String + fun new_address(wallet: WalletPtr_t): String // void free_wallet ( // WalletPtr_t * wallet); - //fun free_wallet(wallet: WalletPtr_t) + fun free_wallet(wallet: WalletPtr_t) } diff --git a/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt b/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt index 6d935fe..3c32f94 100644 --- a/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt +++ b/jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt @@ -2,8 +2,6 @@ package org.bitcoindevkit.bdkjni import com.sun.jna.Native import com.sun.jna.NativeLong -import com.sun.jna.Pointer -import com.sun.jna.ptr.PointerByReference import org.junit.Test /** @@ -43,26 +41,26 @@ class LibTest { 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) -// println("wallet created in kotlin: $wallet") -// lib.sync_wallet(wallet) -// //lib.free_wallet(wallet) -// } + @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/*)" -// @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 config = lib.new_config("test test", NativeLong(Long.MAX_VALUE)) -// lib.print_config(config) -// lib.free_config(config) -// } + 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/main.c b/main.c deleted file mode 100644 index 6d133e4..0000000 --- a/main.c +++ /dev/null @@ -1,55 +0,0 @@ -#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/*)"; - - ////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); - - return EXIT_SUCCESS; -} diff --git a/src/lib.rs b/src/lib.rs index 714fe9f..7776e47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,73 +64,68 @@ fn free_config(config: Box) { drop(config) } -//#[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( + 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()); -//#[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(); -//#[ffi_export] -//fn free_wallet( wallet: Box) { -// drop(wallet) -//} + 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) +} /// The following test function is necessary for the header generation. #[::safer_ffi::cfg_headers] From a37bae5b9da8d8c8dc907bcbe9fc546f42c761d4 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 10 Jun 2021 17:22:33 -0700 Subject: [PATCH 004/272] Remove unneeded test structs and functions, cleanup tests --- bdk_ffi_test.c | 47 ++----- jvm/build.gradle | 2 +- .../main/java/org/bitcoindevkit/bdkffi/Lib.kt | 36 +++++ .../main/java/org/bitcoindevkit/bdkjni/Lib.kt | 73 ---------- .../java/org/bitcoindevkit/bdkffi/LibTest.kt | 40 ++++++ .../java/org/bitcoindevkit/bdkjni/LibTest.kt | 66 --------- src/lib.rs | 127 +----------------- src/wallet.rs | 78 +++++++++++ 8 files changed, 165 insertions(+), 304 deletions(-) create mode 100644 jvm/src/main/java/org/bitcoindevkit/bdkffi/Lib.kt delete mode 100644 jvm/src/main/java/org/bitcoindevkit/bdkjni/Lib.kt create mode 100644 jvm/src/test/java/org/bitcoindevkit/bdkffi/LibTest.kt delete mode 100644 jvm/src/test/java/org/bitcoindevkit/bdkjni/LibTest.kt create mode 100644 src/wallet.rs 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) +} From e26663456054c44cf94fea4e14059127dfb844ca Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 14 Jun 2021 13:59:56 -0700 Subject: [PATCH 005/272] Rename bdk-kotlin companion project, fix gradle warnings --- .gitignore | 2 +- Cargo.toml | 2 +- bdk-kotlin/.gitignore | 6 + bdk-kotlin/aar/build.gradle | 73 +++++++++ bdk-kotlin/aar/consumer-rules.pro | 0 bdk-kotlin/aar/proguard-rules.pro | 26 ++++ .../bdk/ExampleInstrumentedTest.kt | 147 ++++++++++++++++++ bdk-kotlin/aar/src/main/AndroidManifest.xml | 6 + build.gradle => bdk-kotlin/build.gradle | 8 +- .../gradle.properties | 0 .../gradle}/wrapper/gradle-wrapper.jar | Bin .../gradle}/wrapper/gradle-wrapper.properties | 5 +- bdk-kotlin/jar/build.gradle | 42 +++++ .../main/java/org/bitcoindevkit/bdk}/Lib.kt | 3 +- .../java/org/bitcoindevkit/bdk}/LibTest.kt | 3 +- bdk-kotlin/settings.gradle | 3 + build.sh | 15 +- bdk_ffi_test.c => cc/bdk_ffi_test.c | 0 jvm/build.gradle | 40 ----- settings.gradle | 3 - src/lib.rs | 2 +- 21 files changed, 323 insertions(+), 63 deletions(-) create mode 100644 bdk-kotlin/.gitignore create mode 100644 bdk-kotlin/aar/build.gradle create mode 100644 bdk-kotlin/aar/consumer-rules.pro create mode 100644 bdk-kotlin/aar/proguard-rules.pro create mode 100644 bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt create mode 100644 bdk-kotlin/aar/src/main/AndroidManifest.xml rename build.gradle => bdk-kotlin/build.gradle (76%) rename gradle.properties => bdk-kotlin/gradle.properties (100%) rename {gradle => bdk-kotlin/gradle}/wrapper/gradle-wrapper.jar (100%) rename {gradle => bdk-kotlin/gradle}/wrapper/gradle-wrapper.properties (80%) create mode 100644 bdk-kotlin/jar/build.gradle rename {jvm/src/main/java/org/bitcoindevkit/bdkffi => bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk}/Lib.kt (92%) rename {jvm/src/test/java/org/bitcoindevkit/bdkffi => bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk}/LibTest.kt (95%) create mode 100644 bdk-kotlin/settings.gradle rename bdk_ffi_test.c => cc/bdk_ffi_test.c (100%) delete mode 100644 jvm/build.gradle delete mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore index 9d55bb8..9bf4fb6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ target build Cargo.lock *.h -/local.properties +/bdk-kotlin/local.properties .gradle wallet_db bdk_ffi_test diff --git a/Cargo.toml b/Cargo.toml index 98e4d9a..c8afbd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bdk_ffi" +name = "bdk-ffi" version = "0.1.0" authors = ["Steve Myers "] edition = "2018" diff --git a/bdk-kotlin/.gitignore b/bdk-kotlin/.gitignore new file mode 100644 index 0000000..138d427 --- /dev/null +++ b/bdk-kotlin/.gitignore @@ -0,0 +1,6 @@ +/target +.idea +.gradle +local.properties +build +*.so \ No newline at end of file diff --git a/bdk-kotlin/aar/build.gradle b/bdk-kotlin/aar/build.gradle new file mode 100644 index 0000000..0ef7007 --- /dev/null +++ b/bdk-kotlin/aar/build.gradle @@ -0,0 +1,73 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'maven-publish' + +android { + compileSdkVersion 30 + buildToolsVersion "29.0.3" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +//task buildRust(type: Exec) { +// workingDir '../' +// commandLine './build.sh' +//} + +afterEvaluate { +// android.libraryVariants.all { variant -> +// variant.javaCompileProvider.get().dependsOn(buildRust) +// } + + publishing { + publications { + // Creates a Maven publication called "release". + release(MavenPublication) { + // Applies the component for the release build variant. + from components.release + + // You can then customize attributes of the publication as shown below. + groupId = 'org.bitcoindevkit.bdkffi' + artifactId = 'bdk' + version = '0.0.1-dev' + } + // Creates a Maven publication called “debug”. + debug(MavenPublication) { + // Applies the component for the debug build variant. + from components.debug + + groupId = 'org.bitcoindevkit.bdkffi' + artifactId = 'bdk-debug' + version = '0.0.1-dev' + } + } + } +} + +dependencies { + implementation project(':jar') + api 'net.java.dev.jna:jna:5.8.0@aar' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'androidx.core:core-ktx:1.5.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' +} diff --git a/bdk-kotlin/aar/consumer-rules.pro b/bdk-kotlin/aar/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/bdk-kotlin/aar/proguard-rules.pro b/bdk-kotlin/aar/proguard-rules.pro new file mode 100644 index 0000000..172980c --- /dev/null +++ b/bdk-kotlin/aar/proguard-rules.pro @@ -0,0 +1,26 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +# for JNA +-dontwarn java.awt.* +-keep class com.sun.jna.* { *; } +-keepclassmembers class * extends com.sun.jna.* { public *; } diff --git a/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt b/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..3187bb2 --- /dev/null +++ b/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt @@ -0,0 +1,147 @@ +package org.bitcoindevkit.bdk + +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.runBlocking +import org.junit.After + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* +import org.junit.Before + +//import org.bitcoindevkit.bdkjni.Types.Network +//import org.bitcoindevkit.bdkjni.Types.WalletConstructor +//import org.bitcoindevkit.bdkjni.Types.WalletPtr +import org.junit.Ignore + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + + companion object { + init { + System.loadLibrary("bdk_jni") + } + } + + private lateinit var wallet: WalletPtr + + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("org.bitcoindevkit.bdkjni.test", appContext.packageName) + } + + @Before + fun constructor() { + val dir = createTempDir() + val descriptor = "wpkh(tprv8ZgxMBicQKsPexGYyaFwnAsCXCjmz2FaTm6LtesyyihjbQE3gRMfXqQBXKM43DvC1UgRVv1qom1qFxNMSqVAs88qx9PhgFnfGVUdiiDf6j4/0/*)" + val electrum = "tcp://electrum.blockstream.info:60001" + wallet = Lib().constructor(WalletConstructor("testnet", Network.regtest, dir.toString(), descriptor, null, electrum, null)) + Lib().sync(wallet) + } + + @Test + fun newAddress() { + val address = Lib().get_new_address(wallet) + assertFalse(address.isEmpty()) + } + + @Test + fun sync() { + Lib().sync(wallet, 100) + val balance = Lib().get_balance(wallet) + assertFalse(balance == 0L) + } + + // TODO need to figure out why this passes when testing with a localhost node but fails when using blockstream.info + @Ignore + @Test + fun multiThreadBalance() { + runBlocking { + val flow1 = newBalanceFlow(1).flowOn(Dispatchers.IO) + val flow2 = newBalanceFlow(2).flowOn(Dispatchers.IO) + flow1.flatMapMerge(concurrency = 2) { flow2 }.collect() + //flow1.collect() + } + } + + private fun newBalanceFlow(id: Int): Flow> { + return (1..10).asFlow() + //.onStart { Log.d("BAL_FLOW", "start flow $id") } + //.onCompletion { Log.d("BAL_FLOW", "complete flow $id") } + //.onEach { Log.d("BAL_FLOW", "flow $id, iteration $it") } + .map { + val balance = Lib().get_balance(wallet) + Pair(it, balance) + } + .catch { e -> + Log.e("BAL_FLOW", "failed flow $id with exception: $e") + fail() + } + .onEach { + //Log.d("BAL_FLOW", "verifying flow $id, iteration ${it.first}") + assertFalse(it.second == 0L) + //Log.d("BAL_FLOW", "finished flow $id iteration ${it.first}") + } + } + + @Test + fun balance() { + val balance = Lib().get_balance(wallet) + assertFalse(balance == 0L) + } + + @Test + fun unspent() { + val unspent = Lib().list_unspent(wallet) + assertFalse(unspent.isEmpty()) + } + + @Test + fun transactions() { + val transactions = Lib().list_transactions(wallet) + assertFalse(transactions.isEmpty()) + } + + @Test + fun generate_key() { + val keys = Lib().generate_extended_key(Network.testnet, 24, "test123") + assertNotNull(keys) + assertEquals(24, keys.mnemonic.split(' ').size) + assertEquals("tprv", keys.xprv.substring(0,4)) + } + + @Test + fun restore_key() { + val mnemonic = "shell bid diary primary focus average truly secret lonely circle radar fall tank action place body wedding sponsor embody glue swing gauge shop penalty" + val keys = Lib().restore_extended_key(Network.testnet, mnemonic, null) + assertNotNull(keys) + assertEquals(mnemonic, keys.mnemonic) + assertEquals("tprv8ZgxMBicQKsPeh5nd4nCDLGh9dLfhqGfUoiQsbThkttjX9oroRY2j5vpEGwkiKiKtzdU7u4eqH2yFicGvz19rMVVXfY8XB9fdoeXWJ7SgVE", keys.xprv) + } + + @Test + fun restore_key_password() { + val mnemonic = "shell bid diary primary focus average truly secret lonely circle radar fall tank action place body wedding sponsor embody glue swing gauge shop penalty" + val keys = Lib().restore_extended_key(Network.testnet, mnemonic, "test123") + assertNotNull(keys) + assertEquals(mnemonic, keys.mnemonic) + assertEquals("tprv8ZgxMBicQKsPebcVXyErMuuv2rgE34m2SLMBhy4hURbSEAWQ1VsWVVmMnD7FKiAuRrxzAETFnUaSvFNQ5SAS5tYEwsM1KHDpUhLLQgd6yG1", keys.xprv) + } + + @After + fun destructor() { + Lib().destructor(wallet) + } +} diff --git a/bdk-kotlin/aar/src/main/AndroidManifest.xml b/bdk-kotlin/aar/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3d47121 --- /dev/null +++ b/bdk-kotlin/aar/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/build.gradle b/bdk-kotlin/build.gradle similarity index 76% rename from build.gradle rename to bdk-kotlin/build.gradle index 7286dea..1f08c7d 100644 --- a/build.gradle +++ b/bdk-kotlin/build.gradle @@ -1,22 +1,22 @@ buildscript { ext.kotlin_version = '1.5.10' repositories { - //google() + google() mavenCentral() } dependencies { - //classpath 'com.android.tools.build:gradle:3.6.4' + classpath 'com.android.tools.build:gradle:4.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { - //google() + google() mavenCentral() } } task clean(type: Delete) { delete rootProject.buildDir -} +} \ No newline at end of file diff --git a/gradle.properties b/bdk-kotlin/gradle.properties similarity index 100% rename from gradle.properties rename to bdk-kotlin/gradle.properties diff --git a/gradle/wrapper/gradle-wrapper.jar b/bdk-kotlin/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/wrapper/gradle-wrapper.jar rename to bdk-kotlin/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties similarity index 80% rename from gradle/wrapper/gradle-wrapper.properties rename to bdk-kotlin/gradle/wrapper/gradle-wrapper.properties index 622ab64..382d831 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Thu Jun 10 21:43:37 PDT 2021 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/bdk-kotlin/jar/build.gradle b/bdk-kotlin/jar/build.gradle new file mode 100644 index 0000000..f6eae54 --- /dev/null +++ b/bdk-kotlin/jar/build.gradle @@ -0,0 +1,42 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'java-library' + id 'maven-publish' +} + +test { + environment "LD_LIBRARY_PATH", file("${projectDir}/../../target/debug").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:$kotlin_version" + 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' + artifactId = 'bdk-debug' + version = '0.0.1-dev' + + from components.java + } + } +} +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/jvm/src/main/java/org/bitcoindevkit/bdkffi/Lib.kt b/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt similarity index 92% rename from jvm/src/main/java/org/bitcoindevkit/bdkffi/Lib.kt rename to bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt index 0be82d5..f4ef503 100644 --- a/jvm/src/main/java/org/bitcoindevkit/bdkffi/Lib.kt +++ b/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt @@ -1,7 +1,6 @@ -package org.bitcoindevkit.bdkjni +package org.bitcoindevkit.bdk import com.sun.jna.* -import com.sun.jna.ptr.PointerByReference interface Lib : Library { diff --git a/jvm/src/test/java/org/bitcoindevkit/bdkffi/LibTest.kt b/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt similarity index 95% rename from jvm/src/test/java/org/bitcoindevkit/bdkffi/LibTest.kt rename to bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt index cf8b469..503ff95 100644 --- a/jvm/src/test/java/org/bitcoindevkit/bdkffi/LibTest.kt +++ b/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt @@ -1,7 +1,6 @@ -package org.bitcoindevkit.bdkjni +package org.bitcoindevkit.bdk import com.sun.jna.Native -import com.sun.jna.NativeLong import org.junit.Test import kotlin.test.assertEquals diff --git a/bdk-kotlin/settings.gradle b/bdk-kotlin/settings.gradle new file mode 100644 index 0000000..9f4e44a --- /dev/null +++ b/bdk-kotlin/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'bdk-kotlin' + +include 'jar' \ No newline at end of file diff --git a/build.sh b/build.sh index 5e9eab6..74d0fd4 100755 --- a/build.sh +++ b/build.sh @@ -1,13 +1,14 @@ # rust cargo build cargo test --features c-headers -- generate_headers -export LD_LIBRARY_PATH=`pwd`/target/debug # cc -cc bdk_ffi_test.c -o bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m -#valgrind --leak-check=full ./bdk_ffi_test -./bdk_ffi_test +export LD_LIBRARY_PATH=`pwd`/target/debug +cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m +#valgrind --leak-check=full cc/bdk_ffi_test +cc/bdk_ffi_test -# jvm -mkdir -p jvm/build/jniLibs/x86_64_linux -cp target/debug/libbdk_ffi.so jvm/build/jniLibs/x86_64_linux +# bdk-kotlin +mkdir -p bdk-kotlin/jar/libs/x86_64_linux +cp target/debug/libbdk_ffi.so bdk-kotlin/jar/libs/x86_64_linux +(cd bdk-kotlin && gradle test) diff --git a/bdk_ffi_test.c b/cc/bdk_ffi_test.c similarity index 100% rename from bdk_ffi_test.c rename to cc/bdk_ffi_test.c diff --git a/jvm/build.gradle b/jvm/build.gradle deleted file mode 100644 index e544c88..0000000 --- a/jvm/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -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.bdkffi' - artifactId = 'bdk-jvm-debug' - version = '0.2.1-dev' - - from components.java - } - } -} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index f14b0a7..0000000 --- a/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -rootProject.name = 'bdk_ffi' - -include 'jvm' diff --git a/src/lib.rs b/src/lib.rs index 75143b0..f3f6fc6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,6 @@ mod wallet; #[test] fn generate_headers() -> ::std::io::Result<()> { ::safer_ffi::headers::builder() - .to_file("bdk_ffi.h")? + .to_file("cc/bdk_ffi.h")? .generate() } From 4682fb3ec8b695b328207fa2c4bd870a18cecd30 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 14 Jun 2021 14:18:16 -0700 Subject: [PATCH 006/272] Execute bdk_ffi_test via valgrind --- build.sh | 4 ++-- cc/bdk_ffi_test.c | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index 74d0fd4..6e501db 100755 --- a/build.sh +++ b/build.sh @@ -5,8 +5,8 @@ cargo test --features c-headers -- generate_headers # cc export LD_LIBRARY_PATH=`pwd`/target/debug cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m -#valgrind --leak-check=full cc/bdk_ffi_test -cc/bdk_ffi_test +valgrind --leak-check=full cc/bdk_ffi_test +#cc/bdk_ffi_test # bdk-kotlin mkdir -p bdk-kotlin/jar/libs/x86_64_linux diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index 83ed17d..48cc0fd 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -35,8 +35,11 @@ int main (int argc, char const * const argv[]) // test free_wallet NULL doesn't crash free_wallet(NULL); - // verify sync_wallet after sync_wallet fails (double free detected, core dumped) - ////sync_wallet(&wallet); + // verify free_wallet after free_wallet fails (core dumped) + ////free_wallet(wallet); + + // verify sync_wallet after free_wallet fails (core dumped) + ////sync_wallet(wallet); } return EXIT_SUCCESS; From bec53bd8366c08ee80b5107a0bbb9f611c4f31af Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 14 Jun 2021 14:24:20 -0700 Subject: [PATCH 007/272] Update jar test LD_LIBRARY_PATH to use libs/x86_64_linux --- bdk-kotlin/jar/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-kotlin/jar/build.gradle b/bdk-kotlin/jar/build.gradle index f6eae54..48b6bdf 100644 --- a/bdk-kotlin/jar/build.gradle +++ b/bdk-kotlin/jar/build.gradle @@ -5,7 +5,7 @@ plugins { } test { - environment "LD_LIBRARY_PATH", file("${projectDir}/../../target/debug").absolutePath + environment "LD_LIBRARY_PATH", file("${projectDir}/libs/x86_64_linux").absolutePath // testLogging { // events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" // } From 610d3939230f263a33a4c238e6c16f7bd3352a2c Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 14 Jun 2021 22:38:29 -0700 Subject: [PATCH 008/272] Add kotlin/aar android device tests --- bdk-kotlin/aar/build.gradle | 12 +- .../org/bitcoindevkit/bdk/AndroidLibTest.kt | 62 ++++++++ .../bdk/ExampleInstrumentedTest.kt | 147 ------------------ bdk-kotlin/jar/build.gradle | 12 +- .../java/org/bitcoindevkit/bdk/LibTest.kt | 44 ++++-- bdk-kotlin/settings.gradle | 2 +- build.sh | 46 +++++- test.sh | 14 ++ 8 files changed, 158 insertions(+), 181 deletions(-) create mode 100644 bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt delete mode 100644 bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt create mode 100755 test.sh diff --git a/bdk-kotlin/aar/build.gradle b/bdk-kotlin/aar/build.gradle index 0ef7007..6116737 100644 --- a/bdk-kotlin/aar/build.gradle +++ b/bdk-kotlin/aar/build.gradle @@ -1,11 +1,10 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' +//apply plugin: 'kotlin-android-extensions' apply plugin: 'maven-publish' android { compileSdkVersion 30 - buildToolsVersion "29.0.3" defaultConfig { minSdkVersion 21 @@ -43,7 +42,7 @@ afterEvaluate { from components.release // You can then customize attributes of the publication as shown below. - groupId = 'org.bitcoindevkit.bdkffi' + groupId = 'org.bitcoindevkit' artifactId = 'bdk' version = '0.0.1-dev' } @@ -52,7 +51,7 @@ afterEvaluate { // Applies the component for the debug build variant. from components.debug - groupId = 'org.bitcoindevkit.bdkffi' + groupId = 'org.bitcoindevkit' artifactId = 'bdk-debug' version = '0.0.1-dev' } @@ -61,7 +60,10 @@ afterEvaluate { } dependencies { - implementation project(':jar') + implementation (project(':jar')) { + exclude group: 'net.java.dev.jna', module: 'jna' + } + api 'net.java.dev.jna:jna:5.8.0@aar' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.3.0' diff --git a/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt new file mode 100644 index 0000000..0dbaa2a --- /dev/null +++ b/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt @@ -0,0 +1,62 @@ +package org.bitcoindevkit.bdk + +import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.sun.jna.Native +import org.junit.* + +import org.junit.runner.RunWith + +import org.junit.Assert.* + +//import org.bitcoindevkit.bdkjni.Types.Network +//import org.bitcoindevkit.bdkjni.Types.WalletConstructor +//import org.bitcoindevkit.bdkjni.Types.WalletPtr + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class AndroidLibTest { + + companion object { + private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) + private lateinit var wallet: Lib.WalletPtr_t + + @BeforeClass + @JvmStatic + fun create_wallet() { + val name = "test_wallet" + val desc = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + val change = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" + + wallet = bdkFfi.new_wallet(name, desc, change) + Log.d("create_wallet", "wallet created") + } + + @AfterClass + @JvmStatic + fun free_wallet() { + bdkFfi.free_wallet(wallet) + Log.d("free_wallet", "wallet freed") + } + } + + @Test + fun sync() { + bdkFfi.sync_wallet(wallet) + Log.d("sync", "wallet synced") + } + + @Test + fun new_address() { + val address = bdkFfi.new_address(wallet) + //println("address created from kotlin: $address") + assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") + Log.d("new_address", "new address: $address") + } +} diff --git a/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt b/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt deleted file mode 100644 index 3187bb2..0000000 --- a/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,147 +0,0 @@ -package org.bitcoindevkit.bdk - -import android.util.Log -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.runBlocking -import org.junit.After - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* -import org.junit.Before - -//import org.bitcoindevkit.bdkjni.Types.Network -//import org.bitcoindevkit.bdkjni.Types.WalletConstructor -//import org.bitcoindevkit.bdkjni.Types.WalletPtr -import org.junit.Ignore - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - - companion object { - init { - System.loadLibrary("bdk_jni") - } - } - - private lateinit var wallet: WalletPtr - - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("org.bitcoindevkit.bdkjni.test", appContext.packageName) - } - - @Before - fun constructor() { - val dir = createTempDir() - val descriptor = "wpkh(tprv8ZgxMBicQKsPexGYyaFwnAsCXCjmz2FaTm6LtesyyihjbQE3gRMfXqQBXKM43DvC1UgRVv1qom1qFxNMSqVAs88qx9PhgFnfGVUdiiDf6j4/0/*)" - val electrum = "tcp://electrum.blockstream.info:60001" - wallet = Lib().constructor(WalletConstructor("testnet", Network.regtest, dir.toString(), descriptor, null, electrum, null)) - Lib().sync(wallet) - } - - @Test - fun newAddress() { - val address = Lib().get_new_address(wallet) - assertFalse(address.isEmpty()) - } - - @Test - fun sync() { - Lib().sync(wallet, 100) - val balance = Lib().get_balance(wallet) - assertFalse(balance == 0L) - } - - // TODO need to figure out why this passes when testing with a localhost node but fails when using blockstream.info - @Ignore - @Test - fun multiThreadBalance() { - runBlocking { - val flow1 = newBalanceFlow(1).flowOn(Dispatchers.IO) - val flow2 = newBalanceFlow(2).flowOn(Dispatchers.IO) - flow1.flatMapMerge(concurrency = 2) { flow2 }.collect() - //flow1.collect() - } - } - - private fun newBalanceFlow(id: Int): Flow> { - return (1..10).asFlow() - //.onStart { Log.d("BAL_FLOW", "start flow $id") } - //.onCompletion { Log.d("BAL_FLOW", "complete flow $id") } - //.onEach { Log.d("BAL_FLOW", "flow $id, iteration $it") } - .map { - val balance = Lib().get_balance(wallet) - Pair(it, balance) - } - .catch { e -> - Log.e("BAL_FLOW", "failed flow $id with exception: $e") - fail() - } - .onEach { - //Log.d("BAL_FLOW", "verifying flow $id, iteration ${it.first}") - assertFalse(it.second == 0L) - //Log.d("BAL_FLOW", "finished flow $id iteration ${it.first}") - } - } - - @Test - fun balance() { - val balance = Lib().get_balance(wallet) - assertFalse(balance == 0L) - } - - @Test - fun unspent() { - val unspent = Lib().list_unspent(wallet) - assertFalse(unspent.isEmpty()) - } - - @Test - fun transactions() { - val transactions = Lib().list_transactions(wallet) - assertFalse(transactions.isEmpty()) - } - - @Test - fun generate_key() { - val keys = Lib().generate_extended_key(Network.testnet, 24, "test123") - assertNotNull(keys) - assertEquals(24, keys.mnemonic.split(' ').size) - assertEquals("tprv", keys.xprv.substring(0,4)) - } - - @Test - fun restore_key() { - val mnemonic = "shell bid diary primary focus average truly secret lonely circle radar fall tank action place body wedding sponsor embody glue swing gauge shop penalty" - val keys = Lib().restore_extended_key(Network.testnet, mnemonic, null) - assertNotNull(keys) - assertEquals(mnemonic, keys.mnemonic) - assertEquals("tprv8ZgxMBicQKsPeh5nd4nCDLGh9dLfhqGfUoiQsbThkttjX9oroRY2j5vpEGwkiKiKtzdU7u4eqH2yFicGvz19rMVVXfY8XB9fdoeXWJ7SgVE", keys.xprv) - } - - @Test - fun restore_key_password() { - val mnemonic = "shell bid diary primary focus average truly secret lonely circle radar fall tank action place body wedding sponsor embody glue swing gauge shop penalty" - val keys = Lib().restore_extended_key(Network.testnet, mnemonic, "test123") - assertNotNull(keys) - assertEquals(mnemonic, keys.mnemonic) - assertEquals("tprv8ZgxMBicQKsPebcVXyErMuuv2rgE34m2SLMBhy4hURbSEAWQ1VsWVVmMnD7FKiAuRrxzAETFnUaSvFNQ5SAS5tYEwsM1KHDpUhLLQgd6yG1", keys.xprv) - } - - @After - fun destructor() { - Lib().destructor(wallet) - } -} diff --git a/bdk-kotlin/jar/build.gradle b/bdk-kotlin/jar/build.gradle index 48b6bdf..4d767df 100644 --- a/bdk-kotlin/jar/build.gradle +++ b/bdk-kotlin/jar/build.gradle @@ -11,17 +11,15 @@ test { // } } -task buildRust(type: Exec) { - workingDir '../' - commandLine './build.sh' -} +//task buildRust(type: Exec) { +// workingDir '../' +// commandLine './build.sh' +//} dependencies { implementation platform('org.jetbrains.kotlin:kotlin-bom') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "net.java.dev.jna:jna:5.8.0" - - testImplementation 'org.jetbrains.kotlin:kotlin-test' testImplementation 'org.jetbrains.kotlin:kotlin-test-junit' } @@ -29,7 +27,7 @@ publishing { publications { maven(MavenPublication) { groupId = 'org.bitcoindevkit' - artifactId = 'bdk-debug' + artifactId = 'bdk' version = '0.0.1-dev' from components.java diff --git a/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt index 503ff95..799163e 100644 --- a/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt @@ -1,8 +1,8 @@ package org.bitcoindevkit.bdk import com.sun.jna.Native -import org.junit.Test -import kotlin.test.assertEquals +import org.junit.* +import org.junit.Assert.assertEquals /** * Library test, which will execute on linux host. @@ -10,30 +10,40 @@ import kotlin.test.assertEquals */ class LibTest { - private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) + companion object { + private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) + private lateinit var wallet: Lib.WalletPtr_t + + @BeforeClass + @JvmStatic + fun create_wallet() { + val name = "test_wallet" + val desc = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + val change = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" + + wallet = bdkFfi.new_wallet(name, desc, change) + println("wallet created") + } + + @AfterClass + @JvmStatic + fun free_wallet() { + bdkFfi.free_wallet(wallet) + println("wallet freed") + } + } @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) + fun sync() { 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/bdk-kotlin/settings.gradle b/bdk-kotlin/settings.gradle index 9f4e44a..17a73f5 100644 --- a/bdk-kotlin/settings.gradle +++ b/bdk-kotlin/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = 'bdk-kotlin' -include 'jar' \ No newline at end of file +include ':jar',':aar' \ No newline at end of file diff --git a/build.sh b/build.sh index 6e501db..60f3bef 100755 --- a/build.sh +++ b/build.sh @@ -1,3 +1,6 @@ +#!/usr/bin/env bash +set -eo pipefail -o xtrace + # rust cargo build cargo test --features c-headers -- generate_headers @@ -5,10 +8,45 @@ cargo test --features c-headers -- generate_headers # cc export LD_LIBRARY_PATH=`pwd`/target/debug cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m -valgrind --leak-check=full cc/bdk_ffi_test -#cc/bdk_ffi_test -# bdk-kotlin +# bdk-kotlin jar mkdir -p bdk-kotlin/jar/libs/x86_64_linux cp target/debug/libbdk_ffi.so bdk-kotlin/jar/libs/x86_64_linux -(cd bdk-kotlin && gradle test) + +(cd bdk-kotlin && gradle :jar:build) + +# rust android + +# If ANDROID_NDK_HOME is not set then set it to github actions default +[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle + +# Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 +export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin + +# Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 +export CFLAGS="-D__ANDROID_API__=21" + +# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on +BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" + +mkdir -p bdk-kotlin/aar/src/main/jniLibs/ bdk-kotlin/aar/src/main/jniLibs/arm64-v8a bdk-kotlin/aar/src/main/jniLibs/x86_64 bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a bdk-kotlin/aar/src/main/jniLibs/x86 + +if echo $BUILD_TARGETS | grep "aarch64"; then + CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android + cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/arm64-v8a +fi +if echo $BUILD_TARGETS | grep "x86_64"; then + CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android + cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86_64 +fi +if echo $BUILD_TARGETS | grep "armv7"; then + CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi + cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a +fi +if echo $BUILD_TARGETS | grep "i686"; then + CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android + cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86 +fi + +# bdk-kotlin aar +(cd bdk-kotlin && gradle :aar:build) diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..da24835 --- /dev/null +++ b/test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -eo pipefail -o xtrace + +# rust +cargo test --features c-headers -- generate_headers + +# cc +export LD_LIBRARY_PATH=`pwd`/target/debug +valgrind --leak-check=full cc/bdk_ffi_test +#cc/bdk_ffi_test + +# bdk-kotlin +(cd bdk-kotlin && gradle test) +(cd bdk-kotlin && gradle :aar:connectedDebugAndroidTest) From 87c823d4970d2d24b63881dbdc5d81dda9a562a8 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 15 Jun 2021 15:21:14 -0700 Subject: [PATCH 009/272] Free rust allocated string from Kotlin in local and android emulator tests --- .../androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt | 4 +++- bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt | 4 ++-- bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt index 0dbaa2a..9a3e7ba 100644 --- a/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt +++ b/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt @@ -54,7 +54,9 @@ class AndroidLibTest { @Test fun new_address() { - val address = bdkFfi.new_address(wallet) + val pointer = bdkFfi.new_address(wallet) + val address = pointer.getString(0) + bdkFfi.free_string(pointer) //println("address created from kotlin: $address") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") Log.d("new_address", "new address: $address") diff --git a/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt b/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt index f4ef503..3a7fe45 100644 --- a/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt +++ b/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt @@ -12,7 +12,7 @@ interface Lib : Library { // void free_string ( // char * string); - fun free_string(string: String) + fun free_string(string: Pointer) // WalletPtr_t * new_wallet ( // char const * name, @@ -27,7 +27,7 @@ interface Lib : Library { // char * new_address ( // WalletPtr_t * const * wallet); - fun new_address(wallet: WalletPtr_t): String + fun new_address(wallet: WalletPtr_t): Pointer // void free_wallet ( // WalletPtr_t * wallet); diff --git a/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt index 799163e..0947557 100644 --- a/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt @@ -42,7 +42,9 @@ class LibTest { @Test fun new_newaddress_wallet() { - val address = bdkFfi.new_address(wallet) + val pointer = bdkFfi.new_address(wallet) + val address = pointer.getString(0) + bdkFfi.free_string(pointer) //println("address created from kotlin: $address") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") } From 9e5aac759d0bca33e13830ed5f1bb9466915b2f5 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 19 Jun 2021 22:53:26 -0700 Subject: [PATCH 010/272] Return results as opaque structs from ffi calls --- bdk-kotlin/jar/build.gradle | 6 +- .../main/java/org/bitcoindevkit/bdk/Lib.kt | 73 +++++-- .../java/org/bitcoindevkit/bdk/LibTest.kt | 39 +++- build.sh | 70 +++---- cc/bdk_ffi_test.c | 42 ++-- src/error.rs | 197 ++++++++++++++++++ src/wallet.rs | 126 ++++++++--- test.sh | 6 +- 8 files changed, 447 insertions(+), 112 deletions(-) create mode 100644 src/error.rs diff --git a/bdk-kotlin/jar/build.gradle b/bdk-kotlin/jar/build.gradle index 4d767df..ba7039e 100644 --- a/bdk-kotlin/jar/build.gradle +++ b/bdk-kotlin/jar/build.gradle @@ -6,9 +6,9 @@ plugins { test { environment "LD_LIBRARY_PATH", file("${projectDir}/libs/x86_64_linux").absolutePath -// testLogging { -// events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" -// } + testLogging { + events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" + } } //task buildRust(type: Exec) { diff --git a/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt b/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt index 3a7fe45..feea907 100644 --- a/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt +++ b/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt @@ -4,32 +4,71 @@ import com.sun.jna.* interface Lib : Library { - // typedef struct WalletPtr WalletPtr_t; - class WalletPtr_t : PointerType { - constructor(): super() - constructor(pointer: Pointer): super(pointer) + // typedef struct VoidResult VoidResult_t; + class VoidResult_t : PointerType { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) } + // char * get_void_err ( + // VoidResult_t const * void_result); + fun get_void_err(void_result: VoidResult_t): Pointer? + + // void free_void_result ( + // VoidResult_t * void_result); + fun free_void_result(void_result: VoidResult_t) + + // typedef struct StringResult StringResult_t; + class StringResult_t : PointerType { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + // char * get_string_ok ( + // StringResult_t const * string_result); + fun get_string_ok(string_result: StringResult_t): Pointer? + + // char * get_string_err ( + // StringResult_t const * string_result); + fun get_string_err(string_result: StringResult_t): Pointer? + + // void free_string_result ( + // StringResult_t * string_result); + fun free_string_result(string_result: StringResult_t) + // void free_string ( // char * string); - fun free_string(string: Pointer) + fun free_string(string: Pointer?) - // WalletPtr_t * new_wallet ( + // typedef struct WalletResult WalletResult_t; + class WalletResult_t : PointerType { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + // WalletResult_t * new_wallet_result ( // char const * name, // char const * descriptor, // char const * change_descriptor); - fun new_wallet(name: String, descriptor: String, changeDescriptor: String?): WalletPtr_t + fun new_wallet_result( + name: String, + descriptor: String, + changeDescriptor: String? + ): WalletResult_t + + // char * get_wallet_err ( + // WalletResult_t const * wallet_result); + // TODO - // void sync_wallet ( - // WalletPtr_t * const * wallet); - //fun sync_wallet(wallet: WalletPtr_t) - fun sync_wallet(wallet: WalletPtr_t) + // VoidResult_t * sync_wallet ( + // WalletResult_t const * wallet_result); + fun sync_wallet(wallet_result: WalletResult_t): VoidResult_t - // char * new_address ( - // WalletPtr_t * const * wallet); - fun new_address(wallet: WalletPtr_t): Pointer + // StringResult_t * new_address ( + // WalletResult_t const * wallet_result); + fun new_address(wallet_result: WalletResult_t): StringResult_t - // void free_wallet ( - // WalletPtr_t * wallet); - fun free_wallet(wallet: WalletPtr_t) + // void free_wallet_result ( + // WalletResult_t * wallet_result); + fun free_wallet_result(wallet_result: WalletResult_t) } diff --git a/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt index 0947557..4b86784 100644 --- a/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt @@ -3,6 +3,8 @@ package org.bitcoindevkit.bdk import com.sun.jna.Native import org.junit.* import org.junit.Assert.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull /** * Library test, which will execute on linux host. @@ -12,39 +14,56 @@ class LibTest { companion object { private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) - private lateinit var wallet: Lib.WalletPtr_t + private lateinit var wallet_result: Lib.WalletResult_t @BeforeClass @JvmStatic - fun create_wallet() { + fun new_wallet() { val name = "test_wallet" val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" - wallet = bdkFfi.new_wallet(name, desc, change) - println("wallet created") + wallet_result = bdkFfi.new_wallet_result(name, desc, change) + //println("wallet created") } @AfterClass @JvmStatic fun free_wallet() { - bdkFfi.free_wallet(wallet) - println("wallet freed") + bdkFfi.free_wallet_result(wallet_result) + //println("wallet freed") } } + @Test + fun wallet_sync_error() { + val bad_wallet_result = bdkFfi.new_wallet_result("test", "bad", null) + println("wallet result created") + val sync_result = bdkFfi.sync_wallet(bad_wallet_result) + val sync_err_pointer = bdkFfi.get_void_err(sync_result) + assertNotNull(sync_err_pointer) + val sync_err = sync_err_pointer.getString(0) + println("wallet sync error $sync_err") + } + @Test fun sync() { - bdkFfi.sync_wallet(wallet) + val sync_result = bdkFfi.sync_wallet(wallet_result) + assertNull(bdkFfi.get_void_err(sync_result)) + bdkFfi.free_void_result(sync_result) } @Test fun new_newaddress_wallet() { - val pointer = bdkFfi.new_address(wallet) - val address = pointer.getString(0) - bdkFfi.free_string(pointer) + val address_result = bdkFfi.new_address(wallet_result) + assertNull(bdkFfi.get_string_err(address_result)) + val address_pointer = bdkFfi.get_string_ok(address_result); + val address = address_pointer!!.getString(0) + + bdkFfi.free_string_result(address_result) + bdkFfi.free_string(address_pointer) //println("address created from kotlin: $address") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") } diff --git a/build.sh b/build.sh index 60f3bef..b8b5c01 100755 --- a/build.sh +++ b/build.sh @@ -15,38 +15,38 @@ cp target/debug/libbdk_ffi.so bdk-kotlin/jar/libs/x86_64_linux (cd bdk-kotlin && gradle :jar:build) -# rust android - -# If ANDROID_NDK_HOME is not set then set it to github actions default -[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle - -# Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 -export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin - -# Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 -export CFLAGS="-D__ANDROID_API__=21" - -# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on -BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" - -mkdir -p bdk-kotlin/aar/src/main/jniLibs/ bdk-kotlin/aar/src/main/jniLibs/arm64-v8a bdk-kotlin/aar/src/main/jniLibs/x86_64 bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a bdk-kotlin/aar/src/main/jniLibs/x86 - -if echo $BUILD_TARGETS | grep "aarch64"; then - CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android - cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/arm64-v8a -fi -if echo $BUILD_TARGETS | grep "x86_64"; then - CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android - cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86_64 -fi -if echo $BUILD_TARGETS | grep "armv7"; then - CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi - cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a -fi -if echo $BUILD_TARGETS | grep "i686"; then - CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android - cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86 -fi - -# bdk-kotlin aar -(cd bdk-kotlin && gradle :aar:build) +## rust android +# +## If ANDROID_NDK_HOME is not set then set it to github actions default +#[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle +# +## Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 +#export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin +# +## Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 +#export CFLAGS="-D__ANDROID_API__=21" +# +## IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on +#BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" +# +#mkdir -p bdk-kotlin/aar/src/main/jniLibs/ bdk-kotlin/aar/src/main/jniLibs/arm64-v8a bdk-kotlin/aar/src/main/jniLibs/x86_64 bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a bdk-kotlin/aar/src/main/jniLibs/x86 +# +#if echo $BUILD_TARGETS | grep "aarch64"; then +# CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android +# cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/arm64-v8a +#fi +#if echo $BUILD_TARGETS | grep "x86_64"; then +# CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android +# cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86_64 +#fi +#if echo $BUILD_TARGETS | grep "armv7"; then +# CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi +# cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a +#fi +#if echo $BUILD_TARGETS | grep "i686"; then +# CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android +# cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86 +#fi +# +## bdk-kotlin aar +#(cd bdk-kotlin && gradle :aar:build) diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index 48cc0fd..e28e5bc 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -6,40 +6,56 @@ int main (int argc, char const * const argv[]) { - 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 new wallet error + { + WalletResult_t *wallet_result = new_wallet_result("bad", "bad", NULL); + assert(wallet_result != NULL); + char *wallet_error = get_wallet_err(wallet_result); + assert(wallet_error != NULL); + //printf("wallet error: %s\n", wallet_error); + free_string(wallet_error); + free_wallet_result(wallet_result); + } - // test new_wallet + // test new wallet { - WalletPtr_t * wallet = new_wallet(name, desc, change); - assert(wallet != NULL); + 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/*)"; + + WalletResult_t *wallet_result = new_wallet_result(name, desc, change); + assert(wallet_result != NULL); // test sync_wallet - sync_wallet(wallet); + VoidResult_t *sync_result = sync_wallet(wallet_result); + free_void_result(sync_result); // test new_address - char * address1 = new_address(wallet); + StringResult_t *address_result1 = new_address(wallet_result); + char *address1 = get_string_ok(address_result1); //printf("address1: %s\n", address1); assert( 0 == strcmp(address1,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp")); free_string(address1); + free_string_result(address_result1); - char * address2 = new_address(wallet); + StringResult_t *address_result2 = new_address(wallet_result); + char *address2 = get_string_ok(address_result2); //printf("address2: %s\n", address2); assert(0 == strcmp(address2,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf")); free_string(address2); + free_string_result(address_result2); // test free_wallet - free_wallet(wallet); + free_wallet_result(wallet_result); // test free_wallet NULL doesn't crash - free_wallet(NULL); + free_wallet_result(NULL); // verify free_wallet after free_wallet fails (core dumped) - ////free_wallet(wallet); + ////free_wallet_result(wallet_result); // verify sync_wallet after free_wallet fails (core dumped) - ////sync_wallet(wallet); + ////VoidResult_t sync_result2 = sync_wallet(wallet_result); } return EXIT_SUCCESS; diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..fba774c --- /dev/null +++ b/src/error.rs @@ -0,0 +1,197 @@ +//use ::safer_ffi::prelude::*; +use bdk::Error; + +pub fn error_name(error: &bdk::Error) -> &'static str { + match error { + Error::InvalidU32Bytes(_) => "InvalidU32Bytes", + Error::Generic(_) => "Generic", + Error::ScriptDoesntHaveAddressForm => "ScriptDoesntHaveAddressForm", + Error::SingleRecipientMultipleOutputs => "SingleRecipientMultipleOutputs", + Error::SingleRecipientNoInputs => "SingleRecipientNoInputs", + Error::NoRecipients => "NoRecipients", + Error::NoUtxosSelected => "NoUtxosSelected", + Error::OutputBelowDustLimit(_) => "OutputBelowDustLimit", + Error::InsufficientFunds { .. } => "InsufficientFunds", + Error::BnBTotalTriesExceeded => "BnBTotalTriesExceeded", + Error::BnBNoExactMatch => "BnBNoExactMatch", + Error::UnknownUtxo => "UnknownUtxo", + Error::TransactionNotFound => "TransactionNotFound", + Error::TransactionConfirmed => "TransactionConfirmed", + Error::IrreplaceableTransaction => "IrreplaceableTransaction", + Error::FeeRateTooLow { .. } => "FeeRateTooLow", + Error::FeeTooLow { .. } => "FeeTooLow", + Error::MissingKeyOrigin(_) => "MissingKeyOrigin", + Error::Key(_) => "Key", + Error::ChecksumMismatch => "ChecksumMismatch", + Error::SpendingPolicyRequired(_) => "SpendingPolicyRequired", + Error::InvalidPolicyPathError(_) => "InvalidPolicyPathError", + Error::Signer(_) => "Signer", + Error::InvalidProgressValue(_) => "InvalidProgressValue", + Error::ProgressUpdateError => "ProgressUpdateError", + Error::InvalidOutpoint(_) => "InvalidOutpoint", + Error::Descriptor(_) => "Descriptor", + Error::AddressValidator(_) => "AddressValidator", + Error::Encode(_) => "Encode", + Error::Miniscript(_) => "Miniscript", + Error::Bip32(_) => "Bip32", + Error::Secp256k1(_) => "Secp256k1", + Error::Json(_) => "Json", + Error::Hex(_) => "Hex", + Error::Psbt(_) => "Psbt", + Error::Electrum(_) => "Electrum", +// Error::Esplora(_) => {} +// Error::CompactFilters(_) => {} + Error::Sled(_) => "Sled", + _ => "Unknown", + } +} + +//// Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet) +//// Simplified to work over the FFI interface +//#[derive_ReprC] +//#[repr(i16)] +//#[derive(Debug)] +//pub enum WalletError { +// None, +// InvalidU32Bytes, +// Generic, +// ScriptDoesntHaveAddressForm, +// SingleRecipientMultipleOutputs, +// SingleRecipientNoInputs, +// NoRecipients, +// NoUtxosSelected, +// OutputBelowDustLimit, +// InsufficientFunds, +// BnBTotalTriesExceeded, +// BnBNoExactMatch, +// UnknownUtxo, +// TransactionNotFound, +// TransactionConfirmed, +// IrreplaceableTransaction, +// FeeRateTooLow, +// FeeTooLow, +// FeeRateUnavailable, +// MissingKeyOrigin, +// Key, +// ChecksumMismatch, +// SpendingPolicyRequired, +// InvalidPolicyPathError, +// Signer, +// InvalidNetwork, +// InvalidProgressValue, +// ProgressUpdateError, +// InvalidOutpoint, +// Descriptor, +// AddressValidator, +// Encode, +// Miniscript, +// Bip32, +// Secp256k1, +// Json, +// Hex, +// Psbt, +// PsbtParse, +// //#[cfg(feature = "electrum")] +// Electrum, +// //#[cfg(feature = "esplora")] +// //Esplora, +// //#[cfg(feature = "compact_filters")] +// //CompactFilters, +// //#[cfg(feature = "key-value-db")] +// Sled, +//} + +//impl From for WalletError { +// fn from(error: bdk::Error) -> Self { +// match error { +// Error::InvalidU32Bytes(_) => WalletError::InvalidNetwork, +// Error::Generic(_) => WalletError::Generic, +// Error::ScriptDoesntHaveAddressForm => WalletError::ScriptDoesntHaveAddressForm, +// Error::SingleRecipientMultipleOutputs => WalletError::SingleRecipientMultipleOutputs, +// Error::SingleRecipientNoInputs => WalletError::SingleRecipientNoInputs, +// Error::NoRecipients => WalletError::NoRecipients, +// Error::NoUtxosSelected => WalletError::NoUtxosSelected, +// Error::OutputBelowDustLimit(_) => WalletError::OutputBelowDustLimit, +// Error::InsufficientFunds { .. } => WalletError::InsufficientFunds, +// Error::BnBTotalTriesExceeded => WalletError::BnBTotalTriesExceeded, +// Error::BnBNoExactMatch => WalletError::BnBNoExactMatch, +// Error::UnknownUtxo => WalletError::UnknownUtxo, +// Error::TransactionNotFound => WalletError::TransactionNotFound, +// Error::TransactionConfirmed => WalletError::TransactionConfirmed, +// Error::IrreplaceableTransaction => WalletError::IrreplaceableTransaction, +// Error::FeeRateTooLow { .. } => WalletError::FeeRateTooLow, +// Error::FeeTooLow { .. } => WalletError::FeeTooLow, +// Error::MissingKeyOrigin(_) => WalletError::MissingKeyOrigin, +// Error::Key(_) => WalletError::Key, +// Error::ChecksumMismatch => WalletError::ChecksumMismatch, +// Error::SpendingPolicyRequired(_) => WalletError::SpendingPolicyRequired, +// Error::InvalidPolicyPathError(_) => WalletError::InvalidPolicyPathError, +// Error::Signer(_) => WalletError::Signer, +// Error::InvalidProgressValue(_) => WalletError::InvalidProgressValue, +// Error::ProgressUpdateError => WalletError::ProgressUpdateError, +// Error::InvalidOutpoint(_) => WalletError::InvalidOutpoint, +// Error::Descriptor(_) => WalletError::Descriptor, +// Error::AddressValidator(_) => WalletError::AddressValidator, +// Error::Encode(_) => WalletError::Encode, +// Error::Miniscript(_) => WalletError::Miniscript, +// Error::Bip32(_) => WalletError::Bip32, +// Error::Secp256k1(_) => WalletError::Secp256k1, +// Error::Json(_) => WalletError::Json, +// Error::Hex(_) => WalletError::Hex, +// Error::Psbt(_) => WalletError::Psbt, +// Error::Electrum(_) => WalletError::Electrum, +// //Error::Esplora(_) => WalletError::Esplora, +// //Error::CompactFilters(_) => {} +// Error::Sled(_) => WalletError::Sled, +// } +// } +//} + +//type error_code = i16; +// +//impl From for error_code { +// fn from(error: bdk::Error) -> Self { +// match error { +// Error::InvalidU32Bytes(_) => 1, +// Error::Generic(_) => 2, +// Error::ScriptDoesntHaveAddressForm => 3, +// Error::SingleRecipientMultipleOutputs => 4, +// Error::SingleRecipientNoInputs => 5, +// Error::NoRecipients => 6, +// Error::NoUtxosSelected => 7, +// Error::OutputBelowDustLimit(_) => 8, +// Error::InsufficientFunds { .. } => 9, +// Error::BnBTotalTriesExceeded => 10, +// Error::BnBNoExactMatch => 11, +// Error::UnknownUtxo => 12, +// Error::TransactionNotFound => 13, +// Error::TransactionConfirmed => 14, +// Error::IrreplaceableTransaction => 15, +// Error::FeeRateTooLow { .. } => 16, +// Error::FeeTooLow { .. } => 17, +// Error::MissingKeyOrigin(_) => 18, +// Error::Key(_) => 19, +// Error::ChecksumMismatch => 20, +// Error::SpendingPolicyRequired(_) => 21, +// Error::InvalidPolicyPathError(_) => 22, +// Error::Signer(_) => 23, +// Error::InvalidProgressValue(_) => 24, +// Error::ProgressUpdateError => 25, +// Error::InvalidOutpoint(_) => 26, +// Error::Descriptor(_) => 27, +// Error::AddressValidator(_) => 28, +// Error::Encode(_) => 29, +// Error::Miniscript(_) => 30, +// Error::Bip32(_) => 31, +// Error::Secp256k1(_) => 32, +// Error::Json(_) => 33, +// Error::Hex(_) => 34, +// Error::Psbt(_) => 35, +// Error::Electrum(_) => 36, +// //Error::Esplora(_) => WalletError::Esplora, +// //Error::CompactFilters(_) => {} +// Error::Sled(_) => 37, +// _ => -1 +// } +// } +//} diff --git a/src/wallet.rs b/src/wallet.rs index 1a9156b..87b1fcc 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -6,33 +6,84 @@ use bdk::blockchain::{ }; use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase}; use bdk::wallet::AddressIndex::New; -use bdk::Wallet; +use bdk::{Error, 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 } - } +pub struct VoidResult { + raw: Result<(), String>, } #[ffi_export] -fn new_wallet( +fn get_void_err(void_result: &VoidResult) -> Option { + void_result + .raw + .as_ref() + .err() + .map(|s| char_p_boxed::try_from(s.clone()).unwrap()) +} + +#[ffi_export] +fn free_void_result(void_result: Option>) { + drop(void_result) +} + +#[derive_ReprC] +#[ReprC::opaque] +pub struct StringResult { + raw: Result, +} + +#[ffi_export] +fn get_string_ok(string_result: &StringResult) -> Option { + string_result + .raw + .as_ref() + .ok() + .map(|s| char_p_boxed::try_from(s.clone()).unwrap()) +} + +#[ffi_export] +fn get_string_err(string_result: &StringResult) -> Option { + string_result + .raw + .as_ref() + .err() + .map(|s| char_p_boxed::try_from(s.clone()).unwrap()) +} + +#[ffi_export] +fn free_string_result(string_result: Option>) { + drop(string_result) +} + +#[derive_ReprC] +#[ReprC::opaque] +pub struct WalletResult { + raw: Result, String>, +} + +#[ffi_export] +fn new_wallet_result( name: char_p_ref, descriptor: char_p_ref, change_descriptor: Option, -) -> Box { - let network = Testnet; - let _name = name.to_string(); +) -> Box { + let name = name.to_string(); let descriptor = descriptor.to_string(); let change_descriptor = change_descriptor.map(|s| s.to_string()); + let wallet_result = new_wallet(name, descriptor, change_descriptor).map_err(|e| e.to_string()); + Box::new(WalletResult { raw: wallet_result }) +} +fn new_wallet( + name: String, + descriptor: String, + change_descriptor: Option, +) -> Result, Error> { + let network = Testnet; let electrum_config = AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { url: "ssl://electrum.blockstream.info:60002".to_string(), socks5: None, @@ -40,39 +91,52 @@ fn new_wallet( timeout: None, }); let blockchain_config = electrum_config; - let client = AnyBlockchain::from_config(&blockchain_config).unwrap(); + let client = AnyBlockchain::from_config(&blockchain_config)?; let database_config = AnyDatabaseConfig::Memory(()); - let database = AnyDatabase::from_config(&database_config).unwrap(); + let database = AnyDatabase::from_config(&database_config)?; 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)) + Wallet::new(descriptor, change_descriptor, network, database, client) } #[ffi_export] -fn sync_wallet(wallet: &WalletPtr) { - let _r = wallet.raw.sync(log_progress(), Some(100)); +fn get_wallet_err(wallet_result: &WalletResult) -> Option { + wallet_result + .raw + .as_ref() + .err() + .map(|s| char_p_boxed::try_from(s.clone()).unwrap()) } #[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() +fn sync_wallet(wallet_result: &WalletResult) -> Box { + let wallet_result_ref = wallet_result.raw.as_ref().map_err(|error| error.clone()); + let void_result = wallet_result_ref + .and_then(|w| w.sync(log_progress(), Some(100)).map_err(|e| e.to_string())); + Box::new(VoidResult { raw: void_result }) +} + +#[ffi_export] +fn new_address(wallet_result: &WalletResult) -> Box { + let new_address = wallet_result + .raw + .as_ref() + .map_err(|error| error.clone()) + .and_then(|w| w.get_address(New).map_err(|error| error.to_string())); + let string_result = new_address.map(|a| a.to_string()); + Box::new(StringResult { raw: string_result }) +} + +#[ffi_export] +fn free_wallet_result(wallet_result: Option>) { + drop(wallet_result) } /// Frees a Rust-allocated string #[ffi_export] -fn free_string(string: char_p_boxed) { +fn free_string(string: Option) { drop(string) } - -#[ffi_export] -fn free_wallet(wallet: Option>) { - drop(wallet) -} diff --git a/test.sh b/test.sh index da24835..145cdca 100755 --- a/test.sh +++ b/test.sh @@ -6,9 +6,9 @@ cargo test --features c-headers -- generate_headers # cc export LD_LIBRARY_PATH=`pwd`/target/debug -valgrind --leak-check=full cc/bdk_ffi_test -#cc/bdk_ffi_test +#valgrind --leak-check=full cc/bdk_ffi_test +cc/bdk_ffi_test # bdk-kotlin (cd bdk-kotlin && gradle test) -(cd bdk-kotlin && gradle :aar:connectedDebugAndroidTest) +#(cd bdk-kotlin && gradle :aar:connectedDebugAndroidTest) From a830d9b08236233c82188a8550171fe28caec9e6 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 20 Jun 2021 18:48:48 -0700 Subject: [PATCH 011/272] Rename gradle modules to jvm and android --- bdk-kotlin/{aar => android}/build.gradle | 2 +- .../{aar => android}/consumer-rules.pro | 0 .../{aar => android}/proguard-rules.pro | 0 .../org/bitcoindevkit/bdk/AndroidLibTest.kt | 0 .../src/main/AndroidManifest.xml | 0 bdk-kotlin/{jar => jvm}/build.gradle | 0 .../main/java/org/bitcoindevkit/bdk/Lib.kt | 0 .../java/org/bitcoindevkit/bdk/LibTest.kt | 0 bdk-kotlin/settings.gradle | 2 +- build.sh | 76 +++++++++---------- local.properties | 8 ++ test.sh | 2 +- 12 files changed, 49 insertions(+), 41 deletions(-) rename bdk-kotlin/{aar => android}/build.gradle (98%) rename bdk-kotlin/{aar => android}/consumer-rules.pro (100%) rename bdk-kotlin/{aar => android}/proguard-rules.pro (100%) rename bdk-kotlin/{aar => android}/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt (100%) rename bdk-kotlin/{aar => android}/src/main/AndroidManifest.xml (100%) rename bdk-kotlin/{jar => jvm}/build.gradle (100%) rename bdk-kotlin/{jar => jvm}/src/main/java/org/bitcoindevkit/bdk/Lib.kt (100%) rename bdk-kotlin/{jar => jvm}/src/test/java/org/bitcoindevkit/bdk/LibTest.kt (100%) create mode 100644 local.properties diff --git a/bdk-kotlin/aar/build.gradle b/bdk-kotlin/android/build.gradle similarity index 98% rename from bdk-kotlin/aar/build.gradle rename to bdk-kotlin/android/build.gradle index 6116737..2001232 100644 --- a/bdk-kotlin/aar/build.gradle +++ b/bdk-kotlin/android/build.gradle @@ -60,7 +60,7 @@ afterEvaluate { } dependencies { - implementation (project(':jar')) { + implementation (project(':jvm')) { exclude group: 'net.java.dev.jna', module: 'jna' } diff --git a/bdk-kotlin/aar/consumer-rules.pro b/bdk-kotlin/android/consumer-rules.pro similarity index 100% rename from bdk-kotlin/aar/consumer-rules.pro rename to bdk-kotlin/android/consumer-rules.pro diff --git a/bdk-kotlin/aar/proguard-rules.pro b/bdk-kotlin/android/proguard-rules.pro similarity index 100% rename from bdk-kotlin/aar/proguard-rules.pro rename to bdk-kotlin/android/proguard-rules.pro diff --git a/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt similarity index 100% rename from bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt rename to bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt diff --git a/bdk-kotlin/aar/src/main/AndroidManifest.xml b/bdk-kotlin/android/src/main/AndroidManifest.xml similarity index 100% rename from bdk-kotlin/aar/src/main/AndroidManifest.xml rename to bdk-kotlin/android/src/main/AndroidManifest.xml diff --git a/bdk-kotlin/jar/build.gradle b/bdk-kotlin/jvm/build.gradle similarity index 100% rename from bdk-kotlin/jar/build.gradle rename to bdk-kotlin/jvm/build.gradle diff --git a/bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Lib.kt similarity index 100% rename from bdk-kotlin/jar/src/main/java/org/bitcoindevkit/bdk/Lib.kt rename to bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Lib.kt diff --git a/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/LibTest.kt similarity index 100% rename from bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt rename to bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/LibTest.kt diff --git a/bdk-kotlin/settings.gradle b/bdk-kotlin/settings.gradle index 17a73f5..dbc97eb 100644 --- a/bdk-kotlin/settings.gradle +++ b/bdk-kotlin/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = 'bdk-kotlin' -include ':jar',':aar' \ No newline at end of file +include ':jvm',':android' \ No newline at end of file diff --git a/build.sh b/build.sh index b8b5c01..831687a 100755 --- a/build.sh +++ b/build.sh @@ -10,43 +10,43 @@ export LD_LIBRARY_PATH=`pwd`/target/debug cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m # bdk-kotlin jar -mkdir -p bdk-kotlin/jar/libs/x86_64_linux -cp target/debug/libbdk_ffi.so bdk-kotlin/jar/libs/x86_64_linux +mkdir -p bdk-kotlin/jvm/libs/x86_64_linux +cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/libs/x86_64_linux -(cd bdk-kotlin && gradle :jar:build) +(cd bdk-kotlin && gradle :jvm:build) -## rust android -# -## If ANDROID_NDK_HOME is not set then set it to github actions default -#[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle -# -## Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 -#export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin -# -## Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 -#export CFLAGS="-D__ANDROID_API__=21" -# -## IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on -#BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" -# -#mkdir -p bdk-kotlin/aar/src/main/jniLibs/ bdk-kotlin/aar/src/main/jniLibs/arm64-v8a bdk-kotlin/aar/src/main/jniLibs/x86_64 bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a bdk-kotlin/aar/src/main/jniLibs/x86 -# -#if echo $BUILD_TARGETS | grep "aarch64"; then -# CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android -# cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/arm64-v8a -#fi -#if echo $BUILD_TARGETS | grep "x86_64"; then -# CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android -# cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86_64 -#fi -#if echo $BUILD_TARGETS | grep "armv7"; then -# CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi -# cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a -#fi -#if echo $BUILD_TARGETS | grep "i686"; then -# CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android -# cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86 -#fi -# -## bdk-kotlin aar -#(cd bdk-kotlin && gradle :aar:build) +# rust android + +# If ANDROID_NDK_HOME is not set then set it to github actions default +[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle + +# Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 +export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin + +# Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 +export CFLAGS="-D__ANDROID_API__=21" + +# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on +BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" + +mkdir -p bdk-kotlin/android/src/main/jniLibs/ bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-kotlin/android/src/main/jniLibs/x86 + +if echo $BUILD_TARGETS | grep "aarch64"; then + CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android + cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/arm64-v8a +fi +if echo $BUILD_TARGETS | grep "x86_64"; then + CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android + cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86_64 +fi +if echo $BUILD_TARGETS | grep "armv7"; then + CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi + cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/armeabi-v7a +fi +if echo $BUILD_TARGETS | grep "i686"; then + CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android + cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86 +fi + +# bdk-kotlin aar +(cd bdk-kotlin && gradle :android:build) diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..52889ad --- /dev/null +++ b/local.properties @@ -0,0 +1,8 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Sun Jun 20 18:41:16 PDT 2021 +sdk.dir=/home/steve/Android/Sdk diff --git a/test.sh b/test.sh index 145cdca..e44aad0 100755 --- a/test.sh +++ b/test.sh @@ -11,4 +11,4 @@ cc/bdk_ffi_test # bdk-kotlin (cd bdk-kotlin && gradle test) -#(cd bdk-kotlin && gradle :aar:connectedDebugAndroidTest) +#(cd bdk-kotlin && gradle :android:connectedDebugAndroidTest) From f1c1524e61d07293d172abe17a1162185019c621 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 20 Jun 2021 20:25:57 -0700 Subject: [PATCH 012/272] Add kotlin test-fixtures module used by jvm and android --- bdk-kotlin/android/build.gradle | 7 ++- .../org/bitcoindevkit/bdk/AndroidLibTest.kt | 48 +------------------ bdk-kotlin/jvm/build.gradle | 1 + .../java/org/bitcoindevkit/bdk/JvmLibTest.kt | 15 ++++++ bdk-kotlin/settings.gradle | 2 +- bdk-kotlin/test-fixtures/build.gradle | 17 +++++++ .../java/org/bitcoindevkit/bdk/LibTest.kt | 8 ++-- test.sh | 2 +- 8 files changed, 46 insertions(+), 54 deletions(-) create mode 100644 bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt create mode 100644 bdk-kotlin/test-fixtures/build.gradle rename bdk-kotlin/{jvm/src/test => test-fixtures/src/main}/java/org/bitcoindevkit/bdk/LibTest.kt (92%) diff --git a/bdk-kotlin/android/build.gradle b/bdk-kotlin/android/build.gradle index 2001232..2236add 100644 --- a/bdk-kotlin/android/build.gradle +++ b/bdk-kotlin/android/build.gradle @@ -64,11 +64,14 @@ dependencies { exclude group: 'net.java.dev.jna', module: 'jna' } - api 'net.java.dev.jna:jna:5.8.0@aar' + implementation 'net.java.dev.jna:jna:5.8.0@aar' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.core:core-ktx:1.5.0' - testImplementation 'junit:junit:4.12' + + androidTestImplementation (project(':test-fixtures')) { + exclude group: 'net.java.dev.jna', module: 'jna' + } androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' diff --git a/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt index 9a3e7ba..a104f6a 100644 --- a/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt +++ b/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt @@ -9,56 +9,12 @@ import org.junit.runner.RunWith import org.junit.Assert.* -//import org.bitcoindevkit.bdkjni.Types.Network -//import org.bitcoindevkit.bdkjni.Types.WalletConstructor -//import org.bitcoindevkit.bdkjni.Types.WalletPtr - /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) -class AndroidLibTest { - - companion object { - private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) - private lateinit var wallet: Lib.WalletPtr_t - - @BeforeClass - @JvmStatic - fun create_wallet() { - val name = "test_wallet" - val desc = - "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val change = - "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" - - wallet = bdkFfi.new_wallet(name, desc, change) - Log.d("create_wallet", "wallet created") - } - - @AfterClass - @JvmStatic - fun free_wallet() { - bdkFfi.free_wallet(wallet) - Log.d("free_wallet", "wallet freed") - } - } - - @Test - fun sync() { - bdkFfi.sync_wallet(wallet) - Log.d("sync", "wallet synced") - } - - @Test - fun new_address() { - val pointer = bdkFfi.new_address(wallet) - val address = pointer.getString(0) - bdkFfi.free_string(pointer) - //println("address created from kotlin: $address") - assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") - Log.d("new_address", "new address: $address") - } +class AndroidLibTest : LibTest() { + } diff --git a/bdk-kotlin/jvm/build.gradle b/bdk-kotlin/jvm/build.gradle index ba7039e..3b1618a 100644 --- a/bdk-kotlin/jvm/build.gradle +++ b/bdk-kotlin/jvm/build.gradle @@ -21,6 +21,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "net.java.dev.jna:jna:5.8.0" testImplementation 'org.jetbrains.kotlin:kotlin-test-junit' + testImplementation (project(':test-fixtures')) } publishing { diff --git a/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt b/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt new file mode 100644 index 0000000..2ce5b1a --- /dev/null +++ b/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt @@ -0,0 +1,15 @@ +package org.bitcoindevkit.bdk + +import com.sun.jna.Native +import org.junit.* +import org.junit.Assert.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +/** + * Library test, which will execute on linux host. + * + */ +class JvmLibTest : LibTest() { + +} diff --git a/bdk-kotlin/settings.gradle b/bdk-kotlin/settings.gradle index dbc97eb..d38f719 100644 --- a/bdk-kotlin/settings.gradle +++ b/bdk-kotlin/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = 'bdk-kotlin' -include ':jvm',':android' \ No newline at end of file +include ':jvm',':android',':test-fixtures' \ No newline at end of file diff --git a/bdk-kotlin/test-fixtures/build.gradle b/bdk-kotlin/test-fixtures/build.gradle new file mode 100644 index 0000000..f8fdc15 --- /dev/null +++ b/bdk-kotlin/test-fixtures/build.gradle @@ -0,0 +1,17 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'java-library' +} + +dependencies { + implementation platform('org.jetbrains.kotlin:kotlin-bom') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "net.java.dev.jna:jna:5.8.0" + implementation (project(':jvm')) + implementation 'org.jetbrains.kotlin:kotlin-test-junit' +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt similarity index 92% rename from bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/LibTest.kt rename to bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt index 4b86784..1fd545a 100644 --- a/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt @@ -10,7 +10,7 @@ import kotlin.test.assertNull * Library test, which will execute on linux host. * */ -class LibTest { +abstract class LibTest { companion object { private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) @@ -40,12 +40,12 @@ class LibTest { @Test fun wallet_sync_error() { val bad_wallet_result = bdkFfi.new_wallet_result("test", "bad", null) - println("wallet result created") + //println("wallet result created") val sync_result = bdkFfi.sync_wallet(bad_wallet_result) val sync_err_pointer = bdkFfi.get_void_err(sync_result) assertNotNull(sync_err_pointer) - val sync_err = sync_err_pointer.getString(0) - println("wallet sync error $sync_err") + val sync_err = sync_err_pointer!!.getString(0) + //println("wallet sync error $sync_err") } @Test diff --git a/test.sh b/test.sh index e44aad0..f6f86d5 100755 --- a/test.sh +++ b/test.sh @@ -11,4 +11,4 @@ cc/bdk_ffi_test # bdk-kotlin (cd bdk-kotlin && gradle test) -#(cd bdk-kotlin && gradle :android:connectedDebugAndroidTest) +(cd bdk-kotlin && gradle :android:connectedDebugAndroidTest) From adb54e3b87a19de6f756c773da6634988eb6963b Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 20 Jun 2021 23:39:42 -0700 Subject: [PATCH 013/272] Add slf4j logging for kotlin jvm and android --- bdk-kotlin/android/build.gradle | 13 +++---------- .../android/src/androidTest/assets/logback.xml | 14 ++++++++++++++ bdk-kotlin/jvm/build.gradle | 3 +++ bdk-kotlin/test-fixtures/build.gradle | 1 + .../src/main/java/org/bitcoindevkit/bdk/LibTest.kt | 14 +++++++++----- 5 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 bdk-kotlin/android/src/androidTest/assets/logback.xml diff --git a/bdk-kotlin/android/build.gradle b/bdk-kotlin/android/build.gradle index 2236add..8cfcfc3 100644 --- a/bdk-kotlin/android/build.gradle +++ b/bdk-kotlin/android/build.gradle @@ -1,6 +1,5 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -//apply plugin: 'kotlin-android-extensions' apply plugin: 'maven-publish' android { @@ -24,15 +23,7 @@ android { } } -//task buildRust(type: Exec) { -// workingDir '../' -// commandLine './build.sh' -//} - afterEvaluate { -// android.libraryVariants.all { variant -> -// variant.javaCompileProvider.get().dependsOn(buildRust) -// } publishing { publications { @@ -68,7 +59,9 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.core:core-ktx:1.5.0' - + api "org.slf4j:slf4j-api:1.7.30" + + androidTestImplementation 'com.github.tony19:logback-android:2.0.0' androidTestImplementation (project(':test-fixtures')) { exclude group: 'net.java.dev.jna', module: 'jna' } diff --git a/bdk-kotlin/android/src/androidTest/assets/logback.xml b/bdk-kotlin/android/src/androidTest/assets/logback.xml new file mode 100644 index 0000000..8f7c81b --- /dev/null +++ b/bdk-kotlin/android/src/androidTest/assets/logback.xml @@ -0,0 +1,14 @@ + + + + %logger{12} + + + [%-20thread] %msg + + + + + + + \ No newline at end of file diff --git a/bdk-kotlin/jvm/build.gradle b/bdk-kotlin/jvm/build.gradle index 3b1618a..d22bf52 100644 --- a/bdk-kotlin/jvm/build.gradle +++ b/bdk-kotlin/jvm/build.gradle @@ -20,6 +20,9 @@ dependencies { implementation platform('org.jetbrains.kotlin:kotlin-bom') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "net.java.dev.jna:jna:5.8.0" + api "org.slf4j:slf4j-api:1.7.30" + testImplementation "ch.qos.logback:logback-classic:1.2.3" + testImplementation "ch.qos.logback:logback-core:1.2.3" testImplementation 'org.jetbrains.kotlin:kotlin-test-junit' testImplementation (project(':test-fixtures')) } diff --git a/bdk-kotlin/test-fixtures/build.gradle b/bdk-kotlin/test-fixtures/build.gradle index f8fdc15..46b7292 100644 --- a/bdk-kotlin/test-fixtures/build.gradle +++ b/bdk-kotlin/test-fixtures/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation "net.java.dev.jna:jna:5.8.0" implementation (project(':jvm')) implementation 'org.jetbrains.kotlin:kotlin-test-junit' + api "org.slf4j:slf4j-api:1.7.30" } java { diff --git a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt index 1fd545a..9ef5b4e 100644 --- a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt @@ -3,6 +3,8 @@ package org.bitcoindevkit.bdk import com.sun.jna.Native import org.junit.* import org.junit.Assert.assertEquals +import org.slf4j.Logger +import org.slf4j.LoggerFactory import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -13,6 +15,8 @@ import kotlin.test.assertNull abstract class LibTest { companion object { + private val log: Logger = LoggerFactory.getLogger(LibTest::class.java) + private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) private lateinit var wallet_result: Lib.WalletResult_t @@ -26,26 +30,26 @@ abstract class LibTest { "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" wallet_result = bdkFfi.new_wallet_result(name, desc, change) - //println("wallet created") + log.debug("wallet created") } @AfterClass @JvmStatic fun free_wallet() { bdkFfi.free_wallet_result(wallet_result) - //println("wallet freed") + log.debug("wallet freed") } } @Test fun wallet_sync_error() { val bad_wallet_result = bdkFfi.new_wallet_result("test", "bad", null) - //println("wallet result created") + log.debug("wallet result created") val sync_result = bdkFfi.sync_wallet(bad_wallet_result) val sync_err_pointer = bdkFfi.get_void_err(sync_result) assertNotNull(sync_err_pointer) val sync_err = sync_err_pointer!!.getString(0) - //println("wallet sync error $sync_err") + log.debug("wallet sync error $sync_err") } @Test @@ -64,7 +68,7 @@ abstract class LibTest { bdkFfi.free_string_result(address_result) bdkFfi.free_string(address_pointer) - //println("address created from kotlin: $address") + log.debug("address created from kotlin: $address") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") } } From e6fabc81b37a72a9c745e6dc74bd1749e60354ba Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 21 Jun 2021 14:55:56 -0700 Subject: [PATCH 014/272] Rename Lib to LibJna, add LibBase abstract class --- .../java/org/bitcoindevkit/bdk/LibBase.kt | 9 +++++ .../bitcoindevkit/bdk/{Lib.kt => LibJna.kt} | 2 +- .../java/org/bitcoindevkit/bdk/LibTest.kt | 34 +++++++++---------- 3 files changed, 26 insertions(+), 19 deletions(-) create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibBase.kt rename bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/{Lib.kt => LibJna.kt} (98%) diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibBase.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibBase.kt new file mode 100644 index 0000000..e00eec0 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibBase.kt @@ -0,0 +1,9 @@ +package org.bitcoindevkit.bdk + +import com.sun.jna.Native + +abstract class LibBase { + + protected val libJna: LibJna + get() = Native.load("bdk_ffi", LibJna::class.java) +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Lib.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt similarity index 98% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Lib.kt rename to bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt index feea907..411de5b 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Lib.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt @@ -2,7 +2,7 @@ package org.bitcoindevkit.bdk import com.sun.jna.* -interface Lib : Library { +interface LibJna : Library { // typedef struct VoidResult VoidResult_t; class VoidResult_t : PointerType { diff --git a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt index 9ef5b4e..7bb236f 100644 --- a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt @@ -1,6 +1,5 @@ package org.bitcoindevkit.bdk -import com.sun.jna.Native import org.junit.* import org.junit.Assert.assertEquals import org.slf4j.Logger @@ -14,11 +13,10 @@ import kotlin.test.assertNull */ abstract class LibTest { - companion object { + companion object : LibBase() { private val log: Logger = LoggerFactory.getLogger(LibTest::class.java) - private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) - private lateinit var wallet_result: Lib.WalletResult_t + private lateinit var wallet_result: LibJna.WalletResult_t @BeforeClass @JvmStatic @@ -29,45 +27,45 @@ abstract class LibTest { val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" - wallet_result = bdkFfi.new_wallet_result(name, desc, change) + wallet_result = libJna.new_wallet_result(name, desc, change) log.debug("wallet created") } @AfterClass @JvmStatic fun free_wallet() { - bdkFfi.free_wallet_result(wallet_result) + libJna.free_wallet_result(wallet_result) log.debug("wallet freed") } } @Test fun wallet_sync_error() { - val bad_wallet_result = bdkFfi.new_wallet_result("test", "bad", null) + val bad_wallet_result = libJna.new_wallet_result("test", "bad", null) log.debug("wallet result created") - val sync_result = bdkFfi.sync_wallet(bad_wallet_result) - val sync_err_pointer = bdkFfi.get_void_err(sync_result) + val sync_result = libJna.sync_wallet(bad_wallet_result) + val sync_err_pointer = libJna.get_void_err(sync_result) assertNotNull(sync_err_pointer) - val sync_err = sync_err_pointer!!.getString(0) + val sync_err = sync_err_pointer.getString(0) log.debug("wallet sync error $sync_err") } @Test fun sync() { - val sync_result = bdkFfi.sync_wallet(wallet_result) - assertNull(bdkFfi.get_void_err(sync_result)) - bdkFfi.free_void_result(sync_result) + val sync_result = libJna.sync_wallet(wallet_result) + assertNull(libJna.get_void_err(sync_result)) + libJna.free_void_result(sync_result) } @Test fun new_newaddress_wallet() { - val address_result = bdkFfi.new_address(wallet_result) - assertNull(bdkFfi.get_string_err(address_result)) - val address_pointer = bdkFfi.get_string_ok(address_result); + val address_result = libJna.new_address(wallet_result) + assertNull(libJna.get_string_err(address_result)) + val address_pointer = libJna.get_string_ok(address_result); val address = address_pointer!!.getString(0) - bdkFfi.free_string_result(address_result) - bdkFfi.free_string(address_pointer) + libJna.free_string_result(address_result) + libJna.free_string(address_pointer) log.debug("address created from kotlin: $address") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") } From 90c4fd33281c5d2a01f87dfa243df52189ac5ebd Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 21 Jun 2021 15:08:39 -0700 Subject: [PATCH 015/272] Add jna lib to jvm jar resources --- bdk-kotlin/jvm/build.gradle | 12 ------------ build.sh | 4 ++-- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/bdk-kotlin/jvm/build.gradle b/bdk-kotlin/jvm/build.gradle index d22bf52..d356464 100644 --- a/bdk-kotlin/jvm/build.gradle +++ b/bdk-kotlin/jvm/build.gradle @@ -4,18 +4,6 @@ plugins { id 'maven-publish' } -test { - environment "LD_LIBRARY_PATH", file("${projectDir}/libs/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:$kotlin_version" diff --git a/build.sh b/build.sh index 831687a..c0f2ecd 100755 --- a/build.sh +++ b/build.sh @@ -10,8 +10,8 @@ export LD_LIBRARY_PATH=`pwd`/target/debug cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m # bdk-kotlin jar -mkdir -p bdk-kotlin/jvm/libs/x86_64_linux -cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/libs/x86_64_linux +mkdir -p bdk-kotlin/jvm/src/main/resources/jnaLibs/x86_64_linux +cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/src/main/resources/jnaLibs/x86_64_linux (cd bdk-kotlin && gradle :jvm:build) From d248bca29901f5e357c05b2e33cb160c1ce67a9f Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 21 Jun 2021 17:01:53 -0700 Subject: [PATCH 016/272] Add classes to wrap LibJna native types --- bdk-kotlin/jvm/build.gradle | 6 + .../main/java/org/bitcoindevkit/bdk/Error.kt | 43 +++++ .../main/java/org/bitcoindevkit/bdk/LibJna.kt | 32 +++- .../org/bitcoindevkit/bdk/StringResult.kt | 32 ++++ .../java/org/bitcoindevkit/bdk/VoidResult.kt | 25 +++ .../main/java/org/bitcoindevkit/bdk/Wallet.kt | 25 +++ .../org/bitcoindevkit/bdk/WalletResult.kt | 36 ++++ bdk-kotlin/test-fixtures/build.gradle | 1 + .../java/org/bitcoindevkit/bdk/LibTest.kt | 92 +++++----- cc/bdk_ffi_test.c | 23 ++- src/error.rs | 159 +----------------- src/lib.rs | 2 + src/wallet.rs | 52 ++++-- 13 files changed, 291 insertions(+), 237 deletions(-) create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Error.kt create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt diff --git a/bdk-kotlin/jvm/build.gradle b/bdk-kotlin/jvm/build.gradle index d356464..9640d69 100644 --- a/bdk-kotlin/jvm/build.gradle +++ b/bdk-kotlin/jvm/build.gradle @@ -4,6 +4,12 @@ plugins { id 'maven-publish' } +test { + testLogging { + events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" + } +} + dependencies { implementation platform('org.jetbrains.kotlin:kotlin-bom') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Error.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Error.kt new file mode 100644 index 0000000..aeda367 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Error.kt @@ -0,0 +1,43 @@ +package org.bitcoindevkit.bdk + +enum class Error { + InvalidU32Bytes, + Generic, + ScriptDoesntHaveAddressForm, + SingleRecipientMultipleOutputs, + SingleRecipientNoInputs, + NoRecipients, + NoUtxosSelected, + OutputBelowDustLimit, + InsufficientFunds, + BnBTotalTriesExceeded, + BnBNoExactMatch, + UnknownUtxo, + TransactionNotFound, + TransactionConfirmed, + IrreplaceableTransaction, + FeeRateTooLow, + FeeTooLow, + MissingKeyOrigin, + Key, + ChecksumMismatch, + SpendingPolicyRequired, + InvalidPolicyPathError, + Signer, + InvalidProgressValue, + ProgressUpdateError, + InvalidOutpoint, + Descriptor, + AddressValidator, + Encode, + Miniscript, + Bip32, + Secp256k1, + Json, + Hex, + Psbt, + Electrum, +// Esplora +// CompactFilters + Sled, +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt index 411de5b..d186ef5 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt @@ -35,10 +35,16 @@ interface LibJna : Library { // void free_string_result ( // StringResult_t * string_result); fun free_string_result(string_result: StringResult_t) - - // void free_string ( - // char * string); - fun free_string(string: Pointer?) + + // typedef struct WalletRef WalletRef_t; + class WalletRef_t : PointerType { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + // void free_wallet_ref ( + // WalletRef_t * wallet_ref); + fun free_wallet_ref(wallet_ref: WalletRef_t) // typedef struct WalletResult WalletResult_t; class WalletResult_t : PointerType { @@ -58,17 +64,25 @@ interface LibJna : Library { // char * get_wallet_err ( // WalletResult_t const * wallet_result); - // TODO + fun get_wallet_err(wallet_result: WalletResult_t): Pointer? + + // WalletRef_t * get_wallet_ok ( + // WalletResult_t const * wallet_result); + fun get_wallet_ok(wallet_result: WalletResult_t): WalletRef_t? // VoidResult_t * sync_wallet ( - // WalletResult_t const * wallet_result); - fun sync_wallet(wallet_result: WalletResult_t): VoidResult_t + // WalletRef_t const * wallet_ref); + fun sync_wallet(wallet_ref: Pointer): VoidResult_t // StringResult_t * new_address ( - // WalletResult_t const * wallet_result); - fun new_address(wallet_result: WalletResult_t): StringResult_t + // WalletRef_t const * wallet_ref); + fun new_address(wallet_ref: Pointer): StringResult_t // void free_wallet_result ( // WalletResult_t * wallet_result); fun free_wallet_result(wallet_result: WalletResult_t) + + // void free_string ( + // char * string); + fun free_string(string: Pointer?) } diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt new file mode 100644 index 0000000..14e8c5a --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt @@ -0,0 +1,32 @@ +package org.bitcoindevkit.bdk + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class StringResult internal constructor(private val stringResultT: LibJna.StringResult_t) : LibBase() { + + private val log: Logger = LoggerFactory.getLogger(StringResult::class.java) + + fun isErr(): Boolean { + return libJna.get_string_err(stringResultT) != null + } + + fun err(): String? { + val errPointer = libJna.get_string_err(stringResultT) + val err = errPointer?.getString(0) + libJna.free_string(errPointer) + return err + } + + fun ok(): String? { + val okPointer = libJna.get_string_ok(stringResultT) + val ok = okPointer?.getString(0) + libJna.free_string(okPointer) + return ok + } + + protected fun finalize() { + libJna.free_string_result(stringResultT) + log.debug("StringResult_t freed") + } +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt new file mode 100644 index 0000000..50b1c86 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt @@ -0,0 +1,25 @@ +package org.bitcoindevkit.bdk + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class VoidResult internal constructor(private val voidResultT: LibJna.VoidResult_t) : LibBase() { + + private val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) + + fun isErr(): Boolean { + return libJna.get_void_err(voidResultT) != null + } + + fun err(): String? { + val errPointer = libJna.get_void_err(voidResultT) + val err = errPointer?.getString(0) + libJna.free_string(errPointer) + return err + } + + protected fun finalize() { + libJna.free_void_result(voidResultT) + log.debug("VoidResult_t freed") + } +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt new file mode 100644 index 0000000..5ae9878 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt @@ -0,0 +1,25 @@ +package org.bitcoindevkit.bdk + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class Wallet internal constructor( + private val walletRefT: LibJna.WalletRef_t, +) : LibBase() { + + private val log: Logger = LoggerFactory.getLogger(Wallet::class.java) + + fun sync(): VoidResult { + return VoidResult(libJna.sync_wallet(walletRefT.pointer)) + } + + fun getAddress(): StringResult { + return StringResult(libJna.new_address(walletRefT.pointer)) + } + + protected fun finalize() { + libJna.free_wallet_ref(walletRefT) + log.debug("WalletRef_t freed") + } + +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt new file mode 100644 index 0000000..2ac325d --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt @@ -0,0 +1,36 @@ +package org.bitcoindevkit.bdk + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class WalletResult( + name: String, + descriptor: String, + changeDescriptor: String?, +) : LibBase() { + + private val log: Logger = LoggerFactory.getLogger(WalletResult::class.java) + private val walletResultT = libJna.new_wallet_result(name, descriptor, changeDescriptor) + + fun isErr(): Boolean { + return libJna.get_wallet_err(walletResultT) != null + } + + fun err(): String? { + val errPointer = libJna.get_wallet_err(walletResultT) + val err = errPointer?.getString(0) + libJna.free_string(errPointer) + return err + } + + fun ok(): Wallet? { + val okWalletRef = libJna.get_wallet_ok(walletResultT) + return if (okWalletRef != null) Wallet(okWalletRef) else null + } + + protected fun finalize() { + libJna.free_wallet_result(walletResultT) + log.debug("WalletResult_t freed") + } + +} \ No newline at end of file diff --git a/bdk-kotlin/test-fixtures/build.gradle b/bdk-kotlin/test-fixtures/build.gradle index 46b7292..6c6b4ca 100644 --- a/bdk-kotlin/test-fixtures/build.gradle +++ b/bdk-kotlin/test-fixtures/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation "net.java.dev.jna:jna:5.8.0" implementation (project(':jvm')) implementation 'org.jetbrains.kotlin:kotlin-test-junit' + //implementation "org.mockito.kotlin:mockito-kotlin:3.2.0" api "org.slf4j:slf4j-api:1.7.30" } diff --git a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt index 7bb236f..49e6d85 100644 --- a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt @@ -1,71 +1,67 @@ package org.bitcoindevkit.bdk import org.junit.* -import org.junit.Assert.assertEquals +import org.junit.Assert.* import org.slf4j.Logger import org.slf4j.LoggerFactory -import kotlin.test.assertNotNull -import kotlin.test.assertNull /** * Library test, which will execute on linux host. * */ -abstract class LibTest { +abstract class LibTest : LibBase() { - companion object : LibBase() { - private val log: Logger = LoggerFactory.getLogger(LibTest::class.java) - - private lateinit var wallet_result: LibJna.WalletResult_t + private val log: Logger = LoggerFactory.getLogger(LibTest::class.java) - @BeforeClass - @JvmStatic - fun new_wallet() { - val name = "test_wallet" - val desc = - "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val change = - "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" + val name = "test_wallet" + val desc = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + val change = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" - wallet_result = libJna.new_wallet_result(name, desc, change) - log.debug("wallet created") - } - - @AfterClass - @JvmStatic - fun free_wallet() { - libJna.free_wallet_result(wallet_result) - log.debug("wallet freed") + @Test + fun walletResultError() { + val badWalletResult = WalletResult("bad", "bad", "bad") + assertTrue(badWalletResult.isErr()) + val walletErr = badWalletResult.err() + assertNotNull(walletErr) + log.debug("wallet error $walletErr") + assertEquals("Descriptor", walletErr) + val wallet = badWalletResult.ok() + assertNull(wallet) + } + + @Test + fun walletResultFinalize() { + val names = listOf("one", "two", "three") + names.map { + val wallet = WalletResult(it, desc, change) + assertNotNull(wallet) } + System.gc() + // The only way to verify wallets freed is by checking the log } @Test - fun wallet_sync_error() { - val bad_wallet_result = libJna.new_wallet_result("test", "bad", null) - log.debug("wallet result created") - val sync_result = libJna.sync_wallet(bad_wallet_result) - val sync_err_pointer = libJna.get_void_err(sync_result) - assertNotNull(sync_err_pointer) - val sync_err = sync_err_pointer.getString(0) - log.debug("wallet sync error $sync_err") + fun walletSync() { + val walletResult = WalletResult(name, desc, change) + val wallet = walletResult.ok() + assertNotNull(wallet) + val syncResult = wallet!!.sync(); + assertFalse(syncResult.isErr()) + assertNull(syncResult.err()) } @Test - fun sync() { - val sync_result = libJna.sync_wallet(wallet_result) - assertNull(libJna.get_void_err(sync_result)) - libJna.free_void_result(sync_result) - } - - @Test - fun new_newaddress_wallet() { - val address_result = libJna.new_address(wallet_result) - assertNull(libJna.get_string_err(address_result)) - val address_pointer = libJna.get_string_ok(address_result); - val address = address_pointer!!.getString(0) - - libJna.free_string_result(address_result) - libJna.free_string(address_pointer) + fun walletNewAddress() { + val walletResult = WalletResult(name, desc, change) + val wallet = walletResult.ok() + assertNotNull(wallet) + val addressResult = wallet!!.getAddress() + assertFalse(addressResult.isErr()) + assertNull(addressResult.err()) + val address = addressResult.ok() + assertNotNull(address) log.debug("address created from kotlin: $address") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") } diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index e28e5bc..cd01a7b 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -10,10 +10,13 @@ int main (int argc, char const * const argv[]) { WalletResult_t *wallet_result = new_wallet_result("bad", "bad", NULL); assert(wallet_result != NULL); - char *wallet_error = get_wallet_err(wallet_result); - assert(wallet_error != NULL); - //printf("wallet error: %s\n", wallet_error); - free_string(wallet_error); + char *wallet_err = get_wallet_err(wallet_result); + assert(wallet_err != NULL); + assert( 0 == strcmp(wallet_err,"Descriptor") ); + //printf("wallet err: %s\n", wallet_err); + WalletRef_t *wallet_ref = get_wallet_ok(wallet_result); + assert(wallet_ref == NULL); + free_string(wallet_err); free_wallet_result(wallet_result); } @@ -25,26 +28,32 @@ int main (int argc, char const * const argv[]) WalletResult_t *wallet_result = new_wallet_result(name, desc, change); assert(wallet_result != NULL); + char *wallet_err = get_wallet_err(wallet_result); + assert(wallet_err == NULL); + WalletRef_t *wallet_ref = get_wallet_ok(wallet_result); + assert(wallet_ref != NULL); // test sync_wallet - VoidResult_t *sync_result = sync_wallet(wallet_result); + VoidResult_t *sync_result = sync_wallet(wallet_ref); free_void_result(sync_result); // test new_address - StringResult_t *address_result1 = new_address(wallet_result); + StringResult_t *address_result1 = new_address(wallet_ref); char *address1 = get_string_ok(address_result1); //printf("address1: %s\n", address1); assert( 0 == strcmp(address1,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp")); free_string(address1); free_string_result(address_result1); - StringResult_t *address_result2 = new_address(wallet_result); + StringResult_t *address_result2 = new_address(wallet_ref); char *address2 = get_string_ok(address_result2); //printf("address2: %s\n", address2); assert(0 == strcmp(address2,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf")); free_string(address2); free_string_result(address_result2); + free_wallet_ref(wallet_ref); + // test free_wallet free_wallet_result(wallet_result); diff --git a/src/error.rs b/src/error.rs index fba774c..3aa6d82 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,6 @@ -//use ::safer_ffi::prelude::*; use bdk::Error; -pub fn error_name(error: &bdk::Error) -> &'static str { +pub fn get_name(error: &bdk::Error) -> String { match error { Error::InvalidU32Bytes(_) => "InvalidU32Bytes", Error::Generic(_) => "Generic", @@ -39,159 +38,9 @@ pub fn error_name(error: &bdk::Error) -> &'static str { Error::Hex(_) => "Hex", Error::Psbt(_) => "Psbt", Error::Electrum(_) => "Electrum", -// Error::Esplora(_) => {} -// Error::CompactFilters(_) => {} + // Error::Esplora(_) => {} + // Error::CompactFilters(_) => {} Error::Sled(_) => "Sled", - _ => "Unknown", } + .to_string() } - -//// Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet) -//// Simplified to work over the FFI interface -//#[derive_ReprC] -//#[repr(i16)] -//#[derive(Debug)] -//pub enum WalletError { -// None, -// InvalidU32Bytes, -// Generic, -// ScriptDoesntHaveAddressForm, -// SingleRecipientMultipleOutputs, -// SingleRecipientNoInputs, -// NoRecipients, -// NoUtxosSelected, -// OutputBelowDustLimit, -// InsufficientFunds, -// BnBTotalTriesExceeded, -// BnBNoExactMatch, -// UnknownUtxo, -// TransactionNotFound, -// TransactionConfirmed, -// IrreplaceableTransaction, -// FeeRateTooLow, -// FeeTooLow, -// FeeRateUnavailable, -// MissingKeyOrigin, -// Key, -// ChecksumMismatch, -// SpendingPolicyRequired, -// InvalidPolicyPathError, -// Signer, -// InvalidNetwork, -// InvalidProgressValue, -// ProgressUpdateError, -// InvalidOutpoint, -// Descriptor, -// AddressValidator, -// Encode, -// Miniscript, -// Bip32, -// Secp256k1, -// Json, -// Hex, -// Psbt, -// PsbtParse, -// //#[cfg(feature = "electrum")] -// Electrum, -// //#[cfg(feature = "esplora")] -// //Esplora, -// //#[cfg(feature = "compact_filters")] -// //CompactFilters, -// //#[cfg(feature = "key-value-db")] -// Sled, -//} - -//impl From for WalletError { -// fn from(error: bdk::Error) -> Self { -// match error { -// Error::InvalidU32Bytes(_) => WalletError::InvalidNetwork, -// Error::Generic(_) => WalletError::Generic, -// Error::ScriptDoesntHaveAddressForm => WalletError::ScriptDoesntHaveAddressForm, -// Error::SingleRecipientMultipleOutputs => WalletError::SingleRecipientMultipleOutputs, -// Error::SingleRecipientNoInputs => WalletError::SingleRecipientNoInputs, -// Error::NoRecipients => WalletError::NoRecipients, -// Error::NoUtxosSelected => WalletError::NoUtxosSelected, -// Error::OutputBelowDustLimit(_) => WalletError::OutputBelowDustLimit, -// Error::InsufficientFunds { .. } => WalletError::InsufficientFunds, -// Error::BnBTotalTriesExceeded => WalletError::BnBTotalTriesExceeded, -// Error::BnBNoExactMatch => WalletError::BnBNoExactMatch, -// Error::UnknownUtxo => WalletError::UnknownUtxo, -// Error::TransactionNotFound => WalletError::TransactionNotFound, -// Error::TransactionConfirmed => WalletError::TransactionConfirmed, -// Error::IrreplaceableTransaction => WalletError::IrreplaceableTransaction, -// Error::FeeRateTooLow { .. } => WalletError::FeeRateTooLow, -// Error::FeeTooLow { .. } => WalletError::FeeTooLow, -// Error::MissingKeyOrigin(_) => WalletError::MissingKeyOrigin, -// Error::Key(_) => WalletError::Key, -// Error::ChecksumMismatch => WalletError::ChecksumMismatch, -// Error::SpendingPolicyRequired(_) => WalletError::SpendingPolicyRequired, -// Error::InvalidPolicyPathError(_) => WalletError::InvalidPolicyPathError, -// Error::Signer(_) => WalletError::Signer, -// Error::InvalidProgressValue(_) => WalletError::InvalidProgressValue, -// Error::ProgressUpdateError => WalletError::ProgressUpdateError, -// Error::InvalidOutpoint(_) => WalletError::InvalidOutpoint, -// Error::Descriptor(_) => WalletError::Descriptor, -// Error::AddressValidator(_) => WalletError::AddressValidator, -// Error::Encode(_) => WalletError::Encode, -// Error::Miniscript(_) => WalletError::Miniscript, -// Error::Bip32(_) => WalletError::Bip32, -// Error::Secp256k1(_) => WalletError::Secp256k1, -// Error::Json(_) => WalletError::Json, -// Error::Hex(_) => WalletError::Hex, -// Error::Psbt(_) => WalletError::Psbt, -// Error::Electrum(_) => WalletError::Electrum, -// //Error::Esplora(_) => WalletError::Esplora, -// //Error::CompactFilters(_) => {} -// Error::Sled(_) => WalletError::Sled, -// } -// } -//} - -//type error_code = i16; -// -//impl From for error_code { -// fn from(error: bdk::Error) -> Self { -// match error { -// Error::InvalidU32Bytes(_) => 1, -// Error::Generic(_) => 2, -// Error::ScriptDoesntHaveAddressForm => 3, -// Error::SingleRecipientMultipleOutputs => 4, -// Error::SingleRecipientNoInputs => 5, -// Error::NoRecipients => 6, -// Error::NoUtxosSelected => 7, -// Error::OutputBelowDustLimit(_) => 8, -// Error::InsufficientFunds { .. } => 9, -// Error::BnBTotalTriesExceeded => 10, -// Error::BnBNoExactMatch => 11, -// Error::UnknownUtxo => 12, -// Error::TransactionNotFound => 13, -// Error::TransactionConfirmed => 14, -// Error::IrreplaceableTransaction => 15, -// Error::FeeRateTooLow { .. } => 16, -// Error::FeeTooLow { .. } => 17, -// Error::MissingKeyOrigin(_) => 18, -// Error::Key(_) => 19, -// Error::ChecksumMismatch => 20, -// Error::SpendingPolicyRequired(_) => 21, -// Error::InvalidPolicyPathError(_) => 22, -// Error::Signer(_) => 23, -// Error::InvalidProgressValue(_) => 24, -// Error::ProgressUpdateError => 25, -// Error::InvalidOutpoint(_) => 26, -// Error::Descriptor(_) => 27, -// Error::AddressValidator(_) => 28, -// Error::Encode(_) => 29, -// Error::Miniscript(_) => 30, -// Error::Bip32(_) => 31, -// Error::Secp256k1(_) => 32, -// Error::Json(_) => 33, -// Error::Hex(_) => 34, -// Error::Psbt(_) => 35, -// Error::Electrum(_) => 36, -// //Error::Esplora(_) => WalletError::Esplora, -// //Error::CompactFilters(_) => {} -// Error::Sled(_) => 37, -// _ => -1 -// } -// } -//} diff --git a/src/lib.rs b/src/lib.rs index f3f6fc6..3c75153 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![deny(unsafe_code)] /* No `unsafe` needed! */ +mod error; mod wallet; /// The following test function is necessary for the header generation. @@ -7,6 +8,7 @@ mod wallet; #[test] fn generate_headers() -> ::std::io::Result<()> { ::safer_ffi::headers::builder() + .with_guard("__RUST_BDK_FFI__") .to_file("cc/bdk_ffi.h")? .generate() } diff --git a/src/wallet.rs b/src/wallet.rs index 87b1fcc..cfd72bc 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,3 +1,4 @@ +use crate::error::get_name; use ::safer_ffi::prelude::*; use bdk::bitcoin::network::constants::Network::Testnet; use bdk::blockchain::{ @@ -13,7 +14,7 @@ use safer_ffi::char_p::{char_p_boxed, char_p_ref}; #[derive_ReprC] #[ReprC::opaque] pub struct VoidResult { - raw: Result<(), String>, + raw: Result<(), Error>, } #[ffi_export] @@ -22,7 +23,7 @@ fn get_void_err(void_result: &VoidResult) -> Option { .raw .as_ref() .err() - .map(|s| char_p_boxed::try_from(s.clone()).unwrap()) + .map(|e| char_p_boxed::try_from(get_name(e)).unwrap()) } #[ffi_export] @@ -33,7 +34,7 @@ fn free_void_result(void_result: Option>) { #[derive_ReprC] #[ReprC::opaque] pub struct StringResult { - raw: Result, + raw: Result, } #[ffi_export] @@ -51,7 +52,7 @@ fn get_string_err(string_result: &StringResult) -> Option { .raw .as_ref() .err() - .map(|s| char_p_boxed::try_from(s.clone()).unwrap()) + .map(|e| char_p_boxed::try_from(get_name(e)).unwrap()) } #[ffi_export] @@ -59,10 +60,21 @@ fn free_string_result(string_result: Option>) { drop(string_result) } +#[derive_ReprC] +#[ReprC::opaque] +pub struct WalletRef<'lt> { + raw: &'lt Wallet, +} + +#[ffi_export] +fn free_wallet_ref(wallet_ref: Option>) { + drop(wallet_ref) +} + #[derive_ReprC] #[ReprC::opaque] pub struct WalletResult { - raw: Result, String>, + raw: Result, Error>, } #[ffi_export] @@ -74,12 +86,12 @@ fn new_wallet_result( let name = name.to_string(); let descriptor = descriptor.to_string(); let change_descriptor = change_descriptor.map(|s| s.to_string()); - let wallet_result = new_wallet(name, descriptor, change_descriptor).map_err(|e| e.to_string()); + let wallet_result = new_wallet(name, descriptor, change_descriptor); Box::new(WalletResult { raw: wallet_result }) } fn new_wallet( - name: String, + _name: String, descriptor: String, change_descriptor: Option, ) -> Result, Error> { @@ -108,24 +120,28 @@ fn get_wallet_err(wallet_result: &WalletResult) -> Option { .raw .as_ref() .err() - .map(|s| char_p_boxed::try_from(s.clone()).unwrap()) + .map(|e| char_p_boxed::try_from(get_name(&e)).unwrap()) } #[ffi_export] -fn sync_wallet(wallet_result: &WalletResult) -> Box { - let wallet_result_ref = wallet_result.raw.as_ref().map_err(|error| error.clone()); - let void_result = wallet_result_ref - .and_then(|w| w.sync(log_progress(), Some(100)).map_err(|e| e.to_string())); +fn get_wallet_ok<'lt>(wallet_result: &'lt WalletResult) -> Option>> { + wallet_result + .raw + .as_ref() + .ok() + .map(|w| Box::new(WalletRef { raw: w})) +} + +#[ffi_export] +fn sync_wallet<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box { + let void_result = wallet_ref + .raw.sync(log_progress(), Some(100)); Box::new(VoidResult { raw: void_result }) } #[ffi_export] -fn new_address(wallet_result: &WalletResult) -> Box { - let new_address = wallet_result - .raw - .as_ref() - .map_err(|error| error.clone()) - .and_then(|w| w.get_address(New).map_err(|error| error.to_string())); +fn new_address<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box { + let new_address = wallet_ref.raw.get_address(New); let string_result = new_address.map(|a| a.to_string()); Box::new(StringResult { raw: string_result }) } From c921120216c8c4f3acf859c5b20b07422496b561 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 22 Jun 2021 16:49:25 -0700 Subject: [PATCH 017/272] Update Results return Error enum instead of String --- .../jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt | 4 ++-- .../jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt | 4 ++-- .../jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt | 4 ++-- .../src/main/java/org/bitcoindevkit/bdk/LibTest.kt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt index 14e8c5a..4809123 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt @@ -11,11 +11,11 @@ class StringResult internal constructor(private val stringResultT: LibJna.String return libJna.get_string_err(stringResultT) != null } - fun err(): String? { + fun err(): Error? { val errPointer = libJna.get_string_err(stringResultT) val err = errPointer?.getString(0) libJna.free_string(errPointer) - return err + return err?.let { Error.valueOf(it) } } fun ok(): String? { diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt index 50b1c86..dd42517 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt @@ -11,11 +11,11 @@ class VoidResult internal constructor(private val voidResultT: LibJna.VoidResult return libJna.get_void_err(voidResultT) != null } - fun err(): String? { + fun err(): Error? { val errPointer = libJna.get_void_err(voidResultT) val err = errPointer?.getString(0) libJna.free_string(errPointer) - return err + return err?.let { Error.valueOf(it) } } protected fun finalize() { diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt index 2ac325d..1f882d1 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt @@ -16,11 +16,11 @@ class WalletResult( return libJna.get_wallet_err(walletResultT) != null } - fun err(): String? { + fun err(): Error? { val errPointer = libJna.get_wallet_err(walletResultT) val err = errPointer?.getString(0) libJna.free_string(errPointer) - return err + return err?.let { Error.valueOf(it) } } fun ok(): Wallet? { diff --git a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt index 49e6d85..2628607 100644 --- a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt @@ -26,7 +26,7 @@ abstract class LibTest : LibBase() { val walletErr = badWalletResult.err() assertNotNull(walletErr) log.debug("wallet error $walletErr") - assertEquals("Descriptor", walletErr) + assertEquals(Error.Descriptor, walletErr) val wallet = badWalletResult.ok() assertNull(wallet) } From 9fd29871ca69a7555573c371e8c6262372b00cca Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 23 Jun 2021 14:13:33 -0700 Subject: [PATCH 018/272] Fix Kotlin jar: native library path --- build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index c0f2ecd..7503b6d 100755 --- a/build.sh +++ b/build.sh @@ -10,8 +10,8 @@ export LD_LIBRARY_PATH=`pwd`/target/debug cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m # bdk-kotlin jar -mkdir -p bdk-kotlin/jvm/src/main/resources/jnaLibs/x86_64_linux -cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/src/main/resources/jnaLibs/x86_64_linux +mkdir -p bdk-kotlin/jvm/src/main/resources/linux-x86-64 +cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/src/main/resources/linux-x86-64 (cd bdk-kotlin && gradle :jvm:build) From 110dfbd13c019d4f467911fba4c8d3c672755f41 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 23 Jun 2021 14:20:04 -0700 Subject: [PATCH 019/272] Update build.sh to publish to local maven repo --- build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 7503b6d..bbcc9bf 100755 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l mkdir -p bdk-kotlin/jvm/src/main/resources/linux-x86-64 cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/src/main/resources/linux-x86-64 -(cd bdk-kotlin && gradle :jvm:build) +(cd bdk-kotlin && gradle :jvm:build && gradle :jvm:publishToMavenLocal) # rust android @@ -49,4 +49,4 @@ if echo $BUILD_TARGETS | grep "i686"; then fi # bdk-kotlin aar -(cd bdk-kotlin && gradle :android:build) +(cd bdk-kotlin && gradle :android:build && gradle :android:publishToMavenLocal) From 757113c0028d6602a96136df3385acb9d97087e6 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 24 Jun 2021 13:49:25 -0700 Subject: [PATCH 020/272] Simplify Kotlin Wallet api --- .../org/bitcoindevkit/bdk/AndroidLibTest.kt | 5 +-- bdk-kotlin/jvm/build.gradle | 3 +- .../bdk/{Error.kt => JnaError.kt} | 5 ++- .../org/bitcoindevkit/bdk/JnaException.kt | 3 ++ .../java/org/bitcoindevkit/bdk/LibBase.kt | 2 +- .../main/java/org/bitcoindevkit/bdk/LibJna.kt | 12 +++--- .../java/org/bitcoindevkit/bdk/ResultBase.kt | 37 +++++++++++++++++++ .../org/bitcoindevkit/bdk/StringResult.kt | 32 +++++++--------- .../java/org/bitcoindevkit/bdk/VoidResult.kt | 26 ++++++------- .../main/java/org/bitcoindevkit/bdk/Wallet.kt | 35 +++++++++++------- .../org/bitcoindevkit/bdk/WalletResult.kt | 37 ++++++------------- .../java/org/bitcoindevkit/bdk/JvmLibTest.kt | 8 +--- bdk-kotlin/test-fixtures/build.gradle | 2 +- .../java/org/bitcoindevkit/bdk/LibTest.kt | 30 +++++---------- src/wallet.rs | 5 +-- 15 files changed, 125 insertions(+), 117 deletions(-) rename bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/{Error.kt => JnaError.kt} (94%) create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaException.kt create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/ResultBase.kt diff --git a/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt index a104f6a..8107e8e 100644 --- a/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt +++ b/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt @@ -1,9 +1,6 @@ package org.bitcoindevkit.bdk -import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.sun.jna.Native -import org.junit.* import org.junit.runner.RunWith @@ -16,5 +13,5 @@ import org.junit.Assert.* */ @RunWith(AndroidJUnit4::class) class AndroidLibTest : LibTest() { - + } diff --git a/bdk-kotlin/jvm/build.gradle b/bdk-kotlin/jvm/build.gradle index 9640d69..2330523 100644 --- a/bdk-kotlin/jvm/build.gradle +++ b/bdk-kotlin/jvm/build.gradle @@ -17,8 +17,7 @@ dependencies { api "org.slf4j:slf4j-api:1.7.30" testImplementation "ch.qos.logback:logback-classic:1.2.3" testImplementation "ch.qos.logback:logback-core:1.2.3" - testImplementation 'org.jetbrains.kotlin:kotlin-test-junit' - testImplementation (project(':test-fixtures')) + testImplementation(project(':test-fixtures')) } publishing { diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Error.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaError.kt similarity index 94% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Error.kt rename to bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaError.kt index aeda367..598e4a5 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Error.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaError.kt @@ -1,6 +1,6 @@ package org.bitcoindevkit.bdk -enum class Error { +enum class JnaError { InvalidU32Bytes, Generic, ScriptDoesntHaveAddressForm, @@ -37,7 +37,8 @@ enum class Error { Hex, Psbt, Electrum, -// Esplora + + // Esplora // CompactFilters Sled, } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaException.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaException.kt new file mode 100644 index 0000000..6ce9768 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaException.kt @@ -0,0 +1,3 @@ +package org.bitcoindevkit.bdk + +class JnaException internal constructor(val err: JnaError) : Exception() \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibBase.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibBase.kt index e00eec0..03ebf78 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibBase.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibBase.kt @@ -3,7 +3,7 @@ package org.bitcoindevkit.bdk import com.sun.jna.Native abstract class LibBase { - + protected val libJna: LibJna get() = Native.load("bdk_ffi", LibJna::class.java) } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt index d186ef5..c1de293 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt @@ -1,6 +1,8 @@ package org.bitcoindevkit.bdk -import com.sun.jna.* +import com.sun.jna.Library +import com.sun.jna.Pointer +import com.sun.jna.PointerType interface LibJna : Library { @@ -35,13 +37,13 @@ interface LibJna : Library { // void free_string_result ( // StringResult_t * string_result); fun free_string_result(string_result: StringResult_t) - + // typedef struct WalletRef WalletRef_t; class WalletRef_t : PointerType { constructor() : super() constructor(pointer: Pointer) : super(pointer) } - + // void free_wallet_ref ( // WalletRef_t * wallet_ref); fun free_wallet_ref(wallet_ref: WalletRef_t) @@ -61,11 +63,11 @@ interface LibJna : Library { descriptor: String, changeDescriptor: String? ): WalletResult_t - + // char * get_wallet_err ( // WalletResult_t const * wallet_result); fun get_wallet_err(wallet_result: WalletResult_t): Pointer? - + // WalletRef_t * get_wallet_ok ( // WalletResult_t const * wallet_result); fun get_wallet_ok(wallet_result: WalletResult_t): WalletRef_t? diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/ResultBase.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/ResultBase.kt new file mode 100644 index 0000000..eb13cc5 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/ResultBase.kt @@ -0,0 +1,37 @@ +package org.bitcoindevkit.bdk + +import com.sun.jna.Pointer +import com.sun.jna.PointerType +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +abstract class ResultBase internal constructor(protected val resultT: PT) : + LibBase() { + + protected open val log: Logger = LoggerFactory.getLogger(ResultBase::class.java) + + protected abstract fun err(): Pointer? + + protected abstract fun ok(): RT + + protected abstract fun free(pointer: PT) + + private fun checkErr() { + val errPointer = err() + val err = errPointer?.getString(0) + libJna.free_string(errPointer) + if (err != null) { + throw JnaException(JnaError.valueOf(err)) + } + } + + fun value(): RT { + checkErr() + return ok() + } + + protected fun finalize() { + free(resultT) + log.debug("$resultT freed") + } +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt index 4809123..b062f36 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt @@ -1,32 +1,26 @@ package org.bitcoindevkit.bdk +import com.sun.jna.Pointer import org.slf4j.Logger import org.slf4j.LoggerFactory -class StringResult internal constructor(private val stringResultT: LibJna.StringResult_t) : LibBase() { +class StringResult internal constructor(stringResultT: LibJna.StringResult_t) : + ResultBase(stringResultT) { - private val log: Logger = LoggerFactory.getLogger(StringResult::class.java) + override val log: Logger = LoggerFactory.getLogger(StringResult::class.java) - fun isErr(): Boolean { - return libJna.get_string_err(stringResultT) != null + override fun err(): Pointer? { + return libJna.get_string_err(resultT) } - - fun err(): Error? { - val errPointer = libJna.get_string_err(stringResultT) - val err = errPointer?.getString(0) - libJna.free_string(errPointer) - return err?.let { Error.valueOf(it) } - } - - fun ok(): String? { - val okPointer = libJna.get_string_ok(stringResultT) - val ok = okPointer?.getString(0) + + override fun ok(): String { + val okPointer = libJna.get_string_ok(resultT) + val ok = okPointer!!.getString(0) libJna.free_string(okPointer) return ok } - - protected fun finalize() { - libJna.free_string_result(stringResultT) - log.debug("StringResult_t freed") + + override fun free(pointer: LibJna.StringResult_t) { + libJna.free_string_result(resultT) } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt index dd42517..3f4cb5f 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt @@ -1,25 +1,23 @@ package org.bitcoindevkit.bdk +import com.sun.jna.Pointer import org.slf4j.Logger import org.slf4j.LoggerFactory -class VoidResult internal constructor(private val voidResultT: LibJna.VoidResult_t) : LibBase() { +class VoidResult internal constructor(voidResultT: LibJna.VoidResult_t) : + ResultBase(voidResultT) { - private val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) + override val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) - fun isErr(): Boolean { - return libJna.get_void_err(voidResultT) != null + override fun err(): Pointer? { + return libJna.get_void_err(resultT) } - - fun err(): Error? { - val errPointer = libJna.get_void_err(voidResultT) - val err = errPointer?.getString(0) - libJna.free_string(errPointer) - return err?.let { Error.valueOf(it) } + + override fun ok() { + // Void } - - protected fun finalize() { - libJna.free_void_result(voidResultT) - log.debug("VoidResult_t freed") + + override fun free(pointer: LibJna.VoidResult_t) { + libJna.free_void_result(resultT) } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt index 5ae9878..7b62e24 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt @@ -3,23 +3,30 @@ package org.bitcoindevkit.bdk import org.slf4j.Logger import org.slf4j.LoggerFactory -class Wallet internal constructor( - private val walletRefT: LibJna.WalletRef_t, +class Wallet constructor( + name: String, + descriptor: String, + changeDescriptor: String?, ) : LibBase() { - - private val log: Logger = LoggerFactory.getLogger(Wallet::class.java) - - fun sync(): VoidResult { - return VoidResult(libJna.sync_wallet(walletRefT.pointer)) - } - - fun getAddress(): StringResult { - return StringResult(libJna.new_address(walletRefT.pointer)) + + val log: Logger = LoggerFactory.getLogger(Wallet::class.java) + + private val walletResult = + WalletResult(libJna.new_wallet_result(name, descriptor, changeDescriptor)) + private val walletRefT = walletResult.value() + + fun sync() { + val voidResult = VoidResult(libJna.sync_wallet(walletRefT.pointer)) + return voidResult.value() } - protected fun finalize() { + fun getAddress(): String { + val stringResult = StringResult(libJna.new_address(walletRefT.pointer)) + return stringResult.value() + } + + protected fun finalize(pointer: LibJna.WalletResult_t) { libJna.free_wallet_ref(walletRefT) - log.debug("WalletRef_t freed") + log.debug("$walletRefT freed") } - } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt index 1f882d1..46b7be7 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt @@ -1,36 +1,23 @@ package org.bitcoindevkit.bdk +import com.sun.jna.Pointer import org.slf4j.Logger import org.slf4j.LoggerFactory -class WalletResult( - name: String, - descriptor: String, - changeDescriptor: String?, -) : LibBase() { - - private val log: Logger = LoggerFactory.getLogger(WalletResult::class.java) - private val walletResultT = libJna.new_wallet_result(name, descriptor, changeDescriptor) - - fun isErr(): Boolean { - return libJna.get_wallet_err(walletResultT) != null +class WalletResult internal constructor(walletResultT: LibJna.WalletResult_t) : + ResultBase(walletResultT) { + + override val log: Logger = LoggerFactory.getLogger(WalletResult::class.java) + + override fun err(): Pointer? { + return libJna.get_wallet_err(resultT) } - fun err(): Error? { - val errPointer = libJna.get_wallet_err(walletResultT) - val err = errPointer?.getString(0) - libJna.free_string(errPointer) - return err?.let { Error.valueOf(it) } + override fun ok(): LibJna.WalletRef_t { + return libJna.get_wallet_ok(resultT)!! } - fun ok(): Wallet? { - val okWalletRef = libJna.get_wallet_ok(walletResultT) - return if (okWalletRef != null) Wallet(okWalletRef) else null + override fun free(pointer: LibJna.WalletResult_t) { + libJna.free_wallet_result(resultT) } - - protected fun finalize() { - libJna.free_wallet_result(walletResultT) - log.debug("WalletResult_t freed") - } - } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt b/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt index 2ce5b1a..8f97dfd 100644 --- a/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt +++ b/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt @@ -1,15 +1,9 @@ package org.bitcoindevkit.bdk -import com.sun.jna.Native -import org.junit.* -import org.junit.Assert.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull - /** * Library test, which will execute on linux host. * */ class JvmLibTest : LibTest() { - + } diff --git a/bdk-kotlin/test-fixtures/build.gradle b/bdk-kotlin/test-fixtures/build.gradle index 6c6b4ca..6f80396 100644 --- a/bdk-kotlin/test-fixtures/build.gradle +++ b/bdk-kotlin/test-fixtures/build.gradle @@ -8,7 +8,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "net.java.dev.jna:jna:5.8.0" implementation (project(':jvm')) - implementation 'org.jetbrains.kotlin:kotlin-test-junit' + implementation "junit:junit:4.13.2" //implementation "org.mockito.kotlin:mockito-kotlin:3.2.0" api "org.slf4j:slf4j-api:1.7.30" } diff --git a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt index 2628607..70e70ec 100644 --- a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt @@ -4,6 +4,7 @@ import org.junit.* import org.junit.Assert.* import org.slf4j.Logger import org.slf4j.LoggerFactory +import javax.management.Descriptor /** * Library test, which will execute on linux host. @@ -21,21 +22,17 @@ abstract class LibTest : LibBase() { @Test fun walletResultError() { - val badWalletResult = WalletResult("bad", "bad", "bad") - assertTrue(badWalletResult.isErr()) - val walletErr = badWalletResult.err() - assertNotNull(walletErr) - log.debug("wallet error $walletErr") - assertEquals(Error.Descriptor, walletErr) - val wallet = badWalletResult.ok() - assertNull(wallet) + val jnaException = assertThrows(JnaException::class.java) { + Wallet("bad", "bad", "bad") + } + assertEquals(jnaException.err, JnaError.Descriptor) } @Test fun walletResultFinalize() { val names = listOf("one", "two", "three") names.map { - val wallet = WalletResult(it, desc, change) + val wallet = Wallet(it, desc, change) assertNotNull(wallet) } System.gc() @@ -44,23 +41,16 @@ abstract class LibTest : LibBase() { @Test fun walletSync() { - val walletResult = WalletResult(name, desc, change) - val wallet = walletResult.ok() + val wallet = Wallet(name, desc, change) assertNotNull(wallet) - val syncResult = wallet!!.sync(); - assertFalse(syncResult.isErr()) - assertNull(syncResult.err()) + wallet.sync() } @Test fun walletNewAddress() { - val walletResult = WalletResult(name, desc, change) - val wallet = walletResult.ok() + val wallet = Wallet(name, desc, change) assertNotNull(wallet) - val addressResult = wallet!!.getAddress() - assertFalse(addressResult.isErr()) - assertNull(addressResult.err()) - val address = addressResult.ok() + val address = wallet.getAddress() assertNotNull(address) log.debug("address created from kotlin: $address") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") diff --git a/src/wallet.rs b/src/wallet.rs index cfd72bc..988bede 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -129,13 +129,12 @@ fn get_wallet_ok<'lt>(wallet_result: &'lt WalletResult) -> Option(wallet_ref: &'lt WalletRef<'lt>) -> Box { - let void_result = wallet_ref - .raw.sync(log_progress(), Some(100)); + let void_result = wallet_ref.raw.sync(log_progress(), Some(100)); Box::new(VoidResult { raw: void_result }) } From 1249a4c491d1dbe17ef09443b588e5ca797fa1ab Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 25 Jun 2021 23:40:38 -0700 Subject: [PATCH 021/272] Add kotlin BlockchainConfig and DatabaseConfig --- .../org/bitcoindevkit/bdk/BlockchainConfig.kt | 25 +++++ .../org/bitcoindevkit/bdk/DatabaseConfig.kt | 26 +++++ .../main/java/org/bitcoindevkit/bdk/LibJna.kt | 50 ++++++++- .../main/java/org/bitcoindevkit/bdk/Wallet.kt | 5 +- .../java/org/bitcoindevkit/bdk/LibTest.kt | 16 ++- cc/bdk_ffi_test.c | 19 +++- src/blockchain.rs | 104 ++++++++++++++++++ src/database.rs | 32 ++++++ src/error.rs | 4 +- src/lib.rs | 2 + src/wallet.rs | 26 ++--- test.sh | 4 +- 12 files changed, 282 insertions(+), 31 deletions(-) create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/BlockchainConfig.kt create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt create mode 100644 src/blockchain.rs create mode 100644 src/database.rs diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/BlockchainConfig.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/BlockchainConfig.kt new file mode 100644 index 0000000..e604975 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/BlockchainConfig.kt @@ -0,0 +1,25 @@ +package org.bitcoindevkit.bdk + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +abstract class BlockchainConfig() : LibBase() { + private val log: Logger = LoggerFactory.getLogger(BlockchainConfig::class.java) + abstract val blockchainConfigT: LibJna.BlockchainConfig_t + + protected fun finalize() { + libJna.free_blockchain_config(blockchainConfigT) + log.debug("$blockchainConfigT freed") + } +} + +class ElectrumConfig( + url: String, + socks5: String?, + retry: Short, + timeout: Short +) : BlockchainConfig() { + + private val log: Logger = LoggerFactory.getLogger(ElectrumConfig::class.java) + override val blockchainConfigT = libJna.new_electrum_config(url, socks5, retry, timeout) +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt new file mode 100644 index 0000000..317965b --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt @@ -0,0 +1,26 @@ +package org.bitcoindevkit.bdk + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +abstract class DatabaseConfig() : LibBase() { + private val log: Logger = LoggerFactory.getLogger(DatabaseConfig::class.java) + abstract val databaseConfigT: LibJna.DatabaseConfig_t + + protected fun finalize() { + libJna.free_database_config(databaseConfigT) + log.debug("$databaseConfigT freed") + } +} + +class MemoryConfig() : DatabaseConfig() { + + private val log: Logger = LoggerFactory.getLogger(MemoryConfig::class.java) + override val databaseConfigT = libJna.new_memory_config() +} + +class SledConfig(path: String, treeName:String) : DatabaseConfig() { + + private val log: Logger = LoggerFactory.getLogger(SledConfig::class.java) + override val databaseConfigT = libJna.new_sled_config(path, treeName) +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt index c1de293..6e9d04b 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt @@ -48,6 +48,18 @@ interface LibJna : Library { // WalletRef_t * wallet_ref); fun free_wallet_ref(wallet_ref: WalletRef_t) + // typedef struct BlockchainConfig BlockchainConfig_t; + class BlockchainConfig_t : PointerType { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + // typedef struct DatabaseConfig DatabaseConfig_t; + class DatabaseConfig_t : PointerType { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + // typedef struct WalletResult WalletResult_t; class WalletResult_t : PointerType { constructor() : super() @@ -55,13 +67,15 @@ interface LibJna : Library { } // WalletResult_t * new_wallet_result ( - // char const * name, // char const * descriptor, - // char const * change_descriptor); + // char const * change_descriptor, + // BlockchainConfig_t const * blockchain_config, + // DatabaseConfig_t const * database_config); fun new_wallet_result( - name: String, descriptor: String, - changeDescriptor: String? + changeDescriptor: String?, + blockchainConfig: BlockchainConfig_t, + databaseConfig: DatabaseConfig_t, ): WalletResult_t // char * get_wallet_err ( @@ -87,4 +101,32 @@ interface LibJna : Library { // void free_string ( // char * string); fun free_string(string: Pointer?) + + // BlockchainConfig_t * new_electrum_config ( + // char const * url, + // char const * socks5, + // int16_t retry, + // int16_t timeout); + fun new_electrum_config( + url: String, + socks5: String?, + retry: Short, + timeout: Short + ): BlockchainConfig_t + + // void free_blockchain_config ( + // BlockchainConfig_t * blockchain_config); + fun free_blockchain_config( blockchain_config: BlockchainConfig_t) + + // DatabaseConfig_t * new_memory_config (void); + fun new_memory_config(): DatabaseConfig_t + + // DatabaseConfig_t * new_sled_config ( + // char const * path, + // char const * tree_name); + fun new_sled_config(path: String, tree_name: String): DatabaseConfig_t + + // void free_database_config ( + // DatabaseConfig_t * database_config); + fun free_database_config( database_config: DatabaseConfig_t) } diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt index 7b62e24..e2a8e12 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt @@ -4,15 +4,16 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory class Wallet constructor( - name: String, descriptor: String, changeDescriptor: String?, + blockchainConfig: BlockchainConfig, + databaseConfig: DatabaseConfig, ) : LibBase() { val log: Logger = LoggerFactory.getLogger(Wallet::class.java) private val walletResult = - WalletResult(libJna.new_wallet_result(name, descriptor, changeDescriptor)) + WalletResult(libJna.new_wallet_result(descriptor, changeDescriptor, blockchainConfig.blockchainConfigT, databaseConfig.databaseConfigT)) private val walletRefT = walletResult.value() fun sync() { diff --git a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt index 70e70ec..60d02de 100644 --- a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt @@ -22,17 +22,21 @@ abstract class LibTest : LibBase() { @Test fun walletResultError() { + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val databaseConfig = MemoryConfig() val jnaException = assertThrows(JnaException::class.java) { - Wallet("bad", "bad", "bad") + Wallet("bad", "bad", blockchainConfig, databaseConfig) } assertEquals(jnaException.err, JnaError.Descriptor) } @Test fun walletResultFinalize() { + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val databaseConfig = MemoryConfig() val names = listOf("one", "two", "three") names.map { - val wallet = Wallet(it, desc, change) + val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) assertNotNull(wallet) } System.gc() @@ -41,14 +45,18 @@ abstract class LibTest : LibBase() { @Test fun walletSync() { - val wallet = Wallet(name, desc, change) + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val databaseConfig = MemoryConfig() + val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) assertNotNull(wallet) wallet.sync() } @Test fun walletNewAddress() { - val wallet = Wallet(name, desc, change) + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val databaseConfig = MemoryConfig() + val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) assertNotNull(wallet) val address = wallet.getAddress() assertNotNull(address) diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index cd01a7b..487b20f 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -8,8 +8,14 @@ int main (int argc, char const * const argv[]) { // test new wallet error { - WalletResult_t *wallet_result = new_wallet_result("bad", "bad", NULL); + BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + //DatabaseConfig_t *db_config = new_sled_config("/home/steve/.bdk", "test_wallet"); + DatabaseConfig_t *db_config = new_memory_config(); + + WalletResult_t *wallet_result = new_wallet_result("bad", "bad", bc_config, db_config); assert(wallet_result != NULL); + free_blockchain_config(bc_config); + free_database_config(db_config); char *wallet_err = get_wallet_err(wallet_result); assert(wallet_err != NULL); assert( 0 == strcmp(wallet_err,"Descriptor") ); @@ -18,16 +24,22 @@ int main (int argc, char const * const argv[]) assert(wallet_ref == NULL); free_string(wallet_err); free_wallet_result(wallet_result); + } // test new wallet { - 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/*)"; - WalletResult_t *wallet_result = new_wallet_result(name, desc, change); + BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + //DatabaseConfig_t *db_config = new_sled_config("/home/steve/.bdk", "test_wallet"); + DatabaseConfig_t *db_config = new_memory_config(); + + WalletResult_t *wallet_result = new_wallet_result(desc, change, bc_config, db_config); assert(wallet_result != NULL); + free_blockchain_config(bc_config); + free_database_config(db_config); char *wallet_err = get_wallet_err(wallet_result); assert(wallet_err == NULL); WalletRef_t *wallet_ref = get_wallet_ok(wallet_result); @@ -65,6 +77,7 @@ int main (int argc, char const * const argv[]) // verify sync_wallet after free_wallet fails (core dumped) ////VoidResult_t sync_result2 = sync_wallet(wallet_result); + } return EXIT_SUCCESS; diff --git a/src/blockchain.rs b/src/blockchain.rs new file mode 100644 index 0000000..b3ecd8b --- /dev/null +++ b/src/blockchain.rs @@ -0,0 +1,104 @@ +use ::safer_ffi::prelude::*; +use bdk::blockchain::{AnyBlockchainConfig, ElectrumBlockchainConfig}; +use safer_ffi::boxed::Box; +use safer_ffi::char_p::char_p_ref; + +#[derive_ReprC] +#[ReprC::opaque] +#[derive(Debug)] +pub struct BlockchainConfig { + pub raw: AnyBlockchainConfig, +} + +#[ffi_export] +fn new_electrum_config( + url: char_p_ref, + socks5: Option, + retry: i16, + timeout: i16, +) -> Box { + let url = url.to_string(); + let socks5 = socks5.map(|s| s.to_string()); + let retry = short_to_u8(retry); + let timeout = short_to_optional_u8(timeout); + + let electrum_config = AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { + url, + socks5, + retry, + timeout, + }); + Box::new(BlockchainConfig { + raw: electrum_config, + }) +} + +#[ffi_export] +fn free_blockchain_config(blockchain_config: Box) { + drop(blockchain_config); +} + +// TODO compact_filter rocksdb not compiling on android, switch to sqlite? +//#[derive_ReprC] +//#[repr(C)] +//#[derive(Debug)] +//pub struct BitcoinPeerConfig { +// pub address: char_p_boxed, +// pub socks5: Option, +// pub socks5_credentials: Option>>, +//} +// +//impl From<&BitcoinPeerConfig> for BdkBitcoinPeerConfig { +// fn from(config: &BitcoinPeerConfig) -> Self { +// let address = config.address.to_string(); +// let socks5 = config.socks5.as_ref().map(|p| p.to_string()); +// let socks5_credentials = config +// .socks5_credentials.as_ref() +// .map(|c| (c._0.to_string(), c._1.to_string())); +// +// BdkBitcoinPeerConfig { +// address, +// socks5: socks5, +// socks5_credentials: socks5_credentials, +// } +// } +//} +// +// +//#[ffi_export] +//fn new_compact_filters_config<'lt>( +// peers: c_slice::Ref<'lt, BitcoinPeerConfig>, +// network: char_p_ref, +// storage_dir: char_p_ref, +// skip_blocks: usize, +//) -> Box { +// let peers = peers.iter().map(|p| p.into()).collect(); +// let network = Network::from_str(network.to_str()).unwrap(); +// let storage_dir = storage_dir.to_string(); +// let skip_blocks = Some(skip_blocks); +// let cf_config = AnyBlockchainConfig::CompactFilters(CompactFiltersBlockchainConfig { +// peers, +// network, +// storage_dir, +// skip_blocks, +// }); +// Box::new(BlockchainConfig { raw: cf_config }) +//} + +// utility functions + +fn short_to_optional_u8(short: i16) -> Option { + if short < 0 { + None + } else { + Some(short_to_u8(short)) + } +} + +fn short_to_u8(short: i16) -> u8 { + if short < 0 { + u8::MIN + } else { + u8::try_from(short).unwrap_or(u8::MAX) + } +} diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..76dc333 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,32 @@ +use ::safer_ffi::prelude::*; +use bdk::database::any::SledDbConfiguration; +use bdk::database::AnyDatabaseConfig; +use safer_ffi::boxed::Box; +use safer_ffi::char_p::char_p_ref; + +#[derive_ReprC] +#[ReprC::opaque] +#[derive(Debug)] +pub struct DatabaseConfig { + pub raw: AnyDatabaseConfig, +} + +#[ffi_export] +fn new_memory_config() -> Box { + let memory_config = AnyDatabaseConfig::Memory(()); + Box::new(DatabaseConfig { raw: memory_config }) +} + +#[ffi_export] +fn new_sled_config(path: char_p_ref, tree_name: char_p_ref) -> Box { + let path = path.to_string(); + let tree_name = tree_name.to_string(); + + let sled_config = AnyDatabaseConfig::Sled(SledDbConfiguration { path, tree_name }); + Box::new(DatabaseConfig { raw: sled_config }) +} + +#[ffi_export] +fn free_database_config(database_config: Box) { + drop(database_config); +} diff --git a/src/error.rs b/src/error.rs index 3aa6d82..6af73f0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -38,8 +38,8 @@ pub fn get_name(error: &bdk::Error) -> String { Error::Hex(_) => "Hex", Error::Psbt(_) => "Psbt", Error::Electrum(_) => "Electrum", - // Error::Esplora(_) => {} - // Error::CompactFilters(_) => {} + // Error::Esplora(_) => "Esplora", + // Error::CompactFilters(_) => "CompactFilters", Error::Sled(_) => "Sled", } .to_string() diff --git a/src/lib.rs b/src/lib.rs index 3c75153..72e9910 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ #![deny(unsafe_code)] /* No `unsafe` needed! */ +mod blockchain; +mod database; mod error; mod wallet; diff --git a/src/wallet.rs b/src/wallet.rs index 988bede..93ea73c 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -10,6 +10,8 @@ use bdk::wallet::AddressIndex::New; use bdk::{Error, Wallet}; use safer_ffi::boxed::Box; use safer_ffi::char_p::{char_p_boxed, char_p_ref}; +use crate::blockchain::BlockchainConfig; +use crate::database::DatabaseConfig; #[derive_ReprC] #[ReprC::opaque] @@ -79,34 +81,30 @@ pub struct WalletResult { #[ffi_export] fn new_wallet_result( - name: char_p_ref, descriptor: char_p_ref, change_descriptor: Option, + blockchain_config: &BlockchainConfig, + database_config: &DatabaseConfig, ) -> Box { - let name = name.to_string(); + let descriptor = descriptor.to_string(); let change_descriptor = change_descriptor.map(|s| s.to_string()); - let wallet_result = new_wallet(name, descriptor, change_descriptor); + let bc_config = &blockchain_config.raw; + let db_config = &database_config.raw; + let wallet_result = new_wallet(descriptor, change_descriptor, bc_config, db_config); Box::new(WalletResult { raw: wallet_result }) } fn new_wallet( - _name: String, descriptor: String, change_descriptor: Option, + blockchain_config: &AnyBlockchainConfig, + database_config: &AnyDatabaseConfig, ) -> Result, Error> { let network = Testnet; - 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)?; - let database_config = AnyDatabaseConfig::Memory(()); - let database = AnyDatabase::from_config(&database_config)?; + let client = AnyBlockchain::from_config(blockchain_config)?; + let database = AnyDatabase::from_config(database_config)?; let descriptor: &str = descriptor.as_str(); let change_descriptor: Option<&str> = change_descriptor.as_deref(); diff --git a/test.sh b/test.sh index f6f86d5..03855c5 100755 --- a/test.sh +++ b/test.sh @@ -6,9 +6,9 @@ cargo test --features c-headers -- generate_headers # cc export LD_LIBRARY_PATH=`pwd`/target/debug -#valgrind --leak-check=full cc/bdk_ffi_test +#valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test cc/bdk_ffi_test -# bdk-kotlin +## bdk-kotlin (cd bdk-kotlin && gradle test) (cd bdk-kotlin && gradle :android:connectedDebugAndroidTest) From f5dd87b02a24d2e1f06204373eee553c4cba7e31 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 26 Jun 2021 18:50:58 -0700 Subject: [PATCH 022/272] Add LibTest getTestDataDir to fix sled test on android --- bdk-kotlin/android/build.gradle | 8 +++---- .../org/bitcoindevkit/bdk/AndroidLibTest.kt | 10 ++++++--- .../android/src/main/AndroidManifest.xml | 2 +- bdk-kotlin/jvm/build.gradle | 2 +- .../org/bitcoindevkit/bdk/DatabaseConfig.kt | 2 +- .../java/org/bitcoindevkit/bdk/JnaError.kt | 2 +- .../main/java/org/bitcoindevkit/bdk/LibJna.kt | 6 +++--- .../java/org/bitcoindevkit/bdk/ResultBase.kt | 21 ++++++++++--------- .../org/bitcoindevkit/bdk/StringResult.kt | 12 +++++------ .../java/org/bitcoindevkit/bdk/VoidResult.kt | 10 ++++----- .../main/java/org/bitcoindevkit/bdk/Wallet.kt | 11 ++++++++-- .../org/bitcoindevkit/bdk/WalletResult.kt | 12 +++++------ .../java/org/bitcoindevkit/bdk/JvmLibTest.kt | 7 +++++++ bdk-kotlin/settings.gradle | 2 +- bdk-kotlin/test-fixtures/build.gradle | 2 +- .../java/org/bitcoindevkit/bdk/LibTest.kt | 18 +++++++++++----- src/wallet.rs | 7 +++---- 17 files changed, 80 insertions(+), 54 deletions(-) diff --git a/bdk-kotlin/android/build.gradle b/bdk-kotlin/android/build.gradle index 8cfcfc3..f3bcdc8 100644 --- a/bdk-kotlin/android/build.gradle +++ b/bdk-kotlin/android/build.gradle @@ -35,7 +35,7 @@ afterEvaluate { // You can then customize attributes of the publication as shown below. groupId = 'org.bitcoindevkit' artifactId = 'bdk' - version = '0.0.1-dev' + version = '0.0.1-SNAPSHOT' } // Creates a Maven publication called “debug”. debug(MavenPublication) { @@ -44,14 +44,14 @@ afterEvaluate { groupId = 'org.bitcoindevkit' artifactId = 'bdk-debug' - version = '0.0.1-dev' + version = '0.0.1-SNAPSHOT' } } } } dependencies { - implementation (project(':jvm')) { + implementation(project(':jvm')) { exclude group: 'net.java.dev.jna', module: 'jna' } @@ -62,7 +62,7 @@ dependencies { api "org.slf4j:slf4j-api:1.7.30" androidTestImplementation 'com.github.tony19:logback-android:2.0.0' - androidTestImplementation (project(':test-fixtures')) { + androidTestImplementation(project(':test-fixtures')) { exclude group: 'net.java.dev.jna', module: 'jna' } androidTestImplementation 'androidx.test.ext:junit:1.1.2' diff --git a/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt index 8107e8e..9e1eec0 100644 --- a/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt +++ b/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt @@ -1,11 +1,11 @@ package org.bitcoindevkit.bdk +import android.app.Application +import android.content.Context.MODE_PRIVATE +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 - import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -13,5 +13,9 @@ import org.junit.Assert.* */ @RunWith(AndroidJUnit4::class) class AndroidLibTest : LibTest() { + override fun getTestDataDir(): String { + val context = ApplicationProvider.getApplicationContext() + return context.getDir("bdk-test", MODE_PRIVATE).toString() + } } diff --git a/bdk-kotlin/android/src/main/AndroidManifest.xml b/bdk-kotlin/android/src/main/AndroidManifest.xml index 3d47121..2aee369 100644 --- a/bdk-kotlin/android/src/main/AndroidManifest.xml +++ b/bdk-kotlin/android/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="org.bitcoindevkit.bdk"> diff --git a/bdk-kotlin/jvm/build.gradle b/bdk-kotlin/jvm/build.gradle index 2330523..64d2c78 100644 --- a/bdk-kotlin/jvm/build.gradle +++ b/bdk-kotlin/jvm/build.gradle @@ -25,7 +25,7 @@ publishing { maven(MavenPublication) { groupId = 'org.bitcoindevkit' artifactId = 'bdk' - version = '0.0.1-dev' + version = '0.0.1-SNAPSHOT' from components.java } diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt index 317965b..2f29fcd 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt @@ -19,7 +19,7 @@ class MemoryConfig() : DatabaseConfig() { override val databaseConfigT = libJna.new_memory_config() } -class SledConfig(path: String, treeName:String) : DatabaseConfig() { +class SledConfig(path: String, treeName: String) : DatabaseConfig() { private val log: Logger = LoggerFactory.getLogger(SledConfig::class.java) override val databaseConfigT = libJna.new_sled_config(path, treeName) diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaError.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaError.kt index 598e4a5..32dcd5a 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaError.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaError.kt @@ -39,6 +39,6 @@ enum class JnaError { Electrum, // Esplora -// CompactFilters + // CompactFilters Sled, } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt index 6e9d04b..ca50bb7 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt @@ -116,11 +116,11 @@ interface LibJna : Library { // void free_blockchain_config ( // BlockchainConfig_t * blockchain_config); - fun free_blockchain_config( blockchain_config: BlockchainConfig_t) + fun free_blockchain_config(blockchain_config: BlockchainConfig_t) // DatabaseConfig_t * new_memory_config (void); fun new_memory_config(): DatabaseConfig_t - + // DatabaseConfig_t * new_sled_config ( // char const * path, // char const * tree_name); @@ -128,5 +128,5 @@ interface LibJna : Library { // void free_database_config ( // DatabaseConfig_t * database_config); - fun free_database_config( database_config: DatabaseConfig_t) + fun free_database_config(database_config: DatabaseConfig_t) } diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/ResultBase.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/ResultBase.kt index eb13cc5..5fcf168 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/ResultBase.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/ResultBase.kt @@ -5,33 +5,34 @@ import com.sun.jna.PointerType import org.slf4j.Logger import org.slf4j.LoggerFactory -abstract class ResultBase internal constructor(protected val resultT: PT) : +abstract class ResultBase internal constructor(private val pointerT: PT) : LibBase() { protected open val log: Logger = LoggerFactory.getLogger(ResultBase::class.java) - protected abstract fun err(): Pointer? + protected abstract fun err(pointerT: PT): Pointer? - protected abstract fun ok(): RT + protected abstract fun ok(pointerT: PT): RT - protected abstract fun free(pointer: PT) + protected abstract fun free(pointerT: PT) - private fun checkErr() { - val errPointer = err() + private fun checkErr(pointerT: PT) { + val errPointer = err(pointerT) val err = errPointer?.getString(0) libJna.free_string(errPointer) if (err != null) { + log.error("JnaError: $err") throw JnaException(JnaError.valueOf(err)) } } fun value(): RT { - checkErr() - return ok() + checkErr(pointerT) + return ok(pointerT) } protected fun finalize() { - free(resultT) - log.debug("$resultT freed") + free(pointerT) + log.debug("$pointerT freed") } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt index b062f36..6bfa121 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt @@ -9,18 +9,18 @@ class StringResult internal constructor(stringResultT: LibJna.StringResult_t) : override val log: Logger = LoggerFactory.getLogger(StringResult::class.java) - override fun err(): Pointer? { - return libJna.get_string_err(resultT) + override fun err(pointerT: LibJna.StringResult_t): Pointer? { + return libJna.get_string_err(pointerT) } - override fun ok(): String { - val okPointer = libJna.get_string_ok(resultT) + override fun ok(pointerT: LibJna.StringResult_t): String { + val okPointer = libJna.get_string_ok(pointerT) val ok = okPointer!!.getString(0) libJna.free_string(okPointer) return ok } - override fun free(pointer: LibJna.StringResult_t) { - libJna.free_string_result(resultT) + override fun free(pointerT: LibJna.StringResult_t) { + libJna.free_string_result(pointerT) } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt index 3f4cb5f..5ed6c25 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt @@ -9,15 +9,15 @@ class VoidResult internal constructor(voidResultT: LibJna.VoidResult_t) : override val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) - override fun err(): Pointer? { - return libJna.get_void_err(resultT) + override fun err(pointerT: LibJna.VoidResult_t): Pointer? { + return libJna.get_void_err(pointerT) } - override fun ok() { + override fun ok(pointerT: LibJna.VoidResult_t) { // Void } - override fun free(pointer: LibJna.VoidResult_t) { - libJna.free_void_result(resultT) + override fun free(pointerT: LibJna.VoidResult_t) { + libJna.free_void_result(pointerT) } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt index e2a8e12..d438778 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt @@ -13,7 +13,14 @@ class Wallet constructor( val log: Logger = LoggerFactory.getLogger(Wallet::class.java) private val walletResult = - WalletResult(libJna.new_wallet_result(descriptor, changeDescriptor, blockchainConfig.blockchainConfigT, databaseConfig.databaseConfigT)) + WalletResult( + libJna.new_wallet_result( + descriptor, + changeDescriptor, + blockchainConfig.blockchainConfigT, + databaseConfig.databaseConfigT + ) + ) private val walletRefT = walletResult.value() fun sync() { @@ -26,7 +33,7 @@ class Wallet constructor( return stringResult.value() } - protected fun finalize(pointer: LibJna.WalletResult_t) { + protected fun finalize(walletRefT: LibJna.WalletRef_t) { libJna.free_wallet_ref(walletRefT) log.debug("$walletRefT freed") } diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt index 46b7be7..1b77cb8 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt @@ -9,15 +9,15 @@ class WalletResult internal constructor(walletResultT: LibJna.WalletResult_t) : override val log: Logger = LoggerFactory.getLogger(WalletResult::class.java) - override fun err(): Pointer? { - return libJna.get_wallet_err(resultT) + override fun err(pointerT: LibJna.WalletResult_t): Pointer? { + return libJna.get_wallet_err(pointerT) } - override fun ok(): LibJna.WalletRef_t { - return libJna.get_wallet_ok(resultT)!! + override fun ok(pointerT: LibJna.WalletResult_t): LibJna.WalletRef_t { + return libJna.get_wallet_ok(pointerT)!! } - override fun free(pointer: LibJna.WalletResult_t) { - libJna.free_wallet_result(resultT) + override fun free(pointerT: LibJna.WalletResult_t) { + libJna.free_wallet_result(pointerT) } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt b/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt index 8f97dfd..ddb4fb7 100644 --- a/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt +++ b/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt @@ -1,9 +1,16 @@ package org.bitcoindevkit.bdk +import java.nio.file.Paths + /** * Library test, which will execute on linux host. * */ class JvmLibTest : LibTest() { + override fun getTestDataDir(): String { + //return Files.createTempDirectory("bdk-test").toString() + return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString() + } + } diff --git a/bdk-kotlin/settings.gradle b/bdk-kotlin/settings.gradle index d38f719..b44156b 100644 --- a/bdk-kotlin/settings.gradle +++ b/bdk-kotlin/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = 'bdk-kotlin' -include ':jvm',':android',':test-fixtures' \ No newline at end of file +include ':jvm', ':android', ':test-fixtures' \ No newline at end of file diff --git a/bdk-kotlin/test-fixtures/build.gradle b/bdk-kotlin/test-fixtures/build.gradle index 6f80396..2853edb 100644 --- a/bdk-kotlin/test-fixtures/build.gradle +++ b/bdk-kotlin/test-fixtures/build.gradle @@ -7,7 +7,7 @@ dependencies { implementation platform('org.jetbrains.kotlin:kotlin-bom') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "net.java.dev.jna:jna:5.8.0" - implementation (project(':jvm')) + implementation(project(':jvm')) implementation "junit:junit:4.13.2" //implementation "org.mockito.kotlin:mockito-kotlin:3.2.0" api "org.slf4j:slf4j-api:1.7.30" diff --git a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt index 60d02de..3d0c5a0 100644 --- a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt @@ -1,14 +1,13 @@ package org.bitcoindevkit.bdk -import org.junit.* import org.junit.Assert.* +import org.junit.Test import org.slf4j.Logger import org.slf4j.LoggerFactory -import javax.management.Descriptor +import java.io.File /** - * Library test, which will execute on linux host. - * + * Library tests which will execute for jvm and android modules. */ abstract class LibTest : LibBase() { @@ -20,6 +19,12 @@ abstract class LibTest : LibBase() { val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" + abstract fun getTestDataDir(): String + + fun cleanupTestDataDir() { + File(getTestDataDir()).deleteRecursively() + } + @Test fun walletResultError() { val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) @@ -46,10 +51,13 @@ abstract class LibTest : LibBase() { @Test fun walletSync() { val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) - val databaseConfig = MemoryConfig() + val testDataDir = getTestDataDir() + log.debug("testDataDir = $testDataDir") + val databaseConfig = SledConfig(testDataDir, "steve-test") val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) assertNotNull(wallet) wallet.sync() + cleanupTestDataDir() } @Test diff --git a/src/wallet.rs b/src/wallet.rs index 93ea73c..454f547 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,3 +1,5 @@ +use crate::blockchain::BlockchainConfig; +use crate::database::DatabaseConfig; use crate::error::get_name; use ::safer_ffi::prelude::*; use bdk::bitcoin::network::constants::Network::Testnet; @@ -10,8 +12,6 @@ use bdk::wallet::AddressIndex::New; use bdk::{Error, Wallet}; use safer_ffi::boxed::Box; use safer_ffi::char_p::{char_p_boxed, char_p_ref}; -use crate::blockchain::BlockchainConfig; -use crate::database::DatabaseConfig; #[derive_ReprC] #[ReprC::opaque] @@ -86,11 +86,10 @@ fn new_wallet_result( blockchain_config: &BlockchainConfig, database_config: &DatabaseConfig, ) -> Box { - let descriptor = descriptor.to_string(); let change_descriptor = change_descriptor.map(|s| s.to_string()); let bc_config = &blockchain_config.raw; - let db_config = &database_config.raw; + let db_config = &database_config.raw; let wallet_result = new_wallet(descriptor, change_descriptor, bc_config, db_config); Box::new(WalletResult { raw: wallet_result }) } From 273cad831868f53030dba462289da6a1087fe03f Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 30 Jun 2021 17:44:46 -0700 Subject: [PATCH 023/272] Refactor to return results by value, add wallet list_unspent and related types --- .gitignore | 1 - README.md | 18 + .../java/org/bitcoindevkit/bdk/FfiResult.kt | 15 + .../main/java/org/bitcoindevkit/bdk/LibJna.kt | 307 +++++++++++++----- .../main/java/org/bitcoindevkit/bdk/Result.kt | 37 +++ .../org/bitcoindevkit/bdk/StringResult.kt | 23 +- .../bitcoindevkit/bdk/VecLocalUtxoResult.kt | 31 ++ .../java/org/bitcoindevkit/bdk/VoidResult.kt | 20 +- .../main/java/org/bitcoindevkit/bdk/Wallet.kt | 29 +- .../org/bitcoindevkit/bdk/WalletResult.kt | 20 +- .../java/org/bitcoindevkit/bdk/LibTest.kt | 64 ++-- build.sh | 1 + cc/bdk_ffi.h | 154 +++++++++ cc/bdk_ffi_test.c | 118 ++++--- src/lib.rs | 1 + src/types.rs | 34 ++ src/wallet.rs | 231 +++++++------ test.sh | 6 +- 18 files changed, 803 insertions(+), 307 deletions(-) create mode 100644 README.md create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/FfiResult.kt create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Result.kt create mode 100644 bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt create mode 100644 cc/bdk_ffi.h create mode 100644 src/types.rs diff --git a/.gitignore b/.gitignore index 9bf4fb6..fd662a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ target build Cargo.lock -*.h /bdk-kotlin/local.properties .gradle wallet_db diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d3f227 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ + + +Adding new structs and functions + +1. Create C safe Rust structs and related functions using safer-ffi + +2. Test generated library and `bdk_ffi.h` file with c language tests in `cc/bdk_ffi_test.c` + +3. Use `build.sh` and `test.sh` to build c test program and verify functionality and + memory de-allocation via `valgrind` + +4. Update the kotlin native interface LibJna.kt in the `bdk-kotlin` `jvm` module to match `bdk_ffi.h` + +5. Create kotlin wrapper classes and interfaces as needed + +6. Add tests to `bdk-kotlin` `test-fixtures` module + +7. Use `build.sh` and `test.sh` to build and test `bdk-kotlin` `jvm` and `android` modules \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/FfiResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/FfiResult.kt new file mode 100644 index 0000000..3755617 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/FfiResult.kt @@ -0,0 +1,15 @@ +package org.bitcoindevkit.bdk + +import com.sun.jna.Pointer +import com.sun.jna.Structure + +abstract class FfiResult : Structure { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + + @JvmField + var ok: Pointer? = null + + @JvmField + var err: Pointer? = null +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt index ca50bb7..bfb5383 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt @@ -1,52 +1,48 @@ package org.bitcoindevkit.bdk -import com.sun.jna.Library -import com.sun.jna.Pointer -import com.sun.jna.PointerType +import com.sun.jna.* interface LibJna : Library { - // typedef struct VoidResult VoidResult_t; - class VoidResult_t : PointerType { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) + // typedef struct { + // + // char * * ok; + // + // char * * err; + // + // } FfiResult_char_ptr_t; + open class FfiResult_char_ptr_t : FfiResult() { + class ByValue : FfiResult_char_ptr_t(), Structure.ByValue + class ByReference : FfiResult_char_ptr_t(), Structure.ByReference + + override fun getFieldOrder() = listOf("ok", "err") } - // char * get_void_err ( - // VoidResult_t const * void_result); - fun get_void_err(void_result: VoidResult_t): Pointer? - - // void free_void_result ( - // VoidResult_t * void_result); - fun free_void_result(void_result: VoidResult_t) - - // typedef struct StringResult StringResult_t; - class StringResult_t : PointerType { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - } - - // char * get_string_ok ( - // StringResult_t const * string_result); - fun get_string_ok(string_result: StringResult_t): Pointer? - - // char * get_string_err ( - // StringResult_t const * string_result); - fun get_string_err(string_result: StringResult_t): Pointer? - // void free_string_result ( - // StringResult_t * string_result); - fun free_string_result(string_result: StringResult_t) + // FfiResult_char_ptr_t string_result); + fun free_string_result(string_result: FfiResult_char_ptr_t.ByValue) - // typedef struct WalletRef WalletRef_t; - class WalletRef_t : PointerType { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) + // typedef struct { + // + // void * ok; + // + // char * * err; + // + // } FfiResult_void_t; + open class FfiResult_void_t : FfiResult() { + class ByValue : FfiResult_void_t(), Structure.ByValue + class ByReference : FfiResult_void_t(), Structure.ByReference + + override fun getFieldOrder() = listOf("ok", "err") } - // void free_wallet_ref ( - // WalletRef_t * wallet_ref); - fun free_wallet_ref(wallet_ref: WalletRef_t) + // void free_void_result ( + // FfiResult_void_t void_result); + fun free_void_result(void_result: FfiResult_void_t.ByValue) + + // void free_string ( + // char * string); + fun free_string(string: Pointer?) // typedef struct BlockchainConfig BlockchainConfig_t; class BlockchainConfig_t : PointerType { @@ -54,54 +50,6 @@ interface LibJna : Library { constructor(pointer: Pointer) : super(pointer) } - // typedef struct DatabaseConfig DatabaseConfig_t; - class DatabaseConfig_t : PointerType { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - } - - // typedef struct WalletResult WalletResult_t; - class WalletResult_t : PointerType { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - } - - // WalletResult_t * new_wallet_result ( - // char const * descriptor, - // char const * change_descriptor, - // BlockchainConfig_t const * blockchain_config, - // DatabaseConfig_t const * database_config); - fun new_wallet_result( - descriptor: String, - changeDescriptor: String?, - blockchainConfig: BlockchainConfig_t, - databaseConfig: DatabaseConfig_t, - ): WalletResult_t - - // char * get_wallet_err ( - // WalletResult_t const * wallet_result); - fun get_wallet_err(wallet_result: WalletResult_t): Pointer? - - // WalletRef_t * get_wallet_ok ( - // WalletResult_t const * wallet_result); - fun get_wallet_ok(wallet_result: WalletResult_t): WalletRef_t? - - // VoidResult_t * sync_wallet ( - // WalletRef_t const * wallet_ref); - fun sync_wallet(wallet_ref: Pointer): VoidResult_t - - // StringResult_t * new_address ( - // WalletRef_t const * wallet_ref); - fun new_address(wallet_ref: Pointer): StringResult_t - - // void free_wallet_result ( - // WalletResult_t * wallet_result); - fun free_wallet_result(wallet_result: WalletResult_t) - - // void free_string ( - // char * string); - fun free_string(string: Pointer?) - // BlockchainConfig_t * new_electrum_config ( // char const * url, // char const * socks5, @@ -118,6 +66,193 @@ interface LibJna : Library { // BlockchainConfig_t * blockchain_config); fun free_blockchain_config(blockchain_config: BlockchainConfig_t) + // typedef struct DatabaseConfig DatabaseConfig_t; + class DatabaseConfig_t : PointerType { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + // typedef struct OpaqueWallet OpaqueWallet_t; + class OpaqueWallet_t : PointerType { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + // typedef struct { + // + // OpaqueWallet_t * ok; + // + // char * * err; + // + // } FfiResult_OpaqueWallet_t; + open class FfiResult_OpaqueWallet_t : FfiResult() { + class ByValue : FfiResult_OpaqueWallet_t(), Structure.ByValue + class ByReference : FfiResult_OpaqueWallet_t(), Structure.ByReference + + override fun getFieldOrder() = listOf("ok", "err") + } + + // FfiResult_OpaqueWallet_t new_wallet_result ( + // char const * descriptor, + // char const * change_descriptor, + // BlockchainConfig_t const * blockchain_config, + // DatabaseConfig_t const * database_config); + fun new_wallet_result( + descriptor: String, + changeDescriptor: String?, + blockchainConfig: BlockchainConfig_t, + databaseConfig: DatabaseConfig_t, + ): FfiResult_OpaqueWallet_t.ByValue + + // void free_wallet_result ( + // FfiResult_OpaqueWallet_t wallet_result); + fun free_wallet_result(wallet_result: FfiResult_OpaqueWallet_t.ByValue) + + // typedef struct { + // + // char * txid; + // + // uint32_t vout; + // + // } OutPoint_t; + open class OutPoint_t : Structure() { + class ByValue : OutPoint_t(), Structure.ByValue + + @JvmField + var txid: String? = null + + @JvmField + var vout: Int? = null + + override fun getFieldOrder() = listOf("txid", "vout") + } + + // typedef struct { + // + // uint64_t value; + // + // char * script_pubkey; + // + // } TxOut_t; + open class TxOut_t : Structure() { + class ByValue : TxOut_t(), Structure.ByValue + + @JvmField + var value: Long? = null + + @JvmField + var script_pubkey: String? = null + + override fun getFieldOrder() = listOf("value", "script_pubkey") + } + + // typedef struct { + // + // OutPoint_t outpoint; + // + // TxOut_t txout; + // + // uint16_t keychain; + // + // } LocalUtxo_t; + open class LocalUtxo_t : Structure { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + + class ByValue : LocalUtxo_t, Structure.ByValue { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + class ByReference : LocalUtxo_t, Structure.ByReference { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + @JvmField + var outpoint: OutPoint_t? = null + + @JvmField + var txout: TxOut_t? = null + + @JvmField + var keychain: Short? = null + + override fun getFieldOrder() = listOf("outpoint", "txout", "keychain") + } + + // typedef struct { + // + // LocalUtxo_t * ptr; + // + // size_t len; + // + // size_t cap; + // + // } Vec_LocalUtxo_t; + open class Vec_LocalUtxo_t : Structure { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + + class ByReference : Vec_LocalUtxo_t, Structure.ByReference { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + class ByValue : Vec_LocalUtxo_t, Structure.ByValue { + constructor() : super() + constructor(pointer: Pointer) : super(pointer) + } + + @JvmField + var ptr: LocalUtxo_t.ByReference? = null + + @JvmField + var len: NativeLong? = null + + @JvmField + var cap: NativeLong? = null + + override fun getFieldOrder() = listOf("ptr", "len", "cap") + } + + // typedef struct { + // + // Vec_LocalUtxo_t ok; + // + // char * * err; + // + // } FfiResultVec_LocalUtxo_t; + open class FfiResultVec_LocalUtxo_t : Structure() { + + class ByValue : FfiResultVec_LocalUtxo_t(), Structure.ByValue + class ByReference : FfiResultVec_LocalUtxo_t(), Structure.ByReference + + @JvmField + var ok: Vec_LocalUtxo_t = Vec_LocalUtxo_t() + + @JvmField + var err: Pointer? = null + + override fun getFieldOrder() = listOf("ok", "err") + } + + // void free_unspent_result ( + // FfiResultVec_LocalUtxo_t unspent_result); + fun free_unspent_result(unspent_result: FfiResultVec_LocalUtxo_t.ByValue) + + // FfiResult_void_t sync_wallet ( + // OpaqueWallet_t const * opaque_wallet); + fun sync_wallet(opaque_wallet: OpaqueWallet_t): FfiResult_void_t.ByValue + + // FfiResult_char_ptr_t new_address ( + // OpaqueWallet_t const * opaque_wallet); + fun new_address(opaque_wallet: OpaqueWallet_t): FfiResult_char_ptr_t.ByValue + + // FfiResult_Vec_LocalUtxo_t list_unspent ( + // OpaqueWallet_t const * opaque_wallet); + fun list_unspent(opaque_wallet: OpaqueWallet_t): FfiResultVec_LocalUtxo_t.ByValue + // DatabaseConfig_t * new_memory_config (void); fun new_memory_config(): DatabaseConfig_t diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Result.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Result.kt new file mode 100644 index 0000000..6ceef7f --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Result.kt @@ -0,0 +1,37 @@ +package org.bitcoindevkit.bdk + +import com.sun.jna.Pointer +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +abstract class Result(private val ffiResult: T): LibBase() { + + protected open val log: Logger = LoggerFactory.getLogger(Result::class.java) + + protected abstract fun getOkValue(pointer: Pointer): RT + + protected abstract fun freeResult(ffiResult: T) + + fun value(): RT { + val err = ffiResult.err + val ok = ffiResult.ok + when { + err != null -> { + val errString = err.getPointer(0).getString(0) + log.error("JnaError: $errString") + throw JnaException(JnaError.valueOf(errString)) + } + ok != null -> { + return getOkValue(ok) + } + else -> { + throw JnaException(JnaError.Generic) + } + } + } + + protected fun finalize() { + freeResult(ffiResult) + log.debug("$ffiResult freed") + } +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt index 6bfa121..29da3eb 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt @@ -1,26 +1,15 @@ package org.bitcoindevkit.bdk import com.sun.jna.Pointer -import org.slf4j.Logger -import org.slf4j.LoggerFactory -class StringResult internal constructor(stringResultT: LibJna.StringResult_t) : - ResultBase(stringResultT) { +class StringResult constructor(stringResultPtr: LibJna.FfiResult_char_ptr_t.ByValue) : + Result(stringResultPtr) { - override val log: Logger = LoggerFactory.getLogger(StringResult::class.java) - - override fun err(pointerT: LibJna.StringResult_t): Pointer? { - return libJna.get_string_err(pointerT) + override fun getOkValue(pointer: Pointer): String { + return pointer.getPointer(0).getString(0) } - override fun ok(pointerT: LibJna.StringResult_t): String { - val okPointer = libJna.get_string_ok(pointerT) - val ok = okPointer!!.getString(0) - libJna.free_string(okPointer) - return ok - } - - override fun free(pointerT: LibJna.StringResult_t) { - libJna.free_string_result(pointerT) + override fun freeResult(ffiResult: LibJna.FfiResult_char_ptr_t.ByValue) { + libJna.free_string_result(ffiResult) } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt new file mode 100644 index 0000000..da69a1b --- /dev/null +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt @@ -0,0 +1,31 @@ +package org.bitcoindevkit.bdk + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class VecLocalUtxoResult(private val ffiResult: LibJna.FfiResultVec_LocalUtxo_t.ByValue) : + LibBase() { + + protected open val log: Logger = LoggerFactory.getLogger(VecLocalUtxoResult::class.java) + + fun value(): Array { + val err = ffiResult.err + val ok = ffiResult.ok + when { + err != null -> { + val errString = err.getPointer(0).getString(0) + log.error("JnaError: $errString") + throw JnaException(JnaError.valueOf(errString)) + } + else -> { + val first = ok.ptr!! + return first.toArray(ok.len!!.toInt()) as Array + } + } + } + + protected fun finalize() { + libJna.free_unspent_result(ffiResult) + log.debug("$ffiResult freed") + } +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt index 5ed6c25..03b46be 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt @@ -1,23 +1,15 @@ package org.bitcoindevkit.bdk import com.sun.jna.Pointer -import org.slf4j.Logger -import org.slf4j.LoggerFactory -class VoidResult internal constructor(voidResultT: LibJna.VoidResult_t) : - ResultBase(voidResultT) { +class VoidResult constructor(voidResultPtr: LibJna.FfiResult_void_t.ByValue) : + Result(voidResultPtr) { - override val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) - - override fun err(pointerT: LibJna.VoidResult_t): Pointer? { - return libJna.get_void_err(pointerT) + override fun getOkValue(pointer: Pointer) { + // No value } - override fun ok(pointerT: LibJna.VoidResult_t) { - // Void - } - - override fun free(pointerT: LibJna.VoidResult_t) { - libJna.free_void_result(pointerT) + override fun freeResult(ffiResult: LibJna.FfiResult_void_t.ByValue) { + libJna.free_void_result(ffiResult) } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt index d438778..7abea00 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt @@ -12,29 +12,28 @@ class Wallet constructor( val log: Logger = LoggerFactory.getLogger(Wallet::class.java) - private val walletResult = - WalletResult( - libJna.new_wallet_result( - descriptor, - changeDescriptor, - blockchainConfig.blockchainConfigT, - databaseConfig.databaseConfigT - ) + private val walletResult = WalletResult( + libJna.new_wallet_result( + descriptor, + changeDescriptor, + blockchainConfig.blockchainConfigT, + databaseConfig.databaseConfigT ) - private val walletRefT = walletResult.value() + ) + private val wallet = walletResult.value() fun sync() { - val voidResult = VoidResult(libJna.sync_wallet(walletRefT.pointer)) + val voidResult = VoidResult(libJna.sync_wallet(wallet)) return voidResult.value() } fun getAddress(): String { - val stringResult = StringResult(libJna.new_address(walletRefT.pointer)) + val stringResult = StringResult(libJna.new_address(wallet)) return stringResult.value() } - - protected fun finalize(walletRefT: LibJna.WalletRef_t) { - libJna.free_wallet_ref(walletRefT) - log.debug("$walletRefT freed") + + fun listUnspent(): Array { + val vecLocalUtxoResult = VecLocalUtxoResult(libJna.list_unspent(wallet)) + return vecLocalUtxoResult.value() } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt index 1b77cb8..ba8f979 100644 --- a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt +++ b/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt @@ -1,23 +1,15 @@ package org.bitcoindevkit.bdk import com.sun.jna.Pointer -import org.slf4j.Logger -import org.slf4j.LoggerFactory -class WalletResult internal constructor(walletResultT: LibJna.WalletResult_t) : - ResultBase(walletResultT) { +class WalletResult constructor(walletResultPtr: LibJna.FfiResult_OpaqueWallet_t.ByValue) : + Result(walletResultPtr) { - override val log: Logger = LoggerFactory.getLogger(WalletResult::class.java) - - override fun err(pointerT: LibJna.WalletResult_t): Pointer? { - return libJna.get_wallet_err(pointerT) + override fun getOkValue(pointer: Pointer): LibJna.OpaqueWallet_t { + return LibJna.OpaqueWallet_t(pointer) } - override fun ok(pointerT: LibJna.WalletResult_t): LibJna.WalletRef_t { - return libJna.get_wallet_ok(pointerT)!! - } - - override fun free(pointerT: LibJna.WalletResult_t) { - libJna.free_wallet_result(pointerT) + override fun freeResult(ffiResult: LibJna.FfiResult_OpaqueWallet_t.ByValue) { + libJna.free_wallet_result(ffiResult) } } \ No newline at end of file diff --git a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt index 3d0c5a0..5c84338 100644 --- a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt @@ -19,6 +19,9 @@ abstract class LibTest : LibBase() { val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val databaseConfig = MemoryConfig() + abstract fun getTestDataDir(): String fun cleanupTestDataDir() { @@ -27,48 +30,69 @@ abstract class LibTest : LibBase() { @Test fun walletResultError() { - val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) - val databaseConfig = MemoryConfig() val jnaException = assertThrows(JnaException::class.java) { Wallet("bad", "bad", blockchainConfig, databaseConfig) } assertEquals(jnaException.err, JnaError.Descriptor) } - @Test - fun walletResultFinalize() { - val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) - val databaseConfig = MemoryConfig() - val names = listOf("one", "two", "three") - names.map { - val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) - assertNotNull(wallet) - } - System.gc() - // The only way to verify wallets freed is by checking the log - } +// @Test +// fun walletResultFinalize() { +// run { +// val desc = +// "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" +// val change = +// "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" +// +// val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) +// val databaseConfig = MemoryConfig() +// val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) +// wallet.sync() +// assertNotNull(wallet.getAddress()) +// } +// System.gc() +// Thread.sleep(2000) +// // The only way to verify wallets freed is by checking the log +// } @Test fun walletSync() { val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) val testDataDir = getTestDataDir() - log.debug("testDataDir = $testDataDir") + // log.debug("testDataDir = $testDataDir") val databaseConfig = SledConfig(testDataDir, "steve-test") val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) - assertNotNull(wallet) wallet.sync() cleanupTestDataDir() } @Test fun walletNewAddress() { - val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) - val databaseConfig = MemoryConfig() val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) - assertNotNull(wallet) val address = wallet.getAddress() assertNotNull(address) - log.debug("address created from kotlin: $address") + // log.debug("address created from kotlin: $address") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") } + + @Test + fun walletUnspent() { + val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) + wallet.sync() + val unspent = wallet.listUnspent() + assertTrue(unspent.isNotEmpty()) + + unspent.iterator().forEach { + //log.debug("unspent.outpoint.txid: ${it.outpoint!!.txid}") + assertNotNull(it.outpoint?.txid) + //log.debug("unspent.outpoint.vout: ${it.outpoint?.vout}") + assertTrue(it.outpoint?.vout!! >= 0) + //log.debug("unspent.txout.value: ${it.txout?.value}") + assertTrue(it.txout?.value!! > 0) + //log.debug("unspent.txout.script_pubkey: ${it.txout?.script_pubkey}") + assertNotNull(it.txout?.script_pubkey) + //log.debug("unspent.keychain: ${it.keychain}") + assertTrue(it.keychain!! >= 0) + } + } } diff --git a/build.sh b/build.sh index bbcc9bf..6b5a530 100755 --- a/build.sh +++ b/build.sh @@ -2,6 +2,7 @@ set -eo pipefail -o xtrace # rust +cargo fmt cargo build cargo test --features c-headers -- generate_headers diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h new file mode 100644 index 0000000..eba536a --- /dev/null +++ b/cc/bdk_ffi.h @@ -0,0 +1,154 @@ +/*! \file */ +/******************************************* + * * + * File auto-generated by `::safer_ffi`. * + * * + * Do not manually edit this file. * + * * + *******************************************/ + +#ifndef __RUST_BDK_FFI__ +#define __RUST_BDK_FFI__ + +#ifdef __cplusplus +extern "C" { +#endif + + +#include +#include + +typedef struct BlockchainConfig BlockchainConfig_t; + +BlockchainConfig_t * new_electrum_config ( + char const * url, + char const * socks5, + int16_t retry, + int16_t timeout); + +void free_blockchain_config ( + BlockchainConfig_t * blockchain_config); + +typedef struct DatabaseConfig DatabaseConfig_t; + +typedef struct OpaqueWallet OpaqueWallet_t; + +typedef struct { + + OpaqueWallet_t * ok; + + char * * err; + +} FfiResult_OpaqueWallet_t; + +FfiResult_OpaqueWallet_t new_wallet_result ( + char const * descriptor, + char const * change_descriptor, + BlockchainConfig_t const * blockchain_config, + DatabaseConfig_t const * database_config); + +void free_wallet_result ( + FfiResult_OpaqueWallet_t wallet_result); + +typedef struct { + + char * txid; + + uint32_t vout; + +} OutPoint_t; + +typedef struct { + + uint64_t value; + + char * script_pubkey; + +} TxOut_t; + +typedef struct { + + OutPoint_t outpoint; + + TxOut_t txout; + + uint16_t keychain; + +} LocalUtxo_t; + +/** \brief + * Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout + */ +typedef struct { + + LocalUtxo_t * ptr; + + size_t len; + + size_t cap; + +} Vec_LocalUtxo_t; + +typedef struct { + + Vec_LocalUtxo_t ok; + + char * * err; + +} FfiResultVec_LocalUtxo_t; + +void free_unspent_result ( + FfiResultVec_LocalUtxo_t unspent_result); + +typedef struct { + + void * ok; + + char * * err; + +} FfiResult_void_t; + +FfiResult_void_t sync_wallet ( + OpaqueWallet_t const * opaque_wallet); + +typedef struct { + + char * * ok; + + char * * err; + +} FfiResult_char_ptr_t; + +FfiResult_char_ptr_t new_address ( + OpaqueWallet_t const * opaque_wallet); + +FfiResultVec_LocalUtxo_t list_unspent ( + OpaqueWallet_t const * opaque_wallet); + +DatabaseConfig_t * new_memory_config (void); + +DatabaseConfig_t * new_sled_config ( + char const * path, + char const * tree_name); + +void free_database_config ( + DatabaseConfig_t * database_config); + +void free_string_result ( + FfiResult_char_ptr_t string_result); + +void free_void_result ( + FfiResult_void_t void_result); + +/** \brief + * Frees a Rust-allocated string + */ +void free_string ( + char * string); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __RUST_BDK_FFI__ */ diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index 487b20f..8ea5590 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -12,19 +12,20 @@ int main (int argc, char const * const argv[]) //DatabaseConfig_t *db_config = new_sled_config("/home/steve/.bdk", "test_wallet"); DatabaseConfig_t *db_config = new_memory_config(); - WalletResult_t *wallet_result = new_wallet_result("bad", "bad", bc_config, db_config); - assert(wallet_result != NULL); + // new wallet with bad descriptor + FfiResult_OpaqueWallet_t wallet_result = new_wallet_result("bad","bad",bc_config,db_config); + assert(wallet_result.err != NULL); + assert(wallet_result.ok == NULL); + free_blockchain_config(bc_config); free_database_config(db_config); - char *wallet_err = get_wallet_err(wallet_result); + + char *wallet_err = *wallet_result.err; assert(wallet_err != NULL); assert( 0 == strcmp(wallet_err,"Descriptor") ); - //printf("wallet err: %s\n", wallet_err); - WalletRef_t *wallet_ref = get_wallet_ok(wallet_result); - assert(wallet_ref == NULL); - free_string(wallet_err); + // printf("wallet err: %s\n", wallet_err); + free_wallet_result(wallet_result); - } // test new wallet @@ -33,51 +34,94 @@ int main (int argc, char const * const argv[]) char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); - //DatabaseConfig_t *db_config = new_sled_config("/home/steve/.bdk", "test_wallet"); DatabaseConfig_t *db_config = new_memory_config(); - WalletResult_t *wallet_result = new_wallet_result(desc, change, bc_config, db_config); - assert(wallet_result != NULL); + // new wallet + FfiResult_OpaqueWallet_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); + assert(wallet_result.err == NULL); + assert(wallet_result.ok != NULL); + free_blockchain_config(bc_config); free_database_config(db_config); - char *wallet_err = get_wallet_err(wallet_result); - assert(wallet_err == NULL); - WalletRef_t *wallet_ref = get_wallet_ok(wallet_result); - assert(wallet_ref != NULL); - // test sync_wallet - VoidResult_t *sync_result = sync_wallet(wallet_ref); - free_void_result(sync_result); + OpaqueWallet_t *wallet = wallet_result.ok; - // test new_address - StringResult_t *address_result1 = new_address(wallet_ref); - char *address1 = get_string_ok(address_result1); - //printf("address1: %s\n", address1); - assert( 0 == strcmp(address1,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp")); - free_string(address1); + // test sync wallet + FfiResult_void_t sync_result = sync_wallet(wallet); + assert(sync_result.ok != NULL); + assert(sync_result.err == NULL); + free_void_result(sync_result); + + // test new address + FfiResult_char_ptr_t address_result1 = new_address(wallet); + assert(address_result1.ok != NULL); + assert(address_result1.err == NULL); + // printf("address1 = %s\n", *address_result1.ok); + assert( 0 == strcmp(*address_result1.ok,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp")); free_string_result(address_result1); - StringResult_t *address_result2 = new_address(wallet_ref); - char *address2 = get_string_ok(address_result2); - //printf("address2: %s\n", address2); - assert(0 == strcmp(address2,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf")); - free_string(address2); + FfiResult_char_ptr_t address_result2 = new_address(wallet); + assert(address_result2.ok != NULL); + assert(address_result2.err == NULL); + // printf("address2 = %s\n", *address_result2.ok); + assert( 0 == strcmp(*address_result2.ok,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf")); free_string_result(address_result2); - free_wallet_ref(wallet_ref); - // test free_wallet free_wallet_result(wallet_result); - // test free_wallet NULL doesn't crash - free_wallet_result(NULL); - // verify free_wallet after free_wallet fails (core dumped) - ////free_wallet_result(wallet_result); + //// free_wallet_result(wallet_result); // verify sync_wallet after free_wallet fails (core dumped) - ////VoidResult_t sync_result2 = sync_wallet(wallet_result); - + //// FfiResult_void_t sync_result2 = sync_wallet(wallet); + } + + // test get unspent utxos + { + char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; + char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; + + BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + DatabaseConfig_t *db_config = new_memory_config(); + + // new wallet + FfiResult_OpaqueWallet_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); + assert(wallet_result.err == NULL); + assert(wallet_result.ok != NULL); + + free_blockchain_config(bc_config); + free_database_config(db_config); + + OpaqueWallet_t *wallet = wallet_result.ok; + + // test sync wallet + FfiResult_void_t sync_result = sync_wallet(wallet); + assert(sync_result.ok != NULL); + assert(sync_result.err == NULL); + free_void_result(sync_result); + + // list unspent + FfiResultVec_LocalUtxo_t unspent_result = list_unspent(wallet); + assert(unspent_result.ok.len == 7); + assert(unspent_result.err == NULL); + + LocalUtxo_t * unspent_ptr = unspent_result.ok.ptr; + for (int i = 0; i < unspent_result.ok.len; i++) { + // printf("%d: outpoint.txid: %s\n", i, unspent_ptr[i].outpoint.txid); + assert(unspent_ptr[i].outpoint.txid != NULL); + // printf("%d: outpoint.vout: %d\n", i, unspent_ptr[i].outpoint.vout); + assert(unspent_ptr[i].outpoint.vout >= 0); + // printf("%d: txout.value: %ld\n", i, unspent_ptr[i].txout.value); + assert(unspent_ptr[i].txout.value > 0); + // printf("%d: txout.script_pubkey: %s\n", i, unspent_ptr[i].txout.script_pubkey); + assert(unspent_ptr[i].txout.script_pubkey != NULL); + // printf("%d: keychain: %d\n", i, unspent_ptr[i].keychain); + assert(unspent_ptr[i].keychain >= 0); + } + + free_unspent_result(unspent_result); + free_wallet_result(wallet_result); } return EXIT_SUCCESS; diff --git a/src/lib.rs b/src/lib.rs index 72e9910..e1a9c00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod blockchain; mod database; mod error; +mod types; mod wallet; /// The following test function is necessary for the header generation. diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..25a1f2a --- /dev/null +++ b/src/types.rs @@ -0,0 +1,34 @@ +use ::safer_ffi::prelude::*; +use safer_ffi::char_p::char_p_boxed; + +#[derive_ReprC] +#[repr(C)] +#[derive(Debug)] +pub struct FfiResult { + pub ok: Option>, + pub err: Option>, +} + +#[derive_ReprC] +#[repr(C)] +pub struct FfiResultVec { + pub ok: repr_c::Vec, + pub err: Option>, +} + +#[ffi_export] +fn free_string_result(string_result: FfiResult) { + drop(string_result) +} + +#[ffi_export] +fn free_void_result(void_result: FfiResult<()>) { + drop(void_result) +} + +// TODO do we need this? remove? +/// Frees a Rust-allocated string +#[ffi_export] +fn free_string(string: Option) { + drop(string) +} diff --git a/src/wallet.rs b/src/wallet.rs index 454f547..b7996dc 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,82 +1,23 @@ -use crate::blockchain::BlockchainConfig; -use crate::database::DatabaseConfig; -use crate::error::get_name; +use std::convert::TryFrom; + use ::safer_ffi::prelude::*; use bdk::bitcoin::network::constants::Network::Testnet; -use bdk::blockchain::{ - log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain, - ElectrumBlockchainConfig, -}; +use bdk::blockchain::{log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain}; use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase}; use bdk::wallet::AddressIndex::New; use bdk::{Error, Wallet}; use safer_ffi::boxed::Box; use safer_ffi::char_p::{char_p_boxed, char_p_ref}; -#[derive_ReprC] -#[ReprC::opaque] -pub struct VoidResult { - raw: Result<(), Error>, -} - -#[ffi_export] -fn get_void_err(void_result: &VoidResult) -> Option { - void_result - .raw - .as_ref() - .err() - .map(|e| char_p_boxed::try_from(get_name(e)).unwrap()) -} - -#[ffi_export] -fn free_void_result(void_result: Option>) { - drop(void_result) -} +use crate::blockchain::BlockchainConfig; +use crate::database::DatabaseConfig; +use crate::error::get_name; +use crate::types::{FfiResult, FfiResultVec}; #[derive_ReprC] #[ReprC::opaque] -pub struct StringResult { - raw: Result, -} - -#[ffi_export] -fn get_string_ok(string_result: &StringResult) -> Option { - string_result - .raw - .as_ref() - .ok() - .map(|s| char_p_boxed::try_from(s.clone()).unwrap()) -} - -#[ffi_export] -fn get_string_err(string_result: &StringResult) -> Option { - string_result - .raw - .as_ref() - .err() - .map(|e| char_p_boxed::try_from(get_name(e)).unwrap()) -} - -#[ffi_export] -fn free_string_result(string_result: Option>) { - drop(string_result) -} - -#[derive_ReprC] -#[ReprC::opaque] -pub struct WalletRef<'lt> { - raw: &'lt Wallet, -} - -#[ffi_export] -fn free_wallet_ref(wallet_ref: Option>) { - drop(wallet_ref) -} - -#[derive_ReprC] -#[ReprC::opaque] -pub struct WalletResult { - raw: Result, Error>, +pub struct OpaqueWallet { + raw: Wallet, } #[ffi_export] @@ -85,13 +26,83 @@ fn new_wallet_result( change_descriptor: Option, blockchain_config: &BlockchainConfig, database_config: &DatabaseConfig, -) -> Box { +) -> FfiResult { let descriptor = descriptor.to_string(); let change_descriptor = change_descriptor.map(|s| s.to_string()); let bc_config = &blockchain_config.raw; let db_config = &database_config.raw; let wallet_result = new_wallet(descriptor, change_descriptor, bc_config, db_config); - Box::new(WalletResult { raw: wallet_result }) + + match wallet_result { + Ok(w) => FfiResult { + ok: Some(Box::new(OpaqueWallet { raw: w })), + err: None, + }, + Err(e) => FfiResult { + ok: None, + err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())), + }, + } +} + +#[ffi_export] +fn free_wallet_result(wallet_result: FfiResult) { + drop(wallet_result); +} + +#[ffi_export] +fn free_unspent_result(unspent_result: FfiResultVec) { + drop(unspent_result) +} + +#[ffi_export] +fn sync_wallet(opaque_wallet: &OpaqueWallet) -> FfiResult<()> { + let void_result = opaque_wallet.raw.sync(log_progress(), Some(100)); + match void_result { + Ok(v) => FfiResult { + ok: Some(Box::new(v)), + err: None, + }, + Err(e) => FfiResult { + ok: None, + err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())), + }, + } +} + +#[ffi_export] +fn new_address(opaque_wallet: &OpaqueWallet) -> FfiResult { + let new_address = opaque_wallet.raw.get_address(New); + let string_result = new_address.map(|a| a.to_string()); + match string_result { + Ok(a) => FfiResult { + ok: Some(Box::new(char_p_boxed::try_from(a).unwrap())), + err: None, + }, + Err(e) => FfiResult { + ok: None, + err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())), + }, + } +} + +#[ffi_export] +fn list_unspent(opaque_wallet: &OpaqueWallet) -> FfiResultVec { + let unspent_result = opaque_wallet.raw.list_unspent(); + + match unspent_result { + Ok(v) => FfiResultVec { + ok: { + let ve: Vec = v.iter().map(|lu| LocalUtxo::from(lu)).collect(); + repr_c::Vec::from(ve) + }, + err: None, + }, + Err(e) => FfiResultVec { + ok: repr_c::Vec::EMPTY, + err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())), + }, + } } fn new_wallet( @@ -111,44 +122,64 @@ fn new_wallet( Wallet::new(descriptor, change_descriptor, network, database, client) } -#[ffi_export] -fn get_wallet_err(wallet_result: &WalletResult) -> Option { - wallet_result - .raw - .as_ref() - .err() - .map(|e| char_p_boxed::try_from(get_name(&e)).unwrap()) +// Non-opaque returned structs + +#[derive_ReprC] +#[repr(C)] +#[derive(Debug, Clone)] +pub struct OutPoint { + /// The referenced transaction's txid, as hex string + pub txid: char_p_boxed, + /// The index of the referenced output in its transaction's vout + pub vout: u32, } -#[ffi_export] -fn get_wallet_ok<'lt>(wallet_result: &'lt WalletResult) -> Option>> { - wallet_result - .raw - .as_ref() - .ok() - .map(|w| Box::new(WalletRef { raw: w })) +impl From<&bdk::bitcoin::OutPoint> for OutPoint { + fn from(op: &bdk::bitcoin::OutPoint) -> Self { + OutPoint { + txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(), + vout: op.vout, + } + } } -#[ffi_export] -fn sync_wallet<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box { - let void_result = wallet_ref.raw.sync(log_progress(), Some(100)); - Box::new(VoidResult { raw: void_result }) +#[derive_ReprC] +#[repr(C)] +#[derive(Debug, Clone)] +pub struct TxOut { + /// The value of the output, in satoshis + pub value: u64, + /// The script which must satisfy for the output to be spent, as hex string + pub script_pubkey: char_p_boxed, } -#[ffi_export] -fn new_address<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box { - let new_address = wallet_ref.raw.get_address(New); - let string_result = new_address.map(|a| a.to_string()); - Box::new(StringResult { raw: string_result }) +impl From<&bdk::bitcoin::TxOut> for TxOut { + fn from(to: &bdk::bitcoin::TxOut) -> Self { + TxOut { + value: to.value, + script_pubkey: char_p_boxed::try_from(to.script_pubkey.to_string()).unwrap(), + } + } } -#[ffi_export] -fn free_wallet_result(wallet_result: Option>) { - drop(wallet_result) +#[derive_ReprC] +#[repr(C)] +#[derive(Debug, Clone)] +pub struct LocalUtxo { + /// Reference to a transaction output + pub outpoint: OutPoint, + /// Transaction output + pub txout: TxOut, + /// Type of keychain, as short 0 for "external" or 1 for "internal" + pub keychain: u16, } -/// Frees a Rust-allocated string -#[ffi_export] -fn free_string(string: Option) { - drop(string) +impl From<&bdk::LocalUtxo> for LocalUtxo { + fn from(lu: &bdk::LocalUtxo) -> Self { + LocalUtxo { + outpoint: OutPoint::from(&lu.outpoint), + txout: TxOut::from(&lu.txout), + keychain: lu.keychain as u16, + } + } } diff --git a/test.sh b/test.sh index 03855c5..99a3ac8 100755 --- a/test.sh +++ b/test.sh @@ -6,9 +6,9 @@ cargo test --features c-headers -- generate_headers # cc export LD_LIBRARY_PATH=`pwd`/target/debug -#valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test -cc/bdk_ffi_test +valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test +#cc/bdk_ffi_test -## bdk-kotlin +# bdk-kotlin (cd bdk-kotlin && gradle test) (cd bdk-kotlin && gradle :android:connectedDebugAndroidTest) From f8365cc9391f43ba9cdbf6c6487fab17539a18c0 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 2 Jul 2021 21:36:46 -0700 Subject: [PATCH 024/272] Rename kotlin src directories to kotlin --- .../{java => kotlin}/org/bitcoindevkit/bdk/AndroidLibTest.kt | 0 .../{java => kotlin}/org/bitcoindevkit/bdk/BlockchainConfig.kt | 0 .../main/{java => kotlin}/org/bitcoindevkit/bdk/DatabaseConfig.kt | 0 .../src/main/{java => kotlin}/org/bitcoindevkit/bdk/FfiResult.kt | 0 .../src/main/{java => kotlin}/org/bitcoindevkit/bdk/JnaError.kt | 0 .../main/{java => kotlin}/org/bitcoindevkit/bdk/JnaException.kt | 0 .../src/main/{java => kotlin}/org/bitcoindevkit/bdk/LibBase.kt | 0 .../jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/LibJna.kt | 0 .../jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/Result.kt | 0 .../src/main/{java => kotlin}/org/bitcoindevkit/bdk/ResultBase.kt | 0 .../main/{java => kotlin}/org/bitcoindevkit/bdk/StringResult.kt | 0 .../{java => kotlin}/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt | 0 .../src/main/{java => kotlin}/org/bitcoindevkit/bdk/VoidResult.kt | 0 .../jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/Wallet.kt | 0 .../main/{java => kotlin}/org/bitcoindevkit/bdk/WalletResult.kt | 0 .../src/test/{java => kotlin}/org/bitcoindevkit/bdk/JvmLibTest.kt | 0 .../src/main/{java => kotlin}/org/bitcoindevkit/bdk/LibTest.kt | 0 17 files changed, 0 insertions(+), 0 deletions(-) rename bdk-kotlin/android/src/androidTest/{java => kotlin}/org/bitcoindevkit/bdk/AndroidLibTest.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/BlockchainConfig.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/DatabaseConfig.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/FfiResult.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/JnaError.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/JnaException.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/LibBase.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/LibJna.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/Result.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/ResultBase.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/StringResult.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/VoidResult.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/Wallet.kt (100%) rename bdk-kotlin/jvm/src/main/{java => kotlin}/org/bitcoindevkit/bdk/WalletResult.kt (100%) rename bdk-kotlin/jvm/src/test/{java => kotlin}/org/bitcoindevkit/bdk/JvmLibTest.kt (100%) rename bdk-kotlin/test-fixtures/src/main/{java => kotlin}/org/bitcoindevkit/bdk/LibTest.kt (100%) diff --git a/bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt similarity index 100% rename from bdk-kotlin/android/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt rename to bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/BlockchainConfig.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/BlockchainConfig.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/BlockchainConfig.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/BlockchainConfig.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/DatabaseConfig.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/DatabaseConfig.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/DatabaseConfig.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/FfiResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiResult.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/FfiResult.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiResult.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaError.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaError.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaError.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaError.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaException.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaException.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/JnaException.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaException.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibBase.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibBase.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibBase.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibBase.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/LibJna.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Result.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/Result.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Result.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/Result.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/ResultBase.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/ResultBase.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/ResultBase.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/ResultBase.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/StringResult.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/StringResult.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/StringResult.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/VoidResult.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/VoidResult.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/VoidResult.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/Wallet.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Wallet.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/Wallet.kt diff --git a/bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/WalletResult.kt similarity index 100% rename from bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/WalletResult.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/WalletResult.kt diff --git a/bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt b/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt similarity index 100% rename from bdk-kotlin/jvm/src/test/java/org/bitcoindevkit/bdk/JvmLibTest.kt rename to bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt diff --git a/bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt similarity index 100% rename from bdk-kotlin/test-fixtures/src/main/java/org/bitcoindevkit/bdk/LibTest.kt rename to bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt From 62f18bdc2c0e3badc5da5bd36aae9dd89633c125 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 3 Jul 2021 10:16:02 -0700 Subject: [PATCH 025/272] Reorganized code into wallet mod/package --- .../org/bitcoindevkit/bdk/ResultBase.kt | 38 -------------- .../bitcoindevkit/bdk/{ => types}/Result.kt | 6 ++- .../bdk/{ => types}/StringResult.kt | 3 +- .../bdk/{ => types}/VoidResult.kt | 3 +- .../bdk/{ => wallet}/BlockchainConfig.kt | 2 +- .../bdk/{ => wallet}/DatabaseConfig.kt | 4 +- .../bdk/{ => wallet}/VecLocalUtxoResult.kt | 6 ++- .../bitcoindevkit/bdk/{ => wallet}/Wallet.kt | 8 ++- .../bdk/{ => wallet}/WalletResult.kt | 4 +- .../kotlin/org/bitcoindevkit/bdk/LibTest.kt | 1 + cc/bdk_ffi.h | 52 +++++++++---------- src/lib.rs | 2 - src/types.rs | 2 +- src/{ => wallet}/blockchain.rs | 0 src/{ => wallet}/database.rs | 0 src/{wallet.rs => wallet/mod.rs} | 52 +++++++++++-------- 16 files changed, 85 insertions(+), 98 deletions(-) delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/ResultBase.kt rename bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/{ => types}/Result.kt (83%) rename bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/{ => types}/StringResult.kt (85%) rename bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/{ => types}/VoidResult.kt (83%) rename bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/{ => wallet}/BlockchainConfig.kt (93%) rename bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/{ => wallet}/DatabaseConfig.kt (89%) rename bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/{ => wallet}/VecLocalUtxoResult.kt (82%) rename bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/{ => wallet}/Wallet.kt (77%) rename bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/{ => wallet}/WalletResult.kt (80%) rename src/{ => wallet}/blockchain.rs (100%) rename src/{ => wallet}/database.rs (100%) rename src/{wallet.rs => wallet/mod.rs} (96%) diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/ResultBase.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/ResultBase.kt deleted file mode 100644 index 5fcf168..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/ResultBase.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.bitcoindevkit.bdk - -import com.sun.jna.Pointer -import com.sun.jna.PointerType -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -abstract class ResultBase internal constructor(private val pointerT: PT) : - LibBase() { - - protected open val log: Logger = LoggerFactory.getLogger(ResultBase::class.java) - - protected abstract fun err(pointerT: PT): Pointer? - - protected abstract fun ok(pointerT: PT): RT - - protected abstract fun free(pointerT: PT) - - private fun checkErr(pointerT: PT) { - val errPointer = err(pointerT) - val err = errPointer?.getString(0) - libJna.free_string(errPointer) - if (err != null) { - log.error("JnaError: $err") - throw JnaException(JnaError.valueOf(err)) - } - } - - fun value(): RT { - checkErr(pointerT) - return ok(pointerT) - } - - protected fun finalize() { - free(pointerT) - log.debug("$pointerT freed") - } -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/Result.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/Result.kt similarity index 83% rename from bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/Result.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/Result.kt index 6ceef7f..1208930 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/Result.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/Result.kt @@ -1,6 +1,10 @@ -package org.bitcoindevkit.bdk +package org.bitcoindevkit.bdk.types import com.sun.jna.Pointer +import org.bitcoindevkit.bdk.FfiResult +import org.bitcoindevkit.bdk.JnaError +import org.bitcoindevkit.bdk.JnaException +import org.bitcoindevkit.bdk.LibBase import org.slf4j.Logger import org.slf4j.LoggerFactory diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/StringResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt similarity index 85% rename from bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/StringResult.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt index 29da3eb..5489fcf 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/StringResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt @@ -1,6 +1,7 @@ -package org.bitcoindevkit.bdk +package org.bitcoindevkit.bdk.types import com.sun.jna.Pointer +import org.bitcoindevkit.bdk.LibJna class StringResult constructor(stringResultPtr: LibJna.FfiResult_char_ptr_t.ByValue) : Result(stringResultPtr) { diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/VoidResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt similarity index 83% rename from bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/VoidResult.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt index 03b46be..36eb799 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/VoidResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt @@ -1,6 +1,7 @@ -package org.bitcoindevkit.bdk +package org.bitcoindevkit.bdk.types import com.sun.jna.Pointer +import org.bitcoindevkit.bdk.LibJna class VoidResult constructor(voidResultPtr: LibJna.FfiResult_void_t.ByValue) : Result(voidResultPtr) { diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/BlockchainConfig.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt similarity index 93% rename from bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/BlockchainConfig.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt index e604975..592f299 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/BlockchainConfig.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt @@ -3,7 +3,7 @@ package org.bitcoindevkit.bdk import org.slf4j.Logger import org.slf4j.LoggerFactory -abstract class BlockchainConfig() : LibBase() { +abstract class BlockchainConfig : LibBase() { private val log: Logger = LoggerFactory.getLogger(BlockchainConfig::class.java) abstract val blockchainConfigT: LibJna.BlockchainConfig_t diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/DatabaseConfig.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/DatabaseConfig.kt similarity index 89% rename from bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/DatabaseConfig.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/DatabaseConfig.kt index 2f29fcd..f163ff0 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/DatabaseConfig.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/DatabaseConfig.kt @@ -3,7 +3,7 @@ package org.bitcoindevkit.bdk import org.slf4j.Logger import org.slf4j.LoggerFactory -abstract class DatabaseConfig() : LibBase() { +abstract class DatabaseConfig : LibBase() { private val log: Logger = LoggerFactory.getLogger(DatabaseConfig::class.java) abstract val databaseConfigT: LibJna.DatabaseConfig_t @@ -13,7 +13,7 @@ abstract class DatabaseConfig() : LibBase() { } } -class MemoryConfig() : DatabaseConfig() { +class MemoryConfig : DatabaseConfig() { private val log: Logger = LoggerFactory.getLogger(MemoryConfig::class.java) override val databaseConfigT = libJna.new_memory_config() diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt similarity index 82% rename from bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt index da69a1b..ec52e8e 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/VecLocalUtxoResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt @@ -1,5 +1,9 @@ -package org.bitcoindevkit.bdk +package org.bitcoindevkit.bdk.wallet +import org.bitcoindevkit.bdk.JnaError +import org.bitcoindevkit.bdk.JnaException +import org.bitcoindevkit.bdk.LibBase +import org.bitcoindevkit.bdk.LibJna import org.slf4j.Logger import org.slf4j.LoggerFactory diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/Wallet.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt similarity index 77% rename from bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/Wallet.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt index 7abea00..c8d9e4d 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/Wallet.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt @@ -1,5 +1,11 @@ -package org.bitcoindevkit.bdk +package org.bitcoindevkit.bdk.wallet +import org.bitcoindevkit.bdk.BlockchainConfig +import org.bitcoindevkit.bdk.DatabaseConfig +import org.bitcoindevkit.bdk.LibBase +import org.bitcoindevkit.bdk.LibJna +import org.bitcoindevkit.bdk.types.StringResult +import org.bitcoindevkit.bdk.types.VoidResult import org.slf4j.Logger import org.slf4j.LoggerFactory diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/WalletResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt similarity index 80% rename from bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/WalletResult.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt index ba8f979..5082df9 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/WalletResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt @@ -1,6 +1,8 @@ -package org.bitcoindevkit.bdk +package org.bitcoindevkit.bdk.wallet import com.sun.jna.Pointer +import org.bitcoindevkit.bdk.LibJna +import org.bitcoindevkit.bdk.types.Result class WalletResult constructor(walletResultPtr: LibJna.FfiResult_OpaqueWallet_t.ByValue) : Result(walletResultPtr) { diff --git a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt index 5c84338..cf943b9 100644 --- a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt @@ -1,5 +1,6 @@ package org.bitcoindevkit.bdk +import org.bitcoindevkit.bdk.wallet.Wallet import org.junit.Assert.* import org.junit.Test import org.slf4j.Logger diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h index eba536a..b3c3dad 100644 --- a/cc/bdk_ffi.h +++ b/cc/bdk_ffi.h @@ -50,6 +50,28 @@ FfiResult_OpaqueWallet_t new_wallet_result ( void free_wallet_result ( FfiResult_OpaqueWallet_t wallet_result); +typedef struct { + + void * ok; + + char * * err; + +} FfiResult_void_t; + +FfiResult_void_t sync_wallet ( + OpaqueWallet_t const * opaque_wallet); + +typedef struct { + + char * * ok; + + char * * err; + +} FfiResult_char_ptr_t; + +FfiResult_char_ptr_t new_address ( + OpaqueWallet_t const * opaque_wallet); + typedef struct { char * txid; @@ -97,34 +119,12 @@ typedef struct { } FfiResultVec_LocalUtxo_t; -void free_unspent_result ( - FfiResultVec_LocalUtxo_t unspent_result); - -typedef struct { - - void * ok; - - char * * err; - -} FfiResult_void_t; - -FfiResult_void_t sync_wallet ( - OpaqueWallet_t const * opaque_wallet); - -typedef struct { - - char * * ok; - - char * * err; - -} FfiResult_char_ptr_t; - -FfiResult_char_ptr_t new_address ( - OpaqueWallet_t const * opaque_wallet); - FfiResultVec_LocalUtxo_t list_unspent ( OpaqueWallet_t const * opaque_wallet); +void free_unspent_result ( + FfiResultVec_LocalUtxo_t unspent_result); + DatabaseConfig_t * new_memory_config (void); DatabaseConfig_t * new_sled_config ( @@ -141,7 +141,7 @@ void free_void_result ( FfiResult_void_t void_result); /** \brief - * Frees a Rust-allocated string + * Free a Rust-allocated string */ void free_string ( char * string); diff --git a/src/lib.rs b/src/lib.rs index e1a9c00..21b8266 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ #![deny(unsafe_code)] /* No `unsafe` needed! */ -mod blockchain; -mod database; mod error; mod types; mod wallet; diff --git a/src/types.rs b/src/types.rs index 25a1f2a..079e9cd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -27,7 +27,7 @@ fn free_void_result(void_result: FfiResult<()>) { } // TODO do we need this? remove? -/// Frees a Rust-allocated string +/// Free a Rust-allocated string #[ffi_export] fn free_string(string: Option) { drop(string) diff --git a/src/blockchain.rs b/src/wallet/blockchain.rs similarity index 100% rename from src/blockchain.rs rename to src/wallet/blockchain.rs diff --git a/src/database.rs b/src/wallet/database.rs similarity index 100% rename from src/database.rs rename to src/wallet/database.rs diff --git a/src/wallet.rs b/src/wallet/mod.rs similarity index 96% rename from src/wallet.rs rename to src/wallet/mod.rs index b7996dc..300d4c0 100644 --- a/src/wallet.rs +++ b/src/wallet/mod.rs @@ -9,11 +9,17 @@ use bdk::{Error, Wallet}; use safer_ffi::boxed::Box; use safer_ffi::char_p::{char_p_boxed, char_p_ref}; -use crate::blockchain::BlockchainConfig; -use crate::database::DatabaseConfig; +use blockchain::BlockchainConfig; +use database::DatabaseConfig; + use crate::error::get_name; use crate::types::{FfiResult, FfiResultVec}; +mod blockchain; +mod database; + +// create a new wallet + #[derive_ReprC] #[ReprC::opaque] pub struct OpaqueWallet { @@ -45,15 +51,29 @@ fn new_wallet_result( } } +fn new_wallet( + descriptor: String, + change_descriptor: Option, + blockchain_config: &AnyBlockchainConfig, + database_config: &AnyDatabaseConfig, +) -> Result, Error> { + let network = Testnet; + + let client = AnyBlockchain::from_config(blockchain_config)?; + let database = AnyDatabase::from_config(database_config)?; + + let descriptor: &str = descriptor.as_str(); + let change_descriptor: Option<&str> = change_descriptor.as_deref(); + + Wallet::new(descriptor, change_descriptor, network, database, client) +} + #[ffi_export] fn free_wallet_result(wallet_result: FfiResult) { drop(wallet_result); } -#[ffi_export] -fn free_unspent_result(unspent_result: FfiResultVec) { - drop(unspent_result) -} +// wallet operations #[ffi_export] fn sync_wallet(opaque_wallet: &OpaqueWallet) -> FfiResult<()> { @@ -105,24 +125,12 @@ fn list_unspent(opaque_wallet: &OpaqueWallet) -> FfiResultVec { } } -fn new_wallet( - descriptor: String, - change_descriptor: Option, - blockchain_config: &AnyBlockchainConfig, - database_config: &AnyDatabaseConfig, -) -> Result, Error> { - let network = Testnet; - - let client = AnyBlockchain::from_config(blockchain_config)?; - let database = AnyDatabase::from_config(database_config)?; - - let descriptor: &str = descriptor.as_str(); - let change_descriptor: Option<&str> = change_descriptor.as_deref(); - - Wallet::new(descriptor, change_descriptor, network, database, client) +#[ffi_export] +fn free_unspent_result(unspent_result: FfiResultVec) { + drop(unspent_result) } -// Non-opaque returned structs +// Non-opaque returned values #[derive_ReprC] #[repr(C)] From d00cc73261a16a063f1c12705f1d25fc998e0257 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 3 Jul 2021 19:07:49 -0700 Subject: [PATCH 026/272] Remove unneeded pointers from FfiResult types --- .../kotlin/org/bitcoindevkit/bdk/FfiResult.kt | 15 --- .../kotlin/org/bitcoindevkit/bdk/LibJna.kt | 108 +++++++++--------- .../org/bitcoindevkit/bdk/types/Result.kt | 41 ------- .../bitcoindevkit/bdk/types/StringResult.kt | 31 +++-- .../org/bitcoindevkit/bdk/types/VoidResult.kt | 31 +++-- .../bdk/wallet/VecLocalUtxoResult.kt | 19 ++- .../bitcoindevkit/bdk/wallet/WalletResult.kt | 32 ++++-- cc/bdk_ffi.h | 58 +++++----- cc/bdk_ffi_test.c | 104 +++++++++++------ src/types.rs | 15 +-- src/wallet/mod.rs | 47 ++++---- test.sh | 4 +- 12 files changed, 264 insertions(+), 241 deletions(-) delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiResult.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/Result.kt diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiResult.kt deleted file mode 100644 index 3755617..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiResult.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.bitcoindevkit.bdk - -import com.sun.jna.Pointer -import com.sun.jna.Structure - -abstract class FfiResult : Structure { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - - @JvmField - var ok: Pointer? = null - - @JvmField - var err: Pointer? = null -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt index bfb5383..41789c5 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt @@ -6,15 +6,21 @@ interface LibJna : Library { // typedef struct { // - // char * * ok; + // char * ok; // - // char * * err; + // char * err; // - // } FfiResult_char_ptr_t; - open class FfiResult_char_ptr_t : FfiResult() { + //} FfiResult_char_ptr_t; + open class FfiResult_char_ptr_t : Structure() { class ByValue : FfiResult_char_ptr_t(), Structure.ByValue class ByReference : FfiResult_char_ptr_t(), Structure.ByReference + @JvmField + var ok: String = "" + + @JvmField + var err: String = "" + override fun getFieldOrder() = listOf("ok", "err") } @@ -24,21 +30,27 @@ interface LibJna : Library { // typedef struct { // - // void * ok; + // int32_t ok; // - // char * * err; + // char * err; // - // } FfiResult_void_t; - open class FfiResult_void_t : FfiResult() { - class ByValue : FfiResult_void_t(), Structure.ByValue - class ByReference : FfiResult_void_t(), Structure.ByReference + //} FfiResult_int32_t; + open class FfiResult_int32_t : Structure() { + class ByValue : FfiResult_int32_t(), Structure.ByValue + class ByReference : FfiResult_int32_t(), Structure.ByReference + @JvmField + var ok: Int = 0 + + @JvmField + var err: String = "" + override fun getFieldOrder() = listOf("ok", "err") } - // void free_void_result ( - // FfiResult_void_t void_result); - fun free_void_result(void_result: FfiResult_void_t.ByValue) + // void free_int_result ( + // FfiResult_int32_t int_result); + fun free_int_result(void_result: FfiResult_int32_t.ByValue) // void free_string ( // char * string); @@ -82,17 +94,23 @@ interface LibJna : Library { // // OpaqueWallet_t * ok; // - // char * * err; + // char * err; // - // } FfiResult_OpaqueWallet_t; - open class FfiResult_OpaqueWallet_t : FfiResult() { - class ByValue : FfiResult_OpaqueWallet_t(), Structure.ByValue - class ByReference : FfiResult_OpaqueWallet_t(), Structure.ByReference + // } FfiResult_OpaqueWallet_ptr_t; + open class FfiResult_OpaqueWallet_ptr_t : Structure() { + class ByValue : FfiResult_OpaqueWallet_ptr_t(), Structure.ByValue + class ByReference : FfiResult_OpaqueWallet_ptr_t(), Structure.ByReference + @JvmField + var ok: OpaqueWallet_t = OpaqueWallet_t() + + @JvmField + var err: String = "" + override fun getFieldOrder() = listOf("ok", "err") } - // FfiResult_OpaqueWallet_t new_wallet_result ( + // FfiResult_OpaqueWallet_ptr_t new_wallet_result ( // char const * descriptor, // char const * change_descriptor, // BlockchainConfig_t const * blockchain_config, @@ -102,11 +120,11 @@ interface LibJna : Library { changeDescriptor: String?, blockchainConfig: BlockchainConfig_t, databaseConfig: DatabaseConfig_t, - ): FfiResult_OpaqueWallet_t.ByValue + ): FfiResult_OpaqueWallet_ptr_t.ByValue // void free_wallet_result ( - // FfiResult_OpaqueWallet_t wallet_result); - fun free_wallet_result(wallet_result: FfiResult_OpaqueWallet_t.ByValue) + // FfiResult_OpaqueWallet_ptr_t wallet_result); + fun free_wallet_result(wallet_result: FfiResult_OpaqueWallet_ptr_t.ByValue) // typedef struct { // @@ -155,19 +173,10 @@ interface LibJna : Library { // uint16_t keychain; // // } LocalUtxo_t; - open class LocalUtxo_t : Structure { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - - class ByValue : LocalUtxo_t, Structure.ByValue { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - } - - class ByReference : LocalUtxo_t, Structure.ByReference { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - } + open class LocalUtxo_t : Structure() { + + class ByValue : LocalUtxo_t(), Structure.ByValue + class ByReference : LocalUtxo_t(), Structure.ByReference @JvmField var outpoint: OutPoint_t? = null @@ -190,19 +199,10 @@ interface LibJna : Library { // size_t cap; // // } Vec_LocalUtxo_t; - open class Vec_LocalUtxo_t : Structure { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - - class ByReference : Vec_LocalUtxo_t, Structure.ByReference { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - } - - class ByValue : Vec_LocalUtxo_t, Structure.ByValue { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - } + open class Vec_LocalUtxo_t : Structure() { + + class ByReference : Vec_LocalUtxo_t(), Structure.ByReference + class ByValue : Vec_LocalUtxo_t(), Structure.ByValue @JvmField var ptr: LocalUtxo_t.ByReference? = null @@ -220,9 +220,9 @@ interface LibJna : Library { // // Vec_LocalUtxo_t ok; // - // char * * err; + // char * err; // - // } FfiResultVec_LocalUtxo_t; + // } FfiResult_Vec_LocalUtxo_t; open class FfiResultVec_LocalUtxo_t : Structure() { class ByValue : FfiResultVec_LocalUtxo_t(), Structure.ByValue @@ -232,18 +232,18 @@ interface LibJna : Library { var ok: Vec_LocalUtxo_t = Vec_LocalUtxo_t() @JvmField - var err: Pointer? = null + var err: String = "" override fun getFieldOrder() = listOf("ok", "err") } // void free_unspent_result ( - // FfiResultVec_LocalUtxo_t unspent_result); + // FfiResult_Vec_LocalUtxo_t unspent_result); fun free_unspent_result(unspent_result: FfiResultVec_LocalUtxo_t.ByValue) - // FfiResult_void_t sync_wallet ( + // FfiResult_int32_t sync_wallet ( // OpaqueWallet_t const * opaque_wallet); - fun sync_wallet(opaque_wallet: OpaqueWallet_t): FfiResult_void_t.ByValue + fun sync_wallet(opaque_wallet: OpaqueWallet_t): FfiResult_int32_t.ByValue // FfiResult_char_ptr_t new_address ( // OpaqueWallet_t const * opaque_wallet); diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/Result.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/Result.kt deleted file mode 100644 index 1208930..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/Result.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.bitcoindevkit.bdk.types - -import com.sun.jna.Pointer -import org.bitcoindevkit.bdk.FfiResult -import org.bitcoindevkit.bdk.JnaError -import org.bitcoindevkit.bdk.JnaException -import org.bitcoindevkit.bdk.LibBase -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -abstract class Result(private val ffiResult: T): LibBase() { - - protected open val log: Logger = LoggerFactory.getLogger(Result::class.java) - - protected abstract fun getOkValue(pointer: Pointer): RT - - protected abstract fun freeResult(ffiResult: T) - - fun value(): RT { - val err = ffiResult.err - val ok = ffiResult.ok - when { - err != null -> { - val errString = err.getPointer(0).getString(0) - log.error("JnaError: $errString") - throw JnaException(JnaError.valueOf(errString)) - } - ok != null -> { - return getOkValue(ok) - } - else -> { - throw JnaException(JnaError.Generic) - } - } - } - - protected fun finalize() { - freeResult(ffiResult) - log.debug("$ffiResult freed") - } -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt index 5489fcf..2b5b35e 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt @@ -1,16 +1,33 @@ package org.bitcoindevkit.bdk.types -import com.sun.jna.Pointer +import org.bitcoindevkit.bdk.JnaError +import org.bitcoindevkit.bdk.JnaException +import org.bitcoindevkit.bdk.LibBase import org.bitcoindevkit.bdk.LibJna +import org.slf4j.Logger +import org.slf4j.LoggerFactory -class StringResult constructor(stringResultPtr: LibJna.FfiResult_char_ptr_t.ByValue) : - Result(stringResultPtr) { +class StringResult constructor(private val ffiResultCharPtrT: LibJna.FfiResult_char_ptr_t.ByValue) : + LibBase() { - override fun getOkValue(pointer: Pointer): String { - return pointer.getPointer(0).getString(0) + private val log: Logger = LoggerFactory.getLogger(StringResult::class.java) + + fun value(): String { + val err = ffiResultCharPtrT.err + val ok = ffiResultCharPtrT.ok + when { + err.isNotEmpty() -> { + log.error("JnaError: $err") + throw JnaException(JnaError.valueOf(err)) + } + else -> { + return ok + } + } } - override fun freeResult(ffiResult: LibJna.FfiResult_char_ptr_t.ByValue) { - libJna.free_string_result(ffiResult) + protected fun finalize() { + libJna.free_string_result(ffiResultCharPtrT) + log.debug("$ffiResultCharPtrT freed") } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt index 36eb799..e3a79af 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt @@ -1,16 +1,33 @@ package org.bitcoindevkit.bdk.types -import com.sun.jna.Pointer +import org.bitcoindevkit.bdk.JnaError +import org.bitcoindevkit.bdk.JnaException +import org.bitcoindevkit.bdk.LibBase import org.bitcoindevkit.bdk.LibJna +import org.slf4j.Logger +import org.slf4j.LoggerFactory -class VoidResult constructor(voidResultPtr: LibJna.FfiResult_void_t.ByValue) : - Result(voidResultPtr) { +class VoidResult constructor(private val ffiResultInt32T: LibJna.FfiResult_int32_t.ByValue) : + LibBase() { - override fun getOkValue(pointer: Pointer) { - // No value + private val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) + + fun value(): Unit { + val err = ffiResultInt32T.err + //val ok = ffiResultInt32T.ok + when { + err.isNotEmpty() -> { + log.error("JnaError: $err") + throw JnaException(JnaError.valueOf(err)) + } + else -> { + return + } + } } - override fun freeResult(ffiResult: LibJna.FfiResult_void_t.ByValue) { - libJna.free_void_result(ffiResult) + protected fun finalize() { + libJna.free_int_result(ffiResultInt32T) + log.debug("$ffiResultInt32T freed") } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt index ec52e8e..86564ef 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt @@ -7,19 +7,18 @@ import org.bitcoindevkit.bdk.LibJna import org.slf4j.Logger import org.slf4j.LoggerFactory -class VecLocalUtxoResult(private val ffiResult: LibJna.FfiResultVec_LocalUtxo_t.ByValue) : +class VecLocalUtxoResult(private val ffiResultVecLocalUtxoT: LibJna.FfiResultVec_LocalUtxo_t.ByValue) : LibBase() { - protected open val log: Logger = LoggerFactory.getLogger(VecLocalUtxoResult::class.java) + private val log: Logger = LoggerFactory.getLogger(VecLocalUtxoResult::class.java) fun value(): Array { - val err = ffiResult.err - val ok = ffiResult.ok + val err = ffiResultVecLocalUtxoT.err + val ok = ffiResultVecLocalUtxoT.ok when { - err != null -> { - val errString = err.getPointer(0).getString(0) - log.error("JnaError: $errString") - throw JnaException(JnaError.valueOf(errString)) + err .isNotEmpty() -> { + log.error("JnaError: $err") + throw JnaException(JnaError.valueOf(err)) } else -> { val first = ok.ptr!! @@ -29,7 +28,7 @@ class VecLocalUtxoResult(private val ffiResult: LibJna.FfiResultVec_LocalUtxo_t. } protected fun finalize() { - libJna.free_unspent_result(ffiResult) - log.debug("$ffiResult freed") + libJna.free_unspent_result(ffiResultVecLocalUtxoT) + log.debug("$ffiResultVecLocalUtxoT freed") } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt index 5082df9..52592ae 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt @@ -1,17 +1,33 @@ package org.bitcoindevkit.bdk.wallet -import com.sun.jna.Pointer +import org.bitcoindevkit.bdk.JnaError +import org.bitcoindevkit.bdk.JnaException +import org.bitcoindevkit.bdk.LibBase import org.bitcoindevkit.bdk.LibJna -import org.bitcoindevkit.bdk.types.Result +import org.slf4j.Logger +import org.slf4j.LoggerFactory -class WalletResult constructor(walletResultPtr: LibJna.FfiResult_OpaqueWallet_t.ByValue) : - Result(walletResultPtr) { +class WalletResult constructor(private val ffiResultOpaqueWalletPtrT: LibJna.FfiResult_OpaqueWallet_ptr_t.ByValue) : + LibBase() { - override fun getOkValue(pointer: Pointer): LibJna.OpaqueWallet_t { - return LibJna.OpaqueWallet_t(pointer) + private val log: Logger = LoggerFactory.getLogger(WalletResult::class.java) + + fun value(): LibJna.OpaqueWallet_t { + val err = ffiResultOpaqueWalletPtrT.err + val ok = ffiResultOpaqueWalletPtrT.ok + when { + err.isNotEmpty() -> { + log.error("JnaError: $err") + throw JnaException(JnaError.valueOf(err)) + } + else -> { + return ok + } + } } - override fun freeResult(ffiResult: LibJna.FfiResult_OpaqueWallet_t.ByValue) { - libJna.free_wallet_result(ffiResult) + protected fun finalize() { + libJna.free_wallet_result(ffiResultOpaqueWalletPtrT) + log.debug("$ffiResultOpaqueWalletPtrT freed") } } \ No newline at end of file diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h index b3c3dad..452f0a1 100644 --- a/cc/bdk_ffi.h +++ b/cc/bdk_ffi.h @@ -14,21 +14,8 @@ extern "C" { #endif - -#include -#include - typedef struct BlockchainConfig BlockchainConfig_t; -BlockchainConfig_t * new_electrum_config ( - char const * url, - char const * socks5, - int16_t retry, - int16_t timeout); - -void free_blockchain_config ( - BlockchainConfig_t * blockchain_config); - typedef struct DatabaseConfig DatabaseConfig_t; typedef struct OpaqueWallet OpaqueWallet_t; @@ -37,35 +24,39 @@ typedef struct { OpaqueWallet_t * ok; - char * * err; + char * err; -} FfiResult_OpaqueWallet_t; +} FfiResult_OpaqueWallet_ptr_t; -FfiResult_OpaqueWallet_t new_wallet_result ( +FfiResult_OpaqueWallet_ptr_t new_wallet_result ( char const * descriptor, char const * change_descriptor, BlockchainConfig_t const * blockchain_config, DatabaseConfig_t const * database_config); void free_wallet_result ( - FfiResult_OpaqueWallet_t wallet_result); + FfiResult_OpaqueWallet_ptr_t wallet_result); + + +#include +#include typedef struct { - void * ok; + int32_t ok; - char * * err; + char * err; -} FfiResult_void_t; +} FfiResult_int32_t; -FfiResult_void_t sync_wallet ( +FfiResult_int32_t sync_wallet ( OpaqueWallet_t const * opaque_wallet); typedef struct { - char * * ok; + char * ok; - char * * err; + char * err; } FfiResult_char_ptr_t; @@ -115,15 +106,24 @@ typedef struct { Vec_LocalUtxo_t ok; - char * * err; + char * err; -} FfiResultVec_LocalUtxo_t; +} FfiResult_Vec_LocalUtxo_t; -FfiResultVec_LocalUtxo_t list_unspent ( +FfiResult_Vec_LocalUtxo_t list_unspent ( OpaqueWallet_t const * opaque_wallet); void free_unspent_result ( - FfiResultVec_LocalUtxo_t unspent_result); + FfiResult_Vec_LocalUtxo_t unspent_result); + +BlockchainConfig_t * new_electrum_config ( + char const * url, + char const * socks5, + int16_t retry, + int16_t timeout); + +void free_blockchain_config ( + BlockchainConfig_t * blockchain_config); DatabaseConfig_t * new_memory_config (void); @@ -137,8 +137,8 @@ void free_database_config ( void free_string_result ( FfiResult_char_ptr_t string_result); -void free_void_result ( - FfiResult_void_t void_result); +void free_int_result ( + FfiResult_int32_t int_result); /** \brief * Free a Rust-allocated string diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index 8ea5590..ce8df5f 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -13,14 +13,14 @@ int main (int argc, char const * const argv[]) DatabaseConfig_t *db_config = new_memory_config(); // new wallet with bad descriptor - FfiResult_OpaqueWallet_t wallet_result = new_wallet_result("bad","bad",bc_config,db_config); - assert(wallet_result.err != NULL); + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result("bad","bad",bc_config,db_config); + assert(strlen(wallet_result.err) > 0); assert(wallet_result.ok == NULL); free_blockchain_config(bc_config); free_database_config(db_config); - char *wallet_err = *wallet_result.err; + char *wallet_err = wallet_result.err; assert(wallet_err != NULL); assert( 0 == strcmp(wallet_err,"Descriptor") ); // printf("wallet err: %s\n", wallet_err); @@ -37,8 +37,9 @@ int main (int argc, char const * const argv[]) DatabaseConfig_t *db_config = new_memory_config(); // new wallet - FfiResult_OpaqueWallet_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); - assert(wallet_result.err == NULL); + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); + // printf("wallet_result.err = %ld\n", strlen(wallet_result.err)); + assert(strlen(wallet_result.err) == 0); assert(wallet_result.ok != NULL); free_blockchain_config(bc_config); @@ -46,35 +47,35 @@ int main (int argc, char const * const argv[]) OpaqueWallet_t *wallet = wallet_result.ok; - // test sync wallet - FfiResult_void_t sync_result = sync_wallet(wallet); - assert(sync_result.ok != NULL); - assert(sync_result.err == NULL); - free_void_result(sync_result); + // sync wallet + FfiResult_int32_t sync_result = sync_wallet(wallet); + assert(sync_result.ok == 0); + assert(strlen(sync_result.err) == 0); + free_int_result(sync_result); - // test new address - FfiResult_char_ptr_t address_result1 = new_address(wallet); - assert(address_result1.ok != NULL); - assert(address_result1.err == NULL); - // printf("address1 = %s\n", *address_result1.ok); - assert( 0 == strcmp(*address_result1.ok,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp")); - free_string_result(address_result1); + // new address + FfiResult_char_ptr_t address1_result = new_address(wallet); + assert(address1_result.ok != NULL); + assert(strlen(address1_result.err) == 0); + // printf("address1 = %s\n", *address1_result.ok); + assert( 0 == strcmp(address1_result.ok,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp")); + free_string_result(address1_result); - FfiResult_char_ptr_t address_result2 = new_address(wallet); - assert(address_result2.ok != NULL); - assert(address_result2.err == NULL); - // printf("address2 = %s\n", *address_result2.ok); - assert( 0 == strcmp(*address_result2.ok,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf")); - free_string_result(address_result2); + FfiResult_char_ptr_t address2_result = new_address(wallet); + assert(address2_result.ok != NULL); + assert(strlen(address2_result.err) == 0); + // printf("address2 = %s\n", *address2_result.ok); + assert( 0 == strcmp(address2_result.ok,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf")); + free_string_result(address2_result); - // test free_wallet + // free_wallet free_wallet_result(wallet_result); // verify free_wallet after free_wallet fails (core dumped) //// free_wallet_result(wallet_result); // verify sync_wallet after free_wallet fails (core dumped) - //// FfiResult_void_t sync_result2 = sync_wallet(wallet); + //// FfiResult_int32_t sync_result2 = sync_wallet(wallet); } // test get unspent utxos @@ -86,8 +87,8 @@ int main (int argc, char const * const argv[]) DatabaseConfig_t *db_config = new_memory_config(); // new wallet - FfiResult_OpaqueWallet_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); - assert(wallet_result.err == NULL); + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); + assert(strlen(wallet_result.err) == 0); assert(wallet_result.ok != NULL); free_blockchain_config(bc_config); @@ -95,16 +96,16 @@ int main (int argc, char const * const argv[]) OpaqueWallet_t *wallet = wallet_result.ok; - // test sync wallet - FfiResult_void_t sync_result = sync_wallet(wallet); - assert(sync_result.ok != NULL); - assert(sync_result.err == NULL); - free_void_result(sync_result); + // sync wallet + FfiResult_int32_t sync_result = sync_wallet(wallet); + assert(sync_result.ok == 0); + assert(strlen(sync_result.err) == 0); + free_int_result(sync_result); // list unspent - FfiResultVec_LocalUtxo_t unspent_result = list_unspent(wallet); + FfiResult_Vec_LocalUtxo_t unspent_result = list_unspent(wallet); assert(unspent_result.ok.len == 7); - assert(unspent_result.err == NULL); + assert(strlen(unspent_result.err) == 0); LocalUtxo_t * unspent_ptr = unspent_result.ok.ptr; for (int i = 0; i < unspent_result.ok.len; i++) { @@ -123,6 +124,41 @@ int main (int argc, char const * const argv[]) free_unspent_result(unspent_result); free_wallet_result(wallet_result); } + + // test balance + /*{ + char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; + char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; + + BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + DatabaseConfig_t *db_config = new_memory_config(); + + // new wallet + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); + assert(wallet_result.err == NULL); + assert(wallet_result.ok != NULL); + + free_blockchain_config(bc_config); + free_database_config(db_config); + + OpaqueWallet_t *wallet = wallet_result.ok; + + // sync wallet + FfiResult_int32_t sync_result = sync_wallet(wallet); + assert(sync_result.ok == 0); + assert(sync_result.err == NULL); + free_int_result(sync_result); + + // get balance + FfiResultT_uint64_t balance_result = balance(wallet); + assert(balance_result.err == NULL); + printf("balance = %lu\n", balance_result.ok); + assert(balance_result.ok > 0); + + // free balance and wallet results + free_u64_result(balance_result); + free_wallet_result(wallet_result); + }*/ return EXIT_SUCCESS; } diff --git a/src/types.rs b/src/types.rs index 079e9cd..1b2254f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -5,15 +5,8 @@ use safer_ffi::char_p::char_p_boxed; #[repr(C)] #[derive(Debug)] pub struct FfiResult { - pub ok: Option>, - pub err: Option>, -} - -#[derive_ReprC] -#[repr(C)] -pub struct FfiResultVec { - pub ok: repr_c::Vec, - pub err: Option>, + pub ok: T, + pub err: char_p_boxed, } #[ffi_export] @@ -22,8 +15,8 @@ fn free_string_result(string_result: FfiResult) { } #[ffi_export] -fn free_void_result(void_result: FfiResult<()>) { - drop(void_result) +fn free_int_result(int_result: FfiResult) { + drop(int_result) } // TODO do we need this? remove? diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 300d4c0..b6b1a1a 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -13,7 +13,8 @@ use blockchain::BlockchainConfig; use database::DatabaseConfig; use crate::error::get_name; -use crate::types::{FfiResult, FfiResultVec}; +use crate::types::FfiResult; +use std::ffi::CString; mod blockchain; mod database; @@ -32,7 +33,7 @@ fn new_wallet_result( change_descriptor: Option, blockchain_config: &BlockchainConfig, database_config: &DatabaseConfig, -) -> FfiResult { +) -> FfiResult>> { let descriptor = descriptor.to_string(); let change_descriptor = change_descriptor.map(|s| s.to_string()); let bc_config = &blockchain_config.raw; @@ -42,11 +43,11 @@ fn new_wallet_result( match wallet_result { Ok(w) => FfiResult { ok: Some(Box::new(OpaqueWallet { raw: w })), - err: None, + err: char_p_boxed::from(CString::default()), }, Err(e) => FfiResult { ok: None, - err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())), + err: char_p_boxed::try_from(get_name(&e)).unwrap(), }, } } @@ -69,23 +70,23 @@ fn new_wallet( } #[ffi_export] -fn free_wallet_result(wallet_result: FfiResult) { +fn free_wallet_result(wallet_result: FfiResult>>) { drop(wallet_result); } // wallet operations #[ffi_export] -fn sync_wallet(opaque_wallet: &OpaqueWallet) -> FfiResult<()> { - let void_result = opaque_wallet.raw.sync(log_progress(), Some(100)); - match void_result { - Ok(v) => FfiResult { - ok: Some(Box::new(v)), - err: None, +fn sync_wallet(opaque_wallet: &OpaqueWallet) -> FfiResult { + let int_result = opaque_wallet.raw.sync(log_progress(), Some(100)); + match int_result { + Ok(_v) => FfiResult { + ok: 0, + err: char_p_boxed::from(CString::default()), }, Err(e) => FfiResult { - ok: None, - err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())), + ok: -1, + err: char_p_boxed::try_from(get_name(&e)).unwrap(), }, } } @@ -96,37 +97,37 @@ fn new_address(opaque_wallet: &OpaqueWallet) -> FfiResult { let string_result = new_address.map(|a| a.to_string()); match string_result { Ok(a) => FfiResult { - ok: Some(Box::new(char_p_boxed::try_from(a).unwrap())), - err: None, + ok: char_p_boxed::try_from(a).unwrap(), + err: char_p_boxed::from(CString::default()), }, Err(e) => FfiResult { - ok: None, - err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())), + ok: char_p_boxed::from(CString::default()), + err: char_p_boxed::try_from(get_name(&e)).unwrap(), }, } } #[ffi_export] -fn list_unspent(opaque_wallet: &OpaqueWallet) -> FfiResultVec { +fn list_unspent(opaque_wallet: &OpaqueWallet) -> FfiResult> { let unspent_result = opaque_wallet.raw.list_unspent(); match unspent_result { - Ok(v) => FfiResultVec { + Ok(v) => FfiResult { ok: { let ve: Vec = v.iter().map(|lu| LocalUtxo::from(lu)).collect(); repr_c::Vec::from(ve) }, - err: None, + err: char_p_boxed::from(CString::default()), }, - Err(e) => FfiResultVec { + Err(e) => FfiResult { ok: repr_c::Vec::EMPTY, - err: Some(Box::new(char_p_boxed::try_from(get_name(&e)).unwrap())), + err: char_p_boxed::try_from(get_name(&e)).unwrap(), }, } } #[ffi_export] -fn free_unspent_result(unspent_result: FfiResultVec) { +fn free_unspent_result(unspent_result: FfiResult>) { drop(unspent_result) } diff --git a/test.sh b/test.sh index 99a3ac8..c5a23fc 100755 --- a/test.sh +++ b/test.sh @@ -6,8 +6,8 @@ cargo test --features c-headers -- generate_headers # cc export LD_LIBRARY_PATH=`pwd`/target/debug -valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test -#cc/bdk_ffi_test +#valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test +cc/bdk_ffi_test # bdk-kotlin (cd bdk-kotlin && gradle test) From 844326514251e627cd8d8c634a42e43e9c9eddb4 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 3 Jul 2021 19:24:29 -0700 Subject: [PATCH 027/272] Add FfiResultVoid type --- .../kotlin/org/bitcoindevkit/bdk/LibJna.kt | 25 ++++++++----------- .../org/bitcoindevkit/bdk/types/VoidResult.kt | 10 ++++---- cc/bdk_ffi.h | 18 ++++++------- cc/bdk_ffi_test.c | 17 ++++++------- src/types.rs | 11 ++++++-- src/wallet/mod.rs | 10 +++----- 6 files changed, 43 insertions(+), 48 deletions(-) diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt index 41789c5..f31d346 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt @@ -30,27 +30,22 @@ interface LibJna : Library { // typedef struct { // - // int32_t ok; - // // char * err; // - //} FfiResult_int32_t; - open class FfiResult_int32_t : Structure() { - class ByValue : FfiResult_int32_t(), Structure.ByValue - class ByReference : FfiResult_int32_t(), Structure.ByReference - - @JvmField - var ok: Int = 0 + //} FfiResultVoid_t; + open class FfiResultVoid_t : Structure() { + class ByValue : FfiResultVoid_t(), Structure.ByValue + class ByReference : FfiResultVoid_t(), Structure.ByReference @JvmField var err: String = "" - override fun getFieldOrder() = listOf("ok", "err") + override fun getFieldOrder() = listOf("err") } - // void free_int_result ( - // FfiResult_int32_t int_result); - fun free_int_result(void_result: FfiResult_int32_t.ByValue) + // void free_void_result ( + // FfiResultVoid_t void_result); + fun free_void_result(void_result: FfiResultVoid_t.ByValue) // void free_string ( // char * string); @@ -241,9 +236,9 @@ interface LibJna : Library { // FfiResult_Vec_LocalUtxo_t unspent_result); fun free_unspent_result(unspent_result: FfiResultVec_LocalUtxo_t.ByValue) - // FfiResult_int32_t sync_wallet ( + // FfiResultVoid_t sync_wallet ( // OpaqueWallet_t const * opaque_wallet); - fun sync_wallet(opaque_wallet: OpaqueWallet_t): FfiResult_int32_t.ByValue + fun sync_wallet(opaque_wallet: OpaqueWallet_t): FfiResultVoid_t.ByValue // FfiResult_char_ptr_t new_address ( // OpaqueWallet_t const * opaque_wallet); diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt index e3a79af..66071d9 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt @@ -7,14 +7,14 @@ import org.bitcoindevkit.bdk.LibJna import org.slf4j.Logger import org.slf4j.LoggerFactory -class VoidResult constructor(private val ffiResultInt32T: LibJna.FfiResult_int32_t.ByValue) : +class VoidResult constructor(private val ffiResultVoidT: LibJna.FfiResultVoid_t.ByValue) : LibBase() { private val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) fun value(): Unit { - val err = ffiResultInt32T.err - //val ok = ffiResultInt32T.ok + val err = ffiResultVoidT.err + when { err.isNotEmpty() -> { log.error("JnaError: $err") @@ -27,7 +27,7 @@ class VoidResult constructor(private val ffiResultInt32T: LibJna.FfiResult_int32 } protected fun finalize() { - libJna.free_int_result(ffiResultInt32T) - log.debug("$ffiResultInt32T freed") + libJna.free_void_result(ffiResultVoidT) + log.debug("$ffiResultVoidT freed") } } \ No newline at end of file diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h index 452f0a1..058fe82 100644 --- a/cc/bdk_ffi.h +++ b/cc/bdk_ffi.h @@ -37,19 +37,13 @@ FfiResult_OpaqueWallet_ptr_t new_wallet_result ( void free_wallet_result ( FfiResult_OpaqueWallet_ptr_t wallet_result); - -#include -#include - typedef struct { - int32_t ok; - char * err; -} FfiResult_int32_t; +} FfiResultVoid_t; -FfiResult_int32_t sync_wallet ( +FfiResultVoid_t sync_wallet ( OpaqueWallet_t const * opaque_wallet); typedef struct { @@ -63,6 +57,10 @@ typedef struct { FfiResult_char_ptr_t new_address ( OpaqueWallet_t const * opaque_wallet); + +#include +#include + typedef struct { char * txid; @@ -137,8 +135,8 @@ void free_database_config ( void free_string_result ( FfiResult_char_ptr_t string_result); -void free_int_result ( - FfiResult_int32_t int_result); +void free_void_result ( + FfiResultVoid_t void_result); /** \brief * Free a Rust-allocated string diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index ce8df5f..99744ce 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -48,10 +48,9 @@ int main (int argc, char const * const argv[]) OpaqueWallet_t *wallet = wallet_result.ok; // sync wallet - FfiResult_int32_t sync_result = sync_wallet(wallet); - assert(sync_result.ok == 0); + FfiResultVoid_t sync_result = sync_wallet(wallet); assert(strlen(sync_result.err) == 0); - free_int_result(sync_result); + free_void_result(sync_result); // new address FfiResult_char_ptr_t address1_result = new_address(wallet); @@ -75,7 +74,7 @@ int main (int argc, char const * const argv[]) //// free_wallet_result(wallet_result); // verify sync_wallet after free_wallet fails (core dumped) - //// FfiResult_int32_t sync_result2 = sync_wallet(wallet); + //// FfiResultVoid_t sync_result2 = sync_wallet(wallet); } // test get unspent utxos @@ -97,10 +96,9 @@ int main (int argc, char const * const argv[]) OpaqueWallet_t *wallet = wallet_result.ok; // sync wallet - FfiResult_int32_t sync_result = sync_wallet(wallet); - assert(sync_result.ok == 0); + FfiResultVoid_t sync_result = sync_wallet(wallet); assert(strlen(sync_result.err) == 0); - free_int_result(sync_result); + free_void_result(sync_result); // list unspent FfiResult_Vec_LocalUtxo_t unspent_result = list_unspent(wallet); @@ -144,10 +142,9 @@ int main (int argc, char const * const argv[]) OpaqueWallet_t *wallet = wallet_result.ok; // sync wallet - FfiResult_int32_t sync_result = sync_wallet(wallet); - assert(sync_result.ok == 0); + FfiResultVoid_t sync_result = sync_wallet(wallet); assert(sync_result.err == NULL); - free_int_result(sync_result); + free_void_result(sync_result); // get balance FfiResultT_uint64_t balance_result = balance(wallet); diff --git a/src/types.rs b/src/types.rs index 1b2254f..c4dd5ac 100644 --- a/src/types.rs +++ b/src/types.rs @@ -9,14 +9,21 @@ pub struct FfiResult { pub err: char_p_boxed, } +#[derive_ReprC] +#[repr(C)] +#[derive(Debug)] +pub struct FfiResultVoid { + pub err: char_p_boxed, +} + #[ffi_export] fn free_string_result(string_result: FfiResult) { drop(string_result) } #[ffi_export] -fn free_int_result(int_result: FfiResult) { - drop(int_result) +fn free_void_result(void_result: FfiResultVoid) { + drop(void_result) } // TODO do we need this? remove? diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index b6b1a1a..bf12321 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -13,7 +13,7 @@ use blockchain::BlockchainConfig; use database::DatabaseConfig; use crate::error::get_name; -use crate::types::FfiResult; +use crate::types::{FfiResult, FfiResultVoid}; use std::ffi::CString; mod blockchain; @@ -77,15 +77,13 @@ fn free_wallet_result(wallet_result: FfiResult>>) { // wallet operations #[ffi_export] -fn sync_wallet(opaque_wallet: &OpaqueWallet) -> FfiResult { +fn sync_wallet(opaque_wallet: &OpaqueWallet) -> FfiResultVoid { let int_result = opaque_wallet.raw.sync(log_progress(), Some(100)); match int_result { - Ok(_v) => FfiResult { - ok: 0, + Ok(_v) => FfiResultVoid { err: char_p_boxed::from(CString::default()), }, - Err(e) => FfiResult { - ok: -1, + Err(e) => FfiResultVoid { err: char_p_boxed::try_from(get_name(&e)).unwrap(), }, } From adadcbc982cdbae52b4c796b64909083d9305001 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 3 Jul 2021 20:40:08 -0700 Subject: [PATCH 028/272] Return FfiResult errors as FfiError enum short values --- .../bdk/{JnaError.kt => FfiError.kt} | 3 +- .../org/bitcoindevkit/bdk/FfiException.kt | 14 ++ .../org/bitcoindevkit/bdk/JnaException.kt | 3 - .../kotlin/org/bitcoindevkit/bdk/LibJna.kt | 16 +- .../bitcoindevkit/bdk/types/StringResult.kt | 8 +- .../org/bitcoindevkit/bdk/types/VoidResult.kt | 8 +- .../bdk/wallet/VecLocalUtxoResult.kt | 8 +- .../bitcoindevkit/bdk/wallet/WalletResult.kt | 8 +- .../kotlin/org/bitcoindevkit/bdk/LibTest.kt | 4 +- cc/bdk_ffi.h | 171 +++++++++++++----- cc/bdk_ffi_test.c | 25 +-- src/error.rs | 134 +++++++++----- src/types.rs | 5 +- src/wallet/mod.rs | 18 +- 14 files changed, 281 insertions(+), 144 deletions(-) rename bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/{JnaError.kt => FfiError.kt} (96%) create mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiException.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaException.kt diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaError.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiError.kt similarity index 96% rename from bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaError.kt rename to bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiError.kt index 32dcd5a..d5738f9 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaError.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiError.kt @@ -1,6 +1,7 @@ package org.bitcoindevkit.bdk -enum class JnaError { +enum class FfiError { + None, InvalidU32Bytes, Generic, ScriptDoesntHaveAddressForm, diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiException.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiException.kt new file mode 100644 index 0000000..5fd6ba8 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiException.kt @@ -0,0 +1,14 @@ +package org.bitcoindevkit.bdk + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class FfiException(val err: FfiError) : Exception() { + private val log: Logger = LoggerFactory.getLogger(FfiException::class.java) + + init { + log.error("JnaError: [{}] {}",err.ordinal, err.name) + } + + internal constructor(err: Short) : this(FfiError.values()[err.toInt()]) +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaException.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaException.kt deleted file mode 100644 index 6ce9768..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/JnaException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.bitcoindevkit.bdk - -class JnaException internal constructor(val err: JnaError) : Exception() \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt index f31d346..ffc36db 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt @@ -8,7 +8,7 @@ interface LibJna : Library { // // char * ok; // - // char * err; + // FfiError_t err; // //} FfiResult_char_ptr_t; open class FfiResult_char_ptr_t : Structure() { @@ -19,7 +19,7 @@ interface LibJna : Library { var ok: String = "" @JvmField - var err: String = "" + var err: Short = 0 override fun getFieldOrder() = listOf("ok", "err") } @@ -30,7 +30,7 @@ interface LibJna : Library { // typedef struct { // - // char * err; + // FfiError_t err; // //} FfiResultVoid_t; open class FfiResultVoid_t : Structure() { @@ -38,7 +38,7 @@ interface LibJna : Library { class ByReference : FfiResultVoid_t(), Structure.ByReference @JvmField - var err: String = "" + var err: Short = 0 override fun getFieldOrder() = listOf("err") } @@ -89,7 +89,7 @@ interface LibJna : Library { // // OpaqueWallet_t * ok; // - // char * err; + // FfiError_t err; // // } FfiResult_OpaqueWallet_ptr_t; open class FfiResult_OpaqueWallet_ptr_t : Structure() { @@ -100,7 +100,7 @@ interface LibJna : Library { var ok: OpaqueWallet_t = OpaqueWallet_t() @JvmField - var err: String = "" + var err: Short = 0 override fun getFieldOrder() = listOf("ok", "err") } @@ -215,7 +215,7 @@ interface LibJna : Library { // // Vec_LocalUtxo_t ok; // - // char * err; + // FfiError_t err; // // } FfiResult_Vec_LocalUtxo_t; open class FfiResultVec_LocalUtxo_t : Structure() { @@ -227,7 +227,7 @@ interface LibJna : Library { var ok: Vec_LocalUtxo_t = Vec_LocalUtxo_t() @JvmField - var err: String = "" + var err: Short = 0 override fun getFieldOrder() = listOf("ok", "err") } diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt index 2b5b35e..37d86a7 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt @@ -1,7 +1,6 @@ package org.bitcoindevkit.bdk.types -import org.bitcoindevkit.bdk.JnaError -import org.bitcoindevkit.bdk.JnaException +import org.bitcoindevkit.bdk.FfiException import org.bitcoindevkit.bdk.LibBase import org.bitcoindevkit.bdk.LibJna import org.slf4j.Logger @@ -16,9 +15,8 @@ class StringResult constructor(private val ffiResultCharPtrT: LibJna.FfiResult_c val err = ffiResultCharPtrT.err val ok = ffiResultCharPtrT.ok when { - err.isNotEmpty() -> { - log.error("JnaError: $err") - throw JnaException(JnaError.valueOf(err)) + err > 0 -> { + throw FfiException(err) } else -> { return ok diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt index 66071d9..6b9b685 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt @@ -1,7 +1,6 @@ package org.bitcoindevkit.bdk.types -import org.bitcoindevkit.bdk.JnaError -import org.bitcoindevkit.bdk.JnaException +import org.bitcoindevkit.bdk.FfiException import org.bitcoindevkit.bdk.LibBase import org.bitcoindevkit.bdk.LibJna import org.slf4j.Logger @@ -16,9 +15,8 @@ class VoidResult constructor(private val ffiResultVoidT: LibJna.FfiResultVoid_t. val err = ffiResultVoidT.err when { - err.isNotEmpty() -> { - log.error("JnaError: $err") - throw JnaException(JnaError.valueOf(err)) + err > 0 -> { + throw FfiException(err) } else -> { return diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt index 86564ef..c569c49 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt @@ -1,7 +1,6 @@ package org.bitcoindevkit.bdk.wallet -import org.bitcoindevkit.bdk.JnaError -import org.bitcoindevkit.bdk.JnaException +import org.bitcoindevkit.bdk.FfiException import org.bitcoindevkit.bdk.LibBase import org.bitcoindevkit.bdk.LibJna import org.slf4j.Logger @@ -16,9 +15,8 @@ class VecLocalUtxoResult(private val ffiResultVecLocalUtxoT: LibJna.FfiResultVec val err = ffiResultVecLocalUtxoT.err val ok = ffiResultVecLocalUtxoT.ok when { - err .isNotEmpty() -> { - log.error("JnaError: $err") - throw JnaException(JnaError.valueOf(err)) + err > 0 -> { + throw FfiException(err) } else -> { val first = ok.ptr!! diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt index 52592ae..2d43b0c 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt @@ -1,7 +1,6 @@ package org.bitcoindevkit.bdk.wallet -import org.bitcoindevkit.bdk.JnaError -import org.bitcoindevkit.bdk.JnaException +import org.bitcoindevkit.bdk.FfiException import org.bitcoindevkit.bdk.LibBase import org.bitcoindevkit.bdk.LibJna import org.slf4j.Logger @@ -16,9 +15,8 @@ class WalletResult constructor(private val ffiResultOpaqueWalletPtrT: LibJna.Ffi val err = ffiResultOpaqueWalletPtrT.err val ok = ffiResultOpaqueWalletPtrT.ok when { - err.isNotEmpty() -> { - log.error("JnaError: $err") - throw JnaException(JnaError.valueOf(err)) + err > 0 -> { + throw FfiException(err) } else -> { return ok diff --git a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt index cf943b9..79d99d0 100644 --- a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt @@ -31,10 +31,10 @@ abstract class LibTest : LibBase() { @Test fun walletResultError() { - val jnaException = assertThrows(JnaException::class.java) { + val jnaException = assertThrows(FfiException::class.java) { Wallet("bad", "bad", blockchainConfig, databaseConfig) } - assertEquals(jnaException.err, JnaError.Descriptor) + assertEquals(jnaException.err, FfiError.Descriptor) } // @Test diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h index 058fe82..3832f3e 100644 --- a/cc/bdk_ffi.h +++ b/cc/bdk_ffi.h @@ -14,8 +14,136 @@ extern "C" { #endif + +#include +#include + +/** \remark Has the same ABI as `uint16_t` **/ +#ifdef DOXYGEN +typedef enum FfiError +#else +typedef uint16_t FfiError_t; enum +#endif +{ + /** . */ + FFI_ERROR_NONE, + /** . */ + FFI_ERROR_INVALID_U32_BYTES, + /** . */ + FFI_ERROR_GENERIC, + /** . */ + FFI_ERROR_SCRIPT_DOESNT_HAVE_ADDRESS_FORM, + /** . */ + FFI_ERROR_SINGLE_RECIPIENT_MULTIPLE_OUTPUTS, + /** . */ + FFI_ERROR_SINGLE_RECIPIENT_NO_INPUTS, + /** . */ + FFI_ERROR_NO_RECIPIENTS, + /** . */ + FFI_ERROR_NO_UTXOS_SELECTED, + /** . */ + FFI_ERROR_OUTPUT_BELOW_DUST_LIMIT, + /** . */ + FFI_ERROR_INSUFFICIENT_FUNDS, + /** . */ + FFI_ERROR_BN_B_TOTAL_TRIES_EXCEEDED, + /** . */ + FFI_ERROR_BN_B_NO_EXACT_MATCH, + /** . */ + FFI_ERROR_UNKNOWN_UTXO, + /** . */ + FFI_ERROR_TRANSACTION_NOT_FOUND, + /** . */ + FFI_ERROR_TRANSACTION_CONFIRMED, + /** . */ + FFI_ERROR_IRREPLACEABLE_TRANSACTION, + /** . */ + FFI_ERROR_FEE_RATE_TOO_LOW, + /** . */ + FFI_ERROR_FEE_TOO_LOW, + /** . */ + FFI_ERROR_MISSING_KEY_ORIGIN, + /** . */ + FFI_ERROR_KEY, + /** . */ + FFI_ERROR_CHECKSUM_MISMATCH, + /** . */ + FFI_ERROR_SPENDING_POLICY_REQUIRED, + /** . */ + FFI_ERROR_INVALID_POLICY_PATH_ERROR, + /** . */ + FFI_ERROR_SIGNER, + /** . */ + FFI_ERROR_INVALID_PROGRESS_VALUE, + /** . */ + FFI_ERROR_PROGRESS_UPDATE_ERROR, + /** . */ + FFI_ERROR_INVALID_OUTPOINT, + /** . */ + FFI_ERROR_DESCRIPTOR, + /** . */ + FFI_ERROR_ADDRESS_VALIDATOR, + /** . */ + FFI_ERROR_ENCODE, + /** . */ + FFI_ERROR_MINISCRIPT, + /** . */ + FFI_ERROR_BIP32, + /** . */ + FFI_ERROR_SECP256K1, + /** . */ + FFI_ERROR_JSON, + /** . */ + FFI_ERROR_HEX, + /** . */ + FFI_ERROR_PSBT, + /** . */ + FFI_ERROR_ELECTRUM, + /** . */ + FFI_ERROR_SLED, +} +#ifdef DOXYGEN +FfiError_t +#endif +; + +typedef struct { + + char * ok; + + FfiError_t err; + +} FfiResult_char_ptr_t; + +void free_string_result ( + FfiResult_char_ptr_t string_result); + +typedef struct { + + FfiError_t err; + +} FfiResultVoid_t; + +void free_void_result ( + FfiResultVoid_t void_result); + +/** \brief + * Free a Rust-allocated string + */ +void free_string ( + char * string); + typedef struct BlockchainConfig BlockchainConfig_t; +BlockchainConfig_t * new_electrum_config ( + char const * url, + char const * socks5, + int16_t retry, + int16_t timeout); + +void free_blockchain_config ( + BlockchainConfig_t * blockchain_config); + typedef struct DatabaseConfig DatabaseConfig_t; typedef struct OpaqueWallet OpaqueWallet_t; @@ -24,7 +152,7 @@ typedef struct { OpaqueWallet_t * ok; - char * err; + FfiError_t err; } FfiResult_OpaqueWallet_ptr_t; @@ -37,30 +165,12 @@ FfiResult_OpaqueWallet_ptr_t new_wallet_result ( void free_wallet_result ( FfiResult_OpaqueWallet_ptr_t wallet_result); -typedef struct { - - char * err; - -} FfiResultVoid_t; - FfiResultVoid_t sync_wallet ( OpaqueWallet_t const * opaque_wallet); -typedef struct { - - char * ok; - - char * err; - -} FfiResult_char_ptr_t; - FfiResult_char_ptr_t new_address ( OpaqueWallet_t const * opaque_wallet); - -#include -#include - typedef struct { char * txid; @@ -104,7 +214,7 @@ typedef struct { Vec_LocalUtxo_t ok; - char * err; + FfiError_t err; } FfiResult_Vec_LocalUtxo_t; @@ -114,15 +224,6 @@ FfiResult_Vec_LocalUtxo_t list_unspent ( void free_unspent_result ( FfiResult_Vec_LocalUtxo_t unspent_result); -BlockchainConfig_t * new_electrum_config ( - char const * url, - char const * socks5, - int16_t retry, - int16_t timeout); - -void free_blockchain_config ( - BlockchainConfig_t * blockchain_config); - DatabaseConfig_t * new_memory_config (void); DatabaseConfig_t * new_sled_config ( @@ -132,18 +233,6 @@ DatabaseConfig_t * new_sled_config ( void free_database_config ( DatabaseConfig_t * database_config); -void free_string_result ( - FfiResult_char_ptr_t string_result); - -void free_void_result ( - FfiResultVoid_t void_result); - -/** \brief - * Free a Rust-allocated string - */ -void free_string ( - char * string); - #ifdef __cplusplus } /* extern "C" */ diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index 99744ce..fb6674a 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -14,16 +14,11 @@ int main (int argc, char const * const argv[]) // new wallet with bad descriptor FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result("bad","bad",bc_config,db_config); - assert(strlen(wallet_result.err) > 0); + assert(wallet_result.err == FFI_ERROR_DESCRIPTOR); assert(wallet_result.ok == NULL); free_blockchain_config(bc_config); - free_database_config(db_config); - - char *wallet_err = wallet_result.err; - assert(wallet_err != NULL); - assert( 0 == strcmp(wallet_err,"Descriptor") ); - // printf("wallet err: %s\n", wallet_err); + free_database_config(db_config); free_wallet_result(wallet_result); } @@ -38,8 +33,8 @@ int main (int argc, char const * const argv[]) // new wallet FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); - // printf("wallet_result.err = %ld\n", strlen(wallet_result.err)); - assert(strlen(wallet_result.err) == 0); + // printf("wallet_result.err = %d\n", wallet_result.err)); + assert(wallet_result.err == FFI_ERROR_NONE); assert(wallet_result.ok != NULL); free_blockchain_config(bc_config); @@ -49,20 +44,20 @@ int main (int argc, char const * const argv[]) // sync wallet FfiResultVoid_t sync_result = sync_wallet(wallet); - assert(strlen(sync_result.err) == 0); + assert(sync_result.err == FFI_ERROR_NONE); free_void_result(sync_result); // new address FfiResult_char_ptr_t address1_result = new_address(wallet); assert(address1_result.ok != NULL); - assert(strlen(address1_result.err) == 0); + assert(address1_result.err == FFI_ERROR_NONE); // printf("address1 = %s\n", *address1_result.ok); assert( 0 == strcmp(address1_result.ok,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp")); free_string_result(address1_result); FfiResult_char_ptr_t address2_result = new_address(wallet); assert(address2_result.ok != NULL); - assert(strlen(address2_result.err) == 0); + assert(address2_result.err == FFI_ERROR_NONE); // printf("address2 = %s\n", *address2_result.ok); assert( 0 == strcmp(address2_result.ok,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf")); free_string_result(address2_result); @@ -87,7 +82,7 @@ int main (int argc, char const * const argv[]) // new wallet FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); - assert(strlen(wallet_result.err) == 0); + assert(wallet_result.err == FFI_ERROR_NONE); assert(wallet_result.ok != NULL); free_blockchain_config(bc_config); @@ -97,13 +92,13 @@ int main (int argc, char const * const argv[]) // sync wallet FfiResultVoid_t sync_result = sync_wallet(wallet); - assert(strlen(sync_result.err) == 0); + assert(sync_result.err == FFI_ERROR_NONE); free_void_result(sync_result); // list unspent FfiResult_Vec_LocalUtxo_t unspent_result = list_unspent(wallet); assert(unspent_result.ok.len == 7); - assert(strlen(unspent_result.err) == 0); + assert(unspent_result.err == FFI_ERROR_NONE); LocalUtxo_t * unspent_ptr = unspent_result.ok.ptr; for (int i = 0; i < unspent_result.ok.len; i++) { diff --git a/src/error.rs b/src/error.rs index 6af73f0..f27c6dd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,46 +1,94 @@ +use ::safer_ffi::prelude::*; use bdk::Error; -pub fn get_name(error: &bdk::Error) -> String { - match error { - Error::InvalidU32Bytes(_) => "InvalidU32Bytes", - Error::Generic(_) => "Generic", - Error::ScriptDoesntHaveAddressForm => "ScriptDoesntHaveAddressForm", - Error::SingleRecipientMultipleOutputs => "SingleRecipientMultipleOutputs", - Error::SingleRecipientNoInputs => "SingleRecipientNoInputs", - Error::NoRecipients => "NoRecipients", - Error::NoUtxosSelected => "NoUtxosSelected", - Error::OutputBelowDustLimit(_) => "OutputBelowDustLimit", - Error::InsufficientFunds { .. } => "InsufficientFunds", - Error::BnBTotalTriesExceeded => "BnBTotalTriesExceeded", - Error::BnBNoExactMatch => "BnBNoExactMatch", - Error::UnknownUtxo => "UnknownUtxo", - Error::TransactionNotFound => "TransactionNotFound", - Error::TransactionConfirmed => "TransactionConfirmed", - Error::IrreplaceableTransaction => "IrreplaceableTransaction", - Error::FeeRateTooLow { .. } => "FeeRateTooLow", - Error::FeeTooLow { .. } => "FeeTooLow", - Error::MissingKeyOrigin(_) => "MissingKeyOrigin", - Error::Key(_) => "Key", - Error::ChecksumMismatch => "ChecksumMismatch", - Error::SpendingPolicyRequired(_) => "SpendingPolicyRequired", - Error::InvalidPolicyPathError(_) => "InvalidPolicyPathError", - Error::Signer(_) => "Signer", - Error::InvalidProgressValue(_) => "InvalidProgressValue", - Error::ProgressUpdateError => "ProgressUpdateError", - Error::InvalidOutpoint(_) => "InvalidOutpoint", - Error::Descriptor(_) => "Descriptor", - Error::AddressValidator(_) => "AddressValidator", - Error::Encode(_) => "Encode", - Error::Miniscript(_) => "Miniscript", - Error::Bip32(_) => "Bip32", - Error::Secp256k1(_) => "Secp256k1", - Error::Json(_) => "Json", - Error::Hex(_) => "Hex", - Error::Psbt(_) => "Psbt", - Error::Electrum(_) => "Electrum", - // Error::Esplora(_) => "Esplora", - // Error::CompactFilters(_) => "CompactFilters", - Error::Sled(_) => "Sled", - } - .to_string() +#[derive_ReprC] +#[repr(u16)] +#[derive(Debug)] +pub enum FfiError { + None, + InvalidU32Bytes, + Generic, + ScriptDoesntHaveAddressForm, + SingleRecipientMultipleOutputs, + SingleRecipientNoInputs, + NoRecipients, + NoUtxosSelected, + OutputBelowDustLimit, + InsufficientFunds, + BnBTotalTriesExceeded, + BnBNoExactMatch, + UnknownUtxo, + TransactionNotFound, + TransactionConfirmed, + IrreplaceableTransaction, + FeeRateTooLow, + FeeTooLow, + MissingKeyOrigin, + Key, + ChecksumMismatch, + SpendingPolicyRequired, + InvalidPolicyPathError, + Signer, + InvalidProgressValue, + ProgressUpdateError, + InvalidOutpoint, + Descriptor, + AddressValidator, + Encode, + Miniscript, + Bip32, + Secp256k1, + Json, + Hex, + Psbt, + Electrum, + // Esplora, + // CompactFilters, + Sled, +} + +impl From<&bdk::Error> for FfiError { + fn from(error: &bdk::Error) -> Self { + match error { + Error::InvalidU32Bytes(_) => FfiError::InvalidU32Bytes, + Error::Generic(_) => FfiError::Generic, + Error::ScriptDoesntHaveAddressForm => FfiError::ScriptDoesntHaveAddressForm, + Error::SingleRecipientMultipleOutputs => FfiError::SingleRecipientMultipleOutputs, + Error::SingleRecipientNoInputs => FfiError::SingleRecipientNoInputs, + Error::NoRecipients => FfiError::NoRecipients, + Error::NoUtxosSelected => FfiError::NoUtxosSelected, + Error::OutputBelowDustLimit(_) => FfiError::OutputBelowDustLimit, + Error::InsufficientFunds { .. } => FfiError::InsufficientFunds, + Error::BnBTotalTriesExceeded => FfiError::BnBTotalTriesExceeded, + Error::BnBNoExactMatch => FfiError::BnBNoExactMatch, + Error::UnknownUtxo => FfiError::UnknownUtxo, + Error::TransactionNotFound => FfiError::TransactionNotFound, + Error::TransactionConfirmed => FfiError::TransactionConfirmed, + Error::IrreplaceableTransaction => FfiError::IrreplaceableTransaction, + Error::FeeRateTooLow { .. } => FfiError::FeeRateTooLow, + Error::FeeTooLow { .. } => FfiError::FeeTooLow, + Error::MissingKeyOrigin(_) => FfiError::MissingKeyOrigin, + Error::Key(_) => FfiError::Key, + Error::ChecksumMismatch => FfiError::ChecksumMismatch, + Error::SpendingPolicyRequired(_) => FfiError::SpendingPolicyRequired, + Error::InvalidPolicyPathError(_) => FfiError::InvalidPolicyPathError, + Error::Signer(_) => FfiError::Signer, + Error::InvalidProgressValue(_) => FfiError::InvalidProgressValue, + Error::ProgressUpdateError => FfiError::ProgressUpdateError, + Error::InvalidOutpoint(_) => FfiError::InvalidOutpoint, + Error::Descriptor(_) => FfiError::Descriptor, + Error::AddressValidator(_) => FfiError::AddressValidator, + Error::Encode(_) => FfiError::Encode, + Error::Miniscript(_) => FfiError::Miniscript, + Error::Bip32(_) => FfiError::Bip32, + Error::Secp256k1(_) => FfiError::Secp256k1, + Error::Json(_) => FfiError::Json, + Error::Hex(_) => FfiError::Hex, + Error::Psbt(_) => FfiError::Psbt, + Error::Electrum(_) => FfiError::Electrum, + // Error::Esplora(_) => JniError::Esplora, + // Error::CompactFilters(_) => JniError::CompactFilters, + Error::Sled(_) => FfiError::Sled, + } + } } diff --git a/src/types.rs b/src/types.rs index c4dd5ac..5d856d6 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,4 @@ +use crate::error::FfiError; use ::safer_ffi::prelude::*; use safer_ffi::char_p::char_p_boxed; @@ -6,14 +7,14 @@ use safer_ffi::char_p::char_p_boxed; #[derive(Debug)] pub struct FfiResult { pub ok: T, - pub err: char_p_boxed, + pub err: FfiError, } #[derive_ReprC] #[repr(C)] #[derive(Debug)] pub struct FfiResultVoid { - pub err: char_p_boxed, + pub err: FfiError, } #[ffi_export] diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index bf12321..017a0ed 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -12,7 +12,7 @@ use safer_ffi::char_p::{char_p_boxed, char_p_ref}; use blockchain::BlockchainConfig; use database::DatabaseConfig; -use crate::error::get_name; +use crate::error::FfiError; use crate::types::{FfiResult, FfiResultVoid}; use std::ffi::CString; @@ -43,11 +43,11 @@ fn new_wallet_result( match wallet_result { Ok(w) => FfiResult { ok: Some(Box::new(OpaqueWallet { raw: w })), - err: char_p_boxed::from(CString::default()), + err: FfiError::None, }, Err(e) => FfiResult { ok: None, - err: char_p_boxed::try_from(get_name(&e)).unwrap(), + err: FfiError::from(&e), }, } } @@ -81,10 +81,10 @@ fn sync_wallet(opaque_wallet: &OpaqueWallet) -> FfiResultVoid { let int_result = opaque_wallet.raw.sync(log_progress(), Some(100)); match int_result { Ok(_v) => FfiResultVoid { - err: char_p_boxed::from(CString::default()), + err: FfiError::None, }, Err(e) => FfiResultVoid { - err: char_p_boxed::try_from(get_name(&e)).unwrap(), + err: FfiError::from(&e), }, } } @@ -96,11 +96,11 @@ fn new_address(opaque_wallet: &OpaqueWallet) -> FfiResult { match string_result { Ok(a) => FfiResult { ok: char_p_boxed::try_from(a).unwrap(), - err: char_p_boxed::from(CString::default()), + err: FfiError::None, }, Err(e) => FfiResult { ok: char_p_boxed::from(CString::default()), - err: char_p_boxed::try_from(get_name(&e)).unwrap(), + err: FfiError::from(&e), }, } } @@ -115,11 +115,11 @@ fn list_unspent(opaque_wallet: &OpaqueWallet) -> FfiResult = v.iter().map(|lu| LocalUtxo::from(lu)).collect(); repr_c::Vec::from(ve) }, - err: char_p_boxed::from(CString::default()), + err: FfiError::None, }, Err(e) => FfiResult { ok: repr_c::Vec::EMPTY, - err: char_p_boxed::try_from(get_name(&e)).unwrap(), + err: FfiError::from(&e), }, } } From cd813a14b1ce5a1e2e83a940242762bfba10a78a Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 4 Jul 2021 15:54:23 -0700 Subject: [PATCH 029/272] Add Wallet.balance() --- .../kotlin/org/bitcoindevkit/bdk/LibJna.kt | 33 ++++++- .../bitcoindevkit/bdk/types/UInt64Result.kt | 31 ++++++ .../bdk/wallet/VecLocalUtxoResult.kt | 2 +- .../org/bitcoindevkit/bdk/wallet/Wallet.kt | 6 ++ .../kotlin/org/bitcoindevkit/bdk/LibTest.kt | 9 ++ cc/bdk_ffi.h | 98 +++++++++++-------- cc/bdk_ffi_test.c | 19 ++-- src/types.rs | 5 + src/wallet/mod.rs | 20 +++- 9 files changed, 167 insertions(+), 56 deletions(-) create mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt index ffc36db..98b5a19 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt @@ -232,10 +232,35 @@ interface LibJna : Library { override fun getFieldOrder() = listOf("ok", "err") } - // void free_unspent_result ( + // void free_veclocalutxo_result ( // FfiResult_Vec_LocalUtxo_t unspent_result); - fun free_unspent_result(unspent_result: FfiResultVec_LocalUtxo_t.ByValue) + fun free_veclocalutxo_result(unspent_result: FfiResultVec_LocalUtxo_t.ByValue) + // typedef struct { + // + // uint64_t ok; + // + // FfiError_t err; + // + // } FfiResult_uint64_t; + open class FfiResult_uint64_t : Structure() { + + class ByValue : FfiResult_uint64_t(), Structure.ByValue + class ByReference : FfiResult_uint64_t(), Structure.ByReference + + @JvmField + var ok: Long = Long.MIN_VALUE + + @JvmField + var err: Short = 0 + + override fun getFieldOrder() = listOf("ok", "err") + } + + // void free_uint64_result ( + // FfiResult_uint64_t void_result); + fun free_uint64_result(unspent_result: FfiResult_uint64_t.ByValue) + // FfiResultVoid_t sync_wallet ( // OpaqueWallet_t const * opaque_wallet); fun sync_wallet(opaque_wallet: OpaqueWallet_t): FfiResultVoid_t.ByValue @@ -248,6 +273,10 @@ interface LibJna : Library { // OpaqueWallet_t const * opaque_wallet); fun list_unspent(opaque_wallet: OpaqueWallet_t): FfiResultVec_LocalUtxo_t.ByValue + // FfiResult_uint64_t balance ( + // OpaqueWallet_t const * opaque_wallet); + fun balance(opaque_wallet: OpaqueWallet_t): FfiResult_uint64_t.ByValue + // DatabaseConfig_t * new_memory_config (void); fun new_memory_config(): DatabaseConfig_t diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt new file mode 100644 index 0000000..43a7eb7 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt @@ -0,0 +1,31 @@ +package org.bitcoindevkit.bdk.types + +import org.bitcoindevkit.bdk.FfiException +import org.bitcoindevkit.bdk.LibBase +import org.bitcoindevkit.bdk.LibJna +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class UInt64Result constructor(private val ffiResultUint64T: LibJna.FfiResult_uint64_t.ByValue) : + LibBase() { + + private val log: Logger = LoggerFactory.getLogger(UInt64Result::class.java) + + fun value(): Long { + val err = ffiResultUint64T.err + val ok = ffiResultUint64T.ok + when { + err > 0 -> { + throw FfiException(err) + } + else -> { + return ok + } + } + } + + protected fun finalize() { + libJna.free_uint64_result(ffiResultUint64T) + log.debug("$ffiResultUint64T freed") + } +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt index c569c49..4f48d45 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt @@ -26,7 +26,7 @@ class VecLocalUtxoResult(private val ffiResultVecLocalUtxoT: LibJna.FfiResultVec } protected fun finalize() { - libJna.free_unspent_result(ffiResultVecLocalUtxoT) + libJna.free_veclocalutxo_result(ffiResultVecLocalUtxoT) log.debug("$ffiResultVecLocalUtxoT freed") } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt index c8d9e4d..940dfda 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt @@ -5,6 +5,7 @@ import org.bitcoindevkit.bdk.DatabaseConfig import org.bitcoindevkit.bdk.LibBase import org.bitcoindevkit.bdk.LibJna import org.bitcoindevkit.bdk.types.StringResult +import org.bitcoindevkit.bdk.types.UInt64Result import org.bitcoindevkit.bdk.types.VoidResult import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -42,4 +43,9 @@ class Wallet constructor( val vecLocalUtxoResult = VecLocalUtxoResult(libJna.list_unspent(wallet)) return vecLocalUtxoResult.value() } + + fun balance(): Long { + val longResult = UInt64Result(libJna.balance(wallet)) + return longResult.value() + } } \ No newline at end of file diff --git a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt index 79d99d0..2ecfa69 100644 --- a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt @@ -96,4 +96,13 @@ abstract class LibTest : LibBase() { assertTrue(it.keychain!! >= 0) } } + + @Test + fun walletBalance() { + val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) + wallet.sync() + val balance = wallet.balance() + //log.debug("balance from kotlin: $balance") + assertTrue(balance > 0) + } } diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h index 3832f3e..89f0ea2 100644 --- a/cc/bdk_ffi.h +++ b/cc/bdk_ffi.h @@ -14,6 +14,12 @@ extern "C" { #endif +typedef struct BlockchainConfig BlockchainConfig_t; + +typedef struct DatabaseConfig DatabaseConfig_t; + +typedef struct OpaqueWallet OpaqueWallet_t; + #include #include @@ -107,47 +113,6 @@ FfiError_t #endif ; -typedef struct { - - char * ok; - - FfiError_t err; - -} FfiResult_char_ptr_t; - -void free_string_result ( - FfiResult_char_ptr_t string_result); - -typedef struct { - - FfiError_t err; - -} FfiResultVoid_t; - -void free_void_result ( - FfiResultVoid_t void_result); - -/** \brief - * Free a Rust-allocated string - */ -void free_string ( - char * string); - -typedef struct BlockchainConfig BlockchainConfig_t; - -BlockchainConfig_t * new_electrum_config ( - char const * url, - char const * socks5, - int16_t retry, - int16_t timeout); - -void free_blockchain_config ( - BlockchainConfig_t * blockchain_config); - -typedef struct DatabaseConfig DatabaseConfig_t; - -typedef struct OpaqueWallet OpaqueWallet_t; - typedef struct { OpaqueWallet_t * ok; @@ -165,9 +130,23 @@ FfiResult_OpaqueWallet_ptr_t new_wallet_result ( void free_wallet_result ( FfiResult_OpaqueWallet_ptr_t wallet_result); +typedef struct { + + FfiError_t err; + +} FfiResultVoid_t; + FfiResultVoid_t sync_wallet ( OpaqueWallet_t const * opaque_wallet); +typedef struct { + + char * ok; + + FfiError_t err; + +} FfiResult_char_ptr_t; + FfiResult_char_ptr_t new_address ( OpaqueWallet_t const * opaque_wallet); @@ -221,9 +200,44 @@ typedef struct { FfiResult_Vec_LocalUtxo_t list_unspent ( OpaqueWallet_t const * opaque_wallet); -void free_unspent_result ( +void free_veclocalutxo_result ( FfiResult_Vec_LocalUtxo_t unspent_result); +typedef struct { + + uint64_t ok; + + FfiError_t err; + +} FfiResult_uint64_t; + +FfiResult_uint64_t balance ( + OpaqueWallet_t const * opaque_wallet); + +BlockchainConfig_t * new_electrum_config ( + char const * url, + char const * socks5, + int16_t retry, + int16_t timeout); + +void free_blockchain_config ( + BlockchainConfig_t * blockchain_config); + +void free_string_result ( + FfiResult_char_ptr_t string_result); + +void free_void_result ( + FfiResultVoid_t void_result); + +void free_uint64_result ( + FfiResult_uint64_t void_result); + +/** \brief + * Free a Rust-allocated string + */ +void free_string ( + char * string); + DatabaseConfig_t * new_memory_config (void); DatabaseConfig_t * new_sled_config ( diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index fb6674a..82d9417 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -114,12 +114,12 @@ int main (int argc, char const * const argv[]) assert(unspent_ptr[i].keychain >= 0); } - free_unspent_result(unspent_result); + free_veclocalutxo_result(unspent_result); free_wallet_result(wallet_result); } // test balance - /*{ + { char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; @@ -128,7 +128,7 @@ int main (int argc, char const * const argv[]) // new wallet FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); - assert(wallet_result.err == NULL); + assert(wallet_result.err == FFI_ERROR_NONE); assert(wallet_result.ok != NULL); free_blockchain_config(bc_config); @@ -138,19 +138,20 @@ int main (int argc, char const * const argv[]) // sync wallet FfiResultVoid_t sync_result = sync_wallet(wallet); - assert(sync_result.err == NULL); + assert(sync_result.err == FFI_ERROR_NONE); free_void_result(sync_result); // get balance - FfiResultT_uint64_t balance_result = balance(wallet); - assert(balance_result.err == NULL); - printf("balance = %lu\n", balance_result.ok); + FfiResult_uint64_t balance_result = balance(wallet); + //printf("balance.err = %d\n", (balance_result.err)); + assert(balance_result.err == FFI_ERROR_NONE); + //printf("balance.ok = %ld\n", balance_result.ok); assert(balance_result.ok > 0); // free balance and wallet results - free_u64_result(balance_result); + free_uint64_result(balance_result); free_wallet_result(wallet_result); - }*/ + } return EXIT_SUCCESS; } diff --git a/src/types.rs b/src/types.rs index 5d856d6..241cb42 100644 --- a/src/types.rs +++ b/src/types.rs @@ -27,6 +27,11 @@ fn free_void_result(void_result: FfiResultVoid) { drop(void_result) } +#[ffi_export] +fn free_uint64_result(void_result: FfiResult) { + drop(void_result) +} + // TODO do we need this? remove? /// Free a Rust-allocated string #[ffi_export] diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 017a0ed..c395b23 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1,4 +1,5 @@ use std::convert::TryFrom; +use std::ffi::CString; use ::safer_ffi::prelude::*; use bdk::bitcoin::network::constants::Network::Testnet; @@ -14,7 +15,6 @@ use database::DatabaseConfig; use crate::error::FfiError; use crate::types::{FfiResult, FfiResultVoid}; -use std::ffi::CString; mod blockchain; mod database; @@ -125,10 +125,26 @@ fn list_unspent(opaque_wallet: &OpaqueWallet) -> FfiResult>) { +fn free_veclocalutxo_result(unspent_result: FfiResult>) { drop(unspent_result) } +#[ffi_export] +fn balance(opaque_wallet: &OpaqueWallet) -> FfiResult { + let balance_result = opaque_wallet.raw.get_balance(); + + match balance_result { + Ok(b) => FfiResult { + ok: b, + err: FfiError::None, + }, + Err(e) => FfiResult { + ok: u64::MIN, + err: FfiError::from(&e), + }, + } +} + // Non-opaque returned values #[derive_ReprC] From b437b786689da52c781d046e3024561e43895f04 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 4 Jul 2021 22:10:16 -0700 Subject: [PATCH 030/272] Add Wallet.listTransactions() --- .../kotlin/org/bitcoindevkit/bdk/LibJna.kt | 115 ++++++++++++++++-- .../bitcoindevkit/bdk/types/StringResult.kt | 4 +- .../bitcoindevkit/bdk/types/UInt64Result.kt | 4 +- .../org/bitcoindevkit/bdk/types/VoidResult.kt | 2 +- .../bdk/wallet/VecLocalUtxoResult.kt | 4 +- .../bdk/wallet/VecTxDetailsResult.kt | 32 +++++ .../org/bitcoindevkit/bdk/wallet/Wallet.kt | 5 + .../bitcoindevkit/bdk/wallet/WalletResult.kt | 4 +- .../kotlin/org/bitcoindevkit/bdk/LibTest.kt | 22 ++++ cc/bdk_ffi.h | 43 +++++++ cc/bdk_ffi_test.c | 47 +++++++ src/wallet/mod.rs | 77 ++++-------- src/wallet/transaction.rs | 99 +++++++++++++++ 13 files changed, 384 insertions(+), 74 deletions(-) create mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt create mode 100644 src/wallet/transaction.rs diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt index 98b5a19..3fd4cbe 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt @@ -16,10 +16,10 @@ interface LibJna : Library { class ByReference : FfiResult_char_ptr_t(), Structure.ByReference @JvmField - var ok: String = "" + var ok: String? = null @JvmField - var err: Short = 0 + var err: Short? = null override fun getFieldOrder() = listOf("ok", "err") } @@ -38,7 +38,7 @@ interface LibJna : Library { class ByReference : FfiResultVoid_t(), Structure.ByReference @JvmField - var err: Short = 0 + var err: Short? = null override fun getFieldOrder() = listOf("err") } @@ -97,10 +97,10 @@ interface LibJna : Library { class ByReference : FfiResult_OpaqueWallet_ptr_t(), Structure.ByReference @JvmField - var ok: OpaqueWallet_t = OpaqueWallet_t() + var ok: OpaqueWallet_t? = null @JvmField - var err: Short = 0 + var err: Short? = null override fun getFieldOrder() = listOf("ok", "err") } @@ -224,10 +224,10 @@ interface LibJna : Library { class ByReference : FfiResultVec_LocalUtxo_t(), Structure.ByReference @JvmField - var ok: Vec_LocalUtxo_t = Vec_LocalUtxo_t() + var ok: Vec_LocalUtxo_t? = null @JvmField - var err: Short = 0 + var err: Short? = null override fun getFieldOrder() = listOf("ok", "err") } @@ -249,10 +249,10 @@ interface LibJna : Library { class ByReference : FfiResult_uint64_t(), Structure.ByReference @JvmField - var ok: Long = Long.MIN_VALUE + var ok: Long? = null @JvmField - var err: Short = 0 + var err: Short? = null override fun getFieldOrder() = listOf("ok", "err") } @@ -288,4 +288,101 @@ interface LibJna : Library { // void free_database_config ( // DatabaseConfig_t * database_config); fun free_database_config(database_config: DatabaseConfig_t) + + // typedef struct { + // + // char * txid; + // + // uint64_t timestamp; + // + // uint64_t received; + // + // uint64_t sent; + // + // uint64_t fees; + // + // int32_t height; + // + // } TransactionDetails_t; + open class TransactionDetails_t : Structure() { + + class ByValue : TransactionDetails_t(), Structure.ByValue + class ByReference : TransactionDetails_t(), Structure.ByReference + + @JvmField + var txid: String? = null + + @JvmField + var timestamp: Long? = null + + @JvmField + var received: Long? = null + + @JvmField + var sent: Long? = null + + @JvmField + var fees: Long? = null + + @JvmField + var height: Int? = null + + override fun getFieldOrder() = listOf("txid", "timestamp", "received", "sent", "fees", "height") + } + + // typedef struct { + // + // TransactionDetails_t * ptr; + // + // size_t len; + // + // size_t cap; + // + // } Vec_TransactionDetails_t; + open class Vec_TransactionDetails_t : Structure() { + + class ByReference : Vec_TransactionDetails_t(), Structure.ByReference + class ByValue : Vec_TransactionDetails_t(), Structure.ByValue + + @JvmField + var ptr: TransactionDetails_t.ByReference? = null + + @JvmField + var len: NativeLong? = null + + @JvmField + var cap: NativeLong? = null + + override fun getFieldOrder() = listOf("ptr", "len", "cap") + } + + // typedef struct { + // + // Vec_TransactionDetails_t ok; + // + // FfiError_t err; + // + // } FfiResult_Vec_TransactionDetails_t; + open class FfiResult_Vec_TransactionDetails_t : Structure() { + + class ByValue : FfiResult_Vec_TransactionDetails_t(), Structure.ByValue + class ByReference : FfiResult_Vec_TransactionDetails_t(), Structure.ByReference + + @JvmField + var ok: Vec_TransactionDetails_t? = null + + @JvmField + var err: Short? = null + + override fun getFieldOrder() = listOf("ok", "err") + } + + // FfiResult_Vec_TransactionDetails_t list_transactions ( + // OpaqueWallet_t const * opaque_wallet); + fun list_transactions(opaque_wallet: OpaqueWallet_t): FfiResult_Vec_TransactionDetails_t.ByValue + + + // void free_vectxdetails_result ( + // FfiResult_Vec_TransactionDetails_t txdetails_result); + fun free_vectxdetails_result(txdetails_result: FfiResult_Vec_TransactionDetails_t.ByValue) } diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt index 37d86a7..2d7ac46 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt @@ -12,8 +12,8 @@ class StringResult constructor(private val ffiResultCharPtrT: LibJna.FfiResult_c private val log: Logger = LoggerFactory.getLogger(StringResult::class.java) fun value(): String { - val err = ffiResultCharPtrT.err - val ok = ffiResultCharPtrT.ok + val err = ffiResultCharPtrT.err!! + val ok = ffiResultCharPtrT.ok!! when { err > 0 -> { throw FfiException(err) diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt index 43a7eb7..1b4e6f8 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt @@ -12,8 +12,8 @@ class UInt64Result constructor(private val ffiResultUint64T: LibJna.FfiResult_ui private val log: Logger = LoggerFactory.getLogger(UInt64Result::class.java) fun value(): Long { - val err = ffiResultUint64T.err - val ok = ffiResultUint64T.ok + val err = ffiResultUint64T.err!! + val ok = ffiResultUint64T.ok!! when { err > 0 -> { throw FfiException(err) diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt index 6b9b685..c50b328 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt @@ -12,7 +12,7 @@ class VoidResult constructor(private val ffiResultVoidT: LibJna.FfiResultVoid_t. private val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) fun value(): Unit { - val err = ffiResultVoidT.err + val err = ffiResultVoidT.err!! when { err > 0 -> { diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt index 4f48d45..8a005f3 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt @@ -12,8 +12,8 @@ class VecLocalUtxoResult(private val ffiResultVecLocalUtxoT: LibJna.FfiResultVec private val log: Logger = LoggerFactory.getLogger(VecLocalUtxoResult::class.java) fun value(): Array { - val err = ffiResultVecLocalUtxoT.err - val ok = ffiResultVecLocalUtxoT.ok + val err = ffiResultVecLocalUtxoT.err!! + val ok = ffiResultVecLocalUtxoT.ok!! when { err > 0 -> { throw FfiException(err) diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt new file mode 100644 index 0000000..abee9a2 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt @@ -0,0 +1,32 @@ +package org.bitcoindevkit.bdk.wallet + +import org.bitcoindevkit.bdk.FfiException +import org.bitcoindevkit.bdk.LibBase +import org.bitcoindevkit.bdk.LibJna +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class VecTxDetailsResult(private val ffiResultVecTransactionDetailsT: LibJna.FfiResult_Vec_TransactionDetails_t.ByValue) : + LibBase() { + + private val log: Logger = LoggerFactory.getLogger(VecTxDetailsResult::class.java) + + fun value(): Array { + val err = ffiResultVecTransactionDetailsT.err!! + val ok = ffiResultVecTransactionDetailsT.ok!! + when { + err > 0 -> { + throw FfiException(err) + } + else -> { + val first = ok.ptr!! + return first.toArray(ok.len!!.toInt()) as Array + } + } + } + + protected fun finalize() { + libJna.free_vectxdetails_result(ffiResultVecTransactionDetailsT) + log.debug("$ffiResultVecTransactionDetailsT freed") + } +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt index 940dfda..9cd3346 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt @@ -48,4 +48,9 @@ class Wallet constructor( val longResult = UInt64Result(libJna.balance(wallet)) return longResult.value() } + + fun listTransactionDetails(): Array { + val vecTxDetailsResult = VecTxDetailsResult(libJna.list_transactions((wallet))) + return vecTxDetailsResult.value() + } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt index 2d43b0c..c574f27 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt @@ -12,14 +12,14 @@ class WalletResult constructor(private val ffiResultOpaqueWalletPtrT: LibJna.Ffi private val log: Logger = LoggerFactory.getLogger(WalletResult::class.java) fun value(): LibJna.OpaqueWallet_t { - val err = ffiResultOpaqueWalletPtrT.err + val err = ffiResultOpaqueWalletPtrT.err!! val ok = ffiResultOpaqueWalletPtrT.ok when { err > 0 -> { throw FfiException(err) } else -> { - return ok + return ok!! } } } diff --git a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt index 2ecfa69..739e348 100644 --- a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt @@ -105,4 +105,26 @@ abstract class LibTest : LibBase() { //log.debug("balance from kotlin: $balance") assertTrue(balance > 0) } + + @Test + fun walletTxDetails() { + val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) + wallet.sync() + val txDetails = wallet.listTransactionDetails() + assertTrue(txDetails.isNotEmpty()) + + txDetails.iterator().forEach { + //log.debug("txDetails.txid: ${it.txid}") + assertNotNull(it.txid) + //log.debug("txDetails.timestamp: ${it.timestamp}") + assertTrue(it.timestamp!! > 0) + //log.debug("txDetails.received: ${it.received}") + //log.debug("txDetails.sent: ${it.sent}") + assertTrue(it.received!! > 0 || it.sent!! > 0) + //log.debug("txDetails.fees: ${it.fees}") + assertTrue(it.fees!! > 0) + //log.debug("txDetails.fees: ${it.height}") + assertTrue(it.height!! >= -1) + } + } } diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h index 89f0ea2..850e75a 100644 --- a/cc/bdk_ffi.h +++ b/cc/bdk_ffi.h @@ -214,6 +214,49 @@ typedef struct { FfiResult_uint64_t balance ( OpaqueWallet_t const * opaque_wallet); +typedef struct { + + char * txid; + + uint64_t timestamp; + + uint64_t received; + + uint64_t sent; + + uint64_t fees; + + int32_t height; + +} TransactionDetails_t; + +/** \brief + * Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout + */ +typedef struct { + + TransactionDetails_t * ptr; + + size_t len; + + size_t cap; + +} Vec_TransactionDetails_t; + +typedef struct { + + Vec_TransactionDetails_t ok; + + FfiError_t err; + +} FfiResult_Vec_TransactionDetails_t; + +FfiResult_Vec_TransactionDetails_t list_transactions ( + OpaqueWallet_t const * opaque_wallet); + +void free_vectxdetails_result ( + FfiResult_Vec_TransactionDetails_t txdetails_result); + BlockchainConfig_t * new_electrum_config ( char const * url, char const * socks5, diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index 82d9417..5f95924 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -152,6 +152,53 @@ int main (int argc, char const * const argv[]) free_uint64_result(balance_result); free_wallet_result(wallet_result); } + + // test get transaction details + { + char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; + char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; + + BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + DatabaseConfig_t *db_config = new_memory_config(); + + // new wallet + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); + assert(wallet_result.err == FFI_ERROR_NONE); + assert(wallet_result.ok != NULL); + + free_blockchain_config(bc_config); + free_database_config(db_config); + + OpaqueWallet_t *wallet = wallet_result.ok; + + // sync wallet + FfiResultVoid_t sync_result = sync_wallet(wallet); + assert(sync_result.err == FFI_ERROR_NONE); + free_void_result(sync_result); + + // list transactions + FfiResult_Vec_TransactionDetails_t txdetails_result = list_transactions(wallet); + assert(txdetails_result.ok.len > 0); + assert(txdetails_result.err == FFI_ERROR_NONE); + + TransactionDetails_t * txdetails_ptr = txdetails_result.ok.ptr; + for (int i = 0; i < txdetails_result.ok.len; i++) { + //printf("%d: txid: %s\n", i, txdetails_ptr[i].txid); + assert(txdetails_ptr[i].txid != NULL); + //printf("%d: timestamp: %ld\n", i, txdetails_ptr[i].timestamp); + assert(txdetails_ptr[i].timestamp > 0); + //printf("%d: received: %ld\n", i, txdetails_ptr[i].received); + //printf("%d: sent: %ld\n", i, txdetails_ptr[i].sent); + assert(txdetails_ptr[i].received > 0 || txdetails_ptr[i].sent > 0); + //printf("%d: fees: %ld\n", i, txdetails_ptr[i].fees); + assert(txdetails_ptr[i].fees > 0); + //printf("%d: height: %d\n", i, txdetails_ptr[i].height); + assert(txdetails_ptr[i].height >= -1); + } + + free_vectxdetails_result(txdetails_result); + free_wallet_result(wallet_result); + } return EXIT_SUCCESS; } diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index c395b23..90327eb 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -15,9 +15,11 @@ use database::DatabaseConfig; use crate::error::FfiError; use crate::types::{FfiResult, FfiResultVoid}; +use crate::wallet::transaction::{LocalUtxo, TransactionDetails}; mod blockchain; mod database; +mod transaction; // create a new wallet @@ -145,64 +147,27 @@ fn balance(opaque_wallet: &OpaqueWallet) -> FfiResult { } } -// Non-opaque returned values +#[ffi_export] +fn list_transactions(opaque_wallet: &OpaqueWallet) -> FfiResult> { + let transactions_result = opaque_wallet.raw.list_transactions(false); -#[derive_ReprC] -#[repr(C)] -#[derive(Debug, Clone)] -pub struct OutPoint { - /// The referenced transaction's txid, as hex string - pub txid: char_p_boxed, - /// The index of the referenced output in its transaction's vout - pub vout: u32, -} - -impl From<&bdk::bitcoin::OutPoint> for OutPoint { - fn from(op: &bdk::bitcoin::OutPoint) -> Self { - OutPoint { - txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(), - vout: op.vout, - } + match transactions_result { + Ok(v) => FfiResult { + ok: { + let ve: Vec = + v.iter().map(|t| TransactionDetails::from(t)).collect(); + repr_c::Vec::from(ve) + }, + err: FfiError::None, + }, + Err(e) => FfiResult { + ok: repr_c::Vec::EMPTY, + err: FfiError::from(&e), + }, } } -#[derive_ReprC] -#[repr(C)] -#[derive(Debug, Clone)] -pub struct TxOut { - /// The value of the output, in satoshis - pub value: u64, - /// The script which must satisfy for the output to be spent, as hex string - pub script_pubkey: char_p_boxed, -} - -impl From<&bdk::bitcoin::TxOut> for TxOut { - fn from(to: &bdk::bitcoin::TxOut) -> Self { - TxOut { - value: to.value, - script_pubkey: char_p_boxed::try_from(to.script_pubkey.to_string()).unwrap(), - } - } -} - -#[derive_ReprC] -#[repr(C)] -#[derive(Debug, Clone)] -pub struct LocalUtxo { - /// Reference to a transaction output - pub outpoint: OutPoint, - /// Transaction output - pub txout: TxOut, - /// Type of keychain, as short 0 for "external" or 1 for "internal" - pub keychain: u16, -} - -impl From<&bdk::LocalUtxo> for LocalUtxo { - fn from(lu: &bdk::LocalUtxo) -> Self { - LocalUtxo { - outpoint: OutPoint::from(&lu.outpoint), - txout: TxOut::from(&lu.txout), - keychain: lu.keychain as u16, - } - } +#[ffi_export] +fn free_vectxdetails_result(txdetails_result: FfiResult>) { + drop(txdetails_result) } diff --git a/src/wallet/transaction.rs b/src/wallet/transaction.rs new file mode 100644 index 0000000..378d4cf --- /dev/null +++ b/src/wallet/transaction.rs @@ -0,0 +1,99 @@ +use std::convert::TryFrom; + +use ::safer_ffi::prelude::*; +use safer_ffi::char_p::char_p_boxed; + +// Non-opaque returned values + +#[derive_ReprC] +#[repr(C)] +#[derive(Debug, Clone)] +pub struct TransactionDetails { + // TODO Optional transaction + // pub transaction: Option, + /// Transaction id + pub txid: char_p_boxed, + /// Timestamp + pub timestamp: u64, + /// Received value (sats) + pub received: u64, + /// Sent value (sats) + pub sent: u64, + /// Fee value (sats) + pub fees: u64, + /// Confirmed in block height, `None` means unconfirmed + pub height: i32, +} + +impl From<&bdk::TransactionDetails> for TransactionDetails { + fn from(op: &bdk::TransactionDetails) -> Self { + TransactionDetails { + txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(), + timestamp: op.timestamp, + received: op.received, + sent: op.sent, + fees: op.fees, + height: op.height.map(|h| h as i32).unwrap_or(-1), + } + } +} + +#[derive_ReprC] +#[repr(C)] +#[derive(Debug, Clone)] +pub struct OutPoint { + /// The referenced transaction's txid, as hex string + pub txid: char_p_boxed, + /// The index of the referenced output in its transaction's vout + pub vout: u32, +} + +impl From<&bdk::bitcoin::OutPoint> for OutPoint { + fn from(op: &bdk::bitcoin::OutPoint) -> Self { + OutPoint { + txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(), + vout: op.vout, + } + } +} + +#[derive_ReprC] +#[repr(C)] +#[derive(Debug, Clone)] +pub struct TxOut { + /// The value of the output, in satoshis + pub value: u64, + /// The script which must satisfy for the output to be spent, as hex string + pub script_pubkey: char_p_boxed, +} + +impl From<&bdk::bitcoin::TxOut> for TxOut { + fn from(to: &bdk::bitcoin::TxOut) -> Self { + TxOut { + value: to.value, + script_pubkey: char_p_boxed::try_from(to.script_pubkey.to_string()).unwrap(), + } + } +} + +#[derive_ReprC] +#[repr(C)] +#[derive(Debug, Clone)] +pub struct LocalUtxo { + /// Reference to a transaction output + pub outpoint: OutPoint, + /// Transaction output + pub txout: TxOut, + /// Type of keychain, as short 0 for "external" or 1 for "internal" + pub keychain: u16, +} + +impl From<&bdk::LocalUtxo> for LocalUtxo { + fn from(lu: &bdk::LocalUtxo) -> Self { + LocalUtxo { + outpoint: OutPoint::from(&lu.outpoint), + txout: TxOut::from(&lu.txout), + keychain: lu.keychain as u16, + } + } +} From 24e71b5a39b5c52b06fc6ea7690fc86644be6bea Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 5 Jul 2021 14:25:09 -0700 Subject: [PATCH 031/272] Update build.sh to install jvm darwin-x86-64 dylib --- bdk-kotlin/.gitignore | 3 ++- build.sh | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/bdk-kotlin/.gitignore b/bdk-kotlin/.gitignore index 138d427..6501994 100644 --- a/bdk-kotlin/.gitignore +++ b/bdk-kotlin/.gitignore @@ -3,4 +3,5 @@ .gradle local.properties build -*.so \ No newline at end of file +*.so +*.dylib \ No newline at end of file diff --git a/build.sh b/build.sh index 6b5a530..bf0fc42 100755 --- a/build.sh +++ b/build.sh @@ -11,8 +11,19 @@ export LD_LIBRARY_PATH=`pwd`/target/debug cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m # bdk-kotlin jar -mkdir -p bdk-kotlin/jvm/src/main/resources/linux-x86-64 -cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/src/main/resources/linux-x86-64 +OS=$(uname) +case $OS in + "Darwin") + echo "Darwin build system" + mkdir -p bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + cp target/debug/libbdk_ffi.dylib bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + ;; + "Linux") + echo "Linux build system" + mkdir -p bdk-kotlin/jvm/src/main/resources/linux-x86-64 + cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/src/main/resources/linux-x86-64 + ;; +esac (cd bdk-kotlin && gradle :jvm:build && gradle :jvm:publishToMavenLocal) From e5a74344f3f39a2107a9c75acb5a878345b285a9 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 7 Jul 2021 10:42:42 -0700 Subject: [PATCH 032/272] Remove local.properties and add to .gitignore --- .gitignore | 1 + local.properties | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 local.properties diff --git a/.gitignore b/.gitignore index fd662a9..d295440 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ Cargo.lock .gradle wallet_db bdk_ffi_test +local.properties \ No newline at end of file diff --git a/local.properties b/local.properties deleted file mode 100644 index 52889ad..0000000 --- a/local.properties +++ /dev/null @@ -1,8 +0,0 @@ -## This file must *NOT* be checked into Version Control Systems, -# as it contains information specific to your local configuration. -# -# Location of the SDK. This is only used by Gradle. -# For customization when using a Version Control System, please read the -# header note. -#Sun Jun 20 18:41:16 PDT 2021 -sdk.dir=/home/steve/Android/Sdk From c11e17b5e2db148c595e42b5919260be3eb935f4 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 25 Sep 2021 21:17:40 -0700 Subject: [PATCH 033/272] Upgrade bdk dependency to 0.11 --- .gitignore | 3 +- Cargo.toml | 2 +- README.md | 12 ++ bdk-kotlin/gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 +- bdk-kotlin/gradlew | 185 ++++++++++++++++++ bdk-kotlin/gradlew.bat | 89 +++++++++ .../kotlin/org/bitcoindevkit/bdk/LibJna.kt | 57 ++++-- .../bdk/wallet/BlockchainConfig.kt | 5 +- .../org/bitcoindevkit/bdk/wallet/Wallet.kt | 9 + .../kotlin/org/bitcoindevkit/bdk/LibTest.kt | 35 ++-- build.sh | 162 +++++++++------ cc/bdk_ffi.h | 35 +++- cc/bdk_ffi_test.c | 59 +++--- src/error.rs | 11 +- src/wallet/blockchain.rs | 2 + src/wallet/mod.rs | 10 +- src/wallet/transaction.rs | 43 +++- test.sh | 64 +++++- 19 files changed, 625 insertions(+), 163 deletions(-) create mode 100755 bdk-kotlin/gradlew create mode 100644 bdk-kotlin/gradlew.bat diff --git a/.gitignore b/.gitignore index d295440..17d844a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ Cargo.lock .gradle wallet_db bdk_ffi_test -local.properties \ No newline at end of file +local.properties +*.log \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index c8afbd2..cb9787c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -bdk = { version = "^0.7", features = ["all-keys"] } +bdk = { version = "^0.11", features = ["all-keys"] } safer-ffi = { version = "0.0.6", features = ["proc_macros"]} [features] diff --git a/README.md b/README.md index 6d3f227..2935e1c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ +Setup build environment +1. Add Android rust targets + +```sh +rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android +``` + +2. Set ANDROID_NDK_HOME + +```sh +export ANDROID_NDK_HOME=/home//Android/Sdk/ndk/ +``` Adding new structs and functions diff --git a/bdk-kotlin/gradle/wrapper/gradle-wrapper.jar b/bdk-kotlin/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 6656 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 Yte!%o?r4GTrVtxD61Ym!|5fq-1K$0e!T1w z1SC8j)_dObefzK9b=~*c&wBRW>;B{VGKiBofK!FMN5oJBE0V;;!kWUz!jc1W?5KdY zyZ3mCBHprpchz-9{ASiJJh&&h1|4rdw6wxD2+9= z#6#}Uq8&^1F3wgvGFoNDo?bIeEQXpcuAR0-+w$JWoK-@yUal1M&~W_O)r+Rx;{@hWH5n^oQWR36GMYBDDZyPK4L@WVjRrF+XlSzi4X4!_!U%Uujl6LHQ#|l(sUU%{ zefYd8jnVYP91K}Qn-OmmSLYFK1h~_}RPS~>+Xdz%dpvpJ{ll!IKX=JN99qowqslbO zV3DmqPZ}6>KB!9>jEObpi$u5oGPfO3O5!o3N2Mn`ozpje<}1I1H)m2rJDcB7AwXc6 z6j)tnPiql7#)r+b+p9?MVahp&=qJ^$oG+a^C*);FoJ!+V*^W+|2Olx5{*&$bXth)U zejc7mU6cBp?^Rj|dd{GL-0eHRTBi6_yJ&GLP5kIncv^z{?=0AVy^5{S8_n=rtua!J zFGY=A(yV^ZhB}1J_y(F`3QTu+zkHlw;1GiFeP&pw0N1k%NShHlO(4W+(!wy5phcg4 zA-|}(lE_1@@e6y`veg;v7m;q%(PFG&K3#}eRhJioXUU0jg_8{kn$;KVwf;zpL2X_( zC*_R#5*PaBaY73(x*oZ}oE#HPLJQRQ7brNK=v!lsu==lSG1(&q>F)`adBT~d*lMS| z%!%7(p~<7kWNmpZ5-N31*e=8`kih|g5lVrI%2wnLF-2D+G4k6@FrYsJ_80AJ}KMRi>) z-kIeHp{maorNWkF81v0FKgB==_6blyaF$5GaW)B!i4v*jNk6r)vU6?G$0pV8(Y+UK z5lgRVt%;N_gWp)^osv=h+^07UY6+$4^#t=M3>0i0`{`aEkFLL#a)93uXhYO+aKTtu zckg2T9S&GKNtZmdAS^8PzvDva-%-K&g9eqPXQ4$dM^inr@6Zl z{!Cq&C_+V;g*{>!0cZP}?ogDb$#ZS=n@NHE{>k@84lOkl&$Bt2NF)W%GClViJq14_ zQIfa^q+0aq){}CO8j%g%R9|;G0uJuND*HO$2i&U_uW_a5xJ33~(Vy?;%6_(2_Cuq1 zLhThN@xH7-BaNtkKTn^taQHrs$<<)euc6z(dhps>SM;^Wx=7;O&IfNVJq3wk4<1VS z-`*7W4DR_i^W4=dRh>AXi~J$K>`UqP>CKVVH&+T(ODhRJZO7DScU$F7D)di-%^8?O z6)Ux`zdrVOe1GNkPo0FgrrxSu1AGQkJe@pqu}8LkBDm+V!N_1l}`tjLW8${rgDLv3m@E*#zappt-Mm zSC<$o+6UO~w0C=(0$&*y**@nKe_Q{|eAuD!(0YL0_a{z%+sdfSyP={Nyd$re6Rzbp zvsgTY7~VflX0^Vf7qqomYZ_$ryrFVV2$sFyzw2r%Q8*uYDA+)iQdfKms_5(>!s#!( z!P5S(N0i9CKQKaqg(U%Gk#V3*?)lO6dLv`8KB~F<-%VhbtL8Rl>mEz+PN=qx&t*|= zQHV=qG)YKlPk4iCyWIUGjC?kpeA>hIBK*A?B0)rB=RqAal#D%1C9yVQwBcz${#Jb5 zR{TRmMrOrJsLc&6x9qDo@FJ^=do_Y?3oU0G^nV5_EU&+DS+VA7Tp{^TAF>yZbyM3c zf*1CqHY9T|aL_lyY7c)i!_MtGPA!sdy3|mrsKVj1mi&>dms@-ozSa}OZ?2I*tAndg z@S7er$t^d^-;!wLQbG60nWd@1pQVD7tw-G_B#OscoYyremiZ_hj8*sXqQdchuD^!R zpXGuSj5psk+jR>3rWu3^`17>j&*^9^rWbszP=Mf@5KIEj%b=z98v=Ymp%$FYt>%Ld zm8})EDbNOJu9n)gwhz_RS``#Ag)fr)3<*?(!9O~mTQWeh;8c;0@o=iBLQNqx3d_2#W7S9#FXzr6VXfs>4 z;QXw}-STvK9_-7H=uqgal2{GkbjVLN+=D5ddd)4^WvX;(NYA*X*(JxTdiUzqVJopd zQg#~psX4o<)cF>r=rxP`(Xsf<+HG-pf&7aFPL8z|-&B*P?Vmsu5d>Nlg^2$WRY!S@#`g2{81;(1w#o5HsvN}5pFZi});>|VK^kL{Zkx~wgn ztlZp;HW`H8(GdRfIwc~?#N6}o#h158ohI*GIsK%56I_9sf2k_K@4vD!l{(dX9E7PJ;w>$|Y;-VBJSO4@){07bo-89^LZ9g<<%;dOl zyIq{s8`8Ltp*GDwu(l_Z$6sA2nam$BM$Q~6TpZg)w2TtW?G5whV(lRwaf$6EU86is zBP9Rs&vS_~sk?Nn_b}^HkM8LiO@>J}=g(T4hLmvH@5Jj#2aHa~K)lD9VB0k>$V2BP zgh;(=y9Op(KQ=H5vj+%qs>?s4tYN~-Q|fyQePA)s?HrF~;l!+@t8VMzqUpqMLudFT z)=o~s!MM4XkgbetIsODwtQ=FF$IcIp&!pjh6Q6{tL+l*7GQ%8Wsg(tC#qU3oW$~n) zL=>XIxI}Hi7HS0F_mmi+(c%1HDuKiWm>|6Xa}nW7ei55ggru9)xjBvC#JcEIN*#cp zv*ACvr=HTC?dX9NNo9Yhulu_gX5Z~}QQ2&QZ&C77{(>Y3_ z6j5Z1Uc5FtPEpS_31HsgmSLHZijGb_p$WlRJ1p^_1!ZLP8kr6OtCEK7Qh267o$H>e zf<4cNGQRk{g5h$XfvTFQ@`qm@iju83-~}ebAYpZryARHVR$AEt3229U{y@Fp4 z-8FBBtGG&(hTyUdx5ZOfiz`c=<0F%+w|Fl=rWk{K7>70k04SN?RU(^mrKSeKDqA!K^Hsv8C?#ioj4@WUL zC*?{hTai6q0%_oBTqDHygp_Kl;({sAScYQIwMDM1U>{x0ww zve?_}E;DG?+|zsUrsph5X_G7l#Y~vqkq3@NNDabbw7|`eJBmn`Qrlr%?`va=mm$Mc{+FBbQbogAZ6{MuzT|P%QZZotd21eb1hfj|;GYAX&>bx#D5EB+=XMj2XJkpnyMUykaVo) zj3ZLqEl1&)Rturc8m@+uUuD^vaNaSxGwP4dq0-OSb~62lPv8E_K4usLvG{Qg zdR%z8dd2H!{JaT|X_bfm{##*W$YM;_J8Y8&Z)*ImOAf4+| zEyi)qK%Ld1bHuqD+}-WiCnjszDeC-%8g+8JRpG1bOc!xUGB?@?6f~FTrI%U#5R~YF z%t5(S2Q>?0`(XNHa8xKdTEZ~Z4SJOheit#ldfdg63}#W6j8kO;SjQD`vftxS+#x1B zYu|5szEvkyz|}|B3x|DNlyi$;+n+cW$Hu+?)=X1!sa%{H-^;oBO9XACZJ}wkQ!sTa zQ#J3h|HX{{&WwIG3h7d6aWktuJaO)ie6&=KJBoX@w(rBWfin`*a6OmCC5M0HzL(gv zY<*e4hmW>SWVhxk-`UGOAbD%Hk+uu<^7zJ_ytVXamfqCd0$g+W08>?QAB}Cv{b}eM z@X}ILg+uT%>-6`A25p@uhS3%;u>ccSq}8|H_^o&`nBT5S0y z;2H0I^(4MO*S+(4l$gULc4KSeKvidto5Nl0P|%9CqQ*ikY!w_GUlo}sb9HYB=L^oFpJ zfTQskXW!LFVnUo4(OHPDaZSf3zB|3{RGu1>ueE$(+dr?tT zp!SGlqDU8vu{5xLWSvj+j$arHglg54#Lx&TvuO3LIIU>hF9Uoj&=-b*Q?uYr`#V?xz?2 zhirZrv^eA{k%{hFh%9LYVXEYWd5#PuUd1QqaqB*J!CMXEM>fEB$@#1>mtB`Bfil}t zhhTIObqh5HRvT+4q_Do$Q*Jika?qV=Np-DtPkU z(KoXyWLfPwr@UY1)hBAvR3nCBZgd|CevTG?H~HqDF}dzy%2sd2`f{^CBbTk*^K~RO zN~O0+2EjAJlywF%SjgYz810l&G5AqzI<=Ber{912^PpSPRJl3dm8W@dKHL}7_@k3)Y!SXYkyxQy>Q4I2o zr`ev7fLF$1t96h|sH<-#*YzGD-b^3$_!#wsh(Yw;)b@udLz9mm`mFYh z1Zz24KIQJ(*_-E0(3&1InqG;U?wF)GYd>DFo(em`#|UaaYmkA9;GTX7b?0@C@QkTVpGD#mf$dQoRNV=n{^Zi_W*ps;3?^$s`0;ER7;==~OmQ~9 zS5P=FjxE5%|;xq6h4@!_h?@|aK&FYI2IT(OHXv2%1 zWEo-v!L7x^YT(xLVHlpJttcwaF@1Y;-S*q3CRa!g7xdzl|Jan>2#dI0`LKl!T1GMk zRKe4|bQO&ET}Z^Aiym*HII>cSxIzl|F~JEUGxz;+DB=8fxXhnBI4R12q6ews$lA`Jfi}r@A@-)6TOAUMNYFYJ zZ-Zd?lxFTyjN3mXnL!%#>Z%$0gJ4*9g;e;@zSmQ{eGGDaRRNM3s@6!;hYuVc=c+3B z=qzNNS~n^EsJU4aOGE|mdy={C^lPKEfPL-IJAsTpQsDgZ@~s+eHZYmp9yb=YW_4r?lqQaYZQ`nau){W`LY#P)>i zq^wHEuOYs#FlPZeMuT@Etb@~A6feCebq`miJE3w+gAL%bVF_s*5e*@)?xmKSo%I3? zLELHVdWia$}~s6 zr!^LfxSSB4Td&9iTXrzQpl5ZDo#SdmNr;23QsPHQ!x!UT9xtb!Ycz^JF8x)%cFOXK z^EXw%dRz_VD}7?RU^4{)1+xFO=z!EI8IUa3U*rag=1BpHX$Xi<__kSbS{y_xa*MJv z_`thq0Z^sPzjAk48ssDQj}!$N8Q$XC84(bU$t_Bm69Jf+C!h_}ep zwzpQj9sRA94<{x3{~z&ix-DwX;RAzka)4-#6ZHJqKh|SVuO|>Yrv+m30+!|sK<-|E z=)5E->#y<_1V|T1f%Af!ZYqXg}`O zI$qKOWdnclF`%_Z`WGOe{`A`l-#a?s=Q1a#@BOWmExH2;Wl`OB!B-%lq3nO{4=WO& z#k_x|N&(qzm*6S{G*|GCegF2N2ulC+(58z2DG~yUs}i8zvRf&$CJCaexJ6Xu!`qz( z)*v8*kAE#D0KCo*s{8^Rbg=`*E2MzeIt0|x55%n-gO&yX#$l=3W7-_~&(G8j1E(XB hw}tl`5K!1C(72%nnjQrp<7@!WCh47rWB+@R{{wClNUHz< diff --git a/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties b/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties index 382d831..0f80bbf 100644 --- a/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties +++ b/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Thu Jun 10 21:43:37 PDT 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/bdk-kotlin/gradlew b/bdk-kotlin/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/bdk-kotlin/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/bdk-kotlin/gradlew.bat b/bdk-kotlin/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/bdk-kotlin/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt index 3fd4cbe..19d2e43 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt @@ -61,12 +61,14 @@ interface LibJna : Library { // char const * url, // char const * socks5, // int16_t retry, - // int16_t timeout); + // int16_t timeout, + // size_t stop_gap); fun new_electrum_config( url: String, socks5: String?, retry: Short, - timeout: Short + timeout: Short, + stop_gap: Long, ): BlockchainConfig_t // void free_blockchain_config ( @@ -108,11 +110,13 @@ interface LibJna : Library { // FfiResult_OpaqueWallet_ptr_t new_wallet_result ( // char const * descriptor, // char const * change_descriptor, + // char const * network, // BlockchainConfig_t const * blockchain_config, // DatabaseConfig_t const * database_config); fun new_wallet_result( descriptor: String, changeDescriptor: String?, + network: String, blockchainConfig: BlockchainConfig_t, databaseConfig: DatabaseConfig_t, ): FfiResult_OpaqueWallet_ptr_t.ByValue @@ -288,21 +292,33 @@ interface LibJna : Library { // void free_database_config ( // DatabaseConfig_t * database_config); fun free_database_config(database_config: DatabaseConfig_t) - + // typedef struct { - // - // char * txid; - // + // uint32_t height; // uint64_t timestamp; - // + // } ConfirmationTime_t; + open class ConfirmationTime_t : Structure() { + + class ByValue : ConfirmationTime_t(), Structure.ByValue + class ByReference : ConfirmationTime_t(), Structure.ByReference + + @JvmField + var height: Int? = null + + @JvmField + var timestamp: Long? = null + + override fun getFieldOrder() = listOf("height", "timestamp") + } + + // typedef struct { + // char * txid; // uint64_t received; - // // uint64_t sent; - // - // uint64_t fees; - // - // int32_t height; - // + // int64_t fee; + // bool is_confirmed; + // ConfirmationTime_t confirmation_time; + // bool verified; // } TransactionDetails_t; open class TransactionDetails_t : Structure() { @@ -312,9 +328,6 @@ interface LibJna : Library { @JvmField var txid: String? = null - @JvmField - var timestamp: Long? = null - @JvmField var received: Long? = null @@ -322,12 +335,18 @@ interface LibJna : Library { var sent: Long? = null @JvmField - var fees: Long? = null + var fee: Long? = null + + @JvmField + var is_confirmed: Boolean? = null @JvmField - var height: Int? = null + var confirmation_time: ConfirmationTime_t? = null + + @JvmField + var verified: Boolean? = null - override fun getFieldOrder() = listOf("txid", "timestamp", "received", "sent", "fees", "height") + override fun getFieldOrder() = listOf("txid", "received", "sent", "fee", "is_confirmed", "confirmation_time", "verified") } // typedef struct { diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt index 592f299..5a7f5db 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt @@ -17,9 +17,10 @@ class ElectrumConfig( url: String, socks5: String?, retry: Short, - timeout: Short + timeout: Short, + stopGap: Long, ) : BlockchainConfig() { private val log: Logger = LoggerFactory.getLogger(ElectrumConfig::class.java) - override val blockchainConfigT = libJna.new_electrum_config(url, socks5, retry, timeout) + override val blockchainConfigT = libJna.new_electrum_config(url, socks5, retry, timeout, stopGap) } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt index 9cd3346..57db810 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt @@ -10,9 +10,17 @@ import org.bitcoindevkit.bdk.types.VoidResult import org.slf4j.Logger import org.slf4j.LoggerFactory +enum class Network { + Bitcoin, + Testnet, + Signet, + Regtest, +} + class Wallet constructor( descriptor: String, changeDescriptor: String?, + network: Network, blockchainConfig: BlockchainConfig, databaseConfig: DatabaseConfig, ) : LibBase() { @@ -23,6 +31,7 @@ class Wallet constructor( libJna.new_wallet_result( descriptor, changeDescriptor, + network.toString().lowercase(), blockchainConfig.blockchainConfigT, databaseConfig.databaseConfigT ) diff --git a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt index 739e348..24d9b20 100644 --- a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt @@ -1,5 +1,6 @@ package org.bitcoindevkit.bdk +import org.bitcoindevkit.bdk.wallet.Network import org.bitcoindevkit.bdk.wallet.Wallet import org.junit.Assert.* import org.junit.Test @@ -19,8 +20,9 @@ abstract class LibTest : LibBase() { "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" - - val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val network = Network.Testnet + + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30, 100) val databaseConfig = MemoryConfig() abstract fun getTestDataDir(): String @@ -32,7 +34,7 @@ abstract class LibTest : LibBase() { @Test fun walletResultError() { val jnaException = assertThrows(FfiException::class.java) { - Wallet("bad", "bad", blockchainConfig, databaseConfig) + Wallet("bad", "bad", network, blockchainConfig, databaseConfig) } assertEquals(jnaException.err, FfiError.Descriptor) } @@ -58,18 +60,18 @@ abstract class LibTest : LibBase() { @Test fun walletSync() { - val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) + val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30, 100) val testDataDir = getTestDataDir() // log.debug("testDataDir = $testDataDir") val databaseConfig = SledConfig(testDataDir, "steve-test") - val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) + val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig) wallet.sync() cleanupTestDataDir() } @Test fun walletNewAddress() { - val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) + val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig) val address = wallet.getAddress() assertNotNull(address) // log.debug("address created from kotlin: $address") @@ -78,7 +80,7 @@ abstract class LibTest : LibBase() { @Test fun walletUnspent() { - val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) + val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig) wallet.sync() val unspent = wallet.listUnspent() assertTrue(unspent.isNotEmpty()) @@ -99,7 +101,7 @@ abstract class LibTest : LibBase() { @Test fun walletBalance() { - val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) + val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig) wallet.sync() val balance = wallet.balance() //log.debug("balance from kotlin: $balance") @@ -108,7 +110,7 @@ abstract class LibTest : LibBase() { @Test fun walletTxDetails() { - val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) + val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig) wallet.sync() val txDetails = wallet.listTransactionDetails() assertTrue(txDetails.isNotEmpty()) @@ -116,15 +118,18 @@ abstract class LibTest : LibBase() { txDetails.iterator().forEach { //log.debug("txDetails.txid: ${it.txid}") assertNotNull(it.txid) - //log.debug("txDetails.timestamp: ${it.timestamp}") - assertTrue(it.timestamp!! > 0) //log.debug("txDetails.received: ${it.received}") //log.debug("txDetails.sent: ${it.sent}") assertTrue(it.received!! > 0 || it.sent!! > 0) - //log.debug("txDetails.fees: ${it.fees}") - assertTrue(it.fees!! > 0) - //log.debug("txDetails.fees: ${it.height}") - assertTrue(it.height!! >= -1) + //log.debug("txDetails.fee: ${it.fee}") + assertTrue(it.fee!! > 0) + //log.debug("txDetails.is_confirmed: ${it.is_confirmed}") + assertTrue(it.is_confirmed!!) + assertNotNull(it.confirmation_time!!) + //log.debug("txDetails.confirmation_time.timestamp: ${it.confirmation_time!!.timestamp}") + assertTrue(it.confirmation_time!!.timestamp!! > 0) + //log.debug("txDetails.confirmation_time.height: ${it.confirmation_time!!.height}") + assertTrue(it.confirmation_time!!.height!! > 0) } } } diff --git a/build.sh b/build.sh index bf0fc42..67ca036 100755 --- a/build.sh +++ b/build.sh @@ -1,64 +1,114 @@ #!/usr/bin/env bash -set -eo pipefail -o xtrace +set -eo pipefail -# rust -cargo fmt -cargo build -cargo test --features c-headers -- generate_headers +# functions -# cc -export LD_LIBRARY_PATH=`pwd`/target/debug -cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m +## help +help() +{ + # Display Help + echo "Build bdk-ffi and related libraries." + echo + echo "Syntax: build [-a|h|k]" + echo "options:" + echo "-a Android aar." + echo "-h Print this Help." + echo "-k JVM jar." + echo +} + +## rust +build_rust() { + echo "Build Rust library and C headers" + cargo fmt + cargo build + cargo test --features c-headers -- generate_headers +} + +## cc +build_cc() { + echo "Build C test library" + export LD_LIBRARY_PATH=`pwd`/target/debug + cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m +} + +## copy to bdk-kotlin +copy_lib_kotlin() { + echo -n "Copy " + case $OS in + "Darwin") + echo -n "darwin " + mkdir -p bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + cp target/debug/libbdk_ffi.dylib bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + ;; + "Linux") + echo -n "linux " + mkdir -p bdk-kotlin/jvm/src/main/resources/linux-x86-64 + cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/src/main/resources/linux-x86-64 + ;; + esac + echo "libs to kotlin sub-project" +} + +## bdk-kotlin jar +build_kotlin() { + (cd bdk-kotlin && ./gradlew :jvm:build && ./gradlew :jvm:publishToMavenLocal) +} + +## rust android +build_android() { + # If ANDROID_NDK_HOME is not set then set it to github actions default + [ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle + + # Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 + export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin + + # Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 + export CFLAGS="-D__ANDROID_API__=21" + + # IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on + BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" + + mkdir -p bdk-kotlin/android/src/main/jniLibs/ bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-kotlin/android/src/main/jniLibs/x86 + + if echo $BUILD_TARGETS | grep "aarch64"; then + CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android + cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/arm64-v8a + fi + if echo $BUILD_TARGETS | grep "x86_64"; then + CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android + cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86_64 + fi + if echo $BUILD_TARGETS | grep "armv7"; then + CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi + cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/armeabi-v7a + fi + if echo $BUILD_TARGETS | grep "i686"; then + CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android + cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86 + fi + + # bdk-kotlin aar + (cd bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) +} -# bdk-kotlin jar OS=$(uname) -case $OS in - "Darwin") - echo "Darwin build system" - mkdir -p bdk-kotlin/jvm/src/main/resources/darwin-x86-64 - cp target/debug/libbdk_ffi.dylib bdk-kotlin/jvm/src/main/resources/darwin-x86-64 - ;; - "Linux") - echo "Linux build system" - mkdir -p bdk-kotlin/jvm/src/main/resources/linux-x86-64 - cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/src/main/resources/linux-x86-64 - ;; -esac -(cd bdk-kotlin && gradle :jvm:build && gradle :jvm:publishToMavenLocal) +if [ $1 = "-h" ] +then + help +else + build_rust + build_cc + copy_lib_kotlin -# rust android - -# If ANDROID_NDK_HOME is not set then set it to github actions default -[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle - -# Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 -export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin - -# Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 -export CFLAGS="-D__ANDROID_API__=21" - -# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on -BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" - -mkdir -p bdk-kotlin/android/src/main/jniLibs/ bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-kotlin/android/src/main/jniLibs/x86 - -if echo $BUILD_TARGETS | grep "aarch64"; then - CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android - cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/arm64-v8a + while [ -n "$1" ]; do # while loop starts + case "$1" in + -k) build_kotlin ;; + -a) build_android ;; + -h) help ;; + *) echo "Option $1 not recognized" ;; + esac + shift + done fi -if echo $BUILD_TARGETS | grep "x86_64"; then - CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android - cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86_64 -fi -if echo $BUILD_TARGETS | grep "armv7"; then - CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi - cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/armeabi-v7a -fi -if echo $BUILD_TARGETS | grep "i686"; then - CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android - cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86 -fi - -# bdk-kotlin aar -(cd bdk-kotlin && gradle :android:build && gradle :android:publishToMavenLocal) diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h index 850e75a..4170429 100644 --- a/cc/bdk_ffi.h +++ b/cc/bdk_ffi.h @@ -40,10 +40,6 @@ typedef uint16_t FfiError_t; enum /** . */ FFI_ERROR_SCRIPT_DOESNT_HAVE_ADDRESS_FORM, /** . */ - FFI_ERROR_SINGLE_RECIPIENT_MULTIPLE_OUTPUTS, - /** . */ - FFI_ERROR_SINGLE_RECIPIENT_NO_INPUTS, - /** . */ FFI_ERROR_NO_RECIPIENTS, /** . */ FFI_ERROR_NO_UTXOS_SELECTED, @@ -68,6 +64,8 @@ typedef uint16_t FfiError_t; enum /** . */ FFI_ERROR_FEE_TOO_LOW, /** . */ + FFI_ERROR_FEE_RATE_UNAVAILABLE, + /** . */ FFI_ERROR_MISSING_KEY_ORIGIN, /** . */ FFI_ERROR_KEY, @@ -80,6 +78,8 @@ typedef uint16_t FfiError_t; enum /** . */ FFI_ERROR_SIGNER, /** . */ + FFI_ERROR_INVALID_NETWORK, + /** . */ FFI_ERROR_INVALID_PROGRESS_VALUE, /** . */ FFI_ERROR_PROGRESS_UPDATE_ERROR, @@ -104,6 +104,8 @@ typedef uint16_t FfiError_t; enum /** . */ FFI_ERROR_PSBT, /** . */ + FFI_ERROR_PSBT_PARSE, + /** . */ FFI_ERROR_ELECTRUM, /** . */ FFI_ERROR_SLED, @@ -124,6 +126,7 @@ typedef struct { FfiResult_OpaqueWallet_ptr_t new_wallet_result ( char const * descriptor, char const * change_descriptor, + char const * network, BlockchainConfig_t const * blockchain_config, DatabaseConfig_t const * database_config); @@ -214,19 +217,32 @@ typedef struct { FfiResult_uint64_t balance ( OpaqueWallet_t const * opaque_wallet); + +#include + +typedef struct { + + uint32_t height; + + uint64_t timestamp; + +} ConfirmationTime_t; + typedef struct { char * txid; - uint64_t timestamp; - uint64_t received; uint64_t sent; - uint64_t fees; + int64_t fee; - int32_t height; + bool is_confirmed; + + ConfirmationTime_t confirmation_time; + + bool verified; } TransactionDetails_t; @@ -261,7 +277,8 @@ BlockchainConfig_t * new_electrum_config ( char const * url, char const * socks5, int16_t retry, - int16_t timeout); + int16_t timeout, + size_t stop_gap); void free_blockchain_config ( BlockchainConfig_t * blockchain_config); diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index 5f95924..cc10627 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -6,14 +6,21 @@ int main (int argc, char const * const argv[]) { + + // shared consts + char const *desc = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/0/*)"; + char const *change = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/1/*)"; + char const *net = "testnet"; + char const *blocks = "ssl://electrum.blockstream.info:60002"; + // test new wallet error { - BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100); //DatabaseConfig_t *db_config = new_sled_config("/home/steve/.bdk", "test_wallet"); DatabaseConfig_t *db_config = new_memory_config(); // new wallet with bad descriptor - FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result("bad","bad",bc_config,db_config); + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result("bad","bad",net,bc_config,db_config); assert(wallet_result.err == FFI_ERROR_DESCRIPTOR); assert(wallet_result.ok == NULL); @@ -25,14 +32,11 @@ int main (int argc, char const * const argv[]) // test new wallet { - char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; - char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; - - BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100); DatabaseConfig_t *db_config = new_memory_config(); // new wallet - FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config); // printf("wallet_result.err = %d\n", wallet_result.err)); assert(wallet_result.err == FFI_ERROR_NONE); assert(wallet_result.ok != NULL); @@ -51,15 +55,15 @@ int main (int argc, char const * const argv[]) FfiResult_char_ptr_t address1_result = new_address(wallet); assert(address1_result.ok != NULL); assert(address1_result.err == FFI_ERROR_NONE); - // printf("address1 = %s\n", *address1_result.ok); - assert( 0 == strcmp(address1_result.ok,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp")); + //printf("address1 = %s\n", address1_result.ok); + assert( 0 == strcmp(address1_result.ok,"tb1qh4ajvhz9nd76tqddnl99l89hx4dat33hrjauzw")); free_string_result(address1_result); FfiResult_char_ptr_t address2_result = new_address(wallet); assert(address2_result.ok != NULL); assert(address2_result.err == FFI_ERROR_NONE); - // printf("address2 = %s\n", *address2_result.ok); - assert( 0 == strcmp(address2_result.ok,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf")); + //printf("address2 = %s\n", address2_result.ok); + assert( 0 == strcmp(address2_result.ok,"tb1qr7pu0pech43hcjrc4pzxcen0qkslj7xk7s5w3m")); free_string_result(address2_result); // free_wallet @@ -73,15 +77,12 @@ int main (int argc, char const * const argv[]) } // test get unspent utxos - { - char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; - char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; - - BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + { + BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100); DatabaseConfig_t *db_config = new_memory_config(); // new wallet - FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config); assert(wallet_result.err == FFI_ERROR_NONE); assert(wallet_result.ok != NULL); @@ -97,7 +98,7 @@ int main (int argc, char const * const argv[]) // list unspent FfiResult_Vec_LocalUtxo_t unspent_result = list_unspent(wallet); - assert(unspent_result.ok.len == 7); + assert(unspent_result.ok.len == 1); assert(unspent_result.err == FFI_ERROR_NONE); LocalUtxo_t * unspent_ptr = unspent_result.ok.ptr; @@ -119,15 +120,12 @@ int main (int argc, char const * const argv[]) } // test balance - { - char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; - char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; - - BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + { + BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100); DatabaseConfig_t *db_config = new_memory_config(); // new wallet - FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config); assert(wallet_result.err == FFI_ERROR_NONE); assert(wallet_result.ok != NULL); @@ -155,14 +153,11 @@ int main (int argc, char const * const argv[]) // test get transaction details { - char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; - char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; - - BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100); DatabaseConfig_t *db_config = new_memory_config(); // new wallet - FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,bc_config,db_config); + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config); assert(wallet_result.err == FFI_ERROR_NONE); assert(wallet_result.ok != NULL); @@ -186,14 +181,14 @@ int main (int argc, char const * const argv[]) //printf("%d: txid: %s\n", i, txdetails_ptr[i].txid); assert(txdetails_ptr[i].txid != NULL); //printf("%d: timestamp: %ld\n", i, txdetails_ptr[i].timestamp); - assert(txdetails_ptr[i].timestamp > 0); + assert(txdetails_ptr[i].is_confirmed); //printf("%d: received: %ld\n", i, txdetails_ptr[i].received); //printf("%d: sent: %ld\n", i, txdetails_ptr[i].sent); assert(txdetails_ptr[i].received > 0 || txdetails_ptr[i].sent > 0); //printf("%d: fees: %ld\n", i, txdetails_ptr[i].fees); - assert(txdetails_ptr[i].fees > 0); + assert(txdetails_ptr[i].fee > 0); //printf("%d: height: %d\n", i, txdetails_ptr[i].height); - assert(txdetails_ptr[i].height >= -1); + assert(txdetails_ptr[i].confirmation_time.height > 0); } free_vectxdetails_result(txdetails_result); diff --git a/src/error.rs b/src/error.rs index f27c6dd..b0631b3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,8 +9,6 @@ pub enum FfiError { InvalidU32Bytes, Generic, ScriptDoesntHaveAddressForm, - SingleRecipientMultipleOutputs, - SingleRecipientNoInputs, NoRecipients, NoUtxosSelected, OutputBelowDustLimit, @@ -23,12 +21,14 @@ pub enum FfiError { IrreplaceableTransaction, FeeRateTooLow, FeeTooLow, + FeeRateUnavailable, MissingKeyOrigin, Key, ChecksumMismatch, SpendingPolicyRequired, InvalidPolicyPathError, Signer, + InvalidNetwork, InvalidProgressValue, ProgressUpdateError, InvalidOutpoint, @@ -41,6 +41,7 @@ pub enum FfiError { Json, Hex, Psbt, + PsbtParse, Electrum, // Esplora, // CompactFilters, @@ -53,8 +54,6 @@ impl From<&bdk::Error> for FfiError { Error::InvalidU32Bytes(_) => FfiError::InvalidU32Bytes, Error::Generic(_) => FfiError::Generic, Error::ScriptDoesntHaveAddressForm => FfiError::ScriptDoesntHaveAddressForm, - Error::SingleRecipientMultipleOutputs => FfiError::SingleRecipientMultipleOutputs, - Error::SingleRecipientNoInputs => FfiError::SingleRecipientNoInputs, Error::NoRecipients => FfiError::NoRecipients, Error::NoUtxosSelected => FfiError::NoUtxosSelected, Error::OutputBelowDustLimit(_) => FfiError::OutputBelowDustLimit, @@ -67,12 +66,14 @@ impl From<&bdk::Error> for FfiError { Error::IrreplaceableTransaction => FfiError::IrreplaceableTransaction, Error::FeeRateTooLow { .. } => FfiError::FeeRateTooLow, Error::FeeTooLow { .. } => FfiError::FeeTooLow, + Error::FeeRateUnavailable => FfiError::FeeRateUnavailable, Error::MissingKeyOrigin(_) => FfiError::MissingKeyOrigin, Error::Key(_) => FfiError::Key, Error::ChecksumMismatch => FfiError::ChecksumMismatch, Error::SpendingPolicyRequired(_) => FfiError::SpendingPolicyRequired, Error::InvalidPolicyPathError(_) => FfiError::InvalidPolicyPathError, Error::Signer(_) => FfiError::Signer, + Error::InvalidNetwork { .. } => FfiError::InvalidNetwork, Error::InvalidProgressValue(_) => FfiError::InvalidProgressValue, Error::ProgressUpdateError => FfiError::ProgressUpdateError, Error::InvalidOutpoint(_) => FfiError::InvalidOutpoint, @@ -85,9 +86,11 @@ impl From<&bdk::Error> for FfiError { Error::Json(_) => FfiError::Json, Error::Hex(_) => FfiError::Hex, Error::Psbt(_) => FfiError::Psbt, + Error::PsbtParse(_) => FfiError::PsbtParse, Error::Electrum(_) => FfiError::Electrum, // Error::Esplora(_) => JniError::Esplora, // Error::CompactFilters(_) => JniError::CompactFilters, + // Error::Rpc(_) => JniError::Rpc, Error::Sled(_) => FfiError::Sled, } } diff --git a/src/wallet/blockchain.rs b/src/wallet/blockchain.rs index b3ecd8b..50742f8 100644 --- a/src/wallet/blockchain.rs +++ b/src/wallet/blockchain.rs @@ -16,6 +16,7 @@ fn new_electrum_config( socks5: Option, retry: i16, timeout: i16, + stop_gap: usize, ) -> Box { let url = url.to_string(); let socks5 = socks5.map(|s| s.to_string()); @@ -27,6 +28,7 @@ fn new_electrum_config( socks5, retry, timeout, + stop_gap, }); Box::new(BlockchainConfig { raw: electrum_config, diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 90327eb..4637efc 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -2,7 +2,6 @@ use std::convert::TryFrom; use std::ffi::CString; use ::safer_ffi::prelude::*; -use bdk::bitcoin::network::constants::Network::Testnet; use bdk::blockchain::{log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain}; use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase}; use bdk::wallet::AddressIndex::New; @@ -16,6 +15,8 @@ use database::DatabaseConfig; use crate::error::FfiError; use crate::types::{FfiResult, FfiResultVoid}; use crate::wallet::transaction::{LocalUtxo, TransactionDetails}; +use bdk::bitcoin::Network; +use std::str::FromStr; mod blockchain; mod database; @@ -33,14 +34,16 @@ pub struct OpaqueWallet { fn new_wallet_result( descriptor: char_p_ref, change_descriptor: Option, + network: char_p_ref, blockchain_config: &BlockchainConfig, database_config: &DatabaseConfig, ) -> FfiResult>> { let descriptor = descriptor.to_string(); let change_descriptor = change_descriptor.map(|s| s.to_string()); + let net = Network::from_str(network.to_str()).expect("Network name"); let bc_config = &blockchain_config.raw; let db_config = &database_config.raw; - let wallet_result = new_wallet(descriptor, change_descriptor, bc_config, db_config); + let wallet_result = new_wallet(descriptor, change_descriptor, net, bc_config, db_config); match wallet_result { Ok(w) => FfiResult { @@ -57,11 +60,10 @@ fn new_wallet_result( fn new_wallet( descriptor: String, change_descriptor: Option, + network: Network, blockchain_config: &AnyBlockchainConfig, database_config: &AnyDatabaseConfig, ) -> Result, Error> { - let network = Testnet; - let client = AnyBlockchain::from_config(blockchain_config)?; let database = AnyDatabase::from_config(database_config)?; diff --git a/src/wallet/transaction.rs b/src/wallet/transaction.rs index 378d4cf..5c2194c 100644 --- a/src/wallet/transaction.rs +++ b/src/wallet/transaction.rs @@ -13,27 +13,52 @@ pub struct TransactionDetails { // pub transaction: Option, /// Transaction id pub txid: char_p_boxed, - /// Timestamp - pub timestamp: u64, /// Received value (sats) pub received: u64, /// Sent value (sats) pub sent: u64, - /// Fee value (sats) - pub fees: u64, - /// Confirmed in block height, `None` means unconfirmed - pub height: i32, + /// Fee value (sats) if known, -1 if unknown, based on backend + pub fee: i64, + /// true if confirmed + pub is_confirmed: bool, + /// Confirmed in block height + pub confirmation_time: ConfirmationTime, + /// Whether the tx has been verified against the consensus rules + pub verified: bool, +} + +#[derive_ReprC] +#[repr(C)] +#[derive(Debug, Clone)] +pub struct ConfirmationTime { + /// confirmation block height, 0 if is_confirmed is false + pub height: u32, + /// confirmation block timestamp, 0 if is_confirmed is false + pub timestamp: u64, } impl From<&bdk::TransactionDetails> for TransactionDetails { fn from(op: &bdk::TransactionDetails) -> Self { + let fee = op.fee.map(|f| i64::try_from(f).unwrap()).unwrap_or(-1); + let confirmation_time = op + .confirmation_time + .as_ref() + .map(|c| ConfirmationTime { + height: c.height, + timestamp: c.timestamp, + }) + .unwrap_or(ConfirmationTime { + height: 0, + timestamp: 0, + }); TransactionDetails { txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(), - timestamp: op.timestamp, received: op.received, sent: op.sent, - fees: op.fees, - height: op.height.map(|h| h as i32).unwrap_or(-1), + fee, + is_confirmed: op.confirmation_time.is_some(), + confirmation_time, + verified: op.verified, } } } diff --git a/test.sh b/test.sh index c5a23fc..45b497e 100755 --- a/test.sh +++ b/test.sh @@ -1,14 +1,62 @@ #!/usr/bin/env bash -set -eo pipefail -o xtrace +set -eo pipefail + +# functions + +## help +help() +{ + # Display Help + echo "Test bdk-ffi and related libraries." + echo + echo "Syntax: build [-a|h|k|v]" + echo "options:" + echo "-a Android aar tests." + echo "-h Print this Help." + echo "-k JVM jar tests." + echo "-v Valgrind tests." + echo +} # rust -cargo test --features c-headers -- generate_headers +c_headers() { + cargo test --features c-headers -- generate_headers +} # cc -export LD_LIBRARY_PATH=`pwd`/target/debug -#valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test -cc/bdk_ffi_test +test_c() { + export LD_LIBRARY_PATH=`pwd`/target/debug + cc/bdk_ffi_test +} -# bdk-kotlin -(cd bdk-kotlin && gradle test) -(cd bdk-kotlin && gradle :android:connectedDebugAndroidTest) +test_valgrind() { + valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test +} + +test_kotlin() { + (cd bdk-kotlin && ./gradlew test) +} + +test_android() { + (cd bdk-kotlin && ./gradlew :android:connectedDebugAndroidTest) +} + +if [ $1 = "-h" ] +then + help +else + c_headers + test_c + + # optional tests + while [ -n "$1" ]; do # while loop starts + case "$1" in + -a) test_android ;; + -h) help ;; + -k) test_kotlin ;; + -v) test_valgrind ;; + *) echo "Option $1 not recognized" ;; + esac + shift + done +fi \ No newline at end of file From 6f46e2deb61704625a19c0c98e1a81a779b16879 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 2 Oct 2021 12:08:34 -0700 Subject: [PATCH 034/272] [wip] swift --- README.md | 6 +++- bdk-swift/.gitignore | 7 ++++ bdk-swift/bdk.swift/.gitignore | 7 ++++ bdk-swift/bdk.swift/Package.resolved | 16 +++++++++ bdk-swift/bdk.swift/Package.swift | 28 +++++++++++++++ bdk-swift/bdk.swift/README.md | 11 ++++++ .../Sources/bdk.swift/bdk_swift.swift | 8 +++++ .../Tests/bdk.swiftTests/bdk_swiftTests.swift | 35 +++++++++++++++++++ 8 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 bdk-swift/.gitignore create mode 100644 bdk-swift/bdk.swift/.gitignore create mode 100644 bdk-swift/bdk.swift/Package.resolved create mode 100644 bdk-swift/bdk.swift/Package.swift create mode 100644 bdk-swift/bdk.swift/README.md create mode 100644 bdk-swift/bdk.swift/Sources/bdk.swift/bdk_swift.swift create mode 100644 bdk-swift/bdk.swift/Tests/bdk.swiftTests/bdk_swiftTests.swift diff --git a/README.md b/README.md index 2935e1c..495c4c2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Setup build environment +Setup Android build environment 1. Add Android rust targets @@ -12,6 +12,10 @@ rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu x86_64-linux-andr export ANDROID_NDK_HOME=/home//Android/Sdk/ndk/ ``` +Setup Swift build environment + +1. Install Swift, see ["Download Swift"](https://swift.org/download/) page + Adding new structs and functions 1. Create C safe Rust structs and related functions using safer-ffi diff --git a/bdk-swift/.gitignore b/bdk-swift/.gitignore new file mode 100644 index 0000000..bb460e7 --- /dev/null +++ b/bdk-swift/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/bdk-swift/bdk.swift/.gitignore b/bdk-swift/bdk.swift/.gitignore new file mode 100644 index 0000000..bb460e7 --- /dev/null +++ b/bdk-swift/bdk.swift/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/bdk-swift/bdk.swift/Package.resolved b/bdk-swift/bdk.swift/Package.resolved new file mode 100644 index 0000000..e1a027b --- /dev/null +++ b/bdk-swift/bdk.swift/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "Clibbdkffi", + "repositoryURL": "/home/steve/git/notmandatory/Clibbdkffi", + "state": { + "branch": null, + "revision": "9c96e359a3b1e1d5c0db61125147f6ef929bf567", + "version": "0.1.0" + } + } + ] + }, + "version": 1 +} diff --git a/bdk-swift/bdk.swift/Package.swift b/bdk-swift/bdk.swift/Package.swift new file mode 100644 index 0000000..3a7987b --- /dev/null +++ b/bdk-swift/bdk.swift/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version:5.5 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "bdk.swift", + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "bdk.swift", + targets: ["bdk.swift"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "../../../Clibbdkffi", from: "0.1.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "bdk.swift", + dependencies: ["Clibbdkffi"]), + .testTarget( + name: "bdk.swiftTests", + dependencies: ["bdk.swift"]), + ] +) diff --git a/bdk-swift/bdk.swift/README.md b/bdk-swift/bdk.swift/README.md new file mode 100644 index 0000000..a678238 --- /dev/null +++ b/bdk-swift/bdk.swift/README.md @@ -0,0 +1,11 @@ +# bdk.swift + +To build: +``` +swift build -Xlinker -L../../target/debug +``` + +To test: +``` +swift test -Xlinker -L../../target/debug +``` diff --git a/bdk-swift/bdk.swift/Sources/bdk.swift/bdk_swift.swift b/bdk-swift/bdk.swift/Sources/bdk.swift/bdk_swift.swift new file mode 100644 index 0000000..3f0ecc0 --- /dev/null +++ b/bdk-swift/bdk.swift/Sources/bdk.swift/bdk_swift.swift @@ -0,0 +1,8 @@ +import Clibbdkffi + +public struct bdk_swift { + public private(set) var text = "Hello, World!" + + public init() { + } +} diff --git a/bdk-swift/bdk.swift/Tests/bdk.swiftTests/bdk_swiftTests.swift b/bdk-swift/bdk.swift/Tests/bdk.swiftTests/bdk_swiftTests.swift new file mode 100644 index 0000000..8070cc6 --- /dev/null +++ b/bdk-swift/bdk.swift/Tests/bdk.swiftTests/bdk_swiftTests.swift @@ -0,0 +1,35 @@ +import XCTest +import Clibbdkffi + +@testable import bdk_swift + +final class bdk_swiftTests: XCTestCase { + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct + // results. + XCTAssertEqual(bdk_swift().text, "Hello, World!") + let desc = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/0/*)"; + let change = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/1/*)"; + let net = "testnet"; + let blocks = "ssl://electrum.blockstream.info:60002"; + + let bc_config = new_electrum_config(blocks, nil, 5, 30, 100) + let db_config = new_memory_config() + + let wallet_result = new_wallet_result(desc,change,net,bc_config,db_config) + + free_blockchain_config(bc_config) + free_database_config(db_config) + + let wallet = wallet_result.ok + let sync_result = sync_wallet(wallet) + assert(sync_result.err == FFI_ERROR_NONE) + free_void_result(sync_result) + + let address1_result = new_address(wallet).ok + let address1 = String(cString: address1_result!, encoding: .utf8) + //print("address1 = \(address1!)") + assert(address1! == "tb1qh4ajvhz9nd76tqddnl99l89hx4dat33hrjauzw") + } +} From aa63457d9c28063fd87a1f19b07e4c840ffa61da Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 11 Oct 2021 23:04:18 -0700 Subject: [PATCH 035/272] WIP -- NOT WORKING --- Cargo.toml | 8 +- README.md | 7 + build.rs | 3 + build.sh | 16 +- cc/bdk_ffi.h | 98 +++---- kotlin/uniffi/bdk/bdk.kt | 602 +++++++++++++++++++++++++++++++++++++++ src/bdk.udl | 7 + src/error.rs | 49 +++- src/lib.rs | 46 ++- src/uniffi/bdk/bdk.kt | 602 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 1360 insertions(+), 78 deletions(-) create mode 100644 build.rs create mode 100644 kotlin/uniffi/bdk/bdk.kt create mode 100644 src/bdk.udl create mode 100644 src/uniffi/bdk/bdk.kt diff --git a/Cargo.toml b/Cargo.toml index cb9787c..95599f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,9 @@ crate-type = ["cdylib"] [dependencies] bdk = { version = "^0.11", features = ["all-keys"] } -safer-ffi = { version = "0.0.6", features = ["proc_macros"]} +uniffi_macros = "0.14.0" +uniffi = "0.14.0" +thiserror = "1.0" -[features] -c-headers = ["safer-ffi/headers"] +[build-dependencies] +uniffi_build = "0.14.0" diff --git a/README.md b/README.md index 495c4c2..6240a08 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ + +UniFFI + +1. cargo install uniffi_bindgen +2. cargo build +3. uniffi-bindgen generate --no-format --out-dir kotlin src/bdk.udl --language kotlin + Setup Android build environment 1. Add Android rust targets diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..9213f51 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi_build::generate_scaffolding("src/bdk.udl").unwrap(); +} \ No newline at end of file diff --git a/build.sh b/build.sh index 67ca036..2ccb2a7 100755 --- a/build.sh +++ b/build.sh @@ -34,7 +34,7 @@ build_cc() { ## copy to bdk-kotlin copy_lib_kotlin() { - echo -n "Copy " + echo -n "Copy " case $OS in "Darwin") echo -n "darwin " @@ -59,18 +59,18 @@ build_kotlin() { build_android() { # If ANDROID_NDK_HOME is not set then set it to github actions default [ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle - + # Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin - + # Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 export CFLAGS="-D__ANDROID_API__=21" - + # IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" - + mkdir -p bdk-kotlin/android/src/main/jniLibs/ bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-kotlin/android/src/main/jniLibs/x86 - + if echo $BUILD_TARGETS | grep "aarch64"; then CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/arm64-v8a @@ -88,13 +88,13 @@ build_android() { cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86 fi - # bdk-kotlin aar + # bdk-kotlin aar (cd bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) } OS=$(uname) -if [ $1 = "-h" ] +if [ "$1" == "-h" ] then help else diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h index 4170429..ea76ad8 100644 --- a/cc/bdk_ffi.h +++ b/cc/bdk_ffi.h @@ -14,11 +14,16 @@ extern "C" { #endif -typedef struct BlockchainConfig BlockchainConfig_t; - typedef struct DatabaseConfig DatabaseConfig_t; -typedef struct OpaqueWallet OpaqueWallet_t; +DatabaseConfig_t * new_memory_config (void); + +DatabaseConfig_t * new_sled_config ( + char const * path, + char const * tree_name); + +void free_database_config ( + DatabaseConfig_t * database_config); #include @@ -115,6 +120,47 @@ FfiError_t #endif ; +typedef struct { + + char * ok; + + FfiError_t err; + +} FfiResult_char_ptr_t; + +void free_string_result ( + FfiResult_char_ptr_t string_result); + +typedef struct { + + FfiError_t err; + +} FfiResultVoid_t; + +void free_void_result ( + FfiResultVoid_t void_result); + +typedef struct { + + uint64_t ok; + + FfiError_t err; + +} FfiResult_uint64_t; + +void free_uint64_result ( + FfiResult_uint64_t void_result); + +/** \brief + * Free a Rust-allocated string + */ +void free_string ( + char * string); + +typedef struct BlockchainConfig BlockchainConfig_t; + +typedef struct OpaqueWallet OpaqueWallet_t; + typedef struct { OpaqueWallet_t * ok; @@ -133,23 +179,9 @@ FfiResult_OpaqueWallet_ptr_t new_wallet_result ( void free_wallet_result ( FfiResult_OpaqueWallet_ptr_t wallet_result); -typedef struct { - - FfiError_t err; - -} FfiResultVoid_t; - FfiResultVoid_t sync_wallet ( OpaqueWallet_t const * opaque_wallet); -typedef struct { - - char * ok; - - FfiError_t err; - -} FfiResult_char_ptr_t; - FfiResult_char_ptr_t new_address ( OpaqueWallet_t const * opaque_wallet); @@ -206,14 +238,6 @@ FfiResult_Vec_LocalUtxo_t list_unspent ( void free_veclocalutxo_result ( FfiResult_Vec_LocalUtxo_t unspent_result); -typedef struct { - - uint64_t ok; - - FfiError_t err; - -} FfiResult_uint64_t; - FfiResult_uint64_t balance ( OpaqueWallet_t const * opaque_wallet); @@ -283,30 +307,6 @@ BlockchainConfig_t * new_electrum_config ( void free_blockchain_config ( BlockchainConfig_t * blockchain_config); -void free_string_result ( - FfiResult_char_ptr_t string_result); - -void free_void_result ( - FfiResultVoid_t void_result); - -void free_uint64_result ( - FfiResult_uint64_t void_result); - -/** \brief - * Free a Rust-allocated string - */ -void free_string ( - char * string); - -DatabaseConfig_t * new_memory_config (void); - -DatabaseConfig_t * new_sled_config ( - char const * path, - char const * tree_name); - -void free_database_config ( - DatabaseConfig_t * database_config); - #ifdef __cplusplus } /* extern "C" */ diff --git a/kotlin/uniffi/bdk/bdk.kt b/kotlin/uniffi/bdk/bdk.kt new file mode 100644 index 0000000..eedd389 --- /dev/null +++ b/kotlin/uniffi/bdk/bdk.kt @@ -0,0 +1,602 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + +package uniffi.bdk; + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the detils of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Library +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + @JvmField var capacity: Int = 0 + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : RustBuffer(), Structure.ByValue + class ByReference : RustBuffer(), Structure.ByReference + + companion object { + internal fun alloc(size: Int = 0) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_alloc(size, status) + } + + internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_free(buf, status) + } + + internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_reserve(buf, additional, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} + + +// A helper for structured writing of data into a `RustBuffer`. +// This is very similar to `java.nio.ByteBuffer` but it knows how to grow +// the underlying `RustBuffer` on demand. +// +// TODO: we should benchmark writing things into a `RustBuffer` versus building +// up a bytearray and then copying it across. + +class RustBufferBuilder() { + var rbuf = RustBuffer.ByValue() + var bbuf: ByteBuffer? = null + + init { + val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size + rbuf.writeField("len", 0) + this.setRustBuffer(rbuf) + } + + internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { + this.rbuf = rbuf + this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + it.position(rbuf.len) + } + } + + fun finalize() : RustBuffer.ByValue { + val rbuf = this.rbuf + // Ensure that the JVM-level field is written through to native memory + // before turning the buffer, in case its recipient uses it in a context + // JNA doesn't apply its automatic synchronization logic. + rbuf.writeField("len", this.bbuf!!.position()) + this.setRustBuffer(RustBuffer.ByValue()) + return rbuf + } + + fun discard() { + val rbuf = this.finalize() + RustBuffer.free(rbuf) + } + + internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) { + // TODO: this will perform two checks to ensure we're not overflowing the buffer: + // one here where we check if it needs to grow, and another when we call a write + // method on the ByteBuffer. It might be cheaper to use exception-driven control-flow + // here, trying the write and growing if it throws a `BufferOverflowException`. + // Benchmarking needed. + if (this.bbuf!!.position() + size > this.rbuf.capacity) { + rbuf.writeField("len", this.bbuf!!.position()) + this.setRustBuffer(RustBuffer.reserve(this.rbuf, size)) + } + write(this.bbuf!!) + } + + fun putByte(v: Byte) { + this.reserve(1) { bbuf -> + bbuf.put(v) + } + } + + fun putShort(v: Short) { + this.reserve(2) { bbuf -> + bbuf.putShort(v) + } + } + + fun putInt(v: Int) { + this.reserve(4) { bbuf -> + bbuf.putInt(v) + } + } + + fun putLong(v: Long) { + this.reserve(8) { bbuf -> + bbuf.putLong(v) + } + } + + fun putFloat(v: Float) { + this.reserve(4) { bbuf -> + bbuf.putFloat(v) + } + } + + fun putDouble(v: Double) { + this.reserve(8) { bbuf -> + bbuf.putDouble(v) + } + } + + fun put(v: ByteArray) { + this.reserve(v.size) { bbuf -> + bbuf.put(v) + } + } +} + +// Helpers for reading primitive data types from a bytebuffer. + +internal fun liftFromRustBuffer(rbuf: RustBuffer.ByValue, readItem: (ByteBuffer) -> T): T { + val buf = rbuf.asByteBuffer()!! + try { + val item = readItem(buf) + if (buf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } +} + +internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { + // TODO: maybe we can calculate some sort of initial size hint? + val buf = RustBufferBuilder() + try { + writeItem(v, buf) + return buf.finalize() + } catch (e: Throwable) { + buf.discard() + throw e + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + + + + +internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(rbuf.len) + rbuf.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(rbuf) + } +} + +internal fun String.Companion.read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) +} + +internal fun String.lower(): RustBuffer.ByValue { + val byteArr = this.toByteArray(Charsets.UTF_8) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteArr.size) + rbuf.asByteBuffer()!!.put(byteArr) + return rbuf +} + +internal fun String.write(buf: RustBufferBuilder) { + val byteArr = this.toByteArray(Charsets.UTF_8) + buf.putInt(byteArr.size) + buf.put(byteArr) +} + + + + + + + + + + +@Synchronized +fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "uniffi_bdk" +} + +inline fun loadIndirect( + componentName: String +): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. + +internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "bdk") + + + } + } + + fun ffi_bdk_ed55_OfflineWallet_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_ed55_OfflineWallet_new(descriptor: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun ffi_bdk_ed55_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_ed55_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_ed55_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_ed55_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + +} + +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. + + + +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +// The base class for all UniFFI Object types. +// +// This class provides core operations for working with the Rust `Arc` pointer to +// the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an `FFIObject` is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an `FFIObject` instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so will +// leak the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// In the future we may be able to replace some of this with automatic finalization logic, such as using +// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is +// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also +// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], +// so there would still be some complexity here). +// +// Sigh...all of this for want of a robust finalization mechanism. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// +abstract class FFIObject( + protected val pointer: Pointer +): Disposable, AutoCloseable { + + val wasDestroyed = AtomicBoolean(false) + val callCounter = AtomicLong(1) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.pointer) + } finally { + // This decrement aways matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } +} + + + + + +// Public interface members begin here. +// Public facing enums +// Error definitions +@Structure.FieldOrder("code", "error_buf") +internal open class RustCallStatus : Structure() { + @JvmField var code: Int = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + fun isSuccess(): Boolean { + return code == 0 + } + + fun isError(): Boolean { + return code == 1 + } + + fun isPanic(): Boolean { + return code == 2 + } +} + +class InternalException(message: String) : Exception(message) + +// Each top-level error class has a companion object that can lift the error from the call status's rust buffer +interface CallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E; +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus(); + val return_value = callback(status) + if (status.isSuccess()) { + return return_value + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(String.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object NullCallStatusErrorHandler: CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); +} + +// Public facing records + +// Namespace functions + + +// Objects + + +public interface OfflineWalletInterface { + +} + + +class OfflineWallet( + pointer: Pointer +) : FFIObject(pointer), OfflineWalletInterface { + constructor(descriptor: String ) : + this( + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_ed55_OfflineWallet_new(descriptor.lower() ,status) +}) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_ed55_OfflineWallet_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + + + companion object { + internal fun lift(ptr: Pointer): OfflineWallet { + return OfflineWallet(ptr) + } + + internal fun read(buf: ByteBuffer): OfflineWallet { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return OfflineWallet.lift(Pointer(buf.getLong())) + } + + + } +} + + +// Callback Interfaces + + diff --git a/src/bdk.udl b/src/bdk.udl new file mode 100644 index 0000000..20450ec --- /dev/null +++ b/src/bdk.udl @@ -0,0 +1,7 @@ +namespace bdk { + +}; + +interface OfflineWallet { + constructor(string descriptor); +}; \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index b0631b3..737ff62 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,55 +1,92 @@ -use ::safer_ffi::prelude::*; use bdk::Error; +use thiserror::Error; -#[derive_ReprC] -#[repr(u16)] -#[derive(Debug)] +#[derive(Error, Debug)] pub enum FfiError { + #[error("data store disconnected")] None, + #[error("data store disconnected")] InvalidU32Bytes, + #[error("data store disconnected")] Generic, + #[error("data store disconnected")] ScriptDoesntHaveAddressForm, + #[error("data store disconnected")] NoRecipients, + #[error("data store disconnected")] NoUtxosSelected, + #[error("data store disconnected")] OutputBelowDustLimit, + #[error("data store disconnected")] InsufficientFunds, + #[error("data store disconnected")] BnBTotalTriesExceeded, + #[error("data store disconnected")] BnBNoExactMatch, + #[error("data store disconnected")] UnknownUtxo, + #[error("data store disconnected")] TransactionNotFound, + #[error("data store disconnected")] TransactionConfirmed, + #[error("data store disconnected")] IrreplaceableTransaction, + #[error("data store disconnected")] FeeRateTooLow, + #[error("data store disconnected")] FeeTooLow, + #[error("data store disconnected")] FeeRateUnavailable, + #[error("data store disconnected")] MissingKeyOrigin, + #[error("data store disconnected")] Key, + #[error("data store disconnected")] ChecksumMismatch, + #[error("data store disconnected")] SpendingPolicyRequired, + #[error("data store disconnected")] InvalidPolicyPathError, + #[error("data store disconnected")] Signer, + #[error("data store disconnected")] InvalidNetwork, + #[error("data store disconnected")] InvalidProgressValue, + #[error("data store disconnected")] ProgressUpdateError, + #[error("data store disconnected")] InvalidOutpoint, + #[error("data store disconnected")] Descriptor, + #[error("data store disconnected")] AddressValidator, + #[error("data store disconnected")] Encode, + #[error("data store disconnected")] Miniscript, + #[error("data store disconnected")] Bip32, + #[error("data store disconnected")] Secp256k1, + #[error("data store disconnected")] Json, + #[error("data store disconnected")] Hex, + #[error("data store disconnected")] Psbt, + #[error("data store disconnected")] PsbtParse, + #[error("data store disconnected")] Electrum, // Esplora, // CompactFilters, + #[error("data store disconnected")] Sled, } -impl From<&bdk::Error> for FfiError { - fn from(error: &bdk::Error) -> Self { +impl From for FfiError { + fn from(error: bdk::Error) -> Self { match error { Error::InvalidU32Bytes(_) => FfiError::InvalidU32Bytes, Error::Generic(_) => FfiError::Generic, diff --git a/src/lib.rs b/src/lib.rs index 21b8266..50e5853 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,37 @@ -#![deny(unsafe_code)] /* No `unsafe` needed! */ +use bdk::Wallet; +use bdk::database::MemoryDatabase; +use bdk::bitcoin::Network; +// use crate::error::FfiError; +use std::sync::RwLock; +use std::vec::Vec; -mod error; -mod types; -mod wallet; +//mod error; +//mod types; +//mod wallet; -/// The following test function is necessary for the header generation. -#[::safer_ffi::cfg_headers] -#[test] -fn generate_headers() -> ::std::io::Result<()> { - ::safer_ffi::headers::builder() - .with_guard("__RUST_BDK_FFI__") - .to_file("cc/bdk_ffi.h")? - .generate() +uniffi_macros::include_scaffolding!("bdk"); + +struct OfflineWallet { + wallet: Wallet<(), MemoryDatabase>, + //wallet: RwLock> } + +impl OfflineWallet { + fn new(descriptor: String) -> Self { + let wallet = Wallet::new_offline( + &descriptor, + None, + Network::Regtest, + MemoryDatabase::new(), + ).unwrap(); + + OfflineWallet { + wallet + } + +// OfflineWallet { +// wallet: RwLock::new(Vec::new()) +// } + } +} + diff --git a/src/uniffi/bdk/bdk.kt b/src/uniffi/bdk/bdk.kt new file mode 100644 index 0000000..eedd389 --- /dev/null +++ b/src/uniffi/bdk/bdk.kt @@ -0,0 +1,602 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + +package uniffi.bdk; + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the detils of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Library +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + @JvmField var capacity: Int = 0 + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : RustBuffer(), Structure.ByValue + class ByReference : RustBuffer(), Structure.ByReference + + companion object { + internal fun alloc(size: Int = 0) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_alloc(size, status) + } + + internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_free(buf, status) + } + + internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_reserve(buf, additional, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} + + +// A helper for structured writing of data into a `RustBuffer`. +// This is very similar to `java.nio.ByteBuffer` but it knows how to grow +// the underlying `RustBuffer` on demand. +// +// TODO: we should benchmark writing things into a `RustBuffer` versus building +// up a bytearray and then copying it across. + +class RustBufferBuilder() { + var rbuf = RustBuffer.ByValue() + var bbuf: ByteBuffer? = null + + init { + val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size + rbuf.writeField("len", 0) + this.setRustBuffer(rbuf) + } + + internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { + this.rbuf = rbuf + this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + it.position(rbuf.len) + } + } + + fun finalize() : RustBuffer.ByValue { + val rbuf = this.rbuf + // Ensure that the JVM-level field is written through to native memory + // before turning the buffer, in case its recipient uses it in a context + // JNA doesn't apply its automatic synchronization logic. + rbuf.writeField("len", this.bbuf!!.position()) + this.setRustBuffer(RustBuffer.ByValue()) + return rbuf + } + + fun discard() { + val rbuf = this.finalize() + RustBuffer.free(rbuf) + } + + internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) { + // TODO: this will perform two checks to ensure we're not overflowing the buffer: + // one here where we check if it needs to grow, and another when we call a write + // method on the ByteBuffer. It might be cheaper to use exception-driven control-flow + // here, trying the write and growing if it throws a `BufferOverflowException`. + // Benchmarking needed. + if (this.bbuf!!.position() + size > this.rbuf.capacity) { + rbuf.writeField("len", this.bbuf!!.position()) + this.setRustBuffer(RustBuffer.reserve(this.rbuf, size)) + } + write(this.bbuf!!) + } + + fun putByte(v: Byte) { + this.reserve(1) { bbuf -> + bbuf.put(v) + } + } + + fun putShort(v: Short) { + this.reserve(2) { bbuf -> + bbuf.putShort(v) + } + } + + fun putInt(v: Int) { + this.reserve(4) { bbuf -> + bbuf.putInt(v) + } + } + + fun putLong(v: Long) { + this.reserve(8) { bbuf -> + bbuf.putLong(v) + } + } + + fun putFloat(v: Float) { + this.reserve(4) { bbuf -> + bbuf.putFloat(v) + } + } + + fun putDouble(v: Double) { + this.reserve(8) { bbuf -> + bbuf.putDouble(v) + } + } + + fun put(v: ByteArray) { + this.reserve(v.size) { bbuf -> + bbuf.put(v) + } + } +} + +// Helpers for reading primitive data types from a bytebuffer. + +internal fun liftFromRustBuffer(rbuf: RustBuffer.ByValue, readItem: (ByteBuffer) -> T): T { + val buf = rbuf.asByteBuffer()!! + try { + val item = readItem(buf) + if (buf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } +} + +internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { + // TODO: maybe we can calculate some sort of initial size hint? + val buf = RustBufferBuilder() + try { + writeItem(v, buf) + return buf.finalize() + } catch (e: Throwable) { + buf.discard() + throw e + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + + + + +internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(rbuf.len) + rbuf.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(rbuf) + } +} + +internal fun String.Companion.read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) +} + +internal fun String.lower(): RustBuffer.ByValue { + val byteArr = this.toByteArray(Charsets.UTF_8) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteArr.size) + rbuf.asByteBuffer()!!.put(byteArr) + return rbuf +} + +internal fun String.write(buf: RustBufferBuilder) { + val byteArr = this.toByteArray(Charsets.UTF_8) + buf.putInt(byteArr.size) + buf.put(byteArr) +} + + + + + + + + + + +@Synchronized +fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "uniffi_bdk" +} + +inline fun loadIndirect( + componentName: String +): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. + +internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "bdk") + + + } + } + + fun ffi_bdk_ed55_OfflineWallet_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_ed55_OfflineWallet_new(descriptor: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun ffi_bdk_ed55_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_ed55_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_ed55_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_ed55_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + +} + +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. + + + +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +// The base class for all UniFFI Object types. +// +// This class provides core operations for working with the Rust `Arc` pointer to +// the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an `FFIObject` is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an `FFIObject` instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so will +// leak the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// In the future we may be able to replace some of this with automatic finalization logic, such as using +// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is +// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also +// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], +// so there would still be some complexity here). +// +// Sigh...all of this for want of a robust finalization mechanism. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// +abstract class FFIObject( + protected val pointer: Pointer +): Disposable, AutoCloseable { + + val wasDestroyed = AtomicBoolean(false) + val callCounter = AtomicLong(1) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.pointer) + } finally { + // This decrement aways matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } +} + + + + + +// Public interface members begin here. +// Public facing enums +// Error definitions +@Structure.FieldOrder("code", "error_buf") +internal open class RustCallStatus : Structure() { + @JvmField var code: Int = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + fun isSuccess(): Boolean { + return code == 0 + } + + fun isError(): Boolean { + return code == 1 + } + + fun isPanic(): Boolean { + return code == 2 + } +} + +class InternalException(message: String) : Exception(message) + +// Each top-level error class has a companion object that can lift the error from the call status's rust buffer +interface CallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E; +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus(); + val return_value = callback(status) + if (status.isSuccess()) { + return return_value + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(String.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object NullCallStatusErrorHandler: CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); +} + +// Public facing records + +// Namespace functions + + +// Objects + + +public interface OfflineWalletInterface { + +} + + +class OfflineWallet( + pointer: Pointer +) : FFIObject(pointer), OfflineWalletInterface { + constructor(descriptor: String ) : + this( + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_ed55_OfflineWallet_new(descriptor.lower() ,status) +}) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_ed55_OfflineWallet_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + + + companion object { + internal fun lift(ptr: Pointer): OfflineWallet { + return OfflineWallet(ptr) + } + + internal fun read(buf: ByteBuffer): OfflineWallet { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return OfflineWallet.lift(Pointer(buf.getLong())) + } + + + } +} + + +// Callback Interfaces + + From cdb90aa35c0a859e237a9bfe570c1298be35fd2c Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 12 Oct 2021 11:53:11 -0700 Subject: [PATCH 036/272] wip compiles now --- src/lib.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 50e5853..c7f4430 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,13 @@ use bdk::Wallet; use bdk::database::MemoryDatabase; use bdk::bitcoin::Network; // use crate::error::FfiError; -use std::sync::RwLock; +use std::sync::{RwLock, Mutex}; use std::vec::Vec; +use bdk::database::BatchDatabase; +use bdk::sled; +use bdk::sled::Tree; + //mod error; //mod types; //mod wallet; @@ -12,21 +16,24 @@ use std::vec::Vec; uniffi_macros::include_scaffolding!("bdk"); struct OfflineWallet { - wallet: Wallet<(), MemoryDatabase>, + wallet: Mutex>, //wallet: RwLock> } impl OfflineWallet { fn new(descriptor: String) -> Self { + let database = sled::open("testdb").unwrap(); + let tree = database.open_tree("test").unwrap(); + let wallet = Wallet::new_offline( &descriptor, None, Network::Regtest, - MemoryDatabase::new(), + tree, ).unwrap(); OfflineWallet { - wallet + wallet: Mutex::new(wallet) } // OfflineWallet { @@ -35,3 +42,6 @@ impl OfflineWallet { } } +uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); + + From 84d28a0476c67e9a29db68d1bee44b9f72e38ed2 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Wed, 13 Oct 2021 01:45:22 +0530 Subject: [PATCH 037/272] [WIP] Add get new address API to Wallet --- kotlin/uniffi/bdk/bdk.kt | 36 +++++++++++++++++++++++++----------- src/bdk.udl | 1 + src/lib.rs | 14 ++++++++++++-- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/kotlin/uniffi/bdk/bdk.kt b/kotlin/uniffi/bdk/bdk.kt index eedd389..2b99f03 100644 --- a/kotlin/uniffi/bdk/bdk.kt +++ b/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_reserve(buf, additional, status) } } @@ -276,27 +276,31 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_ed55_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_75ce_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_ed55_OfflineWallet_new(descriptor: RustBuffer.ByValue, + fun bdk_75ce_OfflineWallet_new(descriptor: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun ffi_bdk_ed55_rustbuffer_alloc(size: Int, + fun bdk_75ce_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_ed55_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun ffi_bdk_75ce_rustbuffer_alloc(size: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_ed55_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_75ce_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_75ce_rustbuffer_free(buf: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_ed55_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_75ce_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -544,6 +548,7 @@ private inline fun rustCall(callback: (RustCallStatus) -> U): U { public interface OfflineWalletInterface { + fun getNewAddress(): String } @@ -554,7 +559,7 @@ class OfflineWallet( constructor(descriptor: String ) : this( rustCall() { status -> - _UniFFILib.INSTANCE.bdk_ed55_OfflineWallet_new(descriptor.lower() ,status) + _UniFFILib.INSTANCE.bdk_75ce_OfflineWallet_new(descriptor.lower() ,status) }) /** @@ -567,7 +572,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_ed55_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_75ce_OfflineWallet_object_free(this.pointer, status) } } @@ -579,6 +584,15 @@ class OfflineWallet( buf.putLong(Pointer.nativeValue(this.lower())) } + override fun getNewAddress(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_75ce_OfflineWallet_get_new_address(it, status) +} + }.let { + String.lift(it) + } + companion object { diff --git a/src/bdk.udl b/src/bdk.udl index 20450ec..5e30901 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -4,4 +4,5 @@ namespace bdk { interface OfflineWallet { constructor(string descriptor); + string get_new_address(); }; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index c7f4430..4002e1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,13 @@ use bdk::Wallet; +use bdk::wallet::AddressIndex; use bdk::database::MemoryDatabase; use bdk::bitcoin::Network; // use crate::error::FfiError; use std::sync::{RwLock, Mutex}; use std::vec::Vec; - use bdk::database::BatchDatabase; use bdk::sled; use bdk::sled::Tree; - //mod error; //mod types; //mod wallet; @@ -40,6 +39,17 @@ impl OfflineWallet { // wallet: RwLock::new(Vec::new()) // } } + + fn get_new_address(&self) -> String { + self + .wallet + .lock() + .unwrap() + .get_address(AddressIndex::New) + .unwrap() + .address + .to_string() + } } uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); From a92aa663587a10c13c80464a08c87afcf3f45b9d Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Wed, 13 Oct 2021 02:05:46 +0530 Subject: [PATCH 038/272] [WIP] Add generated and test files --- README.md | 2 +- targets/kotlin/build.gradle | 19 + .../kotlin/src/main/kotlin/uniffi/bdk/bdk.kt | 616 ++++++++++++++++++ .../src/test/kotlin/uniffi/bdk/LibTest.kt | 26 + 4 files changed, 662 insertions(+), 1 deletion(-) create mode 100644 targets/kotlin/build.gradle create mode 100644 targets/kotlin/src/main/kotlin/uniffi/bdk/bdk.kt create mode 100644 targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt diff --git a/README.md b/README.md index 6240a08..4c9a43f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ UniFFI 1. cargo install uniffi_bindgen 2. cargo build -3. uniffi-bindgen generate --no-format --out-dir kotlin src/bdk.udl --language kotlin +3. uniffi-bindgen generate --no-format --out-dir targets/kotlin/src/main/kotlin src/bdk.udl --language kotlin Setup Android build environment diff --git a/targets/kotlin/build.gradle b/targets/kotlin/build.gradle new file mode 100644 index 0000000..3084c30 --- /dev/null +++ b/targets/kotlin/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'java-library' +} + +dependencies { + implementation platform('org.jetbrains.kotlin:kotlin-bom') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + // implementation "net.java.dev.jna:jna:5.8.0" + // implementation(project(':jvm')) + implementation "junit:junit:4.13.2" + //implementation "org.mockito.kotlin:mockito-kotlin:3.2.0" + // api "org.slf4j:slf4j-api:1.7.30" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/targets/kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/targets/kotlin/src/main/kotlin/uniffi/bdk/bdk.kt new file mode 100644 index 0000000..2b99f03 --- /dev/null +++ b/targets/kotlin/src/main/kotlin/uniffi/bdk/bdk.kt @@ -0,0 +1,616 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + +package uniffi.bdk; + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the detils of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Library +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + @JvmField var capacity: Int = 0 + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : RustBuffer(), Structure.ByValue + class ByReference : RustBuffer(), Structure.ByReference + + companion object { + internal fun alloc(size: Int = 0) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_alloc(size, status) + } + + internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_free(buf, status) + } + + internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_reserve(buf, additional, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} + + +// A helper for structured writing of data into a `RustBuffer`. +// This is very similar to `java.nio.ByteBuffer` but it knows how to grow +// the underlying `RustBuffer` on demand. +// +// TODO: we should benchmark writing things into a `RustBuffer` versus building +// up a bytearray and then copying it across. + +class RustBufferBuilder() { + var rbuf = RustBuffer.ByValue() + var bbuf: ByteBuffer? = null + + init { + val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size + rbuf.writeField("len", 0) + this.setRustBuffer(rbuf) + } + + internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { + this.rbuf = rbuf + this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + it.position(rbuf.len) + } + } + + fun finalize() : RustBuffer.ByValue { + val rbuf = this.rbuf + // Ensure that the JVM-level field is written through to native memory + // before turning the buffer, in case its recipient uses it in a context + // JNA doesn't apply its automatic synchronization logic. + rbuf.writeField("len", this.bbuf!!.position()) + this.setRustBuffer(RustBuffer.ByValue()) + return rbuf + } + + fun discard() { + val rbuf = this.finalize() + RustBuffer.free(rbuf) + } + + internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) { + // TODO: this will perform two checks to ensure we're not overflowing the buffer: + // one here where we check if it needs to grow, and another when we call a write + // method on the ByteBuffer. It might be cheaper to use exception-driven control-flow + // here, trying the write and growing if it throws a `BufferOverflowException`. + // Benchmarking needed. + if (this.bbuf!!.position() + size > this.rbuf.capacity) { + rbuf.writeField("len", this.bbuf!!.position()) + this.setRustBuffer(RustBuffer.reserve(this.rbuf, size)) + } + write(this.bbuf!!) + } + + fun putByte(v: Byte) { + this.reserve(1) { bbuf -> + bbuf.put(v) + } + } + + fun putShort(v: Short) { + this.reserve(2) { bbuf -> + bbuf.putShort(v) + } + } + + fun putInt(v: Int) { + this.reserve(4) { bbuf -> + bbuf.putInt(v) + } + } + + fun putLong(v: Long) { + this.reserve(8) { bbuf -> + bbuf.putLong(v) + } + } + + fun putFloat(v: Float) { + this.reserve(4) { bbuf -> + bbuf.putFloat(v) + } + } + + fun putDouble(v: Double) { + this.reserve(8) { bbuf -> + bbuf.putDouble(v) + } + } + + fun put(v: ByteArray) { + this.reserve(v.size) { bbuf -> + bbuf.put(v) + } + } +} + +// Helpers for reading primitive data types from a bytebuffer. + +internal fun liftFromRustBuffer(rbuf: RustBuffer.ByValue, readItem: (ByteBuffer) -> T): T { + val buf = rbuf.asByteBuffer()!! + try { + val item = readItem(buf) + if (buf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } +} + +internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { + // TODO: maybe we can calculate some sort of initial size hint? + val buf = RustBufferBuilder() + try { + writeItem(v, buf) + return buf.finalize() + } catch (e: Throwable) { + buf.discard() + throw e + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + + + + +internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(rbuf.len) + rbuf.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(rbuf) + } +} + +internal fun String.Companion.read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) +} + +internal fun String.lower(): RustBuffer.ByValue { + val byteArr = this.toByteArray(Charsets.UTF_8) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteArr.size) + rbuf.asByteBuffer()!!.put(byteArr) + return rbuf +} + +internal fun String.write(buf: RustBufferBuilder) { + val byteArr = this.toByteArray(Charsets.UTF_8) + buf.putInt(byteArr.size) + buf.put(byteArr) +} + + + + + + + + + + +@Synchronized +fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "uniffi_bdk" +} + +inline fun loadIndirect( + componentName: String +): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. + +internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "bdk") + + + } + } + + fun ffi_bdk_75ce_OfflineWallet_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_75ce_OfflineWallet_new(descriptor: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun bdk_75ce_OfflineWallet_get_new_address(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_75ce_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_75ce_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_75ce_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_75ce_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + +} + +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. + + + +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +// The base class for all UniFFI Object types. +// +// This class provides core operations for working with the Rust `Arc` pointer to +// the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an `FFIObject` is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an `FFIObject` instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so will +// leak the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// In the future we may be able to replace some of this with automatic finalization logic, such as using +// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is +// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also +// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], +// so there would still be some complexity here). +// +// Sigh...all of this for want of a robust finalization mechanism. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// +abstract class FFIObject( + protected val pointer: Pointer +): Disposable, AutoCloseable { + + val wasDestroyed = AtomicBoolean(false) + val callCounter = AtomicLong(1) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.pointer) + } finally { + // This decrement aways matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } +} + + + + + +// Public interface members begin here. +// Public facing enums +// Error definitions +@Structure.FieldOrder("code", "error_buf") +internal open class RustCallStatus : Structure() { + @JvmField var code: Int = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + fun isSuccess(): Boolean { + return code == 0 + } + + fun isError(): Boolean { + return code == 1 + } + + fun isPanic(): Boolean { + return code == 2 + } +} + +class InternalException(message: String) : Exception(message) + +// Each top-level error class has a companion object that can lift the error from the call status's rust buffer +interface CallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E; +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus(); + val return_value = callback(status) + if (status.isSuccess()) { + return return_value + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(String.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object NullCallStatusErrorHandler: CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); +} + +// Public facing records + +// Namespace functions + + +// Objects + + +public interface OfflineWalletInterface { + fun getNewAddress(): String + +} + + +class OfflineWallet( + pointer: Pointer +) : FFIObject(pointer), OfflineWalletInterface { + constructor(descriptor: String ) : + this( + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_75ce_OfflineWallet_new(descriptor.lower() ,status) +}) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_75ce_OfflineWallet_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + override fun getNewAddress(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_75ce_OfflineWallet_get_new_address(it, status) +} + }.let { + String.lift(it) + } + + + + companion object { + internal fun lift(ptr: Pointer): OfflineWallet { + return OfflineWallet(ptr) + } + + internal fun read(buf: ByteBuffer): OfflineWallet { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return OfflineWallet.lift(Pointer(buf.getLong())) + } + + + } +} + + +// Callback Interfaces + + diff --git a/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt new file mode 100644 index 0000000..c23f9b1 --- /dev/null +++ b/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -0,0 +1,26 @@ +package uniffi.bdk + +import uniffi.bdk.OfflineWallet +import org.junit.Assert.* +import org.junit.Test +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.File + +/** + * Library tests which will execute for jvm and android modules. + */ +class LibTest { + + val desc = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + + @Test + fun walletNewAddress() { + val wallet = OfflineWallet(desc) + val address = wallet.getNewAddress() + assertNotNull(address) + // log.debug("address created from kotlin: $address") + assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") + } +} From 3ccf780ed3081d54c20e2e7f2af993a5f815f342 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Wed, 13 Oct 2021 03:02:49 +0530 Subject: [PATCH 039/272] [WIP] kotlin tests work! --- Cargo.toml | 2 +- kotlin/uniffi/bdk/bdk.kt | 616 ------------------ targets/kotlin/build.gradle | 26 +- targets/kotlin/gradle.properties | 23 + .../src/test/kotlin/uniffi/bdk/LibTest.kt | 3 +- 5 files changed, 46 insertions(+), 624 deletions(-) delete mode 100644 kotlin/uniffi/bdk/bdk.kt create mode 100644 targets/kotlin/gradle.properties diff --git a/Cargo.toml b/Cargo.toml index 95599f8..35d42a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bdk-ffi" +name = "uniffi_bdk" version = "0.1.0" authors = ["Steve Myers "] edition = "2018" diff --git a/kotlin/uniffi/bdk/bdk.kt b/kotlin/uniffi/bdk/bdk.kt deleted file mode 100644 index 2b99f03..0000000 --- a/kotlin/uniffi/bdk/bdk.kt +++ /dev/null @@ -1,616 +0,0 @@ -// This file was autogenerated by some hot garbage in the `uniffi` crate. -// Trust me, you don't want to mess with it! - -@file:Suppress("NAME_SHADOWING") - -package uniffi.bdk; - -// Common helper code. -// -// Ideally this would live in a separate .kt file where it can be unittested etc -// in isolation, and perhaps even published as a re-useable package. -// -// However, it's important that the detils of how this helper code works (e.g. the -// way that different builtin types are passed across the FFI) exactly match what's -// expected by the Rust code on the other side of the interface. In practice right -// now that means coming from the exact some version of `uniffi` that was used to -// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin -// helpers directly inline like we're doing here. - -import com.sun.jna.Library -import com.sun.jna.Native -import com.sun.jna.Pointer -import com.sun.jna.Structure -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.util.concurrent.atomic.AtomicLong -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicReference -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock - -// This is a helper for safely working with byte buffers returned from the Rust code. -// A rust-owned buffer is represented by its capacity, its current length, and a -// pointer to the underlying data. - -@Structure.FieldOrder("capacity", "len", "data") -open class RustBuffer : Structure() { - @JvmField var capacity: Int = 0 - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : RustBuffer(), Structure.ByValue - class ByReference : RustBuffer(), Structure.ByReference - - companion object { - internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_alloc(size, status) - } - - internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_free(buf, status) - } - - internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_reserve(buf, additional, status) - } - } - - @Suppress("TooGenericExceptionThrown") - fun asByteBuffer() = - this.data?.getByteBuffer(0, this.len.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - } -} - -// This is a helper for safely passing byte references into the rust code. -// It's not actually used at the moment, because there aren't many things that you -// can take a direct pointer to in the JVM, and if we're going to copy something -// then we might as well copy it into a `RustBuffer`. But it's here for API -// completeness. - -@Structure.FieldOrder("len", "data") -open class ForeignBytes : Structure() { - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : ForeignBytes(), Structure.ByValue -} - - -// A helper for structured writing of data into a `RustBuffer`. -// This is very similar to `java.nio.ByteBuffer` but it knows how to grow -// the underlying `RustBuffer` on demand. -// -// TODO: we should benchmark writing things into a `RustBuffer` versus building -// up a bytearray and then copying it across. - -class RustBufferBuilder() { - var rbuf = RustBuffer.ByValue() - var bbuf: ByteBuffer? = null - - init { - val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size - rbuf.writeField("len", 0) - this.setRustBuffer(rbuf) - } - - internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { - this.rbuf = rbuf - this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - it.position(rbuf.len) - } - } - - fun finalize() : RustBuffer.ByValue { - val rbuf = this.rbuf - // Ensure that the JVM-level field is written through to native memory - // before turning the buffer, in case its recipient uses it in a context - // JNA doesn't apply its automatic synchronization logic. - rbuf.writeField("len", this.bbuf!!.position()) - this.setRustBuffer(RustBuffer.ByValue()) - return rbuf - } - - fun discard() { - val rbuf = this.finalize() - RustBuffer.free(rbuf) - } - - internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) { - // TODO: this will perform two checks to ensure we're not overflowing the buffer: - // one here where we check if it needs to grow, and another when we call a write - // method on the ByteBuffer. It might be cheaper to use exception-driven control-flow - // here, trying the write and growing if it throws a `BufferOverflowException`. - // Benchmarking needed. - if (this.bbuf!!.position() + size > this.rbuf.capacity) { - rbuf.writeField("len", this.bbuf!!.position()) - this.setRustBuffer(RustBuffer.reserve(this.rbuf, size)) - } - write(this.bbuf!!) - } - - fun putByte(v: Byte) { - this.reserve(1) { bbuf -> - bbuf.put(v) - } - } - - fun putShort(v: Short) { - this.reserve(2) { bbuf -> - bbuf.putShort(v) - } - } - - fun putInt(v: Int) { - this.reserve(4) { bbuf -> - bbuf.putInt(v) - } - } - - fun putLong(v: Long) { - this.reserve(8) { bbuf -> - bbuf.putLong(v) - } - } - - fun putFloat(v: Float) { - this.reserve(4) { bbuf -> - bbuf.putFloat(v) - } - } - - fun putDouble(v: Double) { - this.reserve(8) { bbuf -> - bbuf.putDouble(v) - } - } - - fun put(v: ByteArray) { - this.reserve(v.size) { bbuf -> - bbuf.put(v) - } - } -} - -// Helpers for reading primitive data types from a bytebuffer. - -internal fun liftFromRustBuffer(rbuf: RustBuffer.ByValue, readItem: (ByteBuffer) -> T): T { - val buf = rbuf.asByteBuffer()!! - try { - val item = readItem(buf) - if (buf.hasRemaining()) { - throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") - } - return item - } finally { - RustBuffer.free(rbuf) - } -} - -internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { - // TODO: maybe we can calculate some sort of initial size hint? - val buf = RustBufferBuilder() - try { - writeItem(v, buf) - return buf.finalize() - } catch (e: Throwable) { - buf.discard() - throw e - } -} - -// For every type used in the interface, we provide helper methods for conveniently -// lifting and lowering that type from C-compatible data, and for reading and writing -// values of that type in a buffer. - - - - -internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String { - try { - val byteArr = ByteArray(rbuf.len) - rbuf.asByteBuffer()!!.get(byteArr) - return byteArr.toString(Charsets.UTF_8) - } finally { - RustBuffer.free(rbuf) - } -} - -internal fun String.Companion.read(buf: ByteBuffer): String { - val len = buf.getInt() - val byteArr = ByteArray(len) - buf.get(byteArr) - return byteArr.toString(Charsets.UTF_8) -} - -internal fun String.lower(): RustBuffer.ByValue { - val byteArr = this.toByteArray(Charsets.UTF_8) - // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us - // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. - val rbuf = RustBuffer.alloc(byteArr.size) - rbuf.asByteBuffer()!!.put(byteArr) - return rbuf -} - -internal fun String.write(buf: RustBufferBuilder) { - val byteArr = this.toByteArray(Charsets.UTF_8) - buf.putInt(byteArr.size) - buf.put(byteArr) -} - - - - - - - - - - -@Synchronized -fun findLibraryName(componentName: String): String { - val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride") - if (libOverride != null) { - return libOverride - } - return "uniffi_bdk" -} - -inline fun loadIndirect( - componentName: String -): Lib { - return Native.load(findLibraryName(componentName), Lib::class.java) -} - -// A JNA Library to expose the extern-C FFI definitions. -// This is an implementation detail which will be called internally by the public API. - -internal interface _UniFFILib : Library { - companion object { - internal val INSTANCE: _UniFFILib by lazy { - loadIndirect<_UniFFILib>(componentName = "bdk") - - - } - } - - fun ffi_bdk_75ce_OfflineWallet_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_75ce_OfflineWallet_new(descriptor: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_75ce_OfflineWallet_get_new_address(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_75ce_rustbuffer_alloc(size: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_75ce_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_75ce_rustbuffer_free(buf: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_75ce_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - -} - -// A handful of classes and functions to support the generated data structures. -// This would be a good candidate for isolating in its own ffi-support lib. - - - -// Interface implemented by anything that can contain an object reference. -// -// Such types expose a `destroy()` method that must be called to cleanly -// dispose of the contained objects. Failure to call this method may result -// in memory leaks. -// -// The easiest way to ensure this method is called is to use the `.use` -// helper method to execute a block and destroy the object at the end. -interface Disposable { - fun destroy() -} - -inline fun T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } - -// The base class for all UniFFI Object types. -// -// This class provides core operations for working with the Rust `Arc` pointer to -// the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an `FFIObject` is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an `FFIObject` instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so will -// leak the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// In the future we may be able to replace some of this with automatic finalization logic, such as using -// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is -// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also -// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], -// so there would still be some complexity here). -// -// Sigh...all of this for want of a robust finalization mechanism. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// -abstract class FFIObject( - protected val pointer: Pointer -): Disposable, AutoCloseable { - - val wasDestroyed = AtomicBoolean(false) - val callCounter = AtomicLong(1) - - open protected fun freeRustArcPtr() { - // To be overridden in subclasses. - } - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.pointer) - } finally { - // This decrement aways matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } -} - - - - - -// Public interface members begin here. -// Public facing enums -// Error definitions -@Structure.FieldOrder("code", "error_buf") -internal open class RustCallStatus : Structure() { - @JvmField var code: Int = 0 - @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - - fun isSuccess(): Boolean { - return code == 0 - } - - fun isError(): Boolean { - return code == 1 - } - - fun isPanic(): Boolean { - return code == 2 - } -} - -class InternalException(message: String) : Exception(message) - -// Each top-level error class has a companion object that can lift the error from the call status's rust buffer -interface CallStatusErrorHandler { - fun lift(error_buf: RustBuffer.ByValue): E; -} - -// Helpers for calling Rust -// In practice we usually need to be synchronized to call this safely, so it doesn't -// synchronize itself - -// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err -private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { - var status = RustCallStatus(); - val return_value = callback(status) - if (status.isSuccess()) { - return return_value - } else if (status.isError()) { - throw errorHandler.lift(status.error_buf) - } else if (status.isPanic()) { - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if (status.error_buf.len > 0) { - throw InternalException(String.lift(status.error_buf)) - } else { - throw InternalException("Rust panic") - } - } else { - throw InternalException("Unknown rust call status: $status.code") - } -} - -// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR -object NullCallStatusErrorHandler: CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): InternalException { - RustBuffer.free(error_buf) - return InternalException("Unexpected CALL_ERROR") - } -} - -// Call a rust function that returns a plain value -private inline fun rustCall(callback: (RustCallStatus) -> U): U { - return rustCallWithError(NullCallStatusErrorHandler, callback); -} - -// Public facing records - -// Namespace functions - - -// Objects - - -public interface OfflineWalletInterface { - fun getNewAddress(): String - -} - - -class OfflineWallet( - pointer: Pointer -) : FFIObject(pointer), OfflineWalletInterface { - constructor(descriptor: String ) : - this( - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_75ce_OfflineWallet_new(descriptor.lower() ,status) -}) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { - rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_75ce_OfflineWallet_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun getNewAddress(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_75ce_OfflineWallet_get_new_address(it, status) -} - }.let { - String.lift(it) - } - - - - companion object { - internal fun lift(ptr: Pointer): OfflineWallet { - return OfflineWallet(ptr) - } - - internal fun read(buf: ByteBuffer): OfflineWallet { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return OfflineWallet.lift(Pointer(buf.getLong())) - } - - - } -} - - -// Callback Interfaces - - diff --git a/targets/kotlin/build.gradle b/targets/kotlin/build.gradle index 3084c30..bb1b0c0 100644 --- a/targets/kotlin/build.gradle +++ b/targets/kotlin/build.gradle @@ -1,16 +1,32 @@ +buildscript { + ext.kotlin_version = '1.5.10' + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.2.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + plugins { - id 'org.jetbrains.kotlin.jvm' + id "org.jetbrains.kotlin.jvm" version "$kotlin_version" id 'java-library' } +allprojects { + repositories { + google() + mavenCentral() + } +} + dependencies { implementation platform('org.jetbrains.kotlin:kotlin-bom') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - // implementation "net.java.dev.jna:jna:5.8.0" - // implementation(project(':jvm')) + implementation "net.java.dev.jna:jna:5.8.0" implementation "junit:junit:4.13.2" - //implementation "org.mockito.kotlin:mockito-kotlin:3.2.0" - // api "org.slf4j:slf4j-api:1.7.30" } java { diff --git a/targets/kotlin/gradle.properties b/targets/kotlin/gradle.properties new file mode 100644 index 0000000..5976929 --- /dev/null +++ b/targets/kotlin/gradle.properties @@ -0,0 +1,23 @@ +# 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 +jna.debug_load=true +jna.debug_load.jna=true diff --git a/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index c23f9b1..eef3228 100644 --- a/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -3,8 +3,6 @@ package uniffi.bdk import uniffi.bdk.OfflineWallet import org.junit.Assert.* import org.junit.Test -import org.slf4j.Logger -import org.slf4j.LoggerFactory import java.io.File /** @@ -19,6 +17,7 @@ class LibTest { fun walletNewAddress() { val wallet = OfflineWallet(desc) val address = wallet.getNewAddress() + println("address:" + address) assertNotNull(address) // log.debug("address created from kotlin: $address") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") From 121e2b34b549a2ff3e591f3c052ba7610c49683f Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Wed, 13 Oct 2021 03:04:48 +0530 Subject: [PATCH 040/272] [WIP] first passing test! --- targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index eef3228..1e161cd 100644 --- a/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -20,6 +20,6 @@ class LibTest { println("address:" + address) assertNotNull(address) // log.debug("address created from kotlin: $address") - assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") + assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") } } From 87a4e0986289efc15125e254325b2d32230226c8 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Wed, 13 Oct 2021 03:05:55 +0530 Subject: [PATCH 041/272] Add some more steps to run --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4c9a43f..41a4d19 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ UniFFI 1. cargo install uniffi_bindgen 2. cargo build 3. uniffi-bindgen generate --no-format --out-dir targets/kotlin/src/main/kotlin src/bdk.udl --language kotlin +4. cp target/debug/libuniffi_bdk.dylib targets/kotlin/src/main/resources/darwin-x86-64 +5. gradle build -Djna.debug_load=true -Djna.debug_load.jna + Setup Android build environment From a7f7ab0ef91da8e3b8a209ee02e67b5ac37bc499 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Wed, 13 Oct 2021 03:06:49 +0530 Subject: [PATCH 042/272] Ignore testdb --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 17d844a..66ded16 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ Cargo.lock wallet_db bdk_ffi_test local.properties -*.log \ No newline at end of file +*.log +targets/kotlin/testdb \ No newline at end of file From 290db0105f007112a0ccf5391fd761bb82f80735 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 12 Oct 2021 15:24:11 -0700 Subject: [PATCH 043/272] [WIP] reorganize and remove old stuff --- .gitignore | 2 +- Cargo.toml | 2 +- README.md | 6 +- bdk-kotlin/.gitignore | 7 - bdk-kotlin/android/build.gradle | 71 --- bdk-kotlin/android/consumer-rules.pro | 0 bdk-kotlin/android/proguard-rules.pro | 26 - .../src/androidTest/assets/logback.xml | 14 - .../org/bitcoindevkit/bdk/AndroidLibTest.kt | 21 - .../android/src/main/AndroidManifest.xml | 6 - bdk-kotlin/build.gradle | 22 - bdk-kotlin/jvm/build.gradle | 37 -- .../kotlin/org/bitcoindevkit/bdk/FfiError.kt | 45 -- .../org/bitcoindevkit/bdk/FfiException.kt | 14 - .../kotlin/org/bitcoindevkit/bdk/LibBase.kt | 9 - .../kotlin/org/bitcoindevkit/bdk/LibJna.kt | 407 ------------ .../bitcoindevkit/bdk/types/StringResult.kt | 31 - .../bitcoindevkit/bdk/types/UInt64Result.kt | 31 - .../org/bitcoindevkit/bdk/types/VoidResult.kt | 31 - .../bdk/wallet/BlockchainConfig.kt | 26 - .../bdk/wallet/DatabaseConfig.kt | 26 - .../bdk/wallet/VecLocalUtxoResult.kt | 32 - .../bdk/wallet/VecTxDetailsResult.kt | 32 - .../org/bitcoindevkit/bdk/wallet/Wallet.kt | 65 -- .../bitcoindevkit/bdk/wallet/WalletResult.kt | 31 - .../org/bitcoindevkit/bdk/JvmLibTest.kt | 16 - bdk-kotlin/settings.gradle | 3 - bdk-kotlin/test-fixtures/build.gradle | 19 - .../kotlin/org/bitcoindevkit/bdk/LibTest.kt | 135 ---- bdk-swift/.gitignore | 7 - bdk-swift/bdk.swift/.gitignore | 7 - bdk-swift/bdk.swift/Package.resolved | 16 - bdk-swift/bdk.swift/Package.swift | 28 - bdk-swift/bdk.swift/README.md | 11 - .../Sources/bdk.swift/bdk_swift.swift | 8 - .../Tests/bdk.swiftTests/bdk_swiftTests.swift | 35 - .../bdk-kotlin}/build.gradle | 0 .../bdk-kotlin}/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 2 +- {bdk-kotlin => bindings/bdk-kotlin}/gradlew | 0 .../bdk-kotlin}/gradlew.bat | 0 .../src/main/kotlin/uniffi/bdk/bdk.kt | 0 .../src/test/kotlin/uniffi/bdk/LibTest.kt | 2 +- build.sh | 28 +- cc/bdk_ffi.h | 315 --------- cc/bdk_ffi_test.c | 199 ------ src/error.rs | 134 ---- src/types.rs | 40 -- src/uniffi/bdk/bdk.kt | 602 ------------------ src/wallet/blockchain.rs | 106 --- src/wallet/database.rs | 32 - src/wallet/mod.rs | 175 ----- src/wallet/transaction.rs | 124 ---- targets/kotlin/gradle.properties | 23 - 55 files changed, 21 insertions(+), 3040 deletions(-) delete mode 100644 bdk-kotlin/.gitignore delete mode 100644 bdk-kotlin/android/build.gradle delete mode 100644 bdk-kotlin/android/consumer-rules.pro delete mode 100644 bdk-kotlin/android/proguard-rules.pro delete mode 100644 bdk-kotlin/android/src/androidTest/assets/logback.xml delete mode 100644 bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt delete mode 100644 bdk-kotlin/android/src/main/AndroidManifest.xml delete mode 100644 bdk-kotlin/build.gradle delete mode 100644 bdk-kotlin/jvm/build.gradle delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiError.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiException.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibBase.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/DatabaseConfig.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt delete mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt delete mode 100644 bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt delete mode 100644 bdk-kotlin/settings.gradle delete mode 100644 bdk-kotlin/test-fixtures/build.gradle delete mode 100644 bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt delete mode 100644 bdk-swift/.gitignore delete mode 100644 bdk-swift/bdk.swift/.gitignore delete mode 100644 bdk-swift/bdk.swift/Package.resolved delete mode 100644 bdk-swift/bdk.swift/Package.swift delete mode 100644 bdk-swift/bdk.swift/README.md delete mode 100644 bdk-swift/bdk.swift/Sources/bdk.swift/bdk_swift.swift delete mode 100644 bdk-swift/bdk.swift/Tests/bdk.swiftTests/bdk_swiftTests.swift rename {targets/kotlin => bindings/bdk-kotlin}/build.gradle (100%) rename {bdk-kotlin => bindings/bdk-kotlin}/gradle.properties (100%) rename {bdk-kotlin => bindings/bdk-kotlin}/gradle/wrapper/gradle-wrapper.jar (100%) rename {bdk-kotlin => bindings/bdk-kotlin}/gradle/wrapper/gradle-wrapper.properties (92%) rename {bdk-kotlin => bindings/bdk-kotlin}/gradlew (100%) rename {bdk-kotlin => bindings/bdk-kotlin}/gradlew.bat (100%) rename {targets/kotlin => bindings/bdk-kotlin}/src/main/kotlin/uniffi/bdk/bdk.kt (100%) rename {targets/kotlin => bindings/bdk-kotlin}/src/test/kotlin/uniffi/bdk/LibTest.kt (90%) delete mode 100644 cc/bdk_ffi.h delete mode 100644 cc/bdk_ffi_test.c delete mode 100644 src/error.rs delete mode 100644 src/types.rs delete mode 100644 src/uniffi/bdk/bdk.kt delete mode 100644 src/wallet/blockchain.rs delete mode 100644 src/wallet/database.rs delete mode 100644 src/wallet/mod.rs delete mode 100644 src/wallet/transaction.rs delete mode 100644 targets/kotlin/gradle.properties diff --git a/.gitignore b/.gitignore index 66ded16..2a8baf6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ wallet_db bdk_ffi_test local.properties *.log -targets/kotlin/testdb \ No newline at end of file +*.dylib \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 35d42a7..f6dc768 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "uniffi_bdk" version = "0.1.0" -authors = ["Steve Myers "] +authors = ["Steve Myers ", ""] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index 41a4d19..e363d70 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ UniFFI 1. cargo install uniffi_bindgen 2. cargo build -3. uniffi-bindgen generate --no-format --out-dir targets/kotlin/src/main/kotlin src/bdk.udl --language kotlin -4. cp target/debug/libuniffi_bdk.dylib targets/kotlin/src/main/resources/darwin-x86-64 -5. gradle build -Djna.debug_load=true -Djna.debug_load.jna +3. uniffi-bindgen generate --no-format --out-dir bindings/bdk-kotlin/src/main/kotlin src/bdk.udl --language kotlin +4. cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/src/main/resources/darwin-x86-64 +5. cd bindings/bdk-kotlin; gradle build -Djna.debug_load=true -Djna.debug_load.jna Setup Android build environment diff --git a/bdk-kotlin/.gitignore b/bdk-kotlin/.gitignore deleted file mode 100644 index 6501994..0000000 --- a/bdk-kotlin/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -/target -.idea -.gradle -local.properties -build -*.so -*.dylib \ No newline at end of file diff --git a/bdk-kotlin/android/build.gradle b/bdk-kotlin/android/build.gradle deleted file mode 100644 index f3bcdc8..0000000 --- a/bdk-kotlin/android/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'maven-publish' - -android { - compileSdkVersion 30 - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 30 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles 'consumer-rules.pro' - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } -} - -afterEvaluate { - - publishing { - publications { - // Creates a Maven publication called "release". - release(MavenPublication) { - // Applies the component for the release build variant. - from components.release - - // You can then customize attributes of the publication as shown below. - groupId = 'org.bitcoindevkit' - artifactId = 'bdk' - version = '0.0.1-SNAPSHOT' - } - // Creates a Maven publication called “debug”. - debug(MavenPublication) { - // Applies the component for the debug build variant. - from components.debug - - groupId = 'org.bitcoindevkit' - artifactId = 'bdk-debug' - version = '0.0.1-SNAPSHOT' - } - } - } -} - -dependencies { - implementation(project(':jvm')) { - exclude group: 'net.java.dev.jna', module: 'jna' - } - - implementation 'net.java.dev.jna:jna:5.8.0@aar' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.3.0' - implementation 'androidx.core:core-ktx:1.5.0' - api "org.slf4j:slf4j-api:1.7.30" - - androidTestImplementation 'com.github.tony19:logback-android:2.0.0' - androidTestImplementation(project(':test-fixtures')) { - exclude group: 'net.java.dev.jna', module: 'jna' - } - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' - androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' -} diff --git a/bdk-kotlin/android/consumer-rules.pro b/bdk-kotlin/android/consumer-rules.pro deleted file mode 100644 index e69de29..0000000 diff --git a/bdk-kotlin/android/proguard-rules.pro b/bdk-kotlin/android/proguard-rules.pro deleted file mode 100644 index 172980c..0000000 --- a/bdk-kotlin/android/proguard-rules.pro +++ /dev/null @@ -1,26 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - -# for JNA --dontwarn java.awt.* --keep class com.sun.jna.* { *; } --keepclassmembers class * extends com.sun.jna.* { public *; } diff --git a/bdk-kotlin/android/src/androidTest/assets/logback.xml b/bdk-kotlin/android/src/androidTest/assets/logback.xml deleted file mode 100644 index 8f7c81b..0000000 --- a/bdk-kotlin/android/src/androidTest/assets/logback.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - %logger{12} - - - [%-20thread] %msg - - - - - - - \ No newline at end of file diff --git a/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt deleted file mode 100644 index 9e1eec0..0000000 --- a/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.bitcoindevkit.bdk - -import android.app.Application -import android.content.Context.MODE_PRIVATE -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class AndroidLibTest : LibTest() { - override fun getTestDataDir(): String { - val context = ApplicationProvider.getApplicationContext() - return context.getDir("bdk-test", MODE_PRIVATE).toString() - } - -} diff --git a/bdk-kotlin/android/src/main/AndroidManifest.xml b/bdk-kotlin/android/src/main/AndroidManifest.xml deleted file mode 100644 index 2aee369..0000000 --- a/bdk-kotlin/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/bdk-kotlin/build.gradle b/bdk-kotlin/build.gradle deleted file mode 100644 index 1f08c7d..0000000 --- a/bdk-kotlin/build.gradle +++ /dev/null @@ -1,22 +0,0 @@ -buildscript { - ext.kotlin_version = '1.5.10' - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/build.gradle b/bdk-kotlin/jvm/build.gradle deleted file mode 100644 index 64d2c78..0000000 --- a/bdk-kotlin/jvm/build.gradle +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - id 'org.jetbrains.kotlin.jvm' - id 'java-library' - id 'maven-publish' -} - -test { - testLogging { - events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" - } -} - -dependencies { - implementation platform('org.jetbrains.kotlin:kotlin-bom') - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "net.java.dev.jna:jna:5.8.0" - api "org.slf4j:slf4j-api:1.7.30" - testImplementation "ch.qos.logback:logback-classic:1.2.3" - testImplementation "ch.qos.logback:logback-core:1.2.3" - testImplementation(project(':test-fixtures')) -} - -publishing { - publications { - maven(MavenPublication) { - groupId = 'org.bitcoindevkit' - artifactId = 'bdk' - version = '0.0.1-SNAPSHOT' - - from components.java - } - } -} -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiError.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiError.kt deleted file mode 100644 index d5738f9..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiError.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.bitcoindevkit.bdk - -enum class FfiError { - None, - InvalidU32Bytes, - Generic, - ScriptDoesntHaveAddressForm, - SingleRecipientMultipleOutputs, - SingleRecipientNoInputs, - NoRecipients, - NoUtxosSelected, - OutputBelowDustLimit, - InsufficientFunds, - BnBTotalTriesExceeded, - BnBNoExactMatch, - UnknownUtxo, - TransactionNotFound, - TransactionConfirmed, - IrreplaceableTransaction, - FeeRateTooLow, - FeeTooLow, - MissingKeyOrigin, - Key, - ChecksumMismatch, - SpendingPolicyRequired, - InvalidPolicyPathError, - Signer, - InvalidProgressValue, - ProgressUpdateError, - InvalidOutpoint, - Descriptor, - AddressValidator, - Encode, - Miniscript, - Bip32, - Secp256k1, - Json, - Hex, - Psbt, - Electrum, - - // Esplora - // CompactFilters - Sled, -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiException.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiException.kt deleted file mode 100644 index 5fd6ba8..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/FfiException.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.bitcoindevkit.bdk - -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -class FfiException(val err: FfiError) : Exception() { - private val log: Logger = LoggerFactory.getLogger(FfiException::class.java) - - init { - log.error("JnaError: [{}] {}",err.ordinal, err.name) - } - - internal constructor(err: Short) : this(FfiError.values()[err.toInt()]) -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibBase.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibBase.kt deleted file mode 100644 index 03ebf78..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibBase.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.bitcoindevkit.bdk - -import com.sun.jna.Native - -abstract class LibBase { - - protected val libJna: LibJna - get() = Native.load("bdk_ffi", LibJna::class.java) -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt deleted file mode 100644 index 19d2e43..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt +++ /dev/null @@ -1,407 +0,0 @@ -package org.bitcoindevkit.bdk - -import com.sun.jna.* - -interface LibJna : Library { - - // typedef struct { - // - // char * ok; - // - // FfiError_t err; - // - //} FfiResult_char_ptr_t; - open class FfiResult_char_ptr_t : Structure() { - class ByValue : FfiResult_char_ptr_t(), Structure.ByValue - class ByReference : FfiResult_char_ptr_t(), Structure.ByReference - - @JvmField - var ok: String? = null - - @JvmField - var err: Short? = null - - override fun getFieldOrder() = listOf("ok", "err") - } - - // void free_string_result ( - // FfiResult_char_ptr_t string_result); - fun free_string_result(string_result: FfiResult_char_ptr_t.ByValue) - - // typedef struct { - // - // FfiError_t err; - // - //} FfiResultVoid_t; - open class FfiResultVoid_t : Structure() { - class ByValue : FfiResultVoid_t(), Structure.ByValue - class ByReference : FfiResultVoid_t(), Structure.ByReference - - @JvmField - var err: Short? = null - - override fun getFieldOrder() = listOf("err") - } - - // void free_void_result ( - // FfiResultVoid_t void_result); - fun free_void_result(void_result: FfiResultVoid_t.ByValue) - - // void free_string ( - // char * string); - fun free_string(string: Pointer?) - - // typedef struct BlockchainConfig BlockchainConfig_t; - class BlockchainConfig_t : PointerType { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - } - - // BlockchainConfig_t * new_electrum_config ( - // char const * url, - // char const * socks5, - // int16_t retry, - // int16_t timeout, - // size_t stop_gap); - fun new_electrum_config( - url: String, - socks5: String?, - retry: Short, - timeout: Short, - stop_gap: Long, - ): BlockchainConfig_t - - // void free_blockchain_config ( - // BlockchainConfig_t * blockchain_config); - fun free_blockchain_config(blockchain_config: BlockchainConfig_t) - - // typedef struct DatabaseConfig DatabaseConfig_t; - class DatabaseConfig_t : PointerType { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - } - - // typedef struct OpaqueWallet OpaqueWallet_t; - class OpaqueWallet_t : PointerType { - constructor() : super() - constructor(pointer: Pointer) : super(pointer) - } - - // typedef struct { - // - // OpaqueWallet_t * ok; - // - // FfiError_t err; - // - // } FfiResult_OpaqueWallet_ptr_t; - open class FfiResult_OpaqueWallet_ptr_t : Structure() { - class ByValue : FfiResult_OpaqueWallet_ptr_t(), Structure.ByValue - class ByReference : FfiResult_OpaqueWallet_ptr_t(), Structure.ByReference - - @JvmField - var ok: OpaqueWallet_t? = null - - @JvmField - var err: Short? = null - - override fun getFieldOrder() = listOf("ok", "err") - } - - // FfiResult_OpaqueWallet_ptr_t new_wallet_result ( - // char const * descriptor, - // char const * change_descriptor, - // char const * network, - // BlockchainConfig_t const * blockchain_config, - // DatabaseConfig_t const * database_config); - fun new_wallet_result( - descriptor: String, - changeDescriptor: String?, - network: String, - blockchainConfig: BlockchainConfig_t, - databaseConfig: DatabaseConfig_t, - ): FfiResult_OpaqueWallet_ptr_t.ByValue - - // void free_wallet_result ( - // FfiResult_OpaqueWallet_ptr_t wallet_result); - fun free_wallet_result(wallet_result: FfiResult_OpaqueWallet_ptr_t.ByValue) - - // typedef struct { - // - // char * txid; - // - // uint32_t vout; - // - // } OutPoint_t; - open class OutPoint_t : Structure() { - class ByValue : OutPoint_t(), Structure.ByValue - - @JvmField - var txid: String? = null - - @JvmField - var vout: Int? = null - - override fun getFieldOrder() = listOf("txid", "vout") - } - - // typedef struct { - // - // uint64_t value; - // - // char * script_pubkey; - // - // } TxOut_t; - open class TxOut_t : Structure() { - class ByValue : TxOut_t(), Structure.ByValue - - @JvmField - var value: Long? = null - - @JvmField - var script_pubkey: String? = null - - override fun getFieldOrder() = listOf("value", "script_pubkey") - } - - // typedef struct { - // - // OutPoint_t outpoint; - // - // TxOut_t txout; - // - // uint16_t keychain; - // - // } LocalUtxo_t; - open class LocalUtxo_t : Structure() { - - class ByValue : LocalUtxo_t(), Structure.ByValue - class ByReference : LocalUtxo_t(), Structure.ByReference - - @JvmField - var outpoint: OutPoint_t? = null - - @JvmField - var txout: TxOut_t? = null - - @JvmField - var keychain: Short? = null - - override fun getFieldOrder() = listOf("outpoint", "txout", "keychain") - } - - // typedef struct { - // - // LocalUtxo_t * ptr; - // - // size_t len; - // - // size_t cap; - // - // } Vec_LocalUtxo_t; - open class Vec_LocalUtxo_t : Structure() { - - class ByReference : Vec_LocalUtxo_t(), Structure.ByReference - class ByValue : Vec_LocalUtxo_t(), Structure.ByValue - - @JvmField - var ptr: LocalUtxo_t.ByReference? = null - - @JvmField - var len: NativeLong? = null - - @JvmField - var cap: NativeLong? = null - - override fun getFieldOrder() = listOf("ptr", "len", "cap") - } - - // typedef struct { - // - // Vec_LocalUtxo_t ok; - // - // FfiError_t err; - // - // } FfiResult_Vec_LocalUtxo_t; - open class FfiResultVec_LocalUtxo_t : Structure() { - - class ByValue : FfiResultVec_LocalUtxo_t(), Structure.ByValue - class ByReference : FfiResultVec_LocalUtxo_t(), Structure.ByReference - - @JvmField - var ok: Vec_LocalUtxo_t? = null - - @JvmField - var err: Short? = null - - override fun getFieldOrder() = listOf("ok", "err") - } - - // void free_veclocalutxo_result ( - // FfiResult_Vec_LocalUtxo_t unspent_result); - fun free_veclocalutxo_result(unspent_result: FfiResultVec_LocalUtxo_t.ByValue) - - // typedef struct { - // - // uint64_t ok; - // - // FfiError_t err; - // - // } FfiResult_uint64_t; - open class FfiResult_uint64_t : Structure() { - - class ByValue : FfiResult_uint64_t(), Structure.ByValue - class ByReference : FfiResult_uint64_t(), Structure.ByReference - - @JvmField - var ok: Long? = null - - @JvmField - var err: Short? = null - - override fun getFieldOrder() = listOf("ok", "err") - } - - // void free_uint64_result ( - // FfiResult_uint64_t void_result); - fun free_uint64_result(unspent_result: FfiResult_uint64_t.ByValue) - - // FfiResultVoid_t sync_wallet ( - // OpaqueWallet_t const * opaque_wallet); - fun sync_wallet(opaque_wallet: OpaqueWallet_t): FfiResultVoid_t.ByValue - - // FfiResult_char_ptr_t new_address ( - // OpaqueWallet_t const * opaque_wallet); - fun new_address(opaque_wallet: OpaqueWallet_t): FfiResult_char_ptr_t.ByValue - - // FfiResult_Vec_LocalUtxo_t list_unspent ( - // OpaqueWallet_t const * opaque_wallet); - fun list_unspent(opaque_wallet: OpaqueWallet_t): FfiResultVec_LocalUtxo_t.ByValue - - // FfiResult_uint64_t balance ( - // OpaqueWallet_t const * opaque_wallet); - fun balance(opaque_wallet: OpaqueWallet_t): FfiResult_uint64_t.ByValue - - // DatabaseConfig_t * new_memory_config (void); - fun new_memory_config(): DatabaseConfig_t - - // DatabaseConfig_t * new_sled_config ( - // char const * path, - // char const * tree_name); - fun new_sled_config(path: String, tree_name: String): DatabaseConfig_t - - // void free_database_config ( - // DatabaseConfig_t * database_config); - fun free_database_config(database_config: DatabaseConfig_t) - - // typedef struct { - // uint32_t height; - // uint64_t timestamp; - // } ConfirmationTime_t; - open class ConfirmationTime_t : Structure() { - - class ByValue : ConfirmationTime_t(), Structure.ByValue - class ByReference : ConfirmationTime_t(), Structure.ByReference - - @JvmField - var height: Int? = null - - @JvmField - var timestamp: Long? = null - - override fun getFieldOrder() = listOf("height", "timestamp") - } - - // typedef struct { - // char * txid; - // uint64_t received; - // uint64_t sent; - // int64_t fee; - // bool is_confirmed; - // ConfirmationTime_t confirmation_time; - // bool verified; - // } TransactionDetails_t; - open class TransactionDetails_t : Structure() { - - class ByValue : TransactionDetails_t(), Structure.ByValue - class ByReference : TransactionDetails_t(), Structure.ByReference - - @JvmField - var txid: String? = null - - @JvmField - var received: Long? = null - - @JvmField - var sent: Long? = null - - @JvmField - var fee: Long? = null - - @JvmField - var is_confirmed: Boolean? = null - - @JvmField - var confirmation_time: ConfirmationTime_t? = null - - @JvmField - var verified: Boolean? = null - - override fun getFieldOrder() = listOf("txid", "received", "sent", "fee", "is_confirmed", "confirmation_time", "verified") - } - - // typedef struct { - // - // TransactionDetails_t * ptr; - // - // size_t len; - // - // size_t cap; - // - // } Vec_TransactionDetails_t; - open class Vec_TransactionDetails_t : Structure() { - - class ByReference : Vec_TransactionDetails_t(), Structure.ByReference - class ByValue : Vec_TransactionDetails_t(), Structure.ByValue - - @JvmField - var ptr: TransactionDetails_t.ByReference? = null - - @JvmField - var len: NativeLong? = null - - @JvmField - var cap: NativeLong? = null - - override fun getFieldOrder() = listOf("ptr", "len", "cap") - } - - // typedef struct { - // - // Vec_TransactionDetails_t ok; - // - // FfiError_t err; - // - // } FfiResult_Vec_TransactionDetails_t; - open class FfiResult_Vec_TransactionDetails_t : Structure() { - - class ByValue : FfiResult_Vec_TransactionDetails_t(), Structure.ByValue - class ByReference : FfiResult_Vec_TransactionDetails_t(), Structure.ByReference - - @JvmField - var ok: Vec_TransactionDetails_t? = null - - @JvmField - var err: Short? = null - - override fun getFieldOrder() = listOf("ok", "err") - } - - // FfiResult_Vec_TransactionDetails_t list_transactions ( - // OpaqueWallet_t const * opaque_wallet); - fun list_transactions(opaque_wallet: OpaqueWallet_t): FfiResult_Vec_TransactionDetails_t.ByValue - - - // void free_vectxdetails_result ( - // FfiResult_Vec_TransactionDetails_t txdetails_result); - fun free_vectxdetails_result(txdetails_result: FfiResult_Vec_TransactionDetails_t.ByValue) -} diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt deleted file mode 100644 index 2d7ac46..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.bitcoindevkit.bdk.types - -import org.bitcoindevkit.bdk.FfiException -import org.bitcoindevkit.bdk.LibBase -import org.bitcoindevkit.bdk.LibJna -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -class StringResult constructor(private val ffiResultCharPtrT: LibJna.FfiResult_char_ptr_t.ByValue) : - LibBase() { - - private val log: Logger = LoggerFactory.getLogger(StringResult::class.java) - - fun value(): String { - val err = ffiResultCharPtrT.err!! - val ok = ffiResultCharPtrT.ok!! - when { - err > 0 -> { - throw FfiException(err) - } - else -> { - return ok - } - } - } - - protected fun finalize() { - libJna.free_string_result(ffiResultCharPtrT) - log.debug("$ffiResultCharPtrT freed") - } -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt deleted file mode 100644 index 1b4e6f8..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.bitcoindevkit.bdk.types - -import org.bitcoindevkit.bdk.FfiException -import org.bitcoindevkit.bdk.LibBase -import org.bitcoindevkit.bdk.LibJna -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -class UInt64Result constructor(private val ffiResultUint64T: LibJna.FfiResult_uint64_t.ByValue) : - LibBase() { - - private val log: Logger = LoggerFactory.getLogger(UInt64Result::class.java) - - fun value(): Long { - val err = ffiResultUint64T.err!! - val ok = ffiResultUint64T.ok!! - when { - err > 0 -> { - throw FfiException(err) - } - else -> { - return ok - } - } - } - - protected fun finalize() { - libJna.free_uint64_result(ffiResultUint64T) - log.debug("$ffiResultUint64T freed") - } -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt deleted file mode 100644 index c50b328..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.bitcoindevkit.bdk.types - -import org.bitcoindevkit.bdk.FfiException -import org.bitcoindevkit.bdk.LibBase -import org.bitcoindevkit.bdk.LibJna -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -class VoidResult constructor(private val ffiResultVoidT: LibJna.FfiResultVoid_t.ByValue) : - LibBase() { - - private val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) - - fun value(): Unit { - val err = ffiResultVoidT.err!! - - when { - err > 0 -> { - throw FfiException(err) - } - else -> { - return - } - } - } - - protected fun finalize() { - libJna.free_void_result(ffiResultVoidT) - log.debug("$ffiResultVoidT freed") - } -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt deleted file mode 100644 index 5a7f5db..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/BlockchainConfig.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.bitcoindevkit.bdk - -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -abstract class BlockchainConfig : LibBase() { - private val log: Logger = LoggerFactory.getLogger(BlockchainConfig::class.java) - abstract val blockchainConfigT: LibJna.BlockchainConfig_t - - protected fun finalize() { - libJna.free_blockchain_config(blockchainConfigT) - log.debug("$blockchainConfigT freed") - } -} - -class ElectrumConfig( - url: String, - socks5: String?, - retry: Short, - timeout: Short, - stopGap: Long, -) : BlockchainConfig() { - - private val log: Logger = LoggerFactory.getLogger(ElectrumConfig::class.java) - override val blockchainConfigT = libJna.new_electrum_config(url, socks5, retry, timeout, stopGap) -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/DatabaseConfig.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/DatabaseConfig.kt deleted file mode 100644 index f163ff0..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/DatabaseConfig.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.bitcoindevkit.bdk - -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -abstract class DatabaseConfig : LibBase() { - private val log: Logger = LoggerFactory.getLogger(DatabaseConfig::class.java) - abstract val databaseConfigT: LibJna.DatabaseConfig_t - - protected fun finalize() { - libJna.free_database_config(databaseConfigT) - log.debug("$databaseConfigT freed") - } -} - -class MemoryConfig : DatabaseConfig() { - - private val log: Logger = LoggerFactory.getLogger(MemoryConfig::class.java) - override val databaseConfigT = libJna.new_memory_config() -} - -class SledConfig(path: String, treeName: String) : DatabaseConfig() { - - private val log: Logger = LoggerFactory.getLogger(SledConfig::class.java) - override val databaseConfigT = libJna.new_sled_config(path, treeName) -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt deleted file mode 100644 index 8a005f3..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.bitcoindevkit.bdk.wallet - -import org.bitcoindevkit.bdk.FfiException -import org.bitcoindevkit.bdk.LibBase -import org.bitcoindevkit.bdk.LibJna -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -class VecLocalUtxoResult(private val ffiResultVecLocalUtxoT: LibJna.FfiResultVec_LocalUtxo_t.ByValue) : - LibBase() { - - private val log: Logger = LoggerFactory.getLogger(VecLocalUtxoResult::class.java) - - fun value(): Array { - val err = ffiResultVecLocalUtxoT.err!! - val ok = ffiResultVecLocalUtxoT.ok!! - when { - err > 0 -> { - throw FfiException(err) - } - else -> { - val first = ok.ptr!! - return first.toArray(ok.len!!.toInt()) as Array - } - } - } - - protected fun finalize() { - libJna.free_veclocalutxo_result(ffiResultVecLocalUtxoT) - log.debug("$ffiResultVecLocalUtxoT freed") - } -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt deleted file mode 100644 index abee9a2..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.bitcoindevkit.bdk.wallet - -import org.bitcoindevkit.bdk.FfiException -import org.bitcoindevkit.bdk.LibBase -import org.bitcoindevkit.bdk.LibJna -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -class VecTxDetailsResult(private val ffiResultVecTransactionDetailsT: LibJna.FfiResult_Vec_TransactionDetails_t.ByValue) : - LibBase() { - - private val log: Logger = LoggerFactory.getLogger(VecTxDetailsResult::class.java) - - fun value(): Array { - val err = ffiResultVecTransactionDetailsT.err!! - val ok = ffiResultVecTransactionDetailsT.ok!! - when { - err > 0 -> { - throw FfiException(err) - } - else -> { - val first = ok.ptr!! - return first.toArray(ok.len!!.toInt()) as Array - } - } - } - - protected fun finalize() { - libJna.free_vectxdetails_result(ffiResultVecTransactionDetailsT) - log.debug("$ffiResultVecTransactionDetailsT freed") - } -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt deleted file mode 100644 index 57db810..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt +++ /dev/null @@ -1,65 +0,0 @@ -package org.bitcoindevkit.bdk.wallet - -import org.bitcoindevkit.bdk.BlockchainConfig -import org.bitcoindevkit.bdk.DatabaseConfig -import org.bitcoindevkit.bdk.LibBase -import org.bitcoindevkit.bdk.LibJna -import org.bitcoindevkit.bdk.types.StringResult -import org.bitcoindevkit.bdk.types.UInt64Result -import org.bitcoindevkit.bdk.types.VoidResult -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -enum class Network { - Bitcoin, - Testnet, - Signet, - Regtest, -} - -class Wallet constructor( - descriptor: String, - changeDescriptor: String?, - network: Network, - blockchainConfig: BlockchainConfig, - databaseConfig: DatabaseConfig, -) : LibBase() { - - val log: Logger = LoggerFactory.getLogger(Wallet::class.java) - - private val walletResult = WalletResult( - libJna.new_wallet_result( - descriptor, - changeDescriptor, - network.toString().lowercase(), - blockchainConfig.blockchainConfigT, - databaseConfig.databaseConfigT - ) - ) - private val wallet = walletResult.value() - - fun sync() { - val voidResult = VoidResult(libJna.sync_wallet(wallet)) - return voidResult.value() - } - - fun getAddress(): String { - val stringResult = StringResult(libJna.new_address(wallet)) - return stringResult.value() - } - - fun listUnspent(): Array { - val vecLocalUtxoResult = VecLocalUtxoResult(libJna.list_unspent(wallet)) - return vecLocalUtxoResult.value() - } - - fun balance(): Long { - val longResult = UInt64Result(libJna.balance(wallet)) - return longResult.value() - } - - fun listTransactionDetails(): Array { - val vecTxDetailsResult = VecTxDetailsResult(libJna.list_transactions((wallet))) - return vecTxDetailsResult.value() - } -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt deleted file mode 100644 index c574f27..0000000 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.bitcoindevkit.bdk.wallet - -import org.bitcoindevkit.bdk.FfiException -import org.bitcoindevkit.bdk.LibBase -import org.bitcoindevkit.bdk.LibJna -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -class WalletResult constructor(private val ffiResultOpaqueWalletPtrT: LibJna.FfiResult_OpaqueWallet_ptr_t.ByValue) : - LibBase() { - - private val log: Logger = LoggerFactory.getLogger(WalletResult::class.java) - - fun value(): LibJna.OpaqueWallet_t { - val err = ffiResultOpaqueWalletPtrT.err!! - val ok = ffiResultOpaqueWalletPtrT.ok - when { - err > 0 -> { - throw FfiException(err) - } - else -> { - return ok!! - } - } - } - - protected fun finalize() { - libJna.free_wallet_result(ffiResultOpaqueWalletPtrT) - log.debug("$ffiResultOpaqueWalletPtrT freed") - } -} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt b/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt deleted file mode 100644 index ddb4fb7..0000000 --- a/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.bitcoindevkit.bdk - -import java.nio.file.Paths - -/** - * Library test, which will execute on linux host. - * - */ -class JvmLibTest : LibTest() { - - override fun getTestDataDir(): String { - //return Files.createTempDirectory("bdk-test").toString() - return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString() - } - -} diff --git a/bdk-kotlin/settings.gradle b/bdk-kotlin/settings.gradle deleted file mode 100644 index b44156b..0000000 --- a/bdk-kotlin/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -rootProject.name = 'bdk-kotlin' - -include ':jvm', ':android', ':test-fixtures' \ No newline at end of file diff --git a/bdk-kotlin/test-fixtures/build.gradle b/bdk-kotlin/test-fixtures/build.gradle deleted file mode 100644 index 2853edb..0000000 --- a/bdk-kotlin/test-fixtures/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - id 'org.jetbrains.kotlin.jvm' - id 'java-library' -} - -dependencies { - implementation platform('org.jetbrains.kotlin:kotlin-bom') - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "net.java.dev.jna:jna:5.8.0" - implementation(project(':jvm')) - implementation "junit:junit:4.13.2" - //implementation "org.mockito.kotlin:mockito-kotlin:3.2.0" - api "org.slf4j:slf4j-api:1.7.30" -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} \ No newline at end of file diff --git a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt deleted file mode 100644 index 24d9b20..0000000 --- a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt +++ /dev/null @@ -1,135 +0,0 @@ -package org.bitcoindevkit.bdk - -import org.bitcoindevkit.bdk.wallet.Network -import org.bitcoindevkit.bdk.wallet.Wallet -import org.junit.Assert.* -import org.junit.Test -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import java.io.File - -/** - * Library tests which will execute for jvm and android modules. - */ -abstract class LibTest : LibBase() { - - private val log: Logger = LoggerFactory.getLogger(LibTest::class.java) - - val name = "test_wallet" - val desc = - "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val change = - "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" - val network = Network.Testnet - - val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30, 100) - val databaseConfig = MemoryConfig() - - abstract fun getTestDataDir(): String - - fun cleanupTestDataDir() { - File(getTestDataDir()).deleteRecursively() - } - - @Test - fun walletResultError() { - val jnaException = assertThrows(FfiException::class.java) { - Wallet("bad", "bad", network, blockchainConfig, databaseConfig) - } - assertEquals(jnaException.err, FfiError.Descriptor) - } - -// @Test -// fun walletResultFinalize() { -// run { -// val desc = -// "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" -// val change = -// "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" -// -// val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30) -// val databaseConfig = MemoryConfig() -// val wallet = Wallet(desc, change, blockchainConfig, databaseConfig) -// wallet.sync() -// assertNotNull(wallet.getAddress()) -// } -// System.gc() -// Thread.sleep(2000) -// // The only way to verify wallets freed is by checking the log -// } - - @Test - fun walletSync() { - val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30, 100) - val testDataDir = getTestDataDir() - // log.debug("testDataDir = $testDataDir") - val databaseConfig = SledConfig(testDataDir, "steve-test") - val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig) - wallet.sync() - cleanupTestDataDir() - } - - @Test - fun walletNewAddress() { - val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig) - val address = wallet.getAddress() - assertNotNull(address) - // log.debug("address created from kotlin: $address") - assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") - } - - @Test - fun walletUnspent() { - val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig) - wallet.sync() - val unspent = wallet.listUnspent() - assertTrue(unspent.isNotEmpty()) - - unspent.iterator().forEach { - //log.debug("unspent.outpoint.txid: ${it.outpoint!!.txid}") - assertNotNull(it.outpoint?.txid) - //log.debug("unspent.outpoint.vout: ${it.outpoint?.vout}") - assertTrue(it.outpoint?.vout!! >= 0) - //log.debug("unspent.txout.value: ${it.txout?.value}") - assertTrue(it.txout?.value!! > 0) - //log.debug("unspent.txout.script_pubkey: ${it.txout?.script_pubkey}") - assertNotNull(it.txout?.script_pubkey) - //log.debug("unspent.keychain: ${it.keychain}") - assertTrue(it.keychain!! >= 0) - } - } - - @Test - fun walletBalance() { - val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig) - wallet.sync() - val balance = wallet.balance() - //log.debug("balance from kotlin: $balance") - assertTrue(balance > 0) - } - - @Test - fun walletTxDetails() { - val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig) - wallet.sync() - val txDetails = wallet.listTransactionDetails() - assertTrue(txDetails.isNotEmpty()) - - txDetails.iterator().forEach { - //log.debug("txDetails.txid: ${it.txid}") - assertNotNull(it.txid) - //log.debug("txDetails.received: ${it.received}") - //log.debug("txDetails.sent: ${it.sent}") - assertTrue(it.received!! > 0 || it.sent!! > 0) - //log.debug("txDetails.fee: ${it.fee}") - assertTrue(it.fee!! > 0) - //log.debug("txDetails.is_confirmed: ${it.is_confirmed}") - assertTrue(it.is_confirmed!!) - assertNotNull(it.confirmation_time!!) - //log.debug("txDetails.confirmation_time.timestamp: ${it.confirmation_time!!.timestamp}") - assertTrue(it.confirmation_time!!.timestamp!! > 0) - //log.debug("txDetails.confirmation_time.height: ${it.confirmation_time!!.height}") - assertTrue(it.confirmation_time!!.height!! > 0) - } - } -} diff --git a/bdk-swift/.gitignore b/bdk-swift/.gitignore deleted file mode 100644 index bb460e7..0000000 --- a/bdk-swift/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.DS_Store -/.build -/Packages -/*.xcodeproj -xcuserdata/ -DerivedData/ -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/bdk-swift/bdk.swift/.gitignore b/bdk-swift/bdk.swift/.gitignore deleted file mode 100644 index bb460e7..0000000 --- a/bdk-swift/bdk.swift/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.DS_Store -/.build -/Packages -/*.xcodeproj -xcuserdata/ -DerivedData/ -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/bdk-swift/bdk.swift/Package.resolved b/bdk-swift/bdk.swift/Package.resolved deleted file mode 100644 index e1a027b..0000000 --- a/bdk-swift/bdk.swift/Package.resolved +++ /dev/null @@ -1,16 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "Clibbdkffi", - "repositoryURL": "/home/steve/git/notmandatory/Clibbdkffi", - "state": { - "branch": null, - "revision": "9c96e359a3b1e1d5c0db61125147f6ef929bf567", - "version": "0.1.0" - } - } - ] - }, - "version": 1 -} diff --git a/bdk-swift/bdk.swift/Package.swift b/bdk-swift/bdk.swift/Package.swift deleted file mode 100644 index 3a7987b..0000000 --- a/bdk-swift/bdk.swift/Package.swift +++ /dev/null @@ -1,28 +0,0 @@ -// swift-tools-version:5.5 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "bdk.swift", - products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. - .library( - name: "bdk.swift", - targets: ["bdk.swift"]), - ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - .package(url: "../../../Clibbdkffi", from: "0.1.0"), - ], - targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. - .target( - name: "bdk.swift", - dependencies: ["Clibbdkffi"]), - .testTarget( - name: "bdk.swiftTests", - dependencies: ["bdk.swift"]), - ] -) diff --git a/bdk-swift/bdk.swift/README.md b/bdk-swift/bdk.swift/README.md deleted file mode 100644 index a678238..0000000 --- a/bdk-swift/bdk.swift/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# bdk.swift - -To build: -``` -swift build -Xlinker -L../../target/debug -``` - -To test: -``` -swift test -Xlinker -L../../target/debug -``` diff --git a/bdk-swift/bdk.swift/Sources/bdk.swift/bdk_swift.swift b/bdk-swift/bdk.swift/Sources/bdk.swift/bdk_swift.swift deleted file mode 100644 index 3f0ecc0..0000000 --- a/bdk-swift/bdk.swift/Sources/bdk.swift/bdk_swift.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Clibbdkffi - -public struct bdk_swift { - public private(set) var text = "Hello, World!" - - public init() { - } -} diff --git a/bdk-swift/bdk.swift/Tests/bdk.swiftTests/bdk_swiftTests.swift b/bdk-swift/bdk.swift/Tests/bdk.swiftTests/bdk_swiftTests.swift deleted file mode 100644 index 8070cc6..0000000 --- a/bdk-swift/bdk.swift/Tests/bdk.swiftTests/bdk_swiftTests.swift +++ /dev/null @@ -1,35 +0,0 @@ -import XCTest -import Clibbdkffi - -@testable import bdk_swift - -final class bdk_swiftTests: XCTestCase { - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(bdk_swift().text, "Hello, World!") - let desc = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/0/*)"; - let change = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/1/*)"; - let net = "testnet"; - let blocks = "ssl://electrum.blockstream.info:60002"; - - let bc_config = new_electrum_config(blocks, nil, 5, 30, 100) - let db_config = new_memory_config() - - let wallet_result = new_wallet_result(desc,change,net,bc_config,db_config) - - free_blockchain_config(bc_config) - free_database_config(db_config) - - let wallet = wallet_result.ok - let sync_result = sync_wallet(wallet) - assert(sync_result.err == FFI_ERROR_NONE) - free_void_result(sync_result) - - let address1_result = new_address(wallet).ok - let address1 = String(cString: address1_result!, encoding: .utf8) - //print("address1 = \(address1!)") - assert(address1! == "tb1qh4ajvhz9nd76tqddnl99l89hx4dat33hrjauzw") - } -} diff --git a/targets/kotlin/build.gradle b/bindings/bdk-kotlin/build.gradle similarity index 100% rename from targets/kotlin/build.gradle rename to bindings/bdk-kotlin/build.gradle diff --git a/bdk-kotlin/gradle.properties b/bindings/bdk-kotlin/gradle.properties similarity index 100% rename from bdk-kotlin/gradle.properties rename to bindings/bdk-kotlin/gradle.properties diff --git a/bdk-kotlin/gradle/wrapper/gradle-wrapper.jar b/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from bdk-kotlin/gradle/wrapper/gradle-wrapper.jar rename to bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.jar diff --git a/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties b/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties similarity index 92% rename from bdk-kotlin/gradle/wrapper/gradle-wrapper.properties rename to bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties index 0f80bbf..05679dc 100644 --- a/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties +++ b/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/bdk-kotlin/gradlew b/bindings/bdk-kotlin/gradlew similarity index 100% rename from bdk-kotlin/gradlew rename to bindings/bdk-kotlin/gradlew diff --git a/bdk-kotlin/gradlew.bat b/bindings/bdk-kotlin/gradlew.bat similarity index 100% rename from bdk-kotlin/gradlew.bat rename to bindings/bdk-kotlin/gradlew.bat diff --git a/targets/kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt similarity index 100% rename from targets/kotlin/src/main/kotlin/uniffi/bdk/bdk.kt rename to bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt diff --git a/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt similarity index 90% rename from targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt rename to bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index 1e161cd..9c9dcfb 100644 --- a/targets/kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -19,7 +19,7 @@ class LibTest { val address = wallet.getNewAddress() println("address:" + address) assertNotNull(address) - // log.debug("address created from kotlin: $address") + // log.debug("address created from bdk-kotlin: $address") assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") } } diff --git a/build.sh b/build.sh index 2ccb2a7..32f6369 100755 --- a/build.sh +++ b/build.sh @@ -32,27 +32,27 @@ build_cc() { cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m } -## copy to bdk-kotlin +## copy to bdk-bdk-kotlin copy_lib_kotlin() { echo -n "Copy " case $OS in "Darwin") echo -n "darwin " - mkdir -p bdk-kotlin/jvm/src/main/resources/darwin-x86-64 - cp target/debug/libbdk_ffi.dylib bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + mkdir -p bdk-bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + cp target/debug/libbdk_ffi.dylib bdk-bdk-kotlin/jvm/src/main/resources/darwin-x86-64 ;; "Linux") echo -n "linux " - mkdir -p bdk-kotlin/jvm/src/main/resources/linux-x86-64 - cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/src/main/resources/linux-x86-64 + mkdir -p bdk-bdk-kotlin/jvm/src/main/resources/linux-x86-64 + cp target/debug/libbdk_ffi.so bdk-bdk-kotlin/jvm/src/main/resources/linux-x86-64 ;; esac echo "libs to kotlin sub-project" } -## bdk-kotlin jar +## bdk-bdk-kotlin jar build_kotlin() { - (cd bdk-kotlin && ./gradlew :jvm:build && ./gradlew :jvm:publishToMavenLocal) + (cd bdk-bdk-kotlin && ./gradlew :jvm:build && ./gradlew :jvm:publishToMavenLocal) } ## rust android @@ -69,27 +69,27 @@ build_android() { # IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" - mkdir -p bdk-kotlin/android/src/main/jniLibs/ bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-kotlin/android/src/main/jniLibs/x86 + mkdir -p bdk-bdk-kotlin/android/src/main/jniLibs/ bdk-bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-bdk-kotlin/android/src/main/jniLibs/x86 if echo $BUILD_TARGETS | grep "aarch64"; then CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android - cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/arm64-v8a + cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/arm64-v8a fi if echo $BUILD_TARGETS | grep "x86_64"; then CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android - cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86_64 + cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/x86_64 fi if echo $BUILD_TARGETS | grep "armv7"; then CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi - cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/armeabi-v7a + cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/armeabi-v7a fi if echo $BUILD_TARGETS | grep "i686"; then CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android - cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86 + cp target/i686-linux-android/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/x86 fi - # bdk-kotlin aar - (cd bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) + # bdk-bdk-kotlin aar + (cd bdk-bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) } OS=$(uname) diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h deleted file mode 100644 index ea76ad8..0000000 --- a/cc/bdk_ffi.h +++ /dev/null @@ -1,315 +0,0 @@ -/*! \file */ -/******************************************* - * * - * File auto-generated by `::safer_ffi`. * - * * - * Do not manually edit this file. * - * * - *******************************************/ - -#ifndef __RUST_BDK_FFI__ -#define __RUST_BDK_FFI__ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct DatabaseConfig DatabaseConfig_t; - -DatabaseConfig_t * new_memory_config (void); - -DatabaseConfig_t * new_sled_config ( - char const * path, - char const * tree_name); - -void free_database_config ( - DatabaseConfig_t * database_config); - - -#include -#include - -/** \remark Has the same ABI as `uint16_t` **/ -#ifdef DOXYGEN -typedef enum FfiError -#else -typedef uint16_t FfiError_t; enum -#endif -{ - /** . */ - FFI_ERROR_NONE, - /** . */ - FFI_ERROR_INVALID_U32_BYTES, - /** . */ - FFI_ERROR_GENERIC, - /** . */ - FFI_ERROR_SCRIPT_DOESNT_HAVE_ADDRESS_FORM, - /** . */ - FFI_ERROR_NO_RECIPIENTS, - /** . */ - FFI_ERROR_NO_UTXOS_SELECTED, - /** . */ - FFI_ERROR_OUTPUT_BELOW_DUST_LIMIT, - /** . */ - FFI_ERROR_INSUFFICIENT_FUNDS, - /** . */ - FFI_ERROR_BN_B_TOTAL_TRIES_EXCEEDED, - /** . */ - FFI_ERROR_BN_B_NO_EXACT_MATCH, - /** . */ - FFI_ERROR_UNKNOWN_UTXO, - /** . */ - FFI_ERROR_TRANSACTION_NOT_FOUND, - /** . */ - FFI_ERROR_TRANSACTION_CONFIRMED, - /** . */ - FFI_ERROR_IRREPLACEABLE_TRANSACTION, - /** . */ - FFI_ERROR_FEE_RATE_TOO_LOW, - /** . */ - FFI_ERROR_FEE_TOO_LOW, - /** . */ - FFI_ERROR_FEE_RATE_UNAVAILABLE, - /** . */ - FFI_ERROR_MISSING_KEY_ORIGIN, - /** . */ - FFI_ERROR_KEY, - /** . */ - FFI_ERROR_CHECKSUM_MISMATCH, - /** . */ - FFI_ERROR_SPENDING_POLICY_REQUIRED, - /** . */ - FFI_ERROR_INVALID_POLICY_PATH_ERROR, - /** . */ - FFI_ERROR_SIGNER, - /** . */ - FFI_ERROR_INVALID_NETWORK, - /** . */ - FFI_ERROR_INVALID_PROGRESS_VALUE, - /** . */ - FFI_ERROR_PROGRESS_UPDATE_ERROR, - /** . */ - FFI_ERROR_INVALID_OUTPOINT, - /** . */ - FFI_ERROR_DESCRIPTOR, - /** . */ - FFI_ERROR_ADDRESS_VALIDATOR, - /** . */ - FFI_ERROR_ENCODE, - /** . */ - FFI_ERROR_MINISCRIPT, - /** . */ - FFI_ERROR_BIP32, - /** . */ - FFI_ERROR_SECP256K1, - /** . */ - FFI_ERROR_JSON, - /** . */ - FFI_ERROR_HEX, - /** . */ - FFI_ERROR_PSBT, - /** . */ - FFI_ERROR_PSBT_PARSE, - /** . */ - FFI_ERROR_ELECTRUM, - /** . */ - FFI_ERROR_SLED, -} -#ifdef DOXYGEN -FfiError_t -#endif -; - -typedef struct { - - char * ok; - - FfiError_t err; - -} FfiResult_char_ptr_t; - -void free_string_result ( - FfiResult_char_ptr_t string_result); - -typedef struct { - - FfiError_t err; - -} FfiResultVoid_t; - -void free_void_result ( - FfiResultVoid_t void_result); - -typedef struct { - - uint64_t ok; - - FfiError_t err; - -} FfiResult_uint64_t; - -void free_uint64_result ( - FfiResult_uint64_t void_result); - -/** \brief - * Free a Rust-allocated string - */ -void free_string ( - char * string); - -typedef struct BlockchainConfig BlockchainConfig_t; - -typedef struct OpaqueWallet OpaqueWallet_t; - -typedef struct { - - OpaqueWallet_t * ok; - - FfiError_t err; - -} FfiResult_OpaqueWallet_ptr_t; - -FfiResult_OpaqueWallet_ptr_t new_wallet_result ( - char const * descriptor, - char const * change_descriptor, - char const * network, - BlockchainConfig_t const * blockchain_config, - DatabaseConfig_t const * database_config); - -void free_wallet_result ( - FfiResult_OpaqueWallet_ptr_t wallet_result); - -FfiResultVoid_t sync_wallet ( - OpaqueWallet_t const * opaque_wallet); - -FfiResult_char_ptr_t new_address ( - OpaqueWallet_t const * opaque_wallet); - -typedef struct { - - char * txid; - - uint32_t vout; - -} OutPoint_t; - -typedef struct { - - uint64_t value; - - char * script_pubkey; - -} TxOut_t; - -typedef struct { - - OutPoint_t outpoint; - - TxOut_t txout; - - uint16_t keychain; - -} LocalUtxo_t; - -/** \brief - * Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout - */ -typedef struct { - - LocalUtxo_t * ptr; - - size_t len; - - size_t cap; - -} Vec_LocalUtxo_t; - -typedef struct { - - Vec_LocalUtxo_t ok; - - FfiError_t err; - -} FfiResult_Vec_LocalUtxo_t; - -FfiResult_Vec_LocalUtxo_t list_unspent ( - OpaqueWallet_t const * opaque_wallet); - -void free_veclocalutxo_result ( - FfiResult_Vec_LocalUtxo_t unspent_result); - -FfiResult_uint64_t balance ( - OpaqueWallet_t const * opaque_wallet); - - -#include - -typedef struct { - - uint32_t height; - - uint64_t timestamp; - -} ConfirmationTime_t; - -typedef struct { - - char * txid; - - uint64_t received; - - uint64_t sent; - - int64_t fee; - - bool is_confirmed; - - ConfirmationTime_t confirmation_time; - - bool verified; - -} TransactionDetails_t; - -/** \brief - * Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout - */ -typedef struct { - - TransactionDetails_t * ptr; - - size_t len; - - size_t cap; - -} Vec_TransactionDetails_t; - -typedef struct { - - Vec_TransactionDetails_t ok; - - FfiError_t err; - -} FfiResult_Vec_TransactionDetails_t; - -FfiResult_Vec_TransactionDetails_t list_transactions ( - OpaqueWallet_t const * opaque_wallet); - -void free_vectxdetails_result ( - FfiResult_Vec_TransactionDetails_t txdetails_result); - -BlockchainConfig_t * new_electrum_config ( - char const * url, - char const * socks5, - int16_t retry, - int16_t timeout, - size_t stop_gap); - -void free_blockchain_config ( - BlockchainConfig_t * blockchain_config); - - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* __RUST_BDK_FFI__ */ diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c deleted file mode 100644 index cc10627..0000000 --- a/cc/bdk_ffi_test.c +++ /dev/null @@ -1,199 +0,0 @@ -#include -#include -#include -#include -#include "bdk_ffi.h" - -int main (int argc, char const * const argv[]) -{ - - // shared consts - char const *desc = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/0/*)"; - char const *change = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/1/*)"; - char const *net = "testnet"; - char const *blocks = "ssl://electrum.blockstream.info:60002"; - - // test new wallet error - { - BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100); - //DatabaseConfig_t *db_config = new_sled_config("/home/steve/.bdk", "test_wallet"); - DatabaseConfig_t *db_config = new_memory_config(); - - // new wallet with bad descriptor - FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result("bad","bad",net,bc_config,db_config); - assert(wallet_result.err == FFI_ERROR_DESCRIPTOR); - assert(wallet_result.ok == NULL); - - free_blockchain_config(bc_config); - free_database_config(db_config); - - free_wallet_result(wallet_result); - } - - // test new wallet - { - BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100); - DatabaseConfig_t *db_config = new_memory_config(); - - // new wallet - FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config); - // printf("wallet_result.err = %d\n", wallet_result.err)); - assert(wallet_result.err == FFI_ERROR_NONE); - assert(wallet_result.ok != NULL); - - free_blockchain_config(bc_config); - free_database_config(db_config); - - OpaqueWallet_t *wallet = wallet_result.ok; - - // sync wallet - FfiResultVoid_t sync_result = sync_wallet(wallet); - assert(sync_result.err == FFI_ERROR_NONE); - free_void_result(sync_result); - - // new address - FfiResult_char_ptr_t address1_result = new_address(wallet); - assert(address1_result.ok != NULL); - assert(address1_result.err == FFI_ERROR_NONE); - //printf("address1 = %s\n", address1_result.ok); - assert( 0 == strcmp(address1_result.ok,"tb1qh4ajvhz9nd76tqddnl99l89hx4dat33hrjauzw")); - free_string_result(address1_result); - - FfiResult_char_ptr_t address2_result = new_address(wallet); - assert(address2_result.ok != NULL); - assert(address2_result.err == FFI_ERROR_NONE); - //printf("address2 = %s\n", address2_result.ok); - assert( 0 == strcmp(address2_result.ok,"tb1qr7pu0pech43hcjrc4pzxcen0qkslj7xk7s5w3m")); - free_string_result(address2_result); - - // free_wallet - free_wallet_result(wallet_result); - - // verify free_wallet after free_wallet fails (core dumped) - //// free_wallet_result(wallet_result); - - // verify sync_wallet after free_wallet fails (core dumped) - //// FfiResultVoid_t sync_result2 = sync_wallet(wallet); - } - - // test get unspent utxos - { - BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100); - DatabaseConfig_t *db_config = new_memory_config(); - - // new wallet - FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config); - assert(wallet_result.err == FFI_ERROR_NONE); - assert(wallet_result.ok != NULL); - - free_blockchain_config(bc_config); - free_database_config(db_config); - - OpaqueWallet_t *wallet = wallet_result.ok; - - // sync wallet - FfiResultVoid_t sync_result = sync_wallet(wallet); - assert(sync_result.err == FFI_ERROR_NONE); - free_void_result(sync_result); - - // list unspent - FfiResult_Vec_LocalUtxo_t unspent_result = list_unspent(wallet); - assert(unspent_result.ok.len == 1); - assert(unspent_result.err == FFI_ERROR_NONE); - - LocalUtxo_t * unspent_ptr = unspent_result.ok.ptr; - for (int i = 0; i < unspent_result.ok.len; i++) { - // printf("%d: outpoint.txid: %s\n", i, unspent_ptr[i].outpoint.txid); - assert(unspent_ptr[i].outpoint.txid != NULL); - // printf("%d: outpoint.vout: %d\n", i, unspent_ptr[i].outpoint.vout); - assert(unspent_ptr[i].outpoint.vout >= 0); - // printf("%d: txout.value: %ld\n", i, unspent_ptr[i].txout.value); - assert(unspent_ptr[i].txout.value > 0); - // printf("%d: txout.script_pubkey: %s\n", i, unspent_ptr[i].txout.script_pubkey); - assert(unspent_ptr[i].txout.script_pubkey != NULL); - // printf("%d: keychain: %d\n", i, unspent_ptr[i].keychain); - assert(unspent_ptr[i].keychain >= 0); - } - - free_veclocalutxo_result(unspent_result); - free_wallet_result(wallet_result); - } - - // test balance - { - BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100); - DatabaseConfig_t *db_config = new_memory_config(); - - // new wallet - FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config); - assert(wallet_result.err == FFI_ERROR_NONE); - assert(wallet_result.ok != NULL); - - free_blockchain_config(bc_config); - free_database_config(db_config); - - OpaqueWallet_t *wallet = wallet_result.ok; - - // sync wallet - FfiResultVoid_t sync_result = sync_wallet(wallet); - assert(sync_result.err == FFI_ERROR_NONE); - free_void_result(sync_result); - - // get balance - FfiResult_uint64_t balance_result = balance(wallet); - //printf("balance.err = %d\n", (balance_result.err)); - assert(balance_result.err == FFI_ERROR_NONE); - //printf("balance.ok = %ld\n", balance_result.ok); - assert(balance_result.ok > 0); - - // free balance and wallet results - free_uint64_result(balance_result); - free_wallet_result(wallet_result); - } - - // test get transaction details - { - BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100); - DatabaseConfig_t *db_config = new_memory_config(); - - // new wallet - FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config); - assert(wallet_result.err == FFI_ERROR_NONE); - assert(wallet_result.ok != NULL); - - free_blockchain_config(bc_config); - free_database_config(db_config); - - OpaqueWallet_t *wallet = wallet_result.ok; - - // sync wallet - FfiResultVoid_t sync_result = sync_wallet(wallet); - assert(sync_result.err == FFI_ERROR_NONE); - free_void_result(sync_result); - - // list transactions - FfiResult_Vec_TransactionDetails_t txdetails_result = list_transactions(wallet); - assert(txdetails_result.ok.len > 0); - assert(txdetails_result.err == FFI_ERROR_NONE); - - TransactionDetails_t * txdetails_ptr = txdetails_result.ok.ptr; - for (int i = 0; i < txdetails_result.ok.len; i++) { - //printf("%d: txid: %s\n", i, txdetails_ptr[i].txid); - assert(txdetails_ptr[i].txid != NULL); - //printf("%d: timestamp: %ld\n", i, txdetails_ptr[i].timestamp); - assert(txdetails_ptr[i].is_confirmed); - //printf("%d: received: %ld\n", i, txdetails_ptr[i].received); - //printf("%d: sent: %ld\n", i, txdetails_ptr[i].sent); - assert(txdetails_ptr[i].received > 0 || txdetails_ptr[i].sent > 0); - //printf("%d: fees: %ld\n", i, txdetails_ptr[i].fees); - assert(txdetails_ptr[i].fee > 0); - //printf("%d: height: %d\n", i, txdetails_ptr[i].height); - assert(txdetails_ptr[i].confirmation_time.height > 0); - } - - free_vectxdetails_result(txdetails_result); - free_wallet_result(wallet_result); - } - - return EXIT_SUCCESS; -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 737ff62..0000000 --- a/src/error.rs +++ /dev/null @@ -1,134 +0,0 @@ -use bdk::Error; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum FfiError { - #[error("data store disconnected")] - None, - #[error("data store disconnected")] - InvalidU32Bytes, - #[error("data store disconnected")] - Generic, - #[error("data store disconnected")] - ScriptDoesntHaveAddressForm, - #[error("data store disconnected")] - NoRecipients, - #[error("data store disconnected")] - NoUtxosSelected, - #[error("data store disconnected")] - OutputBelowDustLimit, - #[error("data store disconnected")] - InsufficientFunds, - #[error("data store disconnected")] - BnBTotalTriesExceeded, - #[error("data store disconnected")] - BnBNoExactMatch, - #[error("data store disconnected")] - UnknownUtxo, - #[error("data store disconnected")] - TransactionNotFound, - #[error("data store disconnected")] - TransactionConfirmed, - #[error("data store disconnected")] - IrreplaceableTransaction, - #[error("data store disconnected")] - FeeRateTooLow, - #[error("data store disconnected")] - FeeTooLow, - #[error("data store disconnected")] - FeeRateUnavailable, - #[error("data store disconnected")] - MissingKeyOrigin, - #[error("data store disconnected")] - Key, - #[error("data store disconnected")] - ChecksumMismatch, - #[error("data store disconnected")] - SpendingPolicyRequired, - #[error("data store disconnected")] - InvalidPolicyPathError, - #[error("data store disconnected")] - Signer, - #[error("data store disconnected")] - InvalidNetwork, - #[error("data store disconnected")] - InvalidProgressValue, - #[error("data store disconnected")] - ProgressUpdateError, - #[error("data store disconnected")] - InvalidOutpoint, - #[error("data store disconnected")] - Descriptor, - #[error("data store disconnected")] - AddressValidator, - #[error("data store disconnected")] - Encode, - #[error("data store disconnected")] - Miniscript, - #[error("data store disconnected")] - Bip32, - #[error("data store disconnected")] - Secp256k1, - #[error("data store disconnected")] - Json, - #[error("data store disconnected")] - Hex, - #[error("data store disconnected")] - Psbt, - #[error("data store disconnected")] - PsbtParse, - #[error("data store disconnected")] - Electrum, - // Esplora, - // CompactFilters, - #[error("data store disconnected")] - Sled, -} - -impl From for FfiError { - fn from(error: bdk::Error) -> Self { - match error { - Error::InvalidU32Bytes(_) => FfiError::InvalidU32Bytes, - Error::Generic(_) => FfiError::Generic, - Error::ScriptDoesntHaveAddressForm => FfiError::ScriptDoesntHaveAddressForm, - Error::NoRecipients => FfiError::NoRecipients, - Error::NoUtxosSelected => FfiError::NoUtxosSelected, - Error::OutputBelowDustLimit(_) => FfiError::OutputBelowDustLimit, - Error::InsufficientFunds { .. } => FfiError::InsufficientFunds, - Error::BnBTotalTriesExceeded => FfiError::BnBTotalTriesExceeded, - Error::BnBNoExactMatch => FfiError::BnBNoExactMatch, - Error::UnknownUtxo => FfiError::UnknownUtxo, - Error::TransactionNotFound => FfiError::TransactionNotFound, - Error::TransactionConfirmed => FfiError::TransactionConfirmed, - Error::IrreplaceableTransaction => FfiError::IrreplaceableTransaction, - Error::FeeRateTooLow { .. } => FfiError::FeeRateTooLow, - Error::FeeTooLow { .. } => FfiError::FeeTooLow, - Error::FeeRateUnavailable => FfiError::FeeRateUnavailable, - Error::MissingKeyOrigin(_) => FfiError::MissingKeyOrigin, - Error::Key(_) => FfiError::Key, - Error::ChecksumMismatch => FfiError::ChecksumMismatch, - Error::SpendingPolicyRequired(_) => FfiError::SpendingPolicyRequired, - Error::InvalidPolicyPathError(_) => FfiError::InvalidPolicyPathError, - Error::Signer(_) => FfiError::Signer, - Error::InvalidNetwork { .. } => FfiError::InvalidNetwork, - Error::InvalidProgressValue(_) => FfiError::InvalidProgressValue, - Error::ProgressUpdateError => FfiError::ProgressUpdateError, - Error::InvalidOutpoint(_) => FfiError::InvalidOutpoint, - Error::Descriptor(_) => FfiError::Descriptor, - Error::AddressValidator(_) => FfiError::AddressValidator, - Error::Encode(_) => FfiError::Encode, - Error::Miniscript(_) => FfiError::Miniscript, - Error::Bip32(_) => FfiError::Bip32, - Error::Secp256k1(_) => FfiError::Secp256k1, - Error::Json(_) => FfiError::Json, - Error::Hex(_) => FfiError::Hex, - Error::Psbt(_) => FfiError::Psbt, - Error::PsbtParse(_) => FfiError::PsbtParse, - Error::Electrum(_) => FfiError::Electrum, - // Error::Esplora(_) => JniError::Esplora, - // Error::CompactFilters(_) => JniError::CompactFilters, - // Error::Rpc(_) => JniError::Rpc, - Error::Sled(_) => FfiError::Sled, - } - } -} diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 241cb42..0000000 --- a/src/types.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::error::FfiError; -use ::safer_ffi::prelude::*; -use safer_ffi::char_p::char_p_boxed; - -#[derive_ReprC] -#[repr(C)] -#[derive(Debug)] -pub struct FfiResult { - pub ok: T, - pub err: FfiError, -} - -#[derive_ReprC] -#[repr(C)] -#[derive(Debug)] -pub struct FfiResultVoid { - pub err: FfiError, -} - -#[ffi_export] -fn free_string_result(string_result: FfiResult) { - drop(string_result) -} - -#[ffi_export] -fn free_void_result(void_result: FfiResultVoid) { - drop(void_result) -} - -#[ffi_export] -fn free_uint64_result(void_result: FfiResult) { - drop(void_result) -} - -// TODO do we need this? remove? -/// Free a Rust-allocated string -#[ffi_export] -fn free_string(string: Option) { - drop(string) -} diff --git a/src/uniffi/bdk/bdk.kt b/src/uniffi/bdk/bdk.kt deleted file mode 100644 index eedd389..0000000 --- a/src/uniffi/bdk/bdk.kt +++ /dev/null @@ -1,602 +0,0 @@ -// This file was autogenerated by some hot garbage in the `uniffi` crate. -// Trust me, you don't want to mess with it! - -@file:Suppress("NAME_SHADOWING") - -package uniffi.bdk; - -// Common helper code. -// -// Ideally this would live in a separate .kt file where it can be unittested etc -// in isolation, and perhaps even published as a re-useable package. -// -// However, it's important that the detils of how this helper code works (e.g. the -// way that different builtin types are passed across the FFI) exactly match what's -// expected by the Rust code on the other side of the interface. In practice right -// now that means coming from the exact some version of `uniffi` that was used to -// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin -// helpers directly inline like we're doing here. - -import com.sun.jna.Library -import com.sun.jna.Native -import com.sun.jna.Pointer -import com.sun.jna.Structure -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.util.concurrent.atomic.AtomicLong -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicReference -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock - -// This is a helper for safely working with byte buffers returned from the Rust code. -// A rust-owned buffer is represented by its capacity, its current length, and a -// pointer to the underlying data. - -@Structure.FieldOrder("capacity", "len", "data") -open class RustBuffer : Structure() { - @JvmField var capacity: Int = 0 - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : RustBuffer(), Structure.ByValue - class ByReference : RustBuffer(), Structure.ByReference - - companion object { - internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_alloc(size, status) - } - - internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_free(buf, status) - } - - internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_reserve(buf, additional, status) - } - } - - @Suppress("TooGenericExceptionThrown") - fun asByteBuffer() = - this.data?.getByteBuffer(0, this.len.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - } -} - -// This is a helper for safely passing byte references into the rust code. -// It's not actually used at the moment, because there aren't many things that you -// can take a direct pointer to in the JVM, and if we're going to copy something -// then we might as well copy it into a `RustBuffer`. But it's here for API -// completeness. - -@Structure.FieldOrder("len", "data") -open class ForeignBytes : Structure() { - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : ForeignBytes(), Structure.ByValue -} - - -// A helper for structured writing of data into a `RustBuffer`. -// This is very similar to `java.nio.ByteBuffer` but it knows how to grow -// the underlying `RustBuffer` on demand. -// -// TODO: we should benchmark writing things into a `RustBuffer` versus building -// up a bytearray and then copying it across. - -class RustBufferBuilder() { - var rbuf = RustBuffer.ByValue() - var bbuf: ByteBuffer? = null - - init { - val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size - rbuf.writeField("len", 0) - this.setRustBuffer(rbuf) - } - - internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { - this.rbuf = rbuf - this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - it.position(rbuf.len) - } - } - - fun finalize() : RustBuffer.ByValue { - val rbuf = this.rbuf - // Ensure that the JVM-level field is written through to native memory - // before turning the buffer, in case its recipient uses it in a context - // JNA doesn't apply its automatic synchronization logic. - rbuf.writeField("len", this.bbuf!!.position()) - this.setRustBuffer(RustBuffer.ByValue()) - return rbuf - } - - fun discard() { - val rbuf = this.finalize() - RustBuffer.free(rbuf) - } - - internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) { - // TODO: this will perform two checks to ensure we're not overflowing the buffer: - // one here where we check if it needs to grow, and another when we call a write - // method on the ByteBuffer. It might be cheaper to use exception-driven control-flow - // here, trying the write and growing if it throws a `BufferOverflowException`. - // Benchmarking needed. - if (this.bbuf!!.position() + size > this.rbuf.capacity) { - rbuf.writeField("len", this.bbuf!!.position()) - this.setRustBuffer(RustBuffer.reserve(this.rbuf, size)) - } - write(this.bbuf!!) - } - - fun putByte(v: Byte) { - this.reserve(1) { bbuf -> - bbuf.put(v) - } - } - - fun putShort(v: Short) { - this.reserve(2) { bbuf -> - bbuf.putShort(v) - } - } - - fun putInt(v: Int) { - this.reserve(4) { bbuf -> - bbuf.putInt(v) - } - } - - fun putLong(v: Long) { - this.reserve(8) { bbuf -> - bbuf.putLong(v) - } - } - - fun putFloat(v: Float) { - this.reserve(4) { bbuf -> - bbuf.putFloat(v) - } - } - - fun putDouble(v: Double) { - this.reserve(8) { bbuf -> - bbuf.putDouble(v) - } - } - - fun put(v: ByteArray) { - this.reserve(v.size) { bbuf -> - bbuf.put(v) - } - } -} - -// Helpers for reading primitive data types from a bytebuffer. - -internal fun liftFromRustBuffer(rbuf: RustBuffer.ByValue, readItem: (ByteBuffer) -> T): T { - val buf = rbuf.asByteBuffer()!! - try { - val item = readItem(buf) - if (buf.hasRemaining()) { - throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") - } - return item - } finally { - RustBuffer.free(rbuf) - } -} - -internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { - // TODO: maybe we can calculate some sort of initial size hint? - val buf = RustBufferBuilder() - try { - writeItem(v, buf) - return buf.finalize() - } catch (e: Throwable) { - buf.discard() - throw e - } -} - -// For every type used in the interface, we provide helper methods for conveniently -// lifting and lowering that type from C-compatible data, and for reading and writing -// values of that type in a buffer. - - - - -internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String { - try { - val byteArr = ByteArray(rbuf.len) - rbuf.asByteBuffer()!!.get(byteArr) - return byteArr.toString(Charsets.UTF_8) - } finally { - RustBuffer.free(rbuf) - } -} - -internal fun String.Companion.read(buf: ByteBuffer): String { - val len = buf.getInt() - val byteArr = ByteArray(len) - buf.get(byteArr) - return byteArr.toString(Charsets.UTF_8) -} - -internal fun String.lower(): RustBuffer.ByValue { - val byteArr = this.toByteArray(Charsets.UTF_8) - // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us - // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. - val rbuf = RustBuffer.alloc(byteArr.size) - rbuf.asByteBuffer()!!.put(byteArr) - return rbuf -} - -internal fun String.write(buf: RustBufferBuilder) { - val byteArr = this.toByteArray(Charsets.UTF_8) - buf.putInt(byteArr.size) - buf.put(byteArr) -} - - - - - - - - - - -@Synchronized -fun findLibraryName(componentName: String): String { - val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride") - if (libOverride != null) { - return libOverride - } - return "uniffi_bdk" -} - -inline fun loadIndirect( - componentName: String -): Lib { - return Native.load(findLibraryName(componentName), Lib::class.java) -} - -// A JNA Library to expose the extern-C FFI definitions. -// This is an implementation detail which will be called internally by the public API. - -internal interface _UniFFILib : Library { - companion object { - internal val INSTANCE: _UniFFILib by lazy { - loadIndirect<_UniFFILib>(componentName = "bdk") - - - } - } - - fun ffi_bdk_ed55_OfflineWallet_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_ed55_OfflineWallet_new(descriptor: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun ffi_bdk_ed55_rustbuffer_alloc(size: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_ed55_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_ed55_rustbuffer_free(buf: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_ed55_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - -} - -// A handful of classes and functions to support the generated data structures. -// This would be a good candidate for isolating in its own ffi-support lib. - - - -// Interface implemented by anything that can contain an object reference. -// -// Such types expose a `destroy()` method that must be called to cleanly -// dispose of the contained objects. Failure to call this method may result -// in memory leaks. -// -// The easiest way to ensure this method is called is to use the `.use` -// helper method to execute a block and destroy the object at the end. -interface Disposable { - fun destroy() -} - -inline fun T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } - -// The base class for all UniFFI Object types. -// -// This class provides core operations for working with the Rust `Arc` pointer to -// the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an `FFIObject` is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an `FFIObject` instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so will -// leak the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// In the future we may be able to replace some of this with automatic finalization logic, such as using -// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is -// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also -// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], -// so there would still be some complexity here). -// -// Sigh...all of this for want of a robust finalization mechanism. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// -abstract class FFIObject( - protected val pointer: Pointer -): Disposable, AutoCloseable { - - val wasDestroyed = AtomicBoolean(false) - val callCounter = AtomicLong(1) - - open protected fun freeRustArcPtr() { - // To be overridden in subclasses. - } - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.pointer) - } finally { - // This decrement aways matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } -} - - - - - -// Public interface members begin here. -// Public facing enums -// Error definitions -@Structure.FieldOrder("code", "error_buf") -internal open class RustCallStatus : Structure() { - @JvmField var code: Int = 0 - @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - - fun isSuccess(): Boolean { - return code == 0 - } - - fun isError(): Boolean { - return code == 1 - } - - fun isPanic(): Boolean { - return code == 2 - } -} - -class InternalException(message: String) : Exception(message) - -// Each top-level error class has a companion object that can lift the error from the call status's rust buffer -interface CallStatusErrorHandler { - fun lift(error_buf: RustBuffer.ByValue): E; -} - -// Helpers for calling Rust -// In practice we usually need to be synchronized to call this safely, so it doesn't -// synchronize itself - -// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err -private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { - var status = RustCallStatus(); - val return_value = callback(status) - if (status.isSuccess()) { - return return_value - } else if (status.isError()) { - throw errorHandler.lift(status.error_buf) - } else if (status.isPanic()) { - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if (status.error_buf.len > 0) { - throw InternalException(String.lift(status.error_buf)) - } else { - throw InternalException("Rust panic") - } - } else { - throw InternalException("Unknown rust call status: $status.code") - } -} - -// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR -object NullCallStatusErrorHandler: CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): InternalException { - RustBuffer.free(error_buf) - return InternalException("Unexpected CALL_ERROR") - } -} - -// Call a rust function that returns a plain value -private inline fun rustCall(callback: (RustCallStatus) -> U): U { - return rustCallWithError(NullCallStatusErrorHandler, callback); -} - -// Public facing records - -// Namespace functions - - -// Objects - - -public interface OfflineWalletInterface { - -} - - -class OfflineWallet( - pointer: Pointer -) : FFIObject(pointer), OfflineWalletInterface { - constructor(descriptor: String ) : - this( - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_ed55_OfflineWallet_new(descriptor.lower() ,status) -}) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { - rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_ed55_OfflineWallet_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - - - companion object { - internal fun lift(ptr: Pointer): OfflineWallet { - return OfflineWallet(ptr) - } - - internal fun read(buf: ByteBuffer): OfflineWallet { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return OfflineWallet.lift(Pointer(buf.getLong())) - } - - - } -} - - -// Callback Interfaces - - diff --git a/src/wallet/blockchain.rs b/src/wallet/blockchain.rs deleted file mode 100644 index 50742f8..0000000 --- a/src/wallet/blockchain.rs +++ /dev/null @@ -1,106 +0,0 @@ -use ::safer_ffi::prelude::*; -use bdk::blockchain::{AnyBlockchainConfig, ElectrumBlockchainConfig}; -use safer_ffi::boxed::Box; -use safer_ffi::char_p::char_p_ref; - -#[derive_ReprC] -#[ReprC::opaque] -#[derive(Debug)] -pub struct BlockchainConfig { - pub raw: AnyBlockchainConfig, -} - -#[ffi_export] -fn new_electrum_config( - url: char_p_ref, - socks5: Option, - retry: i16, - timeout: i16, - stop_gap: usize, -) -> Box { - let url = url.to_string(); - let socks5 = socks5.map(|s| s.to_string()); - let retry = short_to_u8(retry); - let timeout = short_to_optional_u8(timeout); - - let electrum_config = AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { - url, - socks5, - retry, - timeout, - stop_gap, - }); - Box::new(BlockchainConfig { - raw: electrum_config, - }) -} - -#[ffi_export] -fn free_blockchain_config(blockchain_config: Box) { - drop(blockchain_config); -} - -// TODO compact_filter rocksdb not compiling on android, switch to sqlite? -//#[derive_ReprC] -//#[repr(C)] -//#[derive(Debug)] -//pub struct BitcoinPeerConfig { -// pub address: char_p_boxed, -// pub socks5: Option, -// pub socks5_credentials: Option>>, -//} -// -//impl From<&BitcoinPeerConfig> for BdkBitcoinPeerConfig { -// fn from(config: &BitcoinPeerConfig) -> Self { -// let address = config.address.to_string(); -// let socks5 = config.socks5.as_ref().map(|p| p.to_string()); -// let socks5_credentials = config -// .socks5_credentials.as_ref() -// .map(|c| (c._0.to_string(), c._1.to_string())); -// -// BdkBitcoinPeerConfig { -// address, -// socks5: socks5, -// socks5_credentials: socks5_credentials, -// } -// } -//} -// -// -//#[ffi_export] -//fn new_compact_filters_config<'lt>( -// peers: c_slice::Ref<'lt, BitcoinPeerConfig>, -// network: char_p_ref, -// storage_dir: char_p_ref, -// skip_blocks: usize, -//) -> Box { -// let peers = peers.iter().map(|p| p.into()).collect(); -// let network = Network::from_str(network.to_str()).unwrap(); -// let storage_dir = storage_dir.to_string(); -// let skip_blocks = Some(skip_blocks); -// let cf_config = AnyBlockchainConfig::CompactFilters(CompactFiltersBlockchainConfig { -// peers, -// network, -// storage_dir, -// skip_blocks, -// }); -// Box::new(BlockchainConfig { raw: cf_config }) -//} - -// utility functions - -fn short_to_optional_u8(short: i16) -> Option { - if short < 0 { - None - } else { - Some(short_to_u8(short)) - } -} - -fn short_to_u8(short: i16) -> u8 { - if short < 0 { - u8::MIN - } else { - u8::try_from(short).unwrap_or(u8::MAX) - } -} diff --git a/src/wallet/database.rs b/src/wallet/database.rs deleted file mode 100644 index 76dc333..0000000 --- a/src/wallet/database.rs +++ /dev/null @@ -1,32 +0,0 @@ -use ::safer_ffi::prelude::*; -use bdk::database::any::SledDbConfiguration; -use bdk::database::AnyDatabaseConfig; -use safer_ffi::boxed::Box; -use safer_ffi::char_p::char_p_ref; - -#[derive_ReprC] -#[ReprC::opaque] -#[derive(Debug)] -pub struct DatabaseConfig { - pub raw: AnyDatabaseConfig, -} - -#[ffi_export] -fn new_memory_config() -> Box { - let memory_config = AnyDatabaseConfig::Memory(()); - Box::new(DatabaseConfig { raw: memory_config }) -} - -#[ffi_export] -fn new_sled_config(path: char_p_ref, tree_name: char_p_ref) -> Box { - let path = path.to_string(); - let tree_name = tree_name.to_string(); - - let sled_config = AnyDatabaseConfig::Sled(SledDbConfiguration { path, tree_name }); - Box::new(DatabaseConfig { raw: sled_config }) -} - -#[ffi_export] -fn free_database_config(database_config: Box) { - drop(database_config); -} diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs deleted file mode 100644 index 4637efc..0000000 --- a/src/wallet/mod.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::convert::TryFrom; -use std::ffi::CString; - -use ::safer_ffi::prelude::*; -use bdk::blockchain::{log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain}; -use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase}; -use bdk::wallet::AddressIndex::New; -use bdk::{Error, Wallet}; -use safer_ffi::boxed::Box; -use safer_ffi::char_p::{char_p_boxed, char_p_ref}; - -use blockchain::BlockchainConfig; -use database::DatabaseConfig; - -use crate::error::FfiError; -use crate::types::{FfiResult, FfiResultVoid}; -use crate::wallet::transaction::{LocalUtxo, TransactionDetails}; -use bdk::bitcoin::Network; -use std::str::FromStr; - -mod blockchain; -mod database; -mod transaction; - -// create a new wallet - -#[derive_ReprC] -#[ReprC::opaque] -pub struct OpaqueWallet { - raw: Wallet, -} - -#[ffi_export] -fn new_wallet_result( - descriptor: char_p_ref, - change_descriptor: Option, - network: char_p_ref, - blockchain_config: &BlockchainConfig, - database_config: &DatabaseConfig, -) -> FfiResult>> { - let descriptor = descriptor.to_string(); - let change_descriptor = change_descriptor.map(|s| s.to_string()); - let net = Network::from_str(network.to_str()).expect("Network name"); - let bc_config = &blockchain_config.raw; - let db_config = &database_config.raw; - let wallet_result = new_wallet(descriptor, change_descriptor, net, bc_config, db_config); - - match wallet_result { - Ok(w) => FfiResult { - ok: Some(Box::new(OpaqueWallet { raw: w })), - err: FfiError::None, - }, - Err(e) => FfiResult { - ok: None, - err: FfiError::from(&e), - }, - } -} - -fn new_wallet( - descriptor: String, - change_descriptor: Option, - network: Network, - blockchain_config: &AnyBlockchainConfig, - database_config: &AnyDatabaseConfig, -) -> Result, Error> { - let client = AnyBlockchain::from_config(blockchain_config)?; - let database = AnyDatabase::from_config(database_config)?; - - let descriptor: &str = descriptor.as_str(); - let change_descriptor: Option<&str> = change_descriptor.as_deref(); - - Wallet::new(descriptor, change_descriptor, network, database, client) -} - -#[ffi_export] -fn free_wallet_result(wallet_result: FfiResult>>) { - drop(wallet_result); -} - -// wallet operations - -#[ffi_export] -fn sync_wallet(opaque_wallet: &OpaqueWallet) -> FfiResultVoid { - let int_result = opaque_wallet.raw.sync(log_progress(), Some(100)); - match int_result { - Ok(_v) => FfiResultVoid { - err: FfiError::None, - }, - Err(e) => FfiResultVoid { - err: FfiError::from(&e), - }, - } -} - -#[ffi_export] -fn new_address(opaque_wallet: &OpaqueWallet) -> FfiResult { - let new_address = opaque_wallet.raw.get_address(New); - let string_result = new_address.map(|a| a.to_string()); - match string_result { - Ok(a) => FfiResult { - ok: char_p_boxed::try_from(a).unwrap(), - err: FfiError::None, - }, - Err(e) => FfiResult { - ok: char_p_boxed::from(CString::default()), - err: FfiError::from(&e), - }, - } -} - -#[ffi_export] -fn list_unspent(opaque_wallet: &OpaqueWallet) -> FfiResult> { - let unspent_result = opaque_wallet.raw.list_unspent(); - - match unspent_result { - Ok(v) => FfiResult { - ok: { - let ve: Vec = v.iter().map(|lu| LocalUtxo::from(lu)).collect(); - repr_c::Vec::from(ve) - }, - err: FfiError::None, - }, - Err(e) => FfiResult { - ok: repr_c::Vec::EMPTY, - err: FfiError::from(&e), - }, - } -} - -#[ffi_export] -fn free_veclocalutxo_result(unspent_result: FfiResult>) { - drop(unspent_result) -} - -#[ffi_export] -fn balance(opaque_wallet: &OpaqueWallet) -> FfiResult { - let balance_result = opaque_wallet.raw.get_balance(); - - match balance_result { - Ok(b) => FfiResult { - ok: b, - err: FfiError::None, - }, - Err(e) => FfiResult { - ok: u64::MIN, - err: FfiError::from(&e), - }, - } -} - -#[ffi_export] -fn list_transactions(opaque_wallet: &OpaqueWallet) -> FfiResult> { - let transactions_result = opaque_wallet.raw.list_transactions(false); - - match transactions_result { - Ok(v) => FfiResult { - ok: { - let ve: Vec = - v.iter().map(|t| TransactionDetails::from(t)).collect(); - repr_c::Vec::from(ve) - }, - err: FfiError::None, - }, - Err(e) => FfiResult { - ok: repr_c::Vec::EMPTY, - err: FfiError::from(&e), - }, - } -} - -#[ffi_export] -fn free_vectxdetails_result(txdetails_result: FfiResult>) { - drop(txdetails_result) -} diff --git a/src/wallet/transaction.rs b/src/wallet/transaction.rs deleted file mode 100644 index 5c2194c..0000000 --- a/src/wallet/transaction.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::convert::TryFrom; - -use ::safer_ffi::prelude::*; -use safer_ffi::char_p::char_p_boxed; - -// Non-opaque returned values - -#[derive_ReprC] -#[repr(C)] -#[derive(Debug, Clone)] -pub struct TransactionDetails { - // TODO Optional transaction - // pub transaction: Option, - /// Transaction id - pub txid: char_p_boxed, - /// Received value (sats) - pub received: u64, - /// Sent value (sats) - pub sent: u64, - /// Fee value (sats) if known, -1 if unknown, based on backend - pub fee: i64, - /// true if confirmed - pub is_confirmed: bool, - /// Confirmed in block height - pub confirmation_time: ConfirmationTime, - /// Whether the tx has been verified against the consensus rules - pub verified: bool, -} - -#[derive_ReprC] -#[repr(C)] -#[derive(Debug, Clone)] -pub struct ConfirmationTime { - /// confirmation block height, 0 if is_confirmed is false - pub height: u32, - /// confirmation block timestamp, 0 if is_confirmed is false - pub timestamp: u64, -} - -impl From<&bdk::TransactionDetails> for TransactionDetails { - fn from(op: &bdk::TransactionDetails) -> Self { - let fee = op.fee.map(|f| i64::try_from(f).unwrap()).unwrap_or(-1); - let confirmation_time = op - .confirmation_time - .as_ref() - .map(|c| ConfirmationTime { - height: c.height, - timestamp: c.timestamp, - }) - .unwrap_or(ConfirmationTime { - height: 0, - timestamp: 0, - }); - TransactionDetails { - txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(), - received: op.received, - sent: op.sent, - fee, - is_confirmed: op.confirmation_time.is_some(), - confirmation_time, - verified: op.verified, - } - } -} - -#[derive_ReprC] -#[repr(C)] -#[derive(Debug, Clone)] -pub struct OutPoint { - /// The referenced transaction's txid, as hex string - pub txid: char_p_boxed, - /// The index of the referenced output in its transaction's vout - pub vout: u32, -} - -impl From<&bdk::bitcoin::OutPoint> for OutPoint { - fn from(op: &bdk::bitcoin::OutPoint) -> Self { - OutPoint { - txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(), - vout: op.vout, - } - } -} - -#[derive_ReprC] -#[repr(C)] -#[derive(Debug, Clone)] -pub struct TxOut { - /// The value of the output, in satoshis - pub value: u64, - /// The script which must satisfy for the output to be spent, as hex string - pub script_pubkey: char_p_boxed, -} - -impl From<&bdk::bitcoin::TxOut> for TxOut { - fn from(to: &bdk::bitcoin::TxOut) -> Self { - TxOut { - value: to.value, - script_pubkey: char_p_boxed::try_from(to.script_pubkey.to_string()).unwrap(), - } - } -} - -#[derive_ReprC] -#[repr(C)] -#[derive(Debug, Clone)] -pub struct LocalUtxo { - /// Reference to a transaction output - pub outpoint: OutPoint, - /// Transaction output - pub txout: TxOut, - /// Type of keychain, as short 0 for "external" or 1 for "internal" - pub keychain: u16, -} - -impl From<&bdk::LocalUtxo> for LocalUtxo { - fn from(lu: &bdk::LocalUtxo) -> Self { - LocalUtxo { - outpoint: OutPoint::from(&lu.outpoint), - txout: TxOut::from(&lu.txout), - keychain: lu.keychain as u16, - } - } -} diff --git a/targets/kotlin/gradle.properties b/targets/kotlin/gradle.properties deleted file mode 100644 index 5976929..0000000 --- a/targets/kotlin/gradle.properties +++ /dev/null @@ -1,23 +0,0 @@ -# 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 -jna.debug_load=true -jna.debug_load.jna=true From b94620819ca197123041996ceeff45a205be81b5 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 12 Oct 2021 18:22:02 -0700 Subject: [PATCH 044/272] Update README, build.sh and test.sh, rust fmt --- .gitignore | 3 ++- README.md | 56 +++++++++++++++++++++++++++--------------------------- build.rs | 2 +- build.sh | 31 +++++++++++------------------- src/lib.rs | 33 +++++++++----------------------- test.sh | 34 +++++---------------------------- 6 files changed, 56 insertions(+), 103 deletions(-) diff --git a/.gitignore b/.gitignore index 2a8baf6..8fd477d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ wallet_db bdk_ffi_test local.properties *.log -*.dylib \ No newline at end of file +*.dylib +*.so \ No newline at end of file diff --git a/README.md b/README.md index e363d70..95133d9 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,44 @@ +# BDK UniFFI Language Bindings -UniFFI +## Setup Android build environment -1. cargo install uniffi_bindgen -2. cargo build -3. uniffi-bindgen generate --no-format --out-dir bindings/bdk-kotlin/src/main/kotlin src/bdk.udl --language kotlin -4. cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/src/main/resources/darwin-x86-64 -5. cd bindings/bdk-kotlin; gradle build -Djna.debug_load=true -Djna.debug_load.jna + 1. Add Android rust targets + ```sh + rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android + ``` -Setup Android build environment + 2. Set ANDROID_NDK_HOME + + ```sh + export ANDROID_NDK_HOME=/home//Android/Sdk/ndk/ + ``` -1. Add Android rust targets +## Setup Swift build environment -```sh -rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android -``` + 1. install Swift, see ["Download Swift"](https://swift.org/download/) page + (or on Mac OSX install the latest Xcode) -2. Set ANDROID_NDK_HOME +## Setup UniFFI -```sh -export ANDROID_NDK_HOME=/home//Android/Sdk/ndk/ -``` + 1. `cargo install uniffi_bindgen` -Setup Swift build environment +## Adding new structs and functions -1. Install Swift, see ["Download Swift"](https://swift.org/download/) page +See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) -Adding new structs and functions +### For pass by value objects -1. Create C safe Rust structs and related functions using safer-ffi + 1. create new rust struct with only fields that are supported UniFFI types + 2. update mapping `bdk.udl` file with new `dictionary` -2. Test generated library and `bdk_ffi.h` file with c language tests in `cc/bdk_ffi_test.c` +### For pass by reference values -3. Use `build.sh` and `test.sh` to build c test program and verify functionality and - memory de-allocation via `valgrind` + 1. create wrapper rust struct/impl with only fields that are `Sync + Send` + 2. update mapping `bdk.udl` file with new `interface` -4. Update the kotlin native interface LibJna.kt in the `bdk-kotlin` `jvm` module to match `bdk_ffi.h` +### Build and test -5. Create kotlin wrapper classes and interfaces as needed - -6. Add tests to `bdk-kotlin` `test-fixtures` module - -7. Use `build.sh` and `test.sh` to build and test `bdk-kotlin` `jvm` and `android` modules \ No newline at end of file + 1. Use `build.sh` script (TODO do it all in build.rs instead) + 2. Create tests in `bindings/bdk-kotlin` and/or `bindings/bdk-swift` + 3. Use `test.sh` to run all bindings tests \ No newline at end of file diff --git a/build.rs b/build.rs index 9213f51..153077f 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,3 @@ fn main() { uniffi_build::generate_scaffolding("src/bdk.udl").unwrap(); -} \ No newline at end of file +} diff --git a/build.sh b/build.sh index 32f6369..783e847 100755 --- a/build.sh +++ b/build.sh @@ -7,29 +7,21 @@ set -eo pipefail help() { # Display Help - echo "Build bdk-ffi and related libraries." + echo "Build bdk-uniffi and related libraries." echo - echo "Syntax: build [-a|h|k]" + echo "Syntax: build [-h|k]" echo "options:" - echo "-a Android aar." echo "-h Print this Help." - echo "-k JVM jar." + echo "-k Kotlin." echo } ## rust build_rust() { - echo "Build Rust library and C headers" + echo "Build Rust library" cargo fmt cargo build - cargo test --features c-headers -- generate_headers -} - -## cc -build_cc() { - echo "Build C test library" - export LD_LIBRARY_PATH=`pwd`/target/debug - cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m + cargo test } ## copy to bdk-bdk-kotlin @@ -38,13 +30,13 @@ copy_lib_kotlin() { case $OS in "Darwin") echo -n "darwin " - mkdir -p bdk-bdk-kotlin/jvm/src/main/resources/darwin-x86-64 - cp target/debug/libbdk_ffi.dylib bdk-bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + mkdir -p bindings/bdk-kotlin/src/main/resources/darwin-x86-64 + cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/src/main/resources/darwin-x86-64 ;; "Linux") echo -n "linux " - mkdir -p bdk-bdk-kotlin/jvm/src/main/resources/linux-x86-64 - cp target/debug/libbdk_ffi.so bdk-bdk-kotlin/jvm/src/main/resources/linux-x86-64 + mkdir -p bindings/bdk-kotlin/src/main/resources/linux-x86-64 + cp target/debug/libuniffi_bdk.so bindings/bdk-kotlin/src/main/resources/linux-x86-64 ;; esac echo "libs to kotlin sub-project" @@ -52,7 +44,8 @@ copy_lib_kotlin() { ## bdk-bdk-kotlin jar build_kotlin() { - (cd bdk-bdk-kotlin && ./gradlew :jvm:build && ./gradlew :jvm:publishToMavenLocal) + uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-kotlin/src/main/kotlin --language kotlin + (cd bindings/bdk-kotlin && ./gradlew build) } ## rust android @@ -99,13 +92,11 @@ then help else build_rust - build_cc copy_lib_kotlin while [ -n "$1" ]; do # while loop starts case "$1" in -k) build_kotlin ;; - -a) build_android ;; -h) help ;; *) echo "Option $1 not recognized" ;; esac diff --git a/src/lib.rs b/src/lib.rs index 4002e1f..994a884 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,9 @@ -use bdk::Wallet; -use bdk::wallet::AddressIndex; -use bdk::database::MemoryDatabase; use bdk::bitcoin::Network; -// use crate::error::FfiError; -use std::sync::{RwLock, Mutex}; -use std::vec::Vec; -use bdk::database::BatchDatabase; use bdk::sled; use bdk::sled::Tree; -//mod error; -//mod types; -//mod wallet; +use bdk::wallet::AddressIndex; +use bdk::Wallet; +use std::sync::Mutex; uniffi_macros::include_scaffolding!("bdk"); @@ -24,25 +17,19 @@ impl OfflineWallet { let database = sled::open("testdb").unwrap(); let tree = database.open_tree("test").unwrap(); - let wallet = Wallet::new_offline( - &descriptor, - None, - Network::Regtest, - tree, - ).unwrap(); + let wallet = Wallet::new_offline(&descriptor, None, Network::Regtest, tree).unwrap(); OfflineWallet { - wallet: Mutex::new(wallet) + wallet: Mutex::new(wallet), } -// OfflineWallet { -// wallet: RwLock::new(Vec::new()) -// } + // OfflineWallet { + // wallet: RwLock::new(Vec::new()) + // } } fn get_new_address(&self) -> String { - self - .wallet + self.wallet .lock() .unwrap() .get_address(AddressIndex::New) @@ -53,5 +40,3 @@ impl OfflineWallet { } uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); - - diff --git a/test.sh b/test.sh index 45b497e..4f83359 100755 --- a/test.sh +++ b/test.sh @@ -7,54 +7,30 @@ set -eo pipefail help() { # Display Help - echo "Test bdk-ffi and related libraries." + echo "Test bdk-uniffi and related libraries." echo echo "Syntax: build [-a|h|k|v]" echo "options:" - echo "-a Android aar tests." echo "-h Print this Help." - echo "-k JVM jar tests." - echo "-v Valgrind tests." + echo "-k Kotlin tests." echo } -# rust -c_headers() { - cargo test --features c-headers -- generate_headers -} - -# cc -test_c() { - export LD_LIBRARY_PATH=`pwd`/target/debug - cc/bdk_ffi_test -} - -test_valgrind() { - valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test -} - test_kotlin() { - (cd bdk-kotlin && ./gradlew test) -} - -test_android() { - (cd bdk-kotlin && ./gradlew :android:connectedDebugAndroidTest) + (cd bindings/bdk-kotlin && ./gradlew test) } if [ $1 = "-h" ] then help else - c_headers - test_c - + cargo test + # optional tests while [ -n "$1" ]; do # while loop starts case "$1" in - -a) test_android ;; -h) help ;; -k) test_kotlin ;; - -v) test_valgrind ;; *) echo "Option $1 not recognized" ;; esac shift From 5459ad3e7e09b93352d05d34e2dfe2756a659a04 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Wed, 13 Oct 2021 13:42:55 +0530 Subject: [PATCH 045/272] Add editorconfig --- .editorconfig | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f9d7a83 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +insert_final_newline = true +indent_size = 2 +indent_style = space +end_of_line = lf From 9ea24949407fca76dce496d3510a76decfd9181c Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 14 Oct 2021 00:04:44 +0530 Subject: [PATCH 046/272] Use settings from rust-fmt --- .editorconfig | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index f9d7a83..f8d19dc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,26 @@ root = true [*] -insert_final_newline = true +charset = utf-8 +end_of_line = lf indent_size = 2 indent_style = space -end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.rs] +indent_size = 4 + +[*.kt] +indent_size = 4 + +[tests/**/*.rs] +charset = utf-8 +end_of_line = unset +indent_size = unset +indent_style = unset +trim_trailing_whitespace = unset +insert_final_newline = unset From da24bb2bfc6c5113e3bcb693ec47dec2c6f3a66d Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 14 Oct 2021 00:05:04 +0530 Subject: [PATCH 047/272] Ignore more files mac-specific local sled database --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8fd477d..fbdc479 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ bdk_ffi_test local.properties *.log *.dylib -*.so \ No newline at end of file +*.so +.DS_Store +testdb From a8a2de9d244db41fd2b1f7b4c07d0ae9ead2f7f0 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 14 Oct 2021 00:05:29 +0530 Subject: [PATCH 048/272] Stop running gradle build which also runs tests --- build.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/build.sh b/build.sh index 783e847..1f457f6 100755 --- a/build.sh +++ b/build.sh @@ -45,7 +45,6 @@ copy_lib_kotlin() { ## bdk-bdk-kotlin jar build_kotlin() { uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-kotlin/src/main/kotlin --language kotlin - (cd bindings/bdk-kotlin && ./gradlew build) } ## rust android From 07b35bb20fb701cc5672bd39920ae600ef67f015 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 14 Oct 2021 00:05:50 +0530 Subject: [PATCH 049/272] Add a little bit of error handling --- .../src/main/kotlin/uniffi/bdk/bdk.kt | 136 ++++++++++++++++-- src/bdk.udl | 45 +++++- src/lib.rs | 23 ++- 3 files changed, 177 insertions(+), 27 deletions(-) diff --git a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt index 2b99f03..a91f847 100644 --- a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_a71d_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_a71d_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_75ce_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_a71d_rustbuffer_reserve(buf, additional, status) } } @@ -249,6 +249,13 @@ internal fun String.write(buf: RustBufferBuilder) { + + + + + + + @Synchronized fun findLibraryName(componentName: String): String { val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride") @@ -276,31 +283,31 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_75ce_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_a71d_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_75ce_OfflineWallet_new(descriptor: RustBuffer.ByValue, + fun bdk_a71d_OfflineWallet_new(descriptor: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_75ce_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_a71d_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_75ce_rustbuffer_alloc(size: Int, + fun ffi_bdk_a71d_rustbuffer_alloc(size: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_75ce_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun ffi_bdk_a71d_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_75ce_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_a71d_rustbuffer_free(buf: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_75ce_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_a71d_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -500,6 +507,107 @@ interface CallStatusErrorHandler { fun lift(error_buf: RustBuffer.ByValue): E; } +// Error BdkError + +sealed class BdkException(message: String): Exception(message) { + // Each variant is a nested class + // Flat enums carries a string error message, so no special implementation is necessary. + class InvalidU32Bytes(message: String) : BdkException(message) + class Generic(message: String) : BdkException(message) + class ScriptDoesntHaveAddressForm(message: String) : BdkException(message) + class NoRecipients(message: String) : BdkException(message) + class NoUtxosSelected(message: String) : BdkException(message) + class OutputBelowDustLimit(message: String) : BdkException(message) + class InsufficientFunds(message: String) : BdkException(message) + class BnBTotalTriesExceeded(message: String) : BdkException(message) + class BnBNoExactMatch(message: String) : BdkException(message) + class UnknownUtxo(message: String) : BdkException(message) + class TransactionNotFound(message: String) : BdkException(message) + class TransactionConfirmed(message: String) : BdkException(message) + class IrreplaceableTransaction(message: String) : BdkException(message) + class FeeRateTooLow(message: String) : BdkException(message) + class FeeTooLow(message: String) : BdkException(message) + class FeeRateUnavailable(message: String) : BdkException(message) + class MissingKeyOrigin(message: String) : BdkException(message) + class Key(message: String) : BdkException(message) + class ChecksumMismatch(message: String) : BdkException(message) + class SpendingPolicyRequired(message: String) : BdkException(message) + class InvalidPolicyPathException(message: String) : BdkException(message) + class Signer(message: String) : BdkException(message) + class InvalidNetwork(message: String) : BdkException(message) + class InvalidProgressValue(message: String) : BdkException(message) + class ProgressUpdateException(message: String) : BdkException(message) + class InvalidOutpoint(message: String) : BdkException(message) + class Descriptor(message: String) : BdkException(message) + class AddressValidator(message: String) : BdkException(message) + class Encode(message: String) : BdkException(message) + class Miniscript(message: String) : BdkException(message) + class Bip32(message: String) : BdkException(message) + class Secp256k1(message: String) : BdkException(message) + class Json(message: String) : BdkException(message) + class Hex(message: String) : BdkException(message) + class Psbt(message: String) : BdkException(message) + class PsbtParse(message: String) : BdkException(message) + class Electrum(message: String) : BdkException(message) + class Sled(message: String) : BdkException(message) + + + companion object ErrorHandler : CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): BdkException { + return liftFromRustBuffer(error_buf) { error_buf -> read(error_buf) } + } + + fun read(error_buf: ByteBuffer): BdkException { + + return when(error_buf.getInt()) { + 1 -> BdkException.InvalidU32Bytes(String.read(error_buf)) + 2 -> BdkException.Generic(String.read(error_buf)) + 3 -> BdkException.ScriptDoesntHaveAddressForm(String.read(error_buf)) + 4 -> BdkException.NoRecipients(String.read(error_buf)) + 5 -> BdkException.NoUtxosSelected(String.read(error_buf)) + 6 -> BdkException.OutputBelowDustLimit(String.read(error_buf)) + 7 -> BdkException.InsufficientFunds(String.read(error_buf)) + 8 -> BdkException.BnBTotalTriesExceeded(String.read(error_buf)) + 9 -> BdkException.BnBNoExactMatch(String.read(error_buf)) + 10 -> BdkException.UnknownUtxo(String.read(error_buf)) + 11 -> BdkException.TransactionNotFound(String.read(error_buf)) + 12 -> BdkException.TransactionConfirmed(String.read(error_buf)) + 13 -> BdkException.IrreplaceableTransaction(String.read(error_buf)) + 14 -> BdkException.FeeRateTooLow(String.read(error_buf)) + 15 -> BdkException.FeeTooLow(String.read(error_buf)) + 16 -> BdkException.FeeRateUnavailable(String.read(error_buf)) + 17 -> BdkException.MissingKeyOrigin(String.read(error_buf)) + 18 -> BdkException.Key(String.read(error_buf)) + 19 -> BdkException.ChecksumMismatch(String.read(error_buf)) + 20 -> BdkException.SpendingPolicyRequired(String.read(error_buf)) + 21 -> BdkException.InvalidPolicyPathException(String.read(error_buf)) + 22 -> BdkException.Signer(String.read(error_buf)) + 23 -> BdkException.InvalidNetwork(String.read(error_buf)) + 24 -> BdkException.InvalidProgressValue(String.read(error_buf)) + 25 -> BdkException.ProgressUpdateException(String.read(error_buf)) + 26 -> BdkException.InvalidOutpoint(String.read(error_buf)) + 27 -> BdkException.Descriptor(String.read(error_buf)) + 28 -> BdkException.AddressValidator(String.read(error_buf)) + 29 -> BdkException.Encode(String.read(error_buf)) + 30 -> BdkException.Miniscript(String.read(error_buf)) + 31 -> BdkException.Bip32(String.read(error_buf)) + 32 -> BdkException.Secp256k1(String.read(error_buf)) + 33 -> BdkException.Json(String.read(error_buf)) + 34 -> BdkException.Hex(String.read(error_buf)) + 35 -> BdkException.Psbt(String.read(error_buf)) + 36 -> BdkException.PsbtParse(String.read(error_buf)) + 37 -> BdkException.Electrum(String.read(error_buf)) + 38 -> BdkException.Sled(String.read(error_buf)) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + } + } + + + +} + + // Helpers for calling Rust // In practice we usually need to be synchronized to call this safely, so it doesn't // synchronize itself @@ -558,8 +666,8 @@ class OfflineWallet( ) : FFIObject(pointer), OfflineWalletInterface { constructor(descriptor: String ) : this( - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_75ce_OfflineWallet_new(descriptor.lower() ,status) + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_a71d_OfflineWallet_new(descriptor.lower() ,status) }) /** @@ -572,7 +680,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_75ce_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_a71d_OfflineWallet_object_free(this.pointer, status) } } @@ -587,7 +695,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_75ce_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_a71d_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) diff --git a/src/bdk.udl b/src/bdk.udl index 5e30901..597833b 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -2,7 +2,50 @@ namespace bdk { }; +[Error] +enum BdkError { + "InvalidU32Bytes", + "Generic", + "ScriptDoesntHaveAddressForm", + "NoRecipients", + "NoUtxosSelected", + "OutputBelowDustLimit", + "InsufficientFunds", + "BnBTotalTriesExceeded", + "BnBNoExactMatch", + "UnknownUtxo", + "TransactionNotFound", + "TransactionConfirmed", + "IrreplaceableTransaction", + "FeeRateTooLow", + "FeeTooLow", + "FeeRateUnavailable", + "MissingKeyOrigin", + "Key", + "ChecksumMismatch", + "SpendingPolicyRequired", + "InvalidPolicyPathError", + "Signer", + "InvalidNetwork", + "InvalidProgressValue", + "ProgressUpdateError", + "InvalidOutpoint", + "Descriptor", + "AddressValidator", + "Encode", + "Miniscript", + "Bip32", + "Secp256k1", + "Json", + "Hex", + "Psbt", + "PsbtParse", + "Electrum", + "Sled", +}; + interface OfflineWallet { + [Throws=BdkError] constructor(string descriptor); string get_new_address(); -}; \ No newline at end of file +}; diff --git a/src/lib.rs b/src/lib.rs index 994a884..2b89c53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,30 +2,29 @@ use bdk::bitcoin::Network; use bdk::sled; use bdk::sled::Tree; use bdk::wallet::AddressIndex; +use bdk::Error; use bdk::Wallet; use std::sync::Mutex; +type BdkError = Error; + uniffi_macros::include_scaffolding!("bdk"); struct OfflineWallet { wallet: Mutex>, - //wallet: RwLock> } impl OfflineWallet { - fn new(descriptor: String) -> Self { + fn new(descriptor: String) -> Result { let database = sled::open("testdb").unwrap(); let tree = database.open_tree("test").unwrap(); - - let wallet = Wallet::new_offline(&descriptor, None, Network::Regtest, tree).unwrap(); - - OfflineWallet { - wallet: Mutex::new(wallet), - } - - // OfflineWallet { - // wallet: RwLock::new(Vec::new()) - // } + let wallet = Mutex::new(Wallet::new_offline( + &descriptor, + None, + Network::Regtest, + tree, + )?); + Ok(OfflineWallet { wallet }) } fn get_new_address(&self) -> String { From 1864a3e6bac5f08c32fc8e0b43ec3f0cb48b8cc3 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 14 Oct 2021 00:06:05 +0530 Subject: [PATCH 050/272] Add JNA debug_load to gradle script --- test.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test.sh b/test.sh index 4f83359..f098e4b 100755 --- a/test.sh +++ b/test.sh @@ -17,7 +17,7 @@ help() } test_kotlin() { - (cd bindings/bdk-kotlin && ./gradlew test) + (cd bindings/bdk-kotlin && ./gradlew test -Djna.debug_load=true) } if [ $1 = "-h" ] @@ -25,14 +25,14 @@ then help else cargo test - + # optional tests while [ -n "$1" ]; do # while loop starts - case "$1" in + case "$1" in -h) help ;; -k) test_kotlin ;; *) echo "Option $1 not recognized" ;; esac shift done -fi \ No newline at end of file +fi From 42582158bf9353eabb590e5b5592d481a03500c7 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 14 Oct 2021 00:06:17 +0530 Subject: [PATCH 051/272] Add test for exception being thrown --- .../src/test/kotlin/uniffi/bdk/LibTest.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index 9c9dcfb..ddedd1d 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -3,7 +3,6 @@ package uniffi.bdk import uniffi.bdk.OfflineWallet import org.junit.Assert.* import org.junit.Test -import java.io.File /** * Library tests which will execute for jvm and android modules. @@ -13,13 +12,16 @@ class LibTest { val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - @Test - fun walletNewAddress() { - val wallet = OfflineWallet(desc) - val address = wallet.getNewAddress() - println("address:" + address) - assertNotNull(address) - // log.debug("address created from bdk-kotlin: $address") - assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + // @Test + // fun walletNewAddress() { + // val wallet = OfflineWallet(desc) + // val address = wallet.getNewAddress() + // assertNotNull(address) + // assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + // } + + @Test(expected=BdkException.Descriptor::class) + fun invalidDescriptorExceptionIsThrown() { + OfflineWallet("invalid-descriptor") } } From a66e8eb8ed619b5178b45d9dcaa768a68bc83c58 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 14 Oct 2021 00:10:43 +0530 Subject: [PATCH 052/272] Add name to authors --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f6dc768..f5ddd3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "uniffi_bdk" version = "0.1.0" -authors = ["Steve Myers ", ""] +authors = ["Steve Myers ", "Sudarsan Balaji "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 23c17ca84174366dd7b60be1e3352bd4e4aab8c8 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 14 Oct 2021 03:53:22 +0530 Subject: [PATCH 053/272] Use a thread-safe MemoryDatabase --- Cargo.toml | 5 ++++- .../src/test/kotlin/uniffi/bdk/LibTest.kt | 14 +++++++------- src/lib.rs | 12 +++++------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5ddd3f..d59366f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,13 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -bdk = { version = "^0.11", features = ["all-keys"] } +bdk = { version = "^0.12.1-dev", features = ["all-keys"] } uniffi_macros = "0.14.0" uniffi = "0.14.0" thiserror = "1.0" [build-dependencies] uniffi_build = "0.14.0" + +[patch.crates-io] +bdk = { git = "https://github.com/artfuldev/bdk.git", branch = "use-send-and-sync-on-memory-database" } diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index ddedd1d..2c869a6 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -12,13 +12,13 @@ class LibTest { val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - // @Test - // fun walletNewAddress() { - // val wallet = OfflineWallet(desc) - // val address = wallet.getNewAddress() - // assertNotNull(address) - // assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") - // } + @Test + fun walletNewAddress() { + val wallet = OfflineWallet(desc) + val address = wallet.getNewAddress() + assertNotNull(address) + assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + } @Test(expected=BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { diff --git a/src/lib.rs b/src/lib.rs index 2b89c53..5d8cd69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,28 +1,26 @@ use bdk::bitcoin::Network; -use bdk::sled; -use bdk::sled::Tree; +use bdk::database::MemoryDatabase; use bdk::wallet::AddressIndex; use bdk::Error; use bdk::Wallet; + use std::sync::Mutex; type BdkError = Error; uniffi_macros::include_scaffolding!("bdk"); - struct OfflineWallet { - wallet: Mutex>, + wallet: Mutex>, } impl OfflineWallet { fn new(descriptor: String) -> Result { - let database = sled::open("testdb").unwrap(); - let tree = database.open_tree("test").unwrap(); + let database = MemoryDatabase::default(); let wallet = Mutex::new(Wallet::new_offline( &descriptor, None, Network::Regtest, - tree, + database, )?); Ok(OfflineWallet { wallet }) } From 6f01c38a71ce7fd2bf875d01a11a3aa67f7b33b6 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 14 Oct 2021 04:23:17 +0530 Subject: [PATCH 054/272] Allow using configs for database --- .../src/main/kotlin/uniffi/bdk/bdk.kt | 133 ++++++++++++++++-- .../src/test/kotlin/uniffi/bdk/LibTest.kt | 6 +- src/bdk.udl | 13 +- src/lib.rs | 19 ++- 4 files changed, 150 insertions(+), 21 deletions(-) diff --git a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt index a91f847..1398b9e 100644 --- a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_a71d_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_f91_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_a71d_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_f91_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_a71d_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_f91_rustbuffer_reserve(buf, additional, status) } } @@ -247,6 +247,18 @@ internal fun String.write(buf: RustBufferBuilder) { + + + + + + + + + + + + @@ -283,31 +295,31 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_a71d_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_f91_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_a71d_OfflineWallet_new(descriptor: RustBuffer.ByValue, + fun bdk_f91_OfflineWallet_new(descriptor: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_a71d_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_f91_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_a71d_rustbuffer_alloc(size: Int, + fun ffi_bdk_f91_rustbuffer_alloc(size: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_a71d_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun ffi_bdk_f91_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_a71d_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_f91_rustbuffer_free(buf: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_a71d_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_f91_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -481,6 +493,66 @@ abstract class FFIObject( // Public interface members begin here. // Public facing enums + + + + + + + + +sealed class DatabaseConfig { + + data class Memory( + val junk: String + ) : DatabaseConfig() + + data class Sled( + val configuration: SledDbConfiguration + ) : DatabaseConfig() + + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig { + return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): DatabaseConfig { + return when(buf.getInt()) { + 1 -> DatabaseConfig.Memory( + String.read(buf) + ) + 2 -> DatabaseConfig.Sled( + SledDbConfiguration.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is DatabaseConfig.Memory -> { + buf.putInt(1) + this.junk.write(buf) + + } + is DatabaseConfig.Sled -> { + buf.putInt(2) + this.configuration.write(buf) + + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + + + +} + // Error definitions @Structure.FieldOrder("code", "error_buf") internal open class RustCallStatus : Structure() { @@ -649,6 +721,39 @@ private inline fun rustCall(callback: (RustCallStatus) -> U): U { // Public facing records +data class SledDbConfiguration ( + var path: String, + var treeName: String +) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): SledDbConfiguration { + return liftFromRustBuffer(rbuf) { buf -> SledDbConfiguration.read(buf) } + } + + internal fun read(buf: ByteBuffer): SledDbConfiguration { + return SledDbConfiguration( + String.read(buf), + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.path.write(buf) + + this.treeName.write(buf) + + } + + + +} + + // Namespace functions @@ -664,10 +769,10 @@ public interface OfflineWalletInterface { class OfflineWallet( pointer: Pointer ) : FFIObject(pointer), OfflineWalletInterface { - constructor(descriptor: String ) : + constructor(descriptor: String, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_a71d_OfflineWallet_new(descriptor.lower() ,status) + _UniFFILib.INSTANCE.bdk_f91_OfflineWallet_new(descriptor.lower(), databaseConfig.lower() ,status) }) /** @@ -680,7 +785,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_a71d_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_f91_OfflineWallet_object_free(this.pointer, status) } } @@ -695,7 +800,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_a71d_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_f91_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index 2c869a6..c2bfec9 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -14,7 +14,8 @@ class LibTest { @Test fun walletNewAddress() { - val wallet = OfflineWallet(desc) + val config = DatabaseConfig.Memory("") + val wallet = OfflineWallet(desc, config) val address = wallet.getNewAddress() assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") @@ -22,6 +23,7 @@ class LibTest { @Test(expected=BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { - OfflineWallet("invalid-descriptor") + val config = DatabaseConfig.Memory("") + OfflineWallet("invalid-descriptor", config) } } diff --git a/src/bdk.udl b/src/bdk.udl index 597833b..e79ddcc 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -44,8 +44,19 @@ enum BdkError { "Sled", }; +dictionary SledDbConfiguration { + string path; + string tree_name; +}; + +[Enum] +interface DatabaseConfig { + Memory(string junk); + Sled(SledDbConfiguration configuration); +}; + interface OfflineWallet { [Throws=BdkError] - constructor(string descriptor); + constructor(string descriptor, DatabaseConfig database_config); string get_new_address(); }; diff --git a/src/lib.rs b/src/lib.rs index 5d8cd69..db824ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use bdk::bitcoin::Network; -use bdk::database::MemoryDatabase; +use bdk::database::any::{AnyDatabase, SledDbConfiguration}; +use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase}; use bdk::wallet::AddressIndex; use bdk::Error; use bdk::Wallet; @@ -8,14 +9,24 @@ use std::sync::Mutex; type BdkError = Error; +pub enum DatabaseConfig { + Memory { junk: String }, + Sled { configuration: SledDbConfiguration }, +} + uniffi_macros::include_scaffolding!("bdk"); + struct OfflineWallet { - wallet: Mutex>, + wallet: Mutex>, } impl OfflineWallet { - fn new(descriptor: String) -> Result { - let database = MemoryDatabase::default(); + fn new(descriptor: String, database_config: DatabaseConfig) -> Result { + let any_database_config = match database_config { + DatabaseConfig::Memory { .. } => AnyDatabaseConfig::Memory(()), + DatabaseConfig::Sled { configuration } => AnyDatabaseConfig::Sled(configuration), + }; + let database = AnyDatabase::from_config(&any_database_config)?; let wallet = Mutex::new(Wallet::new_offline( &descriptor, None, From 58ef298a42c53234ae7d562b881246604f7eed99 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 14 Oct 2021 04:29:50 +0530 Subject: [PATCH 055/272] Add a test for sled --- .../bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index c2bfec9..b64dc79 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -13,7 +13,7 @@ class LibTest { "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" @Test - fun walletNewAddress() { + fun memoryWalletNewAddress() { val config = DatabaseConfig.Memory("") val wallet = OfflineWallet(desc, config) val address = wallet.getNewAddress() @@ -26,4 +26,13 @@ class LibTest { val config = DatabaseConfig.Memory("") OfflineWallet("invalid-descriptor", config) } + + @Test + fun sledWalletNewAddress() { + val config = DatabaseConfig.Sled(SledDbConfiguration("/tmp/testdb", "testdb")) + val wallet = OfflineWallet(desc, config) + val address = wallet.getNewAddress() + assertNotNull(address) + assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + } } From fffb2e2cbcd15a906256875e49e37e2141ecf132 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 14 Oct 2021 10:58:16 -0700 Subject: [PATCH 056/272] Add Network enum as wallet constructor param --- README.md | 2 +- .../src/main/kotlin/uniffi/bdk/bdk.kt | 63 ++++++++++++++----- .../src/test/kotlin/uniffi/bdk/LibTest.kt | 6 +- src/bdk.udl | 9 ++- src/lib.rs | 17 +++-- 5 files changed, 69 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 95133d9..4ace128 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,4 @@ See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) 1. Use `build.sh` script (TODO do it all in build.rs instead) 2. Create tests in `bindings/bdk-kotlin` and/or `bindings/bdk-swift` - 3. Use `test.sh` to run all bindings tests \ No newline at end of file + 3. Use `test.sh` to run all bindings tests diff --git a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt index 1398b9e..14333b7 100644 --- a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f91_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_f470_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f91_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_f470_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f91_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_f470_rustbuffer_reserve(buf, additional, status) } } @@ -261,6 +261,12 @@ internal fun String.write(buf: RustBufferBuilder) { + + + + + + @@ -295,31 +301,31 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_f91_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_f470_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_f91_OfflineWallet_new(descriptor: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_f470_OfflineWallet_new(network: RustBuffer.ByValue,descriptor: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_f91_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_f470_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_f91_rustbuffer_alloc(size: Int, + fun ffi_bdk_f470_rustbuffer_alloc(size: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_f91_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun ffi_bdk_f470_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_f91_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_f470_rustbuffer_free(buf: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_f91_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_f470_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -498,6 +504,35 @@ abstract class FFIObject( +enum class Network { + BITCOIN,TESTNET,SIGNET,REGTEST; + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): Network { + return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) } + } + + internal fun read(buf: ByteBuffer) = + try { values()[buf.getInt() - 1] } + catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + buf.putInt(this.ordinal + 1) + } +} + + + + + + @@ -769,10 +804,10 @@ public interface OfflineWalletInterface { class OfflineWallet( pointer: Pointer ) : FFIObject(pointer), OfflineWalletInterface { - constructor(descriptor: String, databaseConfig: DatabaseConfig ) : + constructor(network: Network, descriptor: String, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_f91_OfflineWallet_new(descriptor.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_f470_OfflineWallet_new(network.lower(), descriptor.lower(), databaseConfig.lower() ,status) }) /** @@ -785,7 +820,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f91_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_f470_OfflineWallet_object_free(this.pointer, status) } } @@ -800,7 +835,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_f91_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_f470_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index b64dc79..20f9b36 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -15,7 +15,7 @@ class LibTest { @Test fun memoryWalletNewAddress() { val config = DatabaseConfig.Memory("") - val wallet = OfflineWallet(desc, config) + val wallet = OfflineWallet(Network.REGTEST, desc, config) val address = wallet.getNewAddress() assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") @@ -24,13 +24,13 @@ class LibTest { @Test(expected=BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { val config = DatabaseConfig.Memory("") - OfflineWallet("invalid-descriptor", config) + OfflineWallet(Network.REGTEST, "invalid-descriptor", config) } @Test fun sledWalletNewAddress() { val config = DatabaseConfig.Sled(SledDbConfiguration("/tmp/testdb", "testdb")) - val wallet = OfflineWallet(desc, config) + val wallet = OfflineWallet(Network.REGTEST, desc, config) val address = wallet.getNewAddress() assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") diff --git a/src/bdk.udl b/src/bdk.udl index e79ddcc..f87a7fe 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -44,6 +44,13 @@ enum BdkError { "Sled", }; +enum Network { + "Bitcoin", + "Testnet", + "Signet", + "Regtest", +}; + dictionary SledDbConfiguration { string path; string tree_name; @@ -57,6 +64,6 @@ interface DatabaseConfig { interface OfflineWallet { [Throws=BdkError] - constructor(string descriptor, DatabaseConfig database_config); + constructor(Network network, string descriptor, DatabaseConfig database_config); string get_new_address(); }; diff --git a/src/lib.rs b/src/lib.rs index db824ca..3d8b8b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,8 @@ use bdk::Wallet; use std::sync::Mutex; +uniffi_macros::include_scaffolding!("bdk"); + type BdkError = Error; pub enum DatabaseConfig { @@ -14,25 +16,22 @@ pub enum DatabaseConfig { Sled { configuration: SledDbConfiguration }, } -uniffi_macros::include_scaffolding!("bdk"); - struct OfflineWallet { wallet: Mutex>, } impl OfflineWallet { - fn new(descriptor: String, database_config: DatabaseConfig) -> Result { + fn new( + network: Network, + descriptor: String, + database_config: DatabaseConfig, + ) -> Result { let any_database_config = match database_config { DatabaseConfig::Memory { .. } => AnyDatabaseConfig::Memory(()), DatabaseConfig::Sled { configuration } => AnyDatabaseConfig::Sled(configuration), }; let database = AnyDatabase::from_config(&any_database_config)?; - let wallet = Mutex::new(Wallet::new_offline( - &descriptor, - None, - Network::Regtest, - database, - )?); + let wallet = Mutex::new(Wallet::new_offline(&descriptor, None, network, database)?); Ok(OfflineWallet { wallet }) } From 038c9ef23cdc2b0b45464c46f9d0bdaa5e07ba0a Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 14 Oct 2021 11:17:52 -0700 Subject: [PATCH 057/272] Change order of Network param --- .../src/main/kotlin/uniffi/bdk/bdk.kt | 28 +++++++++---------- .../src/test/kotlin/uniffi/bdk/LibTest.kt | 6 ++-- src/bdk.udl | 2 +- src/lib.rs | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt index 14333b7..6343671 100644 --- a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f470_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_146a_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f470_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_146a_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f470_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_146a_rustbuffer_reserve(buf, additional, status) } } @@ -301,31 +301,31 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_f470_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_146a_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_f470_OfflineWallet_new(network: RustBuffer.ByValue,descriptor: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_146a_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_f470_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_146a_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_f470_rustbuffer_alloc(size: Int, + fun ffi_bdk_146a_rustbuffer_alloc(size: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_f470_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun ffi_bdk_146a_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_f470_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_146a_rustbuffer_free(buf: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_f470_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_146a_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -804,10 +804,10 @@ public interface OfflineWalletInterface { class OfflineWallet( pointer: Pointer ) : FFIObject(pointer), OfflineWalletInterface { - constructor(network: Network, descriptor: String, databaseConfig: DatabaseConfig ) : + constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_f470_OfflineWallet_new(network.lower(), descriptor.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_146a_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -820,7 +820,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f470_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_146a_OfflineWallet_object_free(this.pointer, status) } } @@ -835,7 +835,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_f470_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_146a_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index 20f9b36..da33bfb 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -15,7 +15,7 @@ class LibTest { @Test fun memoryWalletNewAddress() { val config = DatabaseConfig.Memory("") - val wallet = OfflineWallet(Network.REGTEST, desc, config) + val wallet = OfflineWallet(desc, Network.REGTEST, config) val address = wallet.getNewAddress() assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") @@ -24,13 +24,13 @@ class LibTest { @Test(expected=BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { val config = DatabaseConfig.Memory("") - OfflineWallet(Network.REGTEST, "invalid-descriptor", config) + OfflineWallet("invalid-descriptor", Network.REGTEST, config) } @Test fun sledWalletNewAddress() { val config = DatabaseConfig.Sled(SledDbConfiguration("/tmp/testdb", "testdb")) - val wallet = OfflineWallet(Network.REGTEST, desc, config) + val wallet = OfflineWallet(desc, Network.REGTEST, config) val address = wallet.getNewAddress() assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") diff --git a/src/bdk.udl b/src/bdk.udl index f87a7fe..2ae60cc 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -64,6 +64,6 @@ interface DatabaseConfig { interface OfflineWallet { [Throws=BdkError] - constructor(Network network, string descriptor, DatabaseConfig database_config); + constructor(string descriptor, Network network, DatabaseConfig database_config); string get_new_address(); }; diff --git a/src/lib.rs b/src/lib.rs index 3d8b8b3..cf0f756 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,8 +22,8 @@ struct OfflineWallet { impl OfflineWallet { fn new( - network: Network, descriptor: String, + network: Network, database_config: DatabaseConfig, ) -> Result { let any_database_config = match database_config { From 0fc04fc34e717277c53add2f3a625fc5f22cf857 Mon Sep 17 00:00:00 2001 From: artfuldev Date: Fri, 15 Oct 2021 00:43:17 +0530 Subject: [PATCH 058/272] Add online wallet --- Cargo.toml | 2 +- bindings/bdk-kotlin/build.gradle | 9 +- .../src/main/kotlin/uniffi/bdk/bdk.kt | 408 +++++++++++++++++- .../src/test/kotlin/uniffi/bdk/LibTest.kt | 8 + src/bdk.udl | 30 +- src/lib.rs | 80 +++- 6 files changed, 509 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d59366f..954e596 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -bdk = { version = "^0.12.1-dev", features = ["all-keys"] } +bdk = { version = "^0.12.1-dev", features = ["all-keys", "use-esplora-ureq"] } uniffi_macros = "0.14.0" uniffi = "0.14.0" thiserror = "1.0" diff --git a/bindings/bdk-kotlin/build.gradle b/bindings/bdk-kotlin/build.gradle index bb1b0c0..9da0de2 100644 --- a/bindings/bdk-kotlin/build.gradle +++ b/bindings/bdk-kotlin/build.gradle @@ -20,6 +20,13 @@ allprojects { google() mavenCentral() } + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + freeCompilerArgs += [ + "-Xuse-experimental=kotlin.ExperimentalUnsignedTypes", + ] + } + } } dependencies { @@ -32,4 +39,4 @@ dependencies { java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 -} \ No newline at end of file +} diff --git a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt index 6343671..4d7121a 100644 --- a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_146a_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_f2ea_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_146a_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_f2ea_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_146a_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_f2ea_rustbuffer_reserve(buf, additional, status) } } @@ -208,6 +208,54 @@ internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> U +@ExperimentalUnsignedTypes +internal fun UByte.Companion.lift(v: Byte): UByte { + return v.toUByte() +} + +@ExperimentalUnsignedTypes +internal fun UByte.Companion.read(buf: ByteBuffer): UByte { + return UByte.lift(buf.get()) +} + +@ExperimentalUnsignedTypes +internal fun UByte.lower(): Byte { + return this.toByte() +} + +@ExperimentalUnsignedTypes +internal fun UByte.write(buf: RustBufferBuilder) { + buf.putByte(this.toByte()) +} + + + + + +@ExperimentalUnsignedTypes +internal fun ULong.Companion.lift(v: Long): ULong { + return v.toULong() +} + +@ExperimentalUnsignedTypes +internal fun ULong.Companion.read(buf: ByteBuffer): ULong { + return ULong.lift(buf.getLong()) +} + +@ExperimentalUnsignedTypes +internal fun ULong.lower(): Long { + return this.toLong() +} + +@ExperimentalUnsignedTypes +internal fun ULong.write(buf: RustBufferBuilder) { + buf.putLong(this.toLong()) +} + + + + + internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String { try { val byteArr = ByteArray(rbuf.len) @@ -274,6 +322,108 @@ internal fun String.write(buf: RustBufferBuilder) { + + + + + + + + + + + + + + + + + + + + + + + + + + + +// Helper functions for pasing values of type UByte? +@ExperimentalUnsignedTypes +internal fun liftOptionalu8(rbuf: RustBuffer.ByValue): UByte? { + return liftFromRustBuffer(rbuf) { buf -> + readOptionalu8(buf) + } +} + +@ExperimentalUnsignedTypes +internal fun readOptionalu8(buf: ByteBuffer): UByte? { + if (buf.get().toInt() == 0) { + return null + } + return UByte.read(buf) +} + +@ExperimentalUnsignedTypes +internal fun lowerOptionalu8(v: UByte?): RustBuffer.ByValue { + return lowerIntoRustBuffer(v) { v, buf -> + writeOptionalu8(v, buf) + } +} + +@ExperimentalUnsignedTypes +internal fun writeOptionalu8(v: UByte?, buf: RustBufferBuilder) { + if (v == null) { + buf.putByte(0) + } else { + buf.putByte(1) + v.write(buf) + } +} + + + + + + + +// Helper functions for pasing values of type String? + +internal fun liftOptionalstring(rbuf: RustBuffer.ByValue): String? { + return liftFromRustBuffer(rbuf) { buf -> + readOptionalstring(buf) + } +} + + +internal fun readOptionalstring(buf: ByteBuffer): String? { + if (buf.get().toInt() == 0) { + return null + } + return String.read(buf) +} + + +internal fun lowerOptionalstring(v: String?): RustBuffer.ByValue { + return lowerIntoRustBuffer(v) { v, buf -> + writeOptionalstring(v, buf) + } +} + + +internal fun writeOptionalstring(v: String?, buf: RustBufferBuilder) { + if (v == null) { + buf.putByte(0) + } else { + buf.putByte(1) + v.write(buf) + } +} + + + + @Synchronized fun findLibraryName(componentName: String): String { val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride") @@ -301,31 +451,39 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_146a_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_f2ea_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_146a_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_f2ea_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_146a_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_f2ea_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_146a_rustbuffer_alloc(size: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_146a_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_146a_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_f2ea_OnlineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_146a_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun bdk_f2ea_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun ffi_bdk_f2ea_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_f2ea_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_f2ea_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_f2ea_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -543,7 +701,7 @@ sealed class DatabaseConfig { ) : DatabaseConfig() data class Sled( - val configuration: SledDbConfiguration + val config: SledDbConfiguration ) : DatabaseConfig() @@ -578,7 +736,67 @@ sealed class DatabaseConfig { } is DatabaseConfig.Sled -> { buf.putInt(2) - this.configuration.write(buf) + this.config.write(buf) + + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + + + +} + + + + + + + + +@ExperimentalUnsignedTypes +sealed class BlockchainConfig { + + data class Electrum( + val config: ElectrumConfig + ) : BlockchainConfig() + + data class Esplora( + val config: EsploraConfig + ) : BlockchainConfig() + + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig { + return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): BlockchainConfig { + return when(buf.getInt()) { + 1 -> BlockchainConfig.Electrum( + ElectrumConfig.read(buf) + ) + 2 -> BlockchainConfig.Esplora( + EsploraConfig.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is BlockchainConfig.Electrum -> { + buf.putInt(1) + this.config.write(buf) + + } + is BlockchainConfig.Esplora -> { + buf.putInt(2) + this.config.write(buf) } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } @@ -656,6 +874,7 @@ sealed class BdkException(message: String): Exception(message) { class Psbt(message: String) : BdkException(message) class PsbtParse(message: String) : BdkException(message) class Electrum(message: String) : BdkException(message) + class Esplora(message: String) : BdkException(message) class Sled(message: String) : BdkException(message) @@ -704,7 +923,8 @@ sealed class BdkException(message: String): Exception(message) { 35 -> BdkException.Psbt(String.read(error_buf)) 36 -> BdkException.PsbtParse(String.read(error_buf)) 37 -> BdkException.Electrum(String.read(error_buf)) - 38 -> BdkException.Sled(String.read(error_buf)) + 38 -> BdkException.Esplora(String.read(error_buf)) + 39 -> BdkException.Sled(String.read(error_buf)) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } } @@ -786,6 +1006,96 @@ data class SledDbConfiguration ( +} + +@ExperimentalUnsignedTypes +data class ElectrumConfig ( + var url: String, + var socks5: String?, + var retry: UByte, + var timeout: UByte?, + var stopGap: ULong +) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): ElectrumConfig { + return liftFromRustBuffer(rbuf) { buf -> ElectrumConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): ElectrumConfig { + return ElectrumConfig( + String.read(buf), + readOptionalstring(buf), + UByte.read(buf), + readOptionalu8(buf), + ULong.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.url.write(buf) + + writeOptionalstring(this.socks5, buf) + + this.retry.write(buf) + + writeOptionalu8(this.timeout, buf) + + this.stopGap.write(buf) + + } + + + +} + +@ExperimentalUnsignedTypes +data class EsploraConfig ( + var baseUrl: String, + var proxy: String?, + var timeoutRead: ULong, + var timeoutWrite: ULong, + var stopGap: ULong +) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): EsploraConfig { + return liftFromRustBuffer(rbuf) { buf -> EsploraConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): EsploraConfig { + return EsploraConfig( + String.read(buf), + readOptionalstring(buf), + ULong.read(buf), + ULong.read(buf), + ULong.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.baseUrl.write(buf) + + writeOptionalstring(this.proxy, buf) + + this.timeoutRead.write(buf) + + this.timeoutWrite.write(buf) + + this.stopGap.write(buf) + + } + + + } @@ -807,7 +1117,7 @@ class OfflineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_146a_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_f2ea_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -820,7 +1130,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_146a_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_f2ea_OfflineWallet_object_free(this.pointer, status) } } @@ -835,7 +1145,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_146a_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_f2ea_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -858,6 +1168,60 @@ class OfflineWallet( } } +@ExperimentalUnsignedTypes +public interface OnlineWalletInterface { + +} + +@ExperimentalUnsignedTypes +class OnlineWallet( + pointer: Pointer +) : FFIObject(pointer), OnlineWalletInterface { + constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : + this( + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_f2ea_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) +}) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_f2ea_OnlineWallet_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + + + companion object { + internal fun lift(ptr: Pointer): OnlineWallet { + return OnlineWallet(ptr) + } + + internal fun read(buf: ByteBuffer): OnlineWallet { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return OnlineWallet.lift(Pointer(buf.getLong())) + } + + + } +} + // Callback Interfaces diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index da33bfb..6423d2b 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -35,4 +35,12 @@ class LibTest { assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") } + + @Test + fun onlineWalletInMemory() { + val db = DatabaseConfig.Memory("") + val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:50002", null, 5u, null, 100u)) + val wallet = OnlineWallet(desc, Network.TESTNET, db, client) + assertNotNull(wallet) + } } diff --git a/src/bdk.udl b/src/bdk.udl index 2ae60cc..1994bc1 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -41,6 +41,7 @@ enum BdkError { "Psbt", "PsbtParse", "Electrum", + "Esplora", "Sled", }; @@ -59,7 +60,7 @@ dictionary SledDbConfiguration { [Enum] interface DatabaseConfig { Memory(string junk); - Sled(SledDbConfiguration configuration); + Sled(SledDbConfiguration config); }; interface OfflineWallet { @@ -67,3 +68,30 @@ interface OfflineWallet { constructor(string descriptor, Network network, DatabaseConfig database_config); string get_new_address(); }; + +dictionary ElectrumConfig { + string url; + string? socks5; + u8 retry; + u8? timeout; + u64 stop_gap; +}; + +dictionary EsploraConfig { + string base_url; + string? proxy; + u64 timeout_read; + u64 timeout_write; + u64 stop_gap; +}; + +[Enum] +interface BlockchainConfig { + Electrum(ElectrumConfig config); + Esplora(EsploraConfig config); +}; + +interface OnlineWallet { + [Throws=BdkError] + constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); +}; diff --git a/src/lib.rs b/src/lib.rs index cf0f756..ae6fcc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,14 @@ use bdk::bitcoin::Network; +use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; +use bdk::blockchain::{ + electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, ConfigurableBlockchain, +}; use bdk::database::any::{AnyDatabase, SledDbConfiguration}; use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase}; use bdk::wallet::AddressIndex; use bdk::Error; use bdk::Wallet; - +use std::convert::TryFrom; use std::sync::Mutex; uniffi_macros::include_scaffolding!("bdk"); @@ -13,7 +17,28 @@ type BdkError = Error; pub enum DatabaseConfig { Memory { junk: String }, - Sled { configuration: SledDbConfiguration }, + Sled { config: SledDbConfiguration }, +} + +pub struct ElectrumConfig { + pub url: String, + pub socks5: Option, + pub retry: u8, + pub timeout: Option, + pub stop_gap: u64, +} + +pub struct EsploraConfig { + pub base_url: String, + pub proxy: Option, + pub timeout_read: u64, + pub timeout_write: u64, + pub stop_gap: u64, +} + +pub enum BlockchainConfig { + Electrum { config: ElectrumConfig }, + Esplora { config: EsploraConfig }, } struct OfflineWallet { @@ -28,7 +53,7 @@ impl OfflineWallet { ) -> Result { let any_database_config = match database_config { DatabaseConfig::Memory { .. } => AnyDatabaseConfig::Memory(()), - DatabaseConfig::Sled { configuration } => AnyDatabaseConfig::Sled(configuration), + DatabaseConfig::Sled { config } => AnyDatabaseConfig::Sled(config), }; let database = AnyDatabase::from_config(&any_database_config)?; let wallet = Mutex::new(Wallet::new_offline(&descriptor, None, network, database)?); @@ -46,4 +71,53 @@ impl OfflineWallet { } } +struct OnlineWallet { + wallet: Mutex>, +} + +impl OnlineWallet { + fn new( + descriptor: String, + network: Network, + database_config: DatabaseConfig, + blockchain_config: BlockchainConfig, + ) -> Result { + let any_database_config = match database_config { + DatabaseConfig::Memory { .. } => AnyDatabaseConfig::Memory(()), + DatabaseConfig::Sled { config } => AnyDatabaseConfig::Sled(config), + }; + let any_blockchain_config = match blockchain_config { + BlockchainConfig::Electrum { config } => { + AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { + retry: config.retry, + socks5: config.socks5, + timeout: config.timeout, + url: config.url, + stop_gap: usize::try_from(config.stop_gap).unwrap(), + }) + } + BlockchainConfig::Esplora { config } => { + AnyBlockchainConfig::Esplora(EsploraBlockchainConfig { + base_url: config.base_url, + proxy: config.proxy, + timeout_read: config.timeout_read, + timeout_write: config.timeout_write, + stop_gap: usize::try_from(config.stop_gap).unwrap(), + }) + } + }; + let database = AnyDatabase::from_config(&any_database_config)?; + let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?; + let wallet = Mutex::new(Wallet::new( + &descriptor, + None, + network, + database, + blockchain, + )?); + Ok(OnlineWallet { wallet }) + } +} + uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); +uniffi::deps::static_assertions::assert_impl_all!(OnlineWallet: Sync, Send); From 40a4b58757649fd98f112eaa3a3c475c80c1661f Mon Sep 17 00:00:00 2001 From: artfuldev Date: Fri, 15 Oct 2021 00:48:48 +0530 Subject: [PATCH 059/272] Add OnlineWallet::getNetwork --- .../src/main/kotlin/uniffi/bdk/bdk.kt | 48 ++++++++++++------- .../src/test/kotlin/uniffi/bdk/LibTest.kt | 2 + src/bdk.udl | 1 + src/lib.rs | 4 ++ 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt index 4d7121a..437b142 100644 --- a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f2ea_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_14a1_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f2ea_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_14a1_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f2ea_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_14a1_rustbuffer_reserve(buf, additional, status) } } @@ -451,39 +451,43 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_f2ea_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_14a1_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_f2ea_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_14a1_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_f2ea_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_14a1_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_f2ea_OnlineWallet_object_free(ptr: Pointer, + fun ffi_bdk_14a1_OnlineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_f2ea_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + fun bdk_14a1_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun ffi_bdk_f2ea_rustbuffer_alloc(size: Int, + fun bdk_14a1_OnlineWallet_get_network(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_f2ea_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun ffi_bdk_14a1_rustbuffer_alloc(size: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_f2ea_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_14a1_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_14a1_rustbuffer_free(buf: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_f2ea_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_14a1_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -1117,7 +1121,7 @@ class OfflineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_f2ea_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_14a1_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -1130,7 +1134,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f2ea_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_14a1_OfflineWallet_object_free(this.pointer, status) } } @@ -1145,7 +1149,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_f2ea_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_14a1_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1170,6 +1174,7 @@ class OfflineWallet( @ExperimentalUnsignedTypes public interface OnlineWalletInterface { + fun getNetwork(): Network } @@ -1180,7 +1185,7 @@ class OnlineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_f2ea_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_14a1_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) }) /** @@ -1193,7 +1198,7 @@ class OnlineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_f2ea_OnlineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_14a1_OnlineWallet_object_free(this.pointer, status) } } @@ -1205,6 +1210,15 @@ class OnlineWallet( buf.putLong(Pointer.nativeValue(this.lower())) } + override fun getNetwork(): Network = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_14a1_OnlineWallet_get_network(it, status) +} + }.let { + Network.lift(it) + } + companion object { diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index 6423d2b..1db8bc9 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -42,5 +42,7 @@ class LibTest { val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:50002", null, 5u, null, 100u)) val wallet = OnlineWallet(desc, Network.TESTNET, db, client) assertNotNull(wallet) + val network = wallet.getNetwork() + assertEquals(network, Network.TESTNET) } } diff --git a/src/bdk.udl b/src/bdk.udl index 1994bc1..593d4e8 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -94,4 +94,5 @@ interface BlockchainConfig { interface OnlineWallet { [Throws=BdkError] constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); + Network get_network(); }; diff --git a/src/lib.rs b/src/lib.rs index ae6fcc3..a1add54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,6 +117,10 @@ impl OnlineWallet { )?); Ok(OnlineWallet { wallet }) } + + fn get_network(&self) -> Network { + self.wallet.lock().unwrap().network() + } } uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); From 2907eb074d24ec94a8f841159d9b3faf13e8d7df Mon Sep 17 00:00:00 2001 From: artfuldev Date: Fri, 15 Oct 2021 01:54:32 +0530 Subject: [PATCH 060/272] Test a callback --- .../src/main/kotlin/uniffi/bdk/bdk.kt | 286 ++++++++++++++++-- .../src/test/kotlin/uniffi/bdk/LibTest.kt | 15 + src/bdk.udl | 6 + src/lib.rs | 33 ++ 4 files changed, 314 insertions(+), 26 deletions(-) diff --git a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt index 437b142..c1b79cc 100644 --- a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_14a1_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_d1e_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_14a1_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_d1e_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_14a1_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_d1e_rustbuffer_reserve(buf, additional, status) } } @@ -232,6 +232,30 @@ internal fun UByte.write(buf: RustBufferBuilder) { +@ExperimentalUnsignedTypes +internal fun UInt.Companion.lift(v: Int): UInt { + return v.toUInt() +} + +@ExperimentalUnsignedTypes +internal fun UInt.Companion.read(buf: ByteBuffer): UInt { + return UInt.lift(buf.getInt()) +} + +@ExperimentalUnsignedTypes +internal fun UInt.lower(): Int { + return this.toInt() +} + +@ExperimentalUnsignedTypes +internal fun UInt.write(buf: RustBufferBuilder) { + buf.putInt(this.toInt()) +} + + + + + @ExperimentalUnsignedTypes internal fun ULong.Companion.lift(v: Long): ULong { return v.toULong() @@ -256,6 +280,26 @@ internal fun ULong.write(buf: RustBufferBuilder) { +internal fun Float.Companion.lift(v: Float): Float { + return v +} + +internal fun Float.Companion.read(buf: ByteBuffer): Float { + return buf.getFloat() +} + +internal fun Float.lower(): Float { + return this +} + +internal fun Float.write(buf: RustBufferBuilder) { + buf.putFloat(this) +} + + + + + internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String { try { val byteArr = ByteArray(rbuf.len) @@ -342,6 +386,12 @@ internal fun String.write(buf: RustBufferBuilder) { + + + + + + @@ -388,6 +438,45 @@ internal fun writeOptionalu8(v: UByte?, buf: RustBufferBuilder) { +// Helper functions for pasing values of type UInt? +@ExperimentalUnsignedTypes +internal fun liftOptionalu32(rbuf: RustBuffer.ByValue): UInt? { + return liftFromRustBuffer(rbuf) { buf -> + readOptionalu32(buf) + } +} + +@ExperimentalUnsignedTypes +internal fun readOptionalu32(buf: ByteBuffer): UInt? { + if (buf.get().toInt() == 0) { + return null + } + return UInt.read(buf) +} + +@ExperimentalUnsignedTypes +internal fun lowerOptionalu32(v: UInt?): RustBuffer.ByValue { + return lowerIntoRustBuffer(v) { v, buf -> + writeOptionalu32(v, buf) + } +} + +@ExperimentalUnsignedTypes +internal fun writeOptionalu32(v: UInt?, buf: RustBufferBuilder) { + if (v == null) { + buf.putByte(0) + } else { + buf.putByte(1) + v.write(buf) + } +} + + + + + + + // Helper functions for pasing values of type String? internal fun liftOptionalstring(rbuf: RustBuffer.ByValue): String? { @@ -446,48 +535,58 @@ internal interface _UniFFILib : Library { companion object { internal val INSTANCE: _UniFFILib by lazy { loadIndirect<_UniFFILib>(componentName = "bdk") - + .also { lib: _UniFFILib -> + CallbackInterfaceBdkProgressInternals.register(lib) + } } } - fun ffi_bdk_14a1_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_d1e_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_14a1_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_d1e_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_14a1_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_d1e_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_14a1_OnlineWallet_object_free(ptr: Pointer, + fun ffi_bdk_d1e_OnlineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_14a1_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + fun bdk_d1e_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_14a1_OnlineWallet_get_network(ptr: Pointer, + fun bdk_d1e_OnlineWallet_get_network(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_14a1_rustbuffer_alloc(size: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_14a1_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_14a1_rustbuffer_free(buf: RustBuffer.ByValue, + fun bdk_d1e_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_14a1_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_d1e_BdkProgress_init_callback(callback_stub: ForeignCallback, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_d1e_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_d1e_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_d1e_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_d1e_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -657,6 +756,82 @@ abstract class FFIObject( +internal typealias Handle = Long +internal class ConcurrentHandleMap( + private val leftMap: MutableMap = mutableMapOf(), + private val rightMap: MutableMap = mutableMapOf() +) { + private val lock = java.util.concurrent.locks.ReentrantLock() + private val currentHandle = AtomicLong(0L) + private val stride = 1L + + fun insert(obj: T): Handle = + lock.withLock { + rightMap[obj] ?: + currentHandle.getAndAdd(stride) + .also { handle -> + leftMap[handle] = obj + rightMap[obj] = handle + } + } + + fun callWithResult(handle: Handle, fn: (T) -> R): R = + lock.withLock { + leftMap[handle] ?: throw RuntimeException("Panic: handle not in handlemap") + }.let { obj -> + fn.invoke(obj) + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } +} + +interface ForeignCallback : com.sun.jna.Callback { + public fun invoke(handle: Long, method: Int, args: RustBuffer.ByValue): RustBuffer.ByValue +} + +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +internal const val IDX_CALLBACK_FREE = 0 + +internal abstract class CallbackInternals( + val foreignCallback: ForeignCallback +) { + val handleMap = ConcurrentHandleMap() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + abstract fun register(lib: _UniFFILib) + + fun drop(handle: Long): RustBuffer.ByValue { + return handleMap.remove(handle).let { RustBuffer.ByValue() } + } + + fun lift(n: Long) = handleMap.get(n) + + fun read(buf: ByteBuffer) = lift(buf.getLong()) + + fun lower(v: CallbackInterface) = + handleMap.insert(v).also { + assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + fun write(v: CallbackInterface, buf: RustBufferBuilder) = + buf.putLong(lower(v)) +} // Public interface members begin here. @@ -1121,7 +1296,7 @@ class OfflineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_14a1_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_d1e_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -1134,7 +1309,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_14a1_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_d1e_OfflineWallet_object_free(this.pointer, status) } } @@ -1149,7 +1324,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_14a1_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_d1e_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1175,6 +1350,7 @@ class OfflineWallet( @ExperimentalUnsignedTypes public interface OnlineWalletInterface { fun getNetwork(): Network + fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) } @@ -1185,7 +1361,7 @@ class OnlineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_14a1_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_d1e_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) }) /** @@ -1198,7 +1374,7 @@ class OnlineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_14a1_OnlineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_d1e_OnlineWallet_object_free(this.pointer, status) } } @@ -1213,12 +1389,19 @@ class OnlineWallet( override fun getNetwork(): Network = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_14a1_OnlineWallet_get_network(it, status) + _UniFFILib.INSTANCE.bdk_d1e_OnlineWallet_get_network(it, status) } }.let { Network.lift(it) } + override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_d1e_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) +} + } + companion object { @@ -1240,3 +1423,54 @@ class OnlineWallet( // Callback Interfaces +public interface BdkProgress { + fun update(progress: Float, message: String? ) + +} + + +internal class CallbackInterfaceBdkProgressFFI : ForeignCallback { + @Suppress("TooGenericExceptionCaught") + override fun invoke(handle: Long, method: Int, args: RustBuffer.ByValue): RustBuffer.ByValue { + return CallbackInterfaceBdkProgressInternals.handleMap.callWithResult(handle) { cb -> + when (method) { + IDX_CALLBACK_FREE -> CallbackInterfaceBdkProgressInternals.drop(handle) + 1 -> this.invokeUpdate(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalException. + // https://github.com/mozilla/uniffi-rs/issues/351 + else -> RustBuffer.ByValue() + } + } + } + + + private fun invokeUpdate(kotlinCallbackInterface: BdkProgress, args: RustBuffer.ByValue): RustBuffer.ByValue = + try { + val buf = args.asByteBuffer() ?: throw InternalException("No ByteBuffer in RustBuffer; this is a Uniffi bug") + kotlinCallbackInterface.update( + Float.read(buf), + readOptionalstring(buf) + ) + .let { RustBuffer.ByValue() } + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + } finally { + RustBuffer.free(args) + } + + +} + +internal object CallbackInterfaceBdkProgressInternals: CallbackInternals( + foreignCallback = CallbackInterfaceBdkProgressFFI() +) { + override fun register(lib: _UniFFILib) { + rustCall() { status -> + lib.ffi_bdk_d1e_BdkProgress_init_callback(this.foreignCallback, status) + } + } +} + + diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index 1db8bc9..92c257d 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -4,6 +4,13 @@ import uniffi.bdk.OfflineWallet import org.junit.Assert.* import org.junit.Test +class LogProgress: BdkProgress { + override fun update(progress: Float, message: String? ) { + println(progress); + println(message); + } +} + /** * Library tests which will execute for jvm and android modules. */ @@ -45,4 +52,12 @@ class LibTest { val network = wallet.getNetwork() assertEquals(network, Network.TESTNET) } + + @Test + fun onlineWalletSync() { + val db = DatabaseConfig.Memory("") + val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:50002", null, 5u, null, 100u)) + val wallet = OnlineWallet(desc, Network.TESTNET, db, client) + wallet.sync(LogProgress(), null) + } } diff --git a/src/bdk.udl b/src/bdk.udl index 593d4e8..cbc79a6 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -91,8 +91,14 @@ interface BlockchainConfig { Esplora(EsploraConfig config); }; +callback interface BdkProgress { + void update(f32 progress, string? message); +}; + interface OnlineWallet { [Throws=BdkError] constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); Network get_network(); + [Throws=BdkError] + void sync(BdkProgress progress_update, u32? max_address_param); }; diff --git a/src/lib.rs b/src/lib.rs index a1add54..94cc219 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use bdk::bitcoin::Network; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; +use bdk::blockchain::Progress; use bdk::blockchain::{ electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, ConfigurableBlockchain, }; @@ -75,6 +76,24 @@ struct OnlineWallet { wallet: Mutex>, } +pub trait BdkProgress: Send { + fn update(&self, progress: f32, message: Option); +} + +struct BdkProgressHolder { + progress_update: Mutex>, +} + +impl Progress for BdkProgressHolder { + fn update(&self, progress: f32, message: Option) -> Result<(), Error> { + self.progress_update + .lock() + .unwrap() + .update(progress, message); + Ok(()) + } +} + impl OnlineWallet { fn new( descriptor: String, @@ -121,6 +140,20 @@ impl OnlineWallet { fn get_network(&self) -> Network { self.wallet.lock().unwrap().network() } + + fn sync( + &self, + progress_update: Box, + max_address_param: Option, + ) -> Result<(), BdkError> { + progress_update.update(21.0, Some("message".to_string())); + self.wallet.lock().unwrap().sync( + BdkProgressHolder { + progress_update: Mutex::new(progress_update), + }, + max_address_param, + ) + } } uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); From 9ba06258244bff9bcc7faac5c1211e560ffae818 Mon Sep 17 00:00:00 2001 From: artfuldev Date: Fri, 15 Oct 2021 03:00:49 +0530 Subject: [PATCH 061/272] Remove unnecessary Mutex wrapper --- src/lib.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 94cc219..7204fcd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,20 +76,17 @@ struct OnlineWallet { wallet: Mutex>, } -pub trait BdkProgress: Send { +pub trait BdkProgress: Send + Sync { fn update(&self, progress: f32, message: Option); } struct BdkProgressHolder { - progress_update: Mutex>, + progress_update: Box, } impl Progress for BdkProgressHolder { fn update(&self, progress: f32, message: Option) -> Result<(), Error> { - self.progress_update - .lock() - .unwrap() - .update(progress, message); + self.progress_update.update(progress, message); Ok(()) } } @@ -147,12 +144,10 @@ impl OnlineWallet { max_address_param: Option, ) -> Result<(), BdkError> { progress_update.update(21.0, Some("message".to_string())); - self.wallet.lock().unwrap().sync( - BdkProgressHolder { - progress_update: Mutex::new(progress_update), - }, - max_address_param, - ) + self.wallet + .lock() + .unwrap() + .sync(BdkProgressHolder { progress_update }, max_address_param) } } From 5ab47ef8156d07e5d089846d6c7f3ec607739be5 Mon Sep 17 00:00:00 2001 From: artfuldev Date: Fri, 15 Oct 2021 03:05:46 +0530 Subject: [PATCH 062/272] Remove testdb before every test --- test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.sh b/test.sh index f098e4b..e46caed 100755 --- a/test.sh +++ b/test.sh @@ -17,7 +17,7 @@ help() } test_kotlin() { - (cd bindings/bdk-kotlin && ./gradlew test -Djna.debug_load=true) + (cd bindings/bdk-kotlin && rm -rf /tmp/testdb && ./gradlew test -Djna.debug_load=true) } if [ $1 = "-h" ] From c15c69fb089bd200a78e355af16aac0d8a448cf7 Mon Sep 17 00:00:00 2001 From: artfuldev Date: Fri, 15 Oct 2021 03:40:33 +0530 Subject: [PATCH 063/272] Add OnlineWallet::getBalance() --- .../src/main/kotlin/uniffi/bdk/bdk.kt | 60 ++++++++++++------- .../src/test/kotlin/uniffi/bdk/LibTest.kt | 5 +- src/bdk.udl | 2 + src/lib.rs | 4 ++ 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt index c1b79cc..5610046 100644 --- a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_d1e_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_3303_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_d1e_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_3303_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_d1e_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_3303_rustbuffer_reserve(buf, additional, status) } } @@ -542,51 +542,55 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_d1e_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_3303_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_d1e_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_3303_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_d1e_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_3303_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_d1e_OnlineWallet_object_free(ptr: Pointer, + fun ffi_bdk_3303_OnlineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_d1e_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + fun bdk_3303_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_d1e_OnlineWallet_get_network(ptr: Pointer, + fun bdk_3303_OnlineWallet_get_network(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun bdk_d1e_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, + fun bdk_3303_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_d1e_BdkProgress_init_callback(callback_stub: ForeignCallback, + fun bdk_3303_OnlineWallet_get_balance(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Long + + fun ffi_bdk_3303_BdkProgress_init_callback(callback_stub: ForeignCallback, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_d1e_rustbuffer_alloc(size: Int, + fun ffi_bdk_3303_rustbuffer_alloc(size: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_d1e_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun ffi_bdk_3303_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_d1e_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_3303_rustbuffer_free(buf: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_d1e_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_3303_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -1296,7 +1300,7 @@ class OfflineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_d1e_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_3303_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -1309,7 +1313,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_d1e_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_3303_OfflineWallet_object_free(this.pointer, status) } } @@ -1324,7 +1328,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_d1e_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_3303_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1351,6 +1355,7 @@ class OfflineWallet( public interface OnlineWalletInterface { fun getNetwork(): Network fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) + fun getBalance(): ULong } @@ -1361,7 +1366,7 @@ class OnlineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_d1e_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_3303_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) }) /** @@ -1374,7 +1379,7 @@ class OnlineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_d1e_OnlineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_3303_OnlineWallet_object_free(this.pointer, status) } } @@ -1389,7 +1394,7 @@ class OnlineWallet( override fun getNetwork(): Network = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_d1e_OnlineWallet_get_network(it, status) + _UniFFILib.INSTANCE.bdk_3303_OnlineWallet_get_network(it, status) } }.let { Network.lift(it) @@ -1398,10 +1403,19 @@ class OnlineWallet( override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_d1e_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) + _UniFFILib.INSTANCE.bdk_3303_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) } } + override fun getBalance(): ULong = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_3303_OnlineWallet_get_balance(it, status) +} + }.let { + ULong.lift(it) + } + companion object { @@ -1468,7 +1482,7 @@ internal object CallbackInterfaceBdkProgressInternals: CallbackInternals - lib.ffi_bdk_d1e_BdkProgress_init_callback(this.foreignCallback, status) + lib.ffi_bdk_3303_BdkProgress_init_callback(this.foreignCallback, status) } } } diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index 92c257d..b9edc4e 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -54,10 +54,13 @@ class LibTest { } @Test - fun onlineWalletSync() { + fun onlineWalletSyncGetBalance() { val db = DatabaseConfig.Memory("") val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:50002", null, 5u, null, 100u)) val wallet = OnlineWallet(desc, Network.TESTNET, db, client) wallet.sync(LogProgress(), null) + val balance = wallet.getBalance() + assertNotNull(balance) + println(balance) } } diff --git a/src/bdk.udl b/src/bdk.udl index cbc79a6..4ceb2c3 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -101,4 +101,6 @@ interface OnlineWallet { Network get_network(); [Throws=BdkError] void sync(BdkProgress progress_update, u32? max_address_param); + [Throws=BdkError] + u64 get_balance(); }; diff --git a/src/lib.rs b/src/lib.rs index 7204fcd..ab4398b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,6 +149,10 @@ impl OnlineWallet { .unwrap() .sync(BdkProgressHolder { progress_update }, max_address_param) } + + fn get_balance(&self) -> Result { + self.wallet.lock().unwrap().get_balance() + } } uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); From 830cbd852e26a2ff7130a46e2d399a6113e2492a Mon Sep 17 00:00:00 2001 From: artfuldev Date: Fri, 15 Oct 2021 03:56:17 +0530 Subject: [PATCH 064/272] Fix electrum testnet url --- bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index b9edc4e..abbe923 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -46,7 +46,7 @@ class LibTest { @Test fun onlineWalletInMemory() { val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:50002", null, 5u, null, 100u)) + val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 100u)) val wallet = OnlineWallet(desc, Network.TESTNET, db, client) assertNotNull(wallet) val network = wallet.getNetwork() @@ -56,11 +56,10 @@ class LibTest { @Test fun onlineWalletSyncGetBalance() { val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:50002", null, 5u, null, 100u)) + val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 100u)) val wallet = OnlineWallet(desc, Network.TESTNET, db, client) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() - assertNotNull(balance) - println(balance) + assertTrue(balance > 0u) } } From 31db42ae0e70c00f4ed2e23dff24afc78b4dac6d Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 14 Oct 2021 22:05:21 -0700 Subject: [PATCH 065/272] Reorganize bdk-kotlin into jvm sub-module --- bindings/bdk-kotlin/.gitignore | 7 +++ bindings/bdk-kotlin/build.gradle | 55 +++++++------------ bindings/bdk-kotlin/jvm/build.gradle | 39 +++++++++++++ .../src/main/kotlin/uniffi/bdk/bdk.kt | 0 .../src/test/kotlin/uniffi/bdk/LibTest.kt | 0 bindings/bdk-kotlin/settings.gradle | 4 ++ build.sh | 24 ++++---- 7 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 bindings/bdk-kotlin/.gitignore create mode 100644 bindings/bdk-kotlin/jvm/build.gradle rename bindings/bdk-kotlin/{ => jvm}/src/main/kotlin/uniffi/bdk/bdk.kt (100%) rename bindings/bdk-kotlin/{ => jvm}/src/test/kotlin/uniffi/bdk/LibTest.kt (100%) create mode 100644 bindings/bdk-kotlin/settings.gradle diff --git a/bindings/bdk-kotlin/.gitignore b/bindings/bdk-kotlin/.gitignore new file mode 100644 index 0000000..f2efc75 --- /dev/null +++ b/bindings/bdk-kotlin/.gitignore @@ -0,0 +1,7 @@ +/target +.idea +.gradle +local.properties +build +*.so +*.dylib diff --git a/bindings/bdk-kotlin/build.gradle b/bindings/bdk-kotlin/build.gradle index 9da0de2..8983efd 100644 --- a/bindings/bdk-kotlin/build.gradle +++ b/bindings/bdk-kotlin/build.gradle @@ -1,42 +1,29 @@ buildscript { - ext.kotlin_version = '1.5.10' - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -plugins { - id "org.jetbrains.kotlin.jvm" version "$kotlin_version" - id 'java-library' + ext.kotlin_version = '1.5.10' + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.2.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } allprojects { - repositories { - google() - mavenCentral() - } - tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions { - freeCompilerArgs += [ - "-Xuse-experimental=kotlin.ExperimentalUnsignedTypes", - ] - } + repositories { + google() + mavenCentral() + } + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + freeCompilerArgs += [ + "-Xuse-experimental=kotlin.ExperimentalUnsignedTypes", + ] } + } } -dependencies { - implementation platform('org.jetbrains.kotlin:kotlin-bom') - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "net.java.dev.jna:jna:5.8.0" - implementation "junit:junit:4.13.2" -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 +task clean(type: Delete) { + delete rootProject.buildDir } diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle new file mode 100644 index 0000000..94f7927 --- /dev/null +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'java-library' + id 'maven-publish' +} + +test { + testLogging { + events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" + } +} + +dependencies { + implementation platform('org.jetbrains.kotlin:kotlin-bom') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "net.java.dev.jna:jna:5.8.0" + implementation "junit:junit:4.13.2" + api "org.slf4j:slf4j-api:1.7.30" + testImplementation "ch.qos.logback:logback-classic:1.2.3" + testImplementation "ch.qos.logback:logback-core:1.2.3" + //testImplementation(project(':test-fixtures')) +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'org.bitcoindevkit' + artifactId = 'bdk' + version = '0.0.1-SNAPSHOT' + + from components.java + } + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} diff --git a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt similarity index 100% rename from bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt rename to bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt similarity index 100% rename from bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt rename to bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt diff --git a/bindings/bdk-kotlin/settings.gradle b/bindings/bdk-kotlin/settings.gradle new file mode 100644 index 0000000..c23d1d9 --- /dev/null +++ b/bindings/bdk-kotlin/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'bdk-kotlin' + +include ':jvm' //, ':android', ':test-fixtures' + diff --git a/build.sh b/build.sh index 1f457f6..eaeb2cd 100755 --- a/build.sh +++ b/build.sh @@ -30,13 +30,13 @@ copy_lib_kotlin() { case $OS in "Darwin") echo -n "darwin " - mkdir -p bindings/bdk-kotlin/src/main/resources/darwin-x86-64 - cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/src/main/resources/darwin-x86-64 + mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 ;; "Linux") echo -n "linux " - mkdir -p bindings/bdk-kotlin/src/main/resources/linux-x86-64 - cp target/debug/libuniffi_bdk.so bindings/bdk-kotlin/src/main/resources/linux-x86-64 + mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 + cp target/debug/libuniffi_bdk.so bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 ;; esac echo "libs to kotlin sub-project" @@ -44,7 +44,7 @@ copy_lib_kotlin() { ## bdk-bdk-kotlin jar build_kotlin() { - uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-kotlin/src/main/kotlin --language kotlin + uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-kotlin/jvm/src/main/kotlin --language kotlin } ## rust android @@ -61,27 +61,27 @@ build_android() { # IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" - mkdir -p bdk-bdk-kotlin/android/src/main/jniLibs/ bdk-bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-bdk-kotlin/android/src/main/jniLibs/x86 + mkdir -p bdk-kotlin/android/src/main/jniLibs/ bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-kotlin/android/src/main/jniLibs/x86 if echo $BUILD_TARGETS | grep "aarch64"; then CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android - cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/arm64-v8a + cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/arm64-v8a fi if echo $BUILD_TARGETS | grep "x86_64"; then CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android - cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/x86_64 + cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86_64 fi if echo $BUILD_TARGETS | grep "armv7"; then CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi - cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/armeabi-v7a + cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/armeabi-v7a fi if echo $BUILD_TARGETS | grep "i686"; then CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android - cp target/i686-linux-android/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/x86 + cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86 fi - # bdk-bdk-kotlin aar - (cd bdk-bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) + # bdk-kotlin aar + (cd bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) } OS=$(uname) From 2dab31209e73d630fe42de6999b7de515ffad033 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 16 Oct 2021 14:19:29 +0530 Subject: [PATCH 066/272] Share OfflineWalletOperations --- .../jvm/src/main/kotlin/uniffi/bdk/bdk.kt | 64 +++++++++++-------- src/bdk.udl | 1 + src/lib.rs | 42 ++++++++---- 3 files changed, 71 insertions(+), 36 deletions(-) diff --git a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt index 5610046..085cb16 100644 --- a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_3303_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_7046_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_3303_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_7046_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_3303_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_7046_rustbuffer_reserve(buf, additional, status) } } @@ -542,55 +542,59 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_3303_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_7046_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_3303_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_7046_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_3303_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_7046_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_3303_OnlineWallet_object_free(ptr: Pointer, + fun ffi_bdk_7046_OnlineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_3303_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + fun bdk_7046_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_3303_OnlineWallet_get_network(ptr: Pointer, + fun bdk_7046_OnlineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun bdk_3303_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, + fun bdk_7046_OnlineWallet_get_network(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_7046_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun bdk_3303_OnlineWallet_get_balance(ptr: Pointer, + fun bdk_7046_OnlineWallet_get_balance(ptr: Pointer, uniffi_out_err: RustCallStatus ): Long - fun ffi_bdk_3303_BdkProgress_init_callback(callback_stub: ForeignCallback, + fun ffi_bdk_7046_BdkProgress_init_callback(callback_stub: ForeignCallback, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_3303_rustbuffer_alloc(size: Int, + fun ffi_bdk_7046_rustbuffer_alloc(size: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_3303_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun ffi_bdk_7046_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_3303_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_7046_rustbuffer_free(buf: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_3303_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_7046_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -1300,7 +1304,7 @@ class OfflineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_3303_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_7046_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -1313,7 +1317,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_3303_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_7046_OfflineWallet_object_free(this.pointer, status) } } @@ -1328,7 +1332,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_3303_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_7046_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1353,6 +1357,7 @@ class OfflineWallet( @ExperimentalUnsignedTypes public interface OnlineWalletInterface { + fun getNewAddress(): String fun getNetwork(): Network fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) fun getBalance(): ULong @@ -1366,7 +1371,7 @@ class OnlineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_3303_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_7046_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) }) /** @@ -1379,7 +1384,7 @@ class OnlineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_3303_OnlineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_7046_OnlineWallet_object_free(this.pointer, status) } } @@ -1391,10 +1396,19 @@ class OnlineWallet( buf.putLong(Pointer.nativeValue(this.lower())) } + override fun getNewAddress(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_7046_OnlineWallet_get_new_address(it, status) +} + }.let { + String.lift(it) + } + override fun getNetwork(): Network = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_3303_OnlineWallet_get_network(it, status) + _UniFFILib.INSTANCE.bdk_7046_OnlineWallet_get_network(it, status) } }.let { Network.lift(it) @@ -1403,14 +1417,14 @@ class OnlineWallet( override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_3303_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) + _UniFFILib.INSTANCE.bdk_7046_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) } } override fun getBalance(): ULong = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_3303_OnlineWallet_get_balance(it, status) + _UniFFILib.INSTANCE.bdk_7046_OnlineWallet_get_balance(it, status) } }.let { ULong.lift(it) @@ -1482,7 +1496,7 @@ internal object CallbackInterfaceBdkProgressInternals: CallbackInternals - lib.ffi_bdk_3303_BdkProgress_init_callback(this.foreignCallback, status) + lib.ffi_bdk_7046_BdkProgress_init_callback(this.foreignCallback, status) } } } diff --git a/src/bdk.udl b/src/bdk.udl index 4ceb2c3..0283237 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -98,6 +98,7 @@ callback interface BdkProgress { interface OnlineWallet { [Throws=BdkError] constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); + string get_new_address(); Network get_network(); [Throws=BdkError] void sync(BdkProgress progress_update, u32? max_address_param); diff --git a/src/lib.rs b/src/lib.rs index ab4398b..9bd5669 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ use bdk::wallet::AddressIndex; use bdk::Error; use bdk::Wallet; use std::convert::TryFrom; -use std::sync::Mutex; +use std::sync::{Mutex, MutexGuard}; uniffi_macros::include_scaffolding!("bdk"); @@ -42,10 +42,30 @@ pub enum BlockchainConfig { Esplora { config: EsploraConfig }, } +trait WalletHolder { + fn get_wallet(&self) -> MutexGuard>; +} + struct OfflineWallet { wallet: Mutex>, } +impl WalletHolder<()> for OfflineWallet { + fn get_wallet(&self) -> MutexGuard> { + self.wallet.lock().unwrap() + } +} + +trait OfflineWalletOperations: WalletHolder { + fn get_new_address(&self) -> String { + self.get_wallet() + .get_address(AddressIndex::New) + .unwrap() + .address + .to_string() + } +} + impl OfflineWallet { fn new( descriptor: String, @@ -60,18 +80,10 @@ impl OfflineWallet { let wallet = Mutex::new(Wallet::new_offline(&descriptor, None, network, database)?); Ok(OfflineWallet { wallet }) } - - fn get_new_address(&self) -> String { - self.wallet - .lock() - .unwrap() - .get_address(AddressIndex::New) - .unwrap() - .address - .to_string() - } } +impl OfflineWalletOperations<()> for OfflineWallet {} + struct OnlineWallet { wallet: Mutex>, } @@ -155,5 +167,13 @@ impl OnlineWallet { } } +impl WalletHolder for OnlineWallet { + fn get_wallet(&self) -> MutexGuard> { + self.wallet.lock().unwrap() + } +} + +impl OfflineWalletOperations for OnlineWallet {} + uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); uniffi::deps::static_assertions::assert_impl_all!(OnlineWallet: Sync, Send); From 3f620ecf19e7b7b4bd7340dea807ceb9b3a20170 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 16 Oct 2021 14:45:32 +0530 Subject: [PATCH 067/272] Add demo application in kotlin --- .editorconfig | 3 ++ README.md | 11 ++++++ bindings/bdk-kotlin/demo/build.gradle | 36 ++++++++++++++++++ .../demo/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 +++ .../bdk-kotlin/demo/src/main/kotlin/Main.kt | 25 ++++++++++++ bindings/bdk-kotlin/settings.gradle | 2 +- build.sh | 2 + 8 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 bindings/bdk-kotlin/demo/build.gradle create mode 100644 bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.jar create mode 100644 bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.properties create mode 100644 bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt diff --git a/.editorconfig b/.editorconfig index f8d19dc..4f1c5cd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,6 +17,9 @@ indent_size = 4 [*.kt] indent_size = 4 +[*.gradle] +indent_size = 4 + [tests/**/*.rs] charset = utf-8 end_of_line = unset diff --git a/README.md b/README.md index 4ace128..f9fd29c 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,14 @@ See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) 1. Use `build.sh` script (TODO do it all in build.rs instead) 2. Create tests in `bindings/bdk-kotlin` and/or `bindings/bdk-swift` 3. Use `test.sh` to run all bindings tests + +### Run kotlin demo application + +We have a kotlin demo console application which uses bdk. +It uses stdin for inputs and can be run from gradle. + +```sh +cd bindings/bdk-kotlin +./gradlew :demo:run +``` + diff --git a/bindings/bdk-kotlin/demo/build.gradle b/bindings/bdk-kotlin/demo/build.gradle new file mode 100644 index 0000000..191a764 --- /dev/null +++ b/bindings/bdk-kotlin/demo/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'application' +} + +group = 'uniffi.bdk' +version = '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.jetbrains.kotlin:kotlin-test' + implementation project(':jvm') +} + +test { + useJUnit() +} + +compileKotlin { + kotlinOptions.jvmTarget = '1.8' +} + +compileTestKotlin { + kotlinOptions.jvmTarget = '1.8' +} + +application { + mainClassName = 'MainKt' +} + +run { + standardInput = System.in +} diff --git a/bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.jar b/bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 zcma&NbC71ylI~qywr$(CZQJHswz}-9F59+k+g;UV+cs{`J?GrGXYR~=-ydruB3JCa zB64N^cILAcWk5iofq)<(fq;O7{th4@;QxID0)qN`mJ?GIqLY#rX8-|G{5M0pdVW5^ zzXk$-2kQTAC?_N@B`&6-N-rmVFE=$QD?>*=4<|!MJu@}isLc4AW#{m2if&A5T5g&~ ziuMQeS*U5sL6J698wOd)K@oK@1{peP5&Esut<#VH^u)gp`9H4)`uE!2$>RTctN+^u z=ASkePDZA-X8)rp%D;p*~P?*a_=*Kwc<^>QSH|^<0>o37lt^+Mj1;4YvJ(JR-Y+?%Nu}JAYj5 z_Qc5%Ao#F?q32i?ZaN2OSNhWL;2oDEw_({7ZbgUjna!Fqn3NzLM@-EWFPZVmc>(fZ z0&bF-Ch#p9C{YJT9Rcr3+Y_uR^At1^BxZ#eo>$PLJF3=;t_$2|t+_6gg5(j{TmjYU zK12c&lE?Eh+2u2&6Gf*IdKS&6?rYbSEKBN!rv{YCm|Rt=UlPcW9j`0o6{66#y5t9C zruFA2iKd=H%jHf%ypOkxLnO8#H}#Zt{8p!oi6)7#NqoF({t6|J^?1e*oxqng9Q2Cc zg%5Vu!em)}Yuj?kaP!D?b?(C*w!1;>R=j90+RTkyEXz+9CufZ$C^umX^+4|JYaO<5 zmIM3#dv`DGM;@F6;(t!WngZSYzHx?9&$xEF70D1BvfVj<%+b#)vz)2iLCrTeYzUcL z(OBnNoG6Le%M+@2oo)&jdOg=iCszzv59e zDRCeaX8l1hC=8LbBt|k5?CXgep=3r9BXx1uR8!p%Z|0+4Xro=xi0G!e{c4U~1j6!) zH6adq0}#l{%*1U(Cb%4AJ}VLWKBPi0MoKFaQH6x?^hQ!6em@993xdtS%_dmevzeNl z(o?YlOI=jl(`L9^ z0O+H9k$_@`6L13eTT8ci-V0ljDMD|0ifUw|Q-Hep$xYj0hTO@0%IS^TD4b4n6EKDG z??uM;MEx`s98KYN(K0>c!C3HZdZ{+_53DO%9k5W%pr6yJusQAv_;IA}925Y%;+!tY z%2k!YQmLLOr{rF~!s<3-WEUs)`ix_mSU|cNRBIWxOox_Yb7Z=~Q45ZNe*u|m^|)d* zog=i>`=bTe!|;8F+#H>EjIMcgWcG2ORD`w0WD;YZAy5#s{65~qfI6o$+Ty&-hyMyJ z3Ra~t>R!p=5ZpxA;QkDAoPi4sYOP6>LT+}{xp}tk+<0k^CKCFdNYG(Es>p0gqD)jP zWOeX5G;9(m@?GOG7g;e74i_|SmE?`B2i;sLYwRWKLy0RLW!Hx`=!LH3&k=FuCsM=9M4|GqzA)anEHfxkB z?2iK-u(DC_T1};KaUT@3nP~LEcENT^UgPvp!QC@Dw&PVAhaEYrPey{nkcn(ro|r7XUz z%#(=$7D8uP_uU-oPHhd>>^adbCSQetgSG`e$U|7mr!`|bU0aHl_cmL)na-5x1#OsVE#m*+k84Y^+UMeSAa zbrVZHU=mFwXEaGHtXQq`2ZtjfS!B2H{5A<3(nb-6ARVV8kEmOkx6D2x7~-6hl;*-*}2Xz;J#a8Wn;_B5=m zl3dY;%krf?i-Ok^Pal-}4F`{F@TYPTwTEhxpZK5WCpfD^UmM_iYPe}wpE!Djai6_{ z*pGO=WB47#Xjb7!n2Ma)s^yeR*1rTxp`Mt4sfA+`HwZf%!7ZqGosPkw69`Ix5Ku6G z@Pa;pjzV&dn{M=QDx89t?p?d9gna*}jBly*#1!6}5K<*xDPJ{wv4& zM$17DFd~L*Te3A%yD;Dp9UGWTjRxAvMu!j^Tbc}2v~q^59d4bz zvu#!IJCy(BcWTc`;v$9tH;J%oiSJ_i7s;2`JXZF+qd4C)vY!hyCtl)sJIC{ebI*0> z@x>;EzyBv>AI-~{D6l6{ST=em*U( z(r$nuXY-#CCi^8Z2#v#UXOt`dbYN1z5jzNF2 z411?w)whZrfA20;nl&C1Gi+gk<`JSm+{|*2o<< zqM#@z_D`Cn|0H^9$|Tah)0M_X4c37|KQ*PmoT@%xHc3L1ZY6(p(sNXHa&49Frzto& zR`c~ClHpE~4Z=uKa5S(-?M8EJ$zt0&fJk~p$M#fGN1-y$7!37hld`Uw>Urri(DxLa;=#rK0g4J)pXMC zxzraOVw1+kNWpi#P=6(qxf`zSdUC?D$i`8ZI@F>k6k zz21?d+dw7b&i*>Kv5L(LH-?J%@WnqT7j#qZ9B>|Zl+=> z^U-pV@1y_ptHo4hl^cPRWewbLQ#g6XYQ@EkiP z;(=SU!yhjHp%1&MsU`FV1Z_#K1&(|5n(7IHbx&gG28HNT)*~-BQi372@|->2Aw5It z0CBpUcMA*QvsPy)#lr!lIdCi@1k4V2m!NH)%Px(vu-r(Q)HYc!p zJ^$|)j^E#q#QOgcb^pd74^JUi7fUmMiNP_o*lvx*q%_odv49Dsv$NV;6J z9GOXKomA{2Pb{w}&+yHtH?IkJJu~}Z?{Uk++2mB8zyvh*xhHKE``99>y#TdD z&(MH^^JHf;g(Tbb^&8P*;_i*2&fS$7${3WJtV7K&&(MBV2~)2KB3%cWg#1!VE~k#C z!;A;?p$s{ihyojEZz+$I1)L}&G~ml=udD9qh>Tu(ylv)?YcJT3ihapi!zgPtWb*CP zlLLJSRCj-^w?@;RU9aL2zDZY1`I3d<&OMuW=c3$o0#STpv_p3b9Wtbql>w^bBi~u4 z3D8KyF?YE?=HcKk!xcp@Cigvzy=lnFgc^9c%(^F22BWYNAYRSho@~*~S)4%AhEttv zvq>7X!!EWKG?mOd9&n>vvH1p4VzE?HCuxT-u+F&mnsfDI^}*-d00-KAauEaXqg3k@ zy#)MGX!X;&3&0s}F3q40ZmVM$(H3CLfpdL?hB6nVqMxX)q=1b}o_PG%r~hZ4gUfSp zOH4qlEOW4OMUc)_m)fMR_rl^pCfXc{$fQbI*E&mV77}kRF z&{<06AJyJ!e863o-V>FA1a9Eemx6>^F$~9ppt()ZbPGfg_NdRXBWoZnDy2;#ODgf! zgl?iOcF7Meo|{AF>KDwTgYrJLb$L2%%BEtO>T$C?|9bAB&}s;gI?lY#^tttY&hfr# zKhC+&b-rpg_?~uVK%S@mQleU#_xCsvIPK*<`E0fHE1&!J7!xD#IB|SSPW6-PyuqGn3^M^Rz%WT{e?OI^svARX&SAdU77V(C~ zM$H{Kg59op{<|8ry9ecfP%=kFm(-!W&?U0@<%z*+!*<e0XesMxRFu9QnGqun6R_%T+B%&9Dtk?*d$Q zb~>84jEAPi@&F@3wAa^Lzc(AJz5gsfZ7J53;@D<;Klpl?sK&u@gie`~vTsbOE~Cd4 z%kr56mI|#b(Jk&;p6plVwmNB0H@0SmgdmjIn5Ne@)}7Vty(yb2t3ev@22AE^s!KaN zyQ>j+F3w=wnx7w@FVCRe+`vUH)3gW%_72fxzqX!S&!dchdkRiHbXW1FMrIIBwjsai8`CB2r4mAbwp%rrO>3B$Zw;9=%fXI9B{d(UzVap7u z6piC-FQ)>}VOEuPpuqznpY`hN4dGa_1Xz9rVg(;H$5Te^F0dDv*gz9JS<|>>U0J^# z6)(4ICh+N_Q`Ft0hF|3fSHs*?a=XC;e`sJaU9&d>X4l?1W=|fr!5ShD|nv$GK;j46@BV6+{oRbWfqOBRb!ir88XD*SbC(LF}I1h#6@dvK%Toe%@ zhDyG$93H8Eu&gCYddP58iF3oQH*zLbNI;rN@E{T9%A8!=v#JLxKyUe}e}BJpB{~uN zqgxRgo0*-@-iaHPV8bTOH(rS(huwK1Xg0u+e!`(Irzu@Bld&s5&bWgVc@m7;JgELd zimVs`>vQ}B_1(2#rv#N9O`fJpVfPc7V2nv34PC);Dzbb;p!6pqHzvy?2pD&1NE)?A zt(t-ucqy@wn9`^MN5apa7K|L=9>ISC>xoc#>{@e}m#YAAa1*8-RUMKwbm|;5p>T`Z zNf*ph@tnF{gmDa3uwwN(g=`Rh)4!&)^oOy@VJaK4lMT&5#YbXkl`q?<*XtsqD z9PRK6bqb)fJw0g-^a@nu`^?71k|m3RPRjt;pIkCo1{*pdqbVs-Yl>4E>3fZx3Sv44grW=*qdSoiZ9?X0wWyO4`yDHh2E!9I!ZFi zVL8|VtW38}BOJHW(Ax#KL_KQzarbuE{(%TA)AY)@tY4%A%P%SqIU~8~-Lp3qY;U-} z`h_Gel7;K1h}7$_5ZZT0&%$Lxxr-<89V&&TCsu}LL#!xpQ1O31jaa{U34~^le*Y%L za?7$>Jk^k^pS^_M&cDs}NgXlR>16AHkSK-4TRaJSh#h&p!-!vQY%f+bmn6x`4fwTp z$727L^y`~!exvmE^W&#@uY!NxJi`g!i#(++!)?iJ(1)2Wk;RN zFK&O4eTkP$Xn~4bB|q8y(btx$R#D`O@epi4ofcETrx!IM(kWNEe42Qh(8*KqfP(c0 zouBl6>Fc_zM+V;F3znbo{x#%!?mH3`_ANJ?y7ppxS@glg#S9^MXu|FM&ynpz3o&Qh z2ujAHLF3($pH}0jXQsa#?t--TnF1P73b?4`KeJ9^qK-USHE)4!IYgMn-7z|=ALF5SNGkrtPG@Y~niUQV2?g$vzJN3nZ{7;HZHzWAeQ;5P|@Tl3YHpyznGG4-f4=XflwSJY+58-+wf?~Fg@1p1wkzuu-RF3j2JX37SQUc? zQ4v%`V8z9ZVZVqS8h|@@RpD?n0W<=hk=3Cf8R?d^9YK&e9ZybFY%jdnA)PeHvtBe- zhMLD+SSteHBq*q)d6x{)s1UrsO!byyLS$58WK;sqip$Mk{l)Y(_6hEIBsIjCr5t>( z7CdKUrJTrW%qZ#1z^n*Lb8#VdfzPw~OIL76aC+Rhr<~;4Tl!sw?Rj6hXj4XWa#6Tp z@)kJ~qOV)^Rh*-?aG>ic2*NlC2M7&LUzc9RT6WM%Cpe78`iAowe!>(T0jo&ivn8-7 zs{Qa@cGy$rE-3AY0V(l8wjI^uB8Lchj@?L}fYal^>T9z;8juH@?rG&g-t+R2dVDBe zq!K%{e-rT5jX19`(bP23LUN4+_zh2KD~EAYzhpEO3MUG8@}uBHH@4J zd`>_(K4q&>*k82(dDuC)X6JuPrBBubOg7qZ{?x!r@{%0);*`h*^F|%o?&1wX?Wr4b z1~&cy#PUuES{C#xJ84!z<1tp9sfrR(i%Tu^jnXy;4`Xk;AQCdFC@?V%|; zySdC7qS|uQRcH}EFZH%mMB~7gi}a0utE}ZE_}8PQH8f;H%PN41Cb9R%w5Oi5el^fd z$n{3SqLCnrF##x?4sa^r!O$7NX!}&}V;0ZGQ&K&i%6$3C_dR%I7%gdQ;KT6YZiQrW zk%q<74oVBV>@}CvJ4Wj!d^?#Zwq(b$E1ze4$99DuNg?6t9H}k_|D7KWD7i0-g*EO7 z;5{hSIYE4DMOK3H%|f5Edx+S0VI0Yw!tsaRS2&Il2)ea^8R5TG72BrJue|f_{2UHa z@w;^c|K3da#$TB0P3;MPlF7RuQeXT$ zS<<|C0OF(k)>fr&wOB=gP8!Qm>F41u;3esv7_0l%QHt(~+n; zf!G6%hp;Gfa9L9=AceiZs~tK+Tf*Wof=4!u{nIO90jH@iS0l+#%8=~%ASzFv7zqSB^?!@N7)kp0t&tCGLmzXSRMRyxCmCYUD2!B`? zhs$4%KO~m=VFk3Buv9osha{v+mAEq=ik3RdK@;WWTV_g&-$U4IM{1IhGX{pAu%Z&H zFfwCpUsX%RKg);B@7OUzZ{Hn{q6Vv!3#8fAg!P$IEx<0vAx;GU%}0{VIsmFBPq_mb zpe^BChDK>sc-WLKl<6 zwbW|e&d&dv9Wu0goueyu>(JyPx1mz0v4E?cJjFuKF71Q1)AL8jHO$!fYT3(;U3Re* zPPOe%*O+@JYt1bW`!W_1!mN&=w3G9ru1XsmwfS~BJ))PhD(+_J_^N6j)sx5VwbWK| zwRyC?W<`pOCY)b#AS?rluxuuGf-AJ=D!M36l{ua?@SJ5>e!IBr3CXIxWw5xUZ@Xrw z_R@%?{>d%Ld4p}nEsiA@v*nc6Ah!MUs?GA7e5Q5lPpp0@`%5xY$C;{%rz24$;vR#* zBP=a{)K#CwIY%p} zXVdxTQ^HS@O&~eIftU+Qt^~(DGxrdi3k}DdT^I7Iy5SMOp$QuD8s;+93YQ!OY{eB24%xY7ml@|M7I(Nb@K_-?F;2?et|CKkuZK_>+>Lvg!>JE~wN`BI|_h6$qi!P)+K-1Hh(1;a`os z55)4Q{oJiA(lQM#;w#Ta%T0jDNXIPM_bgESMCDEg6rM33anEr}=|Fn6)|jBP6Y}u{ zv9@%7*#RI9;fv;Yii5CI+KrRdr0DKh=L>)eO4q$1zmcSmglsV`*N(x=&Wx`*v!!hn6X-l0 zP_m;X??O(skcj+oS$cIdKhfT%ABAzz3w^la-Ucw?yBPEC+=Pe_vU8nd-HV5YX6X8r zZih&j^eLU=%*;VzhUyoLF;#8QsEfmByk+Y~caBqSvQaaWf2a{JKB9B>V&r?l^rXaC z8)6AdR@Qy_BxQrE2Fk?ewD!SwLuMj@&d_n5RZFf7=>O>hzVE*seW3U?_p|R^CfoY`?|#x9)-*yjv#lo&zP=uI`M?J zbzC<^3x7GfXA4{FZ72{PE*-mNHyy59Q;kYG@BB~NhTd6pm2Oj=_ zizmD?MKVRkT^KmXuhsk?eRQllPo2Ubk=uCKiZ&u3Xjj~<(!M94c)Tez@9M1Gfs5JV z->@II)CDJOXTtPrQudNjE}Eltbjq>6KiwAwqvAKd^|g!exgLG3;wP+#mZYr`cy3#39e653d=jrR-ulW|h#ddHu(m9mFoW~2yE zz5?dB%6vF}+`-&-W8vy^OCxm3_{02royjvmwjlp+eQDzFVEUiyO#gLv%QdDSI#3W* z?3!lL8clTaNo-DVJw@ynq?q!%6hTQi35&^>P85G$TqNt78%9_sSJt2RThO|JzM$iL zg|wjxdMC2|Icc5rX*qPL(coL!u>-xxz-rFiC!6hD1IR%|HSRsV3>Kq~&vJ=s3M5y8SG%YBQ|{^l#LGlg!D?E>2yR*eV%9m$_J6VGQ~AIh&P$_aFbh zULr0Z$QE!QpkP=aAeR4ny<#3Fwyw@rZf4?Ewq`;mCVv}xaz+3ni+}a=k~P+yaWt^L z@w67!DqVf7D%7XtXX5xBW;Co|HvQ8WR1k?r2cZD%U;2$bsM%u8{JUJ5Z0k= zZJARv^vFkmWx15CB=rb=D4${+#DVqy5$C%bf`!T0+epLJLnh1jwCdb*zuCL}eEFvE z{rO1%gxg>1!W(I!owu*mJZ0@6FM(?C+d*CeceZRW_4id*D9p5nzMY&{mWqrJomjIZ z97ZNnZ3_%Hx8dn;H>p8m7F#^2;T%yZ3H;a&N7tm=Lvs&lgJLW{V1@h&6Vy~!+Ffbb zv(n3+v)_D$}dqd!2>Y2B)#<+o}LH#%ogGi2-?xRIH)1!SD)u-L65B&bsJTC=LiaF+YOCif2dUX6uAA|#+vNR z>U+KQekVGon)Yi<93(d!(yw1h3&X0N(PxN2{%vn}cnV?rYw z$N^}_o!XUB!mckL`yO1rnUaI4wrOeQ(+&k?2mi47hzxSD`N#-byqd1IhEoh!PGq>t z_MRy{5B0eKY>;Ao3z$RUU7U+i?iX^&r739F)itdrTpAi-NN0=?^m%?{A9Ly2pVv>Lqs6moTP?T2-AHqFD-o_ znVr|7OAS#AEH}h8SRPQ@NGG47dO}l=t07__+iK8nHw^(AHx&Wb<%jPc$$jl6_p(b$ z)!pi(0fQodCHfM)KMEMUR&UID>}m^(!{C^U7sBDOA)$VThRCI0_+2=( zV8mMq0R(#z;C|7$m>$>`tX+T|xGt(+Y48@ZYu#z;0pCgYgmMVbFb!$?%yhZqP_nhn zy4<#3P1oQ#2b51NU1mGnHP$cf0j-YOgAA}A$QoL6JVLcmExs(kU{4z;PBHJD%_=0F z>+sQV`mzijSIT7xn%PiDKHOujX;n|M&qr1T@rOxTdxtZ!&u&3HHFLYD5$RLQ=heur zb>+AFokUVQeJy-#LP*^)spt{mb@Mqe=A~-4p0b+Bt|pZ+@CY+%x}9f}izU5;4&QFE zO1bhg&A4uC1)Zb67kuowWY4xbo&J=%yoXlFB)&$d*-}kjBu|w!^zbD1YPc0-#XTJr z)pm2RDy%J3jlqSMq|o%xGS$bPwn4AqitC6&e?pqWcjWPt{3I{>CBy;hg0Umh#c;hU3RhCUX=8aR>rmd` z7Orw(5tcM{|-^J?ZAA9KP|)X6n9$-kvr#j5YDecTM6n z&07(nD^qb8hpF0B^z^pQ*%5ePYkv&FabrlI61ntiVp!!C8y^}|<2xgAd#FY=8b*y( zuQOuvy2`Ii^`VBNJB&R!0{hABYX55ooCAJSSevl4RPqEGb)iy_0H}v@vFwFzD%>#I>)3PsouQ+_Kkbqy*kKdHdfkN7NBcq%V{x^fSxgXpg7$bF& zj!6AQbDY(1u#1_A#1UO9AxiZaCVN2F0wGXdY*g@x$ByvUA?ePdide0dmr#}udE%K| z3*k}Vv2Ew2u1FXBaVA6aerI36R&rzEZeDDCl5!t0J=ug6kuNZzH>3i_VN`%BsaVB3 zQYw|Xub_SGf{)F{$ZX5`Jc!X!;eybjP+o$I{Z^Hsj@D=E{MnnL+TbC@HEU2DjG{3-LDGIbq()U87x4eS;JXnSh;lRlJ z>EL3D>wHt-+wTjQF$fGyDO$>d+(fq@bPpLBS~xA~R=3JPbS{tzN(u~m#Po!?H;IYv zE;?8%^vle|%#oux(Lj!YzBKv+Fd}*Ur-dCBoX*t{KeNM*n~ZPYJ4NNKkI^MFbz9!v z4(Bvm*Kc!-$%VFEewYJKz-CQN{`2}KX4*CeJEs+Q(!kI%hN1!1P6iOq?ovz}X0IOi z)YfWpwW@pK08^69#wSyCZkX9?uZD?C^@rw^Y?gLS_xmFKkooyx$*^5#cPqntNTtSG zlP>XLMj2!VF^0k#ole7`-c~*~+_T5ls?x4)ah(j8vo_ zwb%S8qoaZqY0-$ZI+ViIA_1~~rAH7K_+yFS{0rT@eQtTAdz#8E5VpwnW!zJ_^{Utv zlW5Iar3V5t&H4D6A=>?mq;G92;1cg9a2sf;gY9pJDVKn$DYdQlvfXq}zz8#LyPGq@ z+`YUMD;^-6w&r-82JL7mA8&M~Pj@aK!m{0+^v<|t%APYf7`}jGEhdYLqsHW-Le9TL z_hZZ1gbrz7$f9^fAzVIP30^KIz!!#+DRLL+qMszvI_BpOSmjtl$hh;&UeM{ER@INV zcI}VbiVTPoN|iSna@=7XkP&-4#06C};8ajbxJ4Gcq8(vWv4*&X8bM^T$mBk75Q92j z1v&%a;OSKc8EIrodmIiw$lOES2hzGDcjjB`kEDfJe{r}yE6`eZL zEB`9u>Cl0IsQ+t}`-cx}{6jqcANucqIB>Qmga_&<+80E2Q|VHHQ$YlAt{6`Qu`HA3 z03s0-sSlwbvgi&_R8s={6<~M^pGvBNjKOa>tWenzS8s zR>L7R5aZ=mSU{f?ib4Grx$AeFvtO5N|D>9#)ChH#Fny2maHWHOf2G=#<9Myot#+4u zWVa6d^Vseq_0=#AYS(-m$Lp;*8nC_6jXIjEM`omUmtH@QDs3|G)i4j*#_?#UYVZvJ z?YjT-?!4Q{BNun;dKBWLEw2C-VeAz`%?A>p;)PL}TAZn5j~HK>v1W&anteARlE+~+ zj>c(F;?qO3pXBb|#OZdQnm<4xWmn~;DR5SDMxt0UK_F^&eD|KZ=O;tO3vy4@4h^;2 zUL~-z`-P1aOe?|ZC1BgVsL)2^J-&vIFI%q@40w0{jjEfeVl)i9(~bt2z#2Vm)p`V_ z1;6$Ae7=YXk#=Qkd24Y23t&GvRxaOoad~NbJ+6pxqzJ>FY#Td7@`N5xp!n(c!=RE& z&<<@^a$_Ys8jqz4|5Nk#FY$~|FPC0`*a5HH!|Gssa9=~66&xG9)|=pOOJ2KE5|YrR zw!w6K2aC=J$t?L-;}5hn6mHd%hC;p8P|Dgh6D>hGnXPgi;6r+eA=?f72y9(Cf_ho{ zH6#)uD&R=73^$$NE;5piWX2bzR67fQ)`b=85o0eOLGI4c-Tb@-KNi2pz=Ke@SDcPn za$AxXib84`!Sf;Z3B@TSo`Dz7GM5Kf(@PR>Ghzi=BBxK8wRp>YQoXm+iL>H*Jo9M3 z6w&E?BC8AFTFT&Tv8zf+m9<&S&%dIaZ)Aoqkak_$r-2{$d~0g2oLETx9Y`eOAf14QXEQw3tJne;fdzl@wV#TFXSLXM2428F-Q}t+n2g%vPRMUzYPvzQ9f# zu(liiJem9P*?0%V@RwA7F53r~|I!Ty)<*AsMX3J{_4&}{6pT%Tpw>)^|DJ)>gpS~1rNEh z0$D?uO8mG?H;2BwM5a*26^7YO$XjUm40XmBsb63MoR;bJh63J;OngS5sSI+o2HA;W zdZV#8pDpC9Oez&L8loZO)MClRz!_!WD&QRtQxnazhT%Vj6Wl4G11nUk8*vSeVab@N#oJ}`KyJv+8Mo@T1-pqZ1t|?cnaVOd;1(h9 z!$DrN=jcGsVYE-0-n?oCJ^4x)F}E;UaD-LZUIzcD?W^ficqJWM%QLy6QikrM1aKZC zi{?;oKwq^Vsr|&`i{jIphA8S6G4)$KGvpULjH%9u(Dq247;R#l&I0{IhcC|oBF*Al zvLo7Xte=C{aIt*otJD}BUq)|_pdR>{zBMT< z(^1RpZv*l*m*OV^8>9&asGBo8h*_4q*)-eCv*|Pq=XNGrZE)^(SF7^{QE_~4VDB(o zVcPA_!G+2CAtLbl+`=Q~9iW`4ZRLku!uB?;tWqVjB0lEOf}2RD7dJ=BExy=<9wkb- z9&7{XFA%n#JsHYN8t5d~=T~5DcW4$B%3M+nNvC2`0!#@sckqlzo5;hhGi(D9=*A4` z5ynobawSPRtWn&CDLEs3Xf`(8^zDP=NdF~F^s&={l7(aw&EG}KWpMjtmz7j_VLO;@ zM2NVLDxZ@GIv7*gzl1 zjq78tv*8#WSY`}Su0&C;2F$Ze(q>F(@Wm^Gw!)(j;dk9Ad{STaxn)IV9FZhm*n+U} zi;4y*3v%A`_c7a__DJ8D1b@dl0Std3F||4Wtvi)fCcBRh!X9$1x!_VzUh>*S5s!oq z;qd{J_r79EL2wIeiGAqFstWtkfIJpjVh%zFo*=55B9Zq~y0=^iqHWfQl@O!Ak;(o*m!pZqe9 z%U2oDOhR)BvW8&F70L;2TpkzIutIvNQaTjjs5V#8mV4!NQ}zN=i`i@WI1z0eN-iCS z;vL-Wxc^Vc_qK<5RPh(}*8dLT{~GzE{w2o$2kMFaEl&q zP{V=>&3kW7tWaK-Exy{~`v4J0U#OZBk{a9{&)&QG18L@6=bsZ1zC_d{{pKZ-Ey>I> z;8H0t4bwyQqgu4hmO`3|4K{R*5>qnQ&gOfdy?z`XD%e5+pTDzUt3`k^u~SaL&XMe= z9*h#kT(*Q9jO#w2Hd|Mr-%DV8i_1{J1MU~XJ3!WUplhXDYBpJH><0OU`**nIvPIof z|N8@I=wA)sf45SAvx||f?Z5uB$kz1qL3Ky_{%RPdP5iN-D2!p5scq}buuC00C@jom zhfGKm3|f?Z0iQ|K$Z~!`8{nmAS1r+fp6r#YDOS8V*;K&Gs7Lc&f^$RC66O|)28oh`NHy&vq zJh+hAw8+ybTB0@VhWN^0iiTnLsCWbS_y`^gs!LX!Lw{yE``!UVzrV24tP8o;I6-65 z1MUiHw^{bB15tmrVT*7-#sj6cs~z`wk52YQJ*TG{SE;KTm#Hf#a~|<(|ImHH17nNM z`Ub{+J3dMD!)mzC8b(2tZtokKW5pAwHa?NFiso~# z1*iaNh4lQ4TS)|@G)H4dZV@l*Vd;Rw;-;odDhW2&lJ%m@jz+Panv7LQm~2Js6rOW3 z0_&2cW^b^MYW3)@o;neZ<{B4c#m48dAl$GCc=$>ErDe|?y@z`$uq3xd(%aAsX)D%l z>y*SQ%My`yDP*zof|3@_w#cjaW_YW4BdA;#Glg1RQcJGY*CJ9`H{@|D+*e~*457kd z73p<%fB^PV!Ybw@)Dr%(ZJbX}xmCStCYv#K3O32ej{$9IzM^I{6FJ8!(=azt7RWf4 z7ib0UOPqN40X!wOnFOoddd8`!_IN~9O)#HRTyjfc#&MCZ zZAMzOVB=;qwt8gV?{Y2?b=iSZG~RF~uyx18K)IDFLl})G1v@$(s{O4@RJ%OTJyF+Cpcx4jmy|F3euCnMK!P2WTDu5j z{{gD$=M*pH!GGzL%P)V2*ROm>!$Y=z|D`!_yY6e7SU$~a5q8?hZGgaYqaiLnkK%?0 zs#oI%;zOxF@g*@(V4p!$7dS1rOr6GVs6uYCTt2h)eB4?(&w8{#o)s#%gN@BBosRUe z)@P@8_Zm89pr~)b>e{tbPC~&_MR--iB{=)y;INU5#)@Gix-YpgP<-c2Ms{9zuCX|3 z!p(?VaXww&(w&uBHzoT%!A2=3HAP>SDxcljrego7rY|%hxy3XlODWffO_%g|l+7Y_ zqV(xbu)s4lV=l7M;f>vJl{`6qBm>#ZeMA}kXb97Z)?R97EkoI?x6Lp0yu1Z>PS?2{ z0QQ(8D)|lc9CO3B~e(pQM&5(1y&y=e>C^X$`)_&XuaI!IgDTVqt31wX#n+@!a_A0ZQkA zCJ2@M_4Gb5MfCrm5UPggeyh)8 zO9?`B0J#rkoCx(R0I!ko_2?iO@|oRf1;3r+i)w-2&j?=;NVIdPFsB)`|IC0zk6r9c zRrkfxWsiJ(#8QndNJj@{@WP2Ackr|r1VxV{7S&rSU(^)-M8gV>@UzOLXu9K<{6e{T zXJ6b92r$!|lwjhmgqkdswY&}c)KW4A)-ac%sU;2^fvq7gfUW4Bw$b!i@duy1CAxSn z(pyh$^Z=&O-q<{bZUP+$U}=*#M9uVc>CQVgDs4swy5&8RAHZ~$)hrTF4W zPsSa~qYv_0mJnF89RnnJTH`3}w4?~epFl=D(35$ zWa07ON$`OMBOHgCmfO(9RFc<)?$x)N}Jd2A(<*Ll7+4jrRt9w zwGxExUXd9VB#I|DwfxvJ;HZ8Q{37^wDhaZ%O!oO(HpcqfLH%#a#!~;Jl7F5>EX_=8 z{()l2NqPz>La3qJR;_v+wlK>GsHl;uRA8%j`A|yH@k5r%55S9{*Cp%uw6t`qc1!*T za2OeqtQj7sAp#Q~=5Fs&aCR9v>5V+s&RdNvo&H~6FJOjvaj--2sYYBvMq;55%z8^o z|BJDA4vzfow#DO#ZQHh;Oq_{r+qP{R9ox2TOgwQiv7Ow!zjN+A@BN;0tA2lUb#+zO z(^b89eV)D7UVE+h{mcNc6&GtpOqDn_?VAQ)Vob$hlFwW%xh>D#wml{t&Ofmm_d_+; zKDxzdr}`n2Rw`DtyIjrG)eD0vut$}dJAZ0AohZ+ZQdWXn_Z@dI_y=7t3q8x#pDI-K z2VVc&EGq445Rq-j0=U=Zx`oBaBjsefY;%)Co>J3v4l8V(T8H?49_@;K6q#r~Wwppc z4XW0(4k}cP=5ex>-Xt3oATZ~bBWKv)aw|I|Lx=9C1s~&b77idz({&q3T(Y(KbWO?+ zmcZ6?WeUsGk6>km*~234YC+2e6Zxdl~<_g2J|IE`GH%n<%PRv-50; zH{tnVts*S5*_RxFT9eM0z-pksIb^drUq4>QSww=u;UFCv2AhOuXE*V4z?MM`|ABOC4P;OfhS(M{1|c%QZ=!%rQTDFx`+}?Kdx$&FU?Y<$x;j7z=(;Lyz+?EE>ov!8vvMtSzG!nMie zsBa9t8as#2nH}n8xzN%W%U$#MHNXmDUVr@GX{?(=yI=4vks|V)!-W5jHsU|h_&+kY zS_8^kd3jlYqOoiI`ZqBVY!(UfnAGny!FowZWY_@YR0z!nG7m{{)4OS$q&YDyw6vC$ zm4!$h>*|!2LbMbxS+VM6&DIrL*X4DeMO!@#EzMVfr)e4Tagn~AQHIU8?e61TuhcKD zr!F4(kEebk(Wdk-?4oXM(rJwanS>Jc%<>R(siF+>+5*CqJLecP_we33iTFTXr6W^G z7M?LPC-qFHK;E!fxCP)`8rkxZyFk{EV;G-|kwf4b$c1k0atD?85+|4V%YATWMG|?K zLyLrws36p%Qz6{}>7b>)$pe>mR+=IWuGrX{3ZPZXF3plvuv5Huax86}KX*lbPVr}L z{C#lDjdDeHr~?l|)Vp_}T|%$qF&q#U;ClHEPVuS+Jg~NjC1RP=17=aQKGOcJ6B3mp z8?4*-fAD~}sX*=E6!}^u8)+m2j<&FSW%pYr_d|p_{28DZ#Cz0@NF=gC-o$MY?8Ca8 zr5Y8DSR^*urS~rhpX^05r30Ik#2>*dIOGxRm0#0YX@YQ%Mg5b6dXlS!4{7O_kdaW8PFSdj1=ryI-=5$fiieGK{LZ+SX(1b=MNL!q#lN zv98?fqqTUH8r8C7v(cx#BQ5P9W>- zmW93;eH6T`vuJ~rqtIBg%A6>q>gnWb3X!r0wh_q;211+Om&?nvYzL1hhtjB zK_7G3!n7PL>d!kj){HQE zE8(%J%dWLh1_k%gVXTZt zEdT09XSKAx27Ncaq|(vzL3gm83q>6CAw<$fTnMU05*xAe&rDfCiu`u^1)CD<>sx0i z*hr^N_TeN89G(nunZoLBf^81#pmM}>JgD@Nn1l*lN#a=B=9pN%tmvYFjFIoKe_(GF z-26x{(KXdfsQL7Uv6UtDuYwV`;8V3w>oT_I<`Ccz3QqK9tYT5ZQzbop{=I=!pMOCb zCU68`n?^DT%^&m>A%+-~#lvF!7`L7a{z<3JqIlk1$<||_J}vW1U9Y&eX<}l8##6i( zZcTT@2`9(Mecptm@{3A_Y(X`w9K0EwtPq~O!16bq{7c0f7#(3wn-^)h zxV&M~iiF!{-6A@>o;$RzQ5A50kxXYj!tcgme=Qjrbje~;5X2xryU;vH|6bE(8z^<7 zQ>BG7_c*JG8~K7Oe68i#0~C$v?-t@~@r3t2inUnLT(c=URpA9kA8uq9PKU(Ps(LVH zqgcqW>Gm?6oV#AldDPKVRcEyQIdTT`Qa1j~vS{<;SwyTdr&3*t?J)y=M7q*CzucZ&B0M=joT zBbj@*SY;o2^_h*>R0e({!QHF0=)0hOj^B^d*m>SnRrwq>MolNSgl^~r8GR#mDWGYEIJA8B<|{{j?-7p zVnV$zancW3&JVDtVpIlI|5djKq0(w$KxEFzEiiL=h5Jw~4Le23@s(mYyXWL9SX6Ot zmb)sZaly_P%BeX_9 zw&{yBef8tFm+%=--m*J|o~+Xg3N+$IH)t)=fqD+|fEk4AAZ&!wcN5=mi~Vvo^i`}> z#_3ahR}Ju)(Px7kev#JGcSwPXJ2id9%Qd2A#Uc@t8~egZ8;iC{e! z%=CGJOD1}j!HW_sgbi_8suYnn4#Ou}%9u)dXd3huFIb!ytlX>Denx@pCS-Nj$`VO&j@(z!kKSP0hE4;YIP#w9ta=3DO$7f*x zc9M4&NK%IrVmZAe=r@skWD`AEWH=g+r|*13Ss$+{c_R!b?>?UaGXlw*8qDmY#xlR= z<0XFbs2t?8i^G~m?b|!Hal^ZjRjt<@a? z%({Gn14b4-a|#uY^=@iiKH+k?~~wTj5K1A&hU z2^9-HTC)7zpoWK|$JXaBL6C z#qSNYtY>65T@Zs&-0cHeu|RX(Pxz6vTITdzJdYippF zC-EB+n4}#lM7`2Ry~SO>FxhKboIAF#Z{1wqxaCb{#yEFhLuX;Rx(Lz%T`Xo1+a2M}7D+@wol2)OJs$TwtRNJ={( zD@#zTUEE}#Fz#&(EoD|SV#bayvr&E0vzmb%H?o~46|FAcx?r4$N z&67W3mdip-T1RIxwSm_&(%U|+WvtGBj*}t69XVd&ebn>KOuL(7Y8cV?THd-(+9>G7*Nt%T zcH;`p={`SOjaf7hNd(=37Lz3-51;58JffzIPgGs_7xIOsB5p2t&@v1mKS$2D$*GQ6 zM(IR*j4{nri7NMK9xlDy-hJW6sW|ZiDRaFiayj%;(%51DN!ZCCCXz+0Vm#};70nOx zJ#yA0P3p^1DED;jGdPbQWo0WATN=&2(QybbVdhd=Vq*liDk`c7iZ?*AKEYC#SY&2g z&Q(Ci)MJ{mEat$ZdSwTjf6h~roanYh2?9j$CF@4hjj_f35kTKuGHvIs9}Re@iKMxS-OI*`0S z6s)fOtz}O$T?PLFVSeOjSO26$@u`e<>k(OSP!&YstH3ANh>)mzmKGNOwOawq-MPXe zy4xbeUAl6tamnx))-`Gi2uV5>9n(73yS)Ukma4*7fI8PaEwa)dWHs6QA6>$}7?(L8 ztN8M}?{Tf!Zu22J5?2@95&rQ|F7=FK-hihT-vDp!5JCcWrVogEnp;CHenAZ)+E+K5 z$Cffk5sNwD_?4+ymgcHR(5xgt20Z8M`2*;MzOM#>yhk{r3x=EyM226wb&!+j`W<%* zSc&|`8!>dn9D@!pYow~(DsY_naSx7(Z4i>cu#hA5=;IuI88}7f%)bRkuY2B;+9Uep zpXcvFWkJ!mQai63BgNXG26$5kyhZ2&*3Q_tk)Ii4M>@p~_~q_cE!|^A;_MHB;7s#9 zKzMzK{lIxotjc};k67^Xsl-gS!^*m*m6kn|sbdun`O?dUkJ{0cmI0-_2y=lTAfn*Y zKg*A-2sJq)CCJgY0LF-VQvl&6HIXZyxo2#!O&6fOhbHXC?%1cMc6y^*dOS{f$=137Ds1m01qs`>iUQ49JijsaQ( zksqV9@&?il$|4Ua%4!O15>Zy&%gBY&wgqB>XA3!EldQ%1CRSM(pp#k~-pkcCg4LAT zXE=puHbgsw)!xtc@P4r~Z}nTF=D2~j(6D%gTBw$(`Fc=OOQ0kiW$_RDd=hcO0t97h zb86S5r=>(@VGy1&#S$Kg_H@7G^;8Ue)X5Y+IWUi`o;mpvoV)`fcVk4FpcT|;EG!;? zHG^zrVVZOm>1KFaHlaogcWj(v!S)O(Aa|Vo?S|P z5|6b{qkH(USa*Z7-y_Uvty_Z1|B{rTS^qmEMLEYUSk03_Fg&!O3BMo{b^*`3SHvl0 zhnLTe^_vVIdcSHe)SQE}r~2dq)VZJ!aSKR?RS<(9lzkYo&dQ?mubnWmgMM37Nudwo z3Vz@R{=m2gENUE3V4NbIzAA$H1z0pagz94-PTJyX{b$yndsdKptmlKQKaaHj@3=ED zc7L?p@%ui|RegVYutK$64q4pe9+5sv34QUpo)u{1ci?)_7gXQd{PL>b0l(LI#rJmN zGuO+%GO`xneFOOr4EU(Wg}_%bhzUf;d@TU+V*2#}!2OLwg~%D;1FAu=Un>OgjPb3S z7l(riiCwgghC=Lm5hWGf5NdGp#01xQ59`HJcLXbUR3&n%P(+W2q$h2Qd z*6+-QXJ*&Kvk9ht0f0*rO_|FMBALen{j7T1l%=Q>gf#kma zQlg#I9+HB+z*5BMxdesMND`_W;q5|FaEURFk|~&{@qY32N$G$2B=&Po{=!)x5b!#n zxLzblkq{yj05#O7(GRuT39(06FJlalyv<#K4m}+vs>9@q-&31@1(QBv82{}Zkns~K ze{eHC_RDX0#^A*JQTwF`a=IkE6Ze@j#-8Q`tTT?k9`^ZhA~3eCZJ-Jr{~7Cx;H4A3 zcZ+Zj{mzFZbVvQ6U~n>$U2ZotGsERZ@}VKrgGh0xM;Jzt29%TX6_&CWzg+YYMozrM z`nutuS)_0dCM8UVaKRj804J4i%z2BA_8A4OJRQ$N(P9Mfn-gF;4#q788C@9XR0O3< zsoS4wIoyt046d+LnSCJOy@B@Uz*#GGd#+Ln1ek5Dv>(ZtD@tgZlPnZZJGBLr^JK+!$$?A_fA3LOrkoDRH&l7 zcMcD$Hsjko3`-{bn)jPL6E9Ds{WskMrivsUu5apD z?grQO@W7i5+%X&E&p|RBaEZ(sGLR@~(y^BI@lDMot^Ll?!`90KT!JXUhYS`ZgX3jnu@Ja^seA*M5R@f`=`ynQV4rc$uT1mvE?@tz)TN<=&H1%Z?5yjxcpO+6y_R z6EPuPKM5uxKpmZfT(WKjRRNHs@ib)F5WAP7QCADvmCSD#hPz$V10wiD&{NXyEwx5S z6NE`3z!IS^$s7m}PCwQutVQ#~w+V z=+~->DI*bR2j0^@dMr9`p>q^Ny~NrAVxrJtX2DUveic5vM%#N*XO|?YAWwNI$Q)_) zvE|L(L1jP@F%gOGtnlXtIv2&1i8q<)Xfz8O3G^Ea~e*HJsQgBxWL(yuLY+jqUK zRE~`-zklrGog(X}$9@ZVUw!8*=l`6mzYLtsg`AvBYz(cxmAhr^j0~(rzXdiOEeu_p zE$sf2(w(BPAvO5DlaN&uQ$4@p-b?fRs}d7&2UQ4Fh?1Hzu*YVjcndqJLw0#q@fR4u zJCJ}>_7-|QbvOfylj+e^_L`5Ep9gqd>XI3-O?Wp z-gt*P29f$Tx(mtS`0d05nHH=gm~Po_^OxxUwV294BDKT>PHVlC5bndncxGR!n(OOm znsNt@Q&N{TLrmsoKFw0&_M9$&+C24`sIXGWgQaz=kY;S{?w`z^Q0JXXBKFLj0w0U6P*+jPKyZHX9F#b0D1$&(- zrm8PJd?+SrVf^JlfTM^qGDK&-p2Kdfg?f>^%>1n8bu&byH(huaocL>l@f%c*QkX2i znl}VZ4R1en4S&Bcqw?$=Zi7ohqB$Jw9x`aM#>pHc0x z0$!q7iFu zZ`tryM70qBI6JWWTF9EjgG@>6SRzsd}3h+4D8d~@CR07P$LJ}MFsYi-*O%XVvD@yT|rJ+Mk zDllJ7$n0V&A!0flbOf)HE6P_afPWZmbhpliqJuw=-h+r;WGk|ntkWN(8tKlYpq5Ow z(@%s>IN8nHRaYb*^d;M(D$zGCv5C|uqmsDjwy4g=Lz>*OhO3z=)VD}C<65;`89Ye} zSCxrv#ILzIpEx1KdLPlM&%Cctf@FqTKvNPXC&`*H9=l=D3r!GLM?UV zOxa(8ZsB`&+76S-_xuj?G#wXBfDY@Z_tMpXJS7^mp z@YX&u0jYw2A+Z+bD#6sgVK5ZgdPSJV3>{K^4~%HV?rn~4D)*2H!67Y>0aOmzup`{D zzDp3c9yEbGCY$U<8biJ_gB*`jluz1ShUd!QUIQJ$*1;MXCMApJ^m*Fiv88RZ zFopLViw}{$Tyhh_{MLGIE2~sZ)t0VvoW%=8qKZ>h=adTe3QM$&$PO2lfqH@brt!9j ziePM8$!CgE9iz6B<6_wyTQj?qYa;eC^{x_0wuwV~W+^fZmFco-o%wsKSnjXFEx02V zF5C2t)T6Gw$Kf^_c;Ei3G~uC8SM-xyycmXyC2hAVi-IfXqhu$$-C=*|X?R0~hu z8`J6TdgflslhrmDZq1f?GXF7*ALeMmOEpRDg(s*H`4>_NAr`2uqF;k;JQ+8>A|_6ZNsNLECC%NNEb1Y1dP zbIEmNpK)#XagtL4R6BC{C5T(+=yA-(Z|Ap}U-AfZM#gwVpus3(gPn}Q$CExObJ5AC z)ff9Yk?wZ}dZ-^)?cbb9Fw#EjqQ8jxF4G3=L?Ra zg_)0QDMV1y^A^>HRI$x?Op@t;oj&H@1xt4SZ9(kifQ zb59B*`M99Td7@aZ3UWvj1rD0sE)d=BsBuW*KwkCds7ay(7*01_+L}b~7)VHI>F_!{ zyxg-&nCO?v#KOUec0{OOKy+sjWA;8rTE|Lv6I9H?CI?H(mUm8VXGwU$49LGpz&{nQp2}dinE1@lZ1iox6{ghN&v^GZv9J${7WaXj)<0S4g_uiJ&JCZ zr8-hsu`U%N;+9N^@&Q0^kVPB3)wY(rr}p7{p0qFHb3NUUHJb672+wRZs`gd1UjKPX z4o6zljKKA+Kkj?H>Ew63o%QjyBk&1!P22;MkD>sM0=z_s-G{mTixJCT9@_|*(p^bz zJ8?ZZ&;pzV+7#6Mn`_U-)k8Pjg?a;|Oe^us^PoPY$Va~yi8|?+&=y$f+lABT<*pZr zP}D{~Pq1Qyni+@|aP;ixO~mbEW9#c0OU#YbDZIaw=_&$K%Ep2f%hO^&P67hApZe`x zv8b`Mz@?M_7-)b!lkQKk)JXXUuT|B8kJlvqRmRpxtQDgvrHMXC1B$M@Y%Me!BSx3P z#2Eawl$HleZhhTS6Txm>lN_+I`>eV$&v9fOg)%zVn3O5mI*lAl>QcHuW6!Kixmq`X zBCZ*Ck6OYtDiK!N47>jxI&O2a9x7M|i^IagRr-fmrmikEQGgw%J7bO|)*$2FW95O4 zeBs>KR)izRG1gRVL;F*sr8A}aRHO0gc$$j&ds8CIO1=Gwq1%_~E)CWNn9pCtBE}+`Jelk4{>S)M)`Ll=!~gnn1yq^EX(+y*ik@3Ou0qU`IgYi3*doM+5&dU!cho$pZ zn%lhKeZkS72P?Cf68<#kll_6OAO26bIbueZx**j6o;I0cS^XiL`y+>{cD}gd%lux} z)3N>MaE24WBZ}s0ApfdM;5J_Ny}rfUyxfkC``Awo2#sgLnGPewK};dORuT?@I6(5~ z?kE)Qh$L&fwJXzK){iYx!l5$Tt|^D~MkGZPA}(o6f7w~O2G6Vvzdo*a;iXzk$B66$ zwF#;wM7A+(;uFG4+UAY(2`*3XXx|V$K8AYu#ECJYSl@S=uZW$ksfC$~qrrbQj4??z-)uz0QL}>k^?fPnJTPw% zGz)~?B4}u0CzOf@l^um}HZzbaIwPmb<)< zi_3@E9lc)Qe2_`*Z^HH;1CXOceL=CHpHS{HySy3T%<^NrWQ}G0i4e1xm_K3(+~oi$ zoHl9wzb?Z4j#90DtURtjtgvi7uw8DzHYmtPb;?%8vb9n@bszT=1qr)V_>R%s!92_` zfnHQPANx z<#hIjIMm#*(v*!OXtF+w8kLu`o?VZ5k7{`vw{Yc^qYclpUGIM_PBN1+c{#Vxv&E*@ zxg=W2W~JuV{IuRYw3>LSI1)a!thID@R=bU+cU@DbR^_SXY`MC7HOsCN z!dO4OKV7(E_Z8T#8MA1H`99?Z!r0)qKW_#|29X3#Jb+5+>qUidbeP1NJ@)(qi2S-X zao|f0_tl(O+$R|Qwd$H{_ig|~I1fbp_$NkI!0E;Y z6JrnU{1Ra6^on{9gUUB0mwzP3S%B#h0fjo>JvV~#+X0P~JV=IG=yHG$O+p5O3NUgG zEQ}z6BTp^Fie)Sg<){Z&I8NwPR(=mO4joTLHkJ>|Tnk23E(Bo`FSbPc05lF2-+)X? z6vV3*m~IBHTy*^E!<0nA(tCOJW2G4DsH7)BxLV8kICn5lu6@U*R`w)o9;Ro$i8=Q^V%uH8n3q=+Yf;SFRZu z!+F&PKcH#8cG?aSK_Tl@K9P#8o+jry@gdexz&d(Q=47<7nw@e@FFfIRNL9^)1i@;A z28+$Z#rjv-wj#heI|<&J_DiJ*s}xd-f!{J8jfqOHE`TiHHZVIA8CjkNQ_u;Ery^^t zl1I75&u^`1_q)crO+JT4rx|z2ToSC>)Or@-D zy3S>jW*sNIZR-EBsfyaJ+Jq4BQE4?SePtD2+jY8*%FsSLZ9MY>+wk?}}}AFAw)vr{ml)8LUG-y9>^t!{~|sgpxYc0Gnkg`&~R z-pilJZjr@y5$>B=VMdZ73svct%##v%wdX~9fz6i3Q-zOKJ9wso+h?VME7}SjL=!NUG{J?M&i!>ma`eoEa@IX`5G>B1(7;%}M*%-# zfhJ(W{y;>MRz!Ic8=S}VaBKqh;~7KdnGEHxcL$kA-6E~=!hrN*zw9N+_=odt<$_H_8dbo;0=42wcAETPCVGUr~v(`Uai zb{=D!Qc!dOEU6v)2eHSZq%5iqK?B(JlCq%T6av$Cb4Rko6onlG&?CqaX7Y_C_cOC3 zYZ;_oI(}=>_07}Oep&Ws7x7-R)cc8zfe!SYxJYP``pi$FDS)4Fvw5HH=FiU6xfVqIM!hJ;Rx8c0cB7~aPtNH(Nmm5Vh{ibAoU#J6 zImRCr?(iyu_4W_6AWo3*vxTPUw@vPwy@E0`(>1Qi=%>5eSIrp^`` zK*Y?fK_6F1W>-7UsB)RPC4>>Ps9)f+^MqM}8AUm@tZ->j%&h1M8s*s!LX5&WxQcAh z8mciQej@RPm?660%>{_D+7er>%zX_{s|$Z+;G7_sfNfBgY(zLB4Ey}J9F>zX#K0f6 z?dVNIeEh?EIShmP6>M+d|0wMM85Sa4diw1hrg|ITJ}JDg@o8y>(rF9mXk5M z2@D|NA)-7>wD&wF;S_$KS=eE84`BGw3g0?6wGxu8ys4rwI?9U=*^VF22t3%mbGeOh z`!O-OpF7#Vceu~F`${bW0nYVU9ecmk31V{tF%iv&5hWofC>I~cqAt@u6|R+|HLMMX zVxuSlMFOK_EQ86#E8&KwxIr8S9tj_goWtLv4f@!&h8;Ov41{J~496vp9vX=(LK#j! zAwi*21RAV-LD>9Cw3bV_9X(X3)Kr0-UaB*7Y>t82EQ%!)(&(XuAYtTsYy-dz+w=$ir)VJpe!_$ z6SGpX^i(af3{o=VlFPC);|J8#(=_8#vdxDe|Cok+ANhYwbE*FO`Su2m1~w+&9<_9~ z-|tTU_ACGN`~CNW5WYYBn^B#SwZ(t4%3aPp z;o)|L6Rk569KGxFLUPx@!6OOa+5OjQLK5w&nAmwxkC5rZ|m&HT8G%GVZxB_@ME z>>{rnXUqyiJrT(8GMj_ap#yN_!9-lO5e8mR3cJiK3NE{_UM&=*vIU`YkiL$1%kf+1 z4=jk@7EEj`u(jy$HnzE33ZVW_J4bj}K;vT?T91YlO(|Y0FU4r+VdbmQ97%(J5 zkK*Bed8+C}FcZ@HIgdCMioV%A<*4pw_n}l*{Cr4}a(lq|injK#O?$tyvyE`S%(1`H z_wwRvk#13ElkZvij2MFGOj`fhy?nC^8`Zyo%yVcUAfEr8x&J#A{|moUBAV_^f$hpaUuyQeY3da^ zS9iRgf87YBwfe}>BO+T&Fl%rfpZh#+AM?Dq-k$Bq`vG6G_b4z%Kbd&v>qFjow*mBl z-OylnqOpLg}or7_VNwRg2za3VBK6FUfFX{|TD z`Wt0Vm2H$vdlRWYQJqDmM?JUbVqL*ZQY|5&sY*?!&%P8qhA~5+Af<{MaGo(dl&C5t zE%t!J0 zh6jqANt4ABdPxSTrVV}fLsRQal*)l&_*rFq(Ez}ClEH6LHv{J#v?+H-BZ2)Wy{K@9 z+ovXHq~DiDvm>O~r$LJo!cOuwL+Oa--6;UFE2q@g3N8Qkw5E>ytz^(&($!O47+i~$ zKM+tkAd-RbmP{s_rh+ugTD;lriL~`Xwkad#;_aM?nQ7L_muEFI}U_4$phjvYgleK~`Fo`;GiC07&Hq1F<%p;9Q;tv5b?*QnR%8DYJH3P>Svmv47Y>*LPZJy8_{9H`g6kQpyZU{oJ`m%&p~D=K#KpfoJ@ zn-3cqmHsdtN!f?~w+(t+I`*7GQA#EQC^lUA9(i6=i1PqSAc|ha91I%X&nXzjYaM{8$s&wEx@aVkQ6M{E2 zfzId#&r(XwUNtPcq4Ngze^+XaJA1EK-%&C9j>^9(secqe{}z>hR5CFNveMsVA)m#S zk)_%SidkY-XmMWlVnQ(mNJ>)ooszQ#vaK;!rPmGKXV7am^_F!Lz>;~{VrIO$;!#30XRhE1QqO_~#+Ux;B_D{Nk=grn z8Y0oR^4RqtcYM)7a%@B(XdbZCOqnX#fD{BQTeLvRHd(irHKq=4*jq34`6@VAQR8WG z^%)@5CXnD_T#f%@-l${>y$tfb>2LPmc{~5A82|16mH)R?&r#KKLs7xpN-D`=&Cm^R zvMA6#Ahr<3X>Q7|-qfTY)}32HkAz$_mibYV!I)u>bmjK`qwBe(>za^0Kt*HnFbSdO z1>+ryKCNxmm^)*$XfiDOF2|{-v3KKB?&!(S_Y=Ht@|ir^hLd978xuI&N{k>?(*f8H z=ClxVJK_%_z1TH0eUwm2J+2To7FK4o+n_na)&#VLn1m;!+CX+~WC+qg1?PA~KdOlC zW)C@pw75_xoe=w7i|r9KGIvQ$+3K?L{7TGHwrQM{dCp=Z*D}3kX7E-@sZnup!BImw z*T#a=+WcTwL78exTgBn|iNE3#EsOorO z*kt)gDzHiPt07fmisA2LWN?AymkdqTgr?=loT7z@d`wnlr6oN}@o|&JX!yPzC*Y8d zu6kWlTzE1)ckyBn+0Y^HMN+GA$wUO_LN6W>mxCo!0?oiQvT`z$jbSEu&{UHRU0E8# z%B^wOc@S!yhMT49Y)ww(Xta^8pmPCe@eI5C*ed96)AX9<>))nKx0(sci8gwob_1}4 z0DIL&vsJ1_s%<@y%U*-eX z5rN&(zef-5G~?@r79oZGW1d!WaTqQn0F6RIOa9tJ=0(kdd{d1{<*tHT#cCvl*i>YY zH+L7jq8xZNcTUBqj(S)ztTU!TM!RQ}In*n&Gn<>(60G7}4%WQL!o>hbJqNDSGwl#H z`4k+twp0cj%PsS+NKaxslAEu9!#U3xT1|_KB6`h=PI0SW`P9GTa7caD1}vKEglV8# zjKZR`pluCW19c2fM&ZG)c3T3Um;ir3y(tSCJ7Agl6|b524dy5El{^EQBG?E61H0XY z`bqg!;zhGhyMFl&(o=JWEJ8n~z)xI}A@C0d2hQGvw7nGv)?POU@(kS1m=%`|+^ika zXl8zjS?xqW$WlO?Ewa;vF~XbybHBor$f<%I&*t$F5fynwZlTGj|IjZtVfGa7l&tK} zW>I<69w(cZLu)QIVG|M2xzW@S+70NinQzk&Y0+3WT*cC)rx~04O-^<{JohU_&HL5XdUKW!uFy|i$FB|EMu0eUyW;gsf`XfIc!Z0V zeK&*hPL}f_cX=@iv>K%S5kL;cl_$v?n(Q9f_cChk8Lq$glT|=e+T*8O4H2n<=NGmn z+2*h+v;kBvF>}&0RDS>)B{1!_*XuE8A$Y=G8w^qGMtfudDBsD5>T5SB;Qo}fSkkiV ze^K^M(UthkwrD!&*tTsu>Dacdj_q`~V%r_twr$(Ct&_dKeeXE?fA&4&yASJWJ*}~- zel=@W)tusynfC_YqH4ll>4Eg`Xjs5F7Tj>tTLz<0N3)X<1px_d2yUY>X~y>>93*$) z5PuNMQLf9Bu?AAGO~a_|J2akO1M*@VYN^VxvP0F$2>;Zb9;d5Yfd8P%oFCCoZE$ z4#N$^J8rxYjUE_6{T%Y>MmWfHgScpuGv59#4u6fpTF%~KB^Ae`t1TD_^Ud#DhL+Dm zbY^VAM#MrAmFj{3-BpVSWph2b_Y6gCnCAombVa|1S@DU)2r9W<> zT5L8BB^er3zxKt1v(y&OYk!^aoQisqU zH(g@_o)D~BufUXcPt!Ydom)e|aW{XiMnes2z&rE?og>7|G+tp7&^;q?Qz5S5^yd$i z8lWr4g5nctBHtigX%0%XzIAB8U|T6&JsC4&^hZBw^*aIcuNO47de?|pGXJ4t}BB`L^d8tD`H`i zqrP8?#J@8T#;{^B!KO6J=@OWKhAerih(phML`(Rg7N1XWf1TN>=Z3Do{l_!d~DND&)O)D>ta20}@Lt77qSnVsA7>)uZAaT9bsB>u&aUQl+7GiY2|dAEg@%Al3i316y;&IhQL^8fw_nwS>f60M_-m+!5)S_6EPM7Y)(Nq^8gL7(3 zOiot`6Wy6%vw~a_H?1hLVzIT^i1;HedHgW9-P#)}Y6vF%C=P70X0Tk^z9Te@kPILI z_(gk!k+0%CG)%!WnBjjw*kAKs_lf#=5HXC00s-}oM-Q1aXYLj)(1d!_a7 z*Gg4Fe6F$*ujVjI|79Z5+Pr`us%zW@ln++2l+0hsngv<{mJ%?OfSo_3HJXOCys{Ug z00*YR-(fv<=&%Q!j%b-_ppA$JsTm^_L4x`$k{VpfLI(FMCap%LFAyq;#ns5bR7V+x zO!o;c5y~DyBPqdVQX)8G^G&jWkBy2|oWTw>)?5u}SAsI$RjT#)lTV&Rf8;>u*qXnb z8F%Xb=7#$m)83z%`E;49)t3fHInhtc#kx4wSLLms!*~Z$V?bTyUGiS&m>1P(952(H zuHdv=;o*{;5#X-uAyon`hP}d#U{uDlV?W?_5UjJvf%11hKwe&(&9_~{W)*y1nR5f_ z!N(R74nNK`y8>B!0Bt_Vr!;nc3W>~RiKtGSBkNlsR#-t^&;$W#)f9tTlZz>n*+Fjz z3zXZ;jf(sTM(oDzJt4FJS*8c&;PLTW(IQDFs_5QPy+7yhi1syPCarvqrHFcf&yTy)^O<1EBx;Ir`5W{TIM>{8w&PB>ro4;YD<5LF^TjTb0!zAP|QijA+1Vg>{Afv^% zmrkc4o6rvBI;Q8rj4*=AZacy*n8B{&G3VJc)so4$XUoie0)vr;qzPZVbb<#Fc=j+8CGBWe$n|3K& z_@%?{l|TzKSlUEO{U{{%Fz_pVDxs7i9H#bnbCw7@4DR=}r_qV!Zo~CvD4ZI*+j3kO zW6_=|S`)(*gM0Z;;}nj`73OigF4p6_NPZQ-Od~e$c_);;4-7sR>+2u$6m$Gf%T{aq zle>e3(*Rt(TPD}03n5)!Ca8Pu!V}m6v0o1;5<1h$*|7z|^(3$Y&;KHKTT}hV056wuF0Xo@mK-52~r=6^SI1NC%c~CC?n>yX6wPTgiWYVz!Sx^atLby9YNn1Rk{g?|pJaxD4|9cUf|V1_I*w zzxK)hRh9%zOl=*$?XUjly5z8?jPMy%vEN)f%T*|WO|bp5NWv@B(K3D6LMl!-6dQg0 zXNE&O>Oyf%K@`ngCvbGPR>HRg5!1IV$_}m@3dWB7x3t&KFyOJn9pxRXCAzFr&%37wXG;z^xaO$ekR=LJG ztIHpY8F5xBP{mtQidqNRoz= z@){+N3(VO5bD+VrmS^YjG@+JO{EOIW)9=F4v_$Ed8rZtHvjpiEp{r^c4F6Ic#ChlC zJX^DtSK+v(YdCW)^EFcs=XP7S>Y!4=xgmv>{S$~@h=xW-G4FF9?I@zYN$e5oF9g$# zb!eVU#J+NjLyX;yb)%SY)xJdvGhsnE*JEkuOVo^k5PyS=o#vq!KD46UTW_%R=Y&0G zFj6bV{`Y6)YoKgqnir2&+sl+i6foAn-**Zd1{_;Zb7Ki=u394C5J{l^H@XN`_6XTKY%X1AgQM6KycJ+= zYO=&t#5oSKB^pYhNdzPgH~aEGW2=ec1O#s-KG z71}LOg@4UEFtp3GY1PBemXpNs6UK-ax*)#$J^pC_me;Z$Je(OqLoh|ZrW*mAMBFn< zHttjwC&fkVfMnQeen8`Rvy^$pNRFVaiEN4Pih*Y3@jo!T0nsClN)pdrr9AYLcZxZ| zJ5Wlj+4q~($hbtuY zVQ7hl>4-+@6g1i`1a)rvtp-;b0>^`Dloy(#{z~ytgv=j4q^Kl}wD>K_Y!l~ zp(_&7sh`vfO(1*MO!B%<6E_bx1)&s+Ae`O)a|X=J9y~XDa@UB`m)`tSG4AUhoM=5& znWoHlA-(z@3n0=l{E)R-p8sB9XkV zZ#D8wietfHL?J5X0%&fGg@MH~(rNS2`GHS4xTo7L$>TPme+Is~!|79=^}QbPF>m%J zFMkGzSndiPO|E~hrhCeo@&Ea{M(ieIgRWMf)E}qeTxT8Q#g-!Lu*x$v8W^M^>?-g= zwMJ$dThI|~M06rG$Sv@C@tWR>_YgaG&!BAbkGggVQa#KdtDB)lMLNVLN|51C@F^y8 zCRvMB^{GO@j=cHfmy}_pCGbP%xb{pNN>? z?7tBz$1^zVaP|uaatYaIN+#xEN4jBzwZ|YI_)p(4CUAz1ZEbDk>J~Y|63SZaak~#0 zoYKruYsWHoOlC1(MhTnsdUOwQfz5p6-D0}4;DO$B;7#M{3lSE^jnTT;ns`>!G%i*F?@pR1JO{QTuD0U+~SlZxcc8~>IB{)@8p`P&+nDxNj`*gh|u?yrv$phpQcW)Us)bi`kT%qLj(fi{dWRZ%Es2!=3mI~UxiW0$-v3vUl?#g{p6eF zMEUAqo5-L0Ar(s{VlR9g=j7+lt!gP!UN2ICMokAZ5(Agd>})#gkA2w|5+<%-CuEP# zqgcM}u@3(QIC^Gx<2dbLj?cFSws_f3e%f4jeR?4M^M3cx1f+Qr6ydQ>n)kz1s##2w zk}UyQc+Z5G-d-1}{WzjkLXgS-2P7auWSJ%pSnD|Uivj5u!xk0 z_^-N9r9o;(rFDt~q1PvE#iJZ_f>J3gcP$)SOqhE~pD2|$=GvpL^d!r z6u=sp-CrMoF7;)}Zd7XO4XihC4ji?>V&(t^?@3Q&t9Mx=qex6C9d%{FE6dvU6%d94 zIE;hJ1J)cCqjv?F``7I*6bc#X)JW2b4f$L^>j{*$R`%5VHFi*+Q$2;nyieduE}qdS{L8y8F08yLs?w}{>8>$3236T-VMh@B zq-nujsb_1aUv_7g#)*rf9h%sFj*^mIcImRV*k~Vmw;%;YH(&ylYpy!&UjUVqqtfG` zox3esju?`unJJA_zKXRJP)rA3nXc$m^{S&-p|v|-0x9LHJm;XIww7C#R$?00l&Yyj z=e}gKUOpsImwW?N)+E(awoF@HyP^EhL+GlNB#k?R<2>95hz!h9sF@U20DHSB3~WMa zk90+858r@-+vWwkawJ)8ougd(i#1m3GLN{iSTylYz$brAsP%=&m$mQQrH$g%3-^VR zE%B`Vi&m8f3T~&myTEK28BDWCVzfWir1I?03;pX))|kY5ClO^+bae z*7E?g=3g7EiisYOrE+lA)2?Ln6q2*HLNpZEWMB|O-JI_oaHZB%CvYB(%=tU= zE*OY%QY58fW#RG5=gm0NR#iMB=EuNF@)%oZJ}nmm=tsJ?eGjia{e{yuU0l3{d^D@)kVDt=1PE)&tf_hHC%0MB znL|CRCPC}SeuVTdf>-QV70`0(EHizc21s^sU>y%hW0t!0&y<7}Wi-wGy>m%(-jsDj zP?mF|>p_K>liZ6ZP(w5(|9Ga%>tLgb$|doDDfkdW>Z z`)>V2XC?NJT26mL^@ zf+IKr27TfM!UbZ@?zRddC7#6ss1sw%CXJ4FWC+t3lHZupzM77m^=9 z&(a?-LxIq}*nvv)y?27lZ{j zifdl9hyJudyP2LpU$-kXctshbJDKS{WfulP5Dk~xU4Le4c#h^(YjJit4#R8_khheS z|8(>2ibaHES4+J|DBM7I#QF5u-*EdN{n=Kt@4Zt?@Tv{JZA{`4 zU#kYOv{#A&gGPwT+$Ud}AXlK3K7hYzo$(fBSFjrP{QQ zeaKg--L&jh$9N}`pu{Bs>?eDFPaWY4|9|foN%}i;3%;@4{dc+iw>m}{3rELqH21G! z`8@;w-zsJ1H(N3%|1B@#ioLOjib)j`EiJqPQVSbPSPVHCj6t5J&(NcWzBrzCiDt{4 zdlPAUKldz%6x5II1H_+jv)(xVL+a;P+-1hv_pM>gMRr%04@k;DTokASSKKhU1Qms| zrWh3a!b(J3n0>-tipg{a?UaKsP7?+|@A+1WPDiQIW1Sf@qDU~M_P65_s}7(gjTn0X zucyEm)o;f8UyshMy&>^SC3I|C6jR*R_GFwGranWZe*I>K+0k}pBuET&M~ z;Odo*ZcT?ZpduHyrf8E%IBFtv;JQ!N_m>!sV6ly$_1D{(&nO~w)G~Y`7sD3#hQk%^ zp}ucDF_$!6DAz*PM8yE(&~;%|=+h(Rn-=1Wykas_-@d&z#=S}rDf`4w(rVlcF&lF! z=1)M3YVz7orwk^BXhslJ8jR);sh^knJW(Qmm(QdSgIAIdlN4Te5KJisifjr?eB{FjAX1a0AB>d?qY4Wx>BZ8&}5K0fA+d{l8 z?^s&l8#j7pR&ijD?0b%;lL9l$P_mi2^*_OL+b}4kuLR$GAf85sOo02?Y#90}CCDiS zZ%rbCw>=H~CBO=C_JVV=xgDe%b4FaEFtuS7Q1##y686r%F6I)s-~2(}PWK|Z8M+Gu zl$y~5@#0Ka%$M<&Cv%L`a8X^@tY&T7<0|(6dNT=EsRe0%kp1Qyq!^43VAKYnr*A5~ zsI%lK1ewqO;0TpLrT9v}!@vJK{QoVa_+N4FYT#h?Y8rS1S&-G+m$FNMP?(8N`MZP zels(*?kK{{^g9DOzkuZXJ2;SrOQsp9T$hwRB1(phw1c7`!Q!by?Q#YsSM#I12RhU{$Q+{xj83axHcftEc$mNJ8_T7A-BQc*k(sZ+~NsO~xAA zxnbb%dam_fZlHvW7fKXrB~F&jS<4FD2FqY?VG?ix*r~MDXCE^WQ|W|WM;gsIA4lQP zJ2hAK@CF*3*VqPr2eeg6GzWFlICi8S>nO>5HvWzyZTE)hlkdC_>pBej*>o0EOHR|) z$?};&I4+_?wvL*g#PJ9)!bc#9BJu1(*RdNEn>#Oxta(VWeM40ola<0aOe2kSS~{^P zDJBd}0L-P#O-CzX*%+$#v;(x%<*SPgAje=F{Zh-@ucd2DA(yC|N_|ocs*|-!H%wEw z@Q!>siv2W;C^^j^59OAX03&}&D*W4EjCvfi(ygcL#~t8XGa#|NPO+*M@Y-)ctFA@I z-p7npT1#5zOLo>7q?aZpCZ=iecn3QYklP;gF0bq@>oyBq94f6C=;Csw3PkZ|5q=(c zfs`aw?II0e(h=|7o&T+hq&m$; zBrE09Twxd9BJ2P+QPN}*OdZ-JZV7%av@OM7v!!NL8R;%WFq*?{9T3{ct@2EKgc8h) zMxoM$SaF#p<`65BwIDfmXG6+OiK0e)`I=!A3E`+K@61f}0e z!2a*FOaDrOe>U`q%K!QN`&=&0C~)CaL3R4VY(NDt{Xz(Xpqru5=r#uQN1L$Je1*dkdqQ*=lofQaN%lO!<5z9ZlHgxt|`THd>2 zsWfU$9=p;yLyJyM^t zS2w9w?Bpto`@H^xJpZDKR1@~^30Il6oFGfk5%g6w*C+VM)+%R@gfIwNprOV5{F^M2 zO?n3DEzpT+EoSV-%OdvZvNF+pDd-ZVZ&d8 zKeIyrrfPN=EcFRCPEDCVflX#3-)Ik_HCkL(ejmY8vzcf-MTA{oHk!R2*36`O68$7J zf}zJC+bbQk--9Xm!u#lgLvx8TXx2J258E5^*IZ(FXMpq$2LUUvhWQPs((z1+2{Op% z?J}9k5^N=z;7ja~zi8a_-exIqWUBJwohe#4QJ`|FF*$C{lM18z^#hX6!5B8KAkLUX ziP=oti-gpV(BsLD{0(3*dw}4JxK23Y7M{BeFPucw!sHpY&l%Ws4pSm`+~V7;bZ%Dx zeI)MK=4vC&5#;2MT7fS?^ch9?2;%<8Jlu-IB&N~gg8t;6S-#C@!NU{`p7M8@2iGc& zg|JPg%@gCoCQ&s6JvDU&`X2S<57f(k8nJ1wvBu{8r?;q3_kpZZ${?|( z+^)UvR33sjSd)aT!UPkA;ylO6{aE3MQa{g%Mcf$1KONcjO@&g5zPHWtzM1rYC{_K> zgQNcs<{&X{OA=cEWw5JGqpr0O>x*Tfak2PE9?FuWtz^DDNI}rwAaT0(bdo-<+SJ6A z&}S%boGMWIS0L}=S>|-#kRX;e^sUsotry(MjE|3_9duvfc|nwF#NHuM-w7ZU!5ei8 z6Mkf>2)WunY2eU@C-Uj-A zG(z0Tz2YoBk>zCz_9-)4a>T46$(~kF+Y{#sA9MWH%5z#zNoz)sdXq7ZR_+`RZ%0(q zC7&GyS_|BGHNFl8Xa%@>iWh%Gr?=J5<(!OEjauj5jyrA-QXBjn0OAhJJ9+v=!LK`` z@g(`^*84Q4jcDL`OA&ZV60djgwG`|bcD*i50O}Q{9_noRg|~?dj%VtKOnyRs$Uzqg z191aWoR^rDX#@iSq0n z?9Sg$WSRPqSeI<}&n1T3!6%Wj@5iw5`*`Btni~G=&;J+4`7g#OQTa>u`{4ZZ(c@s$ zK0y;ySOGD-UTjREKbru{QaS>HjN<2)R%Nn-TZiQ(Twe4p@-saNa3~p{?^V9Nixz@a zykPv~<@lu6-Ng9i$Lrk(xi2Tri3q=RW`BJYOPC;S0Yly%77c727Yj-d1vF!Fuk{Xh z)lMbA69y7*5ufET>P*gXQrxsW+ zz)*MbHZv*eJPEXYE<6g6_M7N%#%mR{#awV3i^PafNv(zyI)&bH?F}2s8_rR(6%!V4SOWlup`TKAb@ee>!9JKPM=&8g#BeYRH9FpFybxBXQI2|g}FGJfJ+ zY-*2hB?o{TVL;Wt_ek;AP5PBqfDR4@Z->_182W z{P@Mc27j6jE*9xG{R$>6_;i=y{qf(c`5w9fa*`rEzX6t!KJ(p1H|>J1pC-2zqWENF zmm=Z5B4u{cY2XYl(PfrInB*~WGWik3@1oRhiMOS|D;acnf-Bs(QCm#wR;@Vf!hOPJ zgjhDCfDj$HcyVLJ=AaTbQ{@vIv14LWWF$=i-BDoC11}V;2V8A`S>_x)vIq44-VB-v z*w-d}$G+Ql?En8j!~ZkCpQ$|cA0|+rrY>tiCeWxkRGPoarxlGU2?7%k#F693RHT24 z-?JsiXlT2PTqZqNb&sSc>$d;O4V@|b6VKSWQb~bUaWn1Cf0+K%`Q&Wc<>mQ>*iEGB zbZ;aYOotBZ{vH3y<0A*L0QVM|#rf*LIsGx(O*-7)r@yyBIzJnBFSKBUSl1e|8lxU* zzFL+YDVVkIuzFWeJ8AbgN&w(4-7zbiaMn{5!JQXu)SELk*CNL+Fro|2v|YO)1l15t zs(0^&EB6DPMyaqvY>=KL>)tEpsn;N5Q#yJj<9}ImL((SqErWN3Q=;tBO~ExTCs9hB z2E$7eN#5wX4<3m^5pdjm#5o>s#eS_Q^P)tm$@SawTqF*1dj_i#)3};JslbLKHXl_N z)Fxzf>FN)EK&Rz&*|6&%Hs-^f{V|+_vL1S;-1K-l$5xiC@}%uDuwHYhmsV?YcOUlk zOYkG5v2+`+UWqpn0aaaqrD3lYdh0*!L`3FAsNKu=Q!vJu?Yc8n|CoYyDo_`r0mPoo z8>XCo$W4>l(==h?2~PoRR*kEe)&IH{1sM41mO#-36`02m#nTX{r*r`Q5rZ2-sE|nA zhnn5T#s#v`52T5|?GNS`%HgS2;R(*|^egNPDzzH_z^W)-Q98~$#YAe)cEZ%vge965AS_am#DK#pjPRr-!^za8>`kksCAUj(Xr*1NW5~e zpypt_eJpD&4_bl_y?G%>^L}=>xAaV>KR6;^aBytqpiHe%!j;&MzI_>Sx7O%F%D*8s zSN}cS^<{iiK)=Ji`FpO#^zY!_|D)qeRNAtgmH)m;qC|mq^j(|hL`7uBz+ULUj37gj zksdbnU+LSVo35riSX_4z{UX=%n&}7s0{WuZYoSfwAP`8aKN9P@%e=~1`~1ASL-z%# zw>DO&ixr}c9%4InGc*_y42bdEk)ZdG7-mTu0bD@_vGAr*NcFoMW;@r?@LUhRI zCUJgHb`O?M3!w)|CPu~ej%fddw20lod?Ufp8Dmt0PbnA0J%KE^2~AIcnKP()025V> zG>noSM3$5Btmc$GZoyP^v1@Poz0FD(6YSTH@aD0}BXva?LphAiSz9f&Y(aDAzBnUh z?d2m``~{z;{}kZJ>a^wYI?ry(V9hIoh;|EFc0*-#*`$T0DRQ1;WsqInG;YPS+I4{g zJGpKk%%Sdc5xBa$Q^_I~(F97eqDO7AN3EN0u)PNBAb+n+ zWBTxQx^;O9o0`=g+Zrt_{lP!sgWZHW?8bLYS$;1a@&7w9rD9|Ge;Gb?sEjFoF9-6v z#!2)t{DMHZ2@0W*fCx;62d#;jouz`R5Y(t{BT=$N4yr^^o$ON8d{PQ=!O zX17^CrdM~7D-;ZrC!||<+FEOxI_WI3CA<35va%4v>gc zEX-@h8esj=a4szW7x{0g$hwoWRQG$yK{@3mqd-jYiVofJE!Wok1* znV7Gm&Ssq#hFuvj1sRyHg(6PFA5U*Q8Rx>-blOs=lb`qa{zFy&n4xY;sd$fE+<3EI z##W$P9M{B3c3Si9gw^jlPU-JqD~Cye;wr=XkV7BSv#6}DrsXWFJ3eUNrc%7{=^sP> zrp)BWKA9<}^R9g!0q7yWlh;gr_TEOD|#BmGq<@IV;ueg+D2}cjpp+dPf&Q(36sFU&K8}hA85U61faW&{ zlB`9HUl-WWCG|<1XANN3JVAkRYvr5U4q6;!G*MTdSUt*Mi=z_y3B1A9j-@aK{lNvx zK%p23>M&=KTCgR!Ee8c?DAO2_R?B zkaqr6^BSP!8dHXxj%N1l+V$_%vzHjqvu7p@%Nl6;>y*S}M!B=pz=aqUV#`;h%M0rU zHfcog>kv3UZAEB*g7Er@t6CF8kHDmKTjO@rejA^ULqn!`LwrEwOVmHx^;g|5PHm#B zZ+jjWgjJ!043F+&#_;D*mz%Q60=L9Ove|$gU&~As5^uz@2-BfQ!bW)Khn}G+Wyjw- z19qI#oB(RSNydn0t~;tAmK!P-d{b-@@E5|cdgOS#!>%#Rj6ynkMvaW@37E>@hJP^8 z2zk8VXx|>#R^JCcWdBCy{0nPmYFOxN55#^-rlqobe0#L6)bi?E?SPymF*a5oDDeSd zO0gx?#KMoOd&G(2O@*W)HgX6y_aa6iMCl^~`{@UR`nMQE`>n_{_aY5nA}vqU8mt8H z`oa=g0SyiLd~BxAj2~l$zRSDHxvDs;I4>+M$W`HbJ|g&P+$!U7-PHX4RAcR0szJ*( ze-417=bO2q{492SWrqDK+L3#ChUHtz*@MP)e^%@>_&#Yk^1|tv@j4%3T)diEX zATx4K*hcO`sY$jk#jN5WD<=C3nvuVsRh||qDHnc~;Kf59zr0;c7VkVSUPD%NnnJC_ zl3F^#f_rDu8l}l8qcAz0FFa)EAt32IUy_JLIhU_J^l~FRH&6-ivSpG2PRqzDdMWft>Zc(c)#tb%wgmWN%>IOPm zZi-noqS!^Ftb81pRcQi`X#UhWK70hy4tGW1mz|+vI8c*h@ zfFGJtW3r>qV>1Z0r|L>7I3un^gcep$AAWfZHRvB|E*kktY$qQP_$YG60C@X~tTQjB3%@`uz!qxtxF+LE!+=nrS^07hn` zEgAp!h|r03h7B!$#OZW#ACD+M;-5J!W+{h|6I;5cNnE(Y863%1(oH}_FTW})8zYb$7czP zg~Szk1+_NTm6SJ0MS_|oSz%e(S~P-&SFp;!k?uFayytV$8HPwuyELSXOs^27XvK-D zOx-Dl!P|28DK6iX>p#Yb%3`A&CG0X2S43FjN%IB}q(!hC$fG}yl1y9W&W&I@KTg6@ zK^kpH8=yFuP+vI^+59|3%Zqnb5lTDAykf z9S#X`3N(X^SpdMyWQGOQRjhiwlj!0W-yD<3aEj^&X%=?`6lCy~?`&WSWt z?U~EKFcCG_RJ(Qp7j=$I%H8t)Z@6VjA#>1f@EYiS8MRHZphp zMA_5`znM=pzUpBPO)pXGYpQ6gkine{6u_o!P@Q+NKJ}k!_X7u|qfpAyIJb$_#3@wJ z<1SE2Edkfk9C!0t%}8Yio09^F`YGzpaJHGk*-ffsn85@)%4@`;Fv^8q(-Wk7r=Q8p zT&hD`5(f?M{gfzGbbwh8(}G#|#fDuk7v1W)5H9wkorE0ZZjL0Q1=NRGY>zwgfm81DdoaVwNH;or{{eSyybt)m<=zXoA^RALYG-2t zouH|L*BLvmm9cdMmn+KGopyR@4*=&0&4g|FLoreZOhRmh=)R0bg~ zT2(8V_q7~42-zvb)+y959OAv!V$u(O3)%Es0M@CRFmG{5sovIq4%8Ahjk#*5w{+)+ zMWQoJI_r$HxL5km1#6(e@{lK3Udc~n0@g`g$s?VrnQJ$!oPnb?IHh-1qA`Rz$)Ai< z6w$-MJW-gKNvOhL+XMbE7&mFt`x1KY>k4(!KbbpZ`>`K@1J<(#vVbjx@Z@(6Q}MF# zMnbr-f55(cTa^q4+#)=s+ThMaV~E`B8V=|W_fZWDwiso8tNMTNse)RNBGi=gVwgg% zbOg8>mbRN%7^Um-7oj4=6`$|(K7!+t^90a{$18Z>}<#!bm%ZEFQ{X(yBZMc>lCz0f1I2w9Sq zuGh<9<=AO&g6BZte6hn>Qmvv;Rt)*cJfTr2=~EnGD8P$v3R|&1RCl&7)b+`=QGapi zPbLg_pxm`+HZurtFZ;wZ=`Vk*do~$wB zxoW&=j0OTbQ=Q%S8XJ%~qoa3Ea|au5o}_(P;=!y-AjFrERh%8la!z6Fn@lR?^E~H12D?8#ht=1F;7@o4$Q8GDj;sSC%Jfn01xgL&%F2 zwG1|5ikb^qHv&9hT8w83+yv&BQXOQyMVJSBL(Ky~p)gU3#%|blG?IR9rP^zUbs7rOA0X52Ao=GRt@C&zlyjNLv-} z9?*x{y(`509qhCV*B47f2hLrGl^<@SuRGR!KwHei?!CM10Tq*YDIoBNyRuO*>3FU? zHjipIE#B~y3FSfOsMfj~F9PNr*H?0oHyYB^G(YyNh{SxcE(Y-`x5jFMKb~HO*m+R% zrq|ic4fzJ#USpTm;X7K+E%xsT_3VHKe?*uc4-FsILUH;kL>_okY(w`VU*8+l>o>Jm ziU#?2^`>arnsl#)*R&nf_%>A+qwl%o{l(u)M?DK1^mf260_oteV3#E_>6Y4!_hhVD zM8AI6MM2V*^_M^sQ0dmHu11fy^kOqXqzpr?K$`}BKWG`=Es(9&S@K@)ZjA{lj3ea7_MBP zk(|hBFRjHVMN!sNUkrB;(cTP)T97M$0Dtc&UXSec<+q?y>5=)}S~{Z@ua;1xt@=T5 zI7{`Z=z_X*no8s>mY;>BvEXK%b`a6(DTS6t&b!vf_z#HM{Uoy_5fiB(zpkF{})ruka$iX*~pq1ZxD?q68dIo zIZSVls9kFGsTwvr4{T_LidcWtt$u{kJlW7moRaH6+A5hW&;;2O#$oKyEN8kx`LmG)Wfq4ykh+q{I3|RfVpkR&QH_x;t41Uw z`P+tft^E2B$domKT@|nNW`EHwyj>&}K;eDpe z1bNOh=fvIfk`&B61+S8ND<(KC%>y&?>opCnY*r5M+!UrWKxv0_QvTlJc>X#AaI^xo zaRXL}t5Ej_Z$y*|w*$6D+A?Lw-CO-$itm^{2Ct82-<0IW)0KMNvJHgBrdsIR0v~=H z?n6^}l{D``Me90`^o|q!olsF?UX3YSq^6Vu>Ijm>>PaZI8G@<^NGw{Cx&%|PwYrfw zR!gX_%AR=L3BFsf8LxI|K^J}deh0ZdV?$3r--FEX`#INxsOG6_=!v)DI>0q|BxT)z z-G6kzA01M?rba+G_mwNMQD1mbVbNTWmBi*{s_v_Ft9m2Avg!^78(QFu&n6mbRJ2bA zv!b;%yo{g*9l2)>tsZJOOp}U~8VUH`}$ z8p_}t*XIOehezolNa-a2x0BS})Y9}&*TPgua{Ewn-=wVrmJUeU39EKx+%w%=ixQWK zDLpwaNJs65#6o7Ln7~~X+p_o2BR1g~VCfxLzxA{HlWAI6^H;`juI=&r1jQrUv_q0Z z1Ja-tjdktrrP>GOC*#p?*xfQU5MqjMsBe!9lh(u8)w$e@Z|>aUHI5o;MGw*|Myiz3 z-f0;pHg~Q#%*Kx8MxH%AluVXjG2C$)WL-K63@Q`#y9_k_+}eR(x4~dp7oV-ek0H>I zgy8p#i4GN{>#v=pFYUQT(g&b$OeTy-X_#FDgNF8XyfGY6R!>inYn8IR2RDa&O!(6< znXs{W!bkP|s_YI*Yx%4stI`=ZO45IK6rBs`g7sP40ic}GZ58s?Mc$&i`kq_tfci>N zIHrC0H+Qpam1bNa=(`SRKjixBTtm&e`j9porEci!zdlg1RI0Jw#b(_Tb@RQK1Zxr_ z%7SUeH6=TrXt3J@js`4iDD0=IoHhK~I7^W8^Rcp~Yaf>2wVe|Hh1bUpX9ATD#moByY57-f2Ef1TP^lBi&p5_s7WGG9|0T}dlfxOx zXvScJO1Cnq`c`~{Dp;{;l<-KkCDE+pmexJkd}zCgE{eF=)K``-qC~IT6GcRog_)!X z?fK^F8UDz$(zFUrwuR$qro5>qqn>+Z%<5>;_*3pZ8QM|yv9CAtrAx;($>4l^_$_-L z*&?(77!-=zvnCVW&kUcZMb6;2!83si518Y%R*A3JZ8Is|kUCMu`!vxDgaWjs7^0j( ziTaS4HhQ)ldR=r)_7vYFUr%THE}cPF{0H45FJ5MQW^+W>P+eEX2kLp3zzFe*-pFVA zdDZRybv?H|>`9f$AKVjFWJ=wegO7hOOIYCtd?Vj{EYLT*^gl35|HQ`R=ti+ADm{jyQE7K@kdjuqJhWVSks>b^ zxha88-h3s;%3_5b1TqFCPTxVjvuB5U>v=HyZ$?JSk+&I%)M7KE*wOg<)1-Iy)8-K! z^XpIt|0ibmk9RtMmlUd7#Ap3Q!q9N4atQy)TmrhrFhfx1DAN`^vq@Q_SRl|V z#lU<~n67$mT)NvHh`%als+G-)x1`Y%4Bp*6Un5Ri9h=_Db zA-AdP!f>f0m@~>7X#uBM?diI@)Egjuz@jXKvm zJo+==juc9_<;CqeRaU9_Mz@;3e=E4=6TK+c`|uu#pIqhSyNm`G(X)&)B`8q0RBv#> z`gGlw(Q=1Xmf55VHj%C#^1lpc>LY8kfA@|rlC1EA<1#`iuyNO z(=;irt{_&K=i4)^x%;U(Xv<)+o=dczC5H3W~+e|f~{*ucxj@{Yi-cw^MqYr3fN zF5D+~!wd$#al?UfMnz(@K#wn`_5na@rRr8XqN@&M&FGEC@`+OEv}sI1hw>Up0qAWf zL#e4~&oM;TVfjRE+10B_gFlLEP9?Q-dARr3xi6nQqnw>k-S;~b z;!0s2VS4}W8b&pGuK=7im+t(`nz@FnT#VD|!)eQNp-W6)@>aA+j~K*H{$G`y2|QHY z|Hmy+CR@#jWY4~)lr1qBJB_RfHJFfP<}pK5(#ZZGSqcpyS&}01LnTWk5fzmXMGHkJ zTP6L^B+uj;lmB_W<~4=${+v0>z31M!-_O@o-O9GyW)j_mjx}!0@br_LE-7SIuPP84 z;5=O(U*g_um0tyG|61N@d9lEuOeiRd+#NY^{nd5;-CVlw&Ap7J?qwM^?E29wvS}2d zbzar4Fz&RSR(-|s!Z6+za&Z zY#D<5q_JUktIzvL0)yq_kLWG6DO{ri=?c!y!f(Dk%G{8)k`Gym%j#!OgXVDD3;$&v@qy#ISJfp=Vm>pls@9-mapVQChAHHd-x+OGx)(*Yr zC1qDUTZ6mM(b_hi!TuFF2k#8uI2;kD70AQ&di$L*4P*Y-@p`jdm%_c3f)XhYD^6M8&#Y$ZpzQMcR|6nsH>b=*R_Von!$BTRj7yGCXokoAQ z&ANvx0-Epw`QIEPgI(^cS2f(Y85yV@ygI{ewyv5Frng)e}KCZF7JbR(&W618_dcEh(#+^zZFY;o<815<5sOHQdeax9_!PyM&;{P zkBa5xymca0#)c#tke@3KNEM8a_mT&1gm;p&&JlMGH(cL(b)BckgMQ^9&vRwj!~3@l zY?L5}=Jzr080OGKb|y`ee(+`flQg|!lo6>=H)X4`$Gz~hLmu2a%kYW_Uu8x09Pa0J zKZ`E$BKJ=2GPj_3l*TEcZ*uYRr<*J^#5pILTT;k_cgto1ZL-%slyc16J~OH-(RgDA z%;EjEnoUkZ&acS{Q8`{i6T5^nywgqQI5bDIymoa7CSZG|WWVk>GM9)zy*bNih|QIm z%0+(Nnc*a_xo;$=!HQYaapLms>J1ToyjtFByY`C2H1wT#178#4+|{H0BBqtCdd$L% z_3Hc60j@{t9~MjM@LBalR&6@>B;9?r<7J~F+WXyYu*y3?px*=8MAK@EA+jRX8{CG?GI-< z54?Dc9CAh>QTAvyOEm0^+x;r2BWX|{3$Y7)L5l*qVE*y0`7J>l2wCmW zL1?|a`pJ-l{fb_N;R(Z9UMiSj6pQjOvQ^%DvhIJF!+Th7jO2~1f1N+(-TyCFYQZYw z4)>7caf^Ki_KJ^Zx2JUb z&$3zJy!*+rCV4%jqwyuNY3j1ZEiltS0xTzd+=itTb;IPYpaf?8Y+RSdVdpacB(bVQ zC(JupLfFp8y43%PMj2}T|VS@%LVp>hv4Y!RPMF?pp8U_$xCJ)S zQx!69>bphNTIb9yn*_yfj{N%bY)t{L1cs8<8|!f$;UQ*}IN=2<6lA;x^(`8t?;+ST zh)z4qeYYgZkIy{$4x28O-pugO&gauRh3;lti9)9Pvw+^)0!h~%m&8Q!AKX%urEMnl z?yEz?g#ODn$UM`+Q#$Q!6|zsq_`dLO5YK-6bJM6ya>}H+vnW^h?o$z;V&wvuM$dR& zeEq;uUUh$XR`TWeC$$c&Jjau2it3#%J-y}Qm>nW*s?En?R&6w@sDXMEr#8~$=b(gk zwDC3)NtAP;M2BW_lL^5ShpK$D%@|BnD{=!Tq)o(5@z3i7Z){} zGr}Exom_qDO{kAVkZ*MbLNHE666Kina#D{&>Jy%~w7yX$oj;cYCd^p9zy z8*+wgSEcj$4{WxKmCF(5o7U4jqwEvO&dm1H#7z}%VXAbW&W24v-tS6N3}qrm1OnE)fUkoE8yMMn9S$?IswS88tQWm4#Oid#ckgr6 zRtHm!mfNl-`d>O*1~d7%;~n+{Rph6BBy^95zqI{K((E!iFQ+h*C3EsbxNo_aRm5gj zKYug($r*Q#W9`p%Bf{bi6;IY0v`pB^^qu)gbg9QHQ7 zWBj(a1YSu)~2RK8Pi#C>{DMlrqFb9e_RehEHyI{n?e3vL_}L>kYJC z_ly$$)zFi*SFyNrnOt(B*7E$??s67EO%DgoZL2XNk8iVx~X_)o++4oaK1M|ou73vA0K^503j@uuVmLcHH4ya-kOIDfM%5%(E z+Xpt~#7y2!KB&)PoyCA+$~DXqxPxxALy!g-O?<9+9KTk4Pgq4AIdUkl`1<1#j^cJg zgU3`0hkHj_jxV>`Y~%LAZl^3o0}`Sm@iw7kwff{M%VwtN)|~!p{AsfA6vB5UolF~d zHWS%*uBDt<9y!9v2Xe|au&1j&iR1HXCdyCjxSgG*L{wmTD4(NQ=mFjpa~xooc6kju z`~+d{j7$h-;HAB04H!Zscu^hZffL#9!p$)9>sRI|Yovm)g@F>ZnosF2EgkU3ln0bR zTA}|+E(tt)!SG)-bEJi_0m{l+(cAz^pi}`9=~n?y&;2eG;d9{M6nj>BHGn(KA2n|O zt}$=FPq!j`p&kQ8>cirSzkU0c08%8{^Qyqi-w2LoO8)^E7;;I1;HQ6B$u0nNaX2CY zSmfi)F`m94zL8>#zu;8|{aBui@RzRKBlP1&mfFxEC@%cjl?NBs`cr^nm){>;$g?rhKr$AO&6qV_Wbn^}5tfFBry^e1`%du2~o zs$~dN;S_#%iwwA_QvmMjh%Qo?0?rR~6liyN5Xmej8(*V9ym*T`xAhHih-v$7U}8=dfXi2i*aAB!xM(Xekg*ix@r|ymDw*{*s0?dlVys2e)z62u1 z+k3esbJE=-P5S$&KdFp+2H7_2e=}OKDrf( z9-207?6$@f4m4B+9E*e((Y89!q?zH|mz_vM>kp*HGXldO0Hg#!EtFhRuOm$u8e~a9 z5(roy7m$Kh+zjW6@zw{&20u?1f2uP&boD}$#Zy)4o&T;vyBoqFiF2t;*g=|1=)PxB z8eM3Mp=l_obbc?I^xyLz?4Y1YDWPa+nm;O<$Cn;@ane616`J9OO2r=rZr{I_Kizyc zP#^^WCdIEp*()rRT+*YZK>V@^Zs=ht32x>Kwe zab)@ZEffz;VM4{XA6e421^h~`ji5r%)B{wZu#hD}f3$y@L0JV9f3g{-RK!A?vBUA}${YF(vO4)@`6f1 z-A|}e#LN{)(eXloDnX4Vs7eH|<@{r#LodP@Nz--$Dg_Par%DCpu2>2jUnqy~|J?eZ zBG4FVsz_A+ibdwv>mLp>P!(t}E>$JGaK$R~;fb{O3($y1ssQQo|5M;^JqC?7qe|hg zu0ZOqeFcp?qVn&Qu7FQJ4hcFi&|nR!*j)MF#b}QO^lN%5)4p*D^H+B){n8%VPUzi! zDihoGcP71a6!ab`l^hK&*dYrVYzJ0)#}xVrp!e;lI!+x+bfCN0KXwUAPU9@#l7@0& QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.properties b/bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..69a9715 --- /dev/null +++ b/bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt new file mode 100644 index 0000000..bca1c3a --- /dev/null +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -0,0 +1,25 @@ +import uniffi.bdk.* + +class LogProgress: BdkProgress { + override fun update(progress: Float, message: String? ) { + println(progress); + println(message); + } +} + +fun main(args: Array) { + val descriptor = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; + val db = DatabaseConfig.Memory("") + val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 100u)) + val wallet = OnlineWallet(descriptor, Network.TESTNET, db, client) + val address = wallet.getNewAddress() + println("Please send satoshis to wallet address: $address") + readLine() + println("Syncing...") + wallet.sync(LogProgress(), null) + val balance = wallet.getBalance() + println("New wallet balance: $balance") + println("Press any key to exit") + readLine() +} diff --git a/bindings/bdk-kotlin/settings.gradle b/bindings/bdk-kotlin/settings.gradle index c23d1d9..ac610be 100644 --- a/bindings/bdk-kotlin/settings.gradle +++ b/bindings/bdk-kotlin/settings.gradle @@ -1,4 +1,4 @@ rootProject.name = 'bdk-kotlin' -include ':jvm' //, ':android', ':test-fixtures' +include ':jvm', ':demo' //, ':android', ':test-fixtures' diff --git a/build.sh b/build.sh index eaeb2cd..b67fff0 100755 --- a/build.sh +++ b/build.sh @@ -32,11 +32,13 @@ copy_lib_kotlin() { echo -n "darwin " mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/demo/src/main/resources/darwin-x86-64 ;; "Linux") echo -n "linux " mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 cp target/debug/libuniffi_bdk.so bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 + cp target/debug/libuniffi_bdk.so bindings/bdk-kotlin/demo/src/main/resources/linux-x86-64 ;; esac echo "libs to kotlin sub-project" From d4c832b8de4a24407ab8b9c5467f3620ec8f835b Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 16 Oct 2021 16:42:35 +0530 Subject: [PATCH 068/272] Allow creating partially signed bitcoin transactions --- .../bdk-kotlin/demo/src/main/kotlin/Main.kt | 10 +- .../jvm/src/main/kotlin/uniffi/bdk/bdk.kt | 130 +++++++++++++----- src/bdk.udl | 5 + src/lib.rs | 31 ++++- 4 files changed, 139 insertions(+), 37 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index bca1c3a..84c270f 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -2,24 +2,28 @@ import uniffi.bdk.* class LogProgress: BdkProgress { override fun update(progress: Float, message: String? ) { - println(progress); - println(message); + println("progress: $progress, message: $message") } } fun main(args: Array) { + println("Configuring an in-memory wallet on electrum..") val descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; + val amount = 10000uL; + val recipient = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt"; val db = DatabaseConfig.Memory("") val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 100u)) val wallet = OnlineWallet(descriptor, Network.TESTNET, db, client) val address = wallet.getNewAddress() - println("Please send satoshis to wallet address: $address") + println("Please send $amount satoshis to address: $address") readLine() println("Syncing...") wallet.sync(LogProgress(), null) val balance = wallet.getBalance() println("New wallet balance: $balance") + println("Refunding $amount satoshis to $recipient") + val psbt = PartiallySignedBitcoinTransaction(wallet, recipient, amount) println("Press any key to exit") readLine() } diff --git a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt index 085cb16..b7777af 100644 --- a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_7046_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_b7c7_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_7046_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_b7c7_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_7046_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_b7c7_rustbuffer_reserve(buf, additional, status) } } @@ -392,6 +392,12 @@ internal fun String.write(buf: RustBufferBuilder) { + + + + + + @@ -542,59 +548,67 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_7046_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_b7c7_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_7046_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_b7c7_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_7046_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_b7c7_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_7046_OnlineWallet_object_free(ptr: Pointer, + fun ffi_bdk_b7c7_OnlineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_7046_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + fun bdk_b7c7_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_7046_OnlineWallet_get_new_address(ptr: Pointer, + fun bdk_b7c7_OnlineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun bdk_7046_OnlineWallet_get_network(ptr: Pointer, + fun bdk_b7c7_OnlineWallet_get_network(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun bdk_7046_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, + fun bdk_b7c7_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun bdk_7046_OnlineWallet_get_balance(ptr: Pointer, + fun bdk_b7c7_OnlineWallet_get_balance(ptr: Pointer, uniffi_out_err: RustCallStatus ): Long - fun ffi_bdk_7046_BdkProgress_init_callback(callback_stub: ForeignCallback, + fun ffi_bdk_b7c7_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_7046_rustbuffer_alloc(size: Int, + fun bdk_b7c7_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long, uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue + ): Pointer - fun ffi_bdk_7046_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_7046_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_b7c7_BdkProgress_init_callback(callback_stub: ForeignCallback, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_7046_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_b7c7_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_b7c7_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_b7c7_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_b7c7_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -1304,7 +1318,7 @@ class OfflineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_7046_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_b7c7_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -1317,7 +1331,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_7046_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_b7c7_OfflineWallet_object_free(this.pointer, status) } } @@ -1332,7 +1346,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_7046_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_b7c7_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1371,7 +1385,7 @@ class OnlineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_7046_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) }) /** @@ -1384,7 +1398,7 @@ class OnlineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_7046_OnlineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_b7c7_OnlineWallet_object_free(this.pointer, status) } } @@ -1399,7 +1413,7 @@ class OnlineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_7046_OnlineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1408,7 +1422,7 @@ class OnlineWallet( override fun getNetwork(): Network = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_7046_OnlineWallet_get_network(it, status) + _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_get_network(it, status) } }.let { Network.lift(it) @@ -1417,14 +1431,14 @@ class OnlineWallet( override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_7046_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) + _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) } } override fun getBalance(): ULong = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_7046_OnlineWallet_get_balance(it, status) + _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_get_balance(it, status) } }.let { ULong.lift(it) @@ -1447,6 +1461,60 @@ class OnlineWallet( } } +@ExperimentalUnsignedTypes +public interface PartiallySignedBitcoinTransactionInterface { + +} + +@ExperimentalUnsignedTypes +class PartiallySignedBitcoinTransaction( + pointer: Pointer +) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface { + constructor(wallet: OnlineWallet, recipient: String, amount: ULong ) : + this( + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_b7c7_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower() ,status) +}) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_b7c7_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + + + companion object { + internal fun lift(ptr: Pointer): PartiallySignedBitcoinTransaction { + return PartiallySignedBitcoinTransaction(ptr) + } + + internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong())) + } + + + } +} + // Callback Interfaces @@ -1496,7 +1564,7 @@ internal object CallbackInterfaceBdkProgressInternals: CallbackInternals - lib.ffi_bdk_7046_BdkProgress_init_callback(this.foreignCallback, status) + lib.ffi_bdk_b7c7_BdkProgress_init_callback(this.foreignCallback, status) } } } diff --git a/src/bdk.udl b/src/bdk.udl index 0283237..6bc08b0 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -105,3 +105,8 @@ interface OnlineWallet { [Throws=BdkError] u64 get_balance(); }; + +interface PartiallySignedBitcoinTransaction { + [Throws=BdkError] + constructor([ByRef] OnlineWallet wallet, string recipient, u64 amount); +}; diff --git a/src/lib.rs b/src/lib.rs index 9bd5669..99f50ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ -use bdk::bitcoin::Network; +use bdk::address_validator::AddressValidatorError; +use bdk::bitcoin::util::psbt::PartiallySignedTransaction; +use bdk::bitcoin::{Address, Network}; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; use bdk::blockchain::Progress; use bdk::blockchain::{ @@ -7,9 +9,9 @@ use bdk::blockchain::{ use bdk::database::any::{AnyDatabase, SledDbConfiguration}; use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase}; use bdk::wallet::AddressIndex; -use bdk::Error; -use bdk::Wallet; +use bdk::{Error, Wallet}; use std::convert::TryFrom; +use std::str::FromStr; use std::sync::{Mutex, MutexGuard}; uniffi_macros::include_scaffolding!("bdk"); @@ -103,6 +105,29 @@ impl Progress for BdkProgressHolder { } } +struct PartiallySignedBitcoinTransaction { + internal: Mutex, +} + +impl PartiallySignedBitcoinTransaction { + fn new(online_wallet: &OnlineWallet, recipient: String, amount: u64) -> Result { + let wallet = online_wallet.get_wallet(); + match Address::from_str(&recipient) { + Ok(address) => { + let mut builder = wallet.build_tx(); + builder.add_recipient(address.script_pubkey(), amount); + let (pst, ..) = builder.finish()?; + Ok(PartiallySignedBitcoinTransaction { + internal: Mutex::new(pst), + }) + } + Err(..) => Err(BdkError::AddressValidator( + AddressValidatorError::InvalidScript, + )), + } + } +} + impl OnlineWallet { fn new( descriptor: String, From 9d6229df58938a9ec668a368bffb7ea3fe42d12e Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 16 Oct 2021 20:10:07 +0530 Subject: [PATCH 069/272] Simplify logger --- bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt index abbe923..6e8c7b1 100644 --- a/bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -6,8 +6,7 @@ import org.junit.Test class LogProgress: BdkProgress { override fun update(progress: Float, message: String? ) { - println(progress); - println(message); + println("progress: $progress, message: $message") } } From 320771d7f8a58a0ae26f45abcaf7124b11c6935d Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 16 Oct 2021 20:19:34 +0530 Subject: [PATCH 070/272] Add sign and broadcast to wallet --- .../bdk-kotlin/demo/src/main/kotlin/Main.kt | 23 +++-- .../jvm/src/main/kotlin/uniffi/bdk/bdk.kt | 88 ++++++++++++------- src/bdk.udl | 4 + src/lib.rs | 35 ++++++-- 4 files changed, 105 insertions(+), 45 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index 84c270f..a9e64c6 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -9,11 +9,11 @@ class LogProgress: BdkProgress { fun main(args: Array) { println("Configuring an in-memory wallet on electrum..") val descriptor = - "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; - val amount = 10000uL; + "pkh(cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR)"; + val amount = 1000uL; val recipient = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt"; val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 100u)) + val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u)) val wallet = OnlineWallet(descriptor, Network.TESTNET, db, client) val address = wallet.getNewAddress() println("Please send $amount satoshis to address: $address") @@ -22,8 +22,19 @@ fun main(args: Array) { wallet.sync(LogProgress(), null) val balance = wallet.getBalance() println("New wallet balance: $balance") - println("Refunding $amount satoshis to $recipient") - val psbt = PartiallySignedBitcoinTransaction(wallet, recipient, amount) - println("Press any key to exit") + println("Press Enter to return funds") + readLine() + println("Creating a partially signed bitcoin transaction with recipient $recipient and amount $amount satoshis...") + val transaction = PartiallySignedBitcoinTransaction(wallet, recipient, amount) + println("Signing the transaction...") + wallet.sign(transaction) + println("Broadcasting the signed transaction...") + val transactionId = wallet.broadcast(transaction) + println("Refunded $amount satoshis to $recipient via transaction id $transactionId") + println("Syncing...") + wallet.sync(LogProgress(), null) + val final_balance = wallet.getBalance() + println("New wallet balance: $final_balance") + println("Press Enter to exit") readLine() } diff --git a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt index b7777af..6ce2179 100644 --- a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b7c7_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_b468_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b7c7_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_b468_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b7c7_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_b468_rustbuffer_reserve(buf, additional, status) } } @@ -548,67 +548,75 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_b7c7_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_b468_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_b7c7_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_b468_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_b7c7_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_b468_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_b7c7_OnlineWallet_object_free(ptr: Pointer, + fun ffi_bdk_b468_OnlineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_b7c7_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + fun bdk_b468_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_b7c7_OnlineWallet_get_new_address(ptr: Pointer, + fun bdk_b468_OnlineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun bdk_b7c7_OnlineWallet_get_network(ptr: Pointer, + fun bdk_b468_OnlineWallet_get_network(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun bdk_b7c7_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, + fun bdk_b468_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun bdk_b7c7_OnlineWallet_get_balance(ptr: Pointer, + fun bdk_b468_OnlineWallet_get_balance(ptr: Pointer, uniffi_out_err: RustCallStatus ): Long - fun ffi_bdk_b7c7_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, + fun bdk_b468_OnlineWallet_sign(ptr: Pointer,psbt: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_b7c7_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long, + fun bdk_b468_OnlineWallet_broadcast(ptr: Pointer,psbt: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_b468_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_b468_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long, uniffi_out_err: RustCallStatus ): Pointer - fun ffi_bdk_b7c7_BdkProgress_init_callback(callback_stub: ForeignCallback, + fun ffi_bdk_b468_BdkProgress_init_callback(callback_stub: ForeignCallback, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_b7c7_rustbuffer_alloc(size: Int, + fun ffi_bdk_b468_rustbuffer_alloc(size: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_b7c7_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun ffi_bdk_b468_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_b7c7_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_b468_rustbuffer_free(buf: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_b7c7_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_b468_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -1318,7 +1326,7 @@ class OfflineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b7c7_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_b468_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -1331,7 +1339,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b7c7_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_b468_OfflineWallet_object_free(this.pointer, status) } } @@ -1346,7 +1354,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_b7c7_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_b468_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1375,6 +1383,8 @@ public interface OnlineWalletInterface { fun getNetwork(): Network fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) fun getBalance(): ULong + fun sign(psbt: PartiallySignedBitcoinTransaction ) + fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String } @@ -1385,7 +1395,7 @@ class OnlineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) }) /** @@ -1398,7 +1408,7 @@ class OnlineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b7c7_OnlineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_b468_OnlineWallet_object_free(this.pointer, status) } } @@ -1413,7 +1423,7 @@ class OnlineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1422,7 +1432,7 @@ class OnlineWallet( override fun getNetwork(): Network = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_get_network(it, status) + _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_get_network(it, status) } }.let { Network.lift(it) @@ -1431,19 +1441,35 @@ class OnlineWallet( override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) + _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) } } override fun getBalance(): ULong = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_get_balance(it, status) + _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_get_balance(it, status) } }.let { ULong.lift(it) } + override fun sign(psbt: PartiallySignedBitcoinTransaction ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_sign(it, psbt.lower() , status) +} + } + + override fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_broadcast(it, psbt.lower() , status) +} + }.let { + String.lift(it) + } + companion object { @@ -1473,7 +1499,7 @@ class PartiallySignedBitcoinTransaction( constructor(wallet: OnlineWallet, recipient: String, amount: ULong ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b7c7_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower() ,status) + _UniFFILib.INSTANCE.bdk_b468_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower() ,status) }) /** @@ -1486,7 +1512,7 @@ class PartiallySignedBitcoinTransaction( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b7c7_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_b468_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) } } @@ -1564,7 +1590,7 @@ internal object CallbackInterfaceBdkProgressInternals: CallbackInternals - lib.ffi_bdk_b7c7_BdkProgress_init_callback(this.foreignCallback, status) + lib.ffi_bdk_b468_BdkProgress_init_callback(this.foreignCallback, status) } } } diff --git a/src/bdk.udl b/src/bdk.udl index 6bc08b0..c0ac19d 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -104,6 +104,10 @@ interface OnlineWallet { void sync(BdkProgress progress_update, u32? max_address_param); [Throws=BdkError] u64 get_balance(); + [Throws=BdkError] + void sign([ByRef] PartiallySignedBitcoinTransaction psbt); + [Throws=BdkError] + string broadcast([ByRef] PartiallySignedBitcoinTransaction psbt); }; interface PartiallySignedBitcoinTransaction { diff --git a/src/lib.rs b/src/lib.rs index 99f50ed..3c232b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -use bdk::address_validator::AddressValidatorError; use bdk::bitcoin::util::psbt::PartiallySignedTransaction; use bdk::bitcoin::{Address, Network}; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; @@ -9,7 +8,7 @@ use bdk::blockchain::{ use bdk::database::any::{AnyDatabase, SledDbConfiguration}; use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase}; use bdk::wallet::AddressIndex; -use bdk::{Error, Wallet}; +use bdk::{Error, SignOptions, Wallet}; use std::convert::TryFrom; use std::str::FromStr; use std::sync::{Mutex, MutexGuard}; @@ -114,15 +113,17 @@ impl PartiallySignedBitcoinTransaction { let wallet = online_wallet.get_wallet(); match Address::from_str(&recipient) { Ok(address) => { - let mut builder = wallet.build_tx(); - builder.add_recipient(address.script_pubkey(), amount); - let (pst, ..) = builder.finish()?; + let (psbt, _) = { + let mut builder = wallet.build_tx(); + builder.add_recipient(address.script_pubkey(), amount); + builder.finish()? + }; Ok(PartiallySignedBitcoinTransaction { - internal: Mutex::new(pst), + internal: Mutex::new(psbt), }) } - Err(..) => Err(BdkError::AddressValidator( - AddressValidatorError::InvalidScript, + Err(..) => Err(BdkError::Generic( + "failed to read wallet address".to_string(), )), } } @@ -190,6 +191,24 @@ impl OnlineWallet { fn get_balance(&self) -> Result { self.wallet.lock().unwrap().get_balance() } + + fn sign<'a>(&self, psbt: &'a PartiallySignedBitcoinTransaction) -> Result<(), Error> { + let mut psbt = psbt.internal.lock().unwrap(); + let finalized = self.get_wallet().sign(&mut psbt, SignOptions::default())?; + match finalized { + true => Ok(()), + false => Err(BdkError::Generic(format!( + "transaction signing not finalized {:?}", + psbt + ))), + } + } + + fn broadcast<'a>(&self, psbt: &'a PartiallySignedBitcoinTransaction) -> Result { + let tx = psbt.internal.lock().unwrap().clone().extract_tx(); + let tx_id = self.get_wallet().broadcast(tx)?; + Ok(tx_id.to_string()) + } } impl WalletHolder for OnlineWallet { From 58d774a3f3f78c9e5f815d0ca9f659c319dd8066 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 16 Oct 2021 20:19:56 +0530 Subject: [PATCH 071/272] Fix formatting --- src/bdk.udl | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/bdk.udl b/src/bdk.udl index c0ac19d..5ee8f95 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -64,25 +64,25 @@ interface DatabaseConfig { }; interface OfflineWallet { - [Throws=BdkError] - constructor(string descriptor, Network network, DatabaseConfig database_config); - string get_new_address(); + [Throws=BdkError] + constructor(string descriptor, Network network, DatabaseConfig database_config); + string get_new_address(); }; dictionary ElectrumConfig { - string url; - string? socks5; - u8 retry; - u8? timeout; - u64 stop_gap; + string url; + string? socks5; + u8 retry; + u8? timeout; + u64 stop_gap; }; dictionary EsploraConfig { - string base_url; - string? proxy; - u64 timeout_read; - u64 timeout_write; - u64 stop_gap; + string base_url; + string? proxy; + u64 timeout_read; + u64 timeout_write; + u64 stop_gap; }; [Enum] @@ -96,14 +96,14 @@ callback interface BdkProgress { }; interface OnlineWallet { - [Throws=BdkError] - constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); - string get_new_address(); - Network get_network(); - [Throws=BdkError] - void sync(BdkProgress progress_update, u32? max_address_param); - [Throws=BdkError] - u64 get_balance(); + [Throws=BdkError] + constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); + string get_new_address(); + Network get_network(); + [Throws=BdkError] + void sync(BdkProgress progress_update, u32? max_address_param); + [Throws=BdkError] + u64 get_balance(); [Throws=BdkError] void sign([ByRef] PartiallySignedBitcoinTransaction psbt); [Throws=BdkError] From ca0a2cba15fedb9c4f17955af954b4b541d0df51 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 16 Oct 2021 20:25:58 +0530 Subject: [PATCH 072/272] Share Wallet::getBalance and Wallet::sign --- .../jvm/src/main/kotlin/uniffi/bdk/bdk.kt | 176 ++++++++++-------- src/bdk.udl | 16 +- src/lib.rs | 32 ++-- 3 files changed, 130 insertions(+), 94 deletions(-) diff --git a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt index 6ce2179..15731af 100644 --- a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b468_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_70ef_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b468_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_70ef_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b468_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_70ef_rustbuffer_reserve(buf, additional, status) } } @@ -548,75 +548,83 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_b468_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_70ef_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_b468_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_70ef_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_b468_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_70ef_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_b468_OnlineWallet_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_b468_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_b468_OnlineWallet_get_new_address(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_b468_OnlineWallet_get_network(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_b468_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_b468_OnlineWallet_get_balance(ptr: Pointer, + fun bdk_70ef_OfflineWallet_get_balance(ptr: Pointer, uniffi_out_err: RustCallStatus ): Long - fun bdk_b468_OnlineWallet_sign(ptr: Pointer,psbt: Pointer, + fun bdk_70ef_OfflineWallet_sign(ptr: Pointer,psbt: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_b468_OnlineWallet_broadcast(ptr: Pointer,psbt: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_b468_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, + fun ffi_bdk_70ef_OnlineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_b468_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long, + fun bdk_70ef_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun ffi_bdk_b468_BdkProgress_init_callback(callback_stub: ForeignCallback, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_b468_rustbuffer_alloc(size: Int, + fun bdk_70ef_OnlineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_b468_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun bdk_70ef_OnlineWallet_get_balance(ptr: Pointer, uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue + ): Long - fun ffi_bdk_b468_rustbuffer_free(buf: RustBuffer.ByValue, + fun bdk_70ef_OnlineWallet_sign(ptr: Pointer,psbt: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_b468_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun bdk_70ef_OnlineWallet_get_network(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_70ef_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_70ef_OnlineWallet_broadcast(ptr: Pointer,psbt: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_70ef_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_70ef_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long, + uniffi_out_err: RustCallStatus + ): Pointer + + fun ffi_bdk_70ef_BdkProgress_init_callback(callback_stub: ForeignCallback, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_70ef_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_70ef_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_70ef_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_70ef_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -1313,20 +1321,22 @@ data class EsploraConfig ( // Objects - +@ExperimentalUnsignedTypes public interface OfflineWalletInterface { fun getNewAddress(): String + fun getBalance(): ULong + fun sign(psbt: PartiallySignedBitcoinTransaction ) } - +@ExperimentalUnsignedTypes class OfflineWallet( pointer: Pointer ) : FFIObject(pointer), OfflineWalletInterface { constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b468_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_70ef_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -1339,7 +1349,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b468_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_70ef_OfflineWallet_object_free(this.pointer, status) } } @@ -1354,12 +1364,28 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_b468_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_70ef_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) } + override fun getBalance(): ULong = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_70ef_OfflineWallet_get_balance(it, status) +} + }.let { + ULong.lift(it) + } + + override fun sign(psbt: PartiallySignedBitcoinTransaction ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_70ef_OfflineWallet_sign(it, psbt.lower() , status) +} + } + companion object { @@ -1380,10 +1406,10 @@ class OfflineWallet( @ExperimentalUnsignedTypes public interface OnlineWalletInterface { fun getNewAddress(): String - fun getNetwork(): Network - fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) fun getBalance(): ULong fun sign(psbt: PartiallySignedBitcoinTransaction ) + fun getNetwork(): Network + fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String } @@ -1395,7 +1421,7 @@ class OnlineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) }) /** @@ -1408,7 +1434,7 @@ class OnlineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b468_OnlineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_70ef_OnlineWallet_object_free(this.pointer, status) } } @@ -1423,32 +1449,16 @@ class OnlineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_get_new_address(it, status) } }.let { String.lift(it) } - override fun getNetwork(): Network = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_get_network(it, status) -} - }.let { - Network.lift(it) - } - - override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) -} - } - override fun getBalance(): ULong = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_get_balance(it, status) + _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_get_balance(it, status) } }.let { ULong.lift(it) @@ -1457,14 +1467,30 @@ class OnlineWallet( override fun sign(psbt: PartiallySignedBitcoinTransaction ) = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_sign(it, psbt.lower() , status) + _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_sign(it, psbt.lower() , status) +} + } + + override fun getNetwork(): Network = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_get_network(it, status) +} + }.let { + Network.lift(it) + } + + override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) } } override fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b468_OnlineWallet_broadcast(it, psbt.lower() , status) + _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_broadcast(it, psbt.lower() , status) } }.let { String.lift(it) @@ -1499,7 +1525,7 @@ class PartiallySignedBitcoinTransaction( constructor(wallet: OnlineWallet, recipient: String, amount: ULong ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_b468_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower() ,status) + _UniFFILib.INSTANCE.bdk_70ef_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower() ,status) }) /** @@ -1512,7 +1538,7 @@ class PartiallySignedBitcoinTransaction( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_b468_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_70ef_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) } } @@ -1590,7 +1616,7 @@ internal object CallbackInterfaceBdkProgressInternals: CallbackInternals - lib.ffi_bdk_b468_BdkProgress_init_callback(this.foreignCallback, status) + lib.ffi_bdk_70ef_BdkProgress_init_callback(this.foreignCallback, status) } } } diff --git a/src/bdk.udl b/src/bdk.udl index 5ee8f95..9f386de 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -66,7 +66,13 @@ interface DatabaseConfig { interface OfflineWallet { [Throws=BdkError] constructor(string descriptor, Network network, DatabaseConfig database_config); + + // OfflineWalletOperations string get_new_address(); + [Throws=BdkError] + u64 get_balance(); + [Throws=BdkError] + void sign([ByRef] PartiallySignedBitcoinTransaction psbt); }; dictionary ElectrumConfig { @@ -98,14 +104,18 @@ callback interface BdkProgress { interface OnlineWallet { [Throws=BdkError] constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); + + // OfflineWalletOperations string get_new_address(); - Network get_network(); - [Throws=BdkError] - void sync(BdkProgress progress_update, u32? max_address_param); [Throws=BdkError] u64 get_balance(); [Throws=BdkError] void sign([ByRef] PartiallySignedBitcoinTransaction psbt); + + // OnlineWalletInterface + Network get_network(); + [Throws=BdkError] + void sync(BdkProgress progress_update, u32? max_address_param); [Throws=BdkError] string broadcast([ByRef] PartiallySignedBitcoinTransaction psbt); }; diff --git a/src/lib.rs b/src/lib.rs index 3c232b4..d9d375f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,22 @@ trait OfflineWalletOperations: WalletHolder { .address .to_string() } + + fn get_balance(&self) -> Result { + self.get_wallet().get_balance() + } + + fn sign<'a>(&self, psbt: &'a PartiallySignedBitcoinTransaction) -> Result<(), Error> { + let mut psbt = psbt.internal.lock().unwrap(); + let finalized = self.get_wallet().sign(&mut psbt, SignOptions::default())?; + match finalized { + true => Ok(()), + false => Err(BdkError::Generic(format!( + "transaction signing not finalized {:?}", + psbt + ))), + } + } } impl OfflineWallet { @@ -188,22 +204,6 @@ impl OnlineWallet { .sync(BdkProgressHolder { progress_update }, max_address_param) } - fn get_balance(&self) -> Result { - self.wallet.lock().unwrap().get_balance() - } - - fn sign<'a>(&self, psbt: &'a PartiallySignedBitcoinTransaction) -> Result<(), Error> { - let mut psbt = psbt.internal.lock().unwrap(); - let finalized = self.get_wallet().sign(&mut psbt, SignOptions::default())?; - match finalized { - true => Ok(()), - false => Err(BdkError::Generic(format!( - "transaction signing not finalized {:?}", - psbt - ))), - } - } - fn broadcast<'a>(&self, psbt: &'a PartiallySignedBitcoinTransaction) -> Result { let tx = psbt.internal.lock().unwrap().clone().extract_tx(); let tx_id = self.get_wallet().broadcast(tx)?; From d343bce815c1ec782990683af5b06db5f464658d Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sun, 17 Oct 2021 02:28:26 +0530 Subject: [PATCH 073/272] Allow listing confirmed transactions --- .../bdk-kotlin/demo/src/main/kotlin/Main.kt | 17 +- .../jvm/src/main/kotlin/uniffi/bdk/bdk.kt | 256 ++++++++++++++---- src/bdk.udl | 13 + src/lib.rs | 25 ++ 4 files changed, 255 insertions(+), 56 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index a9e64c6..c2d091e 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -15,13 +15,14 @@ fun main(args: Array) { val db = DatabaseConfig.Memory("") val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u)) val wallet = OnlineWallet(descriptor, Network.TESTNET, db, client) - val address = wallet.getNewAddress() - println("Please send $amount satoshis to address: $address") + println("Syncing...") + wallet.sync(LogProgress(), null) + println("Initial wallet balance: ${wallet.getBalance()}") + println("Please send $amount satoshis to address: ${wallet.getNewAddress()}") readLine() println("Syncing...") wallet.sync(LogProgress(), null) - val balance = wallet.getBalance() - println("New wallet balance: $balance") + println("New wallet balance: ${wallet.getBalance()}") println("Press Enter to return funds") readLine() println("Creating a partially signed bitcoin transaction with recipient $recipient and amount $amount satoshis...") @@ -31,10 +32,10 @@ fun main(args: Array) { println("Broadcasting the signed transaction...") val transactionId = wallet.broadcast(transaction) println("Refunded $amount satoshis to $recipient via transaction id $transactionId") + println("Confirming transaction...") println("Syncing...") wallet.sync(LogProgress(), null) - val final_balance = wallet.getBalance() - println("New wallet balance: $final_balance") - println("Press Enter to exit") - readLine() + val confirmedTransaction = wallet.getTransactions().stream().filter({ it.id.equals(transactionId) }).findFirst().orElse(null) + println("Confirmed transaction: $confirmedTransaction") + println("Final wallet balance: ${wallet.getBalance()}") } diff --git a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt index 15731af..2bce10b 100644 --- a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_70ef_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_1945_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_70ef_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_1945_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_70ef_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_1945_rustbuffer_reserve(buf, additional, status) } } @@ -398,6 +398,12 @@ internal fun String.write(buf: RustBufferBuilder) { + + + + + + @@ -483,6 +489,45 @@ internal fun writeOptionalu32(v: UInt?, buf: RustBufferBuilder) { +// Helper functions for pasing values of type ULong? +@ExperimentalUnsignedTypes +internal fun liftOptionalu64(rbuf: RustBuffer.ByValue): ULong? { + return liftFromRustBuffer(rbuf) { buf -> + readOptionalu64(buf) + } +} + +@ExperimentalUnsignedTypes +internal fun readOptionalu64(buf: ByteBuffer): ULong? { + if (buf.get().toInt() == 0) { + return null + } + return ULong.read(buf) +} + +@ExperimentalUnsignedTypes +internal fun lowerOptionalu64(v: ULong?): RustBuffer.ByValue { + return lowerIntoRustBuffer(v) { v, buf -> + writeOptionalu64(v, buf) + } +} + +@ExperimentalUnsignedTypes +internal fun writeOptionalu64(v: ULong?, buf: RustBufferBuilder) { + if (v == null) { + buf.putByte(0) + } else { + buf.putByte(1) + v.write(buf) + } +} + + + + + + + // Helper functions for pasing values of type String? internal fun liftOptionalstring(rbuf: RustBuffer.ByValue): String? { @@ -519,6 +564,44 @@ internal fun writeOptionalstring(v: String?, buf: RustBufferBuilder) { + + + +// Helper functions for pasing values of type List + +@ExperimentalUnsignedTypes +internal fun liftSequenceTypeConfirmedTransaction(rbuf: RustBuffer.ByValue): List { + return liftFromRustBuffer(rbuf) { buf -> + readSequenceTypeConfirmedTransaction(buf) + } +} + +@ExperimentalUnsignedTypes +internal fun readSequenceTypeConfirmedTransaction(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + ConfirmedTransaction.read(buf) + } +} + +@ExperimentalUnsignedTypes +internal fun lowerSequenceTypeConfirmedTransaction(v: List): RustBuffer.ByValue { + return lowerIntoRustBuffer(v) { v, buf -> + writeSequenceTypeConfirmedTransaction(v, buf) + } +} + +@ExperimentalUnsignedTypes +internal fun writeSequenceTypeConfirmedTransaction(v: List, buf: RustBufferBuilder) { + buf.putInt(v.size) + v.forEach { + it.write(buf) + } +} + + + + @Synchronized fun findLibraryName(componentName: String): String { val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride") @@ -548,83 +631,91 @@ internal interface _UniFFILib : Library { } } - fun ffi_bdk_70ef_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_1945_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_70ef_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_1945_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_70ef_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_1945_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun bdk_70ef_OfflineWallet_get_balance(ptr: Pointer, + fun bdk_1945_OfflineWallet_get_balance(ptr: Pointer, uniffi_out_err: RustCallStatus ): Long - fun bdk_70ef_OfflineWallet_sign(ptr: Pointer,psbt: Pointer, + fun bdk_1945_OfflineWallet_sign(ptr: Pointer,psbt: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_70ef_OnlineWallet_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_70ef_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_70ef_OnlineWallet_get_new_address(ptr: Pointer, + fun bdk_1945_OfflineWallet_get_transactions(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun bdk_70ef_OnlineWallet_get_balance(ptr: Pointer, + fun ffi_bdk_1945_OnlineWallet_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_1945_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun bdk_1945_OnlineWallet_get_new_address(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_1945_OnlineWallet_get_balance(ptr: Pointer, uniffi_out_err: RustCallStatus ): Long - fun bdk_70ef_OnlineWallet_sign(ptr: Pointer,psbt: Pointer, + fun bdk_1945_OnlineWallet_sign(ptr: Pointer,psbt: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_70ef_OnlineWallet_get_network(ptr: Pointer, + fun bdk_1945_OnlineWallet_get_transactions(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun bdk_70ef_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_70ef_OnlineWallet_broadcast(ptr: Pointer,psbt: Pointer, + fun bdk_1945_OnlineWallet_get_network(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_70ef_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, + fun bdk_1945_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun bdk_70ef_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long, + fun bdk_1945_OnlineWallet_broadcast(ptr: Pointer,psbt: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_1945_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_1945_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long, uniffi_out_err: RustCallStatus ): Pointer - fun ffi_bdk_70ef_BdkProgress_init_callback(callback_stub: ForeignCallback, + fun ffi_bdk_1945_BdkProgress_init_callback(callback_stub: ForeignCallback, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_70ef_rustbuffer_alloc(size: Int, + fun ffi_bdk_1945_rustbuffer_alloc(size: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_70ef_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + fun ffi_bdk_1945_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_70ef_rustbuffer_free(buf: RustBuffer.ByValue, + fun ffi_bdk_1945_rustbuffer_free(buf: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_70ef_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_1945_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -1223,6 +1314,55 @@ data class SledDbConfiguration ( +} + +@ExperimentalUnsignedTypes +data class ConfirmedTransaction ( + var fees: ULong?, + var height: UInt, + var timestamp: ULong, + var received: ULong, + var sent: ULong, + var id: String +) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): ConfirmedTransaction { + return liftFromRustBuffer(rbuf) { buf -> ConfirmedTransaction.read(buf) } + } + + internal fun read(buf: ByteBuffer): ConfirmedTransaction { + return ConfirmedTransaction( + readOptionalu64(buf), + UInt.read(buf), + ULong.read(buf), + ULong.read(buf), + ULong.read(buf), + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + writeOptionalu64(this.fees, buf) + + this.height.write(buf) + + this.timestamp.write(buf) + + this.received.write(buf) + + this.sent.write(buf) + + this.id.write(buf) + + } + + + } @ExperimentalUnsignedTypes @@ -1326,6 +1466,7 @@ public interface OfflineWalletInterface { fun getNewAddress(): String fun getBalance(): ULong fun sign(psbt: PartiallySignedBitcoinTransaction ) + fun getTransactions(): List } @@ -1336,7 +1477,7 @@ class OfflineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_70ef_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -1349,7 +1490,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_70ef_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_1945_OfflineWallet_object_free(this.pointer, status) } } @@ -1364,7 +1505,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_70ef_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1373,7 +1514,7 @@ class OfflineWallet( override fun getBalance(): ULong = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_70ef_OfflineWallet_get_balance(it, status) + _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_get_balance(it, status) } }.let { ULong.lift(it) @@ -1382,10 +1523,19 @@ class OfflineWallet( override fun sign(psbt: PartiallySignedBitcoinTransaction ) = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_70ef_OfflineWallet_sign(it, psbt.lower() , status) + _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_sign(it, psbt.lower() , status) } } + override fun getTransactions(): List = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_get_transactions(it, status) +} + }.let { + liftSequenceTypeConfirmedTransaction(it) + } + companion object { @@ -1408,6 +1558,7 @@ public interface OnlineWalletInterface { fun getNewAddress(): String fun getBalance(): ULong fun sign(psbt: PartiallySignedBitcoinTransaction ) + fun getTransactions(): List fun getNetwork(): Network fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String @@ -1421,7 +1572,7 @@ class OnlineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) }) /** @@ -1434,7 +1585,7 @@ class OnlineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_70ef_OnlineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_1945_OnlineWallet_object_free(this.pointer, status) } } @@ -1449,7 +1600,7 @@ class OnlineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1458,7 +1609,7 @@ class OnlineWallet( override fun getBalance(): ULong = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_get_balance(it, status) + _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_balance(it, status) } }.let { ULong.lift(it) @@ -1467,14 +1618,23 @@ class OnlineWallet( override fun sign(psbt: PartiallySignedBitcoinTransaction ) = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_sign(it, psbt.lower() , status) + _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_sign(it, psbt.lower() , status) } } + override fun getTransactions(): List = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_transactions(it, status) +} + }.let { + liftSequenceTypeConfirmedTransaction(it) + } + override fun getNetwork(): Network = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_get_network(it, status) + _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_network(it, status) } }.let { Network.lift(it) @@ -1483,14 +1643,14 @@ class OnlineWallet( override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) + _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) } } override fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_70ef_OnlineWallet_broadcast(it, psbt.lower() , status) + _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_broadcast(it, psbt.lower() , status) } }.let { String.lift(it) @@ -1525,7 +1685,7 @@ class PartiallySignedBitcoinTransaction( constructor(wallet: OnlineWallet, recipient: String, amount: ULong ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_70ef_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower() ,status) + _UniFFILib.INSTANCE.bdk_1945_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower() ,status) }) /** @@ -1538,7 +1698,7 @@ class PartiallySignedBitcoinTransaction( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_70ef_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_1945_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) } } @@ -1616,7 +1776,7 @@ internal object CallbackInterfaceBdkProgressInternals: CallbackInternals - lib.ffi_bdk_70ef_BdkProgress_init_callback(this.foreignCallback, status) + lib.ffi_bdk_1945_BdkProgress_init_callback(this.foreignCallback, status) } } } diff --git a/src/bdk.udl b/src/bdk.udl index 9f386de..138950c 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -63,6 +63,15 @@ interface DatabaseConfig { Sled(SledDbConfiguration config); }; +dictionary ConfirmedTransaction { + u64? fees; + u32 height; + u64 timestamp; + u64 received; + u64 sent; + string id; +}; + interface OfflineWallet { [Throws=BdkError] constructor(string descriptor, Network network, DatabaseConfig database_config); @@ -73,6 +82,8 @@ interface OfflineWallet { u64 get_balance(); [Throws=BdkError] void sign([ByRef] PartiallySignedBitcoinTransaction psbt); + [Throws=BdkError] + sequence get_transactions(); }; dictionary ElectrumConfig { @@ -111,6 +122,8 @@ interface OnlineWallet { u64 get_balance(); [Throws=BdkError] void sign([ByRef] PartiallySignedBitcoinTransaction psbt); + [Throws=BdkError] + sequence get_transactions(); // OnlineWalletInterface Network get_network(); diff --git a/src/lib.rs b/src/lib.rs index d9d375f..d78ab01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,16 @@ impl WalletHolder<()> for OfflineWallet { } } +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ConfirmedTransaction { + pub fees: Option, + pub height: u32, + pub timestamp: u64, + pub received: u64, + pub sent: u64, + pub id: String, +} + trait OfflineWalletOperations: WalletHolder { fn get_new_address(&self) -> String { self.get_wallet() @@ -81,6 +91,21 @@ trait OfflineWalletOperations: WalletHolder { ))), } } + + fn get_transactions(&self) -> Result, Error> { + let transactions = self.get_wallet().list_transactions(true)?; + Ok(transactions + .iter() + .map(|x| ConfirmedTransaction { + fees: x.fee, + height: x.confirmation_time.clone().map_or(0, |c| c.height), + timestamp: x.confirmation_time.clone().map_or(0, |c| c.timestamp), + id: x.txid.to_string(), + received: x.received, + sent: x.sent, + }) + .collect()) + } } impl OfflineWallet { From 9d3b31b56e2a5fee2f142f25002c1eeaac2908e7 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sun, 17 Oct 2021 02:36:22 +0530 Subject: [PATCH 074/272] Return only confirmed transactions in Wallet::getTransactions --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index d78ab01..2972e14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,7 @@ trait OfflineWalletOperations: WalletHolder { let transactions = self.get_wallet().list_transactions(true)?; Ok(transactions .iter() + .filter(|x| x.confirmation_time.is_some()) .map(|x| ConfirmedTransaction { fees: x.fee, height: x.confirmation_time.clone().map_or(0, |c| c.height), From 25977408df4e983e70ab827ab021072308b4ea96 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sun, 17 Oct 2021 02:36:58 +0530 Subject: [PATCH 075/272] Keep syncing until confirmation --- bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index c2d091e..b1de13b 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -6,6 +6,12 @@ class LogProgress: BdkProgress { } } +fun getConfirmedTransaction(wallet: OnlineWalletInterface, transactionId: String): ConfirmedTransaction? { + println("Syncing...") + wallet.sync(LogProgress(), null) + return wallet.getTransactions().stream().filter({ it.id.equals(transactionId) }).findFirst().orElse(null) +} + fun main(args: Array) { println("Configuring an in-memory wallet on electrum..") val descriptor = @@ -33,9 +39,10 @@ fun main(args: Array) { val transactionId = wallet.broadcast(transaction) println("Refunded $amount satoshis to $recipient via transaction id $transactionId") println("Confirming transaction...") - println("Syncing...") - wallet.sync(LogProgress(), null) - val confirmedTransaction = wallet.getTransactions().stream().filter({ it.id.equals(transactionId) }).findFirst().orElse(null) + var confirmedTransaction = getConfirmedTransaction(wallet, transactionId) + while(confirmedTransaction == null) { + confirmedTransaction = getConfirmedTransaction(wallet, transactionId) + } println("Confirmed transaction: $confirmedTransaction") println("Final wallet balance: ${wallet.getBalance()}") } From 87437fbddc6c208792c0025d5ec34575cfb74ba4 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sun, 17 Oct 2021 02:45:38 +0530 Subject: [PATCH 076/272] Stop printing to console when confirming --- bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index b1de13b..f24fe90 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -6,9 +6,14 @@ class LogProgress: BdkProgress { } } +class NullProgress: BdkProgress { + override fun update(progress: Float, message: String? ) { + + } +} + fun getConfirmedTransaction(wallet: OnlineWalletInterface, transactionId: String): ConfirmedTransaction? { - println("Syncing...") - wallet.sync(LogProgress(), null) + wallet.sync(NullProgress(), null) return wallet.getTransactions().stream().filter({ it.id.equals(transactionId) }).findFirst().orElse(null) } From 50dc701ec4e9cca5f90c18887e87df63ef926d7d Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sun, 17 Oct 2021 02:51:46 +0530 Subject: [PATCH 077/272] Avoid superflous printlns --- bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index f24fe90..64b2ed4 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -2,7 +2,7 @@ import uniffi.bdk.* class LogProgress: BdkProgress { override fun update(progress: Float, message: String? ) { - println("progress: $progress, message: $message") + println("Syncing..") } } @@ -26,12 +26,10 @@ fun main(args: Array) { val db = DatabaseConfig.Memory("") val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u)) val wallet = OnlineWallet(descriptor, Network.TESTNET, db, client) - println("Syncing...") wallet.sync(LogProgress(), null) println("Initial wallet balance: ${wallet.getBalance()}") println("Please send $amount satoshis to address: ${wallet.getNewAddress()}") readLine() - println("Syncing...") wallet.sync(LogProgress(), null) println("New wallet balance: ${wallet.getBalance()}") println("Press Enter to return funds") From b47c3c482da47316b75a59d0d011819dce2ac8d9 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sun, 17 Oct 2021 02:52:45 +0530 Subject: [PATCH 078/272] Apply formatting --- .../bdk-kotlin/demo/src/main/kotlin/Main.kt | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index 64b2ed4..3ecc066 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -1,30 +1,37 @@ import uniffi.bdk.* -class LogProgress: BdkProgress { - override fun update(progress: Float, message: String? ) { +class LogProgress : BdkProgress { + override fun update(progress: Float, message: String?) { println("Syncing..") } } -class NullProgress: BdkProgress { - override fun update(progress: Float, message: String? ) { - - } +class NullProgress : BdkProgress { + override fun update(progress: Float, message: String?) {} } -fun getConfirmedTransaction(wallet: OnlineWalletInterface, transactionId: String): ConfirmedTransaction? { +fun getConfirmedTransaction( + wallet: OnlineWalletInterface, + transactionId: String +): ConfirmedTransaction? { wallet.sync(NullProgress(), null) - return wallet.getTransactions().stream().filter({ it.id.equals(transactionId) }).findFirst().orElse(null) + return wallet.getTransactions() + .stream() + .filter({ it.id.equals(transactionId) }) + .findFirst() + .orElse(null) } fun main(args: Array) { println("Configuring an in-memory wallet on electrum..") - val descriptor = - "pkh(cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR)"; - val amount = 1000uL; - val recipient = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt"; + val descriptor = "pkh(cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR)" + val amount = 1000uL + val recipient = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u)) + val client = + BlockchainConfig.Electrum( + ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u) + ) val wallet = OnlineWallet(descriptor, Network.TESTNET, db, client) wallet.sync(LogProgress(), null) println("Initial wallet balance: ${wallet.getBalance()}") @@ -34,7 +41,9 @@ fun main(args: Array) { println("New wallet balance: ${wallet.getBalance()}") println("Press Enter to return funds") readLine() - println("Creating a partially signed bitcoin transaction with recipient $recipient and amount $amount satoshis...") + println( + "Creating a PSBT with recipient $recipient and amount $amount satoshis..." + ) val transaction = PartiallySignedBitcoinTransaction(wallet, recipient, amount) println("Signing the transaction...") wallet.sign(transaction) @@ -43,7 +52,7 @@ fun main(args: Array) { println("Refunded $amount satoshis to $recipient via transaction id $transactionId") println("Confirming transaction...") var confirmedTransaction = getConfirmedTransaction(wallet, transactionId) - while(confirmedTransaction == null) { + while (confirmedTransaction == null) { confirmedTransaction = getConfirmedTransaction(wallet, transactionId) } println("Confirmed transaction: $confirmedTransaction") From 61220514315b947601bfbb20a9de8ca6282378b8 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sun, 17 Oct 2021 04:11:43 +0530 Subject: [PATCH 079/272] Simplify messages --- bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index 3ecc066..784db84 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -49,12 +49,15 @@ fun main(args: Array) { wallet.sign(transaction) println("Broadcasting the signed transaction...") val transactionId = wallet.broadcast(transaction) - println("Refunded $amount satoshis to $recipient via transaction id $transactionId") + println("Broadcasted transaction with id $transactionId") println("Confirming transaction...") var confirmedTransaction = getConfirmedTransaction(wallet, transactionId) while (confirmedTransaction == null) { confirmedTransaction = getConfirmedTransaction(wallet, transactionId) } println("Confirmed transaction: $confirmedTransaction") + val transactions = wallet.getTransactions() + println("Listing all ${transactions.size} transactions...") + transactions.sortedByDescending { it.timestamp }.forEach { println(it) } println("Final wallet balance: ${wallet.getBalance()}") } From 0b500d8e05c1330d8acb72e0f7fe9f61ac1ac248 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 17 Oct 2021 12:44:36 -0700 Subject: [PATCH 080/272] Remove and ignore generated code and binary libs --- bindings/bdk-kotlin/.gitignore | 2 +- .../jvm/src/main/kotlin/uniffi/bdk/bdk.kt | 1784 ----------------- build.sh | 2 - 3 files changed, 1 insertion(+), 1787 deletions(-) delete mode 100644 bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt diff --git a/bindings/bdk-kotlin/.gitignore b/bindings/bdk-kotlin/.gitignore index f2efc75..f9467fc 100644 --- a/bindings/bdk-kotlin/.gitignore +++ b/bindings/bdk-kotlin/.gitignore @@ -1,7 +1,7 @@ -/target .idea .gradle local.properties build *.so *.dylib +bdk.kt diff --git a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt deleted file mode 100644 index 2bce10b..0000000 --- a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt +++ /dev/null @@ -1,1784 +0,0 @@ -// This file was autogenerated by some hot garbage in the `uniffi` crate. -// Trust me, you don't want to mess with it! - -@file:Suppress("NAME_SHADOWING") - -package uniffi.bdk; - -// Common helper code. -// -// Ideally this would live in a separate .kt file where it can be unittested etc -// in isolation, and perhaps even published as a re-useable package. -// -// However, it's important that the detils of how this helper code works (e.g. the -// way that different builtin types are passed across the FFI) exactly match what's -// expected by the Rust code on the other side of the interface. In practice right -// now that means coming from the exact some version of `uniffi` that was used to -// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin -// helpers directly inline like we're doing here. - -import com.sun.jna.Library -import com.sun.jna.Native -import com.sun.jna.Pointer -import com.sun.jna.Structure -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.util.concurrent.atomic.AtomicLong -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicReference -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock - -// This is a helper for safely working with byte buffers returned from the Rust code. -// A rust-owned buffer is represented by its capacity, its current length, and a -// pointer to the underlying data. - -@Structure.FieldOrder("capacity", "len", "data") -open class RustBuffer : Structure() { - @JvmField var capacity: Int = 0 - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : RustBuffer(), Structure.ByValue - class ByReference : RustBuffer(), Structure.ByReference - - companion object { - internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_1945_rustbuffer_alloc(size, status) - } - - internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_1945_rustbuffer_free(buf, status) - } - - internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_1945_rustbuffer_reserve(buf, additional, status) - } - } - - @Suppress("TooGenericExceptionThrown") - fun asByteBuffer() = - this.data?.getByteBuffer(0, this.len.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - } -} - -// This is a helper for safely passing byte references into the rust code. -// It's not actually used at the moment, because there aren't many things that you -// can take a direct pointer to in the JVM, and if we're going to copy something -// then we might as well copy it into a `RustBuffer`. But it's here for API -// completeness. - -@Structure.FieldOrder("len", "data") -open class ForeignBytes : Structure() { - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : ForeignBytes(), Structure.ByValue -} - - -// A helper for structured writing of data into a `RustBuffer`. -// This is very similar to `java.nio.ByteBuffer` but it knows how to grow -// the underlying `RustBuffer` on demand. -// -// TODO: we should benchmark writing things into a `RustBuffer` versus building -// up a bytearray and then copying it across. - -class RustBufferBuilder() { - var rbuf = RustBuffer.ByValue() - var bbuf: ByteBuffer? = null - - init { - val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size - rbuf.writeField("len", 0) - this.setRustBuffer(rbuf) - } - - internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { - this.rbuf = rbuf - this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - it.position(rbuf.len) - } - } - - fun finalize() : RustBuffer.ByValue { - val rbuf = this.rbuf - // Ensure that the JVM-level field is written through to native memory - // before turning the buffer, in case its recipient uses it in a context - // JNA doesn't apply its automatic synchronization logic. - rbuf.writeField("len", this.bbuf!!.position()) - this.setRustBuffer(RustBuffer.ByValue()) - return rbuf - } - - fun discard() { - val rbuf = this.finalize() - RustBuffer.free(rbuf) - } - - internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) { - // TODO: this will perform two checks to ensure we're not overflowing the buffer: - // one here where we check if it needs to grow, and another when we call a write - // method on the ByteBuffer. It might be cheaper to use exception-driven control-flow - // here, trying the write and growing if it throws a `BufferOverflowException`. - // Benchmarking needed. - if (this.bbuf!!.position() + size > this.rbuf.capacity) { - rbuf.writeField("len", this.bbuf!!.position()) - this.setRustBuffer(RustBuffer.reserve(this.rbuf, size)) - } - write(this.bbuf!!) - } - - fun putByte(v: Byte) { - this.reserve(1) { bbuf -> - bbuf.put(v) - } - } - - fun putShort(v: Short) { - this.reserve(2) { bbuf -> - bbuf.putShort(v) - } - } - - fun putInt(v: Int) { - this.reserve(4) { bbuf -> - bbuf.putInt(v) - } - } - - fun putLong(v: Long) { - this.reserve(8) { bbuf -> - bbuf.putLong(v) - } - } - - fun putFloat(v: Float) { - this.reserve(4) { bbuf -> - bbuf.putFloat(v) - } - } - - fun putDouble(v: Double) { - this.reserve(8) { bbuf -> - bbuf.putDouble(v) - } - } - - fun put(v: ByteArray) { - this.reserve(v.size) { bbuf -> - bbuf.put(v) - } - } -} - -// Helpers for reading primitive data types from a bytebuffer. - -internal fun liftFromRustBuffer(rbuf: RustBuffer.ByValue, readItem: (ByteBuffer) -> T): T { - val buf = rbuf.asByteBuffer()!! - try { - val item = readItem(buf) - if (buf.hasRemaining()) { - throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") - } - return item - } finally { - RustBuffer.free(rbuf) - } -} - -internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { - // TODO: maybe we can calculate some sort of initial size hint? - val buf = RustBufferBuilder() - try { - writeItem(v, buf) - return buf.finalize() - } catch (e: Throwable) { - buf.discard() - throw e - } -} - -// For every type used in the interface, we provide helper methods for conveniently -// lifting and lowering that type from C-compatible data, and for reading and writing -// values of that type in a buffer. - - - - -@ExperimentalUnsignedTypes -internal fun UByte.Companion.lift(v: Byte): UByte { - return v.toUByte() -} - -@ExperimentalUnsignedTypes -internal fun UByte.Companion.read(buf: ByteBuffer): UByte { - return UByte.lift(buf.get()) -} - -@ExperimentalUnsignedTypes -internal fun UByte.lower(): Byte { - return this.toByte() -} - -@ExperimentalUnsignedTypes -internal fun UByte.write(buf: RustBufferBuilder) { - buf.putByte(this.toByte()) -} - - - - - -@ExperimentalUnsignedTypes -internal fun UInt.Companion.lift(v: Int): UInt { - return v.toUInt() -} - -@ExperimentalUnsignedTypes -internal fun UInt.Companion.read(buf: ByteBuffer): UInt { - return UInt.lift(buf.getInt()) -} - -@ExperimentalUnsignedTypes -internal fun UInt.lower(): Int { - return this.toInt() -} - -@ExperimentalUnsignedTypes -internal fun UInt.write(buf: RustBufferBuilder) { - buf.putInt(this.toInt()) -} - - - - - -@ExperimentalUnsignedTypes -internal fun ULong.Companion.lift(v: Long): ULong { - return v.toULong() -} - -@ExperimentalUnsignedTypes -internal fun ULong.Companion.read(buf: ByteBuffer): ULong { - return ULong.lift(buf.getLong()) -} - -@ExperimentalUnsignedTypes -internal fun ULong.lower(): Long { - return this.toLong() -} - -@ExperimentalUnsignedTypes -internal fun ULong.write(buf: RustBufferBuilder) { - buf.putLong(this.toLong()) -} - - - - - -internal fun Float.Companion.lift(v: Float): Float { - return v -} - -internal fun Float.Companion.read(buf: ByteBuffer): Float { - return buf.getFloat() -} - -internal fun Float.lower(): Float { - return this -} - -internal fun Float.write(buf: RustBufferBuilder) { - buf.putFloat(this) -} - - - - - -internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String { - try { - val byteArr = ByteArray(rbuf.len) - rbuf.asByteBuffer()!!.get(byteArr) - return byteArr.toString(Charsets.UTF_8) - } finally { - RustBuffer.free(rbuf) - } -} - -internal fun String.Companion.read(buf: ByteBuffer): String { - val len = buf.getInt() - val byteArr = ByteArray(len) - buf.get(byteArr) - return byteArr.toString(Charsets.UTF_8) -} - -internal fun String.lower(): RustBuffer.ByValue { - val byteArr = this.toByteArray(Charsets.UTF_8) - // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us - // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. - val rbuf = RustBuffer.alloc(byteArr.size) - rbuf.asByteBuffer()!!.put(byteArr) - return rbuf -} - -internal fun String.write(buf: RustBufferBuilder) { - val byteArr = this.toByteArray(Charsets.UTF_8) - buf.putInt(byteArr.size) - buf.put(byteArr) -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// Helper functions for pasing values of type UByte? -@ExperimentalUnsignedTypes -internal fun liftOptionalu8(rbuf: RustBuffer.ByValue): UByte? { - return liftFromRustBuffer(rbuf) { buf -> - readOptionalu8(buf) - } -} - -@ExperimentalUnsignedTypes -internal fun readOptionalu8(buf: ByteBuffer): UByte? { - if (buf.get().toInt() == 0) { - return null - } - return UByte.read(buf) -} - -@ExperimentalUnsignedTypes -internal fun lowerOptionalu8(v: UByte?): RustBuffer.ByValue { - return lowerIntoRustBuffer(v) { v, buf -> - writeOptionalu8(v, buf) - } -} - -@ExperimentalUnsignedTypes -internal fun writeOptionalu8(v: UByte?, buf: RustBufferBuilder) { - if (v == null) { - buf.putByte(0) - } else { - buf.putByte(1) - v.write(buf) - } -} - - - - - - - -// Helper functions for pasing values of type UInt? -@ExperimentalUnsignedTypes -internal fun liftOptionalu32(rbuf: RustBuffer.ByValue): UInt? { - return liftFromRustBuffer(rbuf) { buf -> - readOptionalu32(buf) - } -} - -@ExperimentalUnsignedTypes -internal fun readOptionalu32(buf: ByteBuffer): UInt? { - if (buf.get().toInt() == 0) { - return null - } - return UInt.read(buf) -} - -@ExperimentalUnsignedTypes -internal fun lowerOptionalu32(v: UInt?): RustBuffer.ByValue { - return lowerIntoRustBuffer(v) { v, buf -> - writeOptionalu32(v, buf) - } -} - -@ExperimentalUnsignedTypes -internal fun writeOptionalu32(v: UInt?, buf: RustBufferBuilder) { - if (v == null) { - buf.putByte(0) - } else { - buf.putByte(1) - v.write(buf) - } -} - - - - - - - -// Helper functions for pasing values of type ULong? -@ExperimentalUnsignedTypes -internal fun liftOptionalu64(rbuf: RustBuffer.ByValue): ULong? { - return liftFromRustBuffer(rbuf) { buf -> - readOptionalu64(buf) - } -} - -@ExperimentalUnsignedTypes -internal fun readOptionalu64(buf: ByteBuffer): ULong? { - if (buf.get().toInt() == 0) { - return null - } - return ULong.read(buf) -} - -@ExperimentalUnsignedTypes -internal fun lowerOptionalu64(v: ULong?): RustBuffer.ByValue { - return lowerIntoRustBuffer(v) { v, buf -> - writeOptionalu64(v, buf) - } -} - -@ExperimentalUnsignedTypes -internal fun writeOptionalu64(v: ULong?, buf: RustBufferBuilder) { - if (v == null) { - buf.putByte(0) - } else { - buf.putByte(1) - v.write(buf) - } -} - - - - - - - -// Helper functions for pasing values of type String? - -internal fun liftOptionalstring(rbuf: RustBuffer.ByValue): String? { - return liftFromRustBuffer(rbuf) { buf -> - readOptionalstring(buf) - } -} - - -internal fun readOptionalstring(buf: ByteBuffer): String? { - if (buf.get().toInt() == 0) { - return null - } - return String.read(buf) -} - - -internal fun lowerOptionalstring(v: String?): RustBuffer.ByValue { - return lowerIntoRustBuffer(v) { v, buf -> - writeOptionalstring(v, buf) - } -} - - -internal fun writeOptionalstring(v: String?, buf: RustBufferBuilder) { - if (v == null) { - buf.putByte(0) - } else { - buf.putByte(1) - v.write(buf) - } -} - - - - - - - -// Helper functions for pasing values of type List - -@ExperimentalUnsignedTypes -internal fun liftSequenceTypeConfirmedTransaction(rbuf: RustBuffer.ByValue): List { - return liftFromRustBuffer(rbuf) { buf -> - readSequenceTypeConfirmedTransaction(buf) - } -} - -@ExperimentalUnsignedTypes -internal fun readSequenceTypeConfirmedTransaction(buf: ByteBuffer): List { - val len = buf.getInt() - return List(len) { - ConfirmedTransaction.read(buf) - } -} - -@ExperimentalUnsignedTypes -internal fun lowerSequenceTypeConfirmedTransaction(v: List): RustBuffer.ByValue { - return lowerIntoRustBuffer(v) { v, buf -> - writeSequenceTypeConfirmedTransaction(v, buf) - } -} - -@ExperimentalUnsignedTypes -internal fun writeSequenceTypeConfirmedTransaction(v: List, buf: RustBufferBuilder) { - buf.putInt(v.size) - v.forEach { - it.write(buf) - } -} - - - - -@Synchronized -fun findLibraryName(componentName: String): String { - val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride") - if (libOverride != null) { - return libOverride - } - return "uniffi_bdk" -} - -inline fun loadIndirect( - componentName: String -): Lib { - return Native.load(findLibraryName(componentName), Lib::class.java) -} - -// A JNA Library to expose the extern-C FFI definitions. -// This is an implementation detail which will be called internally by the public API. - -internal interface _UniFFILib : Library { - companion object { - internal val INSTANCE: _UniFFILib by lazy { - loadIndirect<_UniFFILib>(componentName = "bdk") - .also { lib: _UniFFILib -> - CallbackInterfaceBdkProgressInternals.register(lib) - } - - } - } - - fun ffi_bdk_1945_OfflineWallet_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_1945_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_1945_OfflineWallet_get_new_address(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_1945_OfflineWallet_get_balance(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Long - - fun bdk_1945_OfflineWallet_sign(ptr: Pointer,psbt: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_1945_OfflineWallet_get_transactions(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_1945_OnlineWallet_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_1945_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_1945_OnlineWallet_get_new_address(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_1945_OnlineWallet_get_balance(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Long - - fun bdk_1945_OnlineWallet_sign(ptr: Pointer,psbt: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_1945_OnlineWallet_get_transactions(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_1945_OnlineWallet_get_network(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_1945_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_1945_OnlineWallet_broadcast(ptr: Pointer,psbt: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_1945_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_1945_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long, - uniffi_out_err: RustCallStatus - ): Pointer - - fun ffi_bdk_1945_BdkProgress_init_callback(callback_stub: ForeignCallback, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_1945_rustbuffer_alloc(size: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_1945_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_1945_rustbuffer_free(buf: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_1945_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - -} - -// A handful of classes and functions to support the generated data structures. -// This would be a good candidate for isolating in its own ffi-support lib. - - - -// Interface implemented by anything that can contain an object reference. -// -// Such types expose a `destroy()` method that must be called to cleanly -// dispose of the contained objects. Failure to call this method may result -// in memory leaks. -// -// The easiest way to ensure this method is called is to use the `.use` -// helper method to execute a block and destroy the object at the end. -interface Disposable { - fun destroy() -} - -inline fun T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } - -// The base class for all UniFFI Object types. -// -// This class provides core operations for working with the Rust `Arc` pointer to -// the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an `FFIObject` is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an `FFIObject` instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so will -// leak the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// In the future we may be able to replace some of this with automatic finalization logic, such as using -// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is -// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also -// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], -// so there would still be some complexity here). -// -// Sigh...all of this for want of a robust finalization mechanism. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// -abstract class FFIObject( - protected val pointer: Pointer -): Disposable, AutoCloseable { - - val wasDestroyed = AtomicBoolean(false) - val callCounter = AtomicLong(1) - - open protected fun freeRustArcPtr() { - // To be overridden in subclasses. - } - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.pointer) - } finally { - // This decrement aways matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } -} - - - -internal typealias Handle = Long -internal class ConcurrentHandleMap( - private val leftMap: MutableMap = mutableMapOf(), - private val rightMap: MutableMap = mutableMapOf() -) { - private val lock = java.util.concurrent.locks.ReentrantLock() - private val currentHandle = AtomicLong(0L) - private val stride = 1L - - fun insert(obj: T): Handle = - lock.withLock { - rightMap[obj] ?: - currentHandle.getAndAdd(stride) - .also { handle -> - leftMap[handle] = obj - rightMap[obj] = handle - } - } - - fun callWithResult(handle: Handle, fn: (T) -> R): R = - lock.withLock { - leftMap[handle] ?: throw RuntimeException("Panic: handle not in handlemap") - }.let { obj -> - fn.invoke(obj) - } - - fun get(handle: Handle) = lock.withLock { - leftMap[handle] - } - - fun delete(handle: Handle) { - this.remove(handle) - } - - fun remove(handle: Handle): T? = - lock.withLock { - leftMap.remove(handle)?.let { obj -> - rightMap.remove(obj) - obj - } - } -} - -interface ForeignCallback : com.sun.jna.Callback { - public fun invoke(handle: Long, method: Int, args: RustBuffer.ByValue): RustBuffer.ByValue -} - -// Magic number for the Rust proxy to call using the same mechanism as every other method, -// to free the callback once it's dropped by Rust. -internal const val IDX_CALLBACK_FREE = 0 - -internal abstract class CallbackInternals( - val foreignCallback: ForeignCallback -) { - val handleMap = ConcurrentHandleMap() - - // Registers the foreign callback with the Rust side. - // This method is generated for each callback interface. - abstract fun register(lib: _UniFFILib) - - fun drop(handle: Long): RustBuffer.ByValue { - return handleMap.remove(handle).let { RustBuffer.ByValue() } - } - - fun lift(n: Long) = handleMap.get(n) - - fun read(buf: ByteBuffer) = lift(buf.getLong()) - - fun lower(v: CallbackInterface) = - handleMap.insert(v).also { - assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } - } - - fun write(v: CallbackInterface, buf: RustBufferBuilder) = - buf.putLong(lower(v)) -} - - -// Public interface members begin here. -// Public facing enums - - - - - -enum class Network { - BITCOIN,TESTNET,SIGNET,REGTEST; - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): Network { - return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) } - } - - internal fun read(buf: ByteBuffer) = - try { values()[buf.getInt() - 1] } - catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - buf.putInt(this.ordinal + 1) - } -} - - - - - - - - - -sealed class DatabaseConfig { - - data class Memory( - val junk: String - ) : DatabaseConfig() - - data class Sled( - val config: SledDbConfiguration - ) : DatabaseConfig() - - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig { - return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): DatabaseConfig { - return when(buf.getInt()) { - 1 -> DatabaseConfig.Memory( - String.read(buf) - ) - 2 -> DatabaseConfig.Sled( - SledDbConfiguration.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is DatabaseConfig.Memory -> { - buf.putInt(1) - this.junk.write(buf) - - } - is DatabaseConfig.Sled -> { - buf.putInt(2) - this.config.write(buf) - - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - - - -} - - - - - - - - -@ExperimentalUnsignedTypes -sealed class BlockchainConfig { - - data class Electrum( - val config: ElectrumConfig - ) : BlockchainConfig() - - data class Esplora( - val config: EsploraConfig - ) : BlockchainConfig() - - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig { - return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): BlockchainConfig { - return when(buf.getInt()) { - 1 -> BlockchainConfig.Electrum( - ElectrumConfig.read(buf) - ) - 2 -> BlockchainConfig.Esplora( - EsploraConfig.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is BlockchainConfig.Electrum -> { - buf.putInt(1) - this.config.write(buf) - - } - is BlockchainConfig.Esplora -> { - buf.putInt(2) - this.config.write(buf) - - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - - - -} - -// Error definitions -@Structure.FieldOrder("code", "error_buf") -internal open class RustCallStatus : Structure() { - @JvmField var code: Int = 0 - @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - - fun isSuccess(): Boolean { - return code == 0 - } - - fun isError(): Boolean { - return code == 1 - } - - fun isPanic(): Boolean { - return code == 2 - } -} - -class InternalException(message: String) : Exception(message) - -// Each top-level error class has a companion object that can lift the error from the call status's rust buffer -interface CallStatusErrorHandler { - fun lift(error_buf: RustBuffer.ByValue): E; -} - -// Error BdkError - -sealed class BdkException(message: String): Exception(message) { - // Each variant is a nested class - // Flat enums carries a string error message, so no special implementation is necessary. - class InvalidU32Bytes(message: String) : BdkException(message) - class Generic(message: String) : BdkException(message) - class ScriptDoesntHaveAddressForm(message: String) : BdkException(message) - class NoRecipients(message: String) : BdkException(message) - class NoUtxosSelected(message: String) : BdkException(message) - class OutputBelowDustLimit(message: String) : BdkException(message) - class InsufficientFunds(message: String) : BdkException(message) - class BnBTotalTriesExceeded(message: String) : BdkException(message) - class BnBNoExactMatch(message: String) : BdkException(message) - class UnknownUtxo(message: String) : BdkException(message) - class TransactionNotFound(message: String) : BdkException(message) - class TransactionConfirmed(message: String) : BdkException(message) - class IrreplaceableTransaction(message: String) : BdkException(message) - class FeeRateTooLow(message: String) : BdkException(message) - class FeeTooLow(message: String) : BdkException(message) - class FeeRateUnavailable(message: String) : BdkException(message) - class MissingKeyOrigin(message: String) : BdkException(message) - class Key(message: String) : BdkException(message) - class ChecksumMismatch(message: String) : BdkException(message) - class SpendingPolicyRequired(message: String) : BdkException(message) - class InvalidPolicyPathException(message: String) : BdkException(message) - class Signer(message: String) : BdkException(message) - class InvalidNetwork(message: String) : BdkException(message) - class InvalidProgressValue(message: String) : BdkException(message) - class ProgressUpdateException(message: String) : BdkException(message) - class InvalidOutpoint(message: String) : BdkException(message) - class Descriptor(message: String) : BdkException(message) - class AddressValidator(message: String) : BdkException(message) - class Encode(message: String) : BdkException(message) - class Miniscript(message: String) : BdkException(message) - class Bip32(message: String) : BdkException(message) - class Secp256k1(message: String) : BdkException(message) - class Json(message: String) : BdkException(message) - class Hex(message: String) : BdkException(message) - class Psbt(message: String) : BdkException(message) - class PsbtParse(message: String) : BdkException(message) - class Electrum(message: String) : BdkException(message) - class Esplora(message: String) : BdkException(message) - class Sled(message: String) : BdkException(message) - - - companion object ErrorHandler : CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): BdkException { - return liftFromRustBuffer(error_buf) { error_buf -> read(error_buf) } - } - - fun read(error_buf: ByteBuffer): BdkException { - - return when(error_buf.getInt()) { - 1 -> BdkException.InvalidU32Bytes(String.read(error_buf)) - 2 -> BdkException.Generic(String.read(error_buf)) - 3 -> BdkException.ScriptDoesntHaveAddressForm(String.read(error_buf)) - 4 -> BdkException.NoRecipients(String.read(error_buf)) - 5 -> BdkException.NoUtxosSelected(String.read(error_buf)) - 6 -> BdkException.OutputBelowDustLimit(String.read(error_buf)) - 7 -> BdkException.InsufficientFunds(String.read(error_buf)) - 8 -> BdkException.BnBTotalTriesExceeded(String.read(error_buf)) - 9 -> BdkException.BnBNoExactMatch(String.read(error_buf)) - 10 -> BdkException.UnknownUtxo(String.read(error_buf)) - 11 -> BdkException.TransactionNotFound(String.read(error_buf)) - 12 -> BdkException.TransactionConfirmed(String.read(error_buf)) - 13 -> BdkException.IrreplaceableTransaction(String.read(error_buf)) - 14 -> BdkException.FeeRateTooLow(String.read(error_buf)) - 15 -> BdkException.FeeTooLow(String.read(error_buf)) - 16 -> BdkException.FeeRateUnavailable(String.read(error_buf)) - 17 -> BdkException.MissingKeyOrigin(String.read(error_buf)) - 18 -> BdkException.Key(String.read(error_buf)) - 19 -> BdkException.ChecksumMismatch(String.read(error_buf)) - 20 -> BdkException.SpendingPolicyRequired(String.read(error_buf)) - 21 -> BdkException.InvalidPolicyPathException(String.read(error_buf)) - 22 -> BdkException.Signer(String.read(error_buf)) - 23 -> BdkException.InvalidNetwork(String.read(error_buf)) - 24 -> BdkException.InvalidProgressValue(String.read(error_buf)) - 25 -> BdkException.ProgressUpdateException(String.read(error_buf)) - 26 -> BdkException.InvalidOutpoint(String.read(error_buf)) - 27 -> BdkException.Descriptor(String.read(error_buf)) - 28 -> BdkException.AddressValidator(String.read(error_buf)) - 29 -> BdkException.Encode(String.read(error_buf)) - 30 -> BdkException.Miniscript(String.read(error_buf)) - 31 -> BdkException.Bip32(String.read(error_buf)) - 32 -> BdkException.Secp256k1(String.read(error_buf)) - 33 -> BdkException.Json(String.read(error_buf)) - 34 -> BdkException.Hex(String.read(error_buf)) - 35 -> BdkException.Psbt(String.read(error_buf)) - 36 -> BdkException.PsbtParse(String.read(error_buf)) - 37 -> BdkException.Electrum(String.read(error_buf)) - 38 -> BdkException.Esplora(String.read(error_buf)) - 39 -> BdkException.Sled(String.read(error_buf)) - else -> throw RuntimeException("invalid error enum value, something is very wrong!!") - } - } - } - - - -} - - -// Helpers for calling Rust -// In practice we usually need to be synchronized to call this safely, so it doesn't -// synchronize itself - -// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err -private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { - var status = RustCallStatus(); - val return_value = callback(status) - if (status.isSuccess()) { - return return_value - } else if (status.isError()) { - throw errorHandler.lift(status.error_buf) - } else if (status.isPanic()) { - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if (status.error_buf.len > 0) { - throw InternalException(String.lift(status.error_buf)) - } else { - throw InternalException("Rust panic") - } - } else { - throw InternalException("Unknown rust call status: $status.code") - } -} - -// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR -object NullCallStatusErrorHandler: CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): InternalException { - RustBuffer.free(error_buf) - return InternalException("Unexpected CALL_ERROR") - } -} - -// Call a rust function that returns a plain value -private inline fun rustCall(callback: (RustCallStatus) -> U): U { - return rustCallWithError(NullCallStatusErrorHandler, callback); -} - -// Public facing records - -data class SledDbConfiguration ( - var path: String, - var treeName: String -) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): SledDbConfiguration { - return liftFromRustBuffer(rbuf) { buf -> SledDbConfiguration.read(buf) } - } - - internal fun read(buf: ByteBuffer): SledDbConfiguration { - return SledDbConfiguration( - String.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.path.write(buf) - - this.treeName.write(buf) - - } - - - -} - -@ExperimentalUnsignedTypes -data class ConfirmedTransaction ( - var fees: ULong?, - var height: UInt, - var timestamp: ULong, - var received: ULong, - var sent: ULong, - var id: String -) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): ConfirmedTransaction { - return liftFromRustBuffer(rbuf) { buf -> ConfirmedTransaction.read(buf) } - } - - internal fun read(buf: ByteBuffer): ConfirmedTransaction { - return ConfirmedTransaction( - readOptionalu64(buf), - UInt.read(buf), - ULong.read(buf), - ULong.read(buf), - ULong.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - writeOptionalu64(this.fees, buf) - - this.height.write(buf) - - this.timestamp.write(buf) - - this.received.write(buf) - - this.sent.write(buf) - - this.id.write(buf) - - } - - - -} - -@ExperimentalUnsignedTypes -data class ElectrumConfig ( - var url: String, - var socks5: String?, - var retry: UByte, - var timeout: UByte?, - var stopGap: ULong -) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): ElectrumConfig { - return liftFromRustBuffer(rbuf) { buf -> ElectrumConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): ElectrumConfig { - return ElectrumConfig( - String.read(buf), - readOptionalstring(buf), - UByte.read(buf), - readOptionalu8(buf), - ULong.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.url.write(buf) - - writeOptionalstring(this.socks5, buf) - - this.retry.write(buf) - - writeOptionalu8(this.timeout, buf) - - this.stopGap.write(buf) - - } - - - -} - -@ExperimentalUnsignedTypes -data class EsploraConfig ( - var baseUrl: String, - var proxy: String?, - var timeoutRead: ULong, - var timeoutWrite: ULong, - var stopGap: ULong -) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): EsploraConfig { - return liftFromRustBuffer(rbuf) { buf -> EsploraConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): EsploraConfig { - return EsploraConfig( - String.read(buf), - readOptionalstring(buf), - ULong.read(buf), - ULong.read(buf), - ULong.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.baseUrl.write(buf) - - writeOptionalstring(this.proxy, buf) - - this.timeoutRead.write(buf) - - this.timeoutWrite.write(buf) - - this.stopGap.write(buf) - - } - - - -} - - -// Namespace functions - - -// Objects - -@ExperimentalUnsignedTypes -public interface OfflineWalletInterface { - fun getNewAddress(): String - fun getBalance(): ULong - fun sign(psbt: PartiallySignedBitcoinTransaction ) - fun getTransactions(): List - -} - -@ExperimentalUnsignedTypes -class OfflineWallet( - pointer: Pointer -) : FFIObject(pointer), OfflineWalletInterface { - constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : - this( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) -}) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { - rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_1945_OfflineWallet_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun getNewAddress(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_get_new_address(it, status) -} - }.let { - String.lift(it) - } - - override fun getBalance(): ULong = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_get_balance(it, status) -} - }.let { - ULong.lift(it) - } - - override fun sign(psbt: PartiallySignedBitcoinTransaction ) = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_sign(it, psbt.lower() , status) -} - } - - override fun getTransactions(): List = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_get_transactions(it, status) -} - }.let { - liftSequenceTypeConfirmedTransaction(it) - } - - - - companion object { - internal fun lift(ptr: Pointer): OfflineWallet { - return OfflineWallet(ptr) - } - - internal fun read(buf: ByteBuffer): OfflineWallet { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return OfflineWallet.lift(Pointer(buf.getLong())) - } - - - } -} - -@ExperimentalUnsignedTypes -public interface OnlineWalletInterface { - fun getNewAddress(): String - fun getBalance(): ULong - fun sign(psbt: PartiallySignedBitcoinTransaction ) - fun getTransactions(): List - fun getNetwork(): Network - fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) - fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String - -} - -@ExperimentalUnsignedTypes -class OnlineWallet( - pointer: Pointer -) : FFIObject(pointer), OnlineWalletInterface { - constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : - this( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) -}) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { - rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_1945_OnlineWallet_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun getNewAddress(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_new_address(it, status) -} - }.let { - String.lift(it) - } - - override fun getBalance(): ULong = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_balance(it, status) -} - }.let { - ULong.lift(it) - } - - override fun sign(psbt: PartiallySignedBitcoinTransaction ) = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_sign(it, psbt.lower() , status) -} - } - - override fun getTransactions(): List = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_transactions(it, status) -} - }.let { - liftSequenceTypeConfirmedTransaction(it) - } - - override fun getNetwork(): Network = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_network(it, status) -} - }.let { - Network.lift(it) - } - - override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) -} - } - - override fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_broadcast(it, psbt.lower() , status) -} - }.let { - String.lift(it) - } - - - - companion object { - internal fun lift(ptr: Pointer): OnlineWallet { - return OnlineWallet(ptr) - } - - internal fun read(buf: ByteBuffer): OnlineWallet { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return OnlineWallet.lift(Pointer(buf.getLong())) - } - - - } -} - -@ExperimentalUnsignedTypes -public interface PartiallySignedBitcoinTransactionInterface { - -} - -@ExperimentalUnsignedTypes -class PartiallySignedBitcoinTransaction( - pointer: Pointer -) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface { - constructor(wallet: OnlineWallet, recipient: String, amount: ULong ) : - this( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_1945_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower() ,status) -}) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { - rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_1945_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - - - companion object { - internal fun lift(ptr: Pointer): PartiallySignedBitcoinTransaction { - return PartiallySignedBitcoinTransaction(ptr) - } - - internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong())) - } - - - } -} - - -// Callback Interfaces - - -public interface BdkProgress { - fun update(progress: Float, message: String? ) - -} - - -internal class CallbackInterfaceBdkProgressFFI : ForeignCallback { - @Suppress("TooGenericExceptionCaught") - override fun invoke(handle: Long, method: Int, args: RustBuffer.ByValue): RustBuffer.ByValue { - return CallbackInterfaceBdkProgressInternals.handleMap.callWithResult(handle) { cb -> - when (method) { - IDX_CALLBACK_FREE -> CallbackInterfaceBdkProgressInternals.drop(handle) - 1 -> this.invokeUpdate(cb, args) - - // This should never happen, because an out of bounds method index won't - // ever be used. Once we can catch errors, we should return an InternalException. - // https://github.com/mozilla/uniffi-rs/issues/351 - else -> RustBuffer.ByValue() - } - } - } - - - private fun invokeUpdate(kotlinCallbackInterface: BdkProgress, args: RustBuffer.ByValue): RustBuffer.ByValue = - try { - val buf = args.asByteBuffer() ?: throw InternalException("No ByteBuffer in RustBuffer; this is a Uniffi bug") - kotlinCallbackInterface.update( - Float.read(buf), - readOptionalstring(buf) - ) - .let { RustBuffer.ByValue() } - // TODO catch errors and report them back to Rust. - // https://github.com/mozilla/uniffi-rs/issues/351 - } finally { - RustBuffer.free(args) - } - - -} - -internal object CallbackInterfaceBdkProgressInternals: CallbackInternals( - foreignCallback = CallbackInterfaceBdkProgressFFI() -) { - override fun register(lib: _UniFFILib) { - rustCall() { status -> - lib.ffi_bdk_1945_BdkProgress_init_callback(this.foreignCallback, status) - } - } -} - - diff --git a/build.sh b/build.sh index b67fff0..eaeb2cd 100755 --- a/build.sh +++ b/build.sh @@ -32,13 +32,11 @@ copy_lib_kotlin() { echo -n "darwin " mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 - cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/demo/src/main/resources/darwin-x86-64 ;; "Linux") echo -n "linux " mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 cp target/debug/libuniffi_bdk.so bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 - cp target/debug/libuniffi_bdk.so bindings/bdk-kotlin/demo/src/main/resources/linux-x86-64 ;; esac echo "libs to kotlin sub-project" From f556f611b0be0d638dd4e406be781a66f1ad9ddc Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 17 Oct 2021 13:56:28 -0700 Subject: [PATCH 081/272] Move kotlin tests to fest-fixtures submodule --- bindings/bdk-kotlin/jvm/build.gradle | 12 +++--- .../org/bitcoindevkit/bdk/JvmLibTest.kt | 16 ++++++++ bindings/bdk-kotlin/settings.gradle | 2 +- .../bdk-kotlin/test-fixtures/build.gradle | 18 +++++++++ .../kotlin/org/bitcoindevkit}/bdk/LibTest.kt | 37 +++++++++++++------ 5 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt create mode 100644 bindings/bdk-kotlin/test-fixtures/build.gradle rename bindings/bdk-kotlin/{jvm/src/test/kotlin/uniffi => test-fixtures/src/main/kotlin/org/bitcoindevkit}/bdk/LibTest.kt (70%) diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle index 94f7927..58eefc8 100644 --- a/bindings/bdk-kotlin/jvm/build.gradle +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -4,6 +4,11 @@ plugins { id 'maven-publish' } +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + test { testLogging { events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" @@ -18,7 +23,7 @@ dependencies { api "org.slf4j:slf4j-api:1.7.30" testImplementation "ch.qos.logback:logback-classic:1.2.3" testImplementation "ch.qos.logback:logback-core:1.2.3" - //testImplementation(project(':test-fixtures')) + testImplementation(project(':test-fixtures')) } publishing { @@ -32,8 +37,3 @@ publishing { } } } - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} diff --git a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt new file mode 100644 index 0000000..ceea526 --- /dev/null +++ b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt @@ -0,0 +1,16 @@ +package org.bitcoindevkit.bdk + +import java.nio.file.Files + +/** + * Library test, which will execute on linux host. + * + */ +class JvmLibTest : LibTest() { + + override fun getTestDataDir(): String { + return Files.createTempDirectory("bdk-test").toString() + //return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString() + } + +} diff --git a/bindings/bdk-kotlin/settings.gradle b/bindings/bdk-kotlin/settings.gradle index ac610be..4c3c912 100644 --- a/bindings/bdk-kotlin/settings.gradle +++ b/bindings/bdk-kotlin/settings.gradle @@ -1,4 +1,4 @@ rootProject.name = 'bdk-kotlin' -include ':jvm', ':demo' //, ':android', ':test-fixtures' +include ':jvm',':demo',':test-fixtures',':android' diff --git a/bindings/bdk-kotlin/test-fixtures/build.gradle b/bindings/bdk-kotlin/test-fixtures/build.gradle new file mode 100644 index 0000000..44f2c40 --- /dev/null +++ b/bindings/bdk-kotlin/test-fixtures/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'java-library' +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + implementation platform('org.jetbrains.kotlin:kotlin-bom') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation(project(':jvm')) + implementation "junit:junit:4.13.2" + api "org.slf4j:slf4j-api:1.7.30" +} diff --git a/bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt similarity index 70% rename from bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt rename to bindings/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt index 6e8c7b1..1ac682f 100644 --- a/bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt @@ -1,19 +1,24 @@ -package uniffi.bdk +package org.bitcoindevkit.bdk -import uniffi.bdk.OfflineWallet import org.junit.Assert.* import org.junit.Test - -class LogProgress: BdkProgress { - override fun update(progress: Float, message: String? ) { - println("progress: $progress, message: $message") - } -} +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import uniffi.bdk.* +import java.io.File /** * Library tests which will execute for jvm and android modules. */ -class LibTest { +abstract class LibTest { + + abstract fun getTestDataDir(): String + + fun cleanupTestDataDir(testDataDir: String) { + File(testDataDir).deleteRecursively() + } + + val log: Logger = LoggerFactory.getLogger(LibTest::class.java) val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" @@ -27,7 +32,7 @@ class LibTest { assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") } - @Test(expected=BdkException.Descriptor::class) + @Test(expected= BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { val config = DatabaseConfig.Memory("") OfflineWallet("invalid-descriptor", Network.REGTEST, config) @@ -35,11 +40,13 @@ class LibTest { @Test fun sledWalletNewAddress() { - val config = DatabaseConfig.Sled(SledDbConfiguration("/tmp/testdb", "testdb")) + val testDataDir = getTestDataDir() + val config = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) val wallet = OfflineWallet(desc, Network.REGTEST, config) val address = wallet.getNewAddress() assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + cleanupTestDataDir(testDataDir) } @Test @@ -52,6 +59,14 @@ class LibTest { assertEquals(network, Network.TESTNET) } + class LogProgress: BdkProgress { + val log: Logger = LoggerFactory.getLogger(LibTest::class.java) + + override fun update(progress: Float, message: String?) { + log.debug("Syncing...") + } + } + @Test fun onlineWalletSyncGetBalance() { val db = DatabaseConfig.Memory("") From c9e8368694995800e112d1a58a07267b108f7dca Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 17 Oct 2021 14:27:10 -0700 Subject: [PATCH 082/272] Add android aar build and connected device test --- bindings/bdk-kotlin/android/build.gradle | 71 +++++++++++++++++++ .../bdk-kotlin/android/proguard-rules.pro | 26 +++++++ .../src/androidTest/assets/logback.xml | 14 ++++ .../org/bitcoindevkit/bdk/AndroidLibTest.kt | 21 ++++++ .../android/src/main/AndroidManifest.xml | 6 ++ build.sh | 15 ++-- test.sh | 8 ++- 7 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 bindings/bdk-kotlin/android/build.gradle create mode 100644 bindings/bdk-kotlin/android/proguard-rules.pro create mode 100644 bindings/bdk-kotlin/android/src/androidTest/assets/logback.xml create mode 100644 bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt create mode 100644 bindings/bdk-kotlin/android/src/main/AndroidManifest.xml diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle new file mode 100644 index 0000000..f3bcdc8 --- /dev/null +++ b/bindings/bdk-kotlin/android/build.gradle @@ -0,0 +1,71 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' + +android { + compileSdkVersion 30 + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +afterEvaluate { + + publishing { + publications { + // Creates a Maven publication called "release". + release(MavenPublication) { + // Applies the component for the release build variant. + from components.release + + // You can then customize attributes of the publication as shown below. + groupId = 'org.bitcoindevkit' + artifactId = 'bdk' + version = '0.0.1-SNAPSHOT' + } + // Creates a Maven publication called “debug”. + debug(MavenPublication) { + // Applies the component for the debug build variant. + from components.debug + + groupId = 'org.bitcoindevkit' + artifactId = 'bdk-debug' + version = '0.0.1-SNAPSHOT' + } + } + } +} + +dependencies { + implementation(project(':jvm')) { + exclude group: 'net.java.dev.jna', module: 'jna' + } + + implementation 'net.java.dev.jna:jna:5.8.0@aar' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'androidx.core:core-ktx:1.5.0' + api "org.slf4j:slf4j-api:1.7.30" + + androidTestImplementation 'com.github.tony19:logback-android:2.0.0' + androidTestImplementation(project(':test-fixtures')) { + exclude group: 'net.java.dev.jna', module: 'jna' + } + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' +} diff --git a/bindings/bdk-kotlin/android/proguard-rules.pro b/bindings/bdk-kotlin/android/proguard-rules.pro new file mode 100644 index 0000000..172980c --- /dev/null +++ b/bindings/bdk-kotlin/android/proguard-rules.pro @@ -0,0 +1,26 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +# for JNA +-dontwarn java.awt.* +-keep class com.sun.jna.* { *; } +-keepclassmembers class * extends com.sun.jna.* { public *; } diff --git a/bindings/bdk-kotlin/android/src/androidTest/assets/logback.xml b/bindings/bdk-kotlin/android/src/androidTest/assets/logback.xml new file mode 100644 index 0000000..efb1f6b --- /dev/null +++ b/bindings/bdk-kotlin/android/src/androidTest/assets/logback.xml @@ -0,0 +1,14 @@ + + + + %logger{12} + + + [%-20thread] %msg + + + + + + + diff --git a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt new file mode 100644 index 0000000..9e1eec0 --- /dev/null +++ b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt @@ -0,0 +1,21 @@ +package org.bitcoindevkit.bdk + +import android.app.Application +import android.content.Context.MODE_PRIVATE +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class AndroidLibTest : LibTest() { + override fun getTestDataDir(): String { + val context = ApplicationProvider.getApplicationContext() + return context.getDir("bdk-test", MODE_PRIVATE).toString() + } + +} diff --git a/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml b/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c8fa4de --- /dev/null +++ b/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/build.sh b/build.sh index eaeb2cd..d94e956 100755 --- a/build.sh +++ b/build.sh @@ -49,6 +49,8 @@ build_kotlin() { ## rust android build_android() { + build_kotlin + # If ANDROID_NDK_HOME is not set then set it to github actions default [ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle @@ -61,27 +63,27 @@ build_android() { # IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" - mkdir -p bdk-kotlin/android/src/main/jniLibs/ bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-kotlin/android/src/main/jniLibs/x86 + mkdir -p bindings/bdk-kotlin/android/src/main/jniLibs/ bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 bindings/bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bindings/bdk-kotlin/android/src/main/jniLibs/x86 if echo $BUILD_TARGETS | grep "aarch64"; then CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android - cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/arm64-v8a + cp target/aarch64-linux-android/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a fi if echo $BUILD_TARGETS | grep "x86_64"; then CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android - cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86_64 + cp target/x86_64-linux-android/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 fi if echo $BUILD_TARGETS | grep "armv7"; then CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi - cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/armeabi-v7a + cp target/armv7-linux-androideabi/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/armeabi-v7a fi if echo $BUILD_TARGETS | grep "i686"; then CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android - cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86 + cp target/i686-linux-android/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 fi # bdk-kotlin aar - (cd bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) + (cd bindings/bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) } OS=$(uname) @@ -95,6 +97,7 @@ else while [ -n "$1" ]; do # while loop starts case "$1" in + -a) build_android ;; -k) build_kotlin ;; -h) help ;; *) echo "Option $1 not recognized" ;; diff --git a/test.sh b/test.sh index e46caed..e552176 100755 --- a/test.sh +++ b/test.sh @@ -11,13 +11,18 @@ help() echo echo "Syntax: build [-a|h|k|v]" echo "options:" + echo "-a Android connected device tests." echo "-h Print this Help." echo "-k Kotlin tests." echo } test_kotlin() { - (cd bindings/bdk-kotlin && rm -rf /tmp/testdb && ./gradlew test -Djna.debug_load=true) + (cd bindings/bdk-kotlin && ./gradlew :jvm:test -Djna.debug_load=true) +} + +test_android() { + (cd bindings/bdk-kotlin && ./gradlew :android:connectedDebugAndroidTest) } if [ $1 = "-h" ] @@ -29,6 +34,7 @@ else # optional tests while [ -n "$1" ]; do # while loop starts case "$1" in + -a) test_android ;; -h) help ;; -k) test_kotlin ;; *) echo "Option $1 not recognized" ;; From e64b1f67c1b410d227e3e62926117f07bf3eee15 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Mon, 18 Oct 2021 15:48:30 +0530 Subject: [PATCH 083/272] List both confirmed and unconfirmed transactions --- .../bdk-kotlin/demo/src/main/kotlin/Main.kt | 53 ++++++++++++------- src/bdk.udl | 19 +++++-- src/lib.rs | 41 +++++++++----- 3 files changed, 77 insertions(+), 36 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index 784db84..43dd182 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -1,3 +1,5 @@ +import java.util.Optional +import kotlin.ExperimentalUnsignedTypes import uniffi.bdk.* class LogProgress : BdkProgress { @@ -10,18 +12,37 @@ class NullProgress : BdkProgress { override fun update(progress: Float, message: String?) {} } -fun getConfirmedTransaction( - wallet: OnlineWalletInterface, - transactionId: String -): ConfirmedTransaction? { +fun getTransaction(wallet: OnlineWalletInterface, transactionId: String): Optional { wallet.sync(NullProgress(), null) return wallet.getTransactions() .stream() - .filter({ it.id.equals(transactionId) }) + .filter({ + when (it) { + is Transaction.Confirmed -> it.details.id.equals(transactionId) + is Transaction.Unconfirmed -> it.details.id.equals(transactionId) + } + }) .findFirst() - .orElse(null) } +@ExperimentalUnsignedTypes +val unconfirmedFirstThenByTimestampDescending = + Comparator { a, b -> + when { + (a is Transaction.Confirmed && b is Transaction.Confirmed) -> { + val comparison = b.confirmation.timestamp.compareTo(a.confirmation.timestamp) + when { + comparison == 0 -> b.details.id.compareTo(a.details.id) + else -> comparison + } + } + (a is Transaction.Confirmed && b is Transaction.Unconfirmed) -> 1 + (a is Transaction.Unconfirmed && b is Transaction.Confirmed) -> -1 + else -> 0 + } + } + +@ExperimentalUnsignedTypes fun main(args: Array) { println("Configuring an in-memory wallet on electrum..") val descriptor = "pkh(cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR)" @@ -41,23 +62,19 @@ fun main(args: Array) { println("New wallet balance: ${wallet.getBalance()}") println("Press Enter to return funds") readLine() - println( - "Creating a PSBT with recipient $recipient and amount $amount satoshis..." - ) + println("Creating a PSBT with recipient $recipient and amount $amount satoshis...") val transaction = PartiallySignedBitcoinTransaction(wallet, recipient, amount) println("Signing the transaction...") wallet.sign(transaction) println("Broadcasting the signed transaction...") val transactionId = wallet.broadcast(transaction) println("Broadcasted transaction with id $transactionId") - println("Confirming transaction...") - var confirmedTransaction = getConfirmedTransaction(wallet, transactionId) - while (confirmedTransaction == null) { - confirmedTransaction = getConfirmedTransaction(wallet, transactionId) - } - println("Confirmed transaction: $confirmedTransaction") - val transactions = wallet.getTransactions() - println("Listing all ${transactions.size} transactions...") - transactions.sortedByDescending { it.timestamp }.forEach { println(it) } + val take = 5 + println("Listing latest $take transactions...") + wallet + .getTransactions() + .sortedWith(unconfirmedFirstThenByTimestampDescending) + .take(take) + .forEach { println(it) } println("Final wallet balance: ${wallet.getBalance()}") } diff --git a/src/bdk.udl b/src/bdk.udl index 138950c..c52b9ac 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -63,15 +63,24 @@ interface DatabaseConfig { Sled(SledDbConfiguration config); }; -dictionary ConfirmedTransaction { +dictionary TransactionDetails { u64? fees; - u32 height; - u64 timestamp; u64 received; u64 sent; string id; }; +dictionary Confirmation { + u32 height; + u64 timestamp; +}; + +[Enum] +interface Transaction { + Unconfirmed(TransactionDetails details); + Confirmed(TransactionDetails details, Confirmation confirmation); +}; + interface OfflineWallet { [Throws=BdkError] constructor(string descriptor, Network network, DatabaseConfig database_config); @@ -83,7 +92,7 @@ interface OfflineWallet { [Throws=BdkError] void sign([ByRef] PartiallySignedBitcoinTransaction psbt); [Throws=BdkError] - sequence get_transactions(); + sequence get_transactions(); }; dictionary ElectrumConfig { @@ -123,7 +132,7 @@ interface OnlineWallet { [Throws=BdkError] void sign([ByRef] PartiallySignedBitcoinTransaction psbt); [Throws=BdkError] - sequence get_transactions(); + sequence get_transactions(); // OnlineWalletInterface Network get_network(); diff --git a/src/lib.rs b/src/lib.rs index 2972e14..02c22b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ use bdk::blockchain::{ use bdk::database::any::{AnyDatabase, SledDbConfiguration}; use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase}; use bdk::wallet::AddressIndex; -use bdk::{Error, SignOptions, Wallet}; +use bdk::{ConfirmationTime, Error, SignOptions, Wallet}; use std::convert::TryFrom; use std::str::FromStr; use std::sync::{Mutex, MutexGuard}; @@ -58,15 +58,24 @@ impl WalletHolder<()> for OfflineWallet { } #[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct ConfirmedTransaction { +pub struct TransactionDetails { pub fees: Option, - pub height: u32, - pub timestamp: u64, pub received: u64, pub sent: u64, pub id: String, } +type Confirmation = ConfirmationTime; +pub enum Transaction { + Unconfirmed { + details: TransactionDetails, + }, + Confirmed { + details: TransactionDetails, + confirmation: Confirmation, + }, +} + trait OfflineWalletOperations: WalletHolder { fn get_new_address(&self) -> String { self.get_wallet() @@ -92,18 +101,24 @@ trait OfflineWalletOperations: WalletHolder { } } - fn get_transactions(&self) -> Result, Error> { + fn get_transactions(&self) -> Result, Error> { let transactions = self.get_wallet().list_transactions(true)?; Ok(transactions .iter() - .filter(|x| x.confirmation_time.is_some()) - .map(|x| ConfirmedTransaction { - fees: x.fee, - height: x.confirmation_time.clone().map_or(0, |c| c.height), - timestamp: x.confirmation_time.clone().map_or(0, |c| c.timestamp), - id: x.txid.to_string(), - received: x.received, - sent: x.sent, + .map(|x| -> Transaction { + let details = TransactionDetails { + fees: x.fee, + id: x.txid.to_string(), + received: x.received, + sent: x.sent, + }; + match x.confirmation_time.clone() { + Some(confirmation) => Transaction::Confirmed { + details, + confirmation, + }, + None => Transaction::Unconfirmed { details }, + } }) .collect()) } From e9f00dcb750190e4d7f56b0e01e975f9e339a767 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 18 Oct 2021 13:53:05 -0700 Subject: [PATCH 084/272] Move bdk-kotlin test-fixtures tests to jvm module --- bindings/bdk-kotlin/android/build.gradle | 4 +- bindings/bdk-kotlin/jvm/build.gradle | 42 +++++++++---------- .../kotlin/org/bitcoindevkit/bdk/LibTest.kt | 24 +++++++++-- bindings/bdk-kotlin/settings.gradle | 2 +- .../bdk-kotlin/test-fixtures/build.gradle | 18 -------- 5 files changed, 45 insertions(+), 45 deletions(-) rename bindings/bdk-kotlin/{test-fixtures/src/main => jvm/src/testFixtures}/kotlin/org/bitcoindevkit/bdk/LibTest.kt (80%) delete mode 100644 bindings/bdk-kotlin/test-fixtures/build.gradle diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index f3bcdc8..f06c838 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -62,8 +62,10 @@ dependencies { api "org.slf4j:slf4j-api:1.7.30" androidTestImplementation 'com.github.tony19:logback-android:2.0.0' - androidTestImplementation(project(':test-fixtures')) { + androidTestImplementation(testFixtures(project(':jvm'))) { exclude group: 'net.java.dev.jna', module: 'jna' + exclude group: 'ch.qos.logback', module: 'logback-core' + exclude group: 'ch.qos.logback', module: 'logback-classic' } androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle index 58eefc8..ea9408a 100644 --- a/bindings/bdk-kotlin/jvm/build.gradle +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -1,7 +1,8 @@ plugins { - id 'org.jetbrains.kotlin.jvm' - id 'java-library' - id 'maven-publish' + id 'org.jetbrains.kotlin.jvm' + id 'java-library' + id 'java-test-fixtures' + id 'maven-publish' } java { @@ -10,30 +11,29 @@ java { } test { - testLogging { - events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" - } + testLogging { + events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" + } } dependencies { - implementation platform('org.jetbrains.kotlin:kotlin-bom') - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "net.java.dev.jna:jna:5.8.0" - implementation "junit:junit:4.13.2" - api "org.slf4j:slf4j-api:1.7.30" - testImplementation "ch.qos.logback:logback-classic:1.2.3" - testImplementation "ch.qos.logback:logback-core:1.2.3" - testImplementation(project(':test-fixtures')) + implementation platform('org.jetbrains.kotlin:kotlin-bom') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "net.java.dev.jna:jna:5.8.0" + api "org.slf4j:slf4j-api:1.7.30" + testFixturesImplementation "junit:junit:4.13.2" + testFixturesImplementation "ch.qos.logback:logback-classic:1.2.3" + testFixturesImplementation "ch.qos.logback:logback-core:1.2.3" } publishing { - publications { - maven(MavenPublication) { - groupId = 'org.bitcoindevkit' - artifactId = 'bdk' - version = '0.0.1-SNAPSHOT' + publications { + maven(MavenPublication) { + groupId = 'org.bitcoindevkit' + artifactId = 'bdk' + version = '0.0.1-SNAPSHOT' - from components.java + from components.java + } } - } } diff --git a/bindings/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt b/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt similarity index 80% rename from bindings/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt rename to bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt index 1ac682f..8b946b4 100644 --- a/bindings/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt @@ -32,7 +32,7 @@ abstract class LibTest { assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") } - @Test(expected= BdkException.Descriptor::class) + @Test(expected = BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { val config = DatabaseConfig.Memory("") OfflineWallet("invalid-descriptor", Network.REGTEST, config) @@ -52,14 +52,22 @@ abstract class LibTest { @Test fun onlineWalletInMemory() { val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 100u)) + val client = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 100u + ) + ) val wallet = OnlineWallet(desc, Network.TESTNET, db, client) assertNotNull(wallet) val network = wallet.getNetwork() assertEquals(network, Network.TESTNET) } - class LogProgress: BdkProgress { + class LogProgress : BdkProgress { val log: Logger = LoggerFactory.getLogger(LibTest::class.java) override fun update(progress: Float, message: String?) { @@ -70,7 +78,15 @@ abstract class LibTest { @Test fun onlineWalletSyncGetBalance() { val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 100u)) + val client = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 100u + ) + ) val wallet = OnlineWallet(desc, Network.TESTNET, db, client) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() diff --git a/bindings/bdk-kotlin/settings.gradle b/bindings/bdk-kotlin/settings.gradle index 4c3c912..9d76d9e 100644 --- a/bindings/bdk-kotlin/settings.gradle +++ b/bindings/bdk-kotlin/settings.gradle @@ -1,4 +1,4 @@ rootProject.name = 'bdk-kotlin' -include ':jvm',':demo',':test-fixtures',':android' +include ':jvm',':demo',':android' diff --git a/bindings/bdk-kotlin/test-fixtures/build.gradle b/bindings/bdk-kotlin/test-fixtures/build.gradle deleted file mode 100644 index 44f2c40..0000000 --- a/bindings/bdk-kotlin/test-fixtures/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -plugins { - id 'org.jetbrains.kotlin.jvm' - id 'java-library' -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -dependencies { - implementation platform('org.jetbrains.kotlin:kotlin-bom') - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - implementation(project(':jvm')) - implementation "junit:junit:4.13.2" - api "org.slf4j:slf4j-api:1.7.30" -} From db4f2db748b8cdf82cc33c23184291a8566f3c85 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 18 Oct 2021 14:49:10 -0700 Subject: [PATCH 085/272] Fix demo gradle warning --- bindings/bdk-kotlin/demo/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/bdk-kotlin/demo/build.gradle b/bindings/bdk-kotlin/demo/build.gradle index 191a764..b320d2f 100644 --- a/bindings/bdk-kotlin/demo/build.gradle +++ b/bindings/bdk-kotlin/demo/build.gradle @@ -28,7 +28,7 @@ compileTestKotlin { } application { - mainClassName = 'MainKt' + mainClass.set('MainKt') } run { From fd03eb95eb5b63a7ef45abeb6cf254098087d1cd Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 18 Oct 2021 16:51:18 -0700 Subject: [PATCH 086/272] Change descriptor to wpkh with tprv key --- bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index 43dd182..530e15c 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -45,7 +45,7 @@ val unconfirmedFirstThenByTimestampDescending = @ExperimentalUnsignedTypes fun main(args: Array) { println("Configuring an in-memory wallet on electrum..") - val descriptor = "pkh(cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR)" + val descriptor = "wpkh(tprv8ZgxMBicQKsPeSitUfdxhsVaf4BXAASVAbHypn2jnPcjmQZvqZYkeqx7EHQTWvdubTSDa5ben7zHC7sUsx4d8tbTvWdUtHzR8uhHg2CW7MT/*)" val amount = 1000uL val recipient = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" val db = DatabaseConfig.Memory("") From 360d2c90057f34ed52536876126784eb4bde6d69 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 21 Oct 2021 14:35:14 +0530 Subject: [PATCH 087/272] Upgrade to gradle 7.2 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- bindings/bdk-kotlin/gradlew | 269 +++++++++++------- bindings/bdk-kotlin/gradlew.bat | 178 ++++++------ 4 files changed, 249 insertions(+), 200 deletions(-) diff --git a/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.jar b/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18435 zcmY&<19zBR)MXm8v2EM7ZQHi-#I|kQZfv7Tn#Q)%81v4zX3d)U4d4 zYYc!v@NU%|U;_sM`2z(4BAilWijmR>4U^KdN)D8%@2KLcqkTDW%^3U(Wg>{qkAF z&RcYr;D1I5aD(N-PnqoEeBN~JyXiT(+@b`4Pv`;KmkBXYN48@0;iXuq6!ytn`vGp$ z6X4DQHMx^WlOek^bde&~cvEO@K$oJ}i`T`N;M|lX0mhmEH zuRpo!rS~#&rg}ajBdma$$}+vEhz?JAFUW|iZEcL%amAg_pzqul-B7Itq6Y_BGmOCC zX*Bw3rFz3R)DXpCVBkI!SoOHtYstv*e-May|+?b80ZRh$MZ$FerlC`)ZKt} zTd0Arf9N2dimjs>mg5&@sfTPsRXKXI;0L~&t+GH zkB<>wxI9D+k5VHHcB7Rku{Z>i3$&hgd9Mt_hS_GaGg0#2EHzyV=j=u5xSyV~F0*qs zW{k9}lFZ?H%@4hII_!bzao!S(J^^ZZVmG_;^qXkpJb7OyR*sPL>))Jx{K4xtO2xTr@St!@CJ=y3q2wY5F`77Tqwz8!&Q{f7Dp zifvzVV1!Dj*dxG%BsQyRP6${X+Tc$+XOG zzvq5xcC#&-iXlp$)L=9t{oD~bT~v^ZxQG;FRz|HcZj|^L#_(VNG)k{=_6|6Bs-tRNCn-XuaZ^*^hpZ@qwi`m|BxcF6IWc?_bhtK_cDZRTw#*bZ2`1@1HcB`mLUmo_>@2R&nj7&CiH zF&laHkG~7#U>c}rn#H)q^|sk+lc!?6wg0xy`VPn!{4P=u@cs%-V{VisOxVqAR{XX+ zw}R;{Ux@6A_QPka=48|tph^^ZFjSHS1BV3xfrbY84^=?&gX=bmz(7C({=*oy|BEp+ zYgj;<`j)GzINJA>{HeSHC)bvp6ucoE`c+6#2KzY9)TClmtEB1^^Mk)(mXWYvup02e%Ghm9qyjz#fO3bNGBX} zFiB>dvc1+If!>I10;qZk`?6pEd*(?bI&G*3YLt;MWw&!?=Mf7%^Op?qnyXWur- zwX|S^P>jF?{m9c&mmK-epCRg#WB+-VDe!2d2~YVoi%7_q(dyC{(}zB${!ElKB2D}P z7QNFM!*O^?FrPMGZ}wQ0TrQAVqZy!weLhu_Zq&`rlD39r*9&2sJHE(JT0EY5<}~x@ z1>P0!L2IFDqAB!($H9s2fI`&J_c+5QT|b#%99HA3@zUWOuYh(~7q7!Pf_U3u!ij5R zjFzeZta^~RvAmd_TY+RU@e}wQaB_PNZI26zmtzT4iGJg9U(Wrgrl>J%Z3MKHOWV(? zj>~Ph$<~8Q_sI+)$DOP^9FE6WhO09EZJ?1W|KidtEjzBX3RCLUwmj9qH1CM=^}MaK z59kGxRRfH(n|0*lkE?`Rpn6d^u5J6wPfi0WF(rucTv(I;`aW)3;nY=J=igkjsn?ED ztH&ji>}TW8)o!Jg@9Z}=i2-;o4#xUksQHu}XT~yRny|kg-$Pqeq!^78xAz2mYP9+4 z9gwAoti2ICvUWxE&RZ~}E)#M8*zy1iwz zHqN%q;u+f6Ti|SzILm0s-)=4)>eb5o-0K zbMW8ecB4p^6OuIX@u`f{>Yn~m9PINEl#+t*jqalwxIx=TeGB9(b6jA}9VOHnE$9sC zH`;epyH!k-3kNk2XWXW!K`L_G!%xOqk0ljPCMjK&VweAxEaZ==cT#;!7)X&C|X{dY^IY(e4D#!tx^vV3NZqK~--JW~wtXJ8X19adXim?PdN(|@o(OdgH3AiHts~?#QkolO?*=U_buYC&tQ3sc(O5HGHN~=6wB@dgIAVT$ z_OJWJ^&*40Pw&%y^t8-Wn4@l9gOl`uU z{Uda_uk9!Iix?KBu9CYwW9Rs=yt_lE11A+k$+)pkY5pXpocxIEJe|pTxwFgB%Kpr&tH;PzgOQ&m|(#Otm?@H^r`v)9yiR8v&Uy>d#TNdRfyN4Jk;`g zp+jr5@L2A7TS4=G-#O<`A9o;{En5!I8lVUG?!PMsv~{E_yP%QqqTxxG%8%KxZ{uwS zOT+EA5`*moN8wwV`Z=wp<3?~f#frmID^K?t7YL`G^(X43gWbo!6(q*u%HxWh$$^2EOq`Hj zp=-fS#Av+s9r-M)wGIggQ)b<@-BR`R8l1G@2+KODmn<_$Tzb7k35?e8;!V0G>`(!~ zY~qZz!6*&|TupOcnvsQYPbcMiJ!J{RyfezB^;fceBk znpA1XS)~KcC%0^_;ihibczSxwBuy;^ksH7lwfq7*GU;TLt*WmUEVQxt{ zKSfJf;lk$0XO8~48Xn2dnh8tMC9WHu`%DZj&a`2!tNB`5%;Md zBs|#T0Ktf?vkWQ)Y+q!At1qgL`C|nbzvgc(+28Q|4N6Geq)Il%+I5c@t02{9^=QJ?=h2BTe`~BEu=_u3xX2&?^zwcQWL+)7dI>JK0g8_`W1n~ zMaEP97X>Ok#=G*nkPmY`VoP8_{~+Rp7DtdSyWxI~?TZHxJ&=6KffcO2Qx1?j7=LZA z?GQt`oD9QpXw+s7`t+eeLO$cpQpl9(6h3_l9a6OUpbwBasCeCw^UB6we!&h9Ik@1zvJ`j4i=tvG9X8o34+N|y(ay~ho$f=l z514~mP>Z>#6+UxM<6@4z*|hFJ?KnkQBs_9{H(-v!_#Vm6Z4(xV5WgWMd3mB9A(>@XE292#k(HdI7P zJkQ2)`bQXTKlr}{VrhSF5rK9TsjtGs0Rs&nUMcH@$ZX_`Hh$Uje*)(Wd&oLW($hZQ z_tPt`{O@f8hZ<}?aQc6~|9iHt>=!%We3=F9yIfiqhXqp=QUVa!@UY@IF5^dr5H8$R zIh{=%S{$BHG+>~a=vQ={!B9B=<-ID=nyjfA0V8->gN{jRL>Qc4Rc<86;~aY+R!~Vs zV7MI~gVzGIY`B*Tt@rZk#Lg}H8sL39OE31wr_Bm%mn}8n773R&N)8B;l+-eOD@N$l zh&~Wz`m1qavVdxwtZLACS(U{rAa0;}KzPq9r76xL?c{&GaG5hX_NK!?)iq`t7q*F# zFoKI{h{*8lb>&sOeHXoAiqm*vV6?C~5U%tXR8^XQ9Y|(XQvcz*>a?%HQ(Vy<2UhNf zVmGeOO#v159KV@1g`m%gJ)XGPLa`a|?9HSzSSX{j;)xg>G(Ncc7+C>AyAWYa(k}5B3mtzg4tsA=C^Wfezb1&LlyrBE1~kNfeiubLls{C)!<%#m@f}v^o+7<VZ6!FZ;JeiAG@5vw7Li{flC8q1%jD_WP2ApBI{fQ}kN zhvhmdZ0bb5(qK@VS5-)G+@GK(tuF6eJuuV5>)Odgmt?i_`tB69DWpC~e8gqh!>jr_ zL1~L0xw@CbMSTmQflpRyjif*Y*O-IVQ_OFhUw-zhPrXXW>6X}+73IoMsu2?uuK3lT>;W#38#qG5tDl66A7Y{mYh=jK8Se!+f=N7%nv zYSHr6a~Nxd`jqov9VgII{%EpC_jFCEc>>SND0;}*Ja8Kv;G)MK7?T~h((c&FEBcQq zvUU1hW2^TX(dDCeU@~a1LF-(+#lz3997A@pipD53&Dr@III2tlw>=!iGabjXzbyUJ z4Hi~M1KCT-5!NR#I%!2Q*A>mqI{dpmUa_mW)%SDs{Iw1LG}0y=wbj@0ba-`q=0!`5 zr(9q1p{#;Rv2CY!L#uTbs(UHVR5+hB@m*zEf4jNu3(Kj$WwW|v?YL*F_0x)GtQC~! zzrnZRmBmwt+i@uXnk05>uR5&1Ddsx1*WwMrIbPD3yU*2By`71pk@gt{|H0D<#B7&8 z2dVmXp*;B)SWY)U1VSNs4ds!yBAj;P=xtatUx^7_gC5tHsF#vvdV;NmKwmNa1GNWZ zi_Jn-B4GnJ%xcYWD5h$*z^haku#_Irh818x^KB)3-;ufjf)D0TE#6>|zFf@~pU;Rs zNw+}c9S+6aPzxkEA6R%s*xhJ37wmgc)-{Zd1&mD5QT}4BQvczWr-Xim>(P^)52`@R z9+Z}44203T5}`AM_G^Snp<_KKc!OrA(5h7{MT^$ZeDsSr(R@^kI?O;}QF)OU zQ9-`t^ys=6DzgLcWt0U{Q(FBs22=r zKD%fLQ^5ZF24c-Z)J{xv?x$&4VhO^mswyb4QTIofCvzq+27*WlYm;h@;Bq%i;{hZA zM97mHI6pP}XFo|^pRTuWQzQs3B-8kY@ajLV!Fb?OYAO3jFv*W-_;AXd;G!CbpZt04iW`Ie^_+cQZGY_Zd@P<*J9EdRsc>c=edf$K|;voXRJ zk*aC@@=MKwR120(%I_HX`3pJ+8GMeO>%30t?~uXT0O-Tu-S{JA;zHoSyXs?Z;fy58 zi>sFtI7hoxNAdOt#3#AWFDW)4EPr4kDYq^`s%JkuO7^efX+u#-qZ56aoRM!tC^P6O zP(cFuBnQGjhX(^LJ(^rVe4-_Vk*3PkBCj!?SsULdmVr0cGJM^=?8b0^DuOFq>0*yA zk1g|C7n%pMS0A8@Aintd$fvRbH?SNdRaFrfoAJ=NoX)G5Gr}3-$^IGF+eI&t{I-GT zp=1fj)2|*ur1Td)+s&w%p#E6tDXX3YYOC{HGHLiCvv?!%%3DO$B$>A}aC;8D0Ef#b z{7NNqC8j+%1n95zq8|hFY`afAB4E)w_&7?oqG0IPJZv)lr{MT}>9p?}Y`=n+^CZ6E zKkjIXPub5!82(B-O2xQojW^P(#Q*;ETpEr^+Wa=qDJ9_k=Wm@fZB6?b(u?LUzX(}+ zE6OyapdG$HC& z&;oa*ALoyIxVvB2cm_N&h&{3ZTuU|aBrJlGOLtZc3KDx)<{ z27@)~GtQF@%6B@w3emrGe?Cv_{iC@a#YO8~OyGRIvp@%RRKC?fclXMP*6GzBFO z5U4QK?~>AR>?KF@I;|(rx(rKxdT9-k-anYS+#S#e1SzKPslK!Z&r8iomPsWG#>`Ld zJ<#+8GFHE!^wsXt(s=CGfVz5K+FHYP5T0E*?0A-z*lNBf)${Y`>Gwc@?j5{Q|6;Bl zkHG1%r$r&O!N^><8AEL+=y(P$7E6hd=>BZ4ZZ9ukJ2*~HR4KGvUR~MUOe$d>E5UK3 z*~O2LK4AnED}4t1Fs$JgvPa*O+WeCji_cn1@Tv7XQ6l@($F1K%{E$!naeX)`bfCG> z8iD<%_M6aeD?a-(Qqu61&fzQqC(E8ksa%CulMnPvR35d{<`VsmaHyzF+B zF6a@1$CT0xGVjofcct4SyxA40uQ`b#9kI)& z?B67-12X-$v#Im4CVUGZHXvPWwuspJ610ITG*A4xMoRVXJl5xbk;OL(;}=+$9?H`b z>u2~yd~gFZ*V}-Q0K6E@p}mtsri&%Zep?ZrPJmv`Qo1>94Lo||Yl)nqwHXEbe)!g( zo`w|LU@H14VvmBjjkl~=(?b{w^G$~q_G(HL`>|aQR%}A64mv0xGHa`S8!*Wb*eB}` zZh)&rkjLK!Rqar)UH)fM<&h&@v*YyOr!Xk2OOMV%$S2mCRdJxKO1RL7xP_Assw)bb z9$sQ30bapFfYTS`i1PihJZYA#0AWNmp>x(;C!?}kZG7Aq?zp!B+gGyJ^FrXQ0E<>2 zCjqZ(wDs-$#pVYP3NGA=en<@_uz!FjFvn1&w1_Igvqs_sL>ExMbcGx4X5f%`Wrri@ z{&vDs)V!rd=pS?G(ricfwPSg(w<8P_6=Qj`qBC7_XNE}1_5>+GBjpURPmvTNE7)~r)Y>ZZecMS7Ro2` z0}nC_GYo3O7j|Wux?6-LFZs%1IV0H`f`l9or-8y0=5VGzjPqO2cd$RRHJIY06Cnh- ztg@Pn1OeY=W`1Mv3`Ti6!@QIT{qcC*&vptnX4Pt1O|dWv8u2s|(CkV`)vBjAC_U5` zCw1f&c4o;LbBSp0=*q z3Y^horBAnR)u=3t?!}e}14%K>^562K!)Vy6r~v({5{t#iRh8WIL|U9H6H97qX09xp zjb0IJ^9Lqxop<-P*VA0By@In*5dq8Pr3bTPu|ArID*4tWM7w+mjit0PgmwLV4&2PW z3MnIzbdR`3tPqtUICEuAH^MR$K_u8~-U2=N1)R=l>zhygus44>6V^6nJFbW-`^)f} zI&h$FK)Mo*x?2`0npTD~jRd}5G~-h8=wL#Y-G+a^C?d>OzsVl7BFAaM==(H zR;ARWa^C3J)`p~_&FRsxt|@e+M&!84`eq)@aO9yBj8iifJv0xVW4F&N-(#E=k`AwJ z3EFXWcpsRlB%l_0Vdu`0G(11F7( zsl~*@XP{jS@?M#ec~%Pr~h z2`M*lIQaolzWN&;hkR2*<=!ORL(>YUMxOzj(60rQfr#wTrkLO!t{h~qg% zv$R}0IqVIg1v|YRu9w7RN&Uh7z$ijV=3U_M(sa`ZF=SIg$uY|=NdC-@%HtkUSEqJv zg|c}mKTCM=Z8YmsFQu7k{VrXtL^!Cts-eb@*v0B3M#3A7JE*)MeW1cfFqz~^S6OXFOIP&iL;Vpy z4dWKsw_1Wn%Y;eW1YOfeP_r1s4*p1C(iDG_hrr~-I%kA>ErxnMWRYu{IcG{sAW;*t z9T|i4bI*g)FXPpKM@~!@a7LDVVGqF}C@mePD$ai|I>73B+9!Ks7W$pw;$W1B%-rb; zJ*-q&ljb=&41dJ^*A0)7>Wa@khGZ;q1fL(2qW=|38j43mTl_;`PEEw07VKY%71l6p z@F|jp88XEnm1p~<5c*cVXvKlj0{THF=n3sU7g>Ki&(ErR;!KSmfH=?49R5(|c_*xw z4$jhCJ1gWT6-g5EV)Ahg?Nw=}`iCyQ6@0DqUb%AZEM^C#?B-@Hmw?LhJ^^VU>&phJ zlB!n5&>I>@sndh~v$2I2Ue23F?0!0}+9H~jg7E`?CS_ERu75^jSwm%!FTAegT`6s7 z^$|%sj2?8wtPQR>@D3sA0-M-g-vL@47YCnxdvd|1mPymvk!j5W1jHnVB&F-0R5e-vs`@u8a5GKdv`LF7uCfKncI4+??Z4iG@AxuX7 z6+@nP^TZ5HX#*z(!y+-KJ3+Ku0M90BTY{SC^{ z&y2#RZPjfX_PE<<>XwGp;g4&wcXsQ0T&XTi(^f+}4qSFH1%^GYi+!rJo~t#ChTeAX zmR0w(iODzQOL+b&{1OqTh*psAb;wT*drr^LKdN?c?HJ*gJl+%kEH&48&S{s28P=%p z7*?(xFW_RYxJxxILS!kdLIJYu@p#mnQ(?moGD1)AxQd66X6b*KN?o&e`u9#N4wu8% z^Gw#G!@|>c740RXziOR=tdbkqf(v~wS_N^CS^1hN-N4{Dww1lvSWcBTX*&9}Cz|s@ z*{O@jZ4RVHq19(HC9xSBZI0M)E;daza+Q*zayrX~N5H4xJ33BD4gn5Ka^Hj{995z4 zzm#Eo?ntC$q1a?)dD$qaC_M{NW!5R!vVZ(XQqS67xR3KP?rA1^+s3M$60WRTVHeTH z6BJO$_jVx0EGPXy}XK_&x597 zt(o6ArN8vZX0?~(lFGHRtHP{gO0y^$iU6Xt2e&v&ugLxfsl;GD)nf~3R^ACqSFLQ< zV7`cXgry((wDMJB55a6D4J;13$z6pupC{-F+wpToW%k1qKjUS^$Mo zN3@}T!ZdpiV7rkNvqP3KbpEn|9aB;@V;gMS1iSb@ zwyD7!5mfj)q+4jE1dq3H`sEKgrVqk|y8{_vmn8bMOi873!rmnu5S=1=-DFx+Oj)Hi zx?~ToiJqOrvSou?RVALltvMADodC7BOg7pOyc4m&6yd(qIuV5?dYUpYzpTe!BuWKi zpTg(JHBYzO&X1e{5o|ZVU-X5e?<}mh=|eMY{ldm>V3NsOGwyxO2h)l#)rH@BI*TN; z`yW26bMSp=k6C4Ja{xB}s`dNp zE+41IwEwo>7*PA|7v-F#jLN>h#a`Er9_86!fwPl{6yWR|fh?c%qc44uP~Ocm2V*(* zICMpS*&aJjxutxKC0Tm8+FBz;3;R^=ajXQUB*nTN*Lb;mruQHUE<&=I7pZ@F-O*VMkJbI#FOrBM8`QEL5Uy=q5e2 z_BwVH%c0^uIWO0*_qD;0jlPoA@sI7BPwOr-mrp7y`|EF)j;$GYdOtEPFRAKyUuUZS z(N4)*6R*ux8s@pMdC*TP?Hx`Zh{{Ser;clg&}CXriXZCr2A!wIoh;j=_eq3_%n7V} za?{KhXg2cXPpKHc90t6=`>s@QF-DNcTJRvLTS)E2FTb+og(wTV7?$kI?QZYgVBn)& zdpJf@tZ{j>B;<MVHiPl_U&KlqBT)$ic+M0uUQWK|N1 zCMl~@o|}!!7yyT%7p#G4?T^Azxt=D(KP{tyx^lD_(q&|zNFgO%!i%7T`>mUuU^FeR zHP&uClWgXm6iXgI8*DEA!O&X#X(zdrNctF{T#pyax16EZ5Lt5Z=RtAja!x+0Z31U8 zjfaky?W)wzd+66$L>o`n;DISQNs09g{GAv%8q2k>2n8q)O^M}=5r#^WR^=se#WSCt zQ`7E1w4qdChz4r@v6hgR?nsaE7pg2B6~+i5 zcTTbBQ2ghUbC-PV(@xvIR(a>Kh?{%YAsMV#4gt1nxBF?$FZ2~nFLKMS!aK=(`WllA zHS<_7ugqKw!#0aUtQwd#A$8|kPN3Af?Tkn)dHF?_?r#X68Wj;|$aw)Wj2Dkw{6)*^ zZfy!TWwh=%g~ECDCy1s8tTgWCi}F1BvTJ9p3H6IFq&zn#3FjZoecA_L_bxGWgeQup zAAs~1IPCnI@H>g|6Lp^Bk)mjrA3_qD4(D(65}l=2RzF-8@h>|Aq!2K-qxt(Q9w7c^ z;gtx`I+=gKOl;h=#fzSgw-V*YT~2_nnSz|!9hIxFb{~dKB!{H zSi??dnmr@%(1w^Be=*Jz5bZeofEKKN&@@uHUMFr-DHS!pb1I&;x9*${bmg6=2I4Zt zHb5LSvojY7ubCNGhp)=95jQ00sMAC{IZdAFsN!lAVQDeiec^HAu=8);2AKqNTT!&E zo+FAR`!A1#T6w@0A+o%&*yzkvxsrqbrfVTG+@z8l4+mRi@j<&)U9n6L>uZoezW>qS zA4YfO;_9dQSyEYpkWnsk0IY}Nr2m(ql@KuQjLgY-@g z4=$uai6^)A5+~^TvLdvhgfd+y?@+tRE^AJabamheJFnpA#O*5_B%s=t8<;?I;qJ}j z&g-9?hbwWEez-!GIhqpB>nFvyi{>Yv>dPU=)qXnr;3v-cd`l}BV?6!v{|cHDOx@IG z;TSiQQ(8=vlH^rCEaZ@Yw}?4#a_Qvx=}BJuxACxm(E7tP4hki^jU@8A zUS|4tTLd)gr@T|F$1eQXPY%fXb7u}(>&9gsd3It^B{W#6F2_g40cgo1^)@-xO&R5X z>qKon+Nvp!4v?-rGQu#M_J2v+3e+?N-WbgPQWf`ZL{Xd9KO^s{uIHTJ6~@d=mc7i z+##ya1p+ZHELmi%3C>g5V#yZt*jMv( zc{m*Y;7v*sjVZ-3mBuaT{$g+^sbs8Rp7BU%Ypi+c%JxtC4O}|9pkF-p-}F{Z7-+45 zDaJQx&CNR)8x~0Yf&M|-1rw%KW3ScjWmKH%J1fBxUp(;F%E+w!U470e_3%+U_q7~P zJm9VSWmZ->K`NfswW(|~fGdMQ!K2z%k-XS?Bh`zrjZDyBMu74Fb4q^A=j6+Vg@{Wc zPRd5Vy*-RS4p1OE-&8f^Fo}^yDj$rb+^>``iDy%t)^pHSV=En5B5~*|32#VkH6S%9 zxgIbsG+|{-$v7mhOww#v-ejaS>u(9KV9_*X!AY#N*LXIxor9hDv%aie@+??X6@Et=xz>6ev9U>6Pn$g4^!}w2Z%Kpqpp+M%mk~?GE-jL&0xLC zy(`*|&gm#mLeoRU8IU?Ujsv=;ab*URmsCl+r?%xcS1BVF*rP}XRR%MO_C!a9J^fOe>U;Y&3aj3 zX`3?i12*^W_|D@VEYR;h&b^s#Kd;JMNbZ#*x8*ZXm(jgw3!jyeHo14Zq!@_Q`V;Dv zKik~!-&%xx`F|l^z2A92aCt4x*I|_oMH9oeqsQgQDgI0j2p!W@BOtCTK8Jp#txi}7 z9kz);EX-2~XmxF5kyAa@n_$YYP^Hd4UPQ>O0-U^-pw1*n{*kdX`Jhz6{!W=V8a$0S z9mYboj#o)!d$gs6vf8I$OVOdZu7L5%)Vo0NhN`SwrQFhP3y4iXe2uV@(G{N{yjNG( zKvcN{k@pXkxyB~9ucR(uPSZ7{~sC=lQtz&V(^A^HppuN!@B4 zS>B=kb14>M-sR>{`teApuHlca6YXs6&sRvRV;9G!XI08CHS~M$=%T~g5Xt~$exVk` zWP^*0h{W%`>K{BktGr@+?ZP}2t0&smjKEVw@3=!rSjw5$gzlx`{dEajg$A58m|Okx zG8@BTPODSk@iqLbS*6>FdVqk}KKHuAHb0UJNnPm!(XO{zg--&@#!niF4T!dGVdNif z3_&r^3+rfQuV^8}2U?bkI5Ng*;&G>(O4&M<86GNxZK{IgKNbRfpg>+32I>(h`T&uv zUN{PRP&onFj$tn1+Yh|0AF330en{b~R+#i9^QIbl9fBv>pN|k&IL2W~j7xbkPyTL^ z*TFONZUS2f33w3)fdzr?)Yg;(s|||=aWZV(nkDaACGSxNCF>XLJSZ=W@?$*` z#sUftY&KqTV+l@2AP5$P-k^N`Bme-xcWPS|5O~arUq~%(z8z87JFB|llS&h>a>Som zC34(_uDViE!H2jI3<@d+F)LYhY)hoW6)i=9u~lM*WH?hI(yA$X#ip}yYld3RAv#1+sBt<)V_9c4(SN9Fn#$}_F}A-}P>N+8io}I3mh!}> z*~*N}ZF4Zergb;`R_g49>ZtTCaEsCHiFb(V{9c@X0`YV2O^@c6~LXg2AE zhA=a~!ALnP6aO9XOC^X15(1T)3!1lNXBEVj5s*G|Wm4YBPV`EOhU&)tTI9-KoLI-U zFI@adu6{w$dvT(zu*#aW*4F=i=!7`P!?hZy(9iL;Z^De3?AW`-gYTPALhrZ*K2|3_ zfz;6xQN9?|;#_U=4t^uS2VkQ8$|?Ub5CgKOj#Ni5j|(zX>x#K(h7LgDP-QHwok~-I zOu9rn%y97qrtKdG=ep)4MKF=TY9^n6CugQ3#G2yx;{))hvlxZGE~rzZ$qEHy-8?pU#G;bwufgSN6?*BeA!7N3RZEh{xS>>-G1!C(e1^ zzd#;39~PE_wFX3Tv;zo>5cc=md{Q}(Rb?37{;YPtAUGZo7j*yHfGH|TOVR#4ACaM2 z;1R0hO(Gl}+0gm9Bo}e@lW)J2OU4nukOTVKshHy7u)tLH^9@QI-jAnDBp(|J8&{fKu=_97$v&F67Z zq+QsJ=gUx3_h_%=+q47msQ*Ub=gMzoSa@S2>`Y9Cj*@Op4plTc!jDhu51nSGI z^sfZ(4=yzlR}kP2rcHRzAY9@T7f`z>fdCU0zibx^gVg&fMkcl)-0bRyWe12bT0}<@ z^h(RgGqS|1y#M;mER;8!CVmX!j=rfNa6>#_^j{^C+SxGhbSJ_a0O|ae!ZxiQCN2qA zKs_Z#Zy|9BOw6x{0*APNm$6tYVG2F$K~JNZ!6>}gJ_NLRYhcIsxY1z~)mt#Yl0pvC zO8#Nod;iow5{B*rUn(0WnN_~~M4|guwfkT(xv;z)olmj=f=aH#Y|#f_*d1H!o( z!EXNxKxth9w1oRr0+1laQceWfgi8z`YS#uzg#s9-QlTT7y2O^^M1PZx z3YS7iegfp6Cs0-ixlG93(JW4wuE7)mfihw}G~Uue{Xb+#F!BkDWs#*cHX^%(We}3% zT%^;m&Juw{hLp^6eyM}J({luCL_$7iRFA6^8B!v|B9P{$42F>|M`4Z_yA{kK()WcM zu#xAZWG%QtiANfX?@+QQOtbU;Avr*_>Yu0C2>=u}zhH9VLp6M>fS&yp*-7}yo8ZWB z{h>ce@HgV?^HgwRThCYnHt{Py0MS=Ja{nIj5%z;0S@?nGQ`z`*EVs&WWNwbzlk`(t zxDSc)$dD+4G6N(p?K>iEKXIk>GlGKTH{08WvrehnHhh%tgpp&8db4*FLN zETA@<$V=I7S^_KxvYv$Em4S{gO>(J#(Wf;Y%(NeECoG3n+o;d~Bjme-4dldKukd`S zRVAnKxOGjWc;L#OL{*BDEA8T=zL8^`J=2N)d&E#?OMUqk&9j_`GX*A9?V-G zdA5QQ#(_Eb^+wDkDiZ6RXL`fck|rVy%)BVv;dvY#`msZ}{x5fmd! zInmWSxvRgXbJ{unxAi*7=Lt&7_e0B#8M5a=Ad0yX#0rvMacnKnXgh>4iiRq<&wit93n!&p zeq~-o37qf)L{KJo3!{l9l9AQb;&>)^-QO4RhG>j`rBlJ09~cbfNMR_~pJD1$UzcGp zOEGTzz01j$=-kLC+O$r8B|VzBotz}sj(rUGOa7PDYwX~9Tum^sW^xjjoncxSz;kqz z$Pz$Ze|sBCTjk7oM&`b5g2mFtuTx>xl{dj*U$L%y-xeQL~|i>KzdUHeep-Yd@}p&L*ig< zgg__3l9T=nbM3bw0Sq&Z2*FA)P~sx0h634BXz0AxV69cED7QGTbK3?P?MENkiy-mV zZ1xV5ry3zIpy>xmThBL0Q!g+Wz@#?6fYvzmEczs(rcujrfCN=^!iWQ6$EM zaCnRThqt~gI-&6v@KZ78unqgv9j6-%TOxpbV`tK{KaoBbhc}$h+rK)5h|bT6wY*t6st-4$e99+Egb#3ip+ERbve08G@Ref&hP)qB&?>B94?eq5i3k;dOuU#!y-@+&5>~!FZik=z4&4|YHy=~!F254 zQAOTZr26}Nc7jzgJ;V~+9ry#?7Z0o*;|Q)k+@a^87lC}}1C)S))f5tk+lMNqw>vh( z`A9E~5m#b9!ZDBltf7QIuMh+VheCoD7nCFhuzThlhA?|8NCt3w?oWW|NDin&&eDU6 zwH`aY=))lpWG?{fda=-auXYp1WIPu&3 zwK|t(Qiqvc@<;1_W#ALDJ}bR;3&v4$9rP)eAg`-~iCte`O^MY+SaP!w%~+{{1tMo` zbp?T%ENs|mHP)Lsxno=nWL&qizR+!Ib=9i%4=B@(Umf$|7!WVxkD%hfRjvxV`Co<; zG*g4QG_>;RE{3V_DOblu$GYm&!+}%>G*yO{-|V9GYG|bH2JIU2iO}ZvY>}Fl%1!OE zZFsirH^$G>BDIy`8;R?lZl|uu@qWj2T5}((RG``6*05AWsVVa2Iu>!F5U>~7_Tlv{ zt=Dpgm~0QVa5mxta+fUt)I0gToeEm9eJX{yYZ~3sLR&nCuyuFWuiDIVJ+-lwViO(E zH+@Rg$&GLueMR$*K8kOl>+aF84Hss5p+dZ8hbW$=bWNIk0paB!qEK$xIm5{*^ad&( zgtA&gb&6FwaaR2G&+L+Pp>t^LrG*-B&Hv;-s(h0QTuYWdnUObu8LRSZoAVd7SJ;%$ zh%V?58mD~3G2X<$H7I)@x?lmbeeSY7X~QiE`dfQ5&K^FB#9e!6!@d9vrSt!);@ZQZ zO#84N5yH$kjm9X4iY#f+U`FKhg=x*FiDoUeu1O5LcC2w&$~5hKB9ZnH+8BpbTGh5T zi_nfmyQY$vQh%ildbR7T;7TKPxSs#vhKR|uup`qi1PufMa(tNCjRbllakshQgn1)a8OO-j8W&aBc_#q1hKDF5-X$h`!CeT z+c#Ial~fDsGAenv7~f@!icm(~)a3OKi((=^zcOb^qH$#DVciGXslUwTd$gt{7)&#a`&Lp ze%AnL0#U?lAl8vUkv$n>bxH*`qOujO0HZkPWZnE0;}0DSEu1O!hg-d9#{&#B1Dm)L zvN%r^hdEt1vR<4zwshg*0_BNrDWjo65be1&_82SW8#iKWs7>TCjUT;-K~*NxpG2P% zovXUo@S|fMGudVSRQrP}J3-Wxq;4xIxJJC|Y#TQBr>pwfy*%=`EUNE*dr-Y?9y9xK zmh1zS@z{^|UL}v**LNYY!?1qIRPTvr!gNXzE{%=-`oKclPrfMKwn` zUwPeIvLcxkIV>(SZ-SeBo-yw~{p!<&_}eELG?wxp zee-V59%@BtB+Z&Xs=O(@P$}v_qy1m=+`!~r^aT> zY+l?+6(L-=P%m4ScfAYR8;f9dyVw)@(;v{|nO#lAPI1xDHXMYt~-BGiP&9y2OQsYdh7-Q1(vL<$u6W0nxVn-qh=nwuRk}{d!uACozccRGx6~xZQ;=#JCE?OuA@;4 zadp$sm}jfgW4?La(pb!3f0B=HUI{5A4b$2rsB|ZGb?3@CTA{|zBf07pYpQ$NM({C6Srv6%_{rVkCndT=1nS}qyEf}Wjtg$e{ng7Wgz$7itYy0sWW_$qld);iUm85GBH)fk3b=2|5mvflm?~inoVo zDH_%e;y`DzoNj|NgZ`U%a9(N*=~8!qqy0Etkxo#`r!!{|(NyT0;5= z8nVZ6AiM+SjMG8J@6c4_f-KXd_}{My?Se1GWP|@wROFpD^5_lu?I%CBzpwi(`x~xh B8dv}T delta 17845 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 X49Luk)baa85H9$5YCsTPT`SVRWMtMW diff --git a/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties b/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties index 05679dc..ffed3a2 100644 --- a/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties +++ b/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/bindings/bdk-kotlin/gradlew b/bindings/bdk-kotlin/gradlew index 4f906e0..1b6c787 100755 --- a/bindings/bdk-kotlin/gradlew +++ b/bindings/bdk-kotlin/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/bindings/bdk-kotlin/gradlew.bat b/bindings/bdk-kotlin/gradlew.bat index 107acd3..ac1b06f 100644 --- a/bindings/bdk-kotlin/gradlew.bat +++ b/bindings/bdk-kotlin/gradlew.bat @@ -1,89 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From e20a41a186296e75f8ecec720a83416ea4f4b662 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 21 Oct 2021 14:35:40 +0530 Subject: [PATCH 088/272] Allow generating extended keys --- src/bdk.udl | 17 ++++++++++++++++- src/lib.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/bdk.udl b/src/bdk.udl index c52b9ac..d183e43 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -1,5 +1,6 @@ namespace bdk { - + [Throws=BdkError] + ExtendedKeyInfo generate_extended_key(Network network, MnemonicType mnemonicType, string? password); }; [Error] @@ -146,3 +147,17 @@ interface PartiallySignedBitcoinTransaction { [Throws=BdkError] constructor([ByRef] OnlineWallet wallet, string recipient, u64 amount); }; + +dictionary ExtendedKeyInfo { + string mnemonic; + string xprv; + string fingerprint; +}; + +enum MnemonicType { + "Words12", + "Words15", + "Words18", + "Words21", + "Words24", +}; diff --git a/src/lib.rs b/src/lib.rs index 02c22b5..8d26c70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +use bdk::bitcoin::secp256k1::Secp256k1; use bdk::bitcoin::util::psbt::PartiallySignedTransaction; use bdk::bitcoin::{Address, Network}; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; @@ -7,6 +8,9 @@ use bdk::blockchain::{ }; use bdk::database::any::{AnyDatabase, SledDbConfiguration}; use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase}; +use bdk::keys::bip39::{Language, Mnemonic, MnemonicType}; +use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey}; +use bdk::miniscript::BareCtx; use bdk::wallet::AddressIndex; use bdk::{ConfirmationTime, Error, SignOptions, Wallet}; use std::convert::TryFrom; @@ -260,5 +264,29 @@ impl WalletHolder for OnlineWallet { impl OfflineWalletOperations for OnlineWallet {} +pub struct ExtendedKeyInfo { + mnemonic: String, + xprv: String, + fingerprint: String, +} + +fn generate_extended_key( + network: Network, + mnemonic_type: MnemonicType, + password: Option, +) -> Result { + let mnemonic: GeneratedKey<_, BareCtx> = + Mnemonic::generate((mnemonic_type, Language::English)).unwrap(); + let mnemonic = mnemonic.into_key(); + let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?; + let xprv = xkey.into_xprv(network).unwrap(); + let fingerprint = xprv.fingerprint(&Secp256k1::new()); + Ok(ExtendedKeyInfo { + mnemonic: mnemonic.to_string(), + xprv: xprv.to_string(), + fingerprint: fingerprint.to_string(), + }) +} + uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); uniffi::deps::static_assertions::assert_impl_all!(OnlineWallet: Sync, Send); From be24f18d843dd424e048a13f8556f874c877303b Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 21 Oct 2021 14:35:55 +0530 Subject: [PATCH 089/272] Show how to generate extended keys --- bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index 530e15c..115c67e 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -44,6 +44,8 @@ val unconfirmedFirstThenByTimestampDescending = @ExperimentalUnsignedTypes fun main(args: Array) { + println("Generating key...") + println("${generateExtendedKey(Network.TESTNET, MnemonicType.WORDS12, null)}") println("Configuring an in-memory wallet on electrum..") val descriptor = "wpkh(tprv8ZgxMBicQKsPeSitUfdxhsVaf4BXAASVAbHypn2jnPcjmQZvqZYkeqx7EHQTWvdubTSDa5ben7zHC7sUsx4d8tbTvWdUtHzR8uhHg2CW7MT/*)" val amount = 1000uL From 852f7a64680ce97ff0d6d6a940d6833378647e47 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 21 Oct 2021 14:40:26 +0530 Subject: [PATCH 090/272] Allow restoring extended keys from mnemonic --- src/bdk.udl | 2 ++ src/lib.rs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/bdk.udl b/src/bdk.udl index d183e43..e830577 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -1,6 +1,8 @@ namespace bdk { [Throws=BdkError] ExtendedKeyInfo generate_extended_key(Network network, MnemonicType mnemonicType, string? password); + [Throws=BdkError] + ExtendedKeyInfo restore_extended_key(Network network, string mnemonic, string? password); }; [Error] diff --git a/src/lib.rs b/src/lib.rs index 8d26c70..8364ecf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -288,5 +288,21 @@ fn generate_extended_key( }) } +fn restore_extended_key( + network: Network, + mnemonic: String, + password: Option, +) -> Result { + let mnemonic = Mnemonic::from_phrase(mnemonic.as_ref(), Language::English).unwrap(); + let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?; + let xprv = xkey.into_xprv(network).unwrap(); + let fingerprint = xprv.fingerprint(&Secp256k1::new()); + Ok(ExtendedKeyInfo { + mnemonic: mnemonic.to_string(), + xprv: xprv.to_string(), + fingerprint: fingerprint.to_string(), + }) +} + uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); uniffi::deps::static_assertions::assert_impl_all!(OnlineWallet: Sync, Send); From 3b9df0d1109f2329e89363ddd7d6e1e13598c385 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 21 Oct 2021 14:44:29 +0530 Subject: [PATCH 091/272] Ensure restoration of extended key from mnemonic --- bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index 115c67e..ef6d335 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -44,8 +44,15 @@ val unconfirmedFirstThenByTimestampDescending = @ExperimentalUnsignedTypes fun main(args: Array) { + val network = Network.TESTNET + val mnemonicType = MnemonicType.WORDS12 + val password: String? = null println("Generating key...") - println("${generateExtendedKey(Network.TESTNET, MnemonicType.WORDS12, null)}") + val extendedKey = generateExtendedKey(network, mnemonicType, password) + println("generated key: $extendedKey") + println("Attempting restore extended key...") + val restoredKey = restoreExtendedKey(network, extendedKey.mnemonic, password) + println("restored key: $restoredKey") println("Configuring an in-memory wallet on electrum..") val descriptor = "wpkh(tprv8ZgxMBicQKsPeSitUfdxhsVaf4BXAASVAbHypn2jnPcjmQZvqZYkeqx7EHQTWvdubTSDa5ben7zHC7sUsx4d8tbTvWdUtHzR8uhHg2CW7MT/*)" val amount = 1000uL @@ -55,7 +62,7 @@ fun main(args: Array) { BlockchainConfig.Electrum( ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u) ) - val wallet = OnlineWallet(descriptor, Network.TESTNET, db, client) + val wallet = OnlineWallet(descriptor, network, db, client) wallet.sync(LogProgress(), null) println("Initial wallet balance: ${wallet.getBalance()}") println("Please send $amount satoshis to address: ${wallet.getNewAddress()}") From 091f5df97ddc896e0e3662a197ee15300ca98f6f Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 21 Oct 2021 14:50:52 +0530 Subject: [PATCH 092/272] Add change descriptor to Wallet --- src/bdk.udl | 2 +- src/lib.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bdk.udl b/src/bdk.udl index e830577..23148c1 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -126,7 +126,7 @@ callback interface BdkProgress { interface OnlineWallet { [Throws=BdkError] - constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); + constructor(string descriptor, string? change_descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); // OfflineWalletOperations string get_new_address(); diff --git a/src/lib.rs b/src/lib.rs index 8364ecf..e19ab21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,6 +193,7 @@ impl PartiallySignedBitcoinTransaction { impl OnlineWallet { fn new( descriptor: String, + change_descriptor: Option, network: Network, database_config: DatabaseConfig, blockchain_config: BlockchainConfig, @@ -225,7 +226,7 @@ impl OnlineWallet { let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?; let wallet = Mutex::new(Wallet::new( &descriptor, - None, + change_descriptor.to_owned().as_ref(), network, database, blockchain, From d833ebadfc22a719a8f138c652f9b2ab94c7dcf3 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 21 Oct 2021 14:53:51 +0530 Subject: [PATCH 093/272] Update consumers of OnlineWallet::new with change descriptor --- bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt | 2 +- .../src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index ef6d335..941b6ea 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -62,7 +62,7 @@ fun main(args: Array) { BlockchainConfig.Electrum( ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u) ) - val wallet = OnlineWallet(descriptor, network, db, client) + val wallet = OnlineWallet(descriptor, null, network, db, client) wallet.sync(LogProgress(), null) println("Initial wallet balance: ${wallet.getBalance()}") println("Please send $amount satoshis to address: ${wallet.getNewAddress()}") diff --git a/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt b/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt index 8b946b4..0811c8a 100644 --- a/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt @@ -61,7 +61,7 @@ abstract class LibTest { 100u ) ) - val wallet = OnlineWallet(desc, Network.TESTNET, db, client) + val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) assertNotNull(wallet) val network = wallet.getNetwork() assertEquals(network, Network.TESTNET) @@ -87,7 +87,7 @@ abstract class LibTest { 100u ) ) - val wallet = OnlineWallet(desc, Network.TESTNET, db, client) + val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() assertTrue(balance > 0u) From 33f37ae5933b350178aa84482195bbd1a62c6e37 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 21 Oct 2021 23:26:34 +0530 Subject: [PATCH 094/272] Export source files with aar --- bindings/bdk-kotlin/android/build.gradle | 10 +++++----- bindings/bdk-kotlin/android/proguard-rules.pro | 2 ++ .../bitcoindevkit => uniffi}/bdk/AndroidLibTest.kt | 2 +- .../bdk-kotlin/android/src/main/AndroidManifest.xml | 2 +- build.sh | 3 +++ 5 files changed, 12 insertions(+), 7 deletions(-) rename bindings/bdk-kotlin/android/src/androidTest/kotlin/{org/bitcoindevkit => uniffi}/bdk/AndroidLibTest.kt (95%) diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index f06c838..ef034b4 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -33,7 +33,7 @@ afterEvaluate { from components.release // You can then customize attributes of the publication as shown below. - groupId = 'org.bitcoindevkit' + groupId = 'uniffi.bdk' artifactId = 'bdk' version = '0.0.1-SNAPSHOT' } @@ -42,7 +42,7 @@ afterEvaluate { // Applies the component for the debug build variant. from components.debug - groupId = 'org.bitcoindevkit' + groupId = 'uniffi.bdk' artifactId = 'bdk-debug' version = '0.0.1-SNAPSHOT' } @@ -51,9 +51,9 @@ afterEvaluate { } dependencies { - implementation(project(':jvm')) { - exclude group: 'net.java.dev.jna', module: 'jna' - } +// implementation(project(':jvm')) { +// exclude group: 'net.java.dev.jna', module: 'jna' +// } implementation 'net.java.dev.jna:jna:5.8.0@aar' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/bindings/bdk-kotlin/android/proguard-rules.pro b/bindings/bdk-kotlin/android/proguard-rules.pro index 172980c..e319ee0 100644 --- a/bindings/bdk-kotlin/android/proguard-rules.pro +++ b/bindings/bdk-kotlin/android/proguard-rules.pro @@ -23,4 +23,6 @@ # for JNA -dontwarn java.awt.* -keep class com.sun.jna.* { *; } +-keep class uniffi.bdk.* { *; } +-keepclassmembers class * extends uniffi.bdk.* { public *; } -keepclassmembers class * extends com.sun.jna.* { public *; } diff --git a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bindings/bdk-kotlin/android/src/androidTest/kotlin/uniffi/bdk/AndroidLibTest.kt similarity index 95% rename from bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt rename to bindings/bdk-kotlin/android/src/androidTest/kotlin/uniffi/bdk/AndroidLibTest.kt index 9e1eec0..b339681 100644 --- a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt +++ b/bindings/bdk-kotlin/android/src/androidTest/kotlin/uniffi/bdk/AndroidLibTest.kt @@ -1,4 +1,4 @@ -package org.bitcoindevkit.bdk +package uniffi.bdk import android.app.Application import android.content.Context.MODE_PRIVATE diff --git a/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml b/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml index c8fa4de..406335d 100644 --- a/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml +++ b/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="uniffi.bdk"> diff --git a/build.sh b/build.sh index d94e956..13302c7 100755 --- a/build.sh +++ b/build.sh @@ -82,6 +82,9 @@ build_android() { cp target/i686-linux-android/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 fi + # copy sources + cp -R bindings/bdk-kotlin/jvm/src/main/ bindings/bdk-kotlin/android/src/main/ + # bdk-kotlin aar (cd bindings/bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) } From 6fc53fdfe70926d3f36b91ec60a95b328cf6175e Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 21 Oct 2021 23:42:38 +0530 Subject: [PATCH 095/272] Use snake_case for identifier --- src/bdk.udl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bdk.udl b/src/bdk.udl index 23148c1..e3ab62e 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -1,6 +1,6 @@ namespace bdk { [Throws=BdkError] - ExtendedKeyInfo generate_extended_key(Network network, MnemonicType mnemonicType, string? password); + ExtendedKeyInfo generate_extended_key(Network network, MnemonicType mnemonic_type, string? password); [Throws=BdkError] ExtendedKeyInfo restore_extended_key(Network network, string mnemonic, string? password); }; From f04c1f7fa8c821a50e36210a99e892d96bcdbf92 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 28 Oct 2021 00:33:10 +0530 Subject: [PATCH 096/272] Remove callback interface --- src/bdk.udl | 6 +----- src/lib.rs | 16 ++++------------ 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/bdk.udl b/src/bdk.udl index e3ab62e..27c554d 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -120,10 +120,6 @@ interface BlockchainConfig { Esplora(EsploraConfig config); }; -callback interface BdkProgress { - void update(f32 progress, string? message); -}; - interface OnlineWallet { [Throws=BdkError] constructor(string descriptor, string? change_descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); @@ -140,7 +136,7 @@ interface OnlineWallet { // OnlineWalletInterface Network get_network(); [Throws=BdkError] - void sync(BdkProgress progress_update, u32? max_address_param); + void sync(u32? max_address_param); [Throws=BdkError] string broadcast([ByRef] PartiallySignedBitcoinTransaction psbt); }; diff --git a/src/lib.rs b/src/lib.rs index e19ab21..9faf5f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,13 +154,10 @@ pub trait BdkProgress: Send + Sync { fn update(&self, progress: f32, message: Option); } -struct BdkProgressHolder { - progress_update: Box, -} +struct BdkProgressHolder {} impl Progress for BdkProgressHolder { - fn update(&self, progress: f32, message: Option) -> Result<(), Error> { - self.progress_update.update(progress, message); + fn update(&self, _progress: f32, _message: Option) -> Result<(), Error> { Ok(()) } } @@ -238,16 +235,11 @@ impl OnlineWallet { self.wallet.lock().unwrap().network() } - fn sync( - &self, - progress_update: Box, - max_address_param: Option, - ) -> Result<(), BdkError> { - progress_update.update(21.0, Some("message".to_string())); + fn sync(&self, max_address_param: Option) -> Result<(), BdkError> { self.wallet .lock() .unwrap() - .sync(BdkProgressHolder { progress_update }, max_address_param) + .sync(BdkProgressHolder {}, max_address_param) } fn broadcast<'a>(&self, psbt: &'a PartiallySignedBitcoinTransaction) -> Result { From 8406ff04b797e76339b52c91bec6697d815b7df6 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 28 Oct 2021 00:35:09 +0530 Subject: [PATCH 097/272] Copy kotlin libs only when building kotlin --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 13302c7..d0a9f28 100755 --- a/build.sh +++ b/build.sh @@ -44,6 +44,7 @@ copy_lib_kotlin() { ## bdk-bdk-kotlin jar build_kotlin() { + copy_lib_kotlin uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-kotlin/jvm/src/main/kotlin --language kotlin } @@ -96,7 +97,6 @@ then help else build_rust - copy_lib_kotlin while [ -n "$1" ]; do # while loop starts case "$1" in From a946a0cb4496b1ca5f928b915c97b5f4c61d6d51 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 28 Oct 2021 00:35:22 +0530 Subject: [PATCH 098/272] Generate bindings for swift --- Cargo.toml | 2 +- build.sh | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 954e596..b53c75f 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 = ["cdylib"] +crate-type = ["staticlib", "cdylib"] [dependencies] bdk = { version = "^0.12.1-dev", features = ["all-keys", "use-esplora-ureq"] } diff --git a/build.sh b/build.sh index d0a9f28..4adf44c 100755 --- a/build.sh +++ b/build.sh @@ -48,6 +48,41 @@ build_kotlin() { uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-kotlin/jvm/src/main/kotlin --language kotlin } +## bdk swift +build_swift() { + uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-swift/ --language swift + TARGETDIR=target + RELDIR=debug + STATIC_LIB_NAME=libuniffi_bdk.a + + # We can't use cargo lipo because we can't link to universal libraries :( + # https://github.com/rust-lang/rust/issues/55235 + LIBS_ARCHS=("x86_64" "arm64") + IOS_TRIPLES=("x86_64-apple-ios" "aarch64-apple-ios") + for i in "${!LIBS_ARCHS[@]}"; do + cargo build --target "${IOS_TRIPLES[${i}]}" + done + + UNIVERSAL_BINARY=./${TARGETDIR}/ios/universal/${RELDIR}/${STATIC_LIB_NAME} + NEED_LIPO= + + # if the universal binary doesnt exist, or if it's older than the static libs, + # we need to run `lipo` again. + if [[ ! -f "${UNIVERSAL_BINARY}" ]]; then + NEED_LIPO=1 + elif [[ "$(stat -f "%m" "./${TARGETDIR}/x86_64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}")" -gt "$(stat -f "%m" "${UNIVERSAL_BINARY}")" ]]; then + NEED_LIPO=1 + elif [[ "$(stat -f "%m" "./${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}")" -gt "$(stat -f "%m" "${UNIVERSAL_BINARY}")" ]]; then + NEED_LIPO=1 + fi + if [[ "${NEED_LIPO}" = "1" ]]; then + mkdir -p "${TARGETDIR}/ios/universal/${RELDIR}" + lipo -create -output "${UNIVERSAL_BINARY}" \ + "${TARGETDIR}/x86_64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}" \ + "${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}" + fi +} + ## rust android build_android() { build_kotlin @@ -102,6 +137,7 @@ else case "$1" in -a) build_android ;; -k) build_kotlin ;; + -s) build_swift ;; -h) help ;; *) echo "Option $1 not recognized" ;; esac From f5be0fae3dfd257d3f83d156a67e08df0090c0df Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 28 Oct 2021 00:37:42 +0530 Subject: [PATCH 099/272] Add swiftmodule --- build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sh b/build.sh index 4adf44c..3a0d37c 100755 --- a/build.sh +++ b/build.sh @@ -51,6 +51,7 @@ build_kotlin() { ## bdk swift build_swift() { uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-swift/ --language swift + swiftc -module-name bdk -emit-library -o libuniffi_bdk.dylib -emit-module -emit-module-path ./bindings/bdk-swift/ -parse-as-library -L ./target/debug/ -luniffi_bdk -Xcc -fmodule-map-file=./bindings/bdk-swift/bdkFFI.modulemap ./bindings/bdk-swift/bdk.swift TARGETDIR=target RELDIR=debug STATIC_LIB_NAME=libuniffi_bdk.a From 95074b4834f6415ad9198f4ab948d707a8bf1aba Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 28 Oct 2021 01:51:16 +0530 Subject: [PATCH 100/272] Revert "Remove callback interface" This reverts commit f04c1f7fa8c821a50e36210a99e892d96bcdbf92. --- src/bdk.udl | 6 +++++- src/lib.rs | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/bdk.udl b/src/bdk.udl index 27c554d..e3ab62e 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -120,6 +120,10 @@ interface BlockchainConfig { Esplora(EsploraConfig config); }; +callback interface BdkProgress { + void update(f32 progress, string? message); +}; + interface OnlineWallet { [Throws=BdkError] constructor(string descriptor, string? change_descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); @@ -136,7 +140,7 @@ interface OnlineWallet { // OnlineWalletInterface Network get_network(); [Throws=BdkError] - void sync(u32? max_address_param); + void sync(BdkProgress progress_update, u32? max_address_param); [Throws=BdkError] string broadcast([ByRef] PartiallySignedBitcoinTransaction psbt); }; diff --git a/src/lib.rs b/src/lib.rs index 9faf5f7..e19ab21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,10 +154,13 @@ pub trait BdkProgress: Send + Sync { fn update(&self, progress: f32, message: Option); } -struct BdkProgressHolder {} +struct BdkProgressHolder { + progress_update: Box, +} impl Progress for BdkProgressHolder { - fn update(&self, _progress: f32, _message: Option) -> Result<(), Error> { + fn update(&self, progress: f32, message: Option) -> Result<(), Error> { + self.progress_update.update(progress, message); Ok(()) } } @@ -235,11 +238,16 @@ impl OnlineWallet { self.wallet.lock().unwrap().network() } - fn sync(&self, max_address_param: Option) -> Result<(), BdkError> { + fn sync( + &self, + progress_update: Box, + max_address_param: Option, + ) -> Result<(), BdkError> { + progress_update.update(21.0, Some("message".to_string())); self.wallet .lock() .unwrap() - .sync(BdkProgressHolder {}, max_address_param) + .sync(BdkProgressHolder { progress_update }, max_address_param) } fn broadcast<'a>(&self, psbt: &'a PartiallySignedBitcoinTransaction) -> Result { From 28d13b57d6237277e9e10065e5fd9da45c3203d7 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 28 Oct 2021 23:00:23 +0530 Subject: [PATCH 101/272] Update uniffi to 0.14.1 which supports callback interface in Swift --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b53c75f..b8512b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,12 @@ crate-type = ["staticlib", "cdylib"] [dependencies] bdk = { version = "^0.12.1-dev", features = ["all-keys", "use-esplora-ureq"] } -uniffi_macros = "0.14.0" -uniffi = "0.14.0" +uniffi_macros = "0.14.1" +uniffi = "0.14.1" thiserror = "1.0" [build-dependencies] -uniffi_build = "0.14.0" +uniffi_build = "0.14.1" [patch.crates-io] bdk = { git = "https://github.com/artfuldev/bdk.git", branch = "use-send-and-sync-on-memory-database" } From fec67b7622208774ccb169acaeb55ed6aef39c2a Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 28 Oct 2021 14:21:57 -0700 Subject: [PATCH 102/272] Update bdk dependency to 0.13.0 --- Cargo.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b8512b1..1f15400 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,10 @@ edition = "2018" crate-type = ["staticlib", "cdylib"] [dependencies] -bdk = { version = "^0.12.1-dev", features = ["all-keys", "use-esplora-ureq"] } +bdk = { version = "0.13", features = ["all-keys", "use-esplora-ureq"] } uniffi_macros = "0.14.1" uniffi = "0.14.1" thiserror = "1.0" [build-dependencies] uniffi_build = "0.14.1" - -[patch.crates-io] -bdk = { git = "https://github.com/artfuldev/bdk.git", branch = "use-send-and-sync-on-memory-database" } From 2fc37eef61e0cc1cabe7197253644947f05569f8 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 28 Oct 2021 14:22:45 -0700 Subject: [PATCH 103/272] Fix build.sh and test.sh help --- build.sh | 4 +++- test.sh | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 3a0d37c..0eb41ba 100755 --- a/build.sh +++ b/build.sh @@ -9,10 +9,12 @@ help() # Display Help echo "Build bdk-uniffi and related libraries." echo - echo "Syntax: build [-h|k]" + echo "Syntax: build [-a|h|k|s]" echo "options:" + echo "-a Android." echo "-h Print this Help." echo "-k Kotlin." + echo "-s Swift." echo } diff --git a/test.sh b/test.sh index e552176..288947c 100755 --- a/test.sh +++ b/test.sh @@ -9,7 +9,7 @@ help() # Display Help echo "Test bdk-uniffi and related libraries." echo - echo "Syntax: build [-a|h|k|v]" + echo "Syntax: build [-a|h|k]" echo "options:" echo "-a Android connected device tests." echo "-h Print this Help." From cafc15197ce0196c16cce2fd3e8c5ecbd31f86b0 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 28 Oct 2021 15:34:17 -0700 Subject: [PATCH 104/272] Update README android setup --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index f9fd29c..ae9b527 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,7 @@ rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android ``` - 2. Set ANDROID_NDK_HOME - - ```sh - export ANDROID_NDK_HOME=/home//Android/Sdk/ndk/ - ``` + 2. Install Android SDK and Build-Tools for API level 30+ ## Setup Swift build environment From fea6a3d1ee2a62b2296b1706f0ba8da7096f05ce Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 30 Oct 2021 00:27:02 +0530 Subject: [PATCH 105/272] Add sections and more info --- README.md | 106 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index ae9b527..4542ab4 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,76 @@ -# BDK UniFFI Language Bindings +# Foreign language bindings for BDK (bdk-ffi) -## Setup Android build environment +This repository contains source code for generating foreign language bindings +for the rust library bdk for the Bitcoin Dev Kit (BDK) project. - 1. Add Android rust targets +## Supported target languages and platforms - ```sh - rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android - ``` +| Language | Platform | Status | +| --- | --- | --- | +| Kotlin | JVM | WIP | +| Kotlin | Android | WIP | +| Swift | iOS | WIP | - 2. Install Android SDK and Build-Tools for API level 30+ +## Getting Started -## Setup Swift build environment +This project uses rust. A basic knowledge of the rust ecosystem is helpful. - 1. install Swift, see ["Download Swift"](https://swift.org/download/) page - (or on Mac OSX install the latest Xcode) +### General +1. Install `uniffi-bindgen` + ```sh + cargo install uniffi_bindgen + ``` +1. See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) for more info -## Setup UniFFI +### Kotlin Bindings for JVM (OSX / Linux) - 1. `cargo install uniffi_bindgen` +1. Install required targets + ```sh + rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu + ``` +1. Build kotlin (JVM) bindings + ```sh + ./build.sh -k + ``` +1. Generated kotlin bindings are available at `/bindings/bdk-kotlin/` +1. A demo app is available at `/bindings/bdk-kotlin/demo/`. It uses stdin for +inputs and can be run from gradle. + ```sh + cd bindings/bdk-kotlin + ./gradlew :demo:run + ``` + +### Kotlin bindings for Android + +1. Install required targets + ```sh + rustup target add x86_64-linux-android aarch64-linux-android + armv7-linux-androideabi i686-linux-android + ``` +1. Install Android SDK and Build-Tools for API level 30+ +1. Setup `$ANDROID_NDK_HOME` and `$ANDROID_SDK_ROOT` path variables (which are +required by the build scripts) +1. Build kotlin (Android) bindings + ```sh + ./build.sh -a + ``` +2. A demo android app is available at [notmandatory/bdk-sample-app](https://github.com/notmandatory/bitcoindevkit-android-sample-app/tree/upgrade-to-bdk-ffi/) + +## Swift bindings for iOS + +1. Install the latest version of xcode, download and install the advanced tools. +1. Ensure Swift is installed +1. Install required targets + ```sh + rustup target add aarch64-apple-ios x86_64-apple-ios + ``` +1. Build swift (iOS) bindings + ```sh + ./build.sh -s + ``` +1. Example iOS app can be found in `/examples/iOS` which can be run by xcode. + +## Notes ## Adding new structs and functions @@ -25,27 +78,22 @@ See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) ### For pass by value objects - 1. create new rust struct with only fields that are supported UniFFI types - 2. update mapping `bdk.udl` file with new `dictionary` +1. create new rust struct with only fields that are supported UniFFI types +1. update mapping `bdk.udl` file with new `dictionary` ### For pass by reference values - 1. create wrapper rust struct/impl with only fields that are `Sync + Send` - 2. update mapping `bdk.udl` file with new `interface` +1. create wrapper rust struct/impl with only fields that are `Sync + Send` +1. update mapping `bdk.udl` file with new `interface` -### Build and test +## Goals - 1. Use `build.sh` script (TODO do it all in build.rs instead) - 2. Create tests in `bindings/bdk-kotlin` and/or `bindings/bdk-swift` - 3. Use `test.sh` to run all bindings tests +1. Language bindings should feel idiomatic in target languages/platforms +1. Adding new targets should be easy +1. Getting up and running should be easy +1. Contributing should be easy +1. Get it right, then automate -### Run kotlin demo application - -We have a kotlin demo console application which uses bdk. -It uses stdin for inputs and can be run from gradle. - -```sh -cd bindings/bdk-kotlin -./gradlew :demo:run -``` +## Thanks +This project is made possible thanks to the wonderful work on [mozilla/uniffi-rs](https://github.com/mozilla/uniffi-rs) From 99affffd320c66904cca44d7e989f2ad3bd0a1b9 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 30 Oct 2021 00:28:26 +0530 Subject: [PATCH 106/272] Fix heading levels --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4542ab4..72efea4 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ required by the build scripts) ``` 2. A demo android app is available at [notmandatory/bdk-sample-app](https://github.com/notmandatory/bitcoindevkit-android-sample-app/tree/upgrade-to-bdk-ffi/) -## Swift bindings for iOS +### Swift bindings for iOS 1. Install the latest version of xcode, download and install the advanced tools. 1. Ensure Swift is installed @@ -72,16 +72,16 @@ required by the build scripts) ## Notes -## Adding new structs and functions +### Adding new structs and functions See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) -### For pass by value objects +#### For pass by value objects 1. create new rust struct with only fields that are supported UniFFI types 1. update mapping `bdk.udl` file with new `dictionary` -### For pass by reference values +#### For pass by reference values 1. create wrapper rust struct/impl with only fields that are `Sync + Send` 1. update mapping `bdk.udl` file with new `interface` From a8f69fbd2bbf9cc8d196430f4951b6725e3dc075 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 1 Nov 2021 19:07:27 -0700 Subject: [PATCH 107/272] Rename kotlin package to org.bitcoindevkit, rust lib to bdkffi --- Cargo.toml | 3 ++- bindings/bdk-kotlin/android/build.gradle | 4 ++-- .../kotlin/uniffi/bdk/AndroidLibTest.kt | 2 +- bindings/bdk-kotlin/demo/build.gradle | 2 +- .../bdk-kotlin/demo/src/main/kotlin/Main.kt | 2 +- .../org/bitcoindevkit/{bdk => }/JvmLibTest.kt | 2 +- .../org/bitcoindevkit/{bdk => }/LibTest.kt | 4 ++-- build.sh | 18 +++++++++--------- uniffi.toml | 12 ++++++++++++ 9 files changed, 31 insertions(+), 18 deletions(-) rename bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/{bdk => }/JvmLibTest.kt (91%) rename bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/{bdk => }/LibTest.kt (98%) create mode 100644 uniffi.toml diff --git a/Cargo.toml b/Cargo.toml index 1f15400..4d799ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "uniffi_bdk" +name = "bdk-ffi" version = "0.1.0" authors = ["Steve Myers ", "Sudarsan Balaji "] edition = "2018" @@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["staticlib", "cdylib"] +name = "bdkffi" [dependencies] bdk = { version = "0.13", features = ["all-keys", "use-esplora-ureq"] } diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index ef034b4..06b71ac 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -33,7 +33,7 @@ afterEvaluate { from components.release // You can then customize attributes of the publication as shown below. - groupId = 'uniffi.bdk' + groupId = 'org.bitcoindevkit' artifactId = 'bdk' version = '0.0.1-SNAPSHOT' } @@ -42,7 +42,7 @@ afterEvaluate { // Applies the component for the debug build variant. from components.debug - groupId = 'uniffi.bdk' + groupId = 'org.bitcoindevkit' artifactId = 'bdk-debug' version = '0.0.1-SNAPSHOT' } diff --git a/bindings/bdk-kotlin/android/src/androidTest/kotlin/uniffi/bdk/AndroidLibTest.kt b/bindings/bdk-kotlin/android/src/androidTest/kotlin/uniffi/bdk/AndroidLibTest.kt index b339681..323626e 100644 --- a/bindings/bdk-kotlin/android/src/androidTest/kotlin/uniffi/bdk/AndroidLibTest.kt +++ b/bindings/bdk-kotlin/android/src/androidTest/kotlin/uniffi/bdk/AndroidLibTest.kt @@ -1,4 +1,4 @@ -package uniffi.bdk +package org.bitcoindevkit import android.app.Application import android.content.Context.MODE_PRIVATE diff --git a/bindings/bdk-kotlin/demo/build.gradle b/bindings/bdk-kotlin/demo/build.gradle index b320d2f..1fcea5c 100644 --- a/bindings/bdk-kotlin/demo/build.gradle +++ b/bindings/bdk-kotlin/demo/build.gradle @@ -3,7 +3,7 @@ plugins { id 'application' } -group = 'uniffi.bdk' +group = 'org.bitcoindevkit.bdk' version = '1.0-SNAPSHOT' repositories { diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index 941b6ea..a02f843 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -1,6 +1,6 @@ import java.util.Optional import kotlin.ExperimentalUnsignedTypes -import uniffi.bdk.* +import org.bitcoindevkit.* class LogProgress : BdkProgress { override fun update(progress: Float, message: String?) { diff --git a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt similarity index 91% rename from bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt rename to bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index ceea526..c017092 100644 --- a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt +++ b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -1,4 +1,4 @@ -package org.bitcoindevkit.bdk +package org.bitcoindevkit import java.nio.file.Files diff --git a/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt b/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/LibTest.kt similarity index 98% rename from bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt rename to bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/LibTest.kt index 0811c8a..f6bf1d9 100644 --- a/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/LibTest.kt @@ -1,10 +1,10 @@ -package org.bitcoindevkit.bdk +package org.bitcoindevkit import org.junit.Assert.* import org.junit.Test import org.slf4j.Logger import org.slf4j.LoggerFactory -import uniffi.bdk.* +import org.bitcoindevkit.* import java.io.File /** diff --git a/build.sh b/build.sh index 0eb41ba..89b786d 100755 --- a/build.sh +++ b/build.sh @@ -7,7 +7,7 @@ set -eo pipefail help() { # Display Help - echo "Build bdk-uniffi and related libraries." + echo "Build bdk-ffi and related libraries." echo echo "Syntax: build [-a|h|k|s]" echo "options:" @@ -33,12 +33,12 @@ copy_lib_kotlin() { "Darwin") echo -n "darwin " mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 - cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + cp target/debug/libbdkffi.dylib bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 ;; "Linux") echo -n "linux " mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 - cp target/debug/libuniffi_bdk.so bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 + cp target/debug/libbdkffi.so bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 ;; esac echo "libs to kotlin sub-project" @@ -53,10 +53,10 @@ build_kotlin() { ## bdk swift build_swift() { uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-swift/ --language swift - swiftc -module-name bdk -emit-library -o libuniffi_bdk.dylib -emit-module -emit-module-path ./bindings/bdk-swift/ -parse-as-library -L ./target/debug/ -luniffi_bdk -Xcc -fmodule-map-file=./bindings/bdk-swift/bdkFFI.modulemap ./bindings/bdk-swift/bdk.swift + swiftc -module-name bdk -emit-library -o libbdkffi.dylib -emit-module -emit-module-path ./bindings/bdk-swift/ -parse-as-library -L ./target/debug/ -lbdkffi -Xcc -fmodule-map-file=./bindings/bdk-swift/bdkFFI.modulemap ./bindings/bdk-swift/bdk.swift TARGETDIR=target RELDIR=debug - STATIC_LIB_NAME=libuniffi_bdk.a + STATIC_LIB_NAME=libbdkffi.a # We can't use cargo lipo because we can't link to universal libraries :( # https://github.com/rust-lang/rust/issues/55235 @@ -106,19 +106,19 @@ build_android() { if echo $BUILD_TARGETS | grep "aarch64"; then CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android - cp target/aarch64-linux-android/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a + cp target/aarch64-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a fi if echo $BUILD_TARGETS | grep "x86_64"; then CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android - cp target/x86_64-linux-android/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 + cp target/x86_64-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 fi if echo $BUILD_TARGETS | grep "armv7"; then CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi - cp target/armv7-linux-androideabi/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/armeabi-v7a + cp target/armv7-linux-androideabi/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/armeabi-v7a fi if echo $BUILD_TARGETS | grep "i686"; then CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android - cp target/i686-linux-android/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 + cp target/i686-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 fi # copy sources diff --git a/uniffi.toml b/uniffi.toml new file mode 100644 index 0000000..767e032 --- /dev/null +++ b/uniffi.toml @@ -0,0 +1,12 @@ +[bindings.kotlin] +package_name = "org.bitcoindevkit" +cdylib_name = "bdkffi" + +[bindings.python] +cdylib_name = "bdkffi" + +[bindings.ruby] +cdylib_name = "bdkffi" + +[bindings.swift] +cdylib_name = "bdkffi" From ae2294c83b0d712e9a7cd1d0146873d3e8052677 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 1 Nov 2021 19:36:52 -0700 Subject: [PATCH 108/272] Delete generated swift files, update lib name --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fbdc479..b520e20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ target build Cargo.lock -/bdk-kotlin/local.properties +/bindings/bdk-kotlin/local.properties +/bindings/bdk-swift .gradle wallet_db bdk_ffi_test From b6835364b384565ff8c3824dbc3a419e74f4bf97 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 1 Nov 2021 20:08:07 -0700 Subject: [PATCH 109/272] Fix androidTest package and proguard and manifest --- bindings/bdk-kotlin/android/proguard-rules.pro | 4 ++-- .../{uniffi/bdk => org/bitcoindevkit}/AndroidLibTest.kt | 0 bindings/bdk-kotlin/android/src/main/AndroidManifest.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename bindings/bdk-kotlin/android/src/androidTest/kotlin/{uniffi/bdk => org/bitcoindevkit}/AndroidLibTest.kt (100%) diff --git a/bindings/bdk-kotlin/android/proguard-rules.pro b/bindings/bdk-kotlin/android/proguard-rules.pro index e319ee0..264284c 100644 --- a/bindings/bdk-kotlin/android/proguard-rules.pro +++ b/bindings/bdk-kotlin/android/proguard-rules.pro @@ -23,6 +23,6 @@ # for JNA -dontwarn java.awt.* -keep class com.sun.jna.* { *; } --keep class uniffi.bdk.* { *; } --keepclassmembers class * extends uniffi.bdk.* { public *; } +-keep class org.bitcoindevkit.* { *; } +-keepclassmembers class * extends org.bitcoindevkit.* { public *; } -keepclassmembers class * extends com.sun.jna.* { public *; } diff --git a/bindings/bdk-kotlin/android/src/androidTest/kotlin/uniffi/bdk/AndroidLibTest.kt b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt similarity index 100% rename from bindings/bdk-kotlin/android/src/androidTest/kotlin/uniffi/bdk/AndroidLibTest.kt rename to bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt diff --git a/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml b/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml index 406335d..6aa0e82 100644 --- a/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml +++ b/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="org.bitcoindevkit"> From 12628a62d8ed7659928f2851a478d72da706a35e Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Tue, 2 Nov 2021 18:43:05 +0530 Subject: [PATCH 110/272] Ignore unnecessary files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b520e20..9356b7a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ build Cargo.lock /bindings/bdk-kotlin/local.properties /bindings/bdk-swift +/bindings/bdk-swift.swiftdoc +/bindings/bdk-swift.swiftsourceinfo +examples/ios/IOSBdkAppSample.xcodeproj/project.xcworkspace/xcuserdata/ .gradle wallet_db bdk_ffi_test From 862029a0ba8217e2a17dc6a47d8efc2760a7104d Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Tue, 2 Nov 2021 21:48:29 +0530 Subject: [PATCH 111/272] Ignore xc user data --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9356b7a..85429e1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ Cargo.lock /bindings/bdk-swift /bindings/bdk-swift.swiftdoc /bindings/bdk-swift.swiftsourceinfo -examples/ios/IOSBdkAppSample.xcodeproj/project.xcworkspace/xcuserdata/ .gradle wallet_db bdk_ffi_test @@ -15,3 +14,4 @@ local.properties *.so .DS_Store testdb +xcuserdata From 076de31dccef33d61a45e6eb63ab3f7a847d7489 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 4 Nov 2021 22:44:38 +0530 Subject: [PATCH 112/272] Add a way to get last unused address --- src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e19ab21..f1892f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,14 @@ trait OfflineWalletOperations: WalletHolder { .to_string() } + fn get_last_unused_address(&self) -> String { + self.get_wallet() + .get_address(AddressIndex::LastUnused) + .unwrap() + .address + .to_string() + } + fn get_balance(&self) -> Result { self.get_wallet().get_balance() } From 1888c2e2a1401893d306b2c02e905e007810a3a2 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 4 Nov 2021 22:45:00 +0530 Subject: [PATCH 113/272] Expose Wallet::getLastUnusedAddress --- src/bdk.udl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bdk.udl b/src/bdk.udl index e3ab62e..c5cd9a2 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -90,6 +90,7 @@ interface OfflineWallet { // OfflineWalletOperations string get_new_address(); + string get_last_unused_address(); [Throws=BdkError] u64 get_balance(); [Throws=BdkError] @@ -130,6 +131,7 @@ interface OnlineWallet { // OfflineWalletOperations string get_new_address(); + string get_last_unused_address(); [Throws=BdkError] u64 get_balance(); [Throws=BdkError] From ba12b632b4e9086f781f5905ce20653ad048c15c Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Thu, 4 Nov 2021 23:29:22 +0530 Subject: [PATCH 114/272] Add optional fee rate to a transaction --- src/bdk.udl | 2 +- src/lib.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/bdk.udl b/src/bdk.udl index c5cd9a2..dc2a996 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -149,7 +149,7 @@ interface OnlineWallet { interface PartiallySignedBitcoinTransaction { [Throws=BdkError] - constructor([ByRef] OnlineWallet wallet, string recipient, u64 amount); + constructor([ByRef] OnlineWallet wallet, string recipient, u64 amount, float? fee_rate); }; dictionary ExtendedKeyInfo { diff --git a/src/lib.rs b/src/lib.rs index f1892f2..1510fee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ use bdk::keys::bip39::{Language, Mnemonic, MnemonicType}; use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey}; use bdk::miniscript::BareCtx; use bdk::wallet::AddressIndex; -use bdk::{ConfirmationTime, Error, SignOptions, Wallet}; +use bdk::{ConfirmationTime, Error, FeeRate, SignOptions, Wallet}; use std::convert::TryFrom; use std::str::FromStr; use std::sync::{Mutex, MutexGuard}; @@ -178,13 +178,21 @@ struct PartiallySignedBitcoinTransaction { } impl PartiallySignedBitcoinTransaction { - fn new(online_wallet: &OnlineWallet, recipient: String, amount: u64) -> Result { + fn new( + online_wallet: &OnlineWallet, + recipient: String, + amount: u64, + fee_rate: Option, // satoshis per vbyte + ) -> Result { let wallet = online_wallet.get_wallet(); match Address::from_str(&recipient) { Ok(address) => { let (psbt, _) = { let mut builder = wallet.build_tx(); builder.add_recipient(address.script_pubkey(), amount); + if let Some(sat_per_vb) = fee_rate { + builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb)); + } builder.finish()? }; Ok(PartiallySignedBitcoinTransaction { From 620d65e21730fc4f97cf72152ed12961e5f0ff57 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Fri, 5 Nov 2021 00:43:26 +0530 Subject: [PATCH 115/272] Allow cloning transaction --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 1510fee..05614f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,8 @@ pub struct TransactionDetails { } type Confirmation = ConfirmationTime; + +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Transaction { Unconfirmed { details: TransactionDetails, From f4e9af18b53d5e2bb5444cc7770a911eb5bf1145 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Fri, 5 Nov 2021 00:43:55 +0530 Subject: [PATCH 116/272] Add a way to convert TransactionDetails to Transaction --- src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 05614f1..9bc441f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,22 @@ pub enum Transaction { }, } +fn to_transaction(x: &bdk::TransactionDetails) -> Transaction { + let details = TransactionDetails { + fees: x.fee, + id: x.txid.to_string(), + received: x.received, + sent: x.sent, + }; + match x.confirmation_time.clone() { + Some(confirmation) => Transaction::Confirmed { + details, + confirmation, + }, + None => Transaction::Unconfirmed { details }, + } +} + trait OfflineWalletOperations: WalletHolder { fn get_new_address(&self) -> String { self.get_wallet() From d190008f2ca1da3c202b1e616dd552006224d9bc Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Fri, 5 Nov 2021 00:45:02 +0530 Subject: [PATCH 117/272] Add details to PSBT --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9bc441f..a7a159b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,6 +193,7 @@ impl Progress for BdkProgressHolder { struct PartiallySignedBitcoinTransaction { internal: Mutex, + details: bdk::TransactionDetails, } impl PartiallySignedBitcoinTransaction { @@ -205,7 +206,7 @@ impl PartiallySignedBitcoinTransaction { let wallet = online_wallet.get_wallet(); match Address::from_str(&recipient) { Ok(address) => { - let (psbt, _) = { + let (psbt, details) = { let mut builder = wallet.build_tx(); builder.add_recipient(address.script_pubkey(), amount); if let Some(sat_per_vb) = fee_rate { @@ -215,6 +216,7 @@ impl PartiallySignedBitcoinTransaction { }; Ok(PartiallySignedBitcoinTransaction { internal: Mutex::new(psbt), + details, }) } Err(..) => Err(BdkError::Generic( From 683a817c5543f0b59e8090e65a50d6b4ed58a6b6 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Fri, 5 Nov 2021 00:45:16 +0530 Subject: [PATCH 118/272] Simplify --- src/lib.rs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a7a159b..ffa133a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,24 +133,7 @@ trait OfflineWalletOperations: WalletHolder { fn get_transactions(&self) -> Result, Error> { let transactions = self.get_wallet().list_transactions(true)?; - Ok(transactions - .iter() - .map(|x| -> Transaction { - let details = TransactionDetails { - fees: x.fee, - id: x.txid.to_string(), - received: x.received, - sent: x.sent, - }; - match x.confirmation_time.clone() { - Some(confirmation) => Transaction::Confirmed { - details, - confirmation, - }, - None => Transaction::Unconfirmed { details }, - } - }) - .collect()) + Ok(transactions.iter().map(to_transaction).collect()) } } From 578771ffe1ff80ae37e9390f10f540f186da7aea Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Fri, 5 Nov 2021 00:45:27 +0530 Subject: [PATCH 119/272] Return transaction on broadcast --- src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ffa133a..944197e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -269,10 +269,13 @@ impl OnlineWallet { .sync(BdkProgressHolder { progress_update }, max_address_param) } - fn broadcast<'a>(&self, psbt: &'a PartiallySignedBitcoinTransaction) -> Result { + fn broadcast<'a>( + &self, + psbt: &'a PartiallySignedBitcoinTransaction, + ) -> Result { let tx = psbt.internal.lock().unwrap().clone().extract_tx(); - let tx_id = self.get_wallet().broadcast(tx)?; - Ok(tx_id.to_string()) + self.get_wallet().broadcast(tx)?; + Ok(to_transaction(&psbt.details)) } } From de47771c1240a187b1338bc7e097363ebe29af05 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Fri, 5 Nov 2021 00:45:40 +0530 Subject: [PATCH 120/272] Update Wallet::broadcast API --- src/bdk.udl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bdk.udl b/src/bdk.udl index dc2a996..2d8061c 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -144,7 +144,7 @@ interface OnlineWallet { [Throws=BdkError] void sync(BdkProgress progress_update, u32? max_address_param); [Throws=BdkError] - string broadcast([ByRef] PartiallySignedBitcoinTransaction psbt); + Transaction broadcast([ByRef] PartiallySignedBitcoinTransaction psbt); }; interface PartiallySignedBitcoinTransaction { From e738126bedff23e8a7f094b28927fb2a9c390fe4 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Fri, 5 Nov 2021 00:45:50 +0530 Subject: [PATCH 121/272] Fix demo --- bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index a02f843..45cd0a8 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -72,12 +72,12 @@ fun main(args: Array) { println("Press Enter to return funds") readLine() println("Creating a PSBT with recipient $recipient and amount $amount satoshis...") - val transaction = PartiallySignedBitcoinTransaction(wallet, recipient, amount) + val psbt = PartiallySignedBitcoinTransaction(wallet, recipient, amount, null) println("Signing the transaction...") - wallet.sign(transaction) + wallet.sign(psbt) println("Broadcasting the signed transaction...") - val transactionId = wallet.broadcast(transaction) - println("Broadcasted transaction with id $transactionId") + val transaction = wallet.broadcast(psbt) + println("Broadcasted transaction $transaction") val take = 5 println("Listing latest $take transactions...") wallet @@ -87,3 +87,4 @@ fun main(args: Array) { .forEach { println(it) } println("Final wallet balance: ${wallet.getBalance()}") } + From 87a8af9457a256790427a77d509b16e5a8492256 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Fri, 5 Nov 2021 01:08:50 +0530 Subject: [PATCH 122/272] Use From trait for conversion --- src/lib.rs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 944197e..3ad57a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,19 +82,21 @@ pub enum Transaction { }, } -fn to_transaction(x: &bdk::TransactionDetails) -> Transaction { - let details = TransactionDetails { - fees: x.fee, - id: x.txid.to_string(), - received: x.received, - sent: x.sent, - }; - match x.confirmation_time.clone() { - Some(confirmation) => Transaction::Confirmed { - details, - confirmation, - }, - None => Transaction::Unconfirmed { details }, +impl From<&bdk::TransactionDetails> for Transaction { + fn from(x: &bdk::TransactionDetails) -> Transaction { + let details = TransactionDetails { + fees: x.fee, + id: x.txid.to_string(), + received: x.received, + sent: x.sent, + }; + match x.confirmation_time.clone() { + Some(confirmation) => Transaction::Confirmed { + details, + confirmation, + }, + None => Transaction::Unconfirmed { details }, + } } } @@ -133,7 +135,7 @@ trait OfflineWalletOperations: WalletHolder { fn get_transactions(&self) -> Result, Error> { let transactions = self.get_wallet().list_transactions(true)?; - Ok(transactions.iter().map(to_transaction).collect()) + Ok(transactions.iter().map(Transaction::from).collect()) } } @@ -275,7 +277,7 @@ impl OnlineWallet { ) -> Result { let tx = psbt.internal.lock().unwrap().clone().extract_tx(); self.get_wallet().broadcast(tx)?; - Ok(to_transaction(&psbt.details)) + Ok(Transaction::from(&psbt.details)) } } From bfe38d9890ef0fbf18d02a8aa3de44008305a328 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Fri, 5 Nov 2021 01:13:45 +0530 Subject: [PATCH 123/272] Use From trait for conversion --- src/lib.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3ad57a2..63505de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,20 +82,25 @@ pub enum Transaction { }, } -impl From<&bdk::TransactionDetails> for Transaction { - fn from(x: &bdk::TransactionDetails) -> Transaction { - let details = TransactionDetails { +impl From<&bdk::TransactionDetails> for TransactionDetails { + fn from(x: &bdk::TransactionDetails) -> TransactionDetails { + TransactionDetails { fees: x.fee, id: x.txid.to_string(), received: x.received, sent: x.sent, - }; + } + } +} + +impl From<&bdk::TransactionDetails> for Transaction { + fn from(x: &bdk::TransactionDetails) -> Transaction { match x.confirmation_time.clone() { Some(confirmation) => Transaction::Confirmed { - details, + details: TransactionDetails::from(x), confirmation, }, - None => Transaction::Unconfirmed { details }, + None => Transaction::Unconfirmed { details: TransactionDetails::from(x) }, } } } From 4e5741f55d3b0c190ba565dcf0177056576cc504 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 4 Nov 2021 17:44:02 -0700 Subject: [PATCH 124/272] Fix build.sh kotlin copy for android --- build.sh | 2 +- src/lib.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 89b786d..160a105 100755 --- a/build.sh +++ b/build.sh @@ -122,7 +122,7 @@ build_android() { fi # copy sources - cp -R bindings/bdk-kotlin/jvm/src/main/ bindings/bdk-kotlin/android/src/main/ + cp -R bindings/bdk-kotlin/jvm/src/main/kotlin bindings/bdk-kotlin/android/src/main # bdk-kotlin aar (cd bindings/bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) diff --git a/src/lib.rs b/src/lib.rs index 63505de..7b1c854 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,7 +100,9 @@ impl From<&bdk::TransactionDetails> for Transaction { details: TransactionDetails::from(x), confirmation, }, - None => Transaction::Unconfirmed { details: TransactionDetails::from(x) }, + None => Transaction::Unconfirmed { + details: TransactionDetails::from(x), + }, } } } From 931461e10d5e347e0bbc9efe3cc0d878ec3d2249 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Fri, 5 Nov 2021 23:52:08 +0530 Subject: [PATCH 125/272] Ignore unnecessary files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 85429e1..14035e0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ local.properties .DS_Store testdb xcuserdata +.lsp +.clj-kondo From dc7339a174cbe7e7a2506b17335355b028a3f0d9 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 00:22:38 +0530 Subject: [PATCH 126/272] Add publish configuration --- bindings/bdk-kotlin/android/build.gradle | 27 ++++++++++-------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index 06b71ac..c3637d3 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -1,6 +1,8 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'maven-publish' +group = 'org.bitcoindevkit' +version = '0.1.0' android { compileSdkVersion 30 @@ -26,25 +28,18 @@ android { afterEvaluate { publishing { - publications { - // Creates a Maven publication called "release". - release(MavenPublication) { - // Applies the component for the release build variant. - from components.release - // You can then customize attributes of the publication as shown below. - groupId = 'org.bitcoindevkit' - artifactId = 'bdk' - version = '0.0.1-SNAPSHOT' + repositories { + maven { + url myMavenRepoWriteUrl } - // Creates a Maven publication called “debug”. - debug(MavenPublication) { - // Applies the component for the debug build variant. - from components.debug + } - groupId = 'org.bitcoindevkit' - artifactId = 'bdk-debug' - version = '0.0.1-SNAPSHOT' + publications { + + maven(MavenPublication) { + from components.release + artifactId = 'bdk-android' } } } From cfde899b2cc47fbeff7dadcbc7e686ebb8830d32 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 00:22:45 +0530 Subject: [PATCH 127/272] Add publish configuration --- bindings/bdk-kotlin/jvm/build.gradle | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle index ea9408a..a0f754f 100644 --- a/bindings/bdk-kotlin/jvm/build.gradle +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -27,11 +27,18 @@ dependencies { } publishing { + + repositories { + maven { + url myMavenRepoWriteUrl + } + } + publications { maven(MavenPublication) { groupId = 'org.bitcoindevkit' artifactId = 'bdk' - version = '0.0.1-SNAPSHOT' + version = '0.1.0' from components.java } From fb3bfbde70f5be4f28c39645038a4f60136a6587 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 00:22:58 +0530 Subject: [PATCH 128/272] Stop copying over i686 --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 160a105..8895558 100755 --- a/build.sh +++ b/build.sh @@ -118,7 +118,7 @@ build_android() { fi if echo $BUILD_TARGETS | grep "i686"; then CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android - cp target/i686-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 + # cp target/i686-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 fi # copy sources From a4e32e783311d8cac0cecf883ac15ce99d59fe6c Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 00:23:06 +0530 Subject: [PATCH 129/272] Stop publishing on build --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 8895558..3ab3071 100755 --- a/build.sh +++ b/build.sh @@ -125,7 +125,7 @@ build_android() { cp -R bindings/bdk-kotlin/jvm/src/main/kotlin bindings/bdk-kotlin/android/src/main # bdk-kotlin aar - (cd bindings/bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal) + (cd bindings/bdk-kotlin && ./gradlew :android:build) } OS=$(uname) From 152e2147e60dc9bc82fc2455dc0708b3b26b4d55 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 00:42:32 +0530 Subject: [PATCH 130/272] Remove armv7 ABI target instead of i686 --- build.sh | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/build.sh b/build.sh index 3ab3071..a2309a5 100755 --- a/build.sh +++ b/build.sh @@ -100,9 +100,9 @@ build_android() { export CFLAGS="-D__ANDROID_API__=21" # IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on - BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" + BUILD_TARGETS="${BUILD_TARGETS:-aarch64,x86_64,i686}" - mkdir -p bindings/bdk-kotlin/android/src/main/jniLibs/ bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 bindings/bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bindings/bdk-kotlin/android/src/main/jniLibs/x86 + mkdir -p bindings/bdk-kotlin/android/src/main/jniLibs/ bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 bindings/bdk-kotlin/android/src/main/jniLibs/x86 if echo $BUILD_TARGETS | grep "aarch64"; then CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android @@ -112,13 +112,9 @@ build_android() { CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android cp target/x86_64-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 fi - if echo $BUILD_TARGETS | grep "armv7"; then - CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi - cp target/armv7-linux-androideabi/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/armeabi-v7a - fi if echo $BUILD_TARGETS | grep "i686"; then CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android - # cp target/i686-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 + cp target/i686-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 fi # copy sources From 39554e11be828211cdc164fa182f8b4ac1e2c27b Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 01:23:21 +0530 Subject: [PATCH 131/272] Update jvm package artifact id --- bindings/bdk-kotlin/jvm/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle index a0f754f..3f6b783 100644 --- a/bindings/bdk-kotlin/jvm/build.gradle +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -37,7 +37,7 @@ publishing { publications { maven(MavenPublication) { groupId = 'org.bitcoindevkit' - artifactId = 'bdk' + artifactId = 'bdk-jvm' version = '0.1.0' from components.java From 5ecfab3c0e8d6fc029eb80becaf89b4ab6d9cce4 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 01:30:36 +0530 Subject: [PATCH 132/272] Add some notes on consuming published packages --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 72efea4..365e1ac 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,42 @@ for the rust library bdk for the Bitcoin Dev Kit (BDK) project. | Kotlin | Android | WIP | | Swift | iOS | WIP | -## Getting Started + +## Getting Started (User) + +If you just want to consume the language bindings: + +### Kotlin (JVM) + +1. Add the following maven repository location to your build file: +``` +repositories { + maven { + url = uri("https://mymavenrepo.com/repo/hvZOau2SW1xzbycfC96m/") + } +} +``` +1. Add the dependency +``` +implementation("org.bitcoindevkit:bdk-jvm:0.1.0") +``` + +### Kotlin (Android) + +1. Add the following maven repository location to your build file: +``` +repositories { + maven { + url = uri("https://mymavenrepo.com/repo/hvZOau2SW1xzbycfC96m/") + } +} +``` +1. Add the dependency +``` +implementation("org.bitcoindevkit:bdk-android:0.1.0") +``` + +## Getting Started (Developer) This project uses rust. A basic knowledge of the rust ecosystem is helpful. From 613f25a79ee91567e917c621da2b058ac61ac143 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 05:48:27 +0530 Subject: [PATCH 133/272] Update README with latest published package information --- README.md | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 365e1ac..6fdb848 100644 --- a/README.md +++ b/README.md @@ -18,33 +18,11 @@ If you just want to consume the language bindings: ### Kotlin (JVM) -1. Add the following maven repository location to your build file: -``` -repositories { - maven { - url = uri("https://mymavenrepo.com/repo/hvZOau2SW1xzbycfC96m/") - } -} -``` -1. Add the dependency -``` -implementation("org.bitcoindevkit:bdk-jvm:0.1.0") -``` +Just add the dependency `org.bitcoindevkit:bdk-jvm:0.1.1`. The package is `org.bitcoindevkit.bdk`. ### Kotlin (Android) -1. Add the following maven repository location to your build file: -``` -repositories { - maven { - url = uri("https://mymavenrepo.com/repo/hvZOau2SW1xzbycfC96m/") - } -} -``` -1. Add the dependency -``` -implementation("org.bitcoindevkit:bdk-android:0.1.0") -``` +Just add the dependency `org.bitcoindevkit:bdk-android:0.1.1`. The package is `org.bitcoindevkit.bdk`. ## Getting Started (Developer) From 62d7d6fbd55e7f9357ad70576a5092219e1520e6 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 5 Nov 2021 10:33:37 -0700 Subject: [PATCH 134/272] Configure publishing for jvm and android artifacts to maven central --- bindings/bdk-kotlin/android/build.gradle | 137 ++++++++++++++---- .../org/bitcoindevkit/AndroidLibTest.kt | 16 +- bindings/bdk-kotlin/build.gradle | 82 ++++++++--- bindings/bdk-kotlin/jvm/build.gradle | 70 +++++++-- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 18 +-- 5 files changed, 240 insertions(+), 83 deletions(-) diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index c3637d3..c7bbb78 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -1,8 +1,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'maven-publish' -group = 'org.bitcoindevkit' -version = '0.1.0' +apply plugin: 'signing' android { compileSdkVersion 30 @@ -25,31 +24,13 @@ android { } } -afterEvaluate { - - publishing { - - repositories { - maven { - url myMavenRepoWriteUrl - } - } - - publications { - - maven(MavenPublication) { - from components.release - artifactId = 'bdk-android' - } - } - } -} - dependencies { // implementation(project(':jvm')) { // exclude group: 'net.java.dev.jna', module: 'jna' // } - +// api(project(':jvm')) { +// exclude group: 'net.java.dev.jna', module: 'jna' +// } implementation 'net.java.dev.jna:jna:5.8.0@aar' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.3.0' @@ -57,12 +38,112 @@ dependencies { api "org.slf4j:slf4j-api:1.7.30" androidTestImplementation 'com.github.tony19:logback-android:2.0.0' - androidTestImplementation(testFixtures(project(':jvm'))) { - exclude group: 'net.java.dev.jna', module: 'jna' - exclude group: 'ch.qos.logback', module: 'logback-core' - exclude group: 'ch.qos.logback', module: 'logback-classic' - } +// androidTestImplementation(testFixtures(project(':jvm'))) { +// exclude group: 'net.java.dev.jna', module: 'jna' +// exclude group: 'ch.qos.logback', module: 'logback-core' +// exclude group: 'ch.qos.logback', module: 'logback-classic' +// exclude group: 'junit', module: 'junit' +// } androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' } + +afterEvaluate { + + publishing { + publications { + // Creates a Maven publication called "release". + release(MavenPublication) { + + // You can then customize attributes of the publication as shown below. + groupId = 'org.bitcoindevkit' + artifactId = 'bdk-android' + version = '0.1.0' + + // Applies the component for the release build variant. + from components.release + + pom { + name = 'bdk-android' + description = 'Bitcoin Dev Kit Kotlin language bindings.' + url = "https://bitcoindevkit.org" + licenses { + license { + name = "APACHE" + url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE" + } + license { + name = "MIT" + url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT" + } + } + developers { + developer { + id = 'notmandatory' + name = 'Steve Myers' + email = 'notmandatory@noreply.github.org' + } + developer { + id = 'artfuldev' + name = 'Sudarsan Balaji' + email = 'artfuldev@noreply.github.org' + } + } + scm { + connection = 'scm:git:github.com/bitcoindevkit/bdk-ffi.git' + developerConnection = 'scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git' + url = 'https://github.com/bitcoindevkit/bdk-ffi/tree/master' + } + } + } + // Creates a Maven publication called “debug”. + debug(MavenPublication) { + // Applies the component for the debug build variant. + from components.debug + + groupId = 'org.bitcoindevkit' + artifactId = 'bdk-android-debug' + version = '0.1.0' + + pom { + name = 'bdk-android-debug' + description = 'Bitcoin Dev Kit Kotlin language bindings.' + url = "https://bitcoindevkit.org" + licenses { + license { + name = "APACHE" + url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE" + } + license { + name = "MIT" + url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT" + } + } + developers { + developer { + id = 'notmandatory' + name = 'Steve Myers' + email = 'notmandatory@noreply.github.org' + } + developer { + id = 'artfuldev' + name = 'Sudarsan Balaji' + email = 'artfuldev@noreply.github.org' + } + } + scm { + connection = 'scm:git:github.com/bitcoindevkit/bdk-ffi.git' + developerConnection = 'scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git' + url = 'https://github.com/bitcoindevkit/bdk-ffi/tree/master' + } + } + } + } + } +} + +signing { + useGpgCmd() + sign publishing.publications +} diff --git a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index 323626e..7021eb6 100644 --- a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -11,11 +11,11 @@ import org.junit.runner.RunWith * * See [testing documentation](http://d.android.com/tools/testing). */ -@RunWith(AndroidJUnit4::class) -class AndroidLibTest : LibTest() { - override fun getTestDataDir(): String { - val context = ApplicationProvider.getApplicationContext() - return context.getDir("bdk-test", MODE_PRIVATE).toString() - } - -} +//@RunWith(AndroidJUnit4::class) +//class AndroidLibTest : LibTest() { +// override fun getTestDataDir(): String { +// val context = ApplicationProvider.getApplicationContext() +// return context.getDir("bdk-test", MODE_PRIVATE).toString() +// } +// +//} diff --git a/bindings/bdk-kotlin/build.gradle b/bindings/bdk-kotlin/build.gradle index 8983efd..5b83045 100644 --- a/bindings/bdk-kotlin/build.gradle +++ b/bindings/bdk-kotlin/build.gradle @@ -1,29 +1,67 @@ buildscript { - ext.kotlin_version = '1.5.10' - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } + ext.kotlin_version = '1.5.10' + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.2.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +plugins { + id "java-library" + id "signing" + id "maven-publish" + id "io.github.gradle-nexus.publish-plugin" version "1.1.0" +} + +group = "org.bitcoindevkit" +version = "0.1.0" + +publishing { + publications { + mavenJava(MavenPublication) { + from(components.java) + } + } +} + +signing { + def signingKey = findProperty("signingKey") + def signingPassword = findProperty("signingPassword") + useInMemoryPgpKeys(signingKey, signingPassword) + sign publishing.publications +} + +nexusPublishing { + packageGroup = "org.bitcoindevkit" + repositories { + sonatype { + nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + + username = project.findProperty("ossrhUsername") + password = project.findProperty("ossrhPassword") + } + } } allprojects { - repositories { - google() - mavenCentral() - } - tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions { - freeCompilerArgs += [ - "-Xuse-experimental=kotlin.ExperimentalUnsignedTypes", - ] + repositories { + google() + mavenCentral() + } + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + freeCompilerArgs += [ + "-Xuse-experimental=kotlin.ExperimentalUnsignedTypes", + ] + } } - } } -task clean(type: Delete) { - delete rootProject.buildDir -} +//task clean(type: Delete) { +// delete rootProject.buildDir +//} diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle index 3f6b783..179fe8d 100644 --- a/bindings/bdk-kotlin/jvm/build.gradle +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -1,13 +1,17 @@ plugins { id 'org.jetbrains.kotlin.jvm' id 'java-library' - id 'java-test-fixtures' + //id 'java-test-fixtures' id 'maven-publish' + id 'signing' } java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 + + withJavadocJar() + withSourcesJar() } test { @@ -21,26 +25,60 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "net.java.dev.jna:jna:5.8.0" api "org.slf4j:slf4j-api:1.7.30" - testFixturesImplementation "junit:junit:4.13.2" - testFixturesImplementation "ch.qos.logback:logback-classic:1.2.3" - testFixturesImplementation "ch.qos.logback:logback-core:1.2.3" +// testFixturesImplementation "junit:junit:4.13.2" +// testFixturesImplementation "ch.qos.logback:logback-classic:1.2.3" +// testFixturesImplementation "ch.qos.logback:logback-core:1.2.3" } -publishing { +afterEvaluate { + publishing { + publications { - repositories { - maven { - url myMavenRepoWriteUrl - } - } + release(MavenPublication) { + groupId = 'org.bitcoindevkit' + artifactId = 'bdk-jvm' + version = '0.1.0' - publications { - maven(MavenPublication) { - groupId = 'org.bitcoindevkit' - artifactId = 'bdk-jvm' - version = '0.1.0' + from components.java - from components.java + pom { + name = 'bdk-jvm' + description = 'Bitcoin Dev Kit Kotlin language bindings.' + url = "https://bitcoindevkit.org" + licenses { + license { + name = "APACHE" + url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE" + } + license { + name = "MIT" + url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT" + } + } + developers { + developer { + id = 'notmandatory' + name = 'Steve Myers' + email = 'notmandatory@noreply.github.org' + } + developer { + id = 'artfuldev' + name = 'Sudarsan Balaji' + email = 'artfuldev@noreply.github.org' + } + } + scm { + connection = 'scm:git:github.com/bitcoindevkit/bdk-ffi.git' + developerConnection = 'scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git' + url = 'https://github.com/bitcoindevkit/bdk-ffi/tree/master' + } + } + } } } } + +signing { + useGpgCmd() + sign publishing.publications +} diff --git a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index c017092..7a0a317 100644 --- a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -1,16 +1,16 @@ package org.bitcoindevkit -import java.nio.file.Files +//import java.nio.file.Files /** * Library test, which will execute on linux host. * */ -class JvmLibTest : LibTest() { - - override fun getTestDataDir(): String { - return Files.createTempDirectory("bdk-test").toString() - //return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString() - } - -} +//class JvmLibTest : LibTest() { +// +// override fun getTestDataDir(): String { +// return Files.createTempDirectory("bdk-test").toString() +// //return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString() +// } +// +//} From df5bb9b722d204fc56c33efe5d84b58f31962a6f Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 05:06:50 +0530 Subject: [PATCH 135/272] Update email --- bindings/bdk-kotlin/android/build.gradle | 4 ++-- bindings/bdk-kotlin/jvm/build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index c7bbb78..21ee4bb 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -87,7 +87,7 @@ afterEvaluate { developer { id = 'artfuldev' name = 'Sudarsan Balaji' - email = 'artfuldev@noreply.github.org' + email = 'sudarsan.balaji@artfuldev.com' } } scm { @@ -129,7 +129,7 @@ afterEvaluate { developer { id = 'artfuldev' name = 'Sudarsan Balaji' - email = 'artfuldev@noreply.github.org' + email = 'sudarsan.balaji@artfuldev.com' } } scm { diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle index 179fe8d..3a49979 100644 --- a/bindings/bdk-kotlin/jvm/build.gradle +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -64,7 +64,7 @@ afterEvaluate { developer { id = 'artfuldev' name = 'Sudarsan Balaji' - email = 'artfuldev@noreply.github.org' + email = 'sudarsan.balaji@artfuldev.com' } } scm { From 597d0685ae81cbaccfa3966a014b6a9d1113dc60 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 05:11:51 +0530 Subject: [PATCH 136/272] Remove debug version of android package --- bindings/bdk-kotlin/android/build.gradle | 42 ------------------------ 1 file changed, 42 deletions(-) diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index 21ee4bb..a77b20c 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -97,48 +97,6 @@ afterEvaluate { } } } - // Creates a Maven publication called “debug”. - debug(MavenPublication) { - // Applies the component for the debug build variant. - from components.debug - - groupId = 'org.bitcoindevkit' - artifactId = 'bdk-android-debug' - version = '0.1.0' - - pom { - name = 'bdk-android-debug' - description = 'Bitcoin Dev Kit Kotlin language bindings.' - url = "https://bitcoindevkit.org" - licenses { - license { - name = "APACHE" - url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE" - } - license { - name = "MIT" - url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT" - } - } - developers { - developer { - id = 'notmandatory' - name = 'Steve Myers' - email = 'notmandatory@noreply.github.org' - } - developer { - id = 'artfuldev' - name = 'Sudarsan Balaji' - email = 'sudarsan.balaji@artfuldev.com' - } - } - scm { - connection = 'scm:git:github.com/bitcoindevkit/bdk-ffi.git' - developerConnection = 'scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git' - url = 'https://github.com/bitcoindevkit/bdk-ffi/tree/master' - } - } - } } } } From 9131c37d8ed8aeb2bdbceea85242854e0e925e4d Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Sat, 6 Nov 2021 05:26:56 +0530 Subject: [PATCH 137/272] Re-add test fixtures --- bindings/bdk-kotlin/android/build.gradle | 24 +++++++++---------- .../org/bitcoindevkit/AndroidLibTest.kt | 16 ++++++------- bindings/bdk-kotlin/jvm/build.gradle | 8 +++---- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 18 +++++++------- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index a77b20c..d4f9d19 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -25,12 +25,12 @@ android { } dependencies { -// implementation(project(':jvm')) { -// exclude group: 'net.java.dev.jna', module: 'jna' -// } -// api(project(':jvm')) { -// exclude group: 'net.java.dev.jna', module: 'jna' -// } + implementation(project(':jvm')) { + exclude group: 'net.java.dev.jna', module: 'jna' + } + api(project(':jvm')) { + exclude group: 'net.java.dev.jna', module: 'jna' + } implementation 'net.java.dev.jna:jna:5.8.0@aar' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.3.0' @@ -38,12 +38,12 @@ dependencies { api "org.slf4j:slf4j-api:1.7.30" androidTestImplementation 'com.github.tony19:logback-android:2.0.0' -// androidTestImplementation(testFixtures(project(':jvm'))) { -// exclude group: 'net.java.dev.jna', module: 'jna' -// exclude group: 'ch.qos.logback', module: 'logback-core' -// exclude group: 'ch.qos.logback', module: 'logback-classic' -// exclude group: 'junit', module: 'junit' -// } + androidTestImplementation(testFixtures(project(':jvm'))) { + exclude group: 'net.java.dev.jna', module: 'jna' + exclude group: 'ch.qos.logback', module: 'logback-core' + exclude group: 'ch.qos.logback', module: 'logback-classic' + exclude group: 'junit', module: 'junit' + } androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' diff --git a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index 7021eb6..0905ae0 100644 --- a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -11,11 +11,11 @@ import org.junit.runner.RunWith * * See [testing documentation](http://d.android.com/tools/testing). */ -//@RunWith(AndroidJUnit4::class) -//class AndroidLibTest : LibTest() { -// override fun getTestDataDir(): String { -// val context = ApplicationProvider.getApplicationContext() -// return context.getDir("bdk-test", MODE_PRIVATE).toString() -// } -// -//} +@RunWith(AndroidJUnit4::class) +class AndroidLibTest : LibTest() { + override fun getTestDataDir(): String { + val context = ApplicationProvider.getApplicationContext() + return context.getDir("bdk-test", MODE_PRIVATE).toString() + } + +} diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle index 3a49979..036e7fb 100644 --- a/bindings/bdk-kotlin/jvm/build.gradle +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -1,7 +1,7 @@ plugins { id 'org.jetbrains.kotlin.jvm' id 'java-library' - //id 'java-test-fixtures' + id 'java-test-fixtures' id 'maven-publish' id 'signing' } @@ -25,9 +25,9 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "net.java.dev.jna:jna:5.8.0" api "org.slf4j:slf4j-api:1.7.30" -// testFixturesImplementation "junit:junit:4.13.2" -// testFixturesImplementation "ch.qos.logback:logback-classic:1.2.3" -// testFixturesImplementation "ch.qos.logback:logback-core:1.2.3" + testFixturesImplementation "junit:junit:4.13.2" + testFixturesImplementation "ch.qos.logback:logback-classic:1.2.3" + testFixturesImplementation "ch.qos.logback:logback-core:1.2.3" } afterEvaluate { diff --git a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index 7a0a317..76db175 100644 --- a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -1,16 +1,16 @@ package org.bitcoindevkit -//import java.nio.file.Files +import java.nio.file.Files /** * Library test, which will execute on linux host. * */ -//class JvmLibTest : LibTest() { -// -// override fun getTestDataDir(): String { -// return Files.createTempDirectory("bdk-test").toString() -// //return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString() -// } -// -//} +class JvmLibTest : LibTest() { + + override fun getTestDataDir(): String { + return Files.createTempDirectory("bdk-test").toString() + //return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString() + } + +} From 0d68d2341b576221bd1a8beb73708896b47f2fbb Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 7 Nov 2021 15:12:28 -0800 Subject: [PATCH 138/272] Bump version to 0.1.2 --- bindings/bdk-kotlin/android/build.gradle | 2 +- bindings/bdk-kotlin/build.gradle | 3 --- bindings/bdk-kotlin/jvm/build.gradle | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index d4f9d19..b569602 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -59,7 +59,7 @@ afterEvaluate { // You can then customize attributes of the publication as shown below. groupId = 'org.bitcoindevkit' artifactId = 'bdk-android' - version = '0.1.0' + version = '0.1.2' // Applies the component for the release build variant. from components.release diff --git a/bindings/bdk-kotlin/build.gradle b/bindings/bdk-kotlin/build.gradle index 5b83045..e80654b 100644 --- a/bindings/bdk-kotlin/build.gradle +++ b/bindings/bdk-kotlin/build.gradle @@ -17,9 +17,6 @@ plugins { id "io.github.gradle-nexus.publish-plugin" version "1.1.0" } -group = "org.bitcoindevkit" -version = "0.1.0" - publishing { publications { mavenJava(MavenPublication) { diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle index 036e7fb..45d316d 100644 --- a/bindings/bdk-kotlin/jvm/build.gradle +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -37,7 +37,7 @@ afterEvaluate { release(MavenPublication) { groupId = 'org.bitcoindevkit' artifactId = 'bdk-jvm' - version = '0.1.0' + version = '0.1.2' from components.java From 02dd2af0d37e0c0ca2404b63bbfb188ec8a3372f Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 8 Nov 2021 17:41:06 -0800 Subject: [PATCH 139/272] Remove kotlin jvm testFixtures and dup test code --- bindings/bdk-kotlin/android/build.gradle | 12 --- .../org/bitcoindevkit/AndroidLibTest.kt | 95 ++++++++++++++++++- bindings/bdk-kotlin/build.gradle | 4 - bindings/bdk-kotlin/jvm/build.gradle | 6 +- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 94 +++++++++++++++++- .../kotlin/org/bitcoindevkit/LibTest.kt | 95 ------------------- 6 files changed, 182 insertions(+), 124 deletions(-) delete mode 100644 bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/LibTest.kt diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index b569602..144ccdd 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -25,12 +25,6 @@ android { } dependencies { - implementation(project(':jvm')) { - exclude group: 'net.java.dev.jna', module: 'jna' - } - api(project(':jvm')) { - exclude group: 'net.java.dev.jna', module: 'jna' - } implementation 'net.java.dev.jna:jna:5.8.0@aar' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.3.0' @@ -38,12 +32,6 @@ dependencies { api "org.slf4j:slf4j-api:1.7.30" androidTestImplementation 'com.github.tony19:logback-android:2.0.0' - androidTestImplementation(testFixtures(project(':jvm'))) { - exclude group: 'net.java.dev.jna', module: 'jna' - exclude group: 'ch.qos.logback', module: 'logback-core' - exclude group: 'ch.qos.logback', module: 'logback-classic' - exclude group: 'junit', module: 'junit' - } androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' diff --git a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index 0905ae0..bdb7f90 100644 --- a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -1,10 +1,15 @@ package org.bitcoindevkit +import org.junit.Assert.* +import org.junit.Test import android.app.Application import android.content.Context.MODE_PRIVATE import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.runner.RunWith +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.File /** * Instrumented test, which will execute on an Android device. @@ -12,10 +17,90 @@ import org.junit.runner.RunWith * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) -class AndroidLibTest : LibTest() { - override fun getTestDataDir(): String { - val context = ApplicationProvider.getApplicationContext() - return context.getDir("bdk-test", MODE_PRIVATE).toString() - } +class AndroidLibTest { + + fun getTestDataDir(): String { + val context = ApplicationProvider.getApplicationContext() + return context.getDir("bdk-test", MODE_PRIVATE).toString() + } + + fun cleanupTestDataDir(testDataDir: String) { + File(testDataDir).deleteRecursively() + } + + val log: Logger = LoggerFactory.getLogger(AndroidLibTest::class.java) + + val desc = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + + @Test + fun memoryWalletNewAddress() { + val config = DatabaseConfig.Memory("") + val wallet = OfflineWallet(desc, Network.REGTEST, config) + val address = wallet.getNewAddress() + assertNotNull(address) + assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + } + + @Test(expected = BdkException.Descriptor::class) + fun invalidDescriptorExceptionIsThrown() { + val config = DatabaseConfig.Memory("") + OfflineWallet("invalid-descriptor", Network.REGTEST, config) + } + + @Test + fun sledWalletNewAddress() { + val testDataDir = getTestDataDir() + val config = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) + val wallet = OfflineWallet(desc, Network.REGTEST, config) + val address = wallet.getNewAddress() + assertNotNull(address) + assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + cleanupTestDataDir(testDataDir) + } + + @Test + fun onlineWalletInMemory() { + val db = DatabaseConfig.Memory("") + val client = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 100u + ) + ) + val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) + assertNotNull(wallet) + val network = wallet.getNetwork() + assertEquals(network, Network.TESTNET) + } + + class LogProgress : BdkProgress { + val log: Logger = LoggerFactory.getLogger(AndroidLibTest::class.java) + + override fun update(progress: Float, message: String?) { + log.debug("Syncing...") + } + } + + @Test + fun onlineWalletSyncGetBalance() { + val db = DatabaseConfig.Memory("") + val client = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 100u + ) + ) + val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) + wallet.sync(LogProgress(), null) + val balance = wallet.getBalance() + assertTrue(balance > 0u) + } } diff --git a/bindings/bdk-kotlin/build.gradle b/bindings/bdk-kotlin/build.gradle index e80654b..5946db4 100644 --- a/bindings/bdk-kotlin/build.gradle +++ b/bindings/bdk-kotlin/build.gradle @@ -58,7 +58,3 @@ allprojects { } } } - -//task clean(type: Delete) { -// delete rootProject.buildDir -//} diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle index 45d316d..9f11731 100644 --- a/bindings/bdk-kotlin/jvm/build.gradle +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -25,9 +25,9 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "net.java.dev.jna:jna:5.8.0" api "org.slf4j:slf4j-api:1.7.30" - testFixturesImplementation "junit:junit:4.13.2" - testFixturesImplementation "ch.qos.logback:logback-classic:1.2.3" - testFixturesImplementation "ch.qos.logback:logback-core:1.2.3" + testImplementation "junit:junit:4.13.2" + testImplementation "ch.qos.logback:logback-classic:1.2.3" + testImplementation "ch.qos.logback:logback-core:1.2.3" } afterEvaluate { diff --git a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index 76db175..67beaf0 100644 --- a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -1,16 +1,100 @@ package org.bitcoindevkit +import org.junit.Assert.* +import org.junit.Test +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.File import java.nio.file.Files /** * Library test, which will execute on linux host. * */ -class JvmLibTest : LibTest() { +class JvmLibTest { - override fun getTestDataDir(): String { - return Files.createTempDirectory("bdk-test").toString() - //return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString() - } + fun getTestDataDir(): String { + return Files.createTempDirectory("bdk-test").toString() + //return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString() + } + + fun cleanupTestDataDir(testDataDir: String) { + File(testDataDir).deleteRecursively() + } + + val log: Logger = LoggerFactory.getLogger(JvmLibTest::class.java) + + val desc = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + + @Test + fun memoryWalletNewAddress() { + val config = DatabaseConfig.Memory("") + val wallet = OfflineWallet(desc, Network.REGTEST, config) + val address = wallet.getNewAddress() + assertNotNull(address) + assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + } + + @Test(expected = BdkException.Descriptor::class) + fun invalidDescriptorExceptionIsThrown() { + val config = DatabaseConfig.Memory("") + OfflineWallet("invalid-descriptor", Network.REGTEST, config) + } + + @Test + fun sledWalletNewAddress() { + val testDataDir = getTestDataDir() + val config = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) + val wallet = OfflineWallet(desc, Network.REGTEST, config) + val address = wallet.getNewAddress() + assertNotNull(address) + assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + cleanupTestDataDir(testDataDir) + } + + @Test + fun onlineWalletInMemory() { + val db = DatabaseConfig.Memory("") + val client = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 100u + ) + ) + val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) + assertNotNull(wallet) + val network = wallet.getNetwork() + assertEquals(network, Network.TESTNET) + } + + class LogProgress : BdkProgress { + val log: Logger = LoggerFactory.getLogger(JvmLibTest::class.java) + + override fun update(progress: Float, message: String?) { + log.debug("Syncing...") + } + } + + @Test + fun onlineWalletSyncGetBalance() { + val db = DatabaseConfig.Memory("") + val client = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 100u + ) + ) + val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) + wallet.sync(LogProgress(), null) + val balance = wallet.getBalance() + assertTrue(balance > 0u) + } } diff --git a/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/LibTest.kt b/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/LibTest.kt deleted file mode 100644 index f6bf1d9..0000000 --- a/bindings/bdk-kotlin/jvm/src/testFixtures/kotlin/org/bitcoindevkit/LibTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -package org.bitcoindevkit - -import org.junit.Assert.* -import org.junit.Test -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.bitcoindevkit.* -import java.io.File - -/** - * Library tests which will execute for jvm and android modules. - */ -abstract class LibTest { - - abstract fun getTestDataDir(): String - - fun cleanupTestDataDir(testDataDir: String) { - File(testDataDir).deleteRecursively() - } - - val log: Logger = LoggerFactory.getLogger(LibTest::class.java) - - val desc = - "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - - @Test - fun memoryWalletNewAddress() { - val config = DatabaseConfig.Memory("") - val wallet = OfflineWallet(desc, Network.REGTEST, config) - val address = wallet.getNewAddress() - assertNotNull(address) - assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") - } - - @Test(expected = BdkException.Descriptor::class) - fun invalidDescriptorExceptionIsThrown() { - val config = DatabaseConfig.Memory("") - OfflineWallet("invalid-descriptor", Network.REGTEST, config) - } - - @Test - fun sledWalletNewAddress() { - val testDataDir = getTestDataDir() - val config = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) - val wallet = OfflineWallet(desc, Network.REGTEST, config) - val address = wallet.getNewAddress() - assertNotNull(address) - assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") - cleanupTestDataDir(testDataDir) - } - - @Test - fun onlineWalletInMemory() { - val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum( - ElectrumConfig( - "ssl://electrum.blockstream.info:60002", - null, - 5u, - null, - 100u - ) - ) - val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) - assertNotNull(wallet) - val network = wallet.getNetwork() - assertEquals(network, Network.TESTNET) - } - - class LogProgress : BdkProgress { - val log: Logger = LoggerFactory.getLogger(LibTest::class.java) - - override fun update(progress: Float, message: String?) { - log.debug("Syncing...") - } - } - - @Test - fun onlineWalletSyncGetBalance() { - val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum( - ElectrumConfig( - "ssl://electrum.blockstream.info:60002", - null, - 5u, - null, - 100u - ) - ) - val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) - wallet.sync(LogProgress(), null) - val balance = wallet.getBalance() - assertTrue(balance > 0u) - } -} From 3f28be08546662e99beedf8374fc7b882c1173e6 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 8 Nov 2021 17:43:47 -0800 Subject: [PATCH 140/272] Bump version to 0.1.3-dev --- bindings/bdk-kotlin/android/build.gradle | 2 +- bindings/bdk-kotlin/jvm/build.gradle | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle index 144ccdd..bdbf9a9 100644 --- a/bindings/bdk-kotlin/android/build.gradle +++ b/bindings/bdk-kotlin/android/build.gradle @@ -47,7 +47,7 @@ afterEvaluate { // You can then customize attributes of the publication as shown below. groupId = 'org.bitcoindevkit' artifactId = 'bdk-android' - version = '0.1.2' + version = '0.1.3-dev' // Applies the component for the release build variant. from components.release diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle index 9f11731..c1185ef 100644 --- a/bindings/bdk-kotlin/jvm/build.gradle +++ b/bindings/bdk-kotlin/jvm/build.gradle @@ -1,7 +1,6 @@ plugins { id 'org.jetbrains.kotlin.jvm' id 'java-library' - id 'java-test-fixtures' id 'maven-publish' id 'signing' } @@ -37,7 +36,7 @@ afterEvaluate { release(MavenPublication) { groupId = 'org.bitcoindevkit' artifactId = 'bdk-jvm' - version = '0.1.2' + version = '0.1.3-dev' from components.java From 9fa6fd513339e55b162a82138397b7658b6369f3 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 12 Nov 2021 12:29:53 -0500 Subject: [PATCH 141/272] Refactor transaction 'id' property to 'txid' --- bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt | 6 +++--- src/bdk.udl | 2 +- src/lib.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt index 45cd0a8..2c6629b 100644 --- a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt +++ b/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt @@ -18,8 +18,8 @@ fun getTransaction(wallet: OnlineWalletInterface, transactionId: String): Option .stream() .filter({ when (it) { - is Transaction.Confirmed -> it.details.id.equals(transactionId) - is Transaction.Unconfirmed -> it.details.id.equals(transactionId) + is Transaction.Confirmed -> it.details.txid.equals(transactionId) + is Transaction.Unconfirmed -> it.details.txid.equals(transactionId) } }) .findFirst() @@ -32,7 +32,7 @@ val unconfirmedFirstThenByTimestampDescending = (a is Transaction.Confirmed && b is Transaction.Confirmed) -> { val comparison = b.confirmation.timestamp.compareTo(a.confirmation.timestamp) when { - comparison == 0 -> b.details.id.compareTo(a.details.id) + comparison == 0 -> b.details.txid.compareTo(a.details.txid) else -> comparison } } diff --git a/src/bdk.udl b/src/bdk.udl index 2d8061c..0597368 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -70,7 +70,7 @@ dictionary TransactionDetails { u64? fees; u64 received; u64 sent; - string id; + string txid; }; dictionary Confirmation { diff --git a/src/lib.rs b/src/lib.rs index 7b1c854..d88d7d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ pub struct TransactionDetails { pub fees: Option, pub received: u64, pub sent: u64, - pub id: String, + pub txid: String, } type Confirmation = ConfirmationTime; @@ -86,7 +86,7 @@ impl From<&bdk::TransactionDetails> for TransactionDetails { fn from(x: &bdk::TransactionDetails) -> TransactionDetails { TransactionDetails { fees: x.fee, - id: x.txid.to_string(), + txid: x.txid.to_string(), received: x.received, sent: x.sent, } From 581787a77551758d5354b9f9b2dcd546d42bb3ee Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 22 Nov 2021 15:59:12 -0800 Subject: [PATCH 142/272] Pin anyhow version to "=1.0.45" This change can be removed after upgrading to the next version of uniffi. See: https://github.com/mozilla/uniffi-rs/issues/1109 --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 4d799ff..bb01c73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ bdk = { version = "0.13", features = ["all-keys", "use-esplora-ureq"] } uniffi_macros = "0.14.1" uniffi = "0.14.1" thiserror = "1.0" +anyhow = "=1.0.45" # remove after upgrading to next version of uniffi [build-dependencies] uniffi_build = "0.14.1" From af101d0b41f9ad63789596aa3c09c3992d89d01d Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 16 Nov 2021 22:56:31 -0800 Subject: [PATCH 143/272] Update build.sh to create swift xcframework --- .gitignore | 7 +++++- build.sh | 64 ++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 14035e0..9508bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,10 @@ target build Cargo.lock /bindings/bdk-kotlin/local.properties -/bindings/bdk-swift +/bindings/bdk-swift/* +!bindings/bdk-swift/bdkFFI-umbrella.h +!bindings/bdk-swift/Info.plist +!bindings/bdk-swift/module.modulemap /bindings/bdk-swift.swiftdoc /bindings/bdk-swift.swiftsourceinfo .gradle @@ -17,3 +20,5 @@ testdb xcuserdata .lsp .clj-kondo +*.xcframework +*.xcframework.zip diff --git a/build.sh b/build.sh index a2309a5..fc217a2 100755 --- a/build.sh +++ b/build.sh @@ -54,36 +54,58 @@ build_kotlin() { build_swift() { uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-swift/ --language swift swiftc -module-name bdk -emit-library -o libbdkffi.dylib -emit-module -emit-module-path ./bindings/bdk-swift/ -parse-as-library -L ./target/debug/ -lbdkffi -Xcc -fmodule-map-file=./bindings/bdk-swift/bdkFFI.modulemap ./bindings/bdk-swift/bdk.swift - TARGETDIR=target - RELDIR=debug + TARGET_DIR=target + BUILD_PROFILE=debug STATIC_LIB_NAME=libbdkffi.a - # We can't use cargo lipo because we can't link to universal libraries :( - # https://github.com/rust-lang/rust/issues/55235 + # Build ios and ios x86_64 ios simulator binaries LIBS_ARCHS=("x86_64" "arm64") IOS_TRIPLES=("x86_64-apple-ios" "aarch64-apple-ios") for i in "${!LIBS_ARCHS[@]}"; do cargo build --target "${IOS_TRIPLES[${i}]}" done - UNIVERSAL_BINARY=./${TARGETDIR}/ios/universal/${RELDIR}/${STATIC_LIB_NAME} - NEED_LIPO= + ## Manually construct xcframework + LIB_NAME=libbdkffi.a + SWIFT_DIR="bindings/bdk-swift" + XCFRAMEWORK_NAME="bdkFFI" + XCFRAMEWORK_ROOT="$SWIFT_DIR/$XCFRAMEWORK_NAME.xcframework" - # if the universal binary doesnt exist, or if it's older than the static libs, - # we need to run `lipo` again. - if [[ ! -f "${UNIVERSAL_BINARY}" ]]; then - NEED_LIPO=1 - elif [[ "$(stat -f "%m" "./${TARGETDIR}/x86_64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}")" -gt "$(stat -f "%m" "${UNIVERSAL_BINARY}")" ]]; then - NEED_LIPO=1 - elif [[ "$(stat -f "%m" "./${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}")" -gt "$(stat -f "%m" "${UNIVERSAL_BINARY}")" ]]; then - NEED_LIPO=1 - fi - if [[ "${NEED_LIPO}" = "1" ]]; then - mkdir -p "${TARGETDIR}/ios/universal/${RELDIR}" - lipo -create -output "${UNIVERSAL_BINARY}" \ - "${TARGETDIR}/x86_64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}" \ - "${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}" - fi + # Cleanup prior build + rm -rf "$XCFRAMEWORK_ROOT" + + # Common files + XCFRAMEWORK_COMMON="$XCFRAMEWORK_ROOT/common/$XCFRAMEWORK_NAME.framework" + mkdir -p "$XCFRAMEWORK_COMMON/Modules" + cp "$SWIFT_DIR/module.modulemap" "$XCFRAMEWORK_COMMON/Modules/" + mkdir -p "$XCFRAMEWORK_COMMON/Headers" + cp "$SWIFT_DIR/bdkFFI-umbrella.h" "$XCFRAMEWORK_COMMON/Headers" + cp "$SWIFT_DIR/bdkFFI.h" "$XCFRAMEWORK_COMMON/Headers" + #mkdir -p "$XCFRAMEWORK_COMMON/$XCFRAMEWORK_NAME" + #cp "$SWIFT_DIR/bdk.swift" "$XCFRAMEWORK_COMMON/$XCFRAMEWORK_NAME" + + # iOS hardware + mkdir -p "$XCFRAMEWORK_ROOT/ios-arm64" + cp -R "$XCFRAMEWORK_COMMON" "$XCFRAMEWORK_ROOT/ios-arm64/$XCFRAMEWORK_NAME.framework" + cp "$TARGET_DIR/aarch64-apple-ios/$BUILD_PROFILE/$LIB_NAME" "$XCFRAMEWORK_ROOT/ios-arm64/$XCFRAMEWORK_NAME.framework/$XCFRAMEWORK_NAME" + + # iOS simulator, currently x86_64 only (need to make fat binary to add M1) + mkdir -p "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator" + cp -R "$XCFRAMEWORK_COMMON" "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator/$XCFRAMEWORK_NAME.framework" + cp "$TARGET_DIR/x86_64-apple-ios/$BUILD_PROFILE/$LIB_NAME" "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator/$XCFRAMEWORK_NAME.framework/$XCFRAMEWORK_NAME" + + # Set up the metadata for the XCFramework as a whole. + cp "$SWIFT_DIR/Info.plist" "$XCFRAMEWORK_ROOT/Info.plist" + # TODO add license info + + # Remove common + rm -rf "$XCFRAMEWORK_ROOT/common" + + # Zip it all up into a bundle for distribution. + (cd $SWIFT_DIR; zip -9 -r "$XCFRAMEWORK_NAME.xcframework.zip" "$XCFRAMEWORK_NAME.xcframework") + + # Cleanup build + # rm -rf "$XCFRAMEWORK_ROOT" } ## rust android From 743ba939caf6854936f77f156f673ac2eb51815f Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 20 Nov 2021 14:44:57 -0800 Subject: [PATCH 144/272] Fix IOSBdkAppSample to work with git hosted BitcoinDevKit swift package --- build.sh | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/build.sh b/build.sh index fc217a2..d6d2118 100755 --- a/build.sh +++ b/build.sh @@ -22,7 +22,7 @@ help() build_rust() { echo "Build Rust library" cargo fmt - cargo build + cargo build --release cargo test } @@ -33,12 +33,12 @@ copy_lib_kotlin() { "Darwin") echo -n "darwin " mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 - cp target/debug/libbdkffi.dylib bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 + cp target/release/libbdkffi.dylib bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 ;; "Linux") echo -n "linux " mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 - cp target/debug/libbdkffi.so bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 + cp target/release/libbdkffi.so bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 ;; esac echo "libs to kotlin sub-project" @@ -52,17 +52,19 @@ build_kotlin() { ## bdk swift build_swift() { - uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-swift/ --language swift - swiftc -module-name bdk -emit-library -o libbdkffi.dylib -emit-module -emit-module-path ./bindings/bdk-swift/ -parse-as-library -L ./target/debug/ -lbdkffi -Xcc -fmodule-map-file=./bindings/bdk-swift/bdkFFI.modulemap ./bindings/bdk-swift/bdk.swift + BUILD_PROFILE=release TARGET_DIR=target - BUILD_PROFILE=debug + + uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-swift/ --language swift + swiftc -module-name bdk -emit-library -o libbdkffi.dylib -emit-module -emit-module-path ./bindings/bdk-swift/ -parse-as-library -L ./target/release/ -lbdkffi -Xcc -fmodule-map-file=./bindings/bdk-swift/bdkFFI.modulemap ./bindings/bdk-swift/bdk.swift + STATIC_LIB_NAME=libbdkffi.a # Build ios and ios x86_64 ios simulator binaries LIBS_ARCHS=("x86_64" "arm64") IOS_TRIPLES=("x86_64-apple-ios" "aarch64-apple-ios") for i in "${!LIBS_ARCHS[@]}"; do - cargo build --target "${IOS_TRIPLES[${i}]}" + cargo build --release --target "${IOS_TRIPLES[${i}]}" done ## Manually construct xcframework @@ -127,16 +129,16 @@ build_android() { mkdir -p bindings/bdk-kotlin/android/src/main/jniLibs/ bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 bindings/bdk-kotlin/android/src/main/jniLibs/x86 if echo $BUILD_TARGETS | grep "aarch64"; then - CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android - cp target/aarch64-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a + CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --release --target=aarch64-linux-android + cp target/aarch64-linux-android/release/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a fi if echo $BUILD_TARGETS | grep "x86_64"; then - CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android - cp target/x86_64-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 + CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --release --target=x86_64-linux-android + cp target/x86_64-linux-android/release/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 fi if echo $BUILD_TARGETS | grep "i686"; then - CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android - cp target/i686-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 + CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --release --target=i686-linux-android + cp target/i686-linux-android/release/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 fi # copy sources From 09ce97170897d36f87112ff59f900500b88a8a65 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 24 Nov 2021 12:11:53 -0800 Subject: [PATCH 145/272] Remove swift related files and -s option in build.sh Build script and files to create a bdkFFI binary xcframework and BitcoinDevKit swift package have been moved to the bdk-swift repo. --- .gitignore | 8 ------- build.sh | 62 ------------------------------------------------------ 2 files changed, 70 deletions(-) diff --git a/.gitignore b/.gitignore index 9508bf9..757510a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,6 @@ target build Cargo.lock /bindings/bdk-kotlin/local.properties -/bindings/bdk-swift/* -!bindings/bdk-swift/bdkFFI-umbrella.h -!bindings/bdk-swift/Info.plist -!bindings/bdk-swift/module.modulemap -/bindings/bdk-swift.swiftdoc -/bindings/bdk-swift.swiftsourceinfo .gradle wallet_db bdk_ffi_test @@ -20,5 +14,3 @@ testdb xcuserdata .lsp .clj-kondo -*.xcframework -*.xcframework.zip diff --git a/build.sh b/build.sh index d6d2118..7c4b99a 100755 --- a/build.sh +++ b/build.sh @@ -14,7 +14,6 @@ help() echo "-a Android." echo "-h Print this Help." echo "-k Kotlin." - echo "-s Swift." echo } @@ -50,66 +49,6 @@ build_kotlin() { uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-kotlin/jvm/src/main/kotlin --language kotlin } -## bdk swift -build_swift() { - BUILD_PROFILE=release - TARGET_DIR=target - - uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-swift/ --language swift - swiftc -module-name bdk -emit-library -o libbdkffi.dylib -emit-module -emit-module-path ./bindings/bdk-swift/ -parse-as-library -L ./target/release/ -lbdkffi -Xcc -fmodule-map-file=./bindings/bdk-swift/bdkFFI.modulemap ./bindings/bdk-swift/bdk.swift - - STATIC_LIB_NAME=libbdkffi.a - - # Build ios and ios x86_64 ios simulator binaries - LIBS_ARCHS=("x86_64" "arm64") - IOS_TRIPLES=("x86_64-apple-ios" "aarch64-apple-ios") - for i in "${!LIBS_ARCHS[@]}"; do - cargo build --release --target "${IOS_TRIPLES[${i}]}" - done - - ## Manually construct xcframework - LIB_NAME=libbdkffi.a - SWIFT_DIR="bindings/bdk-swift" - XCFRAMEWORK_NAME="bdkFFI" - XCFRAMEWORK_ROOT="$SWIFT_DIR/$XCFRAMEWORK_NAME.xcframework" - - # Cleanup prior build - rm -rf "$XCFRAMEWORK_ROOT" - - # Common files - XCFRAMEWORK_COMMON="$XCFRAMEWORK_ROOT/common/$XCFRAMEWORK_NAME.framework" - mkdir -p "$XCFRAMEWORK_COMMON/Modules" - cp "$SWIFT_DIR/module.modulemap" "$XCFRAMEWORK_COMMON/Modules/" - mkdir -p "$XCFRAMEWORK_COMMON/Headers" - cp "$SWIFT_DIR/bdkFFI-umbrella.h" "$XCFRAMEWORK_COMMON/Headers" - cp "$SWIFT_DIR/bdkFFI.h" "$XCFRAMEWORK_COMMON/Headers" - #mkdir -p "$XCFRAMEWORK_COMMON/$XCFRAMEWORK_NAME" - #cp "$SWIFT_DIR/bdk.swift" "$XCFRAMEWORK_COMMON/$XCFRAMEWORK_NAME" - - # iOS hardware - mkdir -p "$XCFRAMEWORK_ROOT/ios-arm64" - cp -R "$XCFRAMEWORK_COMMON" "$XCFRAMEWORK_ROOT/ios-arm64/$XCFRAMEWORK_NAME.framework" - cp "$TARGET_DIR/aarch64-apple-ios/$BUILD_PROFILE/$LIB_NAME" "$XCFRAMEWORK_ROOT/ios-arm64/$XCFRAMEWORK_NAME.framework/$XCFRAMEWORK_NAME" - - # iOS simulator, currently x86_64 only (need to make fat binary to add M1) - mkdir -p "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator" - cp -R "$XCFRAMEWORK_COMMON" "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator/$XCFRAMEWORK_NAME.framework" - cp "$TARGET_DIR/x86_64-apple-ios/$BUILD_PROFILE/$LIB_NAME" "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator/$XCFRAMEWORK_NAME.framework/$XCFRAMEWORK_NAME" - - # Set up the metadata for the XCFramework as a whole. - cp "$SWIFT_DIR/Info.plist" "$XCFRAMEWORK_ROOT/Info.plist" - # TODO add license info - - # Remove common - rm -rf "$XCFRAMEWORK_ROOT/common" - - # Zip it all up into a bundle for distribution. - (cd $SWIFT_DIR; zip -9 -r "$XCFRAMEWORK_NAME.xcframework.zip" "$XCFRAMEWORK_NAME.xcframework") - - # Cleanup build - # rm -rf "$XCFRAMEWORK_ROOT" -} - ## rust android build_android() { build_kotlin @@ -160,7 +99,6 @@ else case "$1" in -a) build_android ;; -k) build_kotlin ;; - -s) build_swift ;; -h) help ;; *) echo "Option $1 not recognized" ;; esac From 0f42ba7590e7875bf22e58b63b159d6255affa5d Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 13 Dec 2021 22:14:16 -0800 Subject: [PATCH 146/272] Remove non-kotlin related files, add bdk-ffi as submodule, update build.sh --- .gitignore | 2 + .gitmodules | 3 + Cargo.toml | 20 -- .../android => android}/build.gradle | 0 .../android => android}/proguard-rules.pro | 0 .../src/androidTest/assets/logback.xml | 0 .../org/bitcoindevkit/AndroidLibTest.kt | 0 .../src/main/AndroidManifest.xml | 0 bdk-ffi | 1 + bindings/bdk-kotlin/.gitignore | 7 - .../bdk-kotlin/build.gradle => build.gradle | 0 build.rs | 3 - build.sh | 154 +++----- .../bdk-kotlin/demo => demo}/build.gradle | 0 .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 .../demo => demo}/src/main/kotlin/Main.kt | 0 .../gradle.properties => gradle.properties | 0 .../wrapper/gradle-wrapper.jar | Bin .../wrapper/gradle-wrapper.properties | 0 bindings/bdk-kotlin/gradlew => gradlew | 0 .../bdk-kotlin/gradlew.bat => gradlew.bat | 178 ++++----- {bindings/bdk-kotlin/jvm => jvm}/build.gradle | 0 .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 0 .../settings.gradle => settings.gradle | 0 src/bdk.udl | 167 --------- src/lib.rs | 340 ------------------ test.sh | 44 --- uniffi.toml | 12 - 29 files changed, 151 insertions(+), 780 deletions(-) create mode 100644 .gitmodules delete mode 100644 Cargo.toml rename {bindings/bdk-kotlin/android => android}/build.gradle (100%) rename {bindings/bdk-kotlin/android => android}/proguard-rules.pro (100%) rename {bindings/bdk-kotlin/android => android}/src/androidTest/assets/logback.xml (100%) rename {bindings/bdk-kotlin/android => android}/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt (100%) rename {bindings/bdk-kotlin/android => android}/src/main/AndroidManifest.xml (100%) create mode 160000 bdk-ffi delete mode 100644 bindings/bdk-kotlin/.gitignore rename bindings/bdk-kotlin/build.gradle => build.gradle (100%) delete mode 100644 build.rs rename {bindings/bdk-kotlin/demo => demo}/build.gradle (100%) rename {bindings/bdk-kotlin/demo => demo}/gradle/wrapper/gradle-wrapper.jar (100%) rename {bindings/bdk-kotlin/demo => demo}/gradle/wrapper/gradle-wrapper.properties (100%) rename {bindings/bdk-kotlin/demo => demo}/src/main/kotlin/Main.kt (100%) rename bindings/bdk-kotlin/gradle.properties => gradle.properties (100%) rename {bindings/bdk-kotlin/gradle => gradle}/wrapper/gradle-wrapper.jar (100%) rename {bindings/bdk-kotlin/gradle => gradle}/wrapper/gradle-wrapper.properties (100%) rename bindings/bdk-kotlin/gradlew => gradlew (100%) rename bindings/bdk-kotlin/gradlew.bat => gradlew.bat (96%) rename {bindings/bdk-kotlin/jvm => jvm}/build.gradle (100%) rename {bindings/bdk-kotlin/jvm => jvm}/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt (100%) rename bindings/bdk-kotlin/settings.gradle => settings.gradle (100%) delete mode 100644 src/bdk.udl delete mode 100644 src/lib.rs delete mode 100755 test.sh delete mode 100644 uniffi.toml diff --git a/.gitignore b/.gitignore index 757510a..277a164 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ testdb xcuserdata .lsp .clj-kondo +.idea +bdk.kt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9c7f2ac --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "bdk-ffi"] + path = bdk-ffi + url = git@github.com:bitcoindevkit/bdk-ffi.git diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index bb01c73..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "bdk-ffi" -version = "0.1.0" -authors = ["Steve Myers ", "Sudarsan Balaji "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -crate-type = ["staticlib", "cdylib"] -name = "bdkffi" - -[dependencies] -bdk = { version = "0.13", features = ["all-keys", "use-esplora-ureq"] } -uniffi_macros = "0.14.1" -uniffi = "0.14.1" -thiserror = "1.0" -anyhow = "=1.0.45" # remove after upgrading to next version of uniffi - -[build-dependencies] -uniffi_build = "0.14.1" diff --git a/bindings/bdk-kotlin/android/build.gradle b/android/build.gradle similarity index 100% rename from bindings/bdk-kotlin/android/build.gradle rename to android/build.gradle diff --git a/bindings/bdk-kotlin/android/proguard-rules.pro b/android/proguard-rules.pro similarity index 100% rename from bindings/bdk-kotlin/android/proguard-rules.pro rename to android/proguard-rules.pro diff --git a/bindings/bdk-kotlin/android/src/androidTest/assets/logback.xml b/android/src/androidTest/assets/logback.xml similarity index 100% rename from bindings/bdk-kotlin/android/src/androidTest/assets/logback.xml rename to android/src/androidTest/assets/logback.xml diff --git a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt similarity index 100% rename from bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt rename to android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt diff --git a/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml similarity index 100% rename from bindings/bdk-kotlin/android/src/main/AndroidManifest.xml rename to android/src/main/AndroidManifest.xml diff --git a/bdk-ffi b/bdk-ffi new file mode 160000 index 0000000..e4d53b5 --- /dev/null +++ b/bdk-ffi @@ -0,0 +1 @@ +Subproject commit e4d53b5e4b213e484bf4b76a4bf33884dd68f086 diff --git a/bindings/bdk-kotlin/.gitignore b/bindings/bdk-kotlin/.gitignore deleted file mode 100644 index f9467fc..0000000 --- a/bindings/bdk-kotlin/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.idea -.gradle -local.properties -build -*.so -*.dylib -bdk.kt diff --git a/bindings/bdk-kotlin/build.gradle b/build.gradle similarity index 100% rename from bindings/bdk-kotlin/build.gradle rename to build.gradle diff --git a/build.rs b/build.rs deleted file mode 100644 index 153077f..0000000 --- a/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - uniffi_build::generate_scaffolding("src/bdk.udl").unwrap(); -} diff --git a/build.sh b/build.sh index 7c4b99a..4ab1677 100755 --- a/build.sh +++ b/build.sh @@ -1,107 +1,65 @@ #!/usr/bin/env bash set -eo pipefail -# functions +echo "Build and test bdk-ffi library for local platform (darwin or linux)" +pushd bdk-ffi -## help -help() -{ - # Display Help - echo "Build bdk-ffi and related libraries." - echo - echo "Syntax: build [-a|h|k|s]" - echo "options:" - echo "-a Android." - echo "-h Print this Help." - echo "-k Kotlin." - echo -} - -## rust -build_rust() { - echo "Build Rust library" - cargo fmt - cargo build --release - cargo test -} - -## copy to bdk-bdk-kotlin -copy_lib_kotlin() { - echo -n "Copy " - case $OS in - "Darwin") - echo -n "darwin " - mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 - cp target/release/libbdkffi.dylib bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64 - ;; - "Linux") - echo -n "linux " - mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 - cp target/release/libbdkffi.so bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64 - ;; - esac - echo "libs to kotlin sub-project" -} - -## bdk-bdk-kotlin jar -build_kotlin() { - copy_lib_kotlin - uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-kotlin/jvm/src/main/kotlin --language kotlin -} - -## rust android -build_android() { - build_kotlin - - # If ANDROID_NDK_HOME is not set then set it to github actions default - [ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle - - # Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 - export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin - - # Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 - export CFLAGS="-D__ANDROID_API__=21" - - # IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on - BUILD_TARGETS="${BUILD_TARGETS:-aarch64,x86_64,i686}" - - mkdir -p bindings/bdk-kotlin/android/src/main/jniLibs/ bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 bindings/bdk-kotlin/android/src/main/jniLibs/x86 - - if echo $BUILD_TARGETS | grep "aarch64"; then - CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --release --target=aarch64-linux-android - cp target/aarch64-linux-android/release/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a - fi - if echo $BUILD_TARGETS | grep "x86_64"; then - CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --release --target=x86_64-linux-android - cp target/x86_64-linux-android/release/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 - fi - if echo $BUILD_TARGETS | grep "i686"; then - CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --release --target=i686-linux-android - cp target/i686-linux-android/release/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86 - fi - - # copy sources - cp -R bindings/bdk-kotlin/jvm/src/main/kotlin bindings/bdk-kotlin/android/src/main - - # bdk-kotlin aar - (cd bindings/bdk-kotlin && ./gradlew :android:build) -} +cargo fmt +cargo build --release +cargo test OS=$(uname) +echo -n "Copy " +case $OS in + "Darwin") + echo -n "darwin " + mkdir -p ../jvm/src/main/resources/darwin-x86-64 + cp target/release/libbdkffi.dylib ../jvm/src/main/resources/darwin-x86-64 + ;; + "Linux") + echo -n "linux " + mkdir -p ../jvm/src/main/resources/linux-x86-64 + cp target/release/libbdkffi.so ../jvm/src/main/resources/linux-x86-64 + ;; +esac +echo "libs to jvm subproject" -if [ "$1" == "-h" ] -then - help -else - build_rust +echo "Generate kotlin bindings from bdk.udl to jvm subproject" +uniffi-bindgen generate src/bdk.udl --no-format --out-dir ../jvm/src/main/kotlin --language kotlin - while [ -n "$1" ]; do # while loop starts - case "$1" in - -a) build_android ;; - -k) build_kotlin ;; - -h) help ;; - *) echo "Option $1 not recognized" ;; - esac - shift - done +## android + +# If ANDROID_NDK_HOME is not set then set it to github actions default +[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle + +# Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 +export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin + +# Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 +export CFLAGS="-D__ANDROID_API__=21" + +# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on +BUILD_TARGETS="${BUILD_TARGETS:-aarch64,x86_64,i686}" + +mkdir -p ../android/src/main/jniLibs/arm64-v8a ../android/src/main/jniLibs/x86_64 ../android/src/main/jniLibs/x86 + +if echo $BUILD_TARGETS | grep "aarch64"; then + CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --release --target=aarch64-linux-android + cp target/aarch64-linux-android/release/libbdkffi.so ../android/src/main/jniLibs/arm64-v8a fi +if echo $BUILD_TARGETS | grep "x86_64"; then + CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --release --target=x86_64-linux-android + cp target/x86_64-linux-android/release/libbdkffi.so ../android/src/main/jniLibs/x86_64 +fi +if echo $BUILD_TARGETS | grep "i686"; then + CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --release --target=i686-linux-android + cp target/i686-linux-android/release/libbdkffi.so ../android/src/main/jniLibs/x86 +fi + +popd + +# copy bdk-ffi kotlin binding sources from jvm to android +cp -R jvm/src/main/kotlin android/src/main + +# bdk-kotlin build jar and aar subprojects +./gradlew build diff --git a/bindings/bdk-kotlin/demo/build.gradle b/demo/build.gradle similarity index 100% rename from bindings/bdk-kotlin/demo/build.gradle rename to demo/build.gradle diff --git a/bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.jar b/demo/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.jar rename to demo/gradle/wrapper/gradle-wrapper.jar diff --git a/bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.properties b/demo/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from bindings/bdk-kotlin/demo/gradle/wrapper/gradle-wrapper.properties rename to demo/gradle/wrapper/gradle-wrapper.properties diff --git a/bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt b/demo/src/main/kotlin/Main.kt similarity index 100% rename from bindings/bdk-kotlin/demo/src/main/kotlin/Main.kt rename to demo/src/main/kotlin/Main.kt diff --git a/bindings/bdk-kotlin/gradle.properties b/gradle.properties similarity index 100% rename from bindings/bdk-kotlin/gradle.properties rename to gradle.properties diff --git a/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.jar rename to gradle/wrapper/gradle-wrapper.jar diff --git a/bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from bindings/bdk-kotlin/gradle/wrapper/gradle-wrapper.properties rename to gradle/wrapper/gradle-wrapper.properties diff --git a/bindings/bdk-kotlin/gradlew b/gradlew similarity index 100% rename from bindings/bdk-kotlin/gradlew rename to gradlew diff --git a/bindings/bdk-kotlin/gradlew.bat b/gradlew.bat similarity index 96% rename from bindings/bdk-kotlin/gradlew.bat rename to gradlew.bat index ac1b06f..107acd3 100644 --- a/bindings/bdk-kotlin/gradlew.bat +++ b/gradlew.bat @@ -1,89 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/jvm/build.gradle similarity index 100% rename from bindings/bdk-kotlin/jvm/build.gradle rename to jvm/build.gradle diff --git a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt similarity index 100% rename from bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt rename to jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt diff --git a/bindings/bdk-kotlin/settings.gradle b/settings.gradle similarity index 100% rename from bindings/bdk-kotlin/settings.gradle rename to settings.gradle diff --git a/src/bdk.udl b/src/bdk.udl deleted file mode 100644 index 0597368..0000000 --- a/src/bdk.udl +++ /dev/null @@ -1,167 +0,0 @@ -namespace bdk { - [Throws=BdkError] - ExtendedKeyInfo generate_extended_key(Network network, MnemonicType mnemonic_type, string? password); - [Throws=BdkError] - ExtendedKeyInfo restore_extended_key(Network network, string mnemonic, string? password); -}; - -[Error] -enum BdkError { - "InvalidU32Bytes", - "Generic", - "ScriptDoesntHaveAddressForm", - "NoRecipients", - "NoUtxosSelected", - "OutputBelowDustLimit", - "InsufficientFunds", - "BnBTotalTriesExceeded", - "BnBNoExactMatch", - "UnknownUtxo", - "TransactionNotFound", - "TransactionConfirmed", - "IrreplaceableTransaction", - "FeeRateTooLow", - "FeeTooLow", - "FeeRateUnavailable", - "MissingKeyOrigin", - "Key", - "ChecksumMismatch", - "SpendingPolicyRequired", - "InvalidPolicyPathError", - "Signer", - "InvalidNetwork", - "InvalidProgressValue", - "ProgressUpdateError", - "InvalidOutpoint", - "Descriptor", - "AddressValidator", - "Encode", - "Miniscript", - "Bip32", - "Secp256k1", - "Json", - "Hex", - "Psbt", - "PsbtParse", - "Electrum", - "Esplora", - "Sled", -}; - -enum Network { - "Bitcoin", - "Testnet", - "Signet", - "Regtest", -}; - -dictionary SledDbConfiguration { - string path; - string tree_name; -}; - -[Enum] -interface DatabaseConfig { - Memory(string junk); - Sled(SledDbConfiguration config); -}; - -dictionary TransactionDetails { - u64? fees; - u64 received; - u64 sent; - string txid; -}; - -dictionary Confirmation { - u32 height; - u64 timestamp; -}; - -[Enum] -interface Transaction { - Unconfirmed(TransactionDetails details); - Confirmed(TransactionDetails details, Confirmation confirmation); -}; - -interface OfflineWallet { - [Throws=BdkError] - constructor(string descriptor, Network network, DatabaseConfig database_config); - - // OfflineWalletOperations - string get_new_address(); - string get_last_unused_address(); - [Throws=BdkError] - u64 get_balance(); - [Throws=BdkError] - void sign([ByRef] PartiallySignedBitcoinTransaction psbt); - [Throws=BdkError] - sequence get_transactions(); -}; - -dictionary ElectrumConfig { - string url; - string? socks5; - u8 retry; - u8? timeout; - u64 stop_gap; -}; - -dictionary EsploraConfig { - string base_url; - string? proxy; - u64 timeout_read; - u64 timeout_write; - u64 stop_gap; -}; - -[Enum] -interface BlockchainConfig { - Electrum(ElectrumConfig config); - Esplora(EsploraConfig config); -}; - -callback interface BdkProgress { - void update(f32 progress, string? message); -}; - -interface OnlineWallet { - [Throws=BdkError] - constructor(string descriptor, string? change_descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); - - // OfflineWalletOperations - string get_new_address(); - string get_last_unused_address(); - [Throws=BdkError] - u64 get_balance(); - [Throws=BdkError] - void sign([ByRef] PartiallySignedBitcoinTransaction psbt); - [Throws=BdkError] - sequence get_transactions(); - - // OnlineWalletInterface - Network get_network(); - [Throws=BdkError] - void sync(BdkProgress progress_update, u32? max_address_param); - [Throws=BdkError] - Transaction broadcast([ByRef] PartiallySignedBitcoinTransaction psbt); -}; - -interface PartiallySignedBitcoinTransaction { - [Throws=BdkError] - constructor([ByRef] OnlineWallet wallet, string recipient, u64 amount, float? fee_rate); -}; - -dictionary ExtendedKeyInfo { - string mnemonic; - string xprv; - string fingerprint; -}; - -enum MnemonicType { - "Words12", - "Words15", - "Words18", - "Words21", - "Words24", -}; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index d88d7d6..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,340 +0,0 @@ -use bdk::bitcoin::secp256k1::Secp256k1; -use bdk::bitcoin::util::psbt::PartiallySignedTransaction; -use bdk::bitcoin::{Address, Network}; -use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; -use bdk::blockchain::Progress; -use bdk::blockchain::{ - electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, ConfigurableBlockchain, -}; -use bdk::database::any::{AnyDatabase, SledDbConfiguration}; -use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase}; -use bdk::keys::bip39::{Language, Mnemonic, MnemonicType}; -use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey}; -use bdk::miniscript::BareCtx; -use bdk::wallet::AddressIndex; -use bdk::{ConfirmationTime, Error, FeeRate, SignOptions, Wallet}; -use std::convert::TryFrom; -use std::str::FromStr; -use std::sync::{Mutex, MutexGuard}; - -uniffi_macros::include_scaffolding!("bdk"); - -type BdkError = Error; - -pub enum DatabaseConfig { - Memory { junk: String }, - Sled { config: SledDbConfiguration }, -} - -pub struct ElectrumConfig { - pub url: String, - pub socks5: Option, - pub retry: u8, - pub timeout: Option, - pub stop_gap: u64, -} - -pub struct EsploraConfig { - pub base_url: String, - pub proxy: Option, - pub timeout_read: u64, - pub timeout_write: u64, - pub stop_gap: u64, -} - -pub enum BlockchainConfig { - Electrum { config: ElectrumConfig }, - Esplora { config: EsploraConfig }, -} - -trait WalletHolder { - fn get_wallet(&self) -> MutexGuard>; -} - -struct OfflineWallet { - wallet: Mutex>, -} - -impl WalletHolder<()> for OfflineWallet { - fn get_wallet(&self) -> MutexGuard> { - self.wallet.lock().unwrap() - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct TransactionDetails { - pub fees: Option, - pub received: u64, - pub sent: u64, - pub txid: String, -} - -type Confirmation = ConfirmationTime; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Transaction { - Unconfirmed { - details: TransactionDetails, - }, - Confirmed { - details: TransactionDetails, - confirmation: Confirmation, - }, -} - -impl From<&bdk::TransactionDetails> for TransactionDetails { - fn from(x: &bdk::TransactionDetails) -> TransactionDetails { - TransactionDetails { - fees: x.fee, - txid: x.txid.to_string(), - received: x.received, - sent: x.sent, - } - } -} - -impl From<&bdk::TransactionDetails> for Transaction { - fn from(x: &bdk::TransactionDetails) -> Transaction { - match x.confirmation_time.clone() { - Some(confirmation) => Transaction::Confirmed { - details: TransactionDetails::from(x), - confirmation, - }, - None => Transaction::Unconfirmed { - details: TransactionDetails::from(x), - }, - } - } -} - -trait OfflineWalletOperations: WalletHolder { - fn get_new_address(&self) -> String { - self.get_wallet() - .get_address(AddressIndex::New) - .unwrap() - .address - .to_string() - } - - fn get_last_unused_address(&self) -> String { - self.get_wallet() - .get_address(AddressIndex::LastUnused) - .unwrap() - .address - .to_string() - } - - fn get_balance(&self) -> Result { - self.get_wallet().get_balance() - } - - fn sign<'a>(&self, psbt: &'a PartiallySignedBitcoinTransaction) -> Result<(), Error> { - let mut psbt = psbt.internal.lock().unwrap(); - let finalized = self.get_wallet().sign(&mut psbt, SignOptions::default())?; - match finalized { - true => Ok(()), - false => Err(BdkError::Generic(format!( - "transaction signing not finalized {:?}", - psbt - ))), - } - } - - fn get_transactions(&self) -> Result, Error> { - let transactions = self.get_wallet().list_transactions(true)?; - Ok(transactions.iter().map(Transaction::from).collect()) - } -} - -impl OfflineWallet { - fn new( - descriptor: String, - network: Network, - database_config: DatabaseConfig, - ) -> Result { - let any_database_config = match database_config { - DatabaseConfig::Memory { .. } => AnyDatabaseConfig::Memory(()), - DatabaseConfig::Sled { config } => AnyDatabaseConfig::Sled(config), - }; - let database = AnyDatabase::from_config(&any_database_config)?; - let wallet = Mutex::new(Wallet::new_offline(&descriptor, None, network, database)?); - Ok(OfflineWallet { wallet }) - } -} - -impl OfflineWalletOperations<()> for OfflineWallet {} - -struct OnlineWallet { - wallet: Mutex>, -} - -pub trait BdkProgress: Send + Sync { - fn update(&self, progress: f32, message: Option); -} - -struct BdkProgressHolder { - progress_update: Box, -} - -impl Progress for BdkProgressHolder { - fn update(&self, progress: f32, message: Option) -> Result<(), Error> { - self.progress_update.update(progress, message); - Ok(()) - } -} - -struct PartiallySignedBitcoinTransaction { - internal: Mutex, - details: bdk::TransactionDetails, -} - -impl PartiallySignedBitcoinTransaction { - fn new( - online_wallet: &OnlineWallet, - recipient: String, - amount: u64, - fee_rate: Option, // satoshis per vbyte - ) -> Result { - let wallet = online_wallet.get_wallet(); - match Address::from_str(&recipient) { - Ok(address) => { - let (psbt, details) = { - let mut builder = wallet.build_tx(); - builder.add_recipient(address.script_pubkey(), amount); - if let Some(sat_per_vb) = fee_rate { - builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb)); - } - builder.finish()? - }; - Ok(PartiallySignedBitcoinTransaction { - internal: Mutex::new(psbt), - details, - }) - } - Err(..) => Err(BdkError::Generic( - "failed to read wallet address".to_string(), - )), - } - } -} - -impl OnlineWallet { - fn new( - descriptor: String, - change_descriptor: Option, - network: Network, - database_config: DatabaseConfig, - blockchain_config: BlockchainConfig, - ) -> Result { - let any_database_config = match database_config { - DatabaseConfig::Memory { .. } => AnyDatabaseConfig::Memory(()), - DatabaseConfig::Sled { config } => AnyDatabaseConfig::Sled(config), - }; - let any_blockchain_config = match blockchain_config { - BlockchainConfig::Electrum { config } => { - AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig { - retry: config.retry, - socks5: config.socks5, - timeout: config.timeout, - url: config.url, - stop_gap: usize::try_from(config.stop_gap).unwrap(), - }) - } - BlockchainConfig::Esplora { config } => { - AnyBlockchainConfig::Esplora(EsploraBlockchainConfig { - base_url: config.base_url, - proxy: config.proxy, - timeout_read: config.timeout_read, - timeout_write: config.timeout_write, - stop_gap: usize::try_from(config.stop_gap).unwrap(), - }) - } - }; - let database = AnyDatabase::from_config(&any_database_config)?; - let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?; - let wallet = Mutex::new(Wallet::new( - &descriptor, - change_descriptor.to_owned().as_ref(), - network, - database, - blockchain, - )?); - Ok(OnlineWallet { wallet }) - } - - fn get_network(&self) -> Network { - self.wallet.lock().unwrap().network() - } - - fn sync( - &self, - progress_update: Box, - max_address_param: Option, - ) -> Result<(), BdkError> { - progress_update.update(21.0, Some("message".to_string())); - self.wallet - .lock() - .unwrap() - .sync(BdkProgressHolder { progress_update }, max_address_param) - } - - fn broadcast<'a>( - &self, - psbt: &'a PartiallySignedBitcoinTransaction, - ) -> Result { - let tx = psbt.internal.lock().unwrap().clone().extract_tx(); - self.get_wallet().broadcast(tx)?; - Ok(Transaction::from(&psbt.details)) - } -} - -impl WalletHolder for OnlineWallet { - fn get_wallet(&self) -> MutexGuard> { - self.wallet.lock().unwrap() - } -} - -impl OfflineWalletOperations for OnlineWallet {} - -pub struct ExtendedKeyInfo { - mnemonic: String, - xprv: String, - fingerprint: String, -} - -fn generate_extended_key( - network: Network, - mnemonic_type: MnemonicType, - password: Option, -) -> Result { - let mnemonic: GeneratedKey<_, BareCtx> = - Mnemonic::generate((mnemonic_type, Language::English)).unwrap(); - let mnemonic = mnemonic.into_key(); - let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?; - let xprv = xkey.into_xprv(network).unwrap(); - let fingerprint = xprv.fingerprint(&Secp256k1::new()); - Ok(ExtendedKeyInfo { - mnemonic: mnemonic.to_string(), - xprv: xprv.to_string(), - fingerprint: fingerprint.to_string(), - }) -} - -fn restore_extended_key( - network: Network, - mnemonic: String, - password: Option, -) -> Result { - let mnemonic = Mnemonic::from_phrase(mnemonic.as_ref(), Language::English).unwrap(); - let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?; - let xprv = xkey.into_xprv(network).unwrap(); - let fingerprint = xprv.fingerprint(&Secp256k1::new()); - Ok(ExtendedKeyInfo { - mnemonic: mnemonic.to_string(), - xprv: xprv.to_string(), - fingerprint: fingerprint.to_string(), - }) -} - -uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send); -uniffi::deps::static_assertions::assert_impl_all!(OnlineWallet: Sync, Send); diff --git a/test.sh b/test.sh deleted file mode 100755 index 288947c..0000000 --- a/test.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -# functions - -## help -help() -{ - # Display Help - echo "Test bdk-uniffi and related libraries." - echo - echo "Syntax: build [-a|h|k]" - echo "options:" - echo "-a Android connected device tests." - echo "-h Print this Help." - echo "-k Kotlin tests." - echo -} - -test_kotlin() { - (cd bindings/bdk-kotlin && ./gradlew :jvm:test -Djna.debug_load=true) -} - -test_android() { - (cd bindings/bdk-kotlin && ./gradlew :android:connectedDebugAndroidTest) -} - -if [ $1 = "-h" ] -then - help -else - cargo test - - # optional tests - while [ -n "$1" ]; do # while loop starts - case "$1" in - -a) test_android ;; - -h) help ;; - -k) test_kotlin ;; - *) echo "Option $1 not recognized" ;; - esac - shift - done -fi diff --git a/uniffi.toml b/uniffi.toml deleted file mode 100644 index 767e032..0000000 --- a/uniffi.toml +++ /dev/null @@ -1,12 +0,0 @@ -[bindings.kotlin] -package_name = "org.bitcoindevkit" -cdylib_name = "bdkffi" - -[bindings.python] -cdylib_name = "bdkffi" - -[bindings.ruby] -cdylib_name = "bdkffi" - -[bindings.swift] -cdylib_name = "bdkffi" From d0be3bd6da026267044aeeb430bd65bce7bd8e07 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 13 Dec 2021 23:00:26 -0800 Subject: [PATCH 147/272] Bump version to 0.2.0 --- android/build.gradle | 2 +- jvm/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index bdbf9a9..3d68c8d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -47,7 +47,7 @@ afterEvaluate { // You can then customize attributes of the publication as shown below. groupId = 'org.bitcoindevkit' artifactId = 'bdk-android' - version = '0.1.3-dev' + version = '0.2.0' // Applies the component for the release build variant. from components.release diff --git a/jvm/build.gradle b/jvm/build.gradle index c1185ef..9354102 100644 --- a/jvm/build.gradle +++ b/jvm/build.gradle @@ -36,7 +36,7 @@ afterEvaluate { release(MavenPublication) { groupId = 'org.bitcoindevkit' artifactId = 'bdk-jvm' - version = '0.1.3-dev' + version = '0.2.0' from components.java From e409856d6214eff8446a58641cdfbcb41922f101 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 14 Dec 2021 08:24:41 -0800 Subject: [PATCH 148/272] Update README.md --- README.md | 160 +++++++++++++++++++++++++----------------------------- 1 file changed, 73 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 6fdb848..c0e4388 100644 --- a/README.md +++ b/README.md @@ -1,112 +1,98 @@ -# Foreign language bindings for BDK (bdk-ffi) +# bdk-kotlin -This repository contains source code for generating foreign language bindings -for the rust library bdk for the Bitcoin Dev Kit (BDK) project. +This project builds .jar and .aar packages for the `jvm` and `android` platforms that provide +[Kotlin] language bindings for the [`bdk`] library. The Kotlin language bindings are created by the +[`bdk-ffi`] project which is included as a git submodule of this repository. -## Supported target languages and platforms +## How to Use -| Language | Platform | Status | -| --- | --- | --- | -| Kotlin | JVM | WIP | -| Kotlin | Android | WIP | -| Swift | iOS | WIP | +To use the Kotlin language bindings for [`bdk`] in your `jvm` or `android` project add the +following to your gradle dependencies: +```groovy +repositories { + mavenCentral() +} +dependencies { + + // for jvm + implementation 'org.bitcoindevkit:bdk-jvm:0.2.0' + // OR for android + implementation 'org.bitcoindevkit:bdk-android:0.2.0' + +} -## Getting Started (User) +``` -If you just want to consume the language bindings: +You may then import and use the `org.bitcoindevkit` library in your Kotlin code. For example: -### Kotlin (JVM) +```kotlin +import org.bitcoindevkit.* -Just add the dependency `org.bitcoindevkit:bdk-jvm:0.1.1`. The package is `org.bitcoindevkit.bdk`. +// ... -### Kotlin (Android) +val descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" +val db = DatabaseConfig.Memory("") -Just add the dependency `org.bitcoindevkit:bdk-android:0.1.1`. The package is `org.bitcoindevkit.bdk`. +val client = + BlockchainConfig.Electrum( + ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u) + ) +val wallet = OnlineWallet(descriptor, null, Network.TESTNET, db, client) +val newAddress = wallet.getNewAddress() +``` -## Getting Started (Developer) - -This project uses rust. A basic knowledge of the rust ecosystem is helpful. - -### General -1. Install `uniffi-bindgen` - ```sh - cargo install uniffi_bindgen - ``` -1. See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) for more info - -### Kotlin Bindings for JVM (OSX / Linux) +### How to build 1. Install required targets ```sh - rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu - ``` -1. Build kotlin (JVM) bindings - ```sh - ./build.sh -k - ``` -1. Generated kotlin bindings are available at `/bindings/bdk-kotlin/` -1. A demo app is available at `/bindings/bdk-kotlin/demo/`. It uses stdin for -inputs and can be run from gradle. - ```sh - cd bindings/bdk-kotlin - ./gradlew :demo:run - ``` - -### Kotlin bindings for Android - -1. Install required targets - ```sh - rustup target add x86_64-linux-android aarch64-linux-android - armv7-linux-androideabi i686-linux-android + rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android ``` 1. Install Android SDK and Build-Tools for API level 30+ -1. Setup `$ANDROID_NDK_HOME` and `$ANDROID_SDK_ROOT` path variables (which are -required by the build scripts) -1. Build kotlin (Android) bindings - ```sh - ./build.sh -a +1. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_HOME` path variables (which are required by the + build scripts), for example: + ```shell + export ANDROID_SDK_ROOT=~/Android/Sdk + export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/21.3.6528147 ``` -2. A demo android app is available at [notmandatory/bdk-sample-app](https://github.com/notmandatory/bitcoindevkit-android-sample-app/tree/upgrade-to-bdk-ffi/) - -### Swift bindings for iOS - -1. Install the latest version of xcode, download and install the advanced tools. -1. Ensure Swift is installed -1. Install required targets +1. Build kotlin bindings ```sh - rustup target add aarch64-apple-ios x86_64-apple-ios + ./build.sh ``` -1. Build swift (iOS) bindings - ```sh - ./build.sh -s - ``` -1. Example iOS app can be found in `/examples/iOS` which can be run by xcode. -## Notes +### How to publish to your local maven repo -### Adding new structs and functions +1. Set your `~/.gradle/gradle.properties` signing key values + ```properties + signing.gnupg.keyName= + signing.gnupg.passphrase= + ``` +1. Publish + ```shell + ./gradlew :jvm:publishReleasePublicationToMavenLocal + ./gradlew :android:publishReleasePublicationToMavenLocal + ``` -See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) +### How to publish to maven central (project maintainers only) -#### For pass by value objects +1. Set your `~/.gradle/gradle.properties` signing key values and SONATYPE login + ```properties + signing.gnupg.keyName= + signing.gnupg.passphrase= + + ossrhUserName= + ossrhPassword= + ``` +1. Publish + ```shell + ./gradlew :jvm:publishToSonatype closeAndReleaseSonatypeStagingRepository + ./gradlew :android:publishToSonatype closeAndReleaseSonatypeStagingRepository + ``` -1. create new rust struct with only fields that are supported UniFFI types -1. update mapping `bdk.udl` file with new `dictionary` + -#### For pass by reference values - -1. create wrapper rust struct/impl with only fields that are `Sync + Send` -1. update mapping `bdk.udl` file with new `interface` - -## Goals - -1. Language bindings should feel idiomatic in target languages/platforms -1. Adding new targets should be easy -1. Getting up and running should be easy -1. Contributing should be easy -1. Get it right, then automate - -## Thanks - -This project is made possible thanks to the wonderful work on [mozilla/uniffi-rs](https://github.com/mozilla/uniffi-rs) +[Kotlin]: https://kotlinlang.org/ +[Android Studio]: https://developer.android.com/studio/ +[`bdk`]: https://github.com/bitcoindevkit/bdk +[`bdk-ffi`]: https://github.com/bitcoindevkit/bdk-ffi +["Getting Started (Developer)"]: https://github.com/bitcoindevkit/bdk-ffi#getting-started-developer From e4a7e4efa1145a4139557c88fa56db9de187ca34 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 17 Dec 2021 12:15:07 -0800 Subject: [PATCH 149/272] remove demo/gradle --- demo/gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 0 bytes demo/gradle/wrapper/gradle-wrapper.properties | 5 ----- 2 files changed, 5 deletions(-) delete mode 100644 demo/gradle/wrapper/gradle-wrapper.jar delete mode 100644 demo/gradle/wrapper/gradle-wrapper.properties diff --git a/demo/gradle/wrapper/gradle-wrapper.jar b/demo/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/demo/gradle/wrapper/gradle-wrapper.properties b/demo/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 69a9715..0000000 --- a/demo/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists From cd55d01f72945a1c14b21022e431785f1aa52259 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 18 Dec 2021 10:45:08 -0800 Subject: [PATCH 150/272] Upgrade gradle and sdk and dependency versions --- README.md | 7 ++++--- android/build.gradle | 19 +++++++++---------- build.gradle | 13 ++----------- gradle/wrapper/gradle-wrapper.properties | 2 +- jvm/build.gradle | 6 +++--- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index c0e4388..32789eb 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,11 @@ val newAddress = wallet.getNewAddress() ``` 1. Publish ```shell - ./gradlew :jvm:publishReleasePublicationToMavenLocal - ./gradlew :android:publishReleasePublicationToMavenLocal + ./gradlew :jvm:publishToMavenLocal + ./gradlew :android:publishToMavenLocal ``` -### How to publish to maven central (project maintainers only) +### How to publish to maven central with [Gradle Nexus Publish Plugin] (project maintainers only) 1. Set your `~/.gradle/gradle.properties` signing key values and SONATYPE login ```properties @@ -96,3 +96,4 @@ val newAddress = wallet.getNewAddress() [`bdk`]: https://github.com/bitcoindevkit/bdk [`bdk-ffi`]: https://github.com/bitcoindevkit/bdk-ffi ["Getting Started (Developer)"]: https://github.com/bitcoindevkit/bdk-ffi#getting-started-developer +[Gradle Nexus Publish Plugin]: https://github.com/gradle-nexus/publish-plugin diff --git a/android/build.gradle b/android/build.gradle index 3d68c8d..eddc329 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,11 +4,11 @@ apply plugin: 'maven-publish' apply plugin: 'signing' android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 1 versionName "1.0" @@ -27,13 +27,13 @@ android { dependencies { implementation 'net.java.dev.jna:jna:5.8.0@aar' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.3.0' - implementation 'androidx.core:core-ktx:1.5.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'androidx.core:core-ktx:1.7.0' api "org.slf4j:slf4j-api:1.7.30" androidTestImplementation 'com.github.tony19:logback-android:2.0.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' } @@ -43,14 +43,13 @@ afterEvaluate { publications { // Creates a Maven publication called "release". release(MavenPublication) { + // Applies the component for the release build variant. + from components.release // You can then customize attributes of the publication as shown below. groupId = 'org.bitcoindevkit' artifactId = 'bdk-android' - version = '0.2.0' - - // Applies the component for the release build variant. - from components.release + version = '0.2.2' pom { name = 'bdk-android' diff --git a/build.gradle b/build.gradle index 5946db4..4fa13db 100644 --- a/build.gradle +++ b/build.gradle @@ -1,30 +1,21 @@ buildscript { - ext.kotlin_version = '1.5.10' + ext.kotlin_version = '1.6.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.android.tools.build:gradle:7.0.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } plugins { - id "java-library" id "signing" id "maven-publish" id "io.github.gradle-nexus.publish-plugin" version "1.1.0" } -publishing { - publications { - mavenJava(MavenPublication) { - from(components.java) - } - } -} - signing { def signingKey = findProperty("signingKey") def signingPassword = findProperty("signingPassword") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ffed3a2..d2880ba 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/jvm/build.gradle b/jvm/build.gradle index 9354102..c0a8c15 100644 --- a/jvm/build.gradle +++ b/jvm/build.gradle @@ -34,11 +34,11 @@ afterEvaluate { publications { release(MavenPublication) { + from components.java + groupId = 'org.bitcoindevkit' artifactId = 'bdk-jvm' - version = '0.2.0' - - from components.java + version = '0.2.2' pom { name = 'bdk-jvm' From 67ecd89d5fd893b35d58e6b4fb5cf2418d7a5fa7 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 21 Dec 2021 13:11:57 -0800 Subject: [PATCH 151/272] Update gradle compilerArgs to use -Xopt-in --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4fa13db..00e81bf 100644 --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,7 @@ allprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { freeCompilerArgs += [ - "-Xuse-experimental=kotlin.ExperimentalUnsignedTypes", + "-Xopt-in=kotlin.ExperimentalUnsignedTypes", ] } } From 7ab48613c3ed7d7d32a746cae79fd757d510b6a4 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Mon, 24 Jan 2022 18:19:17 +0000 Subject: [PATCH 152/272] Add a note on NDK version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 32789eb..0e99aaa 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ val newAddress = wallet.getNewAddress() ``` 1. Install Android SDK and Build-Tools for API level 30+ 1. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_HOME` path variables (which are required by the - build scripts), for example: + build scripts), for example (NDK major version 21 is required): ```shell export ANDROID_SDK_ROOT=~/Android/Sdk - export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/21.3.6528147 + export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/21. ``` 1. Build kotlin bindings ```sh From 6967ee0acebad36eef7924fd9423115967a00ee2 Mon Sep 17 00:00:00 2001 From: Sudarsan Balaji Date: Mon, 24 Jan 2022 18:22:53 +0000 Subject: [PATCH 153/272] Add a note on submodule usage --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 0e99aaa..a4bf1d9 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,12 @@ val newAddress = wallet.getNewAddress() ### How to build +1. Clone this repository and init and update it's [`bdk-ffi`] submodule. + ```shell + git clone https://github.com/bitcoindevkit/bdk-kotlin + git submodule update --init + ``` +1. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions. 1. Install required targets ```sh rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android From 2a658d2ff6d9490ee37f07500f8ef11862691684 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 21 Dec 2021 14:24:21 -0800 Subject: [PATCH 154/272] Remove demo --- demo/build.gradle | 36 --------------- demo/src/main/kotlin/Main.kt | 90 ------------------------------------ settings.gradle | 2 +- 3 files changed, 1 insertion(+), 127 deletions(-) delete mode 100644 demo/build.gradle delete mode 100644 demo/src/main/kotlin/Main.kt diff --git a/demo/build.gradle b/demo/build.gradle deleted file mode 100644 index 1fcea5c..0000000 --- a/demo/build.gradle +++ /dev/null @@ -1,36 +0,0 @@ -plugins { - id 'org.jetbrains.kotlin.jvm' - id 'application' -} - -group = 'org.bitcoindevkit.bdk' -version = '1.0-SNAPSHOT' - -repositories { - mavenCentral() -} - -dependencies { - testImplementation 'org.jetbrains.kotlin:kotlin-test' - implementation project(':jvm') -} - -test { - useJUnit() -} - -compileKotlin { - kotlinOptions.jvmTarget = '1.8' -} - -compileTestKotlin { - kotlinOptions.jvmTarget = '1.8' -} - -application { - mainClass.set('MainKt') -} - -run { - standardInput = System.in -} diff --git a/demo/src/main/kotlin/Main.kt b/demo/src/main/kotlin/Main.kt deleted file mode 100644 index 2c6629b..0000000 --- a/demo/src/main/kotlin/Main.kt +++ /dev/null @@ -1,90 +0,0 @@ -import java.util.Optional -import kotlin.ExperimentalUnsignedTypes -import org.bitcoindevkit.* - -class LogProgress : BdkProgress { - override fun update(progress: Float, message: String?) { - println("Syncing..") - } -} - -class NullProgress : BdkProgress { - override fun update(progress: Float, message: String?) {} -} - -fun getTransaction(wallet: OnlineWalletInterface, transactionId: String): Optional { - wallet.sync(NullProgress(), null) - return wallet.getTransactions() - .stream() - .filter({ - when (it) { - is Transaction.Confirmed -> it.details.txid.equals(transactionId) - is Transaction.Unconfirmed -> it.details.txid.equals(transactionId) - } - }) - .findFirst() -} - -@ExperimentalUnsignedTypes -val unconfirmedFirstThenByTimestampDescending = - Comparator { a, b -> - when { - (a is Transaction.Confirmed && b is Transaction.Confirmed) -> { - val comparison = b.confirmation.timestamp.compareTo(a.confirmation.timestamp) - when { - comparison == 0 -> b.details.txid.compareTo(a.details.txid) - else -> comparison - } - } - (a is Transaction.Confirmed && b is Transaction.Unconfirmed) -> 1 - (a is Transaction.Unconfirmed && b is Transaction.Confirmed) -> -1 - else -> 0 - } - } - -@ExperimentalUnsignedTypes -fun main(args: Array) { - val network = Network.TESTNET - val mnemonicType = MnemonicType.WORDS12 - val password: String? = null - println("Generating key...") - val extendedKey = generateExtendedKey(network, mnemonicType, password) - println("generated key: $extendedKey") - println("Attempting restore extended key...") - val restoredKey = restoreExtendedKey(network, extendedKey.mnemonic, password) - println("restored key: $restoredKey") - println("Configuring an in-memory wallet on electrum..") - val descriptor = "wpkh(tprv8ZgxMBicQKsPeSitUfdxhsVaf4BXAASVAbHypn2jnPcjmQZvqZYkeqx7EHQTWvdubTSDa5ben7zHC7sUsx4d8tbTvWdUtHzR8uhHg2CW7MT/*)" - val amount = 1000uL - val recipient = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" - val db = DatabaseConfig.Memory("") - val client = - BlockchainConfig.Electrum( - ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u) - ) - val wallet = OnlineWallet(descriptor, null, network, db, client) - wallet.sync(LogProgress(), null) - println("Initial wallet balance: ${wallet.getBalance()}") - println("Please send $amount satoshis to address: ${wallet.getNewAddress()}") - readLine() - wallet.sync(LogProgress(), null) - println("New wallet balance: ${wallet.getBalance()}") - println("Press Enter to return funds") - readLine() - println("Creating a PSBT with recipient $recipient and amount $amount satoshis...") - val psbt = PartiallySignedBitcoinTransaction(wallet, recipient, amount, null) - println("Signing the transaction...") - wallet.sign(psbt) - println("Broadcasting the signed transaction...") - val transaction = wallet.broadcast(psbt) - println("Broadcasted transaction $transaction") - val take = 5 - println("Listing latest $take transactions...") - wallet - .getTransactions() - .sortedWith(unconfirmedFirstThenByTimestampDescending) - .take(take) - .forEach { println(it) } - println("Final wallet balance: ${wallet.getBalance()}") -} - diff --git a/settings.gradle b/settings.gradle index 9d76d9e..024cf9f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ rootProject.name = 'bdk-kotlin' -include ':jvm',':demo',':android' +include ':jvm',':android' From 9bb629d0a8138ee38a85486b9cbf177ca6e1070f Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 21 Dec 2021 14:25:15 -0800 Subject: [PATCH 155/272] Update README.md with links to padawan and tatooine --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a4bf1d9..232208e 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,17 @@ val wallet = OnlineWallet(descriptor, null, Network.TESTNET, db, client) val newAddress = wallet.getNewAddress() ``` -### How to build +### Example Projects + +#### `bdk-android` + +* [padawan-wallet](https://github.com/thunderbiscuit/padawan-wallet) + +#### `bdk-jvm` + +* [tatooine](https://github.com/thunderbiscuit/tatooine) + +## How to build 1. Clone this repository and init and update it's [`bdk-ffi`] submodule. ```shell @@ -66,7 +76,9 @@ val newAddress = wallet.getNewAddress() ./build.sh ``` -### How to publish to your local maven repo +## How to publish + +### Publish to your local maven repo 1. Set your `~/.gradle/gradle.properties` signing key values ```properties @@ -79,7 +91,7 @@ val newAddress = wallet.getNewAddress() ./gradlew :android:publishToMavenLocal ``` -### How to publish to maven central with [Gradle Nexus Publish Plugin] (project maintainers only) +### Publish to maven central with [Gradle Nexus Publish Plugin] (project maintainers only) 1. Set your `~/.gradle/gradle.properties` signing key values and SONATYPE login ```properties @@ -95,8 +107,6 @@ val newAddress = wallet.getNewAddress() ./gradlew :android:publishToSonatype closeAndReleaseSonatypeStagingRepository ``` - - [Kotlin]: https://kotlinlang.org/ [Android Studio]: https://developer.android.com/studio/ [`bdk`]: https://github.com/bitcoindevkit/bdk From 68dea8b2589179928b7dd09b9fee51f7ab8298dd Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 28 Jan 2022 23:34:33 -0600 Subject: [PATCH 156/272] Update build.sh to support building mac aarch64 (m1) targets, update bdk-ffi --- README.md | 14 +++++++++++--- bdk-ffi | 2 +- build.sh | 13 ++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 232208e..21e96ae 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ repositories { dependencies { // for jvm - implementation 'org.bitcoindevkit:bdk-jvm:0.2.0' + implementation 'org.bitcoindevkit:bdk-jvm:0.3.0' // OR for android - implementation 'org.bitcoindevkit:bdk-android:0.2.0' + implementation 'org.bitcoindevkit:bdk-android:0.3.0' } @@ -52,7 +52,7 @@ val newAddress = wallet.getNewAddress() * [tatooine](https://github.com/thunderbiscuit/tatooine) -## How to build +### How to build 1. Clone this repository and init and update it's [`bdk-ffi`] submodule. ```shell @@ -60,6 +60,10 @@ val newAddress = wallet.getNewAddress() git submodule update --init ``` 1. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions. +1. If building on MacOS install required intel and m1 jvm targets + ```sh + rustup target add x86_64-apple-darwin aarch64-apple-darwin + ``` 1. Install required targets ```sh rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android @@ -75,6 +79,10 @@ val newAddress = wallet.getNewAddress() ```sh ./build.sh ``` +1. Start android emulator and run tests + ```sh + ./gradlew connectedAndroidTest + ``` ## How to publish diff --git a/bdk-ffi b/bdk-ffi index e4d53b5..4cc183b 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit e4d53b5e4b213e484bf4b76a4bf33884dd68f086 +Subproject commit 4cc183bef5eeb62e8b79a445ccca60ddf66e68d4 diff --git a/build.sh b/build.sh index 4ab1677..31a673c 100755 --- a/build.sh +++ b/build.sh @@ -4,20 +4,23 @@ set -eo pipefail echo "Build and test bdk-ffi library for local platform (darwin or linux)" pushd bdk-ffi -cargo fmt -cargo build --release -cargo test - OS=$(uname) echo -n "Copy " case $OS in "Darwin") echo -n "darwin " + # x86_64 (intel) + cargo build --release --target x86_64-apple-darwin mkdir -p ../jvm/src/main/resources/darwin-x86-64 - cp target/release/libbdkffi.dylib ../jvm/src/main/resources/darwin-x86-64 + cp target/x86_64-apple-darwin/release/libbdkffi.dylib ../jvm/src/main/resources/darwin-x86-64 + # aarch64 (m1) + cargo build --release --target aarch64-apple-darwin + mkdir -p ../jvm/src/main/resources/darwin-arm64 + cp target/aarch64-apple-darwin/release/libbdkffi.dylib ../jvm/src/main/resources/darwin-arm64 ;; "Linux") echo -n "linux " + cargo build --release mkdir -p ../jvm/src/main/resources/linux-x86-64 cp target/release/libbdkffi.so ../jvm/src/main/resources/linux-x86-64 ;; From 7b060f2e7e6c89f9a75d429e85c6be27af336c8b Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 28 Jan 2022 23:35:43 -0600 Subject: [PATCH 157/272] Bump android and jvm versions to 0.3.0 --- android/build.gradle | 2 +- jvm/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index eddc329..5e8131e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -49,7 +49,7 @@ afterEvaluate { // You can then customize attributes of the publication as shown below. groupId = 'org.bitcoindevkit' artifactId = 'bdk-android' - version = '0.2.2' + version = '0.3.0' pom { name = 'bdk-android' diff --git a/jvm/build.gradle b/jvm/build.gradle index c0a8c15..218ca30 100644 --- a/jvm/build.gradle +++ b/jvm/build.gradle @@ -38,7 +38,7 @@ afterEvaluate { groupId = 'org.bitcoindevkit' artifactId = 'bdk-jvm' - version = '0.2.2' + version = '0.3.0' pom { name = 'bdk-jvm' From 438f05a39a894a9578e920875eb2504b48b942b2 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 30 Jan 2022 21:25:25 -0600 Subject: [PATCH 158/272] Bump jvm and android versions to 0.3.1 --- README.md | 4 ++-- android/build.gradle | 2 +- jvm/build.gradle | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 21e96ae..61d82cd 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ repositories { dependencies { // for jvm - implementation 'org.bitcoindevkit:bdk-jvm:0.3.0' + implementation 'org.bitcoindevkit:bdk-jvm:0.3.1' // OR for android - implementation 'org.bitcoindevkit:bdk-android:0.3.0' + implementation 'org.bitcoindevkit:bdk-android:0.3.1' } diff --git a/android/build.gradle b/android/build.gradle index 5e8131e..9a671bc 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -49,7 +49,7 @@ afterEvaluate { // You can then customize attributes of the publication as shown below. groupId = 'org.bitcoindevkit' artifactId = 'bdk-android' - version = '0.3.0' + version = '0.3.1' pom { name = 'bdk-android' diff --git a/jvm/build.gradle b/jvm/build.gradle index 218ca30..c36c3c2 100644 --- a/jvm/build.gradle +++ b/jvm/build.gradle @@ -38,7 +38,7 @@ afterEvaluate { groupId = 'org.bitcoindevkit' artifactId = 'bdk-jvm' - version = '0.3.0' + version = '0.3.1' pom { name = 'bdk-jvm' From f601a17daa88e701003d98f5f2322ca947285b0c Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 3 Feb 2022 19:39:13 -0600 Subject: [PATCH 159/272] Fix jvm darwin-aarch64 jpa resources path --- build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 31a673c..577e26f 100755 --- a/build.sh +++ b/build.sh @@ -15,8 +15,8 @@ case $OS in cp target/x86_64-apple-darwin/release/libbdkffi.dylib ../jvm/src/main/resources/darwin-x86-64 # aarch64 (m1) cargo build --release --target aarch64-apple-darwin - mkdir -p ../jvm/src/main/resources/darwin-arm64 - cp target/aarch64-apple-darwin/release/libbdkffi.dylib ../jvm/src/main/resources/darwin-arm64 + mkdir -p ../jvm/src/main/resources/darwin-aarch64 + cp target/aarch64-apple-darwin/release/libbdkffi.dylib ../jvm/src/main/resources/darwin-aarch64 ;; "Linux") echo -n "linux " From 530031e088c0d1630f668a0941e5db095a3512eb Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 3 Feb 2022 20:02:28 -0600 Subject: [PATCH 160/272] Bump jvm and android versions to 0.3.2 --- README.md | 4 ++-- android/build.gradle | 2 +- jvm/build.gradle | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 61d82cd..34b1829 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ repositories { dependencies { // for jvm - implementation 'org.bitcoindevkit:bdk-jvm:0.3.1' + implementation 'org.bitcoindevkit:bdk-jvm:0.3.2' // OR for android - implementation 'org.bitcoindevkit:bdk-android:0.3.1' + implementation 'org.bitcoindevkit:bdk-android:0.3.2' } diff --git a/android/build.gradle b/android/build.gradle index 9a671bc..1aa8619 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -49,7 +49,7 @@ afterEvaluate { // You can then customize attributes of the publication as shown below. groupId = 'org.bitcoindevkit' artifactId = 'bdk-android' - version = '0.3.1' + version = '0.3.2' pom { name = 'bdk-android' diff --git a/jvm/build.gradle b/jvm/build.gradle index c36c3c2..34b8073 100644 --- a/jvm/build.gradle +++ b/jvm/build.gradle @@ -38,7 +38,7 @@ afterEvaluate { groupId = 'org.bitcoindevkit' artifactId = 'bdk-jvm' - version = '0.3.1' + version = '0.3.2' pom { name = 'bdk-jvm' From 62db84491148842889ebb9fa2a6c4ad31e4923d5 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 3 Feb 2022 14:49:25 -0600 Subject: [PATCH 161/272] Add Github bug template and issue config and PR template --- .github/ISSUE_TEMPLATE/bug_report.yml | 66 +++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 +++ .../pull_request_template.md | 30 +++++++++ 3 files changed, 104 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..e41345a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,66 @@ +name: Bug Report +description: Create a report to help us improve +labels: [ "bug", "triage" ] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: textarea + id: description + attributes: + label: Description + description: A concise description of the bug, what happened? + placeholder: What did you see... + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: How to Reproduce + description: Steps or code to reproduce the behavior. + placeholder: How can we can reproduce it... + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Result + description: What did you expected to happen. + placeholder: Tell us what you were expecting... + validations: + required: true + - type: textarea + id: version + attributes: + label: Version + description: Which release version(s), commit, or branch of code do you see this bug? + placeholder: Tell us which version(s) of code you saw the bug in... + validations: + required: true + - type: checkboxes + id: artifact + attributes: + label: Artifact + description: With which artifact(s) are you seeing this bug? + options: + - label: bdk-jvm + - label: bdk-android + - type: checkboxes + id: platform + attributes: + label: Platform + description: What target platform(s) are you seeing the problem on? + options: + - label: ARM64 Android + - label: 64-bit x86 Android + - label: 32-bit x86 Android + - label: 64-bit x86 Linux (kernel 2.6.32+, glibc 2.11+) + - label: 64-bit x86 macOS (10.7+, Lion+) + - label: ARM64 macOS (11.0+, Big Sur+) + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..26dcdbc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 📝 Official Documentation + url: https://bitcoindevkit.org/getting-started/ + about: Check our documentation for answers to common questions + - name: 💬 Community Chat + url: https://discord.com/invite/dstn4dQ + about: Ask general questions and get community support in real-time diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..6082c70 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,30 @@ + + +### Description + + + +### Notes to the reviewers + + + +### Checklists + +#### All Submissions: + +* [ ] I've signed all my commits +* [ ] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) +* [ ] I ran `cargo fmt` and `cargo clippy` before committing + +#### New Features: + +* [ ] I've added tests for the new feature +* [ ] I've added docs for the new feature +* [ ] I've updated `CHANGELOG.md` + +#### Bugfixes: + +* [ ] This pull request breaks the existing API +* [ ] I've added tests to reproduce the issue which are now passing +* [ ] I'm linking the issue being fixed by this PR From ba51cbf64eb09c5dc71df41ec4dee5938ed875ce Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 4 Feb 2022 11:36:09 -0600 Subject: [PATCH 162/272] Allow creating a blank Github issue --- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 66 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 26dcdbc..9e5f358 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: false +blank_issues_enabled: true contact_links: - name: 📝 Official Documentation url: https://bitcoindevkit.org/getting-started/ diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..7ebf3eb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,66 @@ +name: New Feature +description: Request a new feature +labels: [ "enhancement", "triage" ] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to request and document this new feature! + - type: textarea + id: description + attributes: + label: Description + description: A concise description of the new feature. + placeholder: What new behavior would you like to see... + validations: + required: true + - type: textarea + id: benefits + attributes: + label: How to Reproduce + description: Steps or code to reproduce the behavior. + placeholder: How can we can reproduce it... + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Result + description: What did you expected to happen. + placeholder: Tell us what you were expecting... + validations: + required: true + - type: textarea + id: version + attributes: + label: Version + description: Which release version(s), commit, or branch of code do you see this bug? + placeholder: Tell us which version(s) of code you saw the bug in... + validations: + required: true + - type: checkboxes + id: artifact + attributes: + label: Artifact + description: With which artifact(s) are you seeing this bug? + options: + - label: bdk-jvm + - label: bdk-android + - type: checkboxes + id: platform + attributes: + label: Platform + description: What target platform(s) are you seeing the problem on? + options: + - label: ARM64 Android + - label: 64-bit x86 Android + - label: 32-bit x86 Android + - label: 64-bit x86 Linux (kernel 2.6.32+, glibc 2.11+) + - label: 64-bit x86 macOS (10.7+, Lion+) + - label: ARM64 macOS (11.0+, Big Sur+) + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell From e26d7b9c9c9e1e7b6adc44296900b3fa3dee9c19 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 4 Feb 2022 11:36:42 -0600 Subject: [PATCH 163/272] Add a Github feature request template --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 66 ++++++---------------- 2 files changed, 17 insertions(+), 51 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e41345a..65cdb95 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -19,7 +19,7 @@ body: attributes: label: How to Reproduce description: Steps or code to reproduce the behavior. - placeholder: How can we can reproduce it... + placeholder: How can we reproduce it... validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 7ebf3eb..cd16c23 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,4 +1,4 @@ -name: New Feature +name: Feature Request description: Request a new feature labels: [ "enhancement", "triage" ] body: @@ -6,61 +6,27 @@ body: attributes: value: | Thanks for taking the time to request and document this new feature! + - type: textarea + id: problem + attributes: + label: Problem + description: A concise description of the problem you need this new feature to solve. + placeholder: What is the problem you are trying to solve... + validations: + required: true - type: textarea id: description attributes: label: Description - description: A concise description of the new feature. - placeholder: What new behavior would you like to see... + description: A concise description of the new feature you need. + placeholder: What will this new feature do, how will it work... validations: required: true - type: textarea - id: benefits + id: alternatives attributes: - label: How to Reproduce - description: Steps or code to reproduce the behavior. - placeholder: How can we can reproduce it... + label: Alternatives + description: Describe any other alternatives you considered. + placeholder: Other ways you considered to solve your problem... validations: - required: true - - type: textarea - id: expected - attributes: - label: Expected Result - description: What did you expected to happen. - placeholder: Tell us what you were expecting... - validations: - required: true - - type: textarea - id: version - attributes: - label: Version - description: Which release version(s), commit, or branch of code do you see this bug? - placeholder: Tell us which version(s) of code you saw the bug in... - validations: - required: true - - type: checkboxes - id: artifact - attributes: - label: Artifact - description: With which artifact(s) are you seeing this bug? - options: - - label: bdk-jvm - - label: bdk-android - - type: checkboxes - id: platform - attributes: - label: Platform - description: What target platform(s) are you seeing the problem on? - options: - - label: ARM64 Android - - label: 64-bit x86 Android - - label: 32-bit x86 Android - - label: 64-bit x86 Linux (kernel 2.6.32+, glibc 2.11+) - - label: 64-bit x86 macOS (10.7+, Lion+) - - label: ARM64 macOS (11.0+, Big Sur+) - - type: textarea - id: logs - attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. - render: shell + required: false From c917a7763918a2e1c7b880f885b14b62bbe3ae3c Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 4 Feb 2022 14:26:31 -0600 Subject: [PATCH 164/272] Fix pull request template --- .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md From 9ef075c67e82e123aedcb0599e2a9141ae5f4478 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Mon, 7 Feb 2022 16:30:13 -0500 Subject: [PATCH 165/272] Add Devkit Wallet link --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 232208e..1ddd539 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,12 @@ val newAddress = wallet.getNewAddress() #### `bdk-android` -* [padawan-wallet](https://github.com/thunderbiscuit/padawan-wallet) +* [Devkit Wallet](https://github.com/thunderbiscuit/devkit-wallet) +* [Padawan Wallet](https://github.com/thunderbiscuit/padawan-wallet) #### `bdk-jvm` -* [tatooine](https://github.com/thunderbiscuit/tatooine) +* [Tatooine Faucet](https://github.com/thunderbiscuit/tatooine) ## How to build From 4656cf50b51c681b9b67c995c7e920e56d2efbb6 Mon Sep 17 00:00:00 2001 From: Caio Faustino Date: Sun, 2 Jan 2022 20:25:18 +0100 Subject: [PATCH 166/272] Add step to install uniffi-bindgen on README This step exists in bdk-ffi readme, but is missing in this repo, and build.sh fails without it. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 9e429a7..6dcf971 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,11 @@ val newAddress = wallet.getNewAddress() ```sh rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android ``` +1. Install `uniffi-bindgen` + ```sh + cargo install uniffi_bindgen + ``` + See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) for more info 1. Install Android SDK and Build-Tools for API level 30+ 1. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_HOME` path variables (which are required by the build scripts), for example (NDK major version 21 is required): From bd13eca0f359c4017f7c80c37adc4f2d6003baa5 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 22 Feb 2022 21:48:18 -0800 Subject: [PATCH 167/272] Add uniffi-bindgen version to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6dcf971..3faafaf 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ val newAddress = wallet.getNewAddress() ``` 1. Install `uniffi-bindgen` ```sh - cargo install uniffi_bindgen + cargo install uniffi_bindgen --version 0.16.0 ``` See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) for more info 1. Install Android SDK and Build-Tools for API level 30+ From 4e66758048cb56887e80ee14645e3535e6771b34 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 11 Feb 2022 15:53:45 -0500 Subject: [PATCH 168/272] Migrate project-level Gradle build script to Kotlin DSL --- build.gradle | 51 ----------------------------------------------- build.gradle.kts | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 51 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 00e81bf..0000000 --- a/build.gradle +++ /dev/null @@ -1,51 +0,0 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:7.0.4' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -plugins { - id "signing" - id "maven-publish" - id "io.github.gradle-nexus.publish-plugin" version "1.1.0" -} - -signing { - def signingKey = findProperty("signingKey") - def signingPassword = findProperty("signingPassword") - useInMemoryPgpKeys(signingKey, signingPassword) - sign publishing.publications -} - -nexusPublishing { - packageGroup = "org.bitcoindevkit" - repositories { - sonatype { - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) - - username = project.findProperty("ossrhUsername") - password = project.findProperty("ossrhPassword") - } - } -} - -allprojects { - repositories { - google() - mavenCentral() - } - tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions { - freeCompilerArgs += [ - "-Xopt-in=kotlin.ExperimentalUnsignedTypes", - ] - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..a03245b --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,52 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:7.0.4") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") + } +} + +plugins { + id("signing") + id("maven-publish") + id("io.github.gradle-nexus.publish-plugin") version "1.1.0" +} + +signing { + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications) +} + +// does this need to be defined here? Not sure +// it used to be defined in the nexusPublishing block but is not required +// I think the group ID is defined in the specific publishing blocks in the respective build.gradle.kts +group = "org.bitcoindevkit" + +nexusPublishing { + repositories { + create("sonatype") { + nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + + val ossrhUsername: String? by project + val ossrhPassword: String? by project + username.set(ossrhUsername) + password.set(ossrhPassword) + } + } +} + +allprojects { + repositories { + google() + mavenCentral() + } + tasks.withType().configureEach { + kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.ExperimentalUnsignedTypes" + } +} From 25f5bd26b4757d1073373b4dab87d4680cd1b8c8 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 11 Feb 2022 16:59:24 -0500 Subject: [PATCH 169/272] Migrate android module Gradle build script to Kotlin DSL --- android/build.gradle | 94 ---------------------------------------- android/build.gradle.kts | 89 +++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 94 deletions(-) delete mode 100644 android/build.gradle create mode 100644 android/build.gradle.kts diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 1aa8619..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,94 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'maven-publish' -apply plugin: 'signing' - -android { - compileSdkVersion 31 - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 31 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles 'consumer-rules.pro' - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - implementation 'net.java.dev.jna:jna:5.8.0@aar' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.4.0' - implementation 'androidx.core:core-ktx:1.7.0' - api "org.slf4j:slf4j-api:1.7.30" - - androidTestImplementation 'com.github.tony19:logback-android:2.0.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' -} - -afterEvaluate { - - publishing { - publications { - // Creates a Maven publication called "release". - release(MavenPublication) { - // Applies the component for the release build variant. - from components.release - - // You can then customize attributes of the publication as shown below. - groupId = 'org.bitcoindevkit' - artifactId = 'bdk-android' - version = '0.3.2' - - pom { - name = 'bdk-android' - description = 'Bitcoin Dev Kit Kotlin language bindings.' - url = "https://bitcoindevkit.org" - licenses { - license { - name = "APACHE" - url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE" - } - license { - name = "MIT" - url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT" - } - } - developers { - developer { - id = 'notmandatory' - name = 'Steve Myers' - email = 'notmandatory@noreply.github.org' - } - developer { - id = 'artfuldev' - name = 'Sudarsan Balaji' - email = 'sudarsan.balaji@artfuldev.com' - } - } - scm { - connection = 'scm:git:github.com/bitcoindevkit/bdk-ffi.git' - developerConnection = 'scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git' - url = 'https://github.com/bitcoindevkit/bdk-ffi/tree/master' - } - } - } - } - } -} - -signing { - useGpgCmd() - sign publishing.publications -} diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..625be75 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,89 @@ +plugins { + id("com.android.library") + id("kotlin-android") + id("maven-publish") + id("signing") +} + +android { + compileSdk = 31 + + defaultConfig { + minSdk = 21 + targetSdk = 31 + // versionCode = 1 + // versionName = "v0.2.2" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(file("proguard-android-optimize.txt"), file("proguard-rules.pro")) + } + } +} + +dependencies { + implementation("net.java.dev.jna:jna:5.8.0@aar") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7") + implementation("androidx.appcompat:appcompat:1.4.0") + implementation("androidx.core:core-ktx:1.7.0") + api("org.slf4j:slf4j-api:1.7.30") + + androidTestImplementation("com.github.tony19:logback-android:2.0.0") + androidTestImplementation("androidx.test.ext:junit:1.1.3") + androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1") +} + +afterEvaluate { + publishing { + publications { + create("maven") { + groupId = "org.bitcoindevkit" + artifactId = "bdk-android" + version = "0.3.2" + from(components["release"]) + pom { + name.set("bdk-android") + description.set("Bitcoin Dev Kit Kotlin language bindings.") + url.set("https://bitcoindevkit.org") + licenses { + license { + name.set("APACHE 2.0") + url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE") + } + license { + name.set("MIT") + url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT") + } + } + developers { + developer { + id.set("notmandatory") + name.set("Steve Myers") + email.set("notmandatory@noreply.github.org") + } + developer { + id.set("artfuldev") + name.set("Sudarsan Balaji") + email.set("sudarsan.balaji@artfuldev.com") + } + } + scm { + connection.set("scm:git:github.com/bitcoindevkit/bdk-ffi.git") + developerConnection.set("scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git") + url.set("https://github.com/bitcoindevkit/bdk-ffi/tree/master") + } + } + } + } + } +} + +signing { + useGpgCmd() + sign(publishing.publications) +} From 95ef8eb3de920bff31e2bcfff344efd146bf8de2 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Sat, 12 Feb 2022 10:06:18 -0500 Subject: [PATCH 170/272] Migrate jvm module Gradle build script to Kotlin DSL --- jvm/build.gradle | 83 ---------------------------------------- jvm/build.gradle.kts | 90 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 83 deletions(-) delete mode 100644 jvm/build.gradle create mode 100644 jvm/build.gradle.kts diff --git a/jvm/build.gradle b/jvm/build.gradle deleted file mode 100644 index 34b8073..0000000 --- a/jvm/build.gradle +++ /dev/null @@ -1,83 +0,0 @@ -plugins { - id 'org.jetbrains.kotlin.jvm' - id 'java-library' - id 'maven-publish' - id 'signing' -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - - withJavadocJar() - withSourcesJar() -} - -test { - testLogging { - events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR" - } -} - -dependencies { - implementation platform('org.jetbrains.kotlin:kotlin-bom') - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "net.java.dev.jna:jna:5.8.0" - api "org.slf4j:slf4j-api:1.7.30" - testImplementation "junit:junit:4.13.2" - testImplementation "ch.qos.logback:logback-classic:1.2.3" - testImplementation "ch.qos.logback:logback-core:1.2.3" -} - -afterEvaluate { - publishing { - publications { - - release(MavenPublication) { - from components.java - - groupId = 'org.bitcoindevkit' - artifactId = 'bdk-jvm' - version = '0.3.2' - - pom { - name = 'bdk-jvm' - description = 'Bitcoin Dev Kit Kotlin language bindings.' - url = "https://bitcoindevkit.org" - licenses { - license { - name = "APACHE" - url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE" - } - license { - name = "MIT" - url = "https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT" - } - } - developers { - developer { - id = 'notmandatory' - name = 'Steve Myers' - email = 'notmandatory@noreply.github.org' - } - developer { - id = 'artfuldev' - name = 'Sudarsan Balaji' - email = 'sudarsan.balaji@artfuldev.com' - } - } - scm { - connection = 'scm:git:github.com/bitcoindevkit/bdk-ffi.git' - developerConnection = 'scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git' - url = 'https://github.com/bitcoindevkit/bdk-ffi/tree/master' - } - } - } - } - } -} - -signing { - useGpgCmd() - sign publishing.publications -} diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts new file mode 100644 index 0000000..0538a9d --- /dev/null +++ b/jvm/build.gradle.kts @@ -0,0 +1,90 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat.* +import org.gradle.api.tasks.testing.logging.TestLogEvent.* + +plugins { + id("org.jetbrains.kotlin.jvm") + id("java-library") + id("maven-publish") + id("signing") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + withSourcesJar() +} + +tasks.withType { + useJUnitPlatform() + + testLogging { + events(PASSED, SKIPPED, FAILED, STANDARD_OUT, STANDARD_ERROR) + exceptionFormat = FULL + showExceptions = true + showCauses = true + showStackTraces = true + } +} + +dependencies { + implementation(platform("org.jetbrains.kotlin:kotlin-bom")) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7") + implementation("net.java.dev.jna:jna:5.8.0") + api("org.slf4j:slf4j-api:1.7.30") + testImplementation("junit:junit:4.13.2") + testImplementation("ch.qos.logback:logback-classic:1.2.3") + testImplementation("ch.qos.logback:logback-core:1.2.3") +} + +afterEvaluate { + publishing { + publications { + create("maven") { + + groupId = "org.bitcoindevkit" + artifactId = "bdk-jvm" + version = "0.2.2" + + from(components["java"]) + + pom { + name.set("bdk-jvm") + description.set("Bitcoin Dev Kit Kotlin language bindings.") + url.set("https://bitcoindevkit.org") + licenses { + license { + name.set("APACHE 2.0") + url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE") + } + license { + name.set("MIT") + url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT") + } + } + developers { + developer { + id.set("notmandatory") + name.set("Steve Myers") + email.set("notmandatory@noreply.github.org") + } + developer { + id.set("artfuldev") + name.set("Sudarsan Balaji") + email.set("sudarsan.balaji@artfuldev.com") + } + } + scm { + connection.set("scm:git:github.com/bitcoindevkit/bdk-ffi.git") + developerConnection.set("scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git") + url.set("https://github.com/bitcoindevkit/bdk-ffi/tree/master") + } + } + } + } + } +} + +signing { + useGpgCmd() + sign(publishing.publications) +} From 19e88e3e67496128647080e70205fd915c7a03c1 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 27 Feb 2022 21:51:30 -0800 Subject: [PATCH 171/272] Update bdk-ffi to v0.3.0 --- bdk-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-ffi b/bdk-ffi index 4cc183b..2ac69a1 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 4cc183bef5eeb62e8b79a445ccca60ddf66e68d4 +Subproject commit 2ac69a1bc8567d756eecf63b22058487524259a2 From 6df9a98fb6cddcae703327b74c94323f35e0e2dd Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 27 Feb 2022 22:40:42 -0800 Subject: [PATCH 172/272] Fix tests for bdk-ffi v0.3.0, add psbt serde test --- .../org/bitcoindevkit/AndroidLibTest.kt | 39 +++++++++++++------ .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 39 +++++++++++++------ 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index bdb7f90..530756f 100644 --- a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -30,13 +30,23 @@ class AndroidLibTest { val log: Logger = LoggerFactory.getLogger(AndroidLibTest::class.java) - val desc = + val descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + val databaseConfig = DatabaseConfig.Memory("") + val blockchainConfig = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 100u + ) + ) + @Test fun memoryWalletNewAddress() { - val config = DatabaseConfig.Memory("") - val wallet = OfflineWallet(desc, Network.REGTEST, config) + val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) val address = wallet.getNewAddress() assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") @@ -44,15 +54,14 @@ class AndroidLibTest { @Test(expected = BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { - val config = DatabaseConfig.Memory("") - OfflineWallet("invalid-descriptor", Network.REGTEST, config) + Wallet("invalid-descriptor", null, Network.REGTEST, databaseConfig, blockchainConfig) } @Test fun sledWalletNewAddress() { val testDataDir = getTestDataDir() - val config = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) - val wallet = OfflineWallet(desc, Network.REGTEST, config) + val databaseConfig = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) + val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) val address = wallet.getNewAddress() assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") @@ -61,8 +70,8 @@ class AndroidLibTest { @Test fun onlineWalletInMemory() { - val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum( + val database = DatabaseConfig.Memory("") + val blockchain = BlockchainConfig.Electrum( ElectrumConfig( "ssl://electrum.blockstream.info:60002", null, @@ -71,7 +80,7 @@ class AndroidLibTest { 100u ) ) - val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) + val wallet = Wallet(descriptor, null, Network.TESTNET, database, blockchain) assertNotNull(wallet) val network = wallet.getNetwork() assertEquals(network, Network.TESTNET) @@ -97,10 +106,18 @@ class AndroidLibTest { 100u ) ) - val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) + val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() assertTrue(balance > 0u) } + @Test + fun validPsbtSerde() { + val validSerializedPsbt = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA" + val psbt = PartiallySignedBitcoinTransaction.deserialize(validSerializedPsbt) + val psbtSerialized = psbt.serialize() + assertEquals(psbtSerialized, validSerializedPsbt) + } + } diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index 67beaf0..0cdf9ae 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -24,13 +24,23 @@ class JvmLibTest { val log: Logger = LoggerFactory.getLogger(JvmLibTest::class.java) - val desc = + val descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + val databaseConfig = DatabaseConfig.Memory("") + val blockchainConfig = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 100u + ) + ) + @Test fun memoryWalletNewAddress() { - val config = DatabaseConfig.Memory("") - val wallet = OfflineWallet(desc, Network.REGTEST, config) + val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) val address = wallet.getNewAddress() assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") @@ -38,15 +48,14 @@ class JvmLibTest { @Test(expected = BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { - val config = DatabaseConfig.Memory("") - OfflineWallet("invalid-descriptor", Network.REGTEST, config) + Wallet("invalid-descriptor", null, Network.REGTEST, databaseConfig, blockchainConfig) } @Test fun sledWalletNewAddress() { val testDataDir = getTestDataDir() - val config = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) - val wallet = OfflineWallet(desc, Network.REGTEST, config) + val databaseConfig = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) + val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) val address = wallet.getNewAddress() assertNotNull(address) assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") @@ -55,8 +64,8 @@ class JvmLibTest { @Test fun onlineWalletInMemory() { - val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum( + val database = DatabaseConfig.Memory("") + val blockchain = BlockchainConfig.Electrum( ElectrumConfig( "ssl://electrum.blockstream.info:60002", null, @@ -65,7 +74,7 @@ class JvmLibTest { 100u ) ) - val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) + val wallet = Wallet(descriptor, null, Network.TESTNET, database, blockchain) assertNotNull(wallet) val network = wallet.getNetwork() assertEquals(network, Network.TESTNET) @@ -91,10 +100,18 @@ class JvmLibTest { 100u ) ) - val wallet = OnlineWallet(desc, null, Network.TESTNET, db, client) + val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() assertTrue(balance > 0u) } + @Test + fun validPsbtSerde() { + val validSerializedPsbt = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA" + val psbt = PartiallySignedBitcoinTransaction.deserialize(validSerializedPsbt) + val psbtSerialized = psbt.serialize() + assertEquals(psbtSerialized, validSerializedPsbt) + } + } From 74f6d2bb9e1e98fc6b917ddcb6d9862642d7f018 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Mon, 28 Feb 2022 10:28:44 -0500 Subject: [PATCH 173/272] Remove compiler option for experimental unsigned types --- README.md | 1 + build.gradle.kts | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 3faafaf..a985b29 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ val newAddress = wallet.getNewAddress() * [Tatooine Faucet](https://github.com/thunderbiscuit/tatooine) ### How to build +_Note that Kotlin version `1.6.10` or later is required to build the library._ 1. Clone this repository and init and update it's [`bdk-ffi`] submodule. ```shell diff --git a/build.gradle.kts b/build.gradle.kts index a03245b..6271ac9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,7 +46,4 @@ allprojects { google() mavenCentral() } - tasks.withType().configureEach { - kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.ExperimentalUnsignedTypes" - } } From e7e4b0f48b4b6c558934307c00be4bb50a7d0855 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 28 Feb 2022 16:01:05 -0800 Subject: [PATCH 174/272] Update to pruned bdk-ffi v0.3.0 tag --- bdk-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-ffi b/bdk-ffi index 2ac69a1..d17ea4b 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 2ac69a1bc8567d756eecf63b22058487524259a2 +Subproject commit d17ea4b90c015c9a6cf5d2cf2f77e901d93fd089 From 9a3f9301484f406a4e694c77157f45b82caeab54 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 28 Feb 2022 16:51:18 -0800 Subject: [PATCH 175/272] Update README for bdk-jvm and bdk-android 0.4.0 --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3faafaf..e15050f 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ repositories { dependencies { // for jvm - implementation 'org.bitcoindevkit:bdk-jvm:0.3.2' + implementation 'org.bitcoindevkit:bdk-jvm:0.4.0' // OR for android - implementation 'org.bitcoindevkit:bdk-android:0.3.2' + implementation 'org.bitcoindevkit:bdk-android:0.4.0' } @@ -31,14 +31,16 @@ import org.bitcoindevkit.* // ... -val descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" -val db = DatabaseConfig.Memory("") +val externalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" +val internalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" -val client = +val databaseConfig = DatabaseConfig.Memory("") + +val blockchainConfig = BlockchainConfig.Electrum( ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u) ) -val wallet = OnlineWallet(descriptor, null, Network.TESTNET, db, client) +val wallet = Wallet(externalDescriptor, internalDescriptor, Network.TESTNET, databaseConfig, blockchainConfig) val newAddress = wallet.getNewAddress() ``` From 6195ba089688ccd35e5dc01a802b51dbad8dbdc6 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 28 Feb 2022 16:51:49 -0800 Subject: [PATCH 176/272] Bump version to 0.4.0 --- android/build.gradle | 2 +- jvm/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 1aa8619..58caade 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -49,7 +49,7 @@ afterEvaluate { // You can then customize attributes of the publication as shown below. groupId = 'org.bitcoindevkit' artifactId = 'bdk-android' - version = '0.3.2' + version = '0.4.0' pom { name = 'bdk-android' diff --git a/jvm/build.gradle b/jvm/build.gradle index 34b8073..c106561 100644 --- a/jvm/build.gradle +++ b/jvm/build.gradle @@ -38,7 +38,7 @@ afterEvaluate { groupId = 'org.bitcoindevkit' artifactId = 'bdk-jvm' - version = '0.3.2' + version = '0.4.0' pom { name = 'bdk-jvm' From 5514dc577e5c33f87b20107832d8c339f473c230 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 28 Feb 2022 20:36:52 -0800 Subject: [PATCH 177/272] Bump version to 0.4.1-SNAPSHOT --- android/build.gradle | 2 +- jvm/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 58caade..f34fbb4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -49,7 +49,7 @@ afterEvaluate { // You can then customize attributes of the publication as shown below. groupId = 'org.bitcoindevkit' artifactId = 'bdk-android' - version = '0.4.0' + version = '0.4.1-SNAPSHOT' pom { name = 'bdk-android' diff --git a/jvm/build.gradle b/jvm/build.gradle index c106561..143e15e 100644 --- a/jvm/build.gradle +++ b/jvm/build.gradle @@ -38,7 +38,7 @@ afterEvaluate { groupId = 'org.bitcoindevkit' artifactId = 'bdk-jvm' - version = '0.4.0' + version = '0.4.1-SNAPSHOT' pom { name = 'bdk-jvm' From 933af8c70633b754f0967cdd26c87101d9916303 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 28 Feb 2022 21:11:25 -0800 Subject: [PATCH 178/272] Fix jvm module junit dependency error --- jvm/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index 0538a9d..a246422 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { implementation("net.java.dev.jna:jna:5.8.0") api("org.slf4j:slf4j-api:1.7.30") testImplementation("junit:junit:4.13.2") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2") testImplementation("ch.qos.logback:logback-classic:1.2.3") testImplementation("ch.qos.logback:logback-core:1.2.3") } From a21b69a217d064299a9e5bbdb51529e9561ac933 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 28 Feb 2022 21:30:18 -0800 Subject: [PATCH 179/272] Fix jvm artifact version --- jvm/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index a246422..f6b576e 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -44,7 +44,7 @@ afterEvaluate { groupId = "org.bitcoindevkit" artifactId = "bdk-jvm" - version = "0.2.2" + version = "0.3.2" from(components["java"]) From 9123cbbaefceae8be2f0220bfe86865199985057 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 28 Feb 2022 20:38:28 -0800 Subject: [PATCH 180/272] Remove unused code --- .../kotlin/org/bitcoindevkit/AndroidLibTest.kt | 10 ---------- jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index 530756f..d106707 100644 --- a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -96,16 +96,6 @@ class AndroidLibTest { @Test fun onlineWalletSyncGetBalance() { - val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum( - ElectrumConfig( - "ssl://electrum.blockstream.info:60002", - null, - 5u, - null, - 100u - ) - ) val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index 0cdf9ae..a1786ce 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -90,16 +90,6 @@ class JvmLibTest { @Test fun onlineWalletSyncGetBalance() { - val db = DatabaseConfig.Memory("") - val client = BlockchainConfig.Electrum( - ElectrumConfig( - "ssl://electrum.blockstream.info:60002", - null, - 5u, - null, - 100u - ) - ) val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() From 1ec6d2538e7b9218abb8929e94a0bd9ab35f453f Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 28 Feb 2022 22:14:17 -0800 Subject: [PATCH 181/272] Add license files --- LICENSE | 14 ++++ LICENSE-APACHE | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 16 ++++ 3 files changed, 231 insertions(+) create mode 100644 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9c61848 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +This software is licensed under [Apache 2.0](LICENSE-APACHE) or +[MIT](LICENSE-MIT), at your option. + +Some files retain their own copyright notice, however, for full authorship +information, see version control history. + +Except as otherwise noted in individual files, all files in this repository are +licensed under the Apache License, Version 2.0 or the MIT license , at your option. + +You may not use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of this software or any files in this repository except in +accordance with one or both of these licenses. \ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..9d982a4 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,16 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From b0b44550a1c849526aa69c6da616ad6f8822d037 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 11 Mar 2022 23:35:49 -0600 Subject: [PATCH 182/272] Add sqlite database option --- .../kotlin/org/bitcoindevkit/AndroidLibTest.kt | 11 +++++++++++ bdk-ffi | 2 +- jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt | 11 +++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index d106707..210730c 100644 --- a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -68,6 +68,17 @@ class AndroidLibTest { cleanupTestDataDir(testDataDir) } + @Test + fun sqliteWalletSyncGetBalance() { + val testDataDir = getTestDataDir()+"/bdk-wallet.sqlite" + val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir)) + val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) + wallet.sync(LogProgress(), null) + val balance = wallet.getBalance() + assertTrue(balance > 0u) + cleanupTestDataDir(testDataDir) + } + @Test fun onlineWalletInMemory() { val database = DatabaseConfig.Memory("") diff --git a/bdk-ffi b/bdk-ffi index d17ea4b..12f4784 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit d17ea4b90c015c9a6cf5d2cf2f77e901d93fd089 +Subproject commit 12f4784b85fa2a263d9648dcccd8bac2da44644d diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index a1786ce..ad72563 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -62,6 +62,17 @@ class JvmLibTest { cleanupTestDataDir(testDataDir) } + @Test + fun sqliteWalletSyncGetBalance() { + val testDataDir = getTestDataDir()+"/bdk-wallet.sqlite" + val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir)) + val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) + wallet.sync(LogProgress(), null) + val balance = wallet.getBalance() + assertTrue(balance > 0u) + cleanupTestDataDir(testDataDir) + } + @Test fun onlineWalletInMemory() { val database = DatabaseConfig.Memory("") From a495bd96055c696d80e3d69770c065cbe2a7a51c Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 14 Mar 2022 15:13:49 -0500 Subject: [PATCH 183/272] Update bdk-ffi to v0.4.0 --- bdk-ffi | 2 +- jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bdk-ffi b/bdk-ffi index 12f4784..c6e9a62 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 12f4784b85fa2a263d9648dcccd8bac2da44644d +Subproject commit c6e9a626288f7e91236a4a6db381bcf19b6be6ee diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index ad72563..d414e08 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -27,7 +27,7 @@ class JvmLibTest { val descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val databaseConfig = DatabaseConfig.Memory("") + val databaseConfig = DatabaseConfig.Memory val blockchainConfig = BlockchainConfig.Electrum( ElectrumConfig( "ssl://electrum.blockstream.info:60002", @@ -75,7 +75,7 @@ class JvmLibTest { @Test fun onlineWalletInMemory() { - val database = DatabaseConfig.Memory("") + val database = DatabaseConfig.Memory val blockchain = BlockchainConfig.Electrum( ElectrumConfig( "ssl://electrum.blockstream.info:60002", From a1b4d66f479c083d02598dc8b74a117812a8b488 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 15 Mar 2022 08:59:58 -0500 Subject: [PATCH 184/272] Fix README example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a4f442..01d1283 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ import org.bitcoindevkit.* val externalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" val internalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" -val databaseConfig = DatabaseConfig.Memory("") +val databaseConfig = DatabaseConfig.Memory val blockchainConfig = BlockchainConfig.Electrum( From 4e4d2c64b4edd0c4b432605cbf380b9c09cf1444 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 15 Mar 2022 13:47:08 -0400 Subject: [PATCH 185/272] Add documentation on how to skip signing task for local Maven --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 01d1283..574dd42 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ dependencies { implementation 'org.bitcoindevkit:bdk-android:0.4.0' } - ``` You may then import and use the `org.bitcoindevkit` library in your Kotlin code. For example: @@ -108,6 +107,12 @@ _Note that Kotlin version `1.6.10` or later is required to build the library._ ./gradlew :android:publishToMavenLocal ``` +Note that if you do not have gpg keys set up to sign the publication, the task will fail. If you wish to publish to your local Maven repository for local testing without signing the release, you can do so by excluding the `signMavenPublication` subtask like so: +```shell +./gradlew :jvm:publishToMavenLocal --exclude-task signMavenPublication +./gradlew :android:publishToMavenLocal --exclude-task signMavenPublication +``` + ### Publish to maven central with [Gradle Nexus Publish Plugin] (project maintainers only) 1. Set your `~/.gradle/gradle.properties` signing key values and SONATYPE login From e9111f74c52b47a88cc5d8a98c6a6ccb7d2d172b Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 15 Mar 2022 21:06:07 -0500 Subject: [PATCH 186/272] Update bdk-ffi to 0.4.1 --- bdk-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-ffi b/bdk-ffi index c6e9a62..2f5ac99 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit c6e9a626288f7e91236a4a6db381bcf19b6be6ee +Subproject commit 2f5ac99feeb8350aae0a22e1e55b23d3cb1aaeb0 From dc9ad20d9954fb228bdb7c61aea73510a37aeac2 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 17 Mar 2022 15:46:13 -0500 Subject: [PATCH 187/272] Add javadocs to jvm build --- jvm/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index 6dd37bb..f0cc49b 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -12,6 +12,7 @@ java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 withSourcesJar() + withJavadocJar() } tasks.withType { From bb1e69e73fe27ea207d43c08d6759a46d71ff3d0 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 17 Mar 2022 16:10:23 -0500 Subject: [PATCH 188/272] Increment version to 0.6.0-SNAPSHOT --- README.md | 4 ++-- android/build.gradle.kts | 2 +- jvm/build.gradle.kts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 574dd42..0d7b3cc 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ repositories { dependencies { // for jvm - implementation 'org.bitcoindevkit:bdk-jvm:0.4.0' + implementation 'org.bitcoindevkit:bdk-jvm:' // OR for android - implementation 'org.bitcoindevkit:bdk-android:0.4.0' + implementation 'org.bitcoindevkit:bdk-android:' } ``` diff --git a/android/build.gradle.kts b/android/build.gradle.kts index e28c05e..6c31e9d 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -44,7 +44,7 @@ afterEvaluate { create("maven") { groupId = "org.bitcoindevkit" artifactId = "bdk-android" - version = "0.4.1-SNAPSHOT" + version = "0.6.0-SNAPSHOT" from(components["release"]) pom { name.set("bdk-android") diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index f0cc49b..dcf9426 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -45,7 +45,7 @@ afterEvaluate { groupId = "org.bitcoindevkit" artifactId = "bdk-jvm" - version = "0.4.1-SNAPSHOT" + version = "0.6.0-SNAPSHOT" from(components["java"]) From fadaef51058d632033188ce260974e817019577e Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 17 Mar 2022 17:19:17 -0400 Subject: [PATCH 189/272] Fix database memory test --- .../androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index 210730c..6981b38 100644 --- a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -33,7 +33,7 @@ class AndroidLibTest { val descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val databaseConfig = DatabaseConfig.Memory("") + val databaseConfig = DatabaseConfig.Memory val blockchainConfig = BlockchainConfig.Electrum( ElectrumConfig( "ssl://electrum.blockstream.info:60002", @@ -81,7 +81,6 @@ class AndroidLibTest { @Test fun onlineWalletInMemory() { - val database = DatabaseConfig.Memory("") val blockchain = BlockchainConfig.Electrum( ElectrumConfig( "ssl://electrum.blockstream.info:60002", @@ -91,7 +90,7 @@ class AndroidLibTest { 100u ) ) - val wallet = Wallet(descriptor, null, Network.TESTNET, database, blockchain) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchain) assertNotNull(wallet) val network = wallet.getNetwork() assertEquals(network, Network.TESTNET) From f1f69c6fdf5a4b62a0a2c82e1c89d55eeb4132dc Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 18 Mar 2022 14:10:34 -0400 Subject: [PATCH 190/272] Add basic CI workflow that runs tests on pull requests Fixes #27 --- .github/workflows/test.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..ff611a9 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,22 @@ +name: Tests + +on: pull_request + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out PR branch + uses: actions/checkout@v2 + + - name: Set up JDK + uses: actions/setup-java@v2 + with: + distribution: temurin + java-version: 11 + + - name: Run Android tests + run: ./gradlew :android:test --console=rich + + - name: Run JVM tests + run: ./gradlew :jvm:test --console=rich From 6332e783751b6ba482e4b05d26f10ec9a28ead7b Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 1 Mar 2022 12:11:44 -0500 Subject: [PATCH 191/272] Add ability to generate dokka docs --- android/Module.md | 4 + android/build.gradle.kts | 10 + docs-0.4.0.patch | 2088 ++++++++++++++++++++++++++++++++++++++ jvm/Module.md | 4 + jvm/build.gradle.kts | 10 + 5 files changed, 2116 insertions(+) create mode 100644 android/Module.md create mode 100644 docs-0.4.0.patch create mode 100644 jvm/Module.md diff --git a/android/Module.md b/android/Module.md new file mode 100644 index 0000000..036a5ad --- /dev/null +++ b/android/Module.md @@ -0,0 +1,4 @@ +# Module bdk-android +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Android. Current version: `0.4.0`. + +# Package org.bitcoindevkit diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 6c31e9d..aa801c6 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -3,6 +3,7 @@ plugins { id("kotlin-android") id("maven-publish") id("signing") + id("org.jetbrains.dokka") version "1.6.10" } android { @@ -87,3 +88,12 @@ signing { useGpgCmd() sign(publishing.publications) } + +tasks.withType().configureEach { + dokkaSourceSets { + named("main") { + moduleName.set("bdk-android") + includes.from("Module.md") + } + } +} \ No newline at end of file diff --git a/docs-0.4.0.patch b/docs-0.4.0.patch new file mode 100644 index 0000000..3370a50 --- /dev/null +++ b/docs-0.4.0.patch @@ -0,0 +1,2088 @@ +*** ./temp-without-docs/org/bitcoindevkit/bdk.kt 2022-03-15 22:56:50.000000000 -0400 +--- ./android/src/main/kotlin/org/bitcoindevkit/bdk.kt 2022-03-15 23:00:28.000000000 -0400 +*************** +*** 1,181 **** +--- 1,194 ---- + // This file was autogenerated by some hot garbage in the `uniffi` crate. + // Trust me, you don't want to mess with it! + + @file:Suppress("NAME_SHADOWING") + + package org.bitcoindevkit; + + // Common helper code. + // + // Ideally this would live in a separate .kt file where it can be unittested etc + // in isolation, and perhaps even published as a re-useable package. + // + // However, it's important that the detils of how this helper code works (e.g. the + // way that different builtin types are passed across the FFI) exactly match what's + // expected by the Rust code on the other side of the interface. In practice right + // now that means coming from the exact some version of `uniffi` that was used to + // compile the Rust component. The easiest way to ensure this is to bundle the Kotlin + // helpers directly inline like we're doing here. + + import com.sun.jna.Library + import com.sun.jna.Native + import com.sun.jna.Pointer + import com.sun.jna.Structure + import com.sun.jna.ptr.ByReference + import java.nio.ByteBuffer + import java.nio.ByteOrder + import java.util.concurrent.atomic.AtomicBoolean + import java.util.concurrent.atomic.AtomicLong + import java.util.concurrent.locks.ReentrantLock + import kotlin.concurrent.withLock + + // The Rust Buffer and 3 templated methods (alloc, free, reserve). + // This is a helper for safely working with byte buffers returned from the Rust code. + // A rust-owned buffer is represented by its capacity, its current length, and a + // pointer to the underlying data. + ++ ++ /** ++ * @suppress ++ */ + @Structure.FieldOrder("capacity", "len", "data") + open class RustBuffer : Structure() { + @JvmField var capacity: Int = 0 + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : RustBuffer(), Structure.ByValue + class ByReference : RustBuffer(), Structure.ByReference + + companion object { + internal fun alloc(size: Int = 0) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_6983_rustbuffer_alloc(size, status).also { + if(it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") + } + } + } + + internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_6983_rustbuffer_free(buf, status) + } + + internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_6983_rustbuffer_reserve(buf, additional, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } + } + + /** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + */ ++ /** ++ * @suppress ++ */ + class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setInt(0, value.capacity) + pointer.setInt(4, value.len) + pointer.setPointer(8, value.data) + } + } + + // This is a helper for safely passing byte references into the rust code. + // It's not actually used at the moment, because there aren't many things that you + // can take a direct pointer to in the JVM, and if we're going to copy something + // then we might as well copy it into a `RustBuffer`. But it's here for API + // completeness. + ++ /** ++ * @suppress ++ */ + @Structure.FieldOrder("len", "data") + open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue + } + + + // A helper for structured writing of data into a `RustBuffer`. + // This is very similar to `java.nio.ByteBuffer` but it knows how to grow + // the underlying `RustBuffer` on demand. + // + // TODO: we should benchmark writing things into a `RustBuffer` versus building + // up a bytearray and then copying it across. + ++ /** ++ * @suppress ++ */ + class RustBufferBuilder() { + var rbuf = RustBuffer.ByValue() + var bbuf: ByteBuffer? = null + + init { + val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size + rbuf.writeField("len", 0) + this.setRustBuffer(rbuf) + } + + internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { + this.rbuf = rbuf + this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + it.position(rbuf.len) + } + } + + fun finalize() : RustBuffer.ByValue { + val rbuf = this.rbuf + // Ensure that the JVM-level field is written through to native memory + // before turning the buffer, in case its recipient uses it in a context + // JNA doesn't apply its automatic synchronization logic. + rbuf.writeField("len", this.bbuf!!.position()) + this.setRustBuffer(RustBuffer.ByValue()) + return rbuf + } + + fun discard() { + if(this.rbuf.data != null) { + // Free the current `RustBuffer` + RustBuffer.free(this.rbuf) + // Replace it with an empty RustBuffer. + this.setRustBuffer(RustBuffer.ByValue()) + } + } + + internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) { + // TODO: this will perform two checks to ensure we're not overflowing the buffer: + // one here where we check if it needs to grow, and another when we call a write + // method on the ByteBuffer. It might be cheaper to use exception-driven control-flow + // here, trying the write and growing if it throws a `BufferOverflowException`. + // Benchmarking needed. + if (this.bbuf!!.position() + size > this.rbuf.capacity) { + rbuf.writeField("len", this.bbuf!!.position()) + this.setRustBuffer(RustBuffer.reserve(this.rbuf, size)) + } + write(this.bbuf!!) + } + + fun putByte(v: Byte) { + this.reserve(1) { bbuf -> + bbuf.put(v) + } + } + + fun putShort(v: Short) { + this.reserve(2) { bbuf -> + bbuf.putShort(v) + } + } + + fun putInt(v: Int) { + this.reserve(4) { bbuf -> + bbuf.putInt(v) + } + } + + fun putLong(v: Long) { + this.reserve(8) { bbuf -> +*************** +*** 216,934 **** + } + } + + internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { + // TODO: maybe we can calculate some sort of initial size hint? + val buf = RustBufferBuilder() + try { + writeItem(v, buf) + return buf.finalize() + } catch (e: Throwable) { + buf.discard() + throw e + } + } + + // A handful of classes and functions to support the generated data structures. + // This would be a good candidate for isolating in its own ffi-support lib. + // Error runtime. + @Structure.FieldOrder("code", "error_buf") + internal open class RustCallStatus : Structure() { + @JvmField var code: Int = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + fun isSuccess(): Boolean { + return code == 0 + } + + fun isError(): Boolean { + return code == 1 + } + + fun isPanic(): Boolean { + return code == 2 + } + } + + class InternalException(message: String) : Exception(message) + + // Each top-level error class has a companion object that can lift the error from the call status's rust buffer + interface CallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E; + } + + // Helpers for calling Rust + // In practice we usually need to be synchronized to call this safely, so it doesn't + // synchronize itself + + // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err + private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus(); + val return_value = callback(status) + if (status.isSuccess()) { + return return_value + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(String.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } + } + + // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR + object NullCallStatusErrorHandler: CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } + } + + // Call a rust function that returns a plain value + private inline fun rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); + } + + // Contains loading, initialization code, + // and the FFI Function declarations in a com.sun.jna.Library. + @Synchronized + private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "bdkffi" + } + + private inline fun loadIndirect( + componentName: String + ): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) + } + + // A JNA Library to expose the extern-C FFI definitions. + // This is an implementation detail which will be called internally by the public API. + + internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "bdk") + .also { lib: _UniFFILib -> + FfiConverterCallbackInterfaceBdkProgress.register(lib) + } + + } + } + + fun ffi_bdk_6983_Wallet_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_6983_Wallet_new(descriptor: RustBuffer.ByValue,change_descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun bdk_6983_Wallet_get_new_address(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_6983_Wallet_get_last_unused_address(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_6983_Wallet_get_balance(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Long + + fun bdk_6983_Wallet_sign(ptr: Pointer,psbt: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_6983_Wallet_get_transactions(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_6983_Wallet_get_network(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_6983_Wallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_6983_Wallet_broadcast(ptr: Pointer,psbt: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_6983_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_6983_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long,fee_rate: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun bdk_6983_PartiallySignedBitcoinTransaction_deserialize(psbt_base64: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun bdk_6983_PartiallySignedBitcoinTransaction_serialize(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_6983_BdkProgress_init_callback(callback_stub: ForeignCallback, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_6983_generate_extended_key(network: RustBuffer.ByValue,word_count: RustBuffer.ByValue,password: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_6983_restore_extended_key(network: RustBuffer.ByValue,mnemonic: RustBuffer.ByValue,password: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_6983_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_6983_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_6983_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_6983_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + + } + + // Public interface members begin here. + + // Interface implemented by anything that can contain an object reference. + // + // Such types expose a `destroy()` method that must be called to cleanly + // dispose of the contained objects. Failure to call this method may result + // in memory leaks. + // + // The easiest way to ensure this method is called is to use the `.use` + // helper method to execute a block and destroy the object at the end. + interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } + } + + inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + + // The base class for all UniFFI Object types. + // + // This class provides core operations for working with the Rust `Arc` pointer to + // the live Rust struct on the other side of the FFI. + // + // There's some subtlety here, because we have to be careful not to operate on a Rust + // struct after it has been dropped, and because we must expose a public API for freeing + // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: + // + // * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. + // Method calls need to read this pointer from the object's state and pass it in to + // the Rust FFI. + // + // * When an `FFIObject` is no longer needed, its pointer should be passed to a + // special destructor function provided by the Rust FFI, which will drop the + // underlying Rust struct. + // + // * Given an `FFIObject` instance, calling code is expected to call the special + // `destroy` method in order to free it after use, either by calling it explicitly + // or by using a higher-level helper like the `use` method. Failing to do so will + // leak the underlying Rust struct. + // + // * We can't assume that calling code will do the right thing, and must be prepared + // to handle Kotlin method calls executing concurrently with or even after a call to + // `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. + // + // * We must never allow Rust code to operate on the underlying Rust struct after + // the destructor has been called, and must never call the destructor more than once. + // Doing so may trigger memory unsafety. + // + // If we try to implement this with mutual exclusion on access to the pointer, there is the + // possibility of a race between a method call and a concurrent call to `destroy`: + // + // * Thread A starts a method call, reads the value of the pointer, but is interrupted + // before it can pass the pointer over the FFI to Rust. + // * Thread B calls `destroy` and frees the underlying Rust struct. + // * Thread A resumes, passing the already-read pointer value to Rust and triggering + // a use-after-free. + // + // One possible solution would be to use a `ReadWriteLock`, with each method call taking + // a read lock (and thus allowed to run concurrently) and the special `destroy` method + // taking a write lock (and thus blocking on live method calls). However, we aim not to + // generate methods with any hidden blocking semantics, and a `destroy` method that might + // block if called incorrectly seems to meet that bar. + // + // So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track + // the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` + // has been called. These are updated according to the following rules: + // + // * The initial value of the counter is 1, indicating a live object with no in-flight calls. + // The initial value for the flag is false. + // + // * At the start of each method call, we atomically check the counter. + // If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. + // If it is nonzero them we atomically increment it by 1 and proceed with the method call. + // + // * At the end of each method call, we atomically decrement and check the counter. + // If it has reached zero then we destroy the underlying Rust struct. + // + // * When `destroy` is called, we atomically flip the flag from false to true. + // If the flag was already true we silently fail. + // Otherwise we atomically decrement and check the counter. + // If it has reached zero then we destroy the underlying Rust struct. + // + // Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, + // and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. + // + // The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been + // called *and* all in-flight method calls have completed, avoiding violating any of the expectations + // of the underlying Rust code. + // + // In the future we may be able to replace some of this with automatic finalization logic, such as using + // the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is + // invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also + // possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], + // so there would still be some complexity here). + // + // Sigh...all of this for want of a robust finalization mechanism. + // + // [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 + // + abstract class FFIObject( + protected val pointer: Pointer + ): Disposable, AutoCloseable { + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.pointer) + } finally { + // This decrement aways matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + } + internal typealias Handle = Long + internal class ConcurrentHandleMap( + private val leftMap: MutableMap = mutableMapOf(), + private val rightMap: MutableMap = mutableMapOf() + ) { + private val lock = java.util.concurrent.locks.ReentrantLock() + private val currentHandle = AtomicLong(0L) + private val stride = 1L + + fun insert(obj: T): Handle = + lock.withLock { + rightMap[obj] ?: + currentHandle.getAndAdd(stride) + .also { handle -> + leftMap[handle] = obj + rightMap[obj] = handle + } + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } + } + + interface ForeignCallback : com.sun.jna.Callback { + public fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int + } + + // Magic number for the Rust proxy to call using the same mechanism as every other method, + // to free the callback once it's dropped by Rust. + internal const val IDX_CALLBACK_FREE = 0 + + internal abstract class FfiConverterCallbackInterface( + protected val foreignCallback: ForeignCallback + ) { + val handleMap = ConcurrentHandleMap() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + abstract fun register(lib: _UniFFILib) + + fun drop(handle: Handle): RustBuffer.ByValue { + return handleMap.remove(handle).let { RustBuffer.ByValue() } + } + + fun lift(n: Handle) = handleMap.get(n) + + fun read(buf: ByteBuffer) = lift(buf.getLong()) + + fun lower(v: CallbackInterface) = + handleMap.insert(v).also { + assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + fun write(v: CallbackInterface, buf: RustBufferBuilder) = + buf.putLong(lower(v)) + } + + + + enum class Network { + BITCOIN,TESTNET,SIGNET,REGTEST; + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): Network { + return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) } + } + + internal fun read(buf: ByteBuffer) = + try { values()[buf.getInt() - 1] } + catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + buf.putInt(this.ordinal + 1) + } + } + + + + + + + + sealed class DatabaseConfig { + object Memory : DatabaseConfig() + + data class Sled( + val config: SledDbConfiguration + ) : DatabaseConfig() + + data class Sqlite( + val config: SqliteDbConfiguration + ) : DatabaseConfig() +- + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig { + return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): DatabaseConfig { + return when(buf.getInt()) { + 1 -> DatabaseConfig.Memory + 2 -> DatabaseConfig.Sled( + SledDbConfiguration.read(buf) + ) + 3 -> DatabaseConfig.Sqlite( + SqliteDbConfiguration.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is DatabaseConfig.Memory -> { + buf.putInt(1) + + } + is DatabaseConfig.Sled -> { + buf.putInt(2) + this.config.write(buf) + + } + is DatabaseConfig.Sqlite -> { + buf.putInt(3) + this.config.write(buf) + + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + + + + } + + + + + + + + sealed class Transaction { + + data class Unconfirmed( + val details: TransactionDetails + ) : Transaction() + + data class Confirmed( + val details: TransactionDetails, + val confirmation: BlockTime + ) : Transaction() + + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): Transaction { + return liftFromRustBuffer(rbuf) { buf -> Transaction.read(buf) } + } + + internal fun read(buf: ByteBuffer): Transaction { + return when(buf.getInt()) { + 1 -> Transaction.Unconfirmed( + TransactionDetails.read(buf) + ) + 2 -> Transaction.Confirmed( + TransactionDetails.read(buf), + BlockTime.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is Transaction.Unconfirmed -> { + buf.putInt(1) + this.details.write(buf) + + } + is Transaction.Confirmed -> { + buf.putInt(2) + this.details.write(buf) + this.confirmation.write(buf) + + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + + + + } + + + + + + + + sealed class BlockchainConfig { + + data class Electrum( + val config: ElectrumConfig + ) : BlockchainConfig() + + data class Esplora( + val config: EsploraConfig + ) : BlockchainConfig() + + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig { + return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): BlockchainConfig { + return when(buf.getInt()) { + 1 -> BlockchainConfig.Electrum( + ElectrumConfig.read(buf) + ) + 2 -> BlockchainConfig.Esplora( + EsploraConfig.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is BlockchainConfig.Electrum -> { + buf.putInt(1) + this.config.write(buf) + + } + is BlockchainConfig.Esplora -> { + buf.putInt(2) + this.config.write(buf) + + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + + + + } + + + + + + enum class WordCount { + WORDS12,WORDS15,WORDS18,WORDS21,WORDS24; + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): WordCount { + return liftFromRustBuffer(rbuf) { buf -> WordCount.read(buf) } + } + + internal fun read(buf: ByteBuffer) = + try { values()[buf.getInt() - 1] } + catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + buf.putInt(this.ordinal + 1) + } + } + + + + @Throws(BdkException::class) + + fun generateExtendedKey(network: Network, wordCount: WordCount, password: String? ): ExtendedKeyInfo { + val _retval = + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_6983_generate_extended_key(network.lower(), wordCount.lower(), lowerOptionalString(password) ,status) + } + return ExtendedKeyInfo.lift(_retval) + } + + + @Throws(BdkException::class) + + fun restoreExtendedKey(network: Network, mnemonic: String, password: String? ): ExtendedKeyInfo { + val _retval = + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_6983_restore_extended_key(network.lower(), mnemonic.lower(), lowerOptionalString(password) ,status) + } + return ExtendedKeyInfo.lift(_retval) + } + + + public interface WalletInterface { + + fun getNewAddress(): String + + fun getLastUnusedAddress(): String + + @Throws(BdkException::class) + fun getBalance(): ULong + + @Throws(BdkException::class) + fun sign(psbt: PartiallySignedBitcoinTransaction ) + + @Throws(BdkException::class) + fun getTransactions(): List + + fun getNetwork(): Network + + @Throws(BdkException::class) + fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) + + @Throws(BdkException::class) + fun broadcast(psbt: PartiallySignedBitcoinTransaction ): Transaction + + } + +--- 229,980 ---- + } + } + + internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { + // TODO: maybe we can calculate some sort of initial size hint? + val buf = RustBufferBuilder() + try { + writeItem(v, buf) + return buf.finalize() + } catch (e: Throwable) { + buf.discard() + throw e + } + } + + // A handful of classes and functions to support the generated data structures. + // This would be a good candidate for isolating in its own ffi-support lib. + // Error runtime. + @Structure.FieldOrder("code", "error_buf") + internal open class RustCallStatus : Structure() { + @JvmField var code: Int = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + fun isSuccess(): Boolean { + return code == 0 + } + + fun isError(): Boolean { + return code == 1 + } + + fun isPanic(): Boolean { + return code == 2 + } + } + + class InternalException(message: String) : Exception(message) + + // Each top-level error class has a companion object that can lift the error from the call status's rust buffer + interface CallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E; + } + + // Helpers for calling Rust + // In practice we usually need to be synchronized to call this safely, so it doesn't + // synchronize itself + + // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err + private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus(); + val return_value = callback(status) + if (status.isSuccess()) { + return return_value + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(String.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } + } + + // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR ++ /** ++ * @suppress ++ */ + object NullCallStatusErrorHandler: CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } + } + + // Call a rust function that returns a plain value + private inline fun rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); + } + + // Contains loading, initialization code, + // and the FFI Function declarations in a com.sun.jna.Library. + @Synchronized + private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "bdkffi" + } + + private inline fun loadIndirect( + componentName: String + ): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) + } + + // A JNA Library to expose the extern-C FFI definitions. + // This is an implementation detail which will be called internally by the public API. + + internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "bdk") + .also { lib: _UniFFILib -> + FfiConverterCallbackInterfaceBdkProgress.register(lib) + } + + } + } + + fun ffi_bdk_6983_Wallet_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_6983_Wallet_new(descriptor: RustBuffer.ByValue,change_descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun bdk_6983_Wallet_get_new_address(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_6983_Wallet_get_last_unused_address(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_6983_Wallet_get_balance(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Long + + fun bdk_6983_Wallet_sign(ptr: Pointer,psbt: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_6983_Wallet_get_transactions(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_6983_Wallet_get_network(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_6983_Wallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_6983_Wallet_broadcast(ptr: Pointer,psbt: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_6983_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_6983_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long,fee_rate: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun bdk_6983_PartiallySignedBitcoinTransaction_deserialize(psbt_base64: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun bdk_6983_PartiallySignedBitcoinTransaction_serialize(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_6983_BdkProgress_init_callback(callback_stub: ForeignCallback, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_6983_generate_extended_key(network: RustBuffer.ByValue,word_count: RustBuffer.ByValue,password: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun bdk_6983_restore_extended_key(network: RustBuffer.ByValue,mnemonic: RustBuffer.ByValue,password: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_6983_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_6983_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_6983_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_6983_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + + } + + // Public interface members begin here. + + // Interface implemented by anything that can contain an object reference. + // + // Such types expose a `destroy()` method that must be called to cleanly + // dispose of the contained objects. Failure to call this method may result + // in memory leaks. + // + // The easiest way to ensure this method is called is to use the `.use` + // helper method to execute a block and destroy the object at the end. ++ /** ++ * @suppress ++ */ + interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } + } + ++ /** ++ * @suppress ++ */ + inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + + // The base class for all UniFFI Object types. + // + // This class provides core operations for working with the Rust `Arc` pointer to + // the live Rust struct on the other side of the FFI. + // + // There's some subtlety here, because we have to be careful not to operate on a Rust + // struct after it has been dropped, and because we must expose a public API for freeing + // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: + // + // * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. + // Method calls need to read this pointer from the object's state and pass it in to + // the Rust FFI. + // + // * When an `FFIObject` is no longer needed, its pointer should be passed to a + // special destructor function provided by the Rust FFI, which will drop the + // underlying Rust struct. + // + // * Given an `FFIObject` instance, calling code is expected to call the special + // `destroy` method in order to free it after use, either by calling it explicitly + // or by using a higher-level helper like the `use` method. Failing to do so will + // leak the underlying Rust struct. + // + // * We can't assume that calling code will do the right thing, and must be prepared + // to handle Kotlin method calls executing concurrently with or even after a call to + // `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. + // + // * We must never allow Rust code to operate on the underlying Rust struct after + // the destructor has been called, and must never call the destructor more than once. + // Doing so may trigger memory unsafety. + // + // If we try to implement this with mutual exclusion on access to the pointer, there is the + // possibility of a race between a method call and a concurrent call to `destroy`: + // + // * Thread A starts a method call, reads the value of the pointer, but is interrupted + // before it can pass the pointer over the FFI to Rust. + // * Thread B calls `destroy` and frees the underlying Rust struct. + // * Thread A resumes, passing the already-read pointer value to Rust and triggering + // a use-after-free. + // + // One possible solution would be to use a `ReadWriteLock`, with each method call taking + // a read lock (and thus allowed to run concurrently) and the special `destroy` method + // taking a write lock (and thus blocking on live method calls). However, we aim not to + // generate methods with any hidden blocking semantics, and a `destroy` method that might + // block if called incorrectly seems to meet that bar. + // + // So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track + // the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` + // has been called. These are updated according to the following rules: + // + // * The initial value of the counter is 1, indicating a live object with no in-flight calls. + // The initial value for the flag is false. + // + // * At the start of each method call, we atomically check the counter. + // If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. + // If it is nonzero them we atomically increment it by 1 and proceed with the method call. + // + // * At the end of each method call, we atomically decrement and check the counter. + // If it has reached zero then we destroy the underlying Rust struct. + // + // * When `destroy` is called, we atomically flip the flag from false to true. + // If the flag was already true we silently fail. + // Otherwise we atomically decrement and check the counter. + // If it has reached zero then we destroy the underlying Rust struct. + // + // Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, + // and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. + // + // The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been + // called *and* all in-flight method calls have completed, avoiding violating any of the expectations + // of the underlying Rust code. + // + // In the future we may be able to replace some of this with automatic finalization logic, such as using + // the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is + // invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also + // possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], + // so there would still be some complexity here). + // + // Sigh...all of this for want of a robust finalization mechanism. + // + // [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 + // ++ /** ++ * @suppress ++ */ + abstract class FFIObject( + protected val pointer: Pointer + ): Disposable, AutoCloseable { + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.pointer) + } finally { + // This decrement aways matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + } + internal typealias Handle = Long + internal class ConcurrentHandleMap( + private val leftMap: MutableMap = mutableMapOf(), + private val rightMap: MutableMap = mutableMapOf() + ) { + private val lock = java.util.concurrent.locks.ReentrantLock() + private val currentHandle = AtomicLong(0L) + private val stride = 1L + + fun insert(obj: T): Handle = + lock.withLock { + rightMap[obj] ?: + currentHandle.getAndAdd(stride) + .also { handle -> + leftMap[handle] = obj + rightMap[obj] = handle + } + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } + } + ++ /** ++ * @suppress ++ */ + interface ForeignCallback : com.sun.jna.Callback { + public fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int + } + + // Magic number for the Rust proxy to call using the same mechanism as every other method, + // to free the callback once it's dropped by Rust. + internal const val IDX_CALLBACK_FREE = 0 + + internal abstract class FfiConverterCallbackInterface( + protected val foreignCallback: ForeignCallback + ) { + val handleMap = ConcurrentHandleMap() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + abstract fun register(lib: _UniFFILib) + + fun drop(handle: Handle): RustBuffer.ByValue { + return handleMap.remove(handle).let { RustBuffer.ByValue() } + } + + fun lift(n: Handle) = handleMap.get(n) + + fun read(buf: ByteBuffer) = lift(buf.getLong()) + + fun lower(v: CallbackInterface) = + handleMap.insert(v).also { + assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + fun write(v: CallbackInterface, buf: RustBufferBuilder) = + buf.putLong(lower(v)) + } + + + + enum class Network { + BITCOIN,TESTNET,SIGNET,REGTEST; + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): Network { + return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) } + } + + internal fun read(buf: ByteBuffer) = + try { values()[buf.getInt() - 1] } + catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + buf.putInt(this.ordinal + 1) + } + } + + + + + + + + sealed class DatabaseConfig { + object Memory : DatabaseConfig() + + data class Sled( + val config: SledDbConfiguration + ) : DatabaseConfig() + + data class Sqlite( + val config: SqliteDbConfiguration + ) : DatabaseConfig() + ++ ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig { + return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): DatabaseConfig { + return when(buf.getInt()) { + 1 -> DatabaseConfig.Memory + 2 -> DatabaseConfig.Sled( + SledDbConfiguration.read(buf) + ) + 3 -> DatabaseConfig.Sqlite( + SqliteDbConfiguration.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is DatabaseConfig.Memory -> { + buf.putInt(1) + + } + is DatabaseConfig.Sled -> { + buf.putInt(2) + this.config.write(buf) + + } + is DatabaseConfig.Sqlite -> { + buf.putInt(3) + this.config.write(buf) + + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + + + + } + + + + + + + + sealed class Transaction { + + data class Unconfirmed( + val details: TransactionDetails + ) : Transaction() + + data class Confirmed( + val details: TransactionDetails, + val confirmation: BlockTime + ) : Transaction() + + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): Transaction { + return liftFromRustBuffer(rbuf) { buf -> Transaction.read(buf) } + } + + internal fun read(buf: ByteBuffer): Transaction { + return when(buf.getInt()) { + 1 -> Transaction.Unconfirmed( + TransactionDetails.read(buf) + ) + 2 -> Transaction.Confirmed( + TransactionDetails.read(buf), + BlockTime.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is Transaction.Unconfirmed -> { + buf.putInt(1) + this.details.write(buf) + + } + is Transaction.Confirmed -> { + buf.putInt(2) + this.details.write(buf) + this.confirmation.write(buf) + + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + + + + } + + + + + + + ++ /** ++ * Sealed class that can be of either blockchain configuration defined by the library. ++ */ + sealed class BlockchainConfig { + + data class Electrum( + val config: ElectrumConfig + ) : BlockchainConfig() + + data class Esplora( + val config: EsploraConfig + ) : BlockchainConfig() + + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig { + return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): BlockchainConfig { + return when(buf.getInt()) { + 1 -> BlockchainConfig.Electrum( + ElectrumConfig.read(buf) + ) + 2 -> BlockchainConfig.Esplora( + EsploraConfig.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is BlockchainConfig.Electrum -> { + buf.putInt(1) + this.config.write(buf) + + } + is BlockchainConfig.Esplora -> { + buf.putInt(2) + this.config.write(buf) + + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + + + + } + + + + + + enum class WordCount { + WORDS12,WORDS15,WORDS18,WORDS21,WORDS24; + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): WordCount { + return liftFromRustBuffer(rbuf) { buf -> WordCount.read(buf) } + } + + internal fun read(buf: ByteBuffer) = + try { values()[buf.getInt() - 1] } + catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + buf.putInt(this.ordinal + 1) + } + } + + + + @Throws(BdkException::class) + + fun generateExtendedKey(network: Network, wordCount: WordCount, password: String? ): ExtendedKeyInfo { + val _retval = + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_6983_generate_extended_key(network.lower(), wordCount.lower(), lowerOptionalString(password) ,status) + } + return ExtendedKeyInfo.lift(_retval) + } + + + @Throws(BdkException::class) + + fun restoreExtendedKey(network: Network, mnemonic: String, password: String? ): ExtendedKeyInfo { + val _retval = + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_6983_restore_extended_key(network.lower(), mnemonic.lower(), lowerOptionalString(password) ,status) + } + return ExtendedKeyInfo.lift(_retval) + } + + + public interface WalletInterface { + + fun getNewAddress(): String + + fun getLastUnusedAddress(): String + + @Throws(BdkException::class) + fun getBalance(): ULong + + @Throws(BdkException::class) + fun sign(psbt: PartiallySignedBitcoinTransaction ) + + @Throws(BdkException::class) + fun getTransactions(): List + + fun getNetwork(): Network + + @Throws(BdkException::class) + fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) + + @Throws(BdkException::class) + fun broadcast(psbt: PartiallySignedBitcoinTransaction ): Transaction + + } + +*************** +*** 1034,1418 **** +--- 1080,1491 ---- + } + }.let { + Transaction.lift(it) + } + + + + companion object { + internal fun lift(ptr: Pointer): Wallet { + return Wallet(ptr) + } + + internal fun read(buf: ByteBuffer): Wallet { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return Wallet.lift(Pointer(buf.getLong())) + } + + + } + } + + public interface PartiallySignedBitcoinTransactionInterface { + + fun serialize(): String + + } + + class PartiallySignedBitcoinTransaction( + pointer: Pointer + ) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface { + constructor(wallet: Wallet, recipient: String, amount: ULong, feeRate: Float? ) : + this( + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_6983_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower(), lowerOptionalFloat(feeRate) ,status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_6983_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + override fun serialize(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_6983_PartiallySignedBitcoinTransaction_serialize(it, status) + } + }.let { + String.lift(it) + } + + + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(ptr: Pointer): PartiallySignedBitcoinTransaction { + return PartiallySignedBitcoinTransaction(ptr) + } + + internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong())) + } + + fun deserialize(psbtBase64: String ): PartiallySignedBitcoinTransaction = + PartiallySignedBitcoinTransaction( + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_6983_PartiallySignedBitcoinTransaction_deserialize(psbtBase64.lower() ,status) + }) + + } + } + + data class SledDbConfiguration ( + var path: String, + var treeName: String + ) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): SledDbConfiguration { + return liftFromRustBuffer(rbuf) { buf -> SledDbConfiguration.read(buf) } + } + + internal fun read(buf: ByteBuffer): SledDbConfiguration { + return SledDbConfiguration( + String.read(buf), + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.path.write(buf) + + this.treeName.write(buf) + + } + + + + } + + data class SqliteDbConfiguration ( + var path: String + ) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): SqliteDbConfiguration { + return liftFromRustBuffer(rbuf) { buf -> SqliteDbConfiguration.read(buf) } + } + + internal fun read(buf: ByteBuffer): SqliteDbConfiguration { + return SqliteDbConfiguration( + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.path.write(buf) + + } + + + + } + + data class TransactionDetails ( + var fees: ULong?, + var received: ULong, + var sent: ULong, + var txid: String + ) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): TransactionDetails { + return liftFromRustBuffer(rbuf) { buf -> TransactionDetails.read(buf) } + } + + internal fun read(buf: ByteBuffer): TransactionDetails { + return TransactionDetails( + readOptionalULong(buf), + ULong.read(buf), + ULong.read(buf), + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + writeOptionalULong(this.fees, buf) + + this.received.write(buf) + + this.sent.write(buf) + + this.txid.write(buf) + + } + + + + } + ++ /** ++ * Block height and timestamp of a block ++ */ + data class BlockTime ( + var height: UInt, + var timestamp: ULong + ) { ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): BlockTime { + return liftFromRustBuffer(rbuf) { buf -> BlockTime.read(buf) } + } + + internal fun read(buf: ByteBuffer): BlockTime { + return BlockTime( + UInt.read(buf), + ULong.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.height.write(buf) + + this.timestamp.write(buf) + + } + + + + } + ++ /** ++ * Configuration for an ElectrumBlockchain ++ * ++ * @property url URL of the Electrum server (such as ElectrumX, Esplora, BWT). May start with `ssl://` or `tcp://` and include a port, `e.g. ssl://electrum.blockstream.info:60002` ++ * @property socks5 URL of the socks5 proxy server or a Tor service ++ * @property retry Request retry count ++ * @property timeout Request timeout in seconds ++ * @property stopGap Stop searching addresses for transactions after finding an unused gap of this length ++ */ + data class ElectrumConfig ( + var url: String, + var socks5: String?, + var retry: UByte, + var timeout: UByte?, + var stopGap: ULong + ) { ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): ElectrumConfig { + return liftFromRustBuffer(rbuf) { buf -> ElectrumConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): ElectrumConfig { + return ElectrumConfig( + String.read(buf), + readOptionalString(buf), + UByte.read(buf), + readOptionalUByte(buf), + ULong.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.url.write(buf) + + writeOptionalString(this.socks5, buf) + + this.retry.write(buf) + + writeOptionalUByte(this.timeout, buf) + + this.stopGap.write(buf) + + } + + + + } + + data class EsploraConfig ( + var baseUrl: String, + var proxy: String?, + var timeoutRead: ULong, + var timeoutWrite: ULong, + var stopGap: ULong + ) { ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): EsploraConfig { + return liftFromRustBuffer(rbuf) { buf -> EsploraConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): EsploraConfig { + return EsploraConfig( + String.read(buf), + readOptionalString(buf), + ULong.read(buf), + ULong.read(buf), + ULong.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.baseUrl.write(buf) + + writeOptionalString(this.proxy, buf) + + this.timeoutRead.write(buf) + + this.timeoutWrite.write(buf) + + this.stopGap.write(buf) + + } + + + + } + + data class ExtendedKeyInfo ( + var mnemonic: String, + var xprv: String, + var fingerprint: String + ) { ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): ExtendedKeyInfo { + return liftFromRustBuffer(rbuf) { buf -> ExtendedKeyInfo.read(buf) } + } + + internal fun read(buf: ByteBuffer): ExtendedKeyInfo { + return ExtendedKeyInfo( + String.read(buf), + String.read(buf), + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.mnemonic.write(buf) + + this.xprv.write(buf) + + this.fingerprint.write(buf) + + } + + + + } + + + + sealed class BdkException(message: String): Exception(message) { + // Each variant is a nested class + // Flat enums carries a string error message, so no special implementation is necessary. + class InvalidU32Bytes(message: String) : BdkException(message) + class Generic(message: String) : BdkException(message) + class ScriptDoesntHaveAddressForm(message: String) : BdkException(message) + class NoRecipients(message: String) : BdkException(message) + class NoUtxosSelected(message: String) : BdkException(message) + class OutputBelowDustLimit(message: String) : BdkException(message) + class InsufficientFunds(message: String) : BdkException(message) + class BnBTotalTriesExceeded(message: String) : BdkException(message) + class BnBNoExactMatch(message: String) : BdkException(message) + class UnknownUtxo(message: String) : BdkException(message) + class TransactionNotFound(message: String) : BdkException(message) + class TransactionConfirmed(message: String) : BdkException(message) + class IrreplaceableTransaction(message: String) : BdkException(message) + class FeeRateTooLow(message: String) : BdkException(message) + class FeeTooLow(message: String) : BdkException(message) + class FeeRateUnavailable(message: String) : BdkException(message) + class MissingKeyOrigin(message: String) : BdkException(message) + class Key(message: String) : BdkException(message) + class ChecksumMismatch(message: String) : BdkException(message) + class SpendingPolicyRequired(message: String) : BdkException(message) + class InvalidPolicyPathException(message: String) : BdkException(message) + class Signer(message: String) : BdkException(message) + class InvalidNetwork(message: String) : BdkException(message) + class InvalidProgressValue(message: String) : BdkException(message) + class ProgressUpdateException(message: String) : BdkException(message) + class InvalidOutpoint(message: String) : BdkException(message) + class Descriptor(message: String) : BdkException(message) + class AddressValidator(message: String) : BdkException(message) + class Encode(message: String) : BdkException(message) + class Miniscript(message: String) : BdkException(message) + class Bip32(message: String) : BdkException(message) + class Secp256k1(message: String) : BdkException(message) + class Json(message: String) : BdkException(message) + class Hex(message: String) : BdkException(message) diff --git a/jvm/Module.md b/jvm/Module.md new file mode 100644 index 0000000..64d7e82 --- /dev/null +++ b/jvm/Module.md @@ -0,0 +1,4 @@ +# Module bdk-jvm +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for the JVM. Current version: `0.4.0`. + +# Package org.bitcoindevkit diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index dcf9426..a087fca 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -6,6 +6,7 @@ plugins { id("java-library") id("maven-publish") id("signing") + id("org.jetbrains.dokka") version "1.6.10" } java { @@ -90,3 +91,12 @@ signing { useGpgCmd() sign(publishing.publications) } + +tasks.withType().configureEach { + dokkaSourceSets { + named("main") { + moduleName.set("bdk-jvm") + includes.from("Module.md") + } + } +} From aa13e113fa50db6fb4dc455193d477f23f79449b Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Sat, 19 Mar 2022 11:01:18 -0400 Subject: [PATCH 192/272] Add required files for API docs 0.5.1 --- android/Module.md | 2 +- docs-0.4.0.patch | 2088 --------------------------------------------- docs.patch | 804 +++++++++++++++++ jvm/Module.md | 2 +- 4 files changed, 806 insertions(+), 2090 deletions(-) delete mode 100644 docs-0.4.0.patch create mode 100644 docs.patch diff --git a/android/Module.md b/android/Module.md index 036a5ad..f3e8d26 100644 --- a/android/Module.md +++ b/android/Module.md @@ -1,4 +1,4 @@ # Module bdk-android -The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Android. Current version: `0.4.0`. +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Android. Current version: `0.5.1`. # Package org.bitcoindevkit diff --git a/docs-0.4.0.patch b/docs-0.4.0.patch deleted file mode 100644 index 3370a50..0000000 --- a/docs-0.4.0.patch +++ /dev/null @@ -1,2088 +0,0 @@ -*** ./temp-without-docs/org/bitcoindevkit/bdk.kt 2022-03-15 22:56:50.000000000 -0400 ---- ./android/src/main/kotlin/org/bitcoindevkit/bdk.kt 2022-03-15 23:00:28.000000000 -0400 -*************** -*** 1,181 **** ---- 1,194 ---- - // This file was autogenerated by some hot garbage in the `uniffi` crate. - // Trust me, you don't want to mess with it! - - @file:Suppress("NAME_SHADOWING") - - package org.bitcoindevkit; - - // Common helper code. - // - // Ideally this would live in a separate .kt file where it can be unittested etc - // in isolation, and perhaps even published as a re-useable package. - // - // However, it's important that the detils of how this helper code works (e.g. the - // way that different builtin types are passed across the FFI) exactly match what's - // expected by the Rust code on the other side of the interface. In practice right - // now that means coming from the exact some version of `uniffi` that was used to - // compile the Rust component. The easiest way to ensure this is to bundle the Kotlin - // helpers directly inline like we're doing here. - - import com.sun.jna.Library - import com.sun.jna.Native - import com.sun.jna.Pointer - import com.sun.jna.Structure - import com.sun.jna.ptr.ByReference - import java.nio.ByteBuffer - import java.nio.ByteOrder - import java.util.concurrent.atomic.AtomicBoolean - import java.util.concurrent.atomic.AtomicLong - import java.util.concurrent.locks.ReentrantLock - import kotlin.concurrent.withLock - - // The Rust Buffer and 3 templated methods (alloc, free, reserve). - // This is a helper for safely working with byte buffers returned from the Rust code. - // A rust-owned buffer is represented by its capacity, its current length, and a - // pointer to the underlying data. - -+ -+ /** -+ * @suppress -+ */ - @Structure.FieldOrder("capacity", "len", "data") - open class RustBuffer : Structure() { - @JvmField var capacity: Int = 0 - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : RustBuffer(), Structure.ByValue - class ByReference : RustBuffer(), Structure.ByReference - - companion object { - internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_6983_rustbuffer_alloc(size, status).also { - if(it.data == null) { - throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") - } - } - } - - internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_6983_rustbuffer_free(buf, status) - } - - internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_6983_rustbuffer_reserve(buf, additional, status) - } - } - - @Suppress("TooGenericExceptionThrown") - fun asByteBuffer() = - this.data?.getByteBuffer(0, this.len.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - } - } - - /** - * The equivalent of the `*mut RustBuffer` type. - * Required for callbacks taking in an out pointer. - * - * Size is the sum of all values in the struct. - */ -+ /** -+ * @suppress -+ */ - class RustBufferByReference : ByReference(16) { - /** - * Set the pointed-to `RustBuffer` to the given value. - */ - fun setValue(value: RustBuffer.ByValue) { - // NOTE: The offsets are as they are in the C-like struct. - val pointer = getPointer() - pointer.setInt(0, value.capacity) - pointer.setInt(4, value.len) - pointer.setPointer(8, value.data) - } - } - - // This is a helper for safely passing byte references into the rust code. - // It's not actually used at the moment, because there aren't many things that you - // can take a direct pointer to in the JVM, and if we're going to copy something - // then we might as well copy it into a `RustBuffer`. But it's here for API - // completeness. - -+ /** -+ * @suppress -+ */ - @Structure.FieldOrder("len", "data") - open class ForeignBytes : Structure() { - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : ForeignBytes(), Structure.ByValue - } - - - // A helper for structured writing of data into a `RustBuffer`. - // This is very similar to `java.nio.ByteBuffer` but it knows how to grow - // the underlying `RustBuffer` on demand. - // - // TODO: we should benchmark writing things into a `RustBuffer` versus building - // up a bytearray and then copying it across. - -+ /** -+ * @suppress -+ */ - class RustBufferBuilder() { - var rbuf = RustBuffer.ByValue() - var bbuf: ByteBuffer? = null - - init { - val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size - rbuf.writeField("len", 0) - this.setRustBuffer(rbuf) - } - - internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { - this.rbuf = rbuf - this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - it.position(rbuf.len) - } - } - - fun finalize() : RustBuffer.ByValue { - val rbuf = this.rbuf - // Ensure that the JVM-level field is written through to native memory - // before turning the buffer, in case its recipient uses it in a context - // JNA doesn't apply its automatic synchronization logic. - rbuf.writeField("len", this.bbuf!!.position()) - this.setRustBuffer(RustBuffer.ByValue()) - return rbuf - } - - fun discard() { - if(this.rbuf.data != null) { - // Free the current `RustBuffer` - RustBuffer.free(this.rbuf) - // Replace it with an empty RustBuffer. - this.setRustBuffer(RustBuffer.ByValue()) - } - } - - internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) { - // TODO: this will perform two checks to ensure we're not overflowing the buffer: - // one here where we check if it needs to grow, and another when we call a write - // method on the ByteBuffer. It might be cheaper to use exception-driven control-flow - // here, trying the write and growing if it throws a `BufferOverflowException`. - // Benchmarking needed. - if (this.bbuf!!.position() + size > this.rbuf.capacity) { - rbuf.writeField("len", this.bbuf!!.position()) - this.setRustBuffer(RustBuffer.reserve(this.rbuf, size)) - } - write(this.bbuf!!) - } - - fun putByte(v: Byte) { - this.reserve(1) { bbuf -> - bbuf.put(v) - } - } - - fun putShort(v: Short) { - this.reserve(2) { bbuf -> - bbuf.putShort(v) - } - } - - fun putInt(v: Int) { - this.reserve(4) { bbuf -> - bbuf.putInt(v) - } - } - - fun putLong(v: Long) { - this.reserve(8) { bbuf -> -*************** -*** 216,934 **** - } - } - - internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { - // TODO: maybe we can calculate some sort of initial size hint? - val buf = RustBufferBuilder() - try { - writeItem(v, buf) - return buf.finalize() - } catch (e: Throwable) { - buf.discard() - throw e - } - } - - // A handful of classes and functions to support the generated data structures. - // This would be a good candidate for isolating in its own ffi-support lib. - // Error runtime. - @Structure.FieldOrder("code", "error_buf") - internal open class RustCallStatus : Structure() { - @JvmField var code: Int = 0 - @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - - fun isSuccess(): Boolean { - return code == 0 - } - - fun isError(): Boolean { - return code == 1 - } - - fun isPanic(): Boolean { - return code == 2 - } - } - - class InternalException(message: String) : Exception(message) - - // Each top-level error class has a companion object that can lift the error from the call status's rust buffer - interface CallStatusErrorHandler { - fun lift(error_buf: RustBuffer.ByValue): E; - } - - // Helpers for calling Rust - // In practice we usually need to be synchronized to call this safely, so it doesn't - // synchronize itself - - // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err - private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { - var status = RustCallStatus(); - val return_value = callback(status) - if (status.isSuccess()) { - return return_value - } else if (status.isError()) { - throw errorHandler.lift(status.error_buf) - } else if (status.isPanic()) { - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if (status.error_buf.len > 0) { - throw InternalException(String.lift(status.error_buf)) - } else { - throw InternalException("Rust panic") - } - } else { - throw InternalException("Unknown rust call status: $status.code") - } - } - - // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR - object NullCallStatusErrorHandler: CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): InternalException { - RustBuffer.free(error_buf) - return InternalException("Unexpected CALL_ERROR") - } - } - - // Call a rust function that returns a plain value - private inline fun rustCall(callback: (RustCallStatus) -> U): U { - return rustCallWithError(NullCallStatusErrorHandler, callback); - } - - // Contains loading, initialization code, - // and the FFI Function declarations in a com.sun.jna.Library. - @Synchronized - private fun findLibraryName(componentName: String): String { - val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") - if (libOverride != null) { - return libOverride - } - return "bdkffi" - } - - private inline fun loadIndirect( - componentName: String - ): Lib { - return Native.load(findLibraryName(componentName), Lib::class.java) - } - - // A JNA Library to expose the extern-C FFI definitions. - // This is an implementation detail which will be called internally by the public API. - - internal interface _UniFFILib : Library { - companion object { - internal val INSTANCE: _UniFFILib by lazy { - loadIndirect<_UniFFILib>(componentName = "bdk") - .also { lib: _UniFFILib -> - FfiConverterCallbackInterfaceBdkProgress.register(lib) - } - - } - } - - fun ffi_bdk_6983_Wallet_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_6983_Wallet_new(descriptor: RustBuffer.ByValue,change_descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_6983_Wallet_get_new_address(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_6983_Wallet_get_last_unused_address(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_6983_Wallet_get_balance(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Long - - fun bdk_6983_Wallet_sign(ptr: Pointer,psbt: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_6983_Wallet_get_transactions(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_6983_Wallet_get_network(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_6983_Wallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_6983_Wallet_broadcast(ptr: Pointer,psbt: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_6983_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_6983_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long,fee_rate: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_6983_PartiallySignedBitcoinTransaction_deserialize(psbt_base64: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_6983_PartiallySignedBitcoinTransaction_serialize(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_6983_BdkProgress_init_callback(callback_stub: ForeignCallback, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_6983_generate_extended_key(network: RustBuffer.ByValue,word_count: RustBuffer.ByValue,password: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_6983_restore_extended_key(network: RustBuffer.ByValue,mnemonic: RustBuffer.ByValue,password: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_6983_rustbuffer_alloc(size: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_6983_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_6983_rustbuffer_free(buf: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_6983_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - - } - - // Public interface members begin here. - - // Interface implemented by anything that can contain an object reference. - // - // Such types expose a `destroy()` method that must be called to cleanly - // dispose of the contained objects. Failure to call this method may result - // in memory leaks. - // - // The easiest way to ensure this method is called is to use the `.use` - // helper method to execute a block and destroy the object at the end. - interface Disposable { - fun destroy() - companion object { - fun destroy(vararg args: Any?) { - args.filterIsInstance() - .forEach(Disposable::destroy) - } - } - } - - inline fun T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } - - // The base class for all UniFFI Object types. - // - // This class provides core operations for working with the Rust `Arc` pointer to - // the live Rust struct on the other side of the FFI. - // - // There's some subtlety here, because we have to be careful not to operate on a Rust - // struct after it has been dropped, and because we must expose a public API for freeing - // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: - // - // * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. - // Method calls need to read this pointer from the object's state and pass it in to - // the Rust FFI. - // - // * When an `FFIObject` is no longer needed, its pointer should be passed to a - // special destructor function provided by the Rust FFI, which will drop the - // underlying Rust struct. - // - // * Given an `FFIObject` instance, calling code is expected to call the special - // `destroy` method in order to free it after use, either by calling it explicitly - // or by using a higher-level helper like the `use` method. Failing to do so will - // leak the underlying Rust struct. - // - // * We can't assume that calling code will do the right thing, and must be prepared - // to handle Kotlin method calls executing concurrently with or even after a call to - // `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. - // - // * We must never allow Rust code to operate on the underlying Rust struct after - // the destructor has been called, and must never call the destructor more than once. - // Doing so may trigger memory unsafety. - // - // If we try to implement this with mutual exclusion on access to the pointer, there is the - // possibility of a race between a method call and a concurrent call to `destroy`: - // - // * Thread A starts a method call, reads the value of the pointer, but is interrupted - // before it can pass the pointer over the FFI to Rust. - // * Thread B calls `destroy` and frees the underlying Rust struct. - // * Thread A resumes, passing the already-read pointer value to Rust and triggering - // a use-after-free. - // - // One possible solution would be to use a `ReadWriteLock`, with each method call taking - // a read lock (and thus allowed to run concurrently) and the special `destroy` method - // taking a write lock (and thus blocking on live method calls). However, we aim not to - // generate methods with any hidden blocking semantics, and a `destroy` method that might - // block if called incorrectly seems to meet that bar. - // - // So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track - // the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` - // has been called. These are updated according to the following rules: - // - // * The initial value of the counter is 1, indicating a live object with no in-flight calls. - // The initial value for the flag is false. - // - // * At the start of each method call, we atomically check the counter. - // If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. - // If it is nonzero them we atomically increment it by 1 and proceed with the method call. - // - // * At the end of each method call, we atomically decrement and check the counter. - // If it has reached zero then we destroy the underlying Rust struct. - // - // * When `destroy` is called, we atomically flip the flag from false to true. - // If the flag was already true we silently fail. - // Otherwise we atomically decrement and check the counter. - // If it has reached zero then we destroy the underlying Rust struct. - // - // Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, - // and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. - // - // The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been - // called *and* all in-flight method calls have completed, avoiding violating any of the expectations - // of the underlying Rust code. - // - // In the future we may be able to replace some of this with automatic finalization logic, such as using - // the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is - // invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also - // possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], - // so there would still be some complexity here). - // - // Sigh...all of this for want of a robust finalization mechanism. - // - // [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 - // - abstract class FFIObject( - protected val pointer: Pointer - ): Disposable, AutoCloseable { - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - open protected fun freeRustArcPtr() { - // To be overridden in subclasses. - } - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.pointer) - } finally { - // This decrement aways matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } - } - internal typealias Handle = Long - internal class ConcurrentHandleMap( - private val leftMap: MutableMap = mutableMapOf(), - private val rightMap: MutableMap = mutableMapOf() - ) { - private val lock = java.util.concurrent.locks.ReentrantLock() - private val currentHandle = AtomicLong(0L) - private val stride = 1L - - fun insert(obj: T): Handle = - lock.withLock { - rightMap[obj] ?: - currentHandle.getAndAdd(stride) - .also { handle -> - leftMap[handle] = obj - rightMap[obj] = handle - } - } - - fun get(handle: Handle) = lock.withLock { - leftMap[handle] - } - - fun delete(handle: Handle) { - this.remove(handle) - } - - fun remove(handle: Handle): T? = - lock.withLock { - leftMap.remove(handle)?.let { obj -> - rightMap.remove(obj) - obj - } - } - } - - interface ForeignCallback : com.sun.jna.Callback { - public fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int - } - - // Magic number for the Rust proxy to call using the same mechanism as every other method, - // to free the callback once it's dropped by Rust. - internal const val IDX_CALLBACK_FREE = 0 - - internal abstract class FfiConverterCallbackInterface( - protected val foreignCallback: ForeignCallback - ) { - val handleMap = ConcurrentHandleMap() - - // Registers the foreign callback with the Rust side. - // This method is generated for each callback interface. - abstract fun register(lib: _UniFFILib) - - fun drop(handle: Handle): RustBuffer.ByValue { - return handleMap.remove(handle).let { RustBuffer.ByValue() } - } - - fun lift(n: Handle) = handleMap.get(n) - - fun read(buf: ByteBuffer) = lift(buf.getLong()) - - fun lower(v: CallbackInterface) = - handleMap.insert(v).also { - assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } - } - - fun write(v: CallbackInterface, buf: RustBufferBuilder) = - buf.putLong(lower(v)) - } - - - - enum class Network { - BITCOIN,TESTNET,SIGNET,REGTEST; - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): Network { - return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) } - } - - internal fun read(buf: ByteBuffer) = - try { values()[buf.getInt() - 1] } - catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - buf.putInt(this.ordinal + 1) - } - } - - - - - - - - sealed class DatabaseConfig { - object Memory : DatabaseConfig() - - data class Sled( - val config: SledDbConfiguration - ) : DatabaseConfig() - - data class Sqlite( - val config: SqliteDbConfiguration - ) : DatabaseConfig() -- - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig { - return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): DatabaseConfig { - return when(buf.getInt()) { - 1 -> DatabaseConfig.Memory - 2 -> DatabaseConfig.Sled( - SledDbConfiguration.read(buf) - ) - 3 -> DatabaseConfig.Sqlite( - SqliteDbConfiguration.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is DatabaseConfig.Memory -> { - buf.putInt(1) - - } - is DatabaseConfig.Sled -> { - buf.putInt(2) - this.config.write(buf) - - } - is DatabaseConfig.Sqlite -> { - buf.putInt(3) - this.config.write(buf) - - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - - - - } - - - - - - - - sealed class Transaction { - - data class Unconfirmed( - val details: TransactionDetails - ) : Transaction() - - data class Confirmed( - val details: TransactionDetails, - val confirmation: BlockTime - ) : Transaction() - - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): Transaction { - return liftFromRustBuffer(rbuf) { buf -> Transaction.read(buf) } - } - - internal fun read(buf: ByteBuffer): Transaction { - return when(buf.getInt()) { - 1 -> Transaction.Unconfirmed( - TransactionDetails.read(buf) - ) - 2 -> Transaction.Confirmed( - TransactionDetails.read(buf), - BlockTime.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is Transaction.Unconfirmed -> { - buf.putInt(1) - this.details.write(buf) - - } - is Transaction.Confirmed -> { - buf.putInt(2) - this.details.write(buf) - this.confirmation.write(buf) - - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - - - - } - - - - - - - - sealed class BlockchainConfig { - - data class Electrum( - val config: ElectrumConfig - ) : BlockchainConfig() - - data class Esplora( - val config: EsploraConfig - ) : BlockchainConfig() - - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig { - return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): BlockchainConfig { - return when(buf.getInt()) { - 1 -> BlockchainConfig.Electrum( - ElectrumConfig.read(buf) - ) - 2 -> BlockchainConfig.Esplora( - EsploraConfig.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is BlockchainConfig.Electrum -> { - buf.putInt(1) - this.config.write(buf) - - } - is BlockchainConfig.Esplora -> { - buf.putInt(2) - this.config.write(buf) - - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - - - - } - - - - - - enum class WordCount { - WORDS12,WORDS15,WORDS18,WORDS21,WORDS24; - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): WordCount { - return liftFromRustBuffer(rbuf) { buf -> WordCount.read(buf) } - } - - internal fun read(buf: ByteBuffer) = - try { values()[buf.getInt() - 1] } - catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - buf.putInt(this.ordinal + 1) - } - } - - - - @Throws(BdkException::class) - - fun generateExtendedKey(network: Network, wordCount: WordCount, password: String? ): ExtendedKeyInfo { - val _retval = - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_6983_generate_extended_key(network.lower(), wordCount.lower(), lowerOptionalString(password) ,status) - } - return ExtendedKeyInfo.lift(_retval) - } - - - @Throws(BdkException::class) - - fun restoreExtendedKey(network: Network, mnemonic: String, password: String? ): ExtendedKeyInfo { - val _retval = - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_6983_restore_extended_key(network.lower(), mnemonic.lower(), lowerOptionalString(password) ,status) - } - return ExtendedKeyInfo.lift(_retval) - } - - - public interface WalletInterface { - - fun getNewAddress(): String - - fun getLastUnusedAddress(): String - - @Throws(BdkException::class) - fun getBalance(): ULong - - @Throws(BdkException::class) - fun sign(psbt: PartiallySignedBitcoinTransaction ) - - @Throws(BdkException::class) - fun getTransactions(): List - - fun getNetwork(): Network - - @Throws(BdkException::class) - fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) - - @Throws(BdkException::class) - fun broadcast(psbt: PartiallySignedBitcoinTransaction ): Transaction - - } - ---- 229,980 ---- - } - } - - internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { - // TODO: maybe we can calculate some sort of initial size hint? - val buf = RustBufferBuilder() - try { - writeItem(v, buf) - return buf.finalize() - } catch (e: Throwable) { - buf.discard() - throw e - } - } - - // A handful of classes and functions to support the generated data structures. - // This would be a good candidate for isolating in its own ffi-support lib. - // Error runtime. - @Structure.FieldOrder("code", "error_buf") - internal open class RustCallStatus : Structure() { - @JvmField var code: Int = 0 - @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - - fun isSuccess(): Boolean { - return code == 0 - } - - fun isError(): Boolean { - return code == 1 - } - - fun isPanic(): Boolean { - return code == 2 - } - } - - class InternalException(message: String) : Exception(message) - - // Each top-level error class has a companion object that can lift the error from the call status's rust buffer - interface CallStatusErrorHandler { - fun lift(error_buf: RustBuffer.ByValue): E; - } - - // Helpers for calling Rust - // In practice we usually need to be synchronized to call this safely, so it doesn't - // synchronize itself - - // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err - private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { - var status = RustCallStatus(); - val return_value = callback(status) - if (status.isSuccess()) { - return return_value - } else if (status.isError()) { - throw errorHandler.lift(status.error_buf) - } else if (status.isPanic()) { - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if (status.error_buf.len > 0) { - throw InternalException(String.lift(status.error_buf)) - } else { - throw InternalException("Rust panic") - } - } else { - throw InternalException("Unknown rust call status: $status.code") - } - } - - // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR -+ /** -+ * @suppress -+ */ - object NullCallStatusErrorHandler: CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): InternalException { - RustBuffer.free(error_buf) - return InternalException("Unexpected CALL_ERROR") - } - } - - // Call a rust function that returns a plain value - private inline fun rustCall(callback: (RustCallStatus) -> U): U { - return rustCallWithError(NullCallStatusErrorHandler, callback); - } - - // Contains loading, initialization code, - // and the FFI Function declarations in a com.sun.jna.Library. - @Synchronized - private fun findLibraryName(componentName: String): String { - val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") - if (libOverride != null) { - return libOverride - } - return "bdkffi" - } - - private inline fun loadIndirect( - componentName: String - ): Lib { - return Native.load(findLibraryName(componentName), Lib::class.java) - } - - // A JNA Library to expose the extern-C FFI definitions. - // This is an implementation detail which will be called internally by the public API. - - internal interface _UniFFILib : Library { - companion object { - internal val INSTANCE: _UniFFILib by lazy { - loadIndirect<_UniFFILib>(componentName = "bdk") - .also { lib: _UniFFILib -> - FfiConverterCallbackInterfaceBdkProgress.register(lib) - } - - } - } - - fun ffi_bdk_6983_Wallet_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_6983_Wallet_new(descriptor: RustBuffer.ByValue,change_descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_6983_Wallet_get_new_address(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_6983_Wallet_get_last_unused_address(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_6983_Wallet_get_balance(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Long - - fun bdk_6983_Wallet_sign(ptr: Pointer,psbt: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_6983_Wallet_get_transactions(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_6983_Wallet_get_network(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_6983_Wallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_6983_Wallet_broadcast(ptr: Pointer,psbt: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_6983_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_6983_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long,fee_rate: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_6983_PartiallySignedBitcoinTransaction_deserialize(psbt_base64: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_6983_PartiallySignedBitcoinTransaction_serialize(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_6983_BdkProgress_init_callback(callback_stub: ForeignCallback, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_6983_generate_extended_key(network: RustBuffer.ByValue,word_count: RustBuffer.ByValue,password: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun bdk_6983_restore_extended_key(network: RustBuffer.ByValue,mnemonic: RustBuffer.ByValue,password: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_6983_rustbuffer_alloc(size: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_6983_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_6983_rustbuffer_free(buf: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_6983_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - - } - - // Public interface members begin here. - - // Interface implemented by anything that can contain an object reference. - // - // Such types expose a `destroy()` method that must be called to cleanly - // dispose of the contained objects. Failure to call this method may result - // in memory leaks. - // - // The easiest way to ensure this method is called is to use the `.use` - // helper method to execute a block and destroy the object at the end. -+ /** -+ * @suppress -+ */ - interface Disposable { - fun destroy() - companion object { - fun destroy(vararg args: Any?) { - args.filterIsInstance() - .forEach(Disposable::destroy) - } - } - } - -+ /** -+ * @suppress -+ */ - inline fun T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } - - // The base class for all UniFFI Object types. - // - // This class provides core operations for working with the Rust `Arc` pointer to - // the live Rust struct on the other side of the FFI. - // - // There's some subtlety here, because we have to be careful not to operate on a Rust - // struct after it has been dropped, and because we must expose a public API for freeing - // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: - // - // * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. - // Method calls need to read this pointer from the object's state and pass it in to - // the Rust FFI. - // - // * When an `FFIObject` is no longer needed, its pointer should be passed to a - // special destructor function provided by the Rust FFI, which will drop the - // underlying Rust struct. - // - // * Given an `FFIObject` instance, calling code is expected to call the special - // `destroy` method in order to free it after use, either by calling it explicitly - // or by using a higher-level helper like the `use` method. Failing to do so will - // leak the underlying Rust struct. - // - // * We can't assume that calling code will do the right thing, and must be prepared - // to handle Kotlin method calls executing concurrently with or even after a call to - // `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. - // - // * We must never allow Rust code to operate on the underlying Rust struct after - // the destructor has been called, and must never call the destructor more than once. - // Doing so may trigger memory unsafety. - // - // If we try to implement this with mutual exclusion on access to the pointer, there is the - // possibility of a race between a method call and a concurrent call to `destroy`: - // - // * Thread A starts a method call, reads the value of the pointer, but is interrupted - // before it can pass the pointer over the FFI to Rust. - // * Thread B calls `destroy` and frees the underlying Rust struct. - // * Thread A resumes, passing the already-read pointer value to Rust and triggering - // a use-after-free. - // - // One possible solution would be to use a `ReadWriteLock`, with each method call taking - // a read lock (and thus allowed to run concurrently) and the special `destroy` method - // taking a write lock (and thus blocking on live method calls). However, we aim not to - // generate methods with any hidden blocking semantics, and a `destroy` method that might - // block if called incorrectly seems to meet that bar. - // - // So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track - // the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` - // has been called. These are updated according to the following rules: - // - // * The initial value of the counter is 1, indicating a live object with no in-flight calls. - // The initial value for the flag is false. - // - // * At the start of each method call, we atomically check the counter. - // If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. - // If it is nonzero them we atomically increment it by 1 and proceed with the method call. - // - // * At the end of each method call, we atomically decrement and check the counter. - // If it has reached zero then we destroy the underlying Rust struct. - // - // * When `destroy` is called, we atomically flip the flag from false to true. - // If the flag was already true we silently fail. - // Otherwise we atomically decrement and check the counter. - // If it has reached zero then we destroy the underlying Rust struct. - // - // Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, - // and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. - // - // The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been - // called *and* all in-flight method calls have completed, avoiding violating any of the expectations - // of the underlying Rust code. - // - // In the future we may be able to replace some of this with automatic finalization logic, such as using - // the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is - // invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also - // possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], - // so there would still be some complexity here). - // - // Sigh...all of this for want of a robust finalization mechanism. - // - // [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 - // -+ /** -+ * @suppress -+ */ - abstract class FFIObject( - protected val pointer: Pointer - ): Disposable, AutoCloseable { - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - open protected fun freeRustArcPtr() { - // To be overridden in subclasses. - } - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.pointer) - } finally { - // This decrement aways matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } - } - internal typealias Handle = Long - internal class ConcurrentHandleMap( - private val leftMap: MutableMap = mutableMapOf(), - private val rightMap: MutableMap = mutableMapOf() - ) { - private val lock = java.util.concurrent.locks.ReentrantLock() - private val currentHandle = AtomicLong(0L) - private val stride = 1L - - fun insert(obj: T): Handle = - lock.withLock { - rightMap[obj] ?: - currentHandle.getAndAdd(stride) - .also { handle -> - leftMap[handle] = obj - rightMap[obj] = handle - } - } - - fun get(handle: Handle) = lock.withLock { - leftMap[handle] - } - - fun delete(handle: Handle) { - this.remove(handle) - } - - fun remove(handle: Handle): T? = - lock.withLock { - leftMap.remove(handle)?.let { obj -> - rightMap.remove(obj) - obj - } - } - } - -+ /** -+ * @suppress -+ */ - interface ForeignCallback : com.sun.jna.Callback { - public fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int - } - - // Magic number for the Rust proxy to call using the same mechanism as every other method, - // to free the callback once it's dropped by Rust. - internal const val IDX_CALLBACK_FREE = 0 - - internal abstract class FfiConverterCallbackInterface( - protected val foreignCallback: ForeignCallback - ) { - val handleMap = ConcurrentHandleMap() - - // Registers the foreign callback with the Rust side. - // This method is generated for each callback interface. - abstract fun register(lib: _UniFFILib) - - fun drop(handle: Handle): RustBuffer.ByValue { - return handleMap.remove(handle).let { RustBuffer.ByValue() } - } - - fun lift(n: Handle) = handleMap.get(n) - - fun read(buf: ByteBuffer) = lift(buf.getLong()) - - fun lower(v: CallbackInterface) = - handleMap.insert(v).also { - assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } - } - - fun write(v: CallbackInterface, buf: RustBufferBuilder) = - buf.putLong(lower(v)) - } - - - - enum class Network { - BITCOIN,TESTNET,SIGNET,REGTEST; - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): Network { - return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) } - } - - internal fun read(buf: ByteBuffer) = - try { values()[buf.getInt() - 1] } - catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - buf.putInt(this.ordinal + 1) - } - } - - - - - - - - sealed class DatabaseConfig { - object Memory : DatabaseConfig() - - data class Sled( - val config: SledDbConfiguration - ) : DatabaseConfig() - - data class Sqlite( - val config: SqliteDbConfiguration - ) : DatabaseConfig() - -+ -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig { - return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): DatabaseConfig { - return when(buf.getInt()) { - 1 -> DatabaseConfig.Memory - 2 -> DatabaseConfig.Sled( - SledDbConfiguration.read(buf) - ) - 3 -> DatabaseConfig.Sqlite( - SqliteDbConfiguration.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is DatabaseConfig.Memory -> { - buf.putInt(1) - - } - is DatabaseConfig.Sled -> { - buf.putInt(2) - this.config.write(buf) - - } - is DatabaseConfig.Sqlite -> { - buf.putInt(3) - this.config.write(buf) - - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - - - - } - - - - - - - - sealed class Transaction { - - data class Unconfirmed( - val details: TransactionDetails - ) : Transaction() - - data class Confirmed( - val details: TransactionDetails, - val confirmation: BlockTime - ) : Transaction() - - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): Transaction { - return liftFromRustBuffer(rbuf) { buf -> Transaction.read(buf) } - } - - internal fun read(buf: ByteBuffer): Transaction { - return when(buf.getInt()) { - 1 -> Transaction.Unconfirmed( - TransactionDetails.read(buf) - ) - 2 -> Transaction.Confirmed( - TransactionDetails.read(buf), - BlockTime.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is Transaction.Unconfirmed -> { - buf.putInt(1) - this.details.write(buf) - - } - is Transaction.Confirmed -> { - buf.putInt(2) - this.details.write(buf) - this.confirmation.write(buf) - - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - - - - } - - - - - - - -+ /** -+ * Sealed class that can be of either blockchain configuration defined by the library. -+ */ - sealed class BlockchainConfig { - - data class Electrum( - val config: ElectrumConfig - ) : BlockchainConfig() - - data class Esplora( - val config: EsploraConfig - ) : BlockchainConfig() - - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig { - return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): BlockchainConfig { - return when(buf.getInt()) { - 1 -> BlockchainConfig.Electrum( - ElectrumConfig.read(buf) - ) - 2 -> BlockchainConfig.Esplora( - EsploraConfig.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is BlockchainConfig.Electrum -> { - buf.putInt(1) - this.config.write(buf) - - } - is BlockchainConfig.Esplora -> { - buf.putInt(2) - this.config.write(buf) - - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - - - - } - - - - - - enum class WordCount { - WORDS12,WORDS15,WORDS18,WORDS21,WORDS24; - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): WordCount { - return liftFromRustBuffer(rbuf) { buf -> WordCount.read(buf) } - } - - internal fun read(buf: ByteBuffer) = - try { values()[buf.getInt() - 1] } - catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - buf.putInt(this.ordinal + 1) - } - } - - - - @Throws(BdkException::class) - - fun generateExtendedKey(network: Network, wordCount: WordCount, password: String? ): ExtendedKeyInfo { - val _retval = - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_6983_generate_extended_key(network.lower(), wordCount.lower(), lowerOptionalString(password) ,status) - } - return ExtendedKeyInfo.lift(_retval) - } - - - @Throws(BdkException::class) - - fun restoreExtendedKey(network: Network, mnemonic: String, password: String? ): ExtendedKeyInfo { - val _retval = - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_6983_restore_extended_key(network.lower(), mnemonic.lower(), lowerOptionalString(password) ,status) - } - return ExtendedKeyInfo.lift(_retval) - } - - - public interface WalletInterface { - - fun getNewAddress(): String - - fun getLastUnusedAddress(): String - - @Throws(BdkException::class) - fun getBalance(): ULong - - @Throws(BdkException::class) - fun sign(psbt: PartiallySignedBitcoinTransaction ) - - @Throws(BdkException::class) - fun getTransactions(): List - - fun getNetwork(): Network - - @Throws(BdkException::class) - fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) - - @Throws(BdkException::class) - fun broadcast(psbt: PartiallySignedBitcoinTransaction ): Transaction - - } - -*************** -*** 1034,1418 **** ---- 1080,1491 ---- - } - }.let { - Transaction.lift(it) - } - - - - companion object { - internal fun lift(ptr: Pointer): Wallet { - return Wallet(ptr) - } - - internal fun read(buf: ByteBuffer): Wallet { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return Wallet.lift(Pointer(buf.getLong())) - } - - - } - } - - public interface PartiallySignedBitcoinTransactionInterface { - - fun serialize(): String - - } - - class PartiallySignedBitcoinTransaction( - pointer: Pointer - ) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface { - constructor(wallet: Wallet, recipient: String, amount: ULong, feeRate: Float? ) : - this( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_6983_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower(), lowerOptionalFloat(feeRate) ,status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { - rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_6983_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun serialize(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_6983_PartiallySignedBitcoinTransaction_serialize(it, status) - } - }.let { - String.lift(it) - } - - - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(ptr: Pointer): PartiallySignedBitcoinTransaction { - return PartiallySignedBitcoinTransaction(ptr) - } - - internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong())) - } - - fun deserialize(psbtBase64: String ): PartiallySignedBitcoinTransaction = - PartiallySignedBitcoinTransaction( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_6983_PartiallySignedBitcoinTransaction_deserialize(psbtBase64.lower() ,status) - }) - - } - } - - data class SledDbConfiguration ( - var path: String, - var treeName: String - ) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): SledDbConfiguration { - return liftFromRustBuffer(rbuf) { buf -> SledDbConfiguration.read(buf) } - } - - internal fun read(buf: ByteBuffer): SledDbConfiguration { - return SledDbConfiguration( - String.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.path.write(buf) - - this.treeName.write(buf) - - } - - - - } - - data class SqliteDbConfiguration ( - var path: String - ) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): SqliteDbConfiguration { - return liftFromRustBuffer(rbuf) { buf -> SqliteDbConfiguration.read(buf) } - } - - internal fun read(buf: ByteBuffer): SqliteDbConfiguration { - return SqliteDbConfiguration( - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.path.write(buf) - - } - - - - } - - data class TransactionDetails ( - var fees: ULong?, - var received: ULong, - var sent: ULong, - var txid: String - ) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): TransactionDetails { - return liftFromRustBuffer(rbuf) { buf -> TransactionDetails.read(buf) } - } - - internal fun read(buf: ByteBuffer): TransactionDetails { - return TransactionDetails( - readOptionalULong(buf), - ULong.read(buf), - ULong.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - writeOptionalULong(this.fees, buf) - - this.received.write(buf) - - this.sent.write(buf) - - this.txid.write(buf) - - } - - - - } - -+ /** -+ * Block height and timestamp of a block -+ */ - data class BlockTime ( - var height: UInt, - var timestamp: ULong - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): BlockTime { - return liftFromRustBuffer(rbuf) { buf -> BlockTime.read(buf) } - } - - internal fun read(buf: ByteBuffer): BlockTime { - return BlockTime( - UInt.read(buf), - ULong.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.height.write(buf) - - this.timestamp.write(buf) - - } - - - - } - -+ /** -+ * Configuration for an ElectrumBlockchain -+ * -+ * @property url URL of the Electrum server (such as ElectrumX, Esplora, BWT). May start with `ssl://` or `tcp://` and include a port, `e.g. ssl://electrum.blockstream.info:60002` -+ * @property socks5 URL of the socks5 proxy server or a Tor service -+ * @property retry Request retry count -+ * @property timeout Request timeout in seconds -+ * @property stopGap Stop searching addresses for transactions after finding an unused gap of this length -+ */ - data class ElectrumConfig ( - var url: String, - var socks5: String?, - var retry: UByte, - var timeout: UByte?, - var stopGap: ULong - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): ElectrumConfig { - return liftFromRustBuffer(rbuf) { buf -> ElectrumConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): ElectrumConfig { - return ElectrumConfig( - String.read(buf), - readOptionalString(buf), - UByte.read(buf), - readOptionalUByte(buf), - ULong.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.url.write(buf) - - writeOptionalString(this.socks5, buf) - - this.retry.write(buf) - - writeOptionalUByte(this.timeout, buf) - - this.stopGap.write(buf) - - } - - - - } - - data class EsploraConfig ( - var baseUrl: String, - var proxy: String?, - var timeoutRead: ULong, - var timeoutWrite: ULong, - var stopGap: ULong - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): EsploraConfig { - return liftFromRustBuffer(rbuf) { buf -> EsploraConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): EsploraConfig { - return EsploraConfig( - String.read(buf), - readOptionalString(buf), - ULong.read(buf), - ULong.read(buf), - ULong.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.baseUrl.write(buf) - - writeOptionalString(this.proxy, buf) - - this.timeoutRead.write(buf) - - this.timeoutWrite.write(buf) - - this.stopGap.write(buf) - - } - - - - } - - data class ExtendedKeyInfo ( - var mnemonic: String, - var xprv: String, - var fingerprint: String - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): ExtendedKeyInfo { - return liftFromRustBuffer(rbuf) { buf -> ExtendedKeyInfo.read(buf) } - } - - internal fun read(buf: ByteBuffer): ExtendedKeyInfo { - return ExtendedKeyInfo( - String.read(buf), - String.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.mnemonic.write(buf) - - this.xprv.write(buf) - - this.fingerprint.write(buf) - - } - - - - } - - - - sealed class BdkException(message: String): Exception(message) { - // Each variant is a nested class - // Flat enums carries a string error message, so no special implementation is necessary. - class InvalidU32Bytes(message: String) : BdkException(message) - class Generic(message: String) : BdkException(message) - class ScriptDoesntHaveAddressForm(message: String) : BdkException(message) - class NoRecipients(message: String) : BdkException(message) - class NoUtxosSelected(message: String) : BdkException(message) - class OutputBelowDustLimit(message: String) : BdkException(message) - class InsufficientFunds(message: String) : BdkException(message) - class BnBTotalTriesExceeded(message: String) : BdkException(message) - class BnBNoExactMatch(message: String) : BdkException(message) - class UnknownUtxo(message: String) : BdkException(message) - class TransactionNotFound(message: String) : BdkException(message) - class TransactionConfirmed(message: String) : BdkException(message) - class IrreplaceableTransaction(message: String) : BdkException(message) - class FeeRateTooLow(message: String) : BdkException(message) - class FeeTooLow(message: String) : BdkException(message) - class FeeRateUnavailable(message: String) : BdkException(message) - class MissingKeyOrigin(message: String) : BdkException(message) - class Key(message: String) : BdkException(message) - class ChecksumMismatch(message: String) : BdkException(message) - class SpendingPolicyRequired(message: String) : BdkException(message) - class InvalidPolicyPathException(message: String) : BdkException(message) - class Signer(message: String) : BdkException(message) - class InvalidNetwork(message: String) : BdkException(message) - class InvalidProgressValue(message: String) : BdkException(message) - class ProgressUpdateException(message: String) : BdkException(message) - class InvalidOutpoint(message: String) : BdkException(message) - class Descriptor(message: String) : BdkException(message) - class AddressValidator(message: String) : BdkException(message) - class Encode(message: String) : BdkException(message) - class Miniscript(message: String) : BdkException(message) - class Bip32(message: String) : BdkException(message) - class Secp256k1(message: String) : BdkException(message) - class Json(message: String) : BdkException(message) - class Hex(message: String) : BdkException(message) diff --git a/docs.patch b/docs.patch new file mode 100644 index 0000000..21cb4e9 --- /dev/null +++ b/docs.patch @@ -0,0 +1,804 @@ +*** bdk-0.5.1-undocumented.kt 2022-03-19 10:29:15.000000000 -0400 +--- bdk-0.5.1-documented.kt 2022-03-19 10:27:57.000000000 -0400 +*************** +*** 17,131 **** +--- 17,144 ---- + // compile the Rust component. The easiest way to ensure this is to bundle the Kotlin + // helpers directly inline like we're doing here. + + import com.sun.jna.Library + import com.sun.jna.Native + import com.sun.jna.Pointer + import com.sun.jna.Structure + import com.sun.jna.ptr.ByReference + import java.nio.ByteBuffer + import java.nio.ByteOrder + import java.util.concurrent.atomic.AtomicBoolean + import java.util.concurrent.atomic.AtomicLong + import java.util.concurrent.locks.ReentrantLock + import kotlin.concurrent.withLock + + // The Rust Buffer and 3 templated methods (alloc, free, reserve). + // This is a helper for safely working with byte buffers returned from the Rust code. + // A rust-owned buffer is represented by its capacity, its current length, and a + // pointer to the underlying data. + ++ ++ /** ++ * @suppress ++ */ + @Structure.FieldOrder("capacity", "len", "data") + open class RustBuffer : Structure() { + @JvmField var capacity: Int = 0 + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : RustBuffer(), Structure.ByValue + class ByReference : RustBuffer(), Structure.ByReference + + companion object { + internal fun alloc(size: Int = 0) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_2b7a_rustbuffer_alloc(size, status).also { + if(it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") + } + } + } + + internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_2b7a_rustbuffer_free(buf, status) + } + + internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_2b7a_rustbuffer_reserve(buf, additional, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } + } + + /** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + */ ++ /** ++ * @suppress ++ */ + class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setInt(0, value.capacity) + pointer.setInt(4, value.len) + pointer.setPointer(8, value.data) + } + } + + // This is a helper for safely passing byte references into the rust code. + // It's not actually used at the moment, because there aren't many things that you + // can take a direct pointer to in the JVM, and if we're going to copy something + // then we might as well copy it into a `RustBuffer`. But it's here for API + // completeness. + ++ /** ++ * @suppress ++ */ + @Structure.FieldOrder("len", "data") + open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue + } + + + // A helper for structured writing of data into a `RustBuffer`. + // This is very similar to `java.nio.ByteBuffer` but it knows how to grow + // the underlying `RustBuffer` on demand. + // + // TODO: we should benchmark writing things into a `RustBuffer` versus building + // up a bytearray and then copying it across. + ++ /** ++ * @suppress ++ */ + class RustBufferBuilder() { + var rbuf = RustBuffer.ByValue() + var bbuf: ByteBuffer? = null + + init { + val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size + rbuf.writeField("len", 0) + this.setRustBuffer(rbuf) + } + + internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { + this.rbuf = rbuf + this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + it.position(rbuf.len) + } + } + + fun finalize() : RustBuffer.ByValue { + val rbuf = this.rbuf +*************** +*** 266,305 **** +--- 279,321 ---- + val return_value = callback(status) + if (status.isSuccess()) { + return return_value + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(String.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } + } + + // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR ++ /** ++ * @suppress ++ */ + object NullCallStatusErrorHandler: CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } + } + + // Call a rust function that returns a plain value + private inline fun rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); + } + + // Contains loading, initialization code, + // and the FFI Function declarations in a com.sun.jna.Library. + @Synchronized + private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } +*************** +*** 406,455 **** +--- 422,477 ---- + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_2b7a_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + + } + + // Public interface members begin here. + + // Interface implemented by anything that can contain an object reference. + // + // Such types expose a `destroy()` method that must be called to cleanly + // dispose of the contained objects. Failure to call this method may result + // in memory leaks. + // + // The easiest way to ensure this method is called is to use the `.use` + // helper method to execute a block and destroy the object at the end. ++ /** ++ * @suppress ++ */ + interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } + } + ++ /** ++ * @suppress ++ */ + inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + + // The base class for all UniFFI Object types. + // + // This class provides core operations for working with the Rust `Arc` pointer to + // the live Rust struct on the other side of the FFI. + // + // There's some subtlety here, because we have to be careful not to operate on a Rust + // struct after it has been dropped, and because we must expose a public API for freeing + // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +*************** +*** 509,548 **** +--- 531,573 ---- + // Otherwise we atomically decrement and check the counter. + // If it has reached zero then we destroy the underlying Rust struct. + // + // Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, + // and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. + // + // The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been + // called *and* all in-flight method calls have completed, avoiding violating any of the expectations + // of the underlying Rust code. + // + // In the future we may be able to replace some of this with automatic finalization logic, such as using + // the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is + // invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also + // possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], + // so there would still be some complexity here). + // + // Sigh...all of this for want of a robust finalization mechanism. + // + // [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 + // ++ /** ++ * @suppress ++ */ + abstract class FFIObject( + protected val pointer: Pointer + ): Disposable, AutoCloseable { + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } +*************** +*** 595,712 **** +--- 620,746 ---- + } + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } + } + ++ /** ++ * @suppress ++ */ + interface ForeignCallback : com.sun.jna.Callback { + public fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int + } + + // Magic number for the Rust proxy to call using the same mechanism as every other method, + // to free the callback once it's dropped by Rust. + internal const val IDX_CALLBACK_FREE = 0 + + internal abstract class FfiConverterCallbackInterface( + protected val foreignCallback: ForeignCallback + ) { + val handleMap = ConcurrentHandleMap() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + abstract fun register(lib: _UniFFILib) + + fun drop(handle: Handle): RustBuffer.ByValue { + return handleMap.remove(handle).let { RustBuffer.ByValue() } + } + + fun lift(n: Handle) = handleMap.get(n) + + fun read(buf: ByteBuffer) = lift(buf.getLong()) + + fun lower(v: CallbackInterface) = + handleMap.insert(v).also { + assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + fun write(v: CallbackInterface, buf: RustBufferBuilder) = + buf.putLong(lower(v)) + } + + + + enum class Network { + BITCOIN,TESTNET,SIGNET,REGTEST; + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): Network { + return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) } + } + + internal fun read(buf: ByteBuffer) = + try { values()[buf.getInt() - 1] } + catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + buf.putInt(this.ordinal + 1) + } + } + + + + + + + + sealed class DatabaseConfig { + object Memory : DatabaseConfig() + + data class Sled( + val config: SledDbConfiguration + ) : DatabaseConfig() + + data class Sqlite( + val config: SqliteDbConfiguration + ) : DatabaseConfig() + + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig { + return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): DatabaseConfig { + return when(buf.getInt()) { + 1 -> DatabaseConfig.Memory + 2 -> DatabaseConfig.Sled( + SledDbConfiguration.read(buf) + ) + 3 -> DatabaseConfig.Sqlite( + SqliteDbConfiguration.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { +*************** +*** 737,776 **** +--- 771,813 ---- + } + + + + + + + + sealed class Transaction { + + data class Unconfirmed( + val details: TransactionDetails + ) : Transaction() + + data class Confirmed( + val details: TransactionDetails, + val confirmation: BlockTime + ) : Transaction() + + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): Transaction { + return liftFromRustBuffer(rbuf) { buf -> Transaction.read(buf) } + } + + internal fun read(buf: ByteBuffer): Transaction { + return when(buf.getInt()) { + 1 -> Transaction.Unconfirmed( + TransactionDetails.read(buf) + ) + 2 -> Transaction.Confirmed( + TransactionDetails.read(buf), + BlockTime.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { +*************** +*** 786,836 **** +--- 823,879 ---- + } + is Transaction.Confirmed -> { + buf.putInt(2) + this.details.write(buf) + this.confirmation.write(buf) + + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + + + + } + + + + + + + ++ /** ++ * Sealed class that can be of either blockchain configuration defined by the library. ++ */ + sealed class BlockchainConfig { + + data class Electrum( + val config: ElectrumConfig + ) : BlockchainConfig() + + data class Esplora( + val config: EsploraConfig + ) : BlockchainConfig() + + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig { + return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): BlockchainConfig { + return when(buf.getInt()) { + 1 -> BlockchainConfig.Electrum( + ElectrumConfig.read(buf) + ) + 2 -> BlockchainConfig.Esplora( + EsploraConfig.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) +*************** +*** 845,884 **** +--- 888,930 ---- + } + is BlockchainConfig.Esplora -> { + buf.putInt(2) + this.config.write(buf) + + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + + + + } + + + + + + enum class WordCount { + WORDS12,WORDS15,WORDS18,WORDS21,WORDS24; + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): WordCount { + return liftFromRustBuffer(rbuf) { buf -> WordCount.read(buf) } + } + + internal fun read(buf: ByteBuffer) = + try { values()[buf.getInt() - 1] } + catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + buf.putInt(this.ordinal + 1) + } + } +*************** +*** 1084,1123 **** +--- 1130,1172 ---- + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + override fun serialize(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_2b7a_PartiallySignedBitcoinTransaction_serialize(it, status) + } + }.let { + String.lift(it) + } + + + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(ptr: Pointer): PartiallySignedBitcoinTransaction { + return PartiallySignedBitcoinTransaction(ptr) + } + + internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong())) + } + + fun deserialize(psbtBase64: String ): PartiallySignedBitcoinTransaction = + PartiallySignedBitcoinTransaction( + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_2b7a_PartiallySignedBitcoinTransaction_deserialize(psbtBase64.lower() ,status) + }) + + } + } + +*************** +*** 1204,1282 **** +--- 1253,1349 ---- + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + writeOptionalULong(this.fees, buf) + + this.received.write(buf) + + this.sent.write(buf) + + this.txid.write(buf) + + } + + + + } + ++ /** ++ * Block height and timestamp of a block ++ */ + data class BlockTime ( + var height: UInt, + var timestamp: ULong + ) { ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): BlockTime { + return liftFromRustBuffer(rbuf) { buf -> BlockTime.read(buf) } + } + + internal fun read(buf: ByteBuffer): BlockTime { + return BlockTime( + UInt.read(buf), + ULong.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.height.write(buf) + + this.timestamp.write(buf) + + } + + + + } + ++ /** ++ * Configuration for an ElectrumBlockchain ++ * ++ * @property url URL of the Electrum server (such as ElectrumX, Esplora, BWT). May start with `ssl://` or `tcp://` and include a port, `e.g. ssl://electrum.blockstream.info:60002` ++ * @property socks5 URL of the socks5 proxy server or a Tor service ++ * @property retry Request retry count ++ * @property timeout Request timeout in seconds ++ * @property stopGap Stop searching addresses for transactions after finding an unused gap of this length ++ */ + data class ElectrumConfig ( + var url: String, + var socks5: String?, + var retry: UByte, + var timeout: UByte?, + var stopGap: ULong + ) { ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): ElectrumConfig { + return liftFromRustBuffer(rbuf) { buf -> ElectrumConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): ElectrumConfig { + return ElectrumConfig( + String.read(buf), + readOptionalString(buf), + UByte.read(buf), + readOptionalUByte(buf), + ULong.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + +*************** +*** 1287,1326 **** +--- 1354,1396 ---- + + this.retry.write(buf) + + writeOptionalUByte(this.timeout, buf) + + this.stopGap.write(buf) + + } + + + + } + + data class EsploraConfig ( + var baseUrl: String, + var proxy: String?, + var timeoutRead: ULong, + var timeoutWrite: ULong, + var stopGap: ULong + ) { ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): EsploraConfig { + return liftFromRustBuffer(rbuf) { buf -> EsploraConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): EsploraConfig { + return EsploraConfig( + String.read(buf), + readOptionalString(buf), + ULong.read(buf), + ULong.read(buf), + ULong.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + +*************** +*** 1329,1368 **** +--- 1399,1441 ---- + + writeOptionalString(this.proxy, buf) + + this.timeoutRead.write(buf) + + this.timeoutWrite.write(buf) + + this.stopGap.write(buf) + + } + + + + } + + data class ExtendedKeyInfo ( + var mnemonic: String, + var xprv: String, + var fingerprint: String + ) { ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): ExtendedKeyInfo { + return liftFromRustBuffer(rbuf) { buf -> ExtendedKeyInfo.read(buf) } + } + + internal fun read(buf: ByteBuffer): ExtendedKeyInfo { + return ExtendedKeyInfo( + String.read(buf), + String.read(buf), + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.mnemonic.write(buf) diff --git a/jvm/Module.md b/jvm/Module.md index 64d7e82..f26d669 100644 --- a/jvm/Module.md +++ b/jvm/Module.md @@ -1,4 +1,4 @@ # Module bdk-jvm -The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for the JVM. Current version: `0.4.0`. +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for the JVM. Current version: `0.5.1`. # Package org.bitcoindevkit From bb9d0869acbb58142f2d5d039d87b227c535ef98 Mon Sep 17 00:00:00 2001 From: Kirill Zhukov Date: Sun, 20 Mar 2022 14:30:39 -0700 Subject: [PATCH 193/272] Update AGP to 7.1.2. AGP 7.1.0 adds maven-publish APIs for making publishing javadocs and sources for Android libraries very easy. We can use that to resolve #32. Changelog: https://developer.android.com/studio/releases/gradle-plugin#versioning-update. --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6271ac9..81e5d68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ buildscript { mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:7.0.4") + classpath("com.android.tools.build:gradle:7.1.2") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") } } From aea8d703e105ecf588f9465da08ddd77a7f2b125 Mon Sep 17 00:00:00 2001 From: Kirill Zhukov Date: Sun, 20 Mar 2022 15:45:01 -0700 Subject: [PATCH 194/272] Enable publishing sources and javadocs for bdk-android. This resolves bitcoindevkit#32. Using new API from AGP 7.1.0 to generate and publish Android sources and javadocs. https://developer.android.com/studio/releases/gradle-plugin#publish-javadoc-jar https://developer.android.com/studio/releases/gradle-plugin#publish-sources-jar --- android/build.gradle.kts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 6c31e9d..eb87810 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -23,6 +23,13 @@ android { proguardFiles(file("proguard-android-optimize.txt"), file("proguard-rules.pro")) } } + + publishing { + singleVariant("release") { + withSourcesJar() + withJavadocJar() + } + } } dependencies { From 16e6a4b170ab11353df344811e0cfad0255fa20d Mon Sep 17 00:00:00 2001 From: Kirill Zhukov Date: Sun, 20 Mar 2022 16:52:56 -0700 Subject: [PATCH 195/272] Update README and build.sh script to use latest env var naming. ANDROID_HOME, ANDROID_SDK_HOME, and ANDROID_NDK_HOME are not used by the Android platform and SDK tools so these are considered deprecated/invalid. Google recommends using ANDROID_SDK_ROOT and ANDROID_NDK_ROOT instead: - https://groups.google.com/g/android-ndk/c/qZjhOaynHXc/m/2ux2ZZdxy2MJ - https://developer.android.com/studio/command-line/variables --- README.md | 4 ++-- build.sh | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0d7b3cc..c7ffbc2 100644 --- a/README.md +++ b/README.md @@ -77,11 +77,11 @@ _Note that Kotlin version `1.6.10` or later is required to build the library._ ``` See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) for more info 1. Install Android SDK and Build-Tools for API level 30+ -1. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_HOME` path variables (which are required by the +1. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_ROOT` path variables (which are required by the build scripts), for example (NDK major version 21 is required): ```shell export ANDROID_SDK_ROOT=~/Android/Sdk - export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/21. + export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21. ``` 1. Build kotlin bindings ```sh diff --git a/build.sh b/build.sh index 577e26f..be16311 100755 --- a/build.sh +++ b/build.sh @@ -32,11 +32,11 @@ uniffi-bindgen generate src/bdk.udl --no-format --out-dir ../jvm/src/main/kotlin ## android -# If ANDROID_NDK_HOME is not set then set it to github actions default -[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle +# If ANDROID_NDK_ROOT is not set then set it to github actions default +[ -z "$ANDROID_NDK_ROOT" ] && export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk-bundle # Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 -export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin +export PATH=$PATH:$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin # Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 export CFLAGS="-D__ANDROID_API__=21" From f99ba7f992815311b408d72074a4bcad01fab561 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 22 Mar 2022 13:42:35 -0500 Subject: [PATCH 196/272] Add CI steps to build bdk-ffi libraries --- .github/workflows/test.yaml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ff611a9..55be077 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,6 +1,6 @@ name: Tests -on: pull_request +on: [push, pull_request] jobs: build: @@ -9,14 +9,38 @@ jobs: - name: Check out PR branch uses: actions/checkout@v2 + - name: Update bdk-ffi git submodule + run: | + git submodule set-url bdk-ffi https://github.com/bitcoindevkit/bdk-ffi.git + git submodule update --init bdk-ffi + + - name: cache + uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} + - name: Set up JDK uses: actions/setup-java@v2 with: distribution: temurin java-version: 11 + - name: Install rust android targets + run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android + + - name: Install uniffi-bindgen + run: cargo install uniffi_bindgen --version 0.16.0 + + - name: Build bdk-ffi libraries + run: ./build.sh + - name: Run Android tests run: ./gradlew :android:test --console=rich - name: Run JVM tests run: ./gradlew :jvm:test --console=rich + From 42b8db8609a01fe79bf602c85f3d7c8b915be520 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 30 Mar 2022 14:29:09 -0400 Subject: [PATCH 197/272] Fix docs homepages module.md files --- android/Module.md | 2 +- jvm/Module.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/Module.md b/android/Module.md index f3e8d26..d21953b 100644 --- a/android/Module.md +++ b/android/Module.md @@ -1,4 +1,4 @@ # Module bdk-android -The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Android. Current version: `0.5.1`. +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Android. # Package org.bitcoindevkit diff --git a/jvm/Module.md b/jvm/Module.md index f26d669..6873108 100644 --- a/jvm/Module.md +++ b/jvm/Module.md @@ -1,4 +1,4 @@ # Module bdk-jvm -The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for the JVM. Current version: `0.5.1`. +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for the JVM. # Package org.bitcoindevkit From 0aa9db450de8f84b442792c065e7eb23e629d72d Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 1 Apr 2022 12:56:16 -0400 Subject: [PATCH 198/272] Add library version to API docs --- android/build.gradle.kts | 1 + jvm/build.gradle.kts | 1 + 2 files changed, 2 insertions(+) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index df2a47f..213fdc8 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -100,6 +100,7 @@ tasks.withType().configureEach { dokkaSourceSets { named("main") { moduleName.set("bdk-android") + moduleVersion.set("0.6.0-SNAPSHOT") includes.from("Module.md") } } diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index a087fca..5ff3dbb 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -96,6 +96,7 @@ tasks.withType().configureEach { dokkaSourceSets { named("main") { moduleName.set("bdk-jvm") + moduleVersion.set("0.6.0-SNAPSHOT") includes.from("Module.md") } } From f205269d9724ca62c5850fc9ef9ea419d193eac0 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 28 Mar 2022 17:17:27 -0700 Subject: [PATCH 199/272] Fix tests network and addresses to TESTNET Must be TESTNET to match BlockchainConfig. --- .../kotlin/org/bitcoindevkit/AndroidLibTest.kt | 14 +++++++------- .../test/kotlin/org/bitcoindevkit/JvmLibTest.kt | 16 ++++++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index 6981b38..785cc7a 100644 --- a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -46,25 +46,25 @@ class AndroidLibTest { @Test fun memoryWalletNewAddress() { - val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) val address = wallet.getNewAddress() assertNotNull(address) - assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) } @Test(expected = BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { - Wallet("invalid-descriptor", null, Network.REGTEST, databaseConfig, blockchainConfig) + Wallet("invalid-descriptor", null, Network.TESTNET, databaseConfig, blockchainConfig) } @Test fun sledWalletNewAddress() { val testDataDir = getTestDataDir() val databaseConfig = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) - val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) val address = wallet.getNewAddress() assertNotNull(address) - assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) cleanupTestDataDir(testDataDir) } @@ -72,7 +72,7 @@ class AndroidLibTest { fun sqliteWalletSyncGetBalance() { val testDataDir = getTestDataDir()+"/bdk-wallet.sqlite" val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir)) - val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() assertTrue(balance > 0u) @@ -106,7 +106,7 @@ class AndroidLibTest { @Test fun onlineWalletSyncGetBalance() { - val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() assertTrue(balance > 0u) diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index d414e08..2f865c1 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -40,33 +40,33 @@ class JvmLibTest { @Test fun memoryWalletNewAddress() { - val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) val address = wallet.getNewAddress() assertNotNull(address) - assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) } @Test(expected = BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { - Wallet("invalid-descriptor", null, Network.REGTEST, databaseConfig, blockchainConfig) + Wallet("invalid-descriptor", null, Network.TESTNET, databaseConfig, blockchainConfig) } @Test fun sledWalletNewAddress() { val testDataDir = getTestDataDir() val databaseConfig = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) - val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) val address = wallet.getNewAddress() assertNotNull(address) - assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs") + assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) cleanupTestDataDir(testDataDir) } @Test fun sqliteWalletSyncGetBalance() { - val testDataDir = getTestDataDir()+"/bdk-wallet.sqlite" + val testDataDir = getTestDataDir() + "/bdk-wallet.sqlite" val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir)) - val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() assertTrue(balance > 0u) @@ -101,7 +101,7 @@ class JvmLibTest { @Test fun onlineWalletSyncGetBalance() { - val wallet = Wallet(descriptor, null, Network.REGTEST, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) wallet.sync(LogProgress(), null) val balance = wallet.getBalance() assertTrue(balance > 0u) From 907f67eb833722fb40860a3131648f8ab403f5e6 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 28 Mar 2022 17:20:30 -0700 Subject: [PATCH 200/272] Update bdk-ffi and add TxBuilder tests --- .../org/bitcoindevkit/AndroidLibTest.kt | 2 +- bdk-ffi | 2 +- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 39 ++++++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index 785cc7a..cf48aaa 100644 --- a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -115,7 +115,7 @@ class AndroidLibTest { @Test fun validPsbtSerde() { val validSerializedPsbt = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA" - val psbt = PartiallySignedBitcoinTransaction.deserialize(validSerializedPsbt) + val psbt = PartiallySignedBitcoinTransaction(validSerializedPsbt) val psbtSerialized = psbt.serialize() assertEquals(psbtSerialized, validSerializedPsbt) } diff --git a/bdk-ffi b/bdk-ffi index 2f5ac99..bc43d2e 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 2f5ac99feeb8350aae0a22e1e55b23d3cb1aaeb0 +Subproject commit bc43d2eb1a74cf05d110f97ecc8e7c0a472bfec8 diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index 2f865c1..f738d21 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -109,10 +109,45 @@ class JvmLibTest { @Test fun validPsbtSerde() { - val validSerializedPsbt = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA" - val psbt = PartiallySignedBitcoinTransaction.deserialize(validSerializedPsbt) + val validSerializedPsbt = + "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA" + val psbt = PartiallySignedBitcoinTransaction(validSerializedPsbt) val psbtSerialized = psbt.serialize() assertEquals(psbtSerialized, validSerializedPsbt) } + // TODO switch all tests to REGTEST, especially this one + @Test + fun walletTxBuilderBroadcast() { + val descriptor = + "wpkh([c1ed86ca/84'/1'/0'/0]tprv8hTkxK6QT7fCQx1wbuHuwbNh4STr2Ruz8RwEX7ymk6qnpixtbRG4T99mHxJwKTHPuKQ61heWrrpxZ8jpHj4sbisrQhDxnyx3HoQEZebtraN/*)" + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) + wallet.sync(LogProgress(), null) + val balance = wallet.getBalance() + if (balance > 2000u) { + println("balance $balance") + // send coins back to https://bitcoinfaucet.uo1.net + val faucetAddress = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" + val txBuilder = TxBuilder().addRecipient(faucetAddress, 1000u).feeRate(1.2f) + val psbt = txBuilder.build(wallet) + wallet.sign(psbt) + val txid = wallet.broadcast(psbt) + println("https://mempool.space/testnet/tx/$txid") + assertNotNull(txid) + } else { + val depositAddress = wallet.getLastUnusedAddress() + println("Send more testnet coins to: $depositAddress") + fail() + } + } + + @Test(expected = BdkException.Generic::class) + fun walletTxBuilderInvalidAddress() { + val descriptor = + "wpkh([c1ed86ca/84'/1'/0'/0]tprv8hTkxK6QT7fCQx1wbuHuwbNh4STr2Ruz8RwEX7ymk6qnpixtbRG4T99mHxJwKTHPuKQ61heWrrpxZ8jpHj4sbisrQhDxnyx3HoQEZebtraN/*)" + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) + val txBuilder = TxBuilder().addRecipient("INVALID_ADDRESS", 1000u).feeRate(1.2f) + txBuilder.build(wallet) + } + } From 43865b0ad03f68dbc17458d2588269ce62f8f39e Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 4 Apr 2022 21:35:47 -0700 Subject: [PATCH 201/272] Update bdk-ffi to v0.5.0 --- bdk-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-ffi b/bdk-ffi index bc43d2e..8a556d0 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit bc43d2eb1a74cf05d110f97ecc8e7c0a472bfec8 +Subproject commit 8a556d0ba0d5cd499b39dd65ca073229a45ffce2 From 65b9a3f9b6018f81d78a2ee64ebbc7600a9155aa Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 5 Apr 2022 10:54:18 -0700 Subject: [PATCH 202/272] Fix CI caching for bdk-ffi/target --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 55be077..908b30b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,7 +20,7 @@ jobs: path: | ~/.cargo/registry ~/.cargo/git - target + bdk-ffi/target key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} - name: Set up JDK From 1905d8804d25aaca10466a3458cda0f81b5c34f3 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 5 Apr 2022 10:55:02 -0700 Subject: [PATCH 203/272] Add jvm walletTxBuilderDrainWallet test --- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index f738d21..87d8abb 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -136,8 +136,7 @@ class JvmLibTest { assertNotNull(txid) } else { val depositAddress = wallet.getLastUnusedAddress() - println("Send more testnet coins to: $depositAddress") - fail() + fail("Send more testnet coins to: $depositAddress") } } @@ -150,4 +149,27 @@ class JvmLibTest { txBuilder.build(wallet) } + @Test + fun walletTxBuilderDrainWallet() { + val descriptor = + "wpkh([8da6afbe/84'/1'/0'/0]tprv8hY7jbMbe17EH1cLw2feTyNDYvjcFYauLmbneBqVnDERBrV51LrxWjCYRZwWS5keYn3ijb7iHJuEzXQk7EzgPeKRBVNBgC4oFPDxGND5S3V/*)" + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) + wallet.sync(LogProgress(), null) + val balance = wallet.getBalance() + if (balance > 2000u) { + println("balance $balance") + // send all coins back to https://bitcoinfaucet.uo1.net + val faucetAddress = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" + val txBuilder = TxBuilder().drainWallet().drainTo(faucetAddress).feeRate(1.1f) + val psbt = txBuilder.build(wallet) + wallet.sign(psbt) + val txid = wallet.broadcast(psbt) + println("https://mempool.space/testnet/tx/$txid") + assertNotNull(txid) + } else { + val depositAddress = wallet.getLastUnusedAddress() + fail("Send more testnet coins to: $depositAddress") + } + } + } From 25863c527f726774da69fd0d2a83a4620c1217cb Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 5 Apr 2022 12:34:44 -0700 Subject: [PATCH 204/272] Comment out jvm test walletTxBuilderDrainWallet --- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index 87d8abb..0ffef17 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -149,27 +149,29 @@ class JvmLibTest { txBuilder.build(wallet) } - @Test - fun walletTxBuilderDrainWallet() { - val descriptor = - "wpkh([8da6afbe/84'/1'/0'/0]tprv8hY7jbMbe17EH1cLw2feTyNDYvjcFYauLmbneBqVnDERBrV51LrxWjCYRZwWS5keYn3ijb7iHJuEzXQk7EzgPeKRBVNBgC4oFPDxGND5S3V/*)" - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) - wallet.sync(LogProgress(), null) - val balance = wallet.getBalance() - if (balance > 2000u) { - println("balance $balance") - // send all coins back to https://bitcoinfaucet.uo1.net - val faucetAddress = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" - val txBuilder = TxBuilder().drainWallet().drainTo(faucetAddress).feeRate(1.1f) - val psbt = txBuilder.build(wallet) - wallet.sign(psbt) - val txid = wallet.broadcast(psbt) - println("https://mempool.space/testnet/tx/$txid") - assertNotNull(txid) - } else { - val depositAddress = wallet.getLastUnusedAddress() - fail("Send more testnet coins to: $depositAddress") - } - } + // Comment this test in for local testing, you will need let it fail ones to get an address + // to pre-fund the test wallet before the test will pass. +// @Test +// fun walletTxBuilderDrainWallet() { +// val descriptor = +// "wpkh([8da6afbe/84'/1'/0'/0]tprv8hY7jbMbe17EH1cLw2feTyNDYvjcFYauLmbneBqVnDERBrV51LrxWjCYRZwWS5keYn3ijb7iHJuEzXQk7EzgPeKRBVNBgC4oFPDxGND5S3V/*)" +// val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) +// wallet.sync(LogProgress(), null) +// val balance = wallet.getBalance() +// if (balance > 2000u) { +// println("balance $balance") +// // send all coins back to https://bitcoinfaucet.uo1.net +// val faucetAddress = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" +// val txBuilder = TxBuilder().drainWallet().drainTo(faucetAddress).feeRate(1.1f) +// val psbt = txBuilder.build(wallet) +// wallet.sign(psbt) +// val txid = wallet.broadcast(psbt) +// println("https://mempool.space/testnet/tx/$txid") +// assertNotNull(txid) +// } else { +// val depositAddress = wallet.getLastUnusedAddress() +// fail("Send more testnet coins to: $depositAddress") +// } +// } } From 1bcacece65fddd8dbd062d6efd5a23f575e08311 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 5 Apr 2022 16:28:55 -0700 Subject: [PATCH 205/272] Update docs.patch file --- docs.patch | 500 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 425 insertions(+), 75 deletions(-) diff --git a/docs.patch b/docs.patch index 21cb4e9..4b51bdd 100644 --- a/docs.patch +++ b/docs.patch @@ -1,8 +1,7 @@ -*** bdk-0.5.1-undocumented.kt 2022-03-19 10:29:15.000000000 -0400 ---- bdk-0.5.1-documented.kt 2022-03-19 10:27:57.000000000 -0400 +*** bdkwithoutdocs.kt 2022-04-05 16:38:27.692000000 -0700 +--- bdk.kt 2022-04-05 16:40:51.727000000 -0700 *************** *** 17,131 **** ---- 17,144 ---- // compile the Rust component. The easiest way to ensure this is to bundle the Kotlin // helpers directly inline like we're doing here. @@ -23,7 +22,122 @@ // A rust-owned buffer is represented by its capacity, its current length, and a // pointer to the underlying data. -+ + @Structure.FieldOrder("capacity", "len", "data") + open class RustBuffer : Structure() { + @JvmField var capacity: Int = 0 + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : RustBuffer(), Structure.ByValue + class ByReference : RustBuffer(), Structure.ByReference + + companion object { + internal fun alloc(size: Int = 0) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_alloc(size, status).also { + if(it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") + } + } + } + + internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_free(buf, status) + } + + internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_reserve(buf, additional, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } + } + + /** +! * The equivalent of the `*mut RustBuffer` type. +! * Required for callbacks taking in an out pointer. +! * +! * Size is the sum of all values in the struct. + */ + class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setInt(0, value.capacity) + pointer.setInt(4, value.len) + pointer.setPointer(8, value.data) + } + } + + // This is a helper for safely passing byte references into the rust code. + // It's not actually used at the moment, because there aren't many things that you + // can take a direct pointer to in the JVM, and if we're going to copy something + // then we might as well copy it into a `RustBuffer`. But it's here for API + // completeness. + + @Structure.FieldOrder("len", "data") + open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue + } + + + // A helper for structured writing of data into a `RustBuffer`. + // This is very similar to `java.nio.ByteBuffer` but it knows how to grow + // the underlying `RustBuffer` on demand. + // + // TODO: we should benchmark writing things into a `RustBuffer` versus building + // up a bytearray and then copying it across. +! + class RustBufferBuilder() { + var rbuf = RustBuffer.ByValue() + var bbuf: ByteBuffer? = null + + init { + val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size + rbuf.writeField("len", 0) + this.setRustBuffer(rbuf) + } + + internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { + this.rbuf = rbuf + this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + it.position(rbuf.len) + } + } + + fun finalize() : RustBuffer.ByValue { + val rbuf = this.rbuf +--- 17,145 ---- + // compile the Rust component. The easiest way to ensure this is to bundle the Kotlin + // helpers directly inline like we're doing here. + + import com.sun.jna.Library + import com.sun.jna.Native + import com.sun.jna.Pointer + import com.sun.jna.Structure + import com.sun.jna.ptr.ByReference + import java.nio.ByteBuffer + import java.nio.ByteOrder + import java.util.concurrent.atomic.AtomicBoolean + import java.util.concurrent.atomic.AtomicLong + import java.util.concurrent.locks.ReentrantLock + import kotlin.concurrent.withLock + + // The Rust Buffer and 3 templated methods (alloc, free, reserve). + // This is a helper for safely working with byte buffers returned from the Rust code. + // A rust-owned buffer is represented by its capacity, its current length, and a + // pointer to the underlying data. + + /** + * @suppress + */ @@ -36,9 +150,12 @@ class ByValue : RustBuffer(), Structure.ByValue class ByReference : RustBuffer(), Structure.ByReference ++ /** ++ * @suppress ++ */ companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_2b7a_rustbuffer_alloc(size, status).also { + _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_alloc(size, status).also { if(it.data == null) { throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") } @@ -46,11 +163,11 @@ } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_2b7a_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_2b7a_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_reserve(buf, additional, status) } } @@ -61,15 +178,15 @@ } } ++ ///** ++ // * The equivalent of the `*mut RustBuffer` type. ++ // * Required for callbacks taking in an out pointer. ++ // * ++ // * Size is the sum of all values in the struct. ++ // */ /** - * The equivalent of the `*mut RustBuffer` type. - * Required for callbacks taking in an out pointer. - * - * Size is the sum of all values in the struct. +! * @suppress */ -+ /** -+ * @suppress -+ */ class RustBufferByReference : ByReference(16) { /** * Set the pointed-to `RustBuffer` to the given value. @@ -107,10 +224,9 @@ // // TODO: we should benchmark writing things into a `RustBuffer` versus building // up a bytearray and then copying it across. - -+ /** -+ * @suppress -+ */ +! /** +! * @suppress +! */ class RustBufferBuilder() { var rbuf = RustBuffer.ByValue() var bbuf: ByteBuffer? = null @@ -132,8 +248,48 @@ fun finalize() : RustBuffer.ByValue { val rbuf = this.rbuf *************** -*** 266,305 **** ---- 279,321 ---- +*** 232,304 **** +--- 246,327 ---- + // This would be a good candidate for isolating in its own ffi-support lib. + // Error runtime. + @Structure.FieldOrder("code", "error_buf") + internal open class RustCallStatus : Structure() { + @JvmField var code: Int = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + fun isSuccess(): Boolean { + return code == 0 + } + + fun isError(): Boolean { + return code == 1 + } + + fun isPanic(): Boolean { + return code == 2 + } + } + ++ /** ++ * @suppress ++ */ + class InternalException(message: String) : Exception(message) + ++ /** ++ * @suppress ++ */ + // Each top-level error class has a companion object that can lift the error from the call status's rust buffer + interface CallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E; + } + + // Helpers for calling Rust + // In practice we usually need to be synchronized to call this safely, so it doesn't + // synchronize itself + + // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err + private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus(); val return_value = callback(status) if (status.isSuccess()) { return return_value @@ -153,10 +309,10 @@ } } - // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR + /** + * @suppress + */ + // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR object NullCallStatusErrorHandler: CallStatusErrorHandler { override fun lift(error_buf: RustBuffer.ByValue): InternalException { RustBuffer.free(error_buf) @@ -176,14 +332,13 @@ val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") if (libOverride != null) { return libOverride - } *************** -*** 406,455 **** ---- 422,477 ---- +*** 430,479 **** +--- 453,508 ---- uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_2b7a_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_9c03_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -237,8 +392,8 @@ // struct after it has been dropped, and because we must expose a public API for freeing // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: *************** -*** 509,548 **** ---- 531,573 ---- +*** 533,572 **** +--- 562,604 ---- // Otherwise we atomically decrement and check the counter. // If it has reached zero then we destroy the underlying Rust struct. // @@ -283,8 +438,8 @@ } } *************** -*** 595,712 **** ---- 620,746 ---- +*** 619,736 **** +--- 651,777 ---- } } @@ -413,8 +568,8 @@ internal fun lower(): RustBuffer.ByValue { *************** -*** 737,776 **** ---- 771,813 ---- +*** 761,800 **** +--- 802,844 ---- } @@ -459,19 +614,8 @@ internal fun lower(): RustBuffer.ByValue { *************** -*** 786,836 **** ---- 823,879 ---- - } - is Transaction.Confirmed -> { - buf.putInt(2) - this.details.write(buf) - this.confirmation.write(buf) - - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - - +*** 821,860 **** +--- 865,907 ---- } @@ -481,9 +625,6 @@ -+ /** -+ * Sealed class that can be of either blockchain configuration defined by the library. -+ */ sealed class BlockchainConfig { data class Electrum( @@ -519,8 +660,8 @@ internal fun lower(): RustBuffer.ByValue { return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) *************** -*** 845,884 **** ---- 888,930 ---- +*** 869,908 **** +--- 916,958 ---- } is BlockchainConfig.Esplora -> { buf.putInt(2) @@ -565,8 +706,54 @@ } } *************** -*** 1084,1123 **** ---- 1130,1172 ---- +*** 1045,1084 **** +--- 1095,1137 ---- + + @Throws(BdkException::class)override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_9c03_Wallet_sync(it, FfiConverterCallbackInterfaceBdkProgress.lower(progressUpdate), lowerOptionalUInt(maxAddressParam) , status) + } + } + + + @Throws(BdkException::class)override fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_9c03_Wallet_broadcast(it, psbt.lower() , status) + } + }.let { + String.lift(it) + } + + + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(ptr: Pointer): Wallet { + return Wallet(ptr) + } + + internal fun read(buf: ByteBuffer): Wallet { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return Wallet.lift(Pointer(buf.getLong())) + } + + + } + } + + public interface PartiallySignedBitcoinTransactionInterface { + + fun serialize(): String + + } +*************** +*** 1108,1147 **** +--- 1161,1203 ---- internal fun lower(): Pointer = callWithPointer { it } @@ -579,7 +766,7 @@ override fun serialize(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_2b7a_PartiallySignedBitcoinTransaction_serialize(it, status) + _UniFFILib.INSTANCE.bdk_9c03_PartiallySignedBitcoinTransaction_serialize(it, status) } }.let { String.lift(it) @@ -601,18 +788,147 @@ return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong())) } - fun deserialize(psbtBase64: String ): PartiallySignedBitcoinTransaction = - PartiallySignedBitcoinTransaction( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_2b7a_PartiallySignedBitcoinTransaction_deserialize(psbtBase64.lower() ,status) - }) } } + public interface TxBuilderInterface { + + fun addRecipient(address: String, amount: ULong ): TxBuilder + + fun feeRate(satPerVbyte: Float ): TxBuilder *************** -*** 1204,1282 **** ---- 1253,1349 ---- +*** 1217,1410 **** +--- 1273,1486 ---- + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_9c03_TxBuilder_drain_to(it, address.lower() , status) + } + }.let { + TxBuilder.lift(it) + } + + + @Throws(BdkException::class)override fun build(wallet: Wallet ): PartiallySignedBitcoinTransaction = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_9c03_TxBuilder_build(it, wallet.lower() , status) + } + }.let { + PartiallySignedBitcoinTransaction.lift(it) + } + + + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(ptr: Pointer): TxBuilder { + return TxBuilder(ptr) + } + + internal fun read(buf: ByteBuffer): TxBuilder { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return TxBuilder.lift(Pointer(buf.getLong())) + } + + + } + } + + data class SledDbConfiguration ( + var path: String, + var treeName: String + ) { ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): SledDbConfiguration { + return liftFromRustBuffer(rbuf) { buf -> SledDbConfiguration.read(buf) } + } + + internal fun read(buf: ByteBuffer): SledDbConfiguration { + return SledDbConfiguration( + String.read(buf), + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.path.write(buf) + + this.treeName.write(buf) + + } + + + + } + + data class SqliteDbConfiguration ( + var path: String + ) { ++ ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): SqliteDbConfiguration { + return liftFromRustBuffer(rbuf) { buf -> SqliteDbConfiguration.read(buf) } + } + + internal fun read(buf: ByteBuffer): SqliteDbConfiguration { + return SqliteDbConfiguration( + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.path.write(buf) + + } + + + + } + + data class TransactionDetails ( + var fees: ULong?, + var received: ULong, + var sent: ULong, + var txid: String + ) { ++ ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): TransactionDetails { + return liftFromRustBuffer(rbuf) { buf -> TransactionDetails.read(buf) } + } + + internal fun read(buf: ByteBuffer): TransactionDetails { + return TransactionDetails( + readOptionalULong(buf), + ULong.read(buf), + ULong.read(buf), + String.read(buf) + ) + } + } internal fun lower(): RustBuffer.ByValue { return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) @@ -633,9 +949,6 @@ } -+ /** -+ * Block height and timestamp of a block -+ */ data class BlockTime ( var height: UInt, var timestamp: ULong @@ -671,15 +984,6 @@ } -+ /** -+ * Configuration for an ElectrumBlockchain -+ * -+ * @property url URL of the Electrum server (such as ElectrumX, Esplora, BWT). May start with `ssl://` or `tcp://` and include a port, `e.g. ssl://electrum.blockstream.info:60002` -+ * @property socks5 URL of the socks5 proxy server or a Tor service -+ * @property retry Request retry count -+ * @property timeout Request timeout in seconds -+ * @property stopGap Stop searching addresses for transactions after finding an unused gap of this length -+ */ data class ElectrumConfig ( var url: String, var socks5: String?, @@ -711,8 +1015,8 @@ } *************** -*** 1287,1326 **** ---- 1354,1396 ---- +*** 1415,1454 **** +--- 1491,1533 ---- this.retry.write(buf) @@ -757,8 +1061,8 @@ } *************** -*** 1329,1368 **** ---- 1399,1441 ---- +*** 1457,1496 **** +--- 1536,1578 ---- writeOptionalString(this.proxy, buf) @@ -802,3 +1106,49 @@ internal fun write(buf: RustBufferBuilder) { this.mnemonic.write(buf) +*************** +*** 1535,1574 **** +--- 1617,1659 ---- + class InvalidNetwork(message: String) : BdkException(message) + class InvalidProgressValue(message: String) : BdkException(message) + class ProgressUpdateException(message: String) : BdkException(message) + class InvalidOutpoint(message: String) : BdkException(message) + class Descriptor(message: String) : BdkException(message) + class AddressValidator(message: String) : BdkException(message) + class Encode(message: String) : BdkException(message) + class Miniscript(message: String) : BdkException(message) + class Bip32(message: String) : BdkException(message) + class Secp256k1(message: String) : BdkException(message) + class Json(message: String) : BdkException(message) + class Hex(message: String) : BdkException(message) + class Psbt(message: String) : BdkException(message) + class PsbtParse(message: String) : BdkException(message) + class Electrum(message: String) : BdkException(message) + class Esplora(message: String) : BdkException(message) + class Sled(message: String) : BdkException(message) + class Rusqlite(message: String) : BdkException(message) + + ++ /** ++ * @suppress ++ */ + companion object ErrorHandler : CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): BdkException { + return liftFromRustBuffer(error_buf) { error_buf -> read(error_buf) } + } + + fun read(error_buf: ByteBuffer): BdkException { + + return when(error_buf.getInt()) { + 1 -> BdkException.InvalidU32Bytes(String.read(error_buf)) + 2 -> BdkException.Generic(String.read(error_buf)) + 3 -> BdkException.ScriptDoesntHaveAddressForm(String.read(error_buf)) + 4 -> BdkException.NoRecipients(String.read(error_buf)) + 5 -> BdkException.NoUtxosSelected(String.read(error_buf)) + 6 -> BdkException.OutputBelowDustLimit(String.read(error_buf)) + 7 -> BdkException.InsufficientFunds(String.read(error_buf)) + 8 -> BdkException.BnBTotalTriesExceeded(String.read(error_buf)) + 9 -> BdkException.BnBNoExactMatch(String.read(error_buf)) + 10 -> BdkException.UnknownUtxo(String.read(error_buf)) + 11 -> BdkException.TransactionNotFound(String.read(error_buf)) + 12 -> BdkException.TransactionConfirmed(String.read(error_buf)) From a8d0bf52d24a68986848d54e61de9de4127838b4 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 22 Mar 2022 11:55:36 -0400 Subject: [PATCH 206/272] Add Gradle plugin MVP for building JVM library --- buildSrc/build.gradle.kts | 8 ++ buildSrc/settings.gradle.kts | 0 .../plugin/generate-bdk-bindings.gradle.kts | 76 +++++++++++++++++++ jvm/build.gradle.kts | 1 + settings.gradle | 4 - settings.gradle.kts | 4 + 6 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/settings.gradle.kts create mode 100644 buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..3f0ee3c --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + `kotlin-dsl` + // id("org.gradle.kotlin.kotlin-dsl") version "2.2.0" +} + +repositories { + mavenCentral() +} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 0000000..e69de29 diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts new file mode 100644 index 0000000..f36d66f --- /dev/null +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts @@ -0,0 +1,76 @@ +package org.bitcoindevkit.plugin + +val operatingSystem: OS = when { + System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC + System.getProperty("os.name").contains("linux", ignoreCase = true) -> OS.LINUX + else -> OS.OTHER +} +val architecture: Arch = when (System.getProperty("os.arch")) { + "x86_64" -> Arch.X86_64 + "aarch64" -> Arch.AARCH64 + else -> Arch.OTHER +} + +tasks.register("buildJvmBinaries") { + group = "Bitcoindevkit" + description = "Build the JVM native binaries for the bitcoindevkit" + + workingDir("${project.projectDir}/../bdk-ffi") + val cargoArgs: MutableList = mutableListOf("build", "--release", "--target") + + if (operatingSystem == OS.MAC && architecture == Arch.X86_64) { + println("Building the JVM native libs for macOS x86_64") + cargoArgs.add("x86_64-apple-darwin") + } else if (operatingSystem == OS.MAC && architecture == Arch.AARCH64) { + println("Building the JVM native libs for macOS x86_64") + cargoArgs.add("aarch64-apple-darwin") + } else if (operatingSystem == OS.LINUX) { + println("Building the JVM native libs for Linux x86_64") + cargoArgs.add("x86_64-unknown-linux-gnu") + } + + executable("cargo") + args(cargoArgs) +} + +tasks.register("moveNativeLibs") { + group = "Bitcoindevkit" + description = "Move the native libraries in the bdk-jvm project" + + var targetDir = "" + var resDir = "" + if (operatingSystem == OS.MAC && architecture == Arch.X86_64) { + targetDir = "x86_64-apple-darwin" + resDir = "darwin-x86-64" + } else if (operatingSystem == OS.MAC && architecture == Arch.AARCH64) { + targetDir = "aarch64-apple-darwin" + resDir = "darwin-aarch64" + } else if (operatingSystem == OS.LINUX) { + targetDir = "x86_64-unknown-linux-gnu" + resDir = "linux-x86-64" + } + + from("${project.projectDir}/../bdk-ffi/target/$targetDir/release/libbdkffi.dylib") + into("${project.projectDir}/../jvm/src/main/resources/$resDir/") +} + +tasks.register("generateBindings") { + group = "Bitcoindevkit" + description = "Building the bindings file for the bitcoindevkit" + + workingDir("${project.projectDir}/../bdk-ffi") + executable("uniffi-bindgen") + args("generate", "./src/bdk.udl", "--no-format", "--out-dir", "../jvm/src/main/kotlin", "--language", "kotlin") +} + +enum class Arch { + AARCH64, + X86_64, + OTHER, +} + +enum class OS { + MAC, + LINUX, + OTHER, +} diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index 5ff3dbb..dfa082b 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -7,6 +7,7 @@ plugins { id("maven-publish") id("signing") id("org.jetbrains.dokka") version "1.6.10" + id("org.bitcoindevkit.plugin.generate-bdk-bindings") } java { diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 024cf9f..0000000 --- a/settings.gradle +++ /dev/null @@ -1,4 +0,0 @@ -rootProject.name = 'bdk-kotlin' - -include ':jvm',':android' - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..ff3e310 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,4 @@ +rootProject.name = "bdk-kotlin" + +include(":jvm", ":android") +//include 'buildSrc' From 06d3f96706df01567ba87a7bcaf73d003dfb6c69 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 22 Mar 2022 14:01:13 -0400 Subject: [PATCH 207/272] Add aggregate task buildJvmLib --- .../kotlin/org/bitcoindevkit/plugin/Enums.kt | 13 ++++++ .../plugin/generate-bdk-bindings.gradle.kts | 41 +++++++++++-------- 2 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/Enums.kt diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/Enums.kt b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/Enums.kt new file mode 100644 index 0000000..6bcd494 --- /dev/null +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/Enums.kt @@ -0,0 +1,13 @@ +package org.bitcoindevkit.plugin + +enum class Arch { + AARCH64, + X86_64, + OTHER, +} + +enum class OS { + MAC, + LINUX, + OTHER, +} diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts index f36d66f..d9a1fa8 100644 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts @@ -11,31 +11,33 @@ val architecture: Arch = when (System.getProperty("os.arch")) { else -> Arch.OTHER } -tasks.register("buildJvmBinaries") { +val buildJvmBinary by tasks.register("buildJvmBinary") { group = "Bitcoindevkit" - description = "Build the JVM native binaries for the bitcoindevkit" + description = "Build the JVM binaries for the bitcoindevkit" workingDir("${project.projectDir}/../bdk-ffi") val cargoArgs: MutableList = mutableListOf("build", "--release", "--target") if (operatingSystem == OS.MAC && architecture == Arch.X86_64) { - println("Building the JVM native libs for macOS x86_64") cargoArgs.add("x86_64-apple-darwin") } else if (operatingSystem == OS.MAC && architecture == Arch.AARCH64) { - println("Building the JVM native libs for macOS x86_64") cargoArgs.add("aarch64-apple-darwin") } else if (operatingSystem == OS.LINUX) { - println("Building the JVM native libs for Linux x86_64") cargoArgs.add("x86_64-unknown-linux-gnu") } executable("cargo") args(cargoArgs) + + doLast { + println("Native library for bdk-jvm on ${cargoArgs.last()} successfully built") + } } -tasks.register("moveNativeLibs") { +val moveNativeJvmLib by tasks.register("moveNativeJvmLib") { group = "Bitcoindevkit" - description = "Move the native libraries in the bdk-jvm project" + description = "Move the native libraries to the bdk-jvm project" + dependsOn(buildJvmBinary) var targetDir = "" var resDir = "" @@ -52,25 +54,28 @@ tasks.register("moveNativeLibs") { from("${project.projectDir}/../bdk-ffi/target/$targetDir/release/libbdkffi.dylib") into("${project.projectDir}/../jvm/src/main/resources/$resDir/") + + doLast { + println("$targetDir native binaries for JVM moved to ./jvm/src/main/resources/$resDir/") + } } -tasks.register("generateBindings") { +val generateJvmBindings by tasks.register("generateJvmBindings") { group = "Bitcoindevkit" description = "Building the bindings file for the bitcoindevkit" + dependsOn(moveNativeJvmLib) workingDir("${project.projectDir}/../bdk-ffi") executable("uniffi-bindgen") args("generate", "./src/bdk.udl", "--no-format", "--out-dir", "../jvm/src/main/kotlin", "--language", "kotlin") + + doLast { + println("JVM bindings file successfully created") + } } -enum class Arch { - AARCH64, - X86_64, - OTHER, -} - -enum class OS { - MAC, - LINUX, - OTHER, +tasks.register("buildJvmLib") { + group = "Bitcoindevkit" + description = "Aggregate task to build JVM library" + dependsOn(buildJvmBinary, moveNativeJvmLib, generateJvmBindings) } From 35f097542b4266ac0a964f4f74f6bc9e4653361a Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 22 Mar 2022 14:01:39 -0400 Subject: [PATCH 208/272] Add documentation for plugin --- buildSrc/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 buildSrc/README.md diff --git a/buildSrc/README.md b/buildSrc/README.md new file mode 100644 index 0000000..dc90dfa --- /dev/null +++ b/buildSrc/README.md @@ -0,0 +1,16 @@ +# Readme +The purpose of this directory is to host a Gradle plugin that adds tasks for building the native binaries required by bdk-jvm/ bdk-android and building the language bindings files. + +The plugin is applied to the specific `build.gradle.kts` files in `bdk-jvm` and `bdk-android` through the `plugins` block: +```kotlin +plugins { + id("org.bitcoindevkit.plugin.generate-bdk-bindings") +} +``` + +It adds a series of tasks (`buildJvmBinary`, `moveNativeJvmLib`, `generateJvmBindings`) which are then brought together into an aggregate task called `buildJvmLib`. + +This task: +1. Builds the native JVM library (on your given platform) using `bdk-ffi` +2. Places it in the correct resource directory +3. Builds the bindings file From e566c4017cd23dcfefd13e3fec15e79fba48b129 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 5 Apr 2022 22:17:12 -0400 Subject: [PATCH 209/272] Remove intermediate tasks from Bitcoindevkit group This ensures they don't show up when using ./gradlew :jvm:tasks. The only two tasks that will appear in the end will be buildJvmLib and buildAndroidLib. --- .../{plugin => plugins}/Enums.kt | 2 +- .../generate-jvm-bindings.gradle.kts} | 25 ++++++++++++------- jvm/build.gradle.kts | 6 ++++- 3 files changed, 22 insertions(+), 11 deletions(-) rename buildSrc/src/main/kotlin/org/bitcoindevkit/{plugin => plugins}/Enums.kt (75%) rename buildSrc/src/main/kotlin/org/bitcoindevkit/{plugin/generate-bdk-bindings.gradle.kts => plugins/generate-jvm-bindings.gradle.kts} (73%) diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/Enums.kt b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt similarity index 75% rename from buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/Enums.kt rename to buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt index 6bcd494..ca12990 100644 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/Enums.kt +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt @@ -1,4 +1,4 @@ -package org.bitcoindevkit.plugin +package org.bitcoindevkit.plugins enum class Arch { AARCH64, diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts similarity index 73% rename from buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts rename to buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts index d9a1fa8..3191c7a 100644 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugin/generate-bdk-bindings.gradle.kts +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts @@ -1,4 +1,4 @@ -package org.bitcoindevkit.plugin +package org.bitcoindevkit.plugins val operatingSystem: OS = when { System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC @@ -11,9 +11,12 @@ val architecture: Arch = when (System.getProperty("os.arch")) { else -> Arch.OTHER } +// register a task of type Exec called buildJvmBinary +// which will run something like +// cargo build --release --target aarch64-apple-darwin val buildJvmBinary by tasks.register("buildJvmBinary") { - group = "Bitcoindevkit" - description = "Build the JVM binaries for the bitcoindevkit" + // group = "Bitcoindevkit" + // description = "Build the JVM binaries for the bitcoindevkit" workingDir("${project.projectDir}/../bdk-ffi") val cargoArgs: MutableList = mutableListOf("build", "--release", "--target") @@ -34,9 +37,11 @@ val buildJvmBinary by tasks.register("buildJvmBinary") { } } +// move the native libs build by cargo from bdk-ffi/target/.../release/ +// to their place in the bdk-jvm library val moveNativeJvmLib by tasks.register("moveNativeJvmLib") { - group = "Bitcoindevkit" - description = "Move the native libraries to the bdk-jvm project" + // group = "Bitcoindevkit" + // description = "Move the native libraries to the bdk-jvm project" dependsOn(buildJvmBinary) var targetDir = "" @@ -60,14 +65,16 @@ val moveNativeJvmLib by tasks.register("moveNativeJvmLib") { } } +// generate the bindings using the bdk-ffi-bindgen tool +// created in the bdk-ffi submodule val generateJvmBindings by tasks.register("generateJvmBindings") { - group = "Bitcoindevkit" - description = "Building the bindings file for the bitcoindevkit" + // group = "Bitcoindevkit" + // description = "Building the bindings file for the bitcoindevkit" dependsOn(moveNativeJvmLib) workingDir("${project.projectDir}/../bdk-ffi") - executable("uniffi-bindgen") - args("generate", "./src/bdk.udl", "--no-format", "--out-dir", "../jvm/src/main/kotlin", "--language", "kotlin") + executable("cargo") + args("run", "--package", "bdk-ffi-bindgen", "--", "--language", "kotlin", "--out-dir", "../jvm/src/main/kotlin") doLast { println("JVM bindings file successfully created") diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index dfa082b..b4d8a19 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -6,8 +6,12 @@ plugins { id("java-library") id("maven-publish") id("signing") + + // API docs id("org.jetbrains.dokka") version "1.6.10" - id("org.bitcoindevkit.plugin.generate-bdk-bindings") + + // Custom plugin to generate the native libs and bindings file + id("org.bitcoindevkit.plugins.generate-jvm-bindings") } java { From 758608419bd01fe33b01b9239b93946b1eed514b Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 7 Apr 2022 13:50:34 -0400 Subject: [PATCH 210/272] Temp: Start Android plugin --- android/build.gradle.kts | 5 + .../generate-android-bindings.gradle.kts | 91 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 213fdc8..c4bf95e 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -3,7 +3,12 @@ plugins { id("kotlin-android") id("maven-publish") id("signing") + + // API docs id("org.jetbrains.dokka") version "1.6.10" + + // Custom plugin to generate the native libs and bindings file + id("org.bitcoindevkit.plugins.generate-android-bindings") } android { diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts new file mode 100644 index 0000000..03e2ff8 --- /dev/null +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts @@ -0,0 +1,91 @@ +package org.bitcoindevkit.plugins + +import org.gradle.kotlin.dsl.register + +val operatingSystem: OS = when { + System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC + System.getProperty("os.name").contains("linux", ignoreCase = true) -> OS.LINUX + else -> OS.OTHER +} +val architecture: Arch = when (System.getProperty("os.arch")) { + "x86_64" -> Arch.X86_64 + "aarch64" -> Arch.AARCH64 + else -> Arch.OTHER +} + +// register a task of type Exec called buildJvmBinary +// which will run something like +// cargo build --release --target aarch64-apple-darwin +val buildJvmBinary by tasks.register("buildJvmBinary") { + // group = "Bitcoindevkit" + // description = "Build the JVM binaries for the bitcoindevkit" + + workingDir("${project.projectDir}/../bdk-ffi") + val cargoArgs: MutableList = mutableListOf("build", "--release", "--target") + + if (operatingSystem == OS.MAC && architecture == Arch.X86_64) { + cargoArgs.add("x86_64-apple-darwin") + } else if (operatingSystem == OS.MAC && architecture == Arch.AARCH64) { + cargoArgs.add("aarch64-apple-darwin") + } else if (operatingSystem == OS.LINUX) { + cargoArgs.add("x86_64-unknown-linux-gnu") + } + + executable("cargo") + args(cargoArgs) + + doLast { + println("Native library for bdk-jvm on ${cargoArgs.last()} successfully built") + } +} +// +// // move the native libs build by cargo from bdk-ffi/target/.../release/ +// // to their place in the bdk-jvm library +// val moveNativeJvmLib by tasks.register("moveNativeJvmLib") { +// // group = "Bitcoindevkit" +// // description = "Move the native libraries to the bdk-jvm project" +// dependsOn(buildJvmBinary) +// +// var targetDir = "" +// var resDir = "" +// if (operatingSystem == OS.MAC && architecture == Arch.X86_64) { +// targetDir = "x86_64-apple-darwin" +// resDir = "darwin-x86-64" +// } else if (operatingSystem == OS.MAC && architecture == Arch.AARCH64) { +// targetDir = "aarch64-apple-darwin" +// resDir = "darwin-aarch64" +// } else if (operatingSystem == OS.LINUX) { +// targetDir = "x86_64-unknown-linux-gnu" +// resDir = "linux-x86-64" +// } +// +// from("${project.projectDir}/../bdk-ffi/target/$targetDir/release/libbdkffi.dylib") +// into("${project.projectDir}/../jvm/src/main/resources/$resDir/") +// +// doLast { +// println("$targetDir native binaries for JVM moved to ./jvm/src/main/resources/$resDir/") +// } +// } +// +// // generate the bindings using the bdk-ffi-bindgen tool +// // created in the bdk-ffi submodule +// val generateJvmBindings by tasks.register("generateJvmBindings") { +// // group = "Bitcoindevkit" +// // description = "Building the bindings file for the bitcoindevkit" +// dependsOn(moveNativeJvmLib) +// +// workingDir("${project.projectDir}/../bdk-ffi") +// executable("cargo") +// args("run", "--package", "bdk-ffi-bindgen", "--", "--language", "kotlin", "--out-dir", "../jvm/src/main/kotlin") +// +// doLast { +// println("JVM bindings file successfully created") +// } +// } +// +// +// tasks.register("buildAndroidLib") { +// group = "Bitcoindevkit" +// description = "Aggregate task to build Android library" +// dependsOn(buildJvmBinary, moveNativeJvmLib, generateJvmBindings) +// } From b8e1282eba9f6495fbc672095ac43054d59b6913 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 7 Apr 2022 14:00:07 -0400 Subject: [PATCH 211/272] Fix dokka theme bug with AGP --- android/build.gradle.kts | 2 +- build.gradle.kts | 1 + jvm/build.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 213fdc8..a4bfd97 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("kotlin-android") id("maven-publish") id("signing") - id("org.jetbrains.dokka") version "1.6.10" + id("org.jetbrains.dokka") } android { diff --git a/build.gradle.kts b/build.gradle.kts index 81e5d68..8583631 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,7 @@ plugins { id("signing") id("maven-publish") id("io.github.gradle-nexus.publish-plugin") version "1.1.0" + id("org.jetbrains.dokka") version "1.6.10" } signing { diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index 5ff3dbb..6a38247 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("java-library") id("maven-publish") id("signing") - id("org.jetbrains.dokka") version "1.6.10" + id("org.jetbrains.dokka") } java { From 51f978e78e2e79ffbbf74cc07eb38eb8dc08b918 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 12 Apr 2022 14:41:04 -0400 Subject: [PATCH 212/272] Add custom Gradle plugin to build bdk-android library --- .../kotlin/org/bitcoindevkit/plugins/Enums.kt | 13 ++ .../generate-android-bindings.gradle.kts | 179 ++++++++++-------- .../plugins/generate-jvm-bindings.gradle.kts | 28 +-- 3 files changed, 126 insertions(+), 94 deletions(-) diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt index ca12990..d00bc94 100644 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt @@ -1,5 +1,18 @@ package org.bitcoindevkit.plugins + +val operatingSystem: OS = when { + System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC + System.getProperty("os.name").contains("linux", ignoreCase = true) -> OS.LINUX + else -> OS.OTHER +} + +val architecture: Arch = when (System.getProperty("os.arch")) { + "x86_64" -> Arch.X86_64 + "aarch64" -> Arch.AARCH64 + else -> Arch.OTHER +} + enum class Arch { AARCH64, X86_64, diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts index 03e2ff8..202a5a2 100644 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts @@ -2,90 +2,117 @@ package org.bitcoindevkit.plugins import org.gradle.kotlin.dsl.register -val operatingSystem: OS = when { - System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC - System.getProperty("os.name").contains("linux", ignoreCase = true) -> OS.LINUX - else -> OS.OTHER -} -val architecture: Arch = when (System.getProperty("os.arch")) { - "x86_64" -> Arch.X86_64 - "aarch64" -> Arch.AARCH64 - else -> Arch.OTHER +val llvmArchPath = when (operatingSystem) { + OS.MAC -> "darwin-x86_64" + OS.LINUX -> "linux-x86_64" + OS.OTHER -> throw Error("Cannot build Android library from current architecture") } -// register a task of type Exec called buildJvmBinary -// which will run something like -// cargo build --release --target aarch64-apple-darwin -val buildJvmBinary by tasks.register("buildJvmBinary") { - // group = "Bitcoindevkit" - // description = "Build the JVM binaries for the bitcoindevkit" +// arm64-v8a is the most popular hardware architecture for Android +val buildAndroidAarch64Binary by tasks.register("buildAndroidAarch64Binary") { workingDir("${project.projectDir}/../bdk-ffi") - val cargoArgs: MutableList = mutableListOf("build", "--release", "--target") - - if (operatingSystem == OS.MAC && architecture == Arch.X86_64) { - cargoArgs.add("x86_64-apple-darwin") - } else if (operatingSystem == OS.MAC && architecture == Arch.AARCH64) { - cargoArgs.add("aarch64-apple-darwin") - } else if (operatingSystem == OS.LINUX) { - cargoArgs.add("x86_64-unknown-linux-gnu") - } + val cargoArgs: MutableList = mutableListOf("build", "--release", "--target", "aarch64-linux-android") executable("cargo") args(cargoArgs) + // if ANDROID_NDK_ROOT is not set then set it to github actions default + if (System.getenv("ANDROID_NDK_ROOT") == null) { + environment( + Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle") + ) + } + + environment( + // add build toolchain to PATH + Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"), + + Pair("CFLAGS", "-D__ANDROID_API__=21"), + Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android21-clang"), + Pair("CC", "aarch64-linux-android21-clang") + ) + doLast { - println("Native library for bdk-jvm on ${cargoArgs.last()} successfully built") + println("Native library for bdk-android on aarch64 built successfully") } } -// -// // move the native libs build by cargo from bdk-ffi/target/.../release/ -// // to their place in the bdk-jvm library -// val moveNativeJvmLib by tasks.register("moveNativeJvmLib") { -// // group = "Bitcoindevkit" -// // description = "Move the native libraries to the bdk-jvm project" -// dependsOn(buildJvmBinary) -// -// var targetDir = "" -// var resDir = "" -// if (operatingSystem == OS.MAC && architecture == Arch.X86_64) { -// targetDir = "x86_64-apple-darwin" -// resDir = "darwin-x86-64" -// } else if (operatingSystem == OS.MAC && architecture == Arch.AARCH64) { -// targetDir = "aarch64-apple-darwin" -// resDir = "darwin-aarch64" -// } else if (operatingSystem == OS.LINUX) { -// targetDir = "x86_64-unknown-linux-gnu" -// resDir = "linux-x86-64" -// } -// -// from("${project.projectDir}/../bdk-ffi/target/$targetDir/release/libbdkffi.dylib") -// into("${project.projectDir}/../jvm/src/main/resources/$resDir/") -// -// doLast { -// println("$targetDir native binaries for JVM moved to ./jvm/src/main/resources/$resDir/") -// } -// } -// -// // generate the bindings using the bdk-ffi-bindgen tool -// // created in the bdk-ffi submodule -// val generateJvmBindings by tasks.register("generateJvmBindings") { -// // group = "Bitcoindevkit" -// // description = "Building the bindings file for the bitcoindevkit" -// dependsOn(moveNativeJvmLib) -// -// workingDir("${project.projectDir}/../bdk-ffi") -// executable("cargo") -// args("run", "--package", "bdk-ffi-bindgen", "--", "--language", "kotlin", "--out-dir", "../jvm/src/main/kotlin") -// -// doLast { -// println("JVM bindings file successfully created") -// } -// } -// -// -// tasks.register("buildAndroidLib") { -// group = "Bitcoindevkit" -// description = "Aggregate task to build Android library" -// dependsOn(buildJvmBinary, moveNativeJvmLib, generateJvmBindings) -// } + +// the x86_64 version of the library is mostly used by emulators +val buildAndroidX86_64Binary by tasks.register("buildAndroidX86_64Binary") { + + workingDir("${project.projectDir}/../bdk-ffi") + val cargoArgs: MutableList = mutableListOf("build", "--release", "--target", "x86_64-linux-android") + + executable("cargo") + args(cargoArgs) + + // if ANDROID_NDK_ROOT is not set then set it to github actions default + if (System.getenv("ANDROID_NDK_ROOT") == null) { + environment( + Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle") + ) + } + + environment( + // add build toolchain to PATH + Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"), + + Pair("CFLAGS", "-D__ANDROID_API__=21"), + Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android21-clang"), + Pair("CC", "x86_64-linux-android21-clang") + ) + + doLast { + println("Native library for bdk-android on x86_64 built successfully") + } +} + +// move the native libs build by cargo from bdk-ffi/target//release/ +// to their place in the bdk-android library +// the task only copies the available binaries built using the buildAndroidBinary tasks +val moveNativeAndroidLibs by tasks.register("moveNativeAndroidLibs") { + + dependsOn(buildAndroidAarch64Binary) + + into("${project.projectDir}/../android/src/main/jniLibs/") + + into("arm64-v8a") { + from("${project.projectDir}/../bdk-ffi/target/aarch64-linux-android/release/libbdkffi.so") + } + + into("x86_64") { + from("${project.projectDir}/../bdk-ffi/target/x86_64-linux-android/release/libbdkffi.so") + } + + doLast { + println("Native binaries for Android moved to ./android/src/main/jniLibs/") + } +} + +// generate the bindings using the bdk-ffi-bindgen tool located in the bdk-ffi submodule +val generateAndroidBindings by tasks.register("generateAndroidBindings") { + dependsOn(moveNativeAndroidLibs) + + workingDir("${project.projectDir}/../bdk-ffi") + executable("cargo") + args("run", "--package", "bdk-ffi-bindgen", "--", "--language", "kotlin", "--out-dir", "../android/src/main/kotlin") + + doLast { + println("Android bindings file successfully created") + } +} + +// create an aggregate task which will run the required tasks to build the Android libs in order +// the task will also appear in the printout of the ./gradlew tasks task with group and description +tasks.register("buildAndroidLib") { + group = "Bitcoindevkit" + description = "Aggregate task to build Android library" + + dependsOn( + buildAndroidAarch64Binary, + buildAndroidX86_64Binary, + moveNativeAndroidLibs, + generateAndroidBindings + ) +} diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts index 3191c7a..26b1fc0 100644 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts @@ -1,22 +1,9 @@ package org.bitcoindevkit.plugins -val operatingSystem: OS = when { - System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC - System.getProperty("os.name").contains("linux", ignoreCase = true) -> OS.LINUX - else -> OS.OTHER -} -val architecture: Arch = when (System.getProperty("os.arch")) { - "x86_64" -> Arch.X86_64 - "aarch64" -> Arch.AARCH64 - else -> Arch.OTHER -} - // register a task of type Exec called buildJvmBinary // which will run something like // cargo build --release --target aarch64-apple-darwin val buildJvmBinary by tasks.register("buildJvmBinary") { - // group = "Bitcoindevkit" - // description = "Build the JVM binaries for the bitcoindevkit" workingDir("${project.projectDir}/../bdk-ffi") val cargoArgs: MutableList = mutableListOf("build", "--release", "--target") @@ -40,8 +27,7 @@ val buildJvmBinary by tasks.register("buildJvmBinary") { // move the native libs build by cargo from bdk-ffi/target/.../release/ // to their place in the bdk-jvm library val moveNativeJvmLib by tasks.register("moveNativeJvmLib") { - // group = "Bitcoindevkit" - // description = "Move the native libraries to the bdk-jvm project" + dependsOn(buildJvmBinary) var targetDir = "" @@ -68,8 +54,7 @@ val moveNativeJvmLib by tasks.register("moveNativeJvmLib") { // generate the bindings using the bdk-ffi-bindgen tool // created in the bdk-ffi submodule val generateJvmBindings by tasks.register("generateJvmBindings") { - // group = "Bitcoindevkit" - // description = "Building the bindings file for the bitcoindevkit" + dependsOn(moveNativeJvmLib) workingDir("${project.projectDir}/../bdk-ffi") @@ -81,8 +66,15 @@ val generateJvmBindings by tasks.register("generateJvmBindings") { } } +// create an aggregate task which will run the 3 required tasks to build the JVM libs in order +// the task will also appear in the printout of the ./gradlew tasks task with group and description tasks.register("buildJvmLib") { group = "Bitcoindevkit" description = "Aggregate task to build JVM library" - dependsOn(buildJvmBinary, moveNativeJvmLib, generateJvmBindings) + + dependsOn( + buildJvmBinary, + moveNativeJvmLib, + generateJvmBindings + ) } From 12e04a634b379edb3bbca8b30cf48d1d2e23b939 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 12 Apr 2022 15:04:24 -0400 Subject: [PATCH 213/272] Clean up docs for custom Gradle plugins --- README.md | 111 +++++++++++++++++++++----------------------- buildSrc/README.md | 18 ++++--- settings.gradle.kts | 1 - 3 files changed, 66 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index c7ffbc2..09f22b2 100644 --- a/README.md +++ b/README.md @@ -46,66 +46,63 @@ val newAddress = wallet.getNewAddress() ### Example Projects #### `bdk-android` - * [Devkit Wallet](https://github.com/thunderbiscuit/devkit-wallet) * [Padawan Wallet](https://github.com/thunderbiscuit/padawan-wallet) #### `bdk-jvm` - * [Tatooine Faucet](https://github.com/thunderbiscuit/tatooine) ### How to build _Note that Kotlin version `1.6.10` or later is required to build the library._ -1. Clone this repository and init and update it's [`bdk-ffi`] submodule. - ```shell - git clone https://github.com/bitcoindevkit/bdk-kotlin - git submodule update --init - ``` -1. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions. -1. If building on MacOS install required intel and m1 jvm targets - ```sh - rustup target add x86_64-apple-darwin aarch64-apple-darwin - ``` -1. Install required targets - ```sh - rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android - ``` -1. Install `uniffi-bindgen` - ```sh - cargo install uniffi_bindgen --version 0.16.0 - ``` - See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) for more info -1. Install Android SDK and Build-Tools for API level 30+ -1. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_ROOT` path variables (which are required by the - build scripts), for example (NDK major version 21 is required): - ```shell - export ANDROID_SDK_ROOT=~/Android/Sdk - export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21. - ``` -1. Build kotlin bindings - ```sh - ./build.sh - ``` -1. Start android emulator and run tests - ```sh - ./gradlew connectedAndroidTest - ``` +1. Clone this repository and initialize and update its [`bdk-ffi`] submodule. +```shell +git clone https://github.com/bitcoindevkit/bdk-kotlin +git submodule update --init +``` +2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions. +3. If building on MacOS install required intel and m1 jvm targets +```sh +rustup target add x86_64-apple-darwin aarch64-apple-darwin +``` +4. Install required targets + ```sh + rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi + ``` +5. Install Android SDK and Build-Tools for API level 30+ +6. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_ROOT` path variables (which are required by the + build tool), for example (NDK major version 21 is required): + ```shell + export ANDROID_SDK_ROOT=~/Android/Sdk + export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21. + ``` +7. Build kotlin bindings + ```sh + # build JVM library + ./gradlew :jvm:buildJvmLib + + # build Android library + ./gradlew :jvm:buildAndroidLib + ``` +8. Start android emulator and run tests +```sh +./gradlew connectedAndroidTest +``` ## How to publish ### Publish to your local maven repo 1. Set your `~/.gradle/gradle.properties` signing key values - ```properties - signing.gnupg.keyName= - signing.gnupg.passphrase= - ``` -1. Publish - ```shell - ./gradlew :jvm:publishToMavenLocal - ./gradlew :android:publishToMavenLocal - ``` +```properties +signing.gnupg.keyName= +signing.gnupg.passphrase= +``` +2. Publish +```shell +./gradlew :jvm:publishToMavenLocal +./gradlew :android:publishToMavenLocal +``` Note that if you do not have gpg keys set up to sign the publication, the task will fail. If you wish to publish to your local Maven repository for local testing without signing the release, you can do so by excluding the `signMavenPublication` subtask like so: ```shell @@ -116,18 +113,18 @@ Note that if you do not have gpg keys set up to sign the publication, the task w ### Publish to maven central with [Gradle Nexus Publish Plugin] (project maintainers only) 1. Set your `~/.gradle/gradle.properties` signing key values and SONATYPE login - ```properties - signing.gnupg.keyName= - signing.gnupg.passphrase= - - ossrhUserName= - ossrhPassword= - ``` -1. Publish - ```shell - ./gradlew :jvm:publishToSonatype closeAndReleaseSonatypeStagingRepository - ./gradlew :android:publishToSonatype closeAndReleaseSonatypeStagingRepository - ``` +```properties +signing.gnupg.keyName= +signing.gnupg.passphrase= + +ossrhUserName= +ossrhPassword= +``` +2. Publish +```shell +./gradlew :jvm:publishToSonatype closeAndReleaseSonatypeStagingRepository +./gradlew :android:publishToSonatype closeAndReleaseSonatypeStagingRepository +``` [Kotlin]: https://kotlinlang.org/ [Android Studio]: https://developer.android.com/studio/ diff --git a/buildSrc/README.md b/buildSrc/README.md index dc90dfa..15b2026 100644 --- a/buildSrc/README.md +++ b/buildSrc/README.md @@ -1,16 +1,22 @@ # Readme -The purpose of this directory is to host a Gradle plugin that adds tasks for building the native binaries required by bdk-jvm/ bdk-android and building the language bindings files. +The purpose of this directory is to host the Gradle plugins that add tasks for building the native binaries required by bdk-jvm and bdk-android, and building the language bindings files. -The plugin is applied to the specific `build.gradle.kts` files in `bdk-jvm` and `bdk-android` through the `plugins` block: +The plugins are applied to the specific `build.gradle.kts` files in `bdk-jvm` and `bdk-android` through the `plugins` block: ```kotlin +// bdk-jvm plugins { - id("org.bitcoindevkit.plugin.generate-bdk-bindings") + id("org.bitcoindevkit.plugin.generate-jvm-bindings") +} + +// bdk-android +plugins { + id("org.bitcoindevkit.plugins.generate-android-bindings") } ``` -It adds a series of tasks (`buildJvmBinary`, `moveNativeJvmLib`, `generateJvmBindings`) which are then brought together into an aggregate task called `buildJvmLib`. +They add a series of tasks which are brought together into an aggregate task called `buildJvmLib` for `bdk-jvm` and `buildAndroidLib` for `bdk-android`. -This task: -1. Builds the native JVM library (on your given platform) using `bdk-ffi` +This aggregate task: +1. Builds the native library(ies) using `bdk-ffi` 2. Places it in the correct resource directory 3. Builds the bindings file diff --git a/settings.gradle.kts b/settings.gradle.kts index ff3e310..eed67d2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,3 @@ rootProject.name = "bdk-kotlin" include(":jvm", ":android") -//include 'buildSrc' From 4dc41822365aefe61889263e9060c36c6d62df4e Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Sun, 17 Apr 2022 08:45:33 -0400 Subject: [PATCH 214/272] Fix docs typo in Gradle build task --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09f22b2..f2527d8 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ rustup target add x86_64-apple-darwin aarch64-apple-darwin ./gradlew :jvm:buildJvmLib # build Android library - ./gradlew :jvm:buildAndroidLib + ./gradlew :android:buildAndroidLib ``` 8. Start android emulator and run tests ```sh From 14622ef75bd6de9cb0bdaaf4e4e3857d42c4be84 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Sun, 17 Apr 2022 08:54:06 -0400 Subject: [PATCH 215/272] Fix requirement for different extensions on JVM native libraries --- .../bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts index 26b1fc0..edd8b23 100644 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts @@ -32,18 +32,22 @@ val moveNativeJvmLib by tasks.register("moveNativeJvmLib") { var targetDir = "" var resDir = "" + var ext = "" if (operatingSystem == OS.MAC && architecture == Arch.X86_64) { targetDir = "x86_64-apple-darwin" resDir = "darwin-x86-64" + ext = "dylib" } else if (operatingSystem == OS.MAC && architecture == Arch.AARCH64) { targetDir = "aarch64-apple-darwin" resDir = "darwin-aarch64" + ext = "dylib" } else if (operatingSystem == OS.LINUX) { targetDir = "x86_64-unknown-linux-gnu" resDir = "linux-x86-64" + ext = "so" } - from("${project.projectDir}/../bdk-ffi/target/$targetDir/release/libbdkffi.dylib") + from("${project.projectDir}/../bdk-ffi/target/$targetDir/release/libbdkffi.$ext") into("${project.projectDir}/../jvm/src/main/resources/$resDir/") doLast { From 0ab14264c0fb90bccc989bd6c81f27161c25fc68 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Sun, 17 Apr 2022 08:59:57 -0400 Subject: [PATCH 216/272] Add comment on requirement for x86_64 emulator in docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2527d8..69b6c59 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ rustup target add x86_64-apple-darwin aarch64-apple-darwin # build Android library ./gradlew :android:buildAndroidLib ``` -8. Start android emulator and run tests +8. Start android emulator (must be x86_64) and run tests ```sh ./gradlew connectedAndroidTest ``` From e41bc9a84f07cde9cf13360ee73a378910930034 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Sun, 17 Apr 2022 09:14:55 -0400 Subject: [PATCH 217/272] Remove build shell script and use Gradle plugin in CI workflow --- .github/workflows/test.yaml | 10 +++--- build.sh | 68 ------------------------------------- 2 files changed, 6 insertions(+), 72 deletions(-) delete mode 100755 build.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 908b30b..b432c99 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -30,17 +30,19 @@ jobs: java-version: 11 - name: Install rust android targets - run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android + run: rustup target add x86_64-linux-android aarch64-linux-android - name: Install uniffi-bindgen run: cargo install uniffi_bindgen --version 0.16.0 - - name: Build bdk-ffi libraries - run: ./build.sh + - name: Build bdk-android library + run: ./gradlew :android:buildAndroidLib - name: Run Android tests run: ./gradlew :android:test --console=rich + - name: Build bdk-jvm library + run: ./gradlew :jvm:buildJvmLib + - name: Run JVM tests run: ./gradlew :jvm:test --console=rich - diff --git a/build.sh b/build.sh deleted file mode 100755 index be16311..0000000 --- a/build.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -echo "Build and test bdk-ffi library for local platform (darwin or linux)" -pushd bdk-ffi - -OS=$(uname) -echo -n "Copy " -case $OS in - "Darwin") - echo -n "darwin " - # x86_64 (intel) - cargo build --release --target x86_64-apple-darwin - mkdir -p ../jvm/src/main/resources/darwin-x86-64 - cp target/x86_64-apple-darwin/release/libbdkffi.dylib ../jvm/src/main/resources/darwin-x86-64 - # aarch64 (m1) - cargo build --release --target aarch64-apple-darwin - mkdir -p ../jvm/src/main/resources/darwin-aarch64 - cp target/aarch64-apple-darwin/release/libbdkffi.dylib ../jvm/src/main/resources/darwin-aarch64 - ;; - "Linux") - echo -n "linux " - cargo build --release - mkdir -p ../jvm/src/main/resources/linux-x86-64 - cp target/release/libbdkffi.so ../jvm/src/main/resources/linux-x86-64 - ;; -esac -echo "libs to jvm subproject" - -echo "Generate kotlin bindings from bdk.udl to jvm subproject" -uniffi-bindgen generate src/bdk.udl --no-format --out-dir ../jvm/src/main/kotlin --language kotlin - -## android - -# If ANDROID_NDK_ROOT is not set then set it to github actions default -[ -z "$ANDROID_NDK_ROOT" ] && export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk-bundle - -# Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 -export PATH=$PATH:$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin - -# Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 -export CFLAGS="-D__ANDROID_API__=21" - -# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on -BUILD_TARGETS="${BUILD_TARGETS:-aarch64,x86_64,i686}" - -mkdir -p ../android/src/main/jniLibs/arm64-v8a ../android/src/main/jniLibs/x86_64 ../android/src/main/jniLibs/x86 - -if echo $BUILD_TARGETS | grep "aarch64"; then - CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --release --target=aarch64-linux-android - cp target/aarch64-linux-android/release/libbdkffi.so ../android/src/main/jniLibs/arm64-v8a -fi -if echo $BUILD_TARGETS | grep "x86_64"; then - CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --release --target=x86_64-linux-android - cp target/x86_64-linux-android/release/libbdkffi.so ../android/src/main/jniLibs/x86_64 -fi -if echo $BUILD_TARGETS | grep "i686"; then - CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --release --target=i686-linux-android - cp target/i686-linux-android/release/libbdkffi.so ../android/src/main/jniLibs/x86 -fi - -popd - -# copy bdk-ffi kotlin binding sources from jvm to android -cp -R jvm/src/main/kotlin android/src/main - -# bdk-kotlin build jar and aar subprojects -./gradlew build From a9f42dd945c3af61169c13471382f27971f5f9ea Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Sun, 17 Apr 2022 09:27:52 -0400 Subject: [PATCH 218/272] Fix dokka plugin declaration in JVM gradle build file --- jvm/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index 92708bb..6d1d2f7 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -8,7 +8,7 @@ plugins { id("signing") // API docs - id("org.jetbrains.dokka") version + id("org.jetbrains.dokka") // Custom plugin to generate the native libs and bindings file id("org.bitcoindevkit.plugins.generate-jvm-bindings") From 44b724ea9f76076035b22be3bf95fbe4834c0ad0 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 22 Apr 2022 14:50:40 -0700 Subject: [PATCH 219/272] Update to bdk-ffi 0.6.0 --- .../org/bitcoindevkit/AndroidLibTest.kt | 20 +++++++------ bdk-ffi | 2 +- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 30 +++++++++++-------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index cf48aaa..8fc157c 100644 --- a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -46,7 +46,7 @@ class AndroidLibTest { @Test fun memoryWalletNewAddress() { - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) val address = wallet.getNewAddress() assertNotNull(address) assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) @@ -54,14 +54,14 @@ class AndroidLibTest { @Test(expected = BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { - Wallet("invalid-descriptor", null, Network.TESTNET, databaseConfig, blockchainConfig) + Wallet("invalid-descriptor", null, Network.TESTNET, databaseConfig) } @Test fun sledWalletNewAddress() { val testDataDir = getTestDataDir() val databaseConfig = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) val address = wallet.getNewAddress() assertNotNull(address) assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) @@ -72,8 +72,9 @@ class AndroidLibTest { fun sqliteWalletSyncGetBalance() { val testDataDir = getTestDataDir()+"/bdk-wallet.sqlite" val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir)) - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) - wallet.sync(LogProgress(), null) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) + val blockchain = Blockchain(blockchainConfig); + wallet.sync(blockchain, LogProgress()) val balance = wallet.getBalance() assertTrue(balance > 0u) cleanupTestDataDir(testDataDir) @@ -90,13 +91,13 @@ class AndroidLibTest { 100u ) ) - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchain) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) assertNotNull(wallet) val network = wallet.getNetwork() assertEquals(network, Network.TESTNET) } - class LogProgress : BdkProgress { + class LogProgress : Progress { val log: Logger = LoggerFactory.getLogger(AndroidLibTest::class.java) override fun update(progress: Float, message: String?) { @@ -106,8 +107,9 @@ class AndroidLibTest { @Test fun onlineWalletSyncGetBalance() { - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) - wallet.sync(LogProgress(), null) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) + val blockchain = Blockchain(blockchainConfig); + wallet.sync(blockchain, LogProgress()) val balance = wallet.getBalance() assertTrue(balance > 0u) } diff --git a/bdk-ffi b/bdk-ffi index 8a556d0..30e54ac 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 8a556d0ba0d5cd499b39dd65ca073229a45ffce2 +Subproject commit 30e54ac067f68e8c22d652837b4d5901c12e3384 diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index 0ffef17..5ec752d 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -40,7 +40,7 @@ class JvmLibTest { @Test fun memoryWalletNewAddress() { - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) val address = wallet.getNewAddress() assertNotNull(address) assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) @@ -48,14 +48,14 @@ class JvmLibTest { @Test(expected = BdkException.Descriptor::class) fun invalidDescriptorExceptionIsThrown() { - Wallet("invalid-descriptor", null, Network.TESTNET, databaseConfig, blockchainConfig) + Wallet("invalid-descriptor", null, Network.TESTNET, databaseConfig) } @Test fun sledWalletNewAddress() { val testDataDir = getTestDataDir() val databaseConfig = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) val address = wallet.getNewAddress() assertNotNull(address) assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) @@ -66,8 +66,9 @@ class JvmLibTest { fun sqliteWalletSyncGetBalance() { val testDataDir = getTestDataDir() + "/bdk-wallet.sqlite" val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir)) - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) - wallet.sync(LogProgress(), null) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) + val blockchain = Blockchain(blockchainConfig); + wallet.sync(blockchain, LogProgress()) val balance = wallet.getBalance() assertTrue(balance > 0u) cleanupTestDataDir(testDataDir) @@ -85,13 +86,13 @@ class JvmLibTest { 100u ) ) - val wallet = Wallet(descriptor, null, Network.TESTNET, database, blockchain) + val wallet = Wallet(descriptor, null, Network.TESTNET, database) assertNotNull(wallet) val network = wallet.getNetwork() assertEquals(network, Network.TESTNET) } - class LogProgress : BdkProgress { + class LogProgress : Progress { val log: Logger = LoggerFactory.getLogger(JvmLibTest::class.java) override fun update(progress: Float, message: String?) { @@ -101,8 +102,9 @@ class JvmLibTest { @Test fun onlineWalletSyncGetBalance() { - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) - wallet.sync(LogProgress(), null) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) + val blockchain = Blockchain(blockchainConfig); + wallet.sync(blockchain, LogProgress()) val balance = wallet.getBalance() assertTrue(balance > 0u) } @@ -121,8 +123,9 @@ class JvmLibTest { fun walletTxBuilderBroadcast() { val descriptor = "wpkh([c1ed86ca/84'/1'/0'/0]tprv8hTkxK6QT7fCQx1wbuHuwbNh4STr2Ruz8RwEX7ymk6qnpixtbRG4T99mHxJwKTHPuKQ61heWrrpxZ8jpHj4sbisrQhDxnyx3HoQEZebtraN/*)" - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) - wallet.sync(LogProgress(), null) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) + val blockchain = Blockchain(blockchainConfig); + wallet.sync(blockchain, LogProgress()) val balance = wallet.getBalance() if (balance > 2000u) { println("balance $balance") @@ -131,7 +134,8 @@ class JvmLibTest { val txBuilder = TxBuilder().addRecipient(faucetAddress, 1000u).feeRate(1.2f) val psbt = txBuilder.build(wallet) wallet.sign(psbt) - val txid = wallet.broadcast(psbt) + blockchain.broadcast(psbt) + val txid = psbt.txid() println("https://mempool.space/testnet/tx/$txid") assertNotNull(txid) } else { @@ -144,7 +148,7 @@ class JvmLibTest { fun walletTxBuilderInvalidAddress() { val descriptor = "wpkh([c1ed86ca/84'/1'/0'/0]tprv8hTkxK6QT7fCQx1wbuHuwbNh4STr2Ruz8RwEX7ymk6qnpixtbRG4T99mHxJwKTHPuKQ61heWrrpxZ8jpHj4sbisrQhDxnyx3HoQEZebtraN/*)" - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) + val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) val txBuilder = TxBuilder().addRecipient("INVALID_ADDRESS", 1000u).feeRate(1.2f) txBuilder.build(wallet) } From 108fcd46ecd0d536adf3a76c9425e0f82e04dae8 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 12 May 2022 14:46:03 -0700 Subject: [PATCH 220/272] Comment out walletTxBuilderBroadcast test --- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index 5ec752d..ba03af4 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -118,31 +118,31 @@ class JvmLibTest { assertEquals(psbtSerialized, validSerializedPsbt) } - // TODO switch all tests to REGTEST, especially this one - @Test - fun walletTxBuilderBroadcast() { - val descriptor = - "wpkh([c1ed86ca/84'/1'/0'/0]tprv8hTkxK6QT7fCQx1wbuHuwbNh4STr2Ruz8RwEX7ymk6qnpixtbRG4T99mHxJwKTHPuKQ61heWrrpxZ8jpHj4sbisrQhDxnyx3HoQEZebtraN/*)" - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val blockchain = Blockchain(blockchainConfig); - wallet.sync(blockchain, LogProgress()) - val balance = wallet.getBalance() - if (balance > 2000u) { - println("balance $balance") - // send coins back to https://bitcoinfaucet.uo1.net - val faucetAddress = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" - val txBuilder = TxBuilder().addRecipient(faucetAddress, 1000u).feeRate(1.2f) - val psbt = txBuilder.build(wallet) - wallet.sign(psbt) - blockchain.broadcast(psbt) - val txid = psbt.txid() - println("https://mempool.space/testnet/tx/$txid") - assertNotNull(txid) - } else { - val depositAddress = wallet.getLastUnusedAddress() - fail("Send more testnet coins to: $depositAddress") - } - } + // TODO move tx_builder tests to bdk-ffi, especially this one +// @Test +// fun walletTxBuilderBroadcast() { +// val descriptor = +// "wpkh([c1ed86ca/84'/1'/0'/0]tprv8hTkxK6QT7fCQx1wbuHuwbNh4STr2Ruz8RwEX7ymk6qnpixtbRG4T99mHxJwKTHPuKQ61heWrrpxZ8jpHj4sbisrQhDxnyx3HoQEZebtraN/*)" +// val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) +// val blockchain = Blockchain(blockchainConfig); +// wallet.sync(blockchain, LogProgress()) +// val balance = wallet.getBalance() +// if (balance > 2000u) { +// println("balance $balance") +// // send coins back to https://bitcoinfaucet.uo1.net +// val faucetAddress = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" +// val txBuilder = TxBuilder().addRecipient(faucetAddress, 1000u).feeRate(1.2f) +// val psbt = txBuilder.finish(wallet) +// wallet.sign(psbt) +// blockchain.broadcast(psbt) +// val txid = psbt.txid() +// println("https://mempool.space/testnet/tx/$txid") +// assertNotNull(txid) +// } else { +// val depositAddress = wallet.getLastUnusedAddress() +// fail("Send more testnet coins to: $depositAddress") +// } +// } @Test(expected = BdkException.Generic::class) fun walletTxBuilderInvalidAddress() { @@ -150,7 +150,7 @@ class JvmLibTest { "wpkh([c1ed86ca/84'/1'/0'/0]tprv8hTkxK6QT7fCQx1wbuHuwbNh4STr2Ruz8RwEX7ymk6qnpixtbRG4T99mHxJwKTHPuKQ61heWrrpxZ8jpHj4sbisrQhDxnyx3HoQEZebtraN/*)" val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) val txBuilder = TxBuilder().addRecipient("INVALID_ADDRESS", 1000u).feeRate(1.2f) - txBuilder.build(wallet) + txBuilder.finish(wallet) } // Comment this test in for local testing, you will need let it fail ones to get an address From cecf973777e3e4d16d08ec15a4f14117e2960bb3 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 25 Apr 2022 21:00:22 -0700 Subject: [PATCH 221/272] Update docs.patch for bdk-ffi 0.6.0 --- docs.patch | 2421 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 2246 insertions(+), 175 deletions(-) diff --git a/docs.patch b/docs.patch index 4b51bdd..8fb620f 100644 --- a/docs.patch +++ b/docs.patch @@ -1,7 +1,8 @@ -*** bdkwithoutdocs.kt 2022-04-05 16:38:27.692000000 -0700 ---- bdk.kt 2022-04-05 16:40:51.727000000 -0700 +*** tmp/bdkwithoutdocs.kt 2022-05-12 15:13:30.132174559 -0700 +--- jvm/src/main/kotlin/org/bitcoindevkit/bdk.kt 2022-05-12 15:36:05.744074453 -0700 *************** -*** 17,131 **** +*** 16,131 **** + // now that means coming from the exact some version of `uniffi` that was used to // compile the Rust component. The easiest way to ensure this is to bundle the Kotlin // helpers directly inline like we're doing here. @@ -21,7 +22,7 @@ // This is a helper for safely working with byte buffers returned from the Rust code. // A rust-owned buffer is represented by its capacity, its current length, and a // pointer to the underlying data. - +! @Structure.FieldOrder("capacity", "len", "data") open class RustBuffer : Structure() { @JvmField var capacity: Int = 0 @@ -33,7 +34,7 @@ companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_alloc(size, status).also { + _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_alloc(size, status).also { if(it.data == null) { throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") } @@ -41,11 +42,11 @@ } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_reserve(buf, additional, status) } } @@ -57,10 +58,10 @@ } /** -! * The equivalent of the `*mut RustBuffer` type. -! * Required for callbacks taking in an out pointer. -! * -! * Size is the sum of all values in the struct. + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. */ class RustBufferByReference : ByReference(16) { /** @@ -117,7 +118,8 @@ fun finalize() : RustBuffer.ByValue { val rbuf = this.rbuf ---- 17,145 ---- +--- 16,141 ---- + // now that means coming from the exact some version of `uniffi` that was used to // compile the Rust component. The easiest way to ensure this is to bundle the Kotlin // helpers directly inline like we're doing here. @@ -137,10 +139,9 @@ // This is a helper for safely working with byte buffers returned from the Rust code. // A rust-owned buffer is represented by its capacity, its current length, and a // pointer to the underlying data. - -+ /** -+ * @suppress -+ */ +! /** +! * @suppress +! */ @Structure.FieldOrder("capacity", "len", "data") open class RustBuffer : Structure() { @JvmField var capacity: Int = 0 @@ -150,12 +151,9 @@ class ByValue : RustBuffer(), Structure.ByValue class ByReference : RustBuffer(), Structure.ByReference -+ /** -+ * @suppress -+ */ companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_alloc(size, status).also { + _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_alloc(size, status).also { if(it.data == null) { throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") } @@ -163,11 +161,11 @@ } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_9c03_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_reserve(buf, additional, status) } } @@ -178,15 +176,15 @@ } } -+ ///** -+ // * The equivalent of the `*mut RustBuffer` type. -+ // * Required for callbacks taking in an out pointer. -+ // * -+ // * Size is the sum of all values in the struct. -+ // */ /** -! * @suppress + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. */ ++ /** ++ * @suppress ++ */ class RustBufferByReference : ByReference(16) { /** * Set the pointed-to `RustBuffer` to the given value. @@ -248,8 +246,122 @@ fun finalize() : RustBuffer.ByValue { val rbuf = this.rbuf *************** -*** 232,304 **** ---- 246,327 ---- +*** 232,345 **** + // This would be a good candidate for isolating in its own ffi-support lib. + // Error runtime. + @Structure.FieldOrder("code", "error_buf") + internal open class RustCallStatus : Structure() { + @JvmField var code: Int = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + fun isSuccess(): Boolean { + return code == 0 + } + + fun isError(): Boolean { + return code == 1 + } + + fun isPanic(): Boolean { + return code == 2 + } + } + + class InternalException(message: String) : Exception(message) + + // Each top-level error class has a companion object that can lift the error from the call status's rust buffer + interface CallStatusErrorHandler { + fun lift(error_buf: RustBuffer.ByValue): E; + } + + // Helpers for calling Rust + // In practice we usually need to be synchronized to call this safely, so it doesn't + // synchronize itself + + // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err + private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus(); + val return_value = callback(status) + if (status.isSuccess()) { + return return_value + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException(String.lift(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } + } + + // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR + object NullCallStatusErrorHandler: CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } + } + + // Call a rust function that returns a plain value + private inline fun rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); + } + + // Contains loading, initialization code, + // and the FFI Function declarations in a com.sun.jna.Library. + @Synchronized + private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "bdkffi" + } + + private inline fun loadIndirect( + componentName: String + ): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) + } + + // A JNA Library to expose the extern-C FFI definitions. + // This is an implementation detail which will be called internally by the public API. + + internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "bdk") + .also { lib: _UniFFILib -> + FfiConverterCallbackInterfaceProgress.register(lib) + } +! + } + } + + fun ffi_bdk_360_Blockchain_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_360_Blockchain_new(config: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun bdk_360_Blockchain_broadcast(ptr: Pointer,psbt: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_360_Wallet_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_360_Wallet_new(descriptor: RustBuffer.ByValue,change_descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, +--- 242,364 ---- // This would be a good candidate for isolating in its own ffi-support lib. // Error runtime. @Structure.FieldOrder("code", "error_buf") @@ -332,17 +444,134 @@ val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") if (libOverride != null) { return libOverride -*************** -*** 430,479 **** ---- 453,508 ---- + } + return "bdkffi" + } + + private inline fun loadIndirect( + componentName: String + ): Lib { + return Native.load(findLibraryName(componentName), Lib::class.java) + } + + // A JNA Library to expose the extern-C FFI definitions. + // This is an implementation detail which will be called internally by the public API. + + internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "bdk") + .also { lib: _UniFFILib -> + FfiConverterCallbackInterfaceProgress.register(lib) + } +! + } + } + + fun ffi_bdk_360_Blockchain_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_9c03_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun bdk_360_Blockchain_new(config: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Pointer + + fun bdk_360_Blockchain_broadcast(ptr: Pointer,psbt: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_360_Wallet_object_free(ptr: Pointer, + uniffi_out_err: RustCallStatus + ): Unit + + fun bdk_360_Wallet_new(descriptor: RustBuffer.ByValue,change_descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, +*************** +*** 461,523 **** + fun bdk_360_restore_extended_key(network: RustBuffer.ByValue,mnemonic: RustBuffer.ByValue,password: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - + fun ffi_bdk_360_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_360_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_360_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_360_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + +! + } + + // Public interface members begin here. + + // Interface implemented by anything that can contain an object reference. + // + // Such types expose a `destroy()` method that must be called to cleanly + // dispose of the contained objects. Failure to call this method may result + // in memory leaks. + // + // The easiest way to ensure this method is called is to use the `.use` + // helper method to execute a block and destroy the object at the end. + interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } + } + + inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + + // The base class for all UniFFI Object types. + // + // This class provides core operations for working with the Rust `Arc` pointer to + // the live Rust struct on the other side of the FFI. + // + // There's some subtlety here, because we have to be careful not to operate on a Rust + // struct after it has been dropped, and because we must expose a public API for freeing + // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +--- 480,548 ---- + fun bdk_360_restore_extended_key(network: RustBuffer.ByValue,mnemonic: RustBuffer.ByValue,password: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_360_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_360_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_360_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_360_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + +! } // Public interface members begin here. @@ -392,8 +621,8 @@ // struct after it has been dropped, and because we must expose a public API for freeing // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: *************** -*** 533,572 **** ---- 562,604 ---- +*** 577,638 **** +--- 602,672 ---- // Otherwise we atomically decrement and check the counter. // If it has reached zero then we destroy the underlying Rust struct. // @@ -428,6 +657,9 @@ // To be overridden in subclasses. } ++ /** ++ * @suppress ++ */ override fun destroy() { // Only allow a single call to this method. // TODO: maybe we should log a warning if called more than once? @@ -437,9 +669,452 @@ this.freeRustArcPtr() } } + } + ++ /** ++ * @suppress ++ */ + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.pointer) *************** -*** 619,736 **** ---- 651,777 ---- +*** 663,1080 **** + } + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } + } + + interface ForeignCallback : com.sun.jna.Callback { + public fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int + } + + // Magic number for the Rust proxy to call using the same mechanism as every other method, + // to free the callback once it's dropped by Rust. + internal const val IDX_CALLBACK_FREE = 0 + + internal abstract class FfiConverterCallbackInterface( + protected val foreignCallback: ForeignCallback + ) { + val handleMap = ConcurrentHandleMap() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + abstract fun register(lib: _UniFFILib) + + fun drop(handle: Handle): RustBuffer.ByValue { + return handleMap.remove(handle).let { RustBuffer.ByValue() } + } + + fun lift(n: Handle) = handleMap.get(n) + + fun read(buf: ByteBuffer) = lift(buf.getLong()) + + fun lower(v: CallbackInterface) = + handleMap.insert(v).also { + assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + fun write(v: CallbackInterface, buf: RustBufferBuilder) = + buf.putLong(lower(v)) + } + + + + enum class Network { + BITCOIN,TESTNET,SIGNET,REGTEST; + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): Network { + return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) } + } + + internal fun read(buf: ByteBuffer) = + try { values()[buf.getInt() - 1] } + catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + buf.putInt(this.ordinal + 1) + } + } + + + + + + + + sealed class DatabaseConfig { + object Memory : DatabaseConfig() +! + data class Sled( +! val config: SledDbConfiguration + ) : DatabaseConfig() +! + data class Sqlite( +! val config: SqliteDbConfiguration + ) : DatabaseConfig() +- + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig { + return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): DatabaseConfig { + return when(buf.getInt()) { + 1 -> DatabaseConfig.Memory + 2 -> DatabaseConfig.Sled( + SledDbConfiguration.read(buf) + ) + 3 -> DatabaseConfig.Sqlite( + SqliteDbConfiguration.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is DatabaseConfig.Memory -> { + buf.putInt(1) +! + } + is DatabaseConfig.Sled -> { + buf.putInt(2) + this.config.write(buf) +! + } + is DatabaseConfig.Sqlite -> { + buf.putInt(3) + this.config.write(buf) +! + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +! +! + } + + + + + + + + sealed class Transaction { +! + data class Unconfirmed( +! val details: TransactionDetails + ) : Transaction() +! + data class Confirmed( +! val details: TransactionDetails, +! val confirmation: BlockTime + ) : Transaction() +- + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): Transaction { + return liftFromRustBuffer(rbuf) { buf -> Transaction.read(buf) } + } + + internal fun read(buf: ByteBuffer): Transaction { + return when(buf.getInt()) { + 1 -> Transaction.Unconfirmed( + TransactionDetails.read(buf) + ) + 2 -> Transaction.Confirmed( + TransactionDetails.read(buf), + BlockTime.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is Transaction.Unconfirmed -> { + buf.putInt(1) + this.details.write(buf) +! + } + is Transaction.Confirmed -> { + buf.putInt(2) + this.details.write(buf) + this.confirmation.write(buf) +! + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +! +! + } + + + + + + + + sealed class BlockchainConfig { +! + data class Electrum( +! val config: ElectrumConfig + ) : BlockchainConfig() +! + data class Esplora( +! val config: EsploraConfig + ) : BlockchainConfig() +- + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig { + return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): BlockchainConfig { + return when(buf.getInt()) { + 1 -> BlockchainConfig.Electrum( + ElectrumConfig.read(buf) + ) + 2 -> BlockchainConfig.Esplora( + EsploraConfig.read(buf) + ) + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is BlockchainConfig.Electrum -> { + buf.putInt(1) + this.config.write(buf) +! + } + is BlockchainConfig.Esplora -> { + buf.putInt(2) + this.config.write(buf) +! + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +! +! + } + + + + + + enum class WordCount { + WORDS12,WORDS15,WORDS18,WORDS21,WORDS24; + + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): WordCount { + return liftFromRustBuffer(rbuf) { buf -> WordCount.read(buf) } + } + + internal fun read(buf: ByteBuffer) = + try { values()[buf.getInt() - 1] } + catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + buf.putInt(this.ordinal + 1) + } + } + + + + @Throws(BdkException::class) + + fun generateExtendedKey(network: Network, wordCount: WordCount, password: String? ): ExtendedKeyInfo { +! val _retval = + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_generate_extended_key(network.lower(), wordCount.lower(), lowerOptionalString(password) ,status) + } + return ExtendedKeyInfo.lift(_retval) + } + + + @Throws(BdkException::class) + + fun restoreExtendedKey(network: Network, mnemonic: String, password: String? ): ExtendedKeyInfo { +! val _retval = + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_restore_extended_key(network.lower(), mnemonic.lower(), lowerOptionalString(password) ,status) + } + return ExtendedKeyInfo.lift(_retval) + } + + + public interface BlockchainInterface { +! + @Throws(BdkException::class) + fun broadcast(psbt: PartiallySignedBitcoinTransaction ) +! + } + + class Blockchain( + pointer: Pointer + ) : FFIObject(pointer), BlockchainInterface { + constructor(config: BlockchainConfig ) : + this( + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Blockchain_new(config.lower() ,status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_360_Blockchain_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + +! + @Throws(BdkException::class)override fun broadcast(psbt: PartiallySignedBitcoinTransaction ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Blockchain_broadcast(it, psbt.lower() , status) + } + } +- +- + + companion object { + internal fun lift(ptr: Pointer): Blockchain { + return Blockchain(ptr) + } + + internal fun read(buf: ByteBuffer): Blockchain { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return Blockchain.lift(Pointer(buf.getLong())) + } + +! + } + } + + public interface WalletInterface { +! + fun getNewAddress(): String +! + fun getLastUnusedAddress(): String +! + @Throws(BdkException::class) + fun getBalance(): ULong +! + @Throws(BdkException::class) + fun sign(psbt: PartiallySignedBitcoinTransaction ) +! + @Throws(BdkException::class) + fun getTransactions(): List +! + fun getNetwork(): Network +! + @Throws(BdkException::class) + fun sync(blockchain: Blockchain, progress: Progress? ) +! + } + + class Wallet( + pointer: Pointer + ) : FFIObject(pointer), WalletInterface { + constructor(descriptor: String, changeDescriptor: String?, network: Network, databaseConfig: DatabaseConfig ) : + this( + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_new(descriptor.lower(), lowerOptionalString(changeDescriptor), network.lower(), databaseConfig.lower() ,status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { +--- 697,1130 ---- } } @@ -534,15 +1209,14 @@ sealed class DatabaseConfig { object Memory : DatabaseConfig() - +! data class Sled( - val config: SledDbConfiguration +! val config: SledDbConfiguration ) : DatabaseConfig() - +! data class Sqlite( - val config: SqliteDbConfiguration +! val config: SqliteDbConfiguration ) : DatabaseConfig() - + /** + * @suppress @@ -567,9 +1241,30 @@ } internal fun lower(): RustBuffer.ByValue { -*************** -*** 761,800 **** ---- 802,844 ---- + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is DatabaseConfig.Memory -> { + buf.putInt(1) +! + } + is DatabaseConfig.Sled -> { + buf.putInt(2) + this.config.write(buf) +! + } + is DatabaseConfig.Sqlite -> { + buf.putInt(3) + this.config.write(buf) +! + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +! +! } @@ -579,16 +1274,15 @@ sealed class Transaction { - +! data class Unconfirmed( - val details: TransactionDetails +! val details: TransactionDetails ) : Transaction() - +! data class Confirmed( - val details: TransactionDetails, - val confirmation: BlockTime +! val details: TransactionDetails, +! val confirmation: BlockTime ) : Transaction() - + /** + * @suppress @@ -613,10 +1307,27 @@ } internal fun lower(): RustBuffer.ByValue { -*************** -*** 821,860 **** ---- 865,907 ---- - + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is Transaction.Unconfirmed -> { + buf.putInt(1) + this.details.write(buf) +! + } + is Transaction.Confirmed -> { + buf.putInt(2) + this.details.write(buf) + this.confirmation.write(buf) +! + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +! +! } @@ -626,15 +1337,14 @@ sealed class BlockchainConfig { - +! data class Electrum( - val config: ElectrumConfig +! val config: ElectrumConfig ) : BlockchainConfig() - +! data class Esplora( - val config: EsploraConfig +! val config: EsploraConfig ) : BlockchainConfig() - + /** + * @suppress @@ -659,20 +1369,25 @@ internal fun lower(): RustBuffer.ByValue { return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) -*************** -*** 869,908 **** ---- 916,958 ---- + } + + internal fun write(buf: RustBufferBuilder) { + when(this) { + is BlockchainConfig.Electrum -> { + buf.putInt(1) + this.config.write(buf) +! } is BlockchainConfig.Esplora -> { buf.putInt(2) this.config.write(buf) - +! } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } } - - +! +! } @@ -705,28 +1420,330 @@ buf.putInt(this.ordinal + 1) } } -*************** -*** 1045,1084 **** ---- 1095,1137 ---- - - @Throws(BdkException::class)override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = + + + + @Throws(BdkException::class) + + fun generateExtendedKey(network: Network, wordCount: WordCount, password: String? ): ExtendedKeyInfo { +! val _retval = + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_generate_extended_key(network.lower(), wordCount.lower(), lowerOptionalString(password) ,status) + } + return ExtendedKeyInfo.lift(_retval) + } + + + @Throws(BdkException::class) + + fun restoreExtendedKey(network: Network, mnemonic: String, password: String? ): ExtendedKeyInfo { +! val _retval = + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_restore_extended_key(network.lower(), mnemonic.lower(), lowerOptionalString(password) ,status) + } + return ExtendedKeyInfo.lift(_retval) + } + + + public interface BlockchainInterface { +! + @Throws(BdkException::class) + fun broadcast(psbt: PartiallySignedBitcoinTransaction ) +! + } + + class Blockchain( + pointer: Pointer + ) : FFIObject(pointer), BlockchainInterface { + constructor(config: BlockchainConfig ) : + this( + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Blockchain_new(config.lower() ,status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_bdk_360_Blockchain_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + +! + @Throws(BdkException::class)override fun broadcast(psbt: PartiallySignedBitcoinTransaction ) = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_9c03_Wallet_sync(it, FfiConverterCallbackInterfaceBdkProgress.lower(progressUpdate), lowerOptionalUInt(maxAddressParam) , status) + _UniFFILib.INSTANCE.bdk_360_Blockchain_broadcast(it, psbt.lower() , status) } } - - - @Throws(BdkException::class)override fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String = - callWithPointer { + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(ptr: Pointer): Blockchain { + return Blockchain(ptr) + } + + internal fun read(buf: ByteBuffer): Blockchain { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return Blockchain.lift(Pointer(buf.getLong())) + } + +! + } + } + + public interface WalletInterface { +! + fun getNewAddress(): String +! + fun getLastUnusedAddress(): String +! + @Throws(BdkException::class) + fun getBalance(): ULong +! + @Throws(BdkException::class) + fun sign(psbt: PartiallySignedBitcoinTransaction ) +! + @Throws(BdkException::class) + fun getTransactions(): List +! + fun getNetwork(): Network +! + @Throws(BdkException::class) + fun sync(blockchain: Blockchain, progress: Progress? ) +! + } + + class Wallet( + pointer: Pointer + ) : FFIObject(pointer), WalletInterface { + constructor(descriptor: String, changeDescriptor: String?, network: Network, databaseConfig: DatabaseConfig ) : + this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_9c03_Wallet_broadcast(it, psbt.lower() , status) + _UniFFILib.INSTANCE.bdk_360_Wallet_new(descriptor.lower(), lowerOptionalString(changeDescriptor), network.lower(), databaseConfig.lower() ,status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { +*************** +*** 1082,1199 **** + _UniFFILib.INSTANCE.ffi_bdk_360_Wallet_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + override fun getNewAddress(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_get_new_address(it, status) } }.let { String.lift(it) } - - +! + override fun getLastUnusedAddress(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_get_last_unused_address(it, status) + } + }.let { + String.lift(it) + } +! +! + @Throws(BdkException::class)override fun getBalance(): ULong = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_get_balance(it, status) + } + }.let { + ULong.lift(it) + } +! +! + @Throws(BdkException::class)override fun sign(psbt: PartiallySignedBitcoinTransaction ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_sign(it, psbt.lower() , status) + } + } +! +! + @Throws(BdkException::class)override fun getTransactions(): List = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_get_transactions(it, status) + } + }.let { + liftSequenceEnumTransaction(it) + } +! + override fun getNetwork(): Network = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_get_network(it, status) + } + }.let { + Network.lift(it) + } +! +! + @Throws(BdkException::class)override fun sync(blockchain: Blockchain, progress: Progress? ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_sync(it, blockchain.lower(), lowerOptionalCallbackInterfaceProgress(progress) , status) + } + } +- +- + + companion object { + internal fun lift(ptr: Pointer): Wallet { + return Wallet(ptr) + } + + internal fun read(buf: ByteBuffer): Wallet { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return Wallet.lift(Pointer(buf.getLong())) + } + +! + } + } + + public interface PartiallySignedBitcoinTransactionInterface { +! + fun serialize(): String +! + fun txid(): String +! + } + + class PartiallySignedBitcoinTransaction( + pointer: Pointer + ) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface { + constructor(psbtBase64: String ) : + this( + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_new(psbtBase64.lower() ,status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { +--- 1132,1250 ---- + _UniFFILib.INSTANCE.ffi_bdk_360_Wallet_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + override fun getNewAddress(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_get_new_address(it, status) + } + }.let { + String.lift(it) + } +! + override fun getLastUnusedAddress(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_get_last_unused_address(it, status) + } + }.let { + String.lift(it) + } +! +! + @Throws(BdkException::class)override fun getBalance(): ULong = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_get_balance(it, status) + } + }.let { + ULong.lift(it) + } +! +! + @Throws(BdkException::class)override fun sign(psbt: PartiallySignedBitcoinTransaction ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_sign(it, psbt.lower() , status) + } + } +! +! + @Throws(BdkException::class)override fun getTransactions(): List = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_get_transactions(it, status) + } + }.let { + liftSequenceEnumTransaction(it) + } +! + override fun getNetwork(): Network = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_get_network(it, status) + } + }.let { + Network.lift(it) + } +! +! + @Throws(BdkException::class)override fun sync(blockchain: Blockchain, progress: Progress? ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_Wallet_sync(it, blockchain.lower(), lowerOptionalCallbackInterfaceProgress(progress) , status) + } + } + /** + * @suppress @@ -742,18 +1759,41 @@ return Wallet.lift(Pointer(buf.getLong())) } - +! } } public interface PartiallySignedBitcoinTransactionInterface { - +! fun serialize(): String - +! + fun txid(): String +! } + + class PartiallySignedBitcoinTransaction( + pointer: Pointer + ) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface { + constructor(psbtBase64: String ) : + this( + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_new(psbtBase64.lower() ,status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { *************** -*** 1108,1147 **** ---- 1161,1203 ---- +*** 1201,1284 **** + _UniFFILib.INSTANCE.ffi_bdk_360_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) + } + } internal fun lower(): Pointer = callWithPointer { it } @@ -766,13 +1806,105 @@ override fun serialize(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_9c03_PartiallySignedBitcoinTransaction_serialize(it, status) + _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_serialize(it, status) + } + }.let { + String.lift(it) + } +! + override fun txid(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_txid(it, status) + } + }.let { + String.lift(it) + } +- +- + + companion object { + internal fun lift(ptr: Pointer): PartiallySignedBitcoinTransaction { + return PartiallySignedBitcoinTransaction(ptr) + } + + internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong())) + } + +! + } + } + + public interface TxBuilderInterface { +! + fun addRecipient(address: String, amount: ULong ): TxBuilder +! + fun feeRate(satPerVbyte: Float ): TxBuilder +! + fun drainWallet(): TxBuilder +! + fun drainTo(address: String ): TxBuilder +! + fun enableRbf(): TxBuilder +! + fun enableRbfWithSequence(nsequence: UInt ): TxBuilder +! + @Throws(BdkException::class) + fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction +! + } + + class TxBuilder( + pointer: Pointer + ) : FFIObject(pointer), TxBuilderInterface { + constructor() : + this( + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_new(status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { +--- 1252,1336 ---- + _UniFFILib.INSTANCE.ffi_bdk_360_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + override fun serialize(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_serialize(it, status) + } + }.let { + String.lift(it) + } +! + override fun txid(): String = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_txid(it, status) } }.let { String.lift(it) } - - + /** + * @suppress @@ -788,37 +1920,249 @@ return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong())) } - +! } } public interface TxBuilderInterface { - +! fun addRecipient(address: String, amount: ULong ): TxBuilder - +! fun feeRate(satPerVbyte: Float ): TxBuilder +! + fun drainWallet(): TxBuilder +! + fun drainTo(address: String ): TxBuilder +! + fun enableRbf(): TxBuilder +! + fun enableRbfWithSequence(nsequence: UInt ): TxBuilder +! + @Throws(BdkException::class) + fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction +! + } + + class TxBuilder( + pointer: Pointer + ) : FFIObject(pointer), TxBuilderInterface { + constructor() : + this( + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_new(status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { *************** -*** 1217,1410 **** ---- 1273,1486 ---- +*** 1286,1409 **** + _UniFFILib.INSTANCE.ffi_bdk_360_TxBuilder_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + override fun addRecipient(address: String, amount: ULong ): TxBuilder = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_9c03_TxBuilder_drain_to(it, address.lower() , status) + _UniFFILib.INSTANCE.bdk_360_TxBuilder_add_recipient(it, address.lower(), amount.lower() , status) } }.let { TxBuilder.lift(it) } - - - @Throws(BdkException::class)override fun build(wallet: Wallet ): PartiallySignedBitcoinTransaction = +! + override fun feeRate(satPerVbyte: Float ): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_fee_rate(it, satPerVbyte.lower() , status) + } + }.let { + TxBuilder.lift(it) + } +! + override fun drainWallet(): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_drain_wallet(it, status) + } + }.let { + TxBuilder.lift(it) + } +! + override fun drainTo(address: String ): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_drain_to(it, address.lower() , status) + } + }.let { + TxBuilder.lift(it) + } +! + override fun enableRbf(): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_enable_rbf(it, status) + } + }.let { + TxBuilder.lift(it) + } +! + override fun enableRbfWithSequence(nsequence: UInt ): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_enable_rbf_with_sequence(it, nsequence.lower() , status) + } + }.let { + TxBuilder.lift(it) + } +! +! + @Throws(BdkException::class)override fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction = callWithPointer { rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_9c03_TxBuilder_build(it, wallet.lower() , status) + _UniFFILib.INSTANCE.bdk_360_TxBuilder_finish(it, wallet.lower() , status) + } + }.let { + PartiallySignedBitcoinTransaction.lift(it) + } +- +- + + companion object { + internal fun lift(ptr: Pointer): TxBuilder { + return TxBuilder(ptr) + } + + internal fun read(buf: ByteBuffer): TxBuilder { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return TxBuilder.lift(Pointer(buf.getLong())) + } + +! + } + } + + public interface BumpFeeTxBuilderInterface { +! + fun allowShrinking(address: String ): BumpFeeTxBuilder +! + fun enableRbf(): BumpFeeTxBuilder +! + fun enableRbfWithSequence(nsequence: UInt ): BumpFeeTxBuilder +! + @Throws(BdkException::class) + fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction +! + } + + class BumpFeeTxBuilder( + pointer: Pointer + ) : FFIObject(pointer), BumpFeeTxBuilderInterface { + constructor(txid: String, newFeeRate: Float ) : + this( + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_new(txid.lower(), newFeeRate.lower() ,status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { +--- 1338,1462 ---- + _UniFFILib.INSTANCE.ffi_bdk_360_TxBuilder_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + override fun addRecipient(address: String, amount: ULong ): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_add_recipient(it, address.lower(), amount.lower() , status) + } + }.let { + TxBuilder.lift(it) + } +! + override fun feeRate(satPerVbyte: Float ): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_fee_rate(it, satPerVbyte.lower() , status) + } + }.let { + TxBuilder.lift(it) + } +! + override fun drainWallet(): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_drain_wallet(it, status) + } + }.let { + TxBuilder.lift(it) + } +! + override fun drainTo(address: String ): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_drain_to(it, address.lower() , status) + } + }.let { + TxBuilder.lift(it) + } +! + override fun enableRbf(): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_enable_rbf(it, status) + } + }.let { + TxBuilder.lift(it) + } +! + override fun enableRbfWithSequence(nsequence: UInt ): TxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_enable_rbf_with_sequence(it, nsequence.lower() , status) + } + }.let { + TxBuilder.lift(it) + } +! +! + @Throws(BdkException::class)override fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_TxBuilder_finish(it, wallet.lower() , status) } }.let { PartiallySignedBitcoinTransaction.lift(it) } - - + /** + * @suppress @@ -834,13 +2178,454 @@ return TxBuilder.lift(Pointer(buf.getLong())) } - +! + } + } + + public interface BumpFeeTxBuilderInterface { +! + fun allowShrinking(address: String ): BumpFeeTxBuilder +! + fun enableRbf(): BumpFeeTxBuilder +! + fun enableRbfWithSequence(nsequence: UInt ): BumpFeeTxBuilder +! + @Throws(BdkException::class) + fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction +! + } + + class BumpFeeTxBuilder( + pointer: Pointer + ) : FFIObject(pointer), BumpFeeTxBuilderInterface { + constructor(txid: String, newFeeRate: Float ) : + this( + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_new(txid.lower(), newFeeRate.lower() ,status) + }) + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { +*************** +*** 1411,1750 **** + _UniFFILib.INSTANCE.ffi_bdk_360_BumpFeeTxBuilder_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + override fun allowShrinking(address: String ): BumpFeeTxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_allow_shrinking(it, address.lower() , status) + } + }.let { + BumpFeeTxBuilder.lift(it) + } +! + override fun enableRbf(): BumpFeeTxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_enable_rbf(it, status) + } + }.let { + BumpFeeTxBuilder.lift(it) + } +! + override fun enableRbfWithSequence(nsequence: UInt ): BumpFeeTxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_enable_rbf_with_sequence(it, nsequence.lower() , status) + } + }.let { + BumpFeeTxBuilder.lift(it) + } +! +! + @Throws(BdkException::class)override fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_finish(it, wallet.lower() , status) + } + }.let { + PartiallySignedBitcoinTransaction.lift(it) + } +- +- + + companion object { + internal fun lift(ptr: Pointer): BumpFeeTxBuilder { + return BumpFeeTxBuilder(ptr) + } + + internal fun read(buf: ByteBuffer): BumpFeeTxBuilder { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return BumpFeeTxBuilder.lift(Pointer(buf.getLong())) + } + +! } } data class SledDbConfiguration ( - var path: String, - var treeName: String +! var path: String, +! var treeName: String + ) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): SledDbConfiguration { + return liftFromRustBuffer(rbuf) { buf -> SledDbConfiguration.read(buf) } + } + + internal fun read(buf: ByteBuffer): SledDbConfiguration { + return SledDbConfiguration( + String.read(buf), + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.path.write(buf) +! + this.treeName.write(buf) +! + } + +! +! + } + + data class SqliteDbConfiguration ( +! var path: String + ) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): SqliteDbConfiguration { + return liftFromRustBuffer(rbuf) { buf -> SqliteDbConfiguration.read(buf) } + } + + internal fun read(buf: ByteBuffer): SqliteDbConfiguration { + return SqliteDbConfiguration( + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.path.write(buf) +! + } + +! +! + } + + data class TransactionDetails ( +! var fee: ULong?, +! var received: ULong, +! var sent: ULong, +! var txid: String + ) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): TransactionDetails { + return liftFromRustBuffer(rbuf) { buf -> TransactionDetails.read(buf) } + } + + internal fun read(buf: ByteBuffer): TransactionDetails { + return TransactionDetails( + readOptionalULong(buf), + ULong.read(buf), + ULong.read(buf), + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + writeOptionalULong(this.fee, buf) +! + this.received.write(buf) +! + this.sent.write(buf) +! + this.txid.write(buf) +! + } + +! +! + } + + data class BlockTime ( +! var height: UInt, +! var timestamp: ULong + ) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): BlockTime { + return liftFromRustBuffer(rbuf) { buf -> BlockTime.read(buf) } + } + + internal fun read(buf: ByteBuffer): BlockTime { + return BlockTime( + UInt.read(buf), + ULong.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.height.write(buf) +! + this.timestamp.write(buf) +! + } + +! +! + } + + data class ElectrumConfig ( +! var url: String, +! var socks5: String?, +! var retry: UByte, +! var timeout: UByte?, +! var stopGap: ULong + ) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): ElectrumConfig { + return liftFromRustBuffer(rbuf) { buf -> ElectrumConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): ElectrumConfig { + return ElectrumConfig( + String.read(buf), + readOptionalString(buf), + UByte.read(buf), + readOptionalUByte(buf), + ULong.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.url.write(buf) +! + writeOptionalString(this.socks5, buf) +! + this.retry.write(buf) +! + writeOptionalUByte(this.timeout, buf) +! + this.stopGap.write(buf) +! + } + +! +! + } + + data class EsploraConfig ( +! var baseUrl: String, +! var proxy: String?, +! var concurrency: UByte?, +! var stopGap: ULong, +! var timeout: ULong? + ) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): EsploraConfig { + return liftFromRustBuffer(rbuf) { buf -> EsploraConfig.read(buf) } + } + + internal fun read(buf: ByteBuffer): EsploraConfig { + return EsploraConfig( + String.read(buf), + readOptionalString(buf), + readOptionalUByte(buf), + ULong.read(buf), + readOptionalULong(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.baseUrl.write(buf) +! + writeOptionalString(this.proxy, buf) +! + writeOptionalUByte(this.concurrency, buf) +! + this.stopGap.write(buf) +! + writeOptionalULong(this.timeout, buf) +! + } + +! +! + } + + data class ExtendedKeyInfo ( +! var mnemonic: String, +! var xprv: String, +! var fingerprint: String + ) { + companion object { + internal fun lift(rbuf: RustBuffer.ByValue): ExtendedKeyInfo { + return liftFromRustBuffer(rbuf) { buf -> ExtendedKeyInfo.read(buf) } + } + + internal fun read(buf: ByteBuffer): ExtendedKeyInfo { + return ExtendedKeyInfo( + String.read(buf), + String.read(buf), + String.read(buf) + ) + } + } + + internal fun lower(): RustBuffer.ByValue { + return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) + } + + internal fun write(buf: RustBufferBuilder) { + this.mnemonic.write(buf) +! + this.xprv.write(buf) +! + this.fingerprint.write(buf) +! + } + +! +! + } + + + + sealed class BdkException(message: String): Exception(message) { + // Each variant is a nested class + // Flat enums carries a string error message, so no special implementation is necessary. + class InvalidU32Bytes(message: String) : BdkException(message) + class Generic(message: String) : BdkException(message) + class ScriptDoesntHaveAddressForm(message: String) : BdkException(message) + class NoRecipients(message: String) : BdkException(message) + class NoUtxosSelected(message: String) : BdkException(message) + class OutputBelowDustLimit(message: String) : BdkException(message) + class InsufficientFunds(message: String) : BdkException(message) + class BnBTotalTriesExceeded(message: String) : BdkException(message) + class BnBNoExactMatch(message: String) : BdkException(message) + class UnknownUtxo(message: String) : BdkException(message) + class TransactionNotFound(message: String) : BdkException(message) + class TransactionConfirmed(message: String) : BdkException(message) + class IrreplaceableTransaction(message: String) : BdkException(message) +--- 1464,1825 ---- + _UniFFILib.INSTANCE.ffi_bdk_360_BumpFeeTxBuilder_object_free(this.pointer, status) + } + } + + internal fun lower(): Pointer = callWithPointer { it } + + internal fun write(buf: RustBufferBuilder) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(this.lower())) + } + + override fun allowShrinking(address: String ): BumpFeeTxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_allow_shrinking(it, address.lower() , status) + } + }.let { + BumpFeeTxBuilder.lift(it) + } +! + override fun enableRbf(): BumpFeeTxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_enable_rbf(it, status) + } + }.let { + BumpFeeTxBuilder.lift(it) + } +! + override fun enableRbfWithSequence(nsequence: UInt ): BumpFeeTxBuilder = + callWithPointer { + rustCall() { status -> + _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_enable_rbf_with_sequence(it, nsequence.lower() , status) + } + }.let { + BumpFeeTxBuilder.lift(it) + } +! +! + @Throws(BdkException::class)override fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_finish(it, wallet.lower() , status) + } + }.let { + PartiallySignedBitcoinTransaction.lift(it) + } + ++ /** ++ * @suppress ++ */ + companion object { + internal fun lift(ptr: Pointer): BumpFeeTxBuilder { + return BumpFeeTxBuilder(ptr) + } + + internal fun read(buf: ByteBuffer): BumpFeeTxBuilder { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return BumpFeeTxBuilder.lift(Pointer(buf.getLong())) + } + +! + } + } + + data class SledDbConfiguration ( +! var path: String, +! var treeName: String ) { + /** + * @suppress @@ -864,19 +2649,18 @@ internal fun write(buf: RustBufferBuilder) { this.path.write(buf) - +! this.treeName.write(buf) - +! } - - +! +! } data class SqliteDbConfiguration ( - var path: String +! var path: String ) { -+ + /** + * @suppress + */ @@ -898,20 +2682,19 @@ internal fun write(buf: RustBufferBuilder) { this.path.write(buf) - +! } - - +! +! } data class TransactionDetails ( - var fees: ULong?, - var received: ULong, - var sent: ULong, - var txid: String +! var fee: ULong?, +! var received: ULong, +! var sent: ULong, +! var txid: String ) { -+ + /** + * @suppress + */ @@ -935,23 +2718,23 @@ } internal fun write(buf: RustBufferBuilder) { - writeOptionalULong(this.fees, buf) - + writeOptionalULong(this.fee, buf) +! this.received.write(buf) - +! this.sent.write(buf) - +! this.txid.write(buf) - +! } - - +! +! } data class BlockTime ( - var height: UInt, - var timestamp: ULong +! var height: UInt, +! var timestamp: ULong ) { + /** + * @suppress @@ -975,21 +2758,21 @@ internal fun write(buf: RustBufferBuilder) { this.height.write(buf) - +! this.timestamp.write(buf) - +! } - - +! +! } data class ElectrumConfig ( - var url: String, - var socks5: String?, - var retry: UByte, - var timeout: UByte?, - var stopGap: ULong +! var url: String, +! var socks5: String?, +! var retry: UByte, +! var timeout: UByte?, +! var stopGap: ULong ) { + /** + * @suppress @@ -1014,28 +2797,29 @@ return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) } -*************** -*** 1415,1454 **** ---- 1491,1533 ---- - + internal fun write(buf: RustBufferBuilder) { + this.url.write(buf) +! + writeOptionalString(this.socks5, buf) +! this.retry.write(buf) - +! writeOptionalUByte(this.timeout, buf) - +! this.stopGap.write(buf) - +! } - - +! +! } data class EsploraConfig ( - var baseUrl: String, - var proxy: String?, - var timeoutRead: ULong, - var timeoutWrite: ULong, - var stopGap: ULong +! var baseUrl: String, +! var proxy: String?, +! var concurrency: UByte?, +! var stopGap: ULong, +! var timeout: ULong? ) { + /** + * @suppress @@ -1049,9 +2833,9 @@ return EsploraConfig( String.read(buf), readOptionalString(buf), + readOptionalUByte(buf), ULong.read(buf), - ULong.read(buf), - ULong.read(buf) + readOptionalULong(buf) ) } } @@ -1060,28 +2844,27 @@ return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) } -*************** -*** 1457,1496 **** ---- 1536,1578 ---- - + internal fun write(buf: RustBufferBuilder) { + this.baseUrl.write(buf) +! writeOptionalString(this.proxy, buf) - - this.timeoutRead.write(buf) - - this.timeoutWrite.write(buf) - +! + writeOptionalUByte(this.concurrency, buf) +! this.stopGap.write(buf) - +! + writeOptionalULong(this.timeout, buf) +! } - - +! +! } data class ExtendedKeyInfo ( - var mnemonic: String, - var xprv: String, - var fingerprint: String +! var mnemonic: String, +! var xprv: String, +! var fingerprint: String ) { + /** + * @suppress @@ -1106,9 +2889,39 @@ internal fun write(buf: RustBufferBuilder) { this.mnemonic.write(buf) +! + this.xprv.write(buf) +! + this.fingerprint.write(buf) +! + } + +! +! + } + + + + sealed class BdkException(message: String): Exception(message) { + // Each variant is a nested class + // Flat enums carries a string error message, so no special implementation is necessary. + class InvalidU32Bytes(message: String) : BdkException(message) + class Generic(message: String) : BdkException(message) + class ScriptDoesntHaveAddressForm(message: String) : BdkException(message) + class NoRecipients(message: String) : BdkException(message) + class NoUtxosSelected(message: String) : BdkException(message) + class OutputBelowDustLimit(message: String) : BdkException(message) + class InsufficientFunds(message: String) : BdkException(message) + class BnBTotalTriesExceeded(message: String) : BdkException(message) + class BnBNoExactMatch(message: String) : BdkException(message) + class UnknownUtxo(message: String) : BdkException(message) + class TransactionNotFound(message: String) : BdkException(message) + class TransactionConfirmed(message: String) : BdkException(message) + class IrreplaceableTransaction(message: String) : BdkException(message) *************** -*** 1535,1574 **** ---- 1617,1659 ---- +*** 1758,1806 **** + class InvalidPolicyPathException(message: String) : BdkException(message) + class Signer(message: String) : BdkException(message) class InvalidNetwork(message: String) : BdkException(message) class InvalidProgressValue(message: String) : BdkException(message) class ProgressUpdateException(message: String) : BdkException(message) @@ -1127,18 +2940,15 @@ class Esplora(message: String) : BdkException(message) class Sled(message: String) : BdkException(message) class Rusqlite(message: String) : BdkException(message) - +- -+ /** -+ * @suppress -+ */ companion object ErrorHandler : CallStatusErrorHandler { override fun lift(error_buf: RustBuffer.ByValue): BdkException { return liftFromRustBuffer(error_buf) { error_buf -> read(error_buf) } } fun read(error_buf: ByteBuffer): BdkException { - +! return when(error_buf.getInt()) { 1 -> BdkException.InvalidU32Bytes(String.read(error_buf)) 2 -> BdkException.Generic(String.read(error_buf)) @@ -1152,3 +2962,264 @@ 10 -> BdkException.UnknownUtxo(String.read(error_buf)) 11 -> BdkException.TransactionNotFound(String.read(error_buf)) 12 -> BdkException.TransactionConfirmed(String.read(error_buf)) + 13 -> BdkException.IrreplaceableTransaction(String.read(error_buf)) + 14 -> BdkException.FeeRateTooLow(String.read(error_buf)) + 15 -> BdkException.FeeTooLow(String.read(error_buf)) + 16 -> BdkException.FeeRateUnavailable(String.read(error_buf)) + 17 -> BdkException.MissingKeyOrigin(String.read(error_buf)) + 18 -> BdkException.Key(String.read(error_buf)) + 19 -> BdkException.ChecksumMismatch(String.read(error_buf)) +--- 1833,1884 ---- + class InvalidPolicyPathException(message: String) : BdkException(message) + class Signer(message: String) : BdkException(message) + class InvalidNetwork(message: String) : BdkException(message) + class InvalidProgressValue(message: String) : BdkException(message) + class ProgressUpdateException(message: String) : BdkException(message) + class InvalidOutpoint(message: String) : BdkException(message) + class Descriptor(message: String) : BdkException(message) + class AddressValidator(message: String) : BdkException(message) + class Encode(message: String) : BdkException(message) + class Miniscript(message: String) : BdkException(message) + class Bip32(message: String) : BdkException(message) + class Secp256k1(message: String) : BdkException(message) + class Json(message: String) : BdkException(message) + class Hex(message: String) : BdkException(message) + class Psbt(message: String) : BdkException(message) + class PsbtParse(message: String) : BdkException(message) + class Electrum(message: String) : BdkException(message) + class Esplora(message: String) : BdkException(message) + class Sled(message: String) : BdkException(message) + class Rusqlite(message: String) : BdkException(message) + ++ ++ /** ++ * @suppress ++ */ + companion object ErrorHandler : CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): BdkException { + return liftFromRustBuffer(error_buf) { error_buf -> read(error_buf) } + } + + fun read(error_buf: ByteBuffer): BdkException { +! + return when(error_buf.getInt()) { + 1 -> BdkException.InvalidU32Bytes(String.read(error_buf)) + 2 -> BdkException.Generic(String.read(error_buf)) + 3 -> BdkException.ScriptDoesntHaveAddressForm(String.read(error_buf)) + 4 -> BdkException.NoRecipients(String.read(error_buf)) + 5 -> BdkException.NoUtxosSelected(String.read(error_buf)) + 6 -> BdkException.OutputBelowDustLimit(String.read(error_buf)) + 7 -> BdkException.InsufficientFunds(String.read(error_buf)) + 8 -> BdkException.BnBTotalTriesExceeded(String.read(error_buf)) + 9 -> BdkException.BnBNoExactMatch(String.read(error_buf)) + 10 -> BdkException.UnknownUtxo(String.read(error_buf)) + 11 -> BdkException.TransactionNotFound(String.read(error_buf)) + 12 -> BdkException.TransactionConfirmed(String.read(error_buf)) + 13 -> BdkException.IrreplaceableTransaction(String.read(error_buf)) + 14 -> BdkException.FeeRateTooLow(String.read(error_buf)) + 15 -> BdkException.FeeTooLow(String.read(error_buf)) + 16 -> BdkException.FeeRateUnavailable(String.read(error_buf)) + 17 -> BdkException.MissingKeyOrigin(String.read(error_buf)) + 18 -> BdkException.Key(String.read(error_buf)) + 19 -> BdkException.ChecksumMismatch(String.read(error_buf)) +*************** +*** 1813,1911 **** + 26 -> BdkException.InvalidOutpoint(String.read(error_buf)) + 27 -> BdkException.Descriptor(String.read(error_buf)) + 28 -> BdkException.AddressValidator(String.read(error_buf)) + 29 -> BdkException.Encode(String.read(error_buf)) + 30 -> BdkException.Miniscript(String.read(error_buf)) + 31 -> BdkException.Bip32(String.read(error_buf)) + 32 -> BdkException.Secp256k1(String.read(error_buf)) + 33 -> BdkException.Json(String.read(error_buf)) + 34 -> BdkException.Hex(String.read(error_buf)) + 35 -> BdkException.Psbt(String.read(error_buf)) + 36 -> BdkException.PsbtParse(String.read(error_buf)) + 37 -> BdkException.Electrum(String.read(error_buf)) + 38 -> BdkException.Esplora(String.read(error_buf)) + 39 -> BdkException.Sled(String.read(error_buf)) + 40 -> BdkException.Rusqlite(String.read(error_buf)) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + } + } + +! +! + } + + + // Declaration and FfiConverters for Progress Callback Interface + + public interface Progress { + fun update(progress: Float, message: String? ) +! + } + + // The ForeignCallback that is passed to Rust. + internal class ForeignCallbackCallbackInterfaceProgress : ForeignCallback { + @Suppress("TooGenericExceptionCaught") + override fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int { + val cb = FfiConverterCallbackInterfaceProgress.lift(handle) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") + return when (method) { + IDX_CALLBACK_FREE -> { + FfiConverterCallbackInterfaceProgress.drop(handle) + // No return value. + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + 0 + } + 1 -> { + val buffer = this.invokeUpdate(cb, args) + outBuf.setValue(buffer) + // Value written to out buffer. + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + 1 + } +! + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalException. + // https://github.com/mozilla/uniffi-rs/issues/351 + else -> { + // An unexpected error happened. + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + -1 + } + } + } + +! + private fun invokeUpdate(kotlinCallbackInterface: Progress, args: RustBuffer.ByValue): RustBuffer.ByValue = + try { + val buf = args.asByteBuffer() ?: throw InternalException("No ByteBuffer in RustBuffer; this is a Uniffi bug") + kotlinCallbackInterface.update( +! Float.read(buf), +! readOptionalString(buf) + ) + .let { RustBuffer.ByValue() } + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + } finally { + RustBuffer.free(args) + } + +! + } + + // The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. + internal object FfiConverterCallbackInterfaceProgress: FfiConverterCallbackInterface( + foreignCallback = ForeignCallbackCallbackInterfaceProgress() + ) { + override fun register(lib: _UniFFILib) { + rustCall() { status -> + lib.ffi_bdk_360_Progress_init_callback(this.foreignCallback, status) + } + } + } + internal fun UByte.Companion.lift(v: Byte): UByte { + return v.toUByte() + } + + internal fun UByte.Companion.read(buf: ByteBuffer): UByte { + return UByte.lift(buf.get()) + } + +--- 1891,1989 ---- + 26 -> BdkException.InvalidOutpoint(String.read(error_buf)) + 27 -> BdkException.Descriptor(String.read(error_buf)) + 28 -> BdkException.AddressValidator(String.read(error_buf)) + 29 -> BdkException.Encode(String.read(error_buf)) + 30 -> BdkException.Miniscript(String.read(error_buf)) + 31 -> BdkException.Bip32(String.read(error_buf)) + 32 -> BdkException.Secp256k1(String.read(error_buf)) + 33 -> BdkException.Json(String.read(error_buf)) + 34 -> BdkException.Hex(String.read(error_buf)) + 35 -> BdkException.Psbt(String.read(error_buf)) + 36 -> BdkException.PsbtParse(String.read(error_buf)) + 37 -> BdkException.Electrum(String.read(error_buf)) + 38 -> BdkException.Esplora(String.read(error_buf)) + 39 -> BdkException.Sled(String.read(error_buf)) + 40 -> BdkException.Rusqlite(String.read(error_buf)) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + } + } + +! +! + } + + + // Declaration and FfiConverters for Progress Callback Interface + + public interface Progress { + fun update(progress: Float, message: String? ) +! + } + + // The ForeignCallback that is passed to Rust. + internal class ForeignCallbackCallbackInterfaceProgress : ForeignCallback { + @Suppress("TooGenericExceptionCaught") + override fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int { + val cb = FfiConverterCallbackInterfaceProgress.lift(handle) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") + return when (method) { + IDX_CALLBACK_FREE -> { + FfiConverterCallbackInterfaceProgress.drop(handle) + // No return value. + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + 0 + } + 1 -> { + val buffer = this.invokeUpdate(cb, args) + outBuf.setValue(buffer) + // Value written to out buffer. + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + 1 + } +! + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalException. + // https://github.com/mozilla/uniffi-rs/issues/351 + else -> { + // An unexpected error happened. + // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` + -1 + } + } + } + +! + private fun invokeUpdate(kotlinCallbackInterface: Progress, args: RustBuffer.ByValue): RustBuffer.ByValue = + try { + val buf = args.asByteBuffer() ?: throw InternalException("No ByteBuffer in RustBuffer; this is a Uniffi bug") + kotlinCallbackInterface.update( +! Float.read(buf), +! readOptionalString(buf) + ) + .let { RustBuffer.ByValue() } + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + } finally { + RustBuffer.free(args) + } + +! + } + + // The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. + internal object FfiConverterCallbackInterfaceProgress: FfiConverterCallbackInterface( + foreignCallback = ForeignCallbackCallbackInterfaceProgress() + ) { + override fun register(lib: _UniFFILib) { + rustCall() { status -> + lib.ffi_bdk_360_Progress_init_callback(this.foreignCallback, status) + } + } + } + internal fun UByte.Companion.lift(v: Byte): UByte { + return v.toUByte() + } + + internal fun UByte.Companion.read(buf: ByteBuffer): UByte { + return UByte.lift(buf.get()) + } + From a9e868cb7e371ada57ce36f3b7d07333b6e6de65 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 16 May 2022 10:38:12 -0700 Subject: [PATCH 222/272] Bump version to 0.7.0-SNAPSHOT --- android/build.gradle.kts | 6 +++--- jvm/build.gradle.kts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 2ea0c6d..5d46d36 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -57,7 +57,7 @@ afterEvaluate { create("maven") { groupId = "org.bitcoindevkit" artifactId = "bdk-android" - version = "0.6.0-SNAPSHOT" + version = "0.7.0-SNAPSHOT" from(components["release"]) pom { name.set("bdk-android") @@ -105,8 +105,8 @@ tasks.withType().configureEach { dokkaSourceSets { named("main") { moduleName.set("bdk-android") - moduleVersion.set("0.6.0-SNAPSHOT") + moduleVersion.set("0.7.0-SNAPSHOT") includes.from("Module.md") } } -} \ No newline at end of file +} diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index 6d1d2f7..366c74d 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -51,7 +51,7 @@ afterEvaluate { groupId = "org.bitcoindevkit" artifactId = "bdk-jvm" - version = "0.6.0-SNAPSHOT" + version = "0.7.0-SNAPSHOT" from(components["java"]) @@ -101,7 +101,7 @@ tasks.withType().configureEach { dokkaSourceSets { named("main") { moduleName.set("bdk-jvm") - moduleVersion.set("0.6.0-SNAPSHOT") + moduleVersion.set("0.7.0-SNAPSHOT") includes.from("Module.md") } } From 9b8cc006ba694f3f0e1e9eed607b9f3ab3caed6d Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 18 May 2022 13:25:58 -0400 Subject: [PATCH 223/272] Build both x86_64 and aarch64 binaries when building bdk-jvm on macOS --- .../plugins/generate-jvm-bindings.gradle.kts | 114 ++++++++++-------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts index edd8b23..2eb11a0 100644 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts @@ -1,65 +1,81 @@ package org.bitcoindevkit.plugins -// register a task of type Exec called buildJvmBinary -// which will run something like +// register a task called buildJvmBinaries which will run something like // cargo build --release --target aarch64-apple-darwin -val buildJvmBinary by tasks.register("buildJvmBinary") { - - workingDir("${project.projectDir}/../bdk-ffi") - val cargoArgs: MutableList = mutableListOf("build", "--release", "--target") - - if (operatingSystem == OS.MAC && architecture == Arch.X86_64) { - cargoArgs.add("x86_64-apple-darwin") - } else if (operatingSystem == OS.MAC && architecture == Arch.AARCH64) { - cargoArgs.add("aarch64-apple-darwin") - } else if (operatingSystem == OS.LINUX) { - cargoArgs.add("x86_64-unknown-linux-gnu") - } - - executable("cargo") - args(cargoArgs) - - doLast { - println("Native library for bdk-jvm on ${cargoArgs.last()} successfully built") +val buildJvmBinaries by tasks.register("buildJvmBinaries") { + if (operatingSystem == OS.MAC) { + exec { + workingDir("${project.projectDir}/../bdk-ffi") + executable("cargo") + val cargoArgs: List = listOf("build", "--release", "--target", "x86_64-apple-darwin") + args(cargoArgs) + } + exec { + workingDir("${project.projectDir}/../bdk-ffi") + executable("cargo") + val cargoArgs: List = listOf("build", "--release", "--target", "aarch64-apple-darwin") + args(cargoArgs) + } + } else if(operatingSystem == OS.LINUX) { + exec { + workingDir("${project.projectDir}/../bdk-ffi") + executable("cargo") + val cargoArgs: List = listOf("build", "--release", "--target", "x86_64-unknown-linux-gnu") + args(cargoArgs) + } } } - // move the native libs build by cargo from bdk-ffi/target/.../release/ // to their place in the bdk-jvm library -val moveNativeJvmLib by tasks.register("moveNativeJvmLib") { +val moveNativeJvmLibs by tasks.register("moveNativeJvmLibs") { - dependsOn(buildJvmBinary) + // dependsOn(buildJvmBinaryX86_64MacOS, buildJvmBinaryAarch64MacOS, buildJvmBinaryLinux) + dependsOn(buildJvmBinaries) - var targetDir = "" - var resDir = "" - var ext = "" - if (operatingSystem == OS.MAC && architecture == Arch.X86_64) { - targetDir = "x86_64-apple-darwin" - resDir = "darwin-x86-64" - ext = "dylib" - } else if (operatingSystem == OS.MAC && architecture == Arch.AARCH64) { - targetDir = "aarch64-apple-darwin" - resDir = "darwin-aarch64" - ext = "dylib" + data class CopyMetadata(val targetDir: String, val resDir: String, val ext: String) + val libsToCopy: MutableList = mutableListOf() + + if (operatingSystem == OS.MAC) { + libsToCopy.add( + CopyMetadata( + targetDir = "aarch64-apple-darwin", + resDir = "darwin-aarch64", + ext = "dylib" + ) + ) + libsToCopy.add( + CopyMetadata( + targetDir = "x86_64-apple-darwin", + resDir = "darwin-x86-64", + ext = "dylib" + ) + ) } else if (operatingSystem == OS.LINUX) { - targetDir = "x86_64-unknown-linux-gnu" - resDir = "linux-x86-64" - ext = "so" + libsToCopy.add( + CopyMetadata( + targetDir = "x86_64-unknown-linux-gnu", + resDir = "linux-x86-64", + ext = "so" + ) + ) } - from("${project.projectDir}/../bdk-ffi/target/$targetDir/release/libbdkffi.$ext") - into("${project.projectDir}/../jvm/src/main/resources/$resDir/") - - doLast { - println("$targetDir native binaries for JVM moved to ./jvm/src/main/resources/$resDir/") + libsToCopy.forEach { + doFirst { + copy { + with(it) { + from("${project.projectDir}/../bdk-ffi/target/${this.targetDir}/release/libbdkffi.${this.ext}") + into("${project.projectDir}/../jvm/src/main/resources/${this.resDir}/") + } + } + } } } -// generate the bindings using the bdk-ffi-bindgen tool -// created in the bdk-ffi submodule +// generate the bindings using the bdk-ffi-bindgen tool created in the bdk-ffi submodule val generateJvmBindings by tasks.register("generateJvmBindings") { - dependsOn(moveNativeJvmLib) + dependsOn(moveNativeJvmLibs) workingDir("${project.projectDir}/../bdk-ffi") executable("cargo") @@ -70,15 +86,15 @@ val generateJvmBindings by tasks.register("generateJvmBindings") { } } -// create an aggregate task which will run the 3 required tasks to build the JVM libs in order -// the task will also appear in the printout of the ./gradlew tasks task with group and description +// we need an aggregate task which will run the 3 required tasks to build the JVM libs in order +// the task will also appear in the printout of the ./gradlew tasks task with a group and description tasks.register("buildJvmLib") { group = "Bitcoindevkit" description = "Aggregate task to build JVM library" dependsOn( - buildJvmBinary, - moveNativeJvmLib, + buildJvmBinaries, + moveNativeJvmLibs, generateJvmBindings ) } From b76bdfcb22ea5c2fe70b2bb58072c60032e1cfb4 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 18 May 2022 13:35:40 -0400 Subject: [PATCH 224/272] Remove unnecessary architecture enum in plugin --- .../main/kotlin/org/bitcoindevkit/plugins/Enums.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt index d00bc94..8f586b6 100644 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt @@ -7,18 +7,6 @@ val operatingSystem: OS = when { else -> OS.OTHER } -val architecture: Arch = when (System.getProperty("os.arch")) { - "x86_64" -> Arch.X86_64 - "aarch64" -> Arch.AARCH64 - else -> Arch.OTHER -} - -enum class Arch { - AARCH64, - X86_64, - OTHER, -} - enum class OS { MAC, LINUX, From fe045c13f472a743df6fcbdcf778688ee736b4d7 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 14 Jun 2022 16:30:06 -0300 Subject: [PATCH 225/272] Bump bdk-ffi version to 0.7.0 --- android/build.gradle.kts | 2 -- bdk-ffi | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 5d46d36..ac1c9c2 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -17,8 +17,6 @@ android { defaultConfig { minSdk = 21 targetSdk = 31 - // versionCode = 1 - // versionName = "v0.2.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") } diff --git a/bdk-ffi b/bdk-ffi index 30e54ac..80ed21e 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 30e54ac067f68e8c22d652837b4d5901c12e3384 +Subproject commit 80ed21e4c9e61d6224e074258229a4d6da6cc049 From de9fde0d9c7708240d71d5f1ed51c117ceb15549 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 14 Jun 2022 16:31:10 -0300 Subject: [PATCH 226/272] Migrate tests to API version 0.7.0 --- .../org/bitcoindevkit/AndroidLibTest.kt | 8 +-- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 55 +++++++++---------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index 8fc157c..ced2f5e 100644 --- a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -47,7 +47,7 @@ class AndroidLibTest { @Test fun memoryWalletNewAddress() { val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val address = wallet.getNewAddress() + val address = wallet.getAddress(AddressIndex.NEW).address assertNotNull(address) assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) } @@ -62,7 +62,7 @@ class AndroidLibTest { val testDataDir = getTestDataDir() val databaseConfig = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val address = wallet.getNewAddress() + val address = wallet.getAddress(AddressIndex.NEW).address assertNotNull(address) assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) cleanupTestDataDir(testDataDir) @@ -73,7 +73,7 @@ class AndroidLibTest { val testDataDir = getTestDataDir()+"/bdk-wallet.sqlite" val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir)) val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val blockchain = Blockchain(blockchainConfig); + val blockchain = Blockchain(blockchainConfig) wallet.sync(blockchain, LogProgress()) val balance = wallet.getBalance() assertTrue(balance > 0u) @@ -108,7 +108,7 @@ class AndroidLibTest { @Test fun onlineWalletSyncGetBalance() { val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val blockchain = Blockchain(blockchainConfig); + val blockchain = Blockchain(blockchainConfig) wallet.sync(blockchain, LogProgress()) val balance = wallet.getBalance() assertTrue(balance > 0u) diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index ba03af4..6a02dec 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -41,7 +41,7 @@ class JvmLibTest { @Test fun memoryWalletNewAddress() { val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val address = wallet.getNewAddress() + val address = wallet.getAddress(AddressIndex.NEW).address assertNotNull(address) assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) } @@ -56,7 +56,7 @@ class JvmLibTest { val testDataDir = getTestDataDir() val databaseConfig = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val address = wallet.getNewAddress() + val address = wallet.getAddress(AddressIndex.NEW).address assertNotNull(address) assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) cleanupTestDataDir(testDataDir) @@ -103,7 +103,7 @@ class JvmLibTest { @Test fun onlineWalletSyncGetBalance() { val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val blockchain = Blockchain(blockchainConfig); + val blockchain = Blockchain(blockchainConfig) wallet.sync(blockchain, LogProgress()) val balance = wallet.getBalance() assertTrue(balance > 0u) @@ -153,29 +153,28 @@ class JvmLibTest { txBuilder.finish(wallet) } - // Comment this test in for local testing, you will need let it fail ones to get an address - // to pre-fund the test wallet before the test will pass. -// @Test -// fun walletTxBuilderDrainWallet() { -// val descriptor = -// "wpkh([8da6afbe/84'/1'/0'/0]tprv8hY7jbMbe17EH1cLw2feTyNDYvjcFYauLmbneBqVnDERBrV51LrxWjCYRZwWS5keYn3ijb7iHJuEzXQk7EzgPeKRBVNBgC4oFPDxGND5S3V/*)" -// val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig, blockchainConfig) -// wallet.sync(LogProgress(), null) -// val balance = wallet.getBalance() -// if (balance > 2000u) { -// println("balance $balance") -// // send all coins back to https://bitcoinfaucet.uo1.net -// val faucetAddress = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" -// val txBuilder = TxBuilder().drainWallet().drainTo(faucetAddress).feeRate(1.1f) -// val psbt = txBuilder.build(wallet) -// wallet.sign(psbt) -// val txid = wallet.broadcast(psbt) -// println("https://mempool.space/testnet/tx/$txid") -// assertNotNull(txid) -// } else { -// val depositAddress = wallet.getLastUnusedAddress() -// fail("Send more testnet coins to: $depositAddress") -// } -// } - + // Comment this test in for local testing, you will need let it fail ones to get an address + // to pre-fund the test wallet before the test will pass. + // @Test + // fun walletTxBuilderDrainWallet() { + // val descriptor = "wpkh([8da6afbe/84'/1'/0'/0]tprv8hY7jbMbe17EH1cLw2feTyNDYvjcFYauLmbneBqVnDERBrV51LrxWjCYRZwWS5keYn3ijb7iHJuEzXQk7EzgPeKRBVNBgC4oFPDxGND5S3V/*)" + // val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) + // val blockchain = Blockchain(blockchainConfig) + // wallet.sync(blockchain, LogProgress()) + // val balance = wallet.getBalance() + // if (balance > 2000u) { + // println("balance $balance") + // // send all coins back to https://bitcoinfaucet.uo1.net + // val faucetAddress = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" + // val txBuilder = TxBuilder().drainWallet().drainTo(faucetAddress).feeRate(1.1f) + // val psbt = txBuilder.finish(wallet) + // wallet.sign(psbt) + // val txid = blockchain.broadcast(psbt) + // println("https://mempool.space/testnet/tx/$txid") + // assertNotNull(txid) + // } else { + // val depositAddress = wallet.getAddress(AddressIndex.LAST_UNUSED).address + // fail("Send more testnet coins to: $depositAddress") + // } + // } } From 5f2297ed7c60e434192335a701fae91ecb07c61c Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 15 Jun 2022 08:48:24 -0300 Subject: [PATCH 227/272] Hardcode ANDROID_NDK_ROOT to fix CI issues --- .github/workflows/test.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b432c99..38b4e7a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,6 +2,9 @@ name: Tests on: [push, pull_request] +env: + ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/21.4.7075529 + jobs: build: runs-on: ubuntu-latest From df67ded9f1c88477e877973fa3076fa7c91c1aa7 Mon Sep 17 00:00:00 2001 From: Tang Yetong Date: Thu, 16 Jun 2022 03:23:26 +0800 Subject: [PATCH 228/272] Feat: Update gitmodule to use HTTPS Signed-off-by: Tang Yetong --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 9c7f2ac..c582725 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "bdk-ffi"] path = bdk-ffi - url = git@github.com:bitcoindevkit/bdk-ffi.git + url = https://github.com/bitcoindevkit/bdk-ffi.git From d8c3ddca167002a9e63db9ee42150a4432ece723 Mon Sep 17 00:00:00 2001 From: Nicola Busanello Date: Wed, 15 Jun 2022 10:43:57 +0200 Subject: [PATCH 229/272] add armv7 support --- .../generate-android-bindings.gradle.kts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts index 202a5a2..e81c60a 100644 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts +++ b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts @@ -68,6 +68,36 @@ val buildAndroidX86_64Binary by tasks.register("buildAndroidX86_64Binary") } } +// armeabi-v7a version of the library for older 32-bit Android hardware +val buildAndroidArmv7Binary by tasks.register("buildAndroidArmv7Binary") { + + workingDir("${project.projectDir}/../bdk-ffi") + val cargoArgs: MutableList = mutableListOf("build", "--release", "--target", "armv7-linux-androideabi") + + executable("cargo") + args(cargoArgs) + + // if ANDROID_NDK_ROOT is not set then set it to github actions default + if (System.getenv("ANDROID_NDK_ROOT") == null) { + environment( + Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle") + ) + } + + environment( + // add build toolchain to PATH + Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"), + + Pair("CFLAGS", "-D__ANDROID_API__=21"), + Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER", "armv7a-linux-androideabi21-clang"), + Pair("CC", "armv7a-linux-androideabi21-clang") + ) + + doLast { + println("Native library for bdk-android on armv7 built successfully") + } +} + // move the native libs build by cargo from bdk-ffi/target//release/ // to their place in the bdk-android library // the task only copies the available binaries built using the buildAndroidBinary tasks @@ -85,6 +115,10 @@ val moveNativeAndroidLibs by tasks.register("moveNativeAndroidLibs") { from("${project.projectDir}/../bdk-ffi/target/x86_64-linux-android/release/libbdkffi.so") } + into("armeabi-v7a") { + from("${project.projectDir}/../bdk-ffi/target/armv7-linux-androideabi/release/libbdkffi.so") + } + doLast { println("Native binaries for Android moved to ./android/src/main/jniLibs/") } @@ -112,6 +146,7 @@ tasks.register("buildAndroidLib") { dependsOn( buildAndroidAarch64Binary, buildAndroidX86_64Binary, + buildAndroidArmv7Binary, moveNativeAndroidLibs, generateAndroidBindings ) From 860130f08c8b75a01a86bdbea769e416f6b1e471 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 15 Jun 2022 16:37:20 -0300 Subject: [PATCH 230/272] Add armv7-linux-androideabi to CI Rust android targets --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 38b4e7a..089651f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -33,7 +33,7 @@ jobs: java-version: 11 - name: Install rust android targets - run: rustup target add x86_64-linux-android aarch64-linux-android + run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi - name: Install uniffi-bindgen run: cargo install uniffi_bindgen --version 0.16.0 From d213266c525b88088795afe5c0c1a4c8946de817 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 17 Jun 2022 14:49:15 -0300 Subject: [PATCH 231/272] Bump version to 0.8.0-SNAPSHOT (#67) --- android/build.gradle.kts | 4 ++-- jvm/build.gradle.kts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index ac1c9c2..74b1784 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -55,7 +55,7 @@ afterEvaluate { create("maven") { groupId = "org.bitcoindevkit" artifactId = "bdk-android" - version = "0.7.0-SNAPSHOT" + version = "0.8.0-SNAPSHOT" from(components["release"]) pom { name.set("bdk-android") @@ -103,7 +103,7 @@ tasks.withType().configureEach { dokkaSourceSets { named("main") { moduleName.set("bdk-android") - moduleVersion.set("0.7.0-SNAPSHOT") + moduleVersion.set("0.8.0-SNAPSHOT") includes.from("Module.md") } } diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index 366c74d..fd792a0 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -51,7 +51,7 @@ afterEvaluate { groupId = "org.bitcoindevkit" artifactId = "bdk-jvm" - version = "0.7.0-SNAPSHOT" + version = "0.8.0-SNAPSHOT" from(components["java"]) @@ -101,7 +101,7 @@ tasks.withType().configureEach { dokkaSourceSets { named("main") { moduleName.set("bdk-jvm") - moduleVersion.set("0.7.0-SNAPSHOT") + moduleVersion.set("0.8.0-SNAPSHOT") includes.from("Module.md") } } From c4b1985076abc29af8330d92f98ba5fee704c8ab Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 17 Jun 2022 14:49:47 -0300 Subject: [PATCH 232/272] Remove install of uniffi-bindgen in CI workflow (#65) --- .github/workflows/test.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 089651f..883f53c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -35,9 +35,6 @@ jobs: - name: Install rust android targets run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi - - name: Install uniffi-bindgen - run: cargo install uniffi_bindgen --version 0.16.0 - - name: Build bdk-android library run: ./gradlew :android:buildAndroidLib From b8129ccd15e972afef5f1edcf6338de9315e0480 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 29 Jul 2022 14:26:25 -0400 Subject: [PATCH 233/272] Bump bdk-ffi submodule to version 0.8.0 Signed-off-by: thunderbiscuit --- bdk-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-ffi b/bdk-ffi index 80ed21e..138200d 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 80ed21e4c9e61d6224e074258229a4d6da6cc049 +Subproject commit 138200db078cbd2b8a8d32ca8f4360bcd4b1eccd From 697b58d33e3518909c2c989f41f4f84946f73dc5 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Mon, 1 Aug 2022 22:27:05 -0400 Subject: [PATCH 234/272] Bump version to 0.9.0-SNAPSHOT --- android/build.gradle.kts | 20 ++++++++++---------- jvm/build.gradle.kts | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 74b1784..e4a377c 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -55,7 +55,7 @@ afterEvaluate { create("maven") { groupId = "org.bitcoindevkit" artifactId = "bdk-android" - version = "0.8.0-SNAPSHOT" + version = "0.9.0-SNAPSHOT" from(components["release"]) pom { name.set("bdk-android") @@ -99,12 +99,12 @@ signing { sign(publishing.publications) } -tasks.withType().configureEach { - dokkaSourceSets { - named("main") { - moduleName.set("bdk-android") - moduleVersion.set("0.8.0-SNAPSHOT") - includes.from("Module.md") - } - } -} +// tasks.withType().configureEach { +// dokkaSourceSets { +// named("main") { +// moduleName.set("bdk-android") +// moduleVersion.set("0.8.0-SNAPSHOT") +// includes.from("Module.md") +// } +// } +// } diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index fd792a0..385fbac 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -51,7 +51,7 @@ afterEvaluate { groupId = "org.bitcoindevkit" artifactId = "bdk-jvm" - version = "0.8.0-SNAPSHOT" + version = "0.9.0-SNAPSHOT" from(components["java"]) @@ -97,12 +97,12 @@ signing { sign(publishing.publications) } -tasks.withType().configureEach { - dokkaSourceSets { - named("main") { - moduleName.set("bdk-jvm") - moduleVersion.set("0.8.0-SNAPSHOT") - includes.from("Module.md") - } - } -} +// tasks.withType().configureEach { +// dokkaSourceSets { +// named("main") { +// moduleName.set("bdk-jvm") +// moduleVersion.set("0.8.0-SNAPSHOT") +// includes.from("Module.md") +// } +// } +// } From d00813e1d6369bc23923663810d11547dd847c73 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 19 Aug 2022 07:53:50 -0400 Subject: [PATCH 235/272] Fix CI test workflow using pinned Android NDK --- .github/workflows/test.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 883f53c..0e214b9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -4,11 +4,20 @@ on: [push, pull_request] env: ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/21.4.7075529 + # By default the new ubuntu-20.04 images use the following ANDROID_NDK_ROOT + # ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/25.0.8775105 jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: + - name: Install Android NDK 21.4.7075529 + run: | + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk + SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + - name: Check out PR branch uses: actions/checkout@v2 From 989b7339a0e7e44ec56f3d3056180bc1ce278de8 Mon Sep 17 00:00:00 2001 From: Kirill Zhukov Date: Fri, 29 Jul 2022 15:23:23 -0700 Subject: [PATCH 236/272] Convert Gradle script plugin for generating UniFfi bindings into a composite build. --- buildSrc/build.gradle.kts | 8 - buildSrc/settings.gradle.kts | 0 .../generate-android-bindings.gradle.kts | 153 --------------- .../plugins/generate-jvm-bindings.gradle.kts | 100 ---------- {buildSrc => plugins}/README.md | 0 plugins/build.gradle.kts | 17 ++ plugins/settings.gradle.kts | 8 + .../kotlin/org/bitcoindevkit/plugins/Enums.kt | 0 .../plugins/UniFfiAndroidPlugin.kt | 178 ++++++++++++++++++ .../bitcoindevkit/plugins/UniFfiJvmPlugin.kt | 114 +++++++++++ settings.gradle.kts | 1 + 11 files changed, 318 insertions(+), 261 deletions(-) delete mode 100644 buildSrc/build.gradle.kts delete mode 100644 buildSrc/settings.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts rename {buildSrc => plugins}/README.md (100%) create mode 100644 plugins/build.gradle.kts create mode 100644 plugins/settings.gradle.kts rename {buildSrc => plugins}/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt (100%) create mode 100644 plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt create mode 100644 plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index 3f0ee3c..0000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,8 +0,0 @@ -plugins { - `kotlin-dsl` - // id("org.gradle.kotlin.kotlin-dsl") version "2.2.0" -} - -repositories { - mavenCentral() -} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts deleted file mode 100644 index e69de29..0000000 diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts deleted file mode 100644 index e81c60a..0000000 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-android-bindings.gradle.kts +++ /dev/null @@ -1,153 +0,0 @@ -package org.bitcoindevkit.plugins - -import org.gradle.kotlin.dsl.register - -val llvmArchPath = when (operatingSystem) { - OS.MAC -> "darwin-x86_64" - OS.LINUX -> "linux-x86_64" - OS.OTHER -> throw Error("Cannot build Android library from current architecture") -} - -// arm64-v8a is the most popular hardware architecture for Android -val buildAndroidAarch64Binary by tasks.register("buildAndroidAarch64Binary") { - - workingDir("${project.projectDir}/../bdk-ffi") - val cargoArgs: MutableList = mutableListOf("build", "--release", "--target", "aarch64-linux-android") - - executable("cargo") - args(cargoArgs) - - // if ANDROID_NDK_ROOT is not set then set it to github actions default - if (System.getenv("ANDROID_NDK_ROOT") == null) { - environment( - Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle") - ) - } - - environment( - // add build toolchain to PATH - Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"), - - Pair("CFLAGS", "-D__ANDROID_API__=21"), - Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android21-clang"), - Pair("CC", "aarch64-linux-android21-clang") - ) - - doLast { - println("Native library for bdk-android on aarch64 built successfully") - } -} - -// the x86_64 version of the library is mostly used by emulators -val buildAndroidX86_64Binary by tasks.register("buildAndroidX86_64Binary") { - - workingDir("${project.projectDir}/../bdk-ffi") - val cargoArgs: MutableList = mutableListOf("build", "--release", "--target", "x86_64-linux-android") - - executable("cargo") - args(cargoArgs) - - // if ANDROID_NDK_ROOT is not set then set it to github actions default - if (System.getenv("ANDROID_NDK_ROOT") == null) { - environment( - Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle") - ) - } - - environment( - // add build toolchain to PATH - Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"), - - Pair("CFLAGS", "-D__ANDROID_API__=21"), - Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android21-clang"), - Pair("CC", "x86_64-linux-android21-clang") - ) - - doLast { - println("Native library for bdk-android on x86_64 built successfully") - } -} - -// armeabi-v7a version of the library for older 32-bit Android hardware -val buildAndroidArmv7Binary by tasks.register("buildAndroidArmv7Binary") { - - workingDir("${project.projectDir}/../bdk-ffi") - val cargoArgs: MutableList = mutableListOf("build", "--release", "--target", "armv7-linux-androideabi") - - executable("cargo") - args(cargoArgs) - - // if ANDROID_NDK_ROOT is not set then set it to github actions default - if (System.getenv("ANDROID_NDK_ROOT") == null) { - environment( - Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle") - ) - } - - environment( - // add build toolchain to PATH - Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"), - - Pair("CFLAGS", "-D__ANDROID_API__=21"), - Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER", "armv7a-linux-androideabi21-clang"), - Pair("CC", "armv7a-linux-androideabi21-clang") - ) - - doLast { - println("Native library for bdk-android on armv7 built successfully") - } -} - -// move the native libs build by cargo from bdk-ffi/target//release/ -// to their place in the bdk-android library -// the task only copies the available binaries built using the buildAndroidBinary tasks -val moveNativeAndroidLibs by tasks.register("moveNativeAndroidLibs") { - - dependsOn(buildAndroidAarch64Binary) - - into("${project.projectDir}/../android/src/main/jniLibs/") - - into("arm64-v8a") { - from("${project.projectDir}/../bdk-ffi/target/aarch64-linux-android/release/libbdkffi.so") - } - - into("x86_64") { - from("${project.projectDir}/../bdk-ffi/target/x86_64-linux-android/release/libbdkffi.so") - } - - into("armeabi-v7a") { - from("${project.projectDir}/../bdk-ffi/target/armv7-linux-androideabi/release/libbdkffi.so") - } - - doLast { - println("Native binaries for Android moved to ./android/src/main/jniLibs/") - } -} - -// generate the bindings using the bdk-ffi-bindgen tool located in the bdk-ffi submodule -val generateAndroidBindings by tasks.register("generateAndroidBindings") { - dependsOn(moveNativeAndroidLibs) - - workingDir("${project.projectDir}/../bdk-ffi") - executable("cargo") - args("run", "--package", "bdk-ffi-bindgen", "--", "--language", "kotlin", "--out-dir", "../android/src/main/kotlin") - - doLast { - println("Android bindings file successfully created") - } -} - -// create an aggregate task which will run the required tasks to build the Android libs in order -// the task will also appear in the printout of the ./gradlew tasks task with group and description -tasks.register("buildAndroidLib") { - group = "Bitcoindevkit" - description = "Aggregate task to build Android library" - - dependsOn( - buildAndroidAarch64Binary, - buildAndroidX86_64Binary, - buildAndroidArmv7Binary, - moveNativeAndroidLibs, - generateAndroidBindings - ) -} diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts b/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts deleted file mode 100644 index 2eb11a0..0000000 --- a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/generate-jvm-bindings.gradle.kts +++ /dev/null @@ -1,100 +0,0 @@ -package org.bitcoindevkit.plugins - -// register a task called buildJvmBinaries which will run something like -// cargo build --release --target aarch64-apple-darwin -val buildJvmBinaries by tasks.register("buildJvmBinaries") { - if (operatingSystem == OS.MAC) { - exec { - workingDir("${project.projectDir}/../bdk-ffi") - executable("cargo") - val cargoArgs: List = listOf("build", "--release", "--target", "x86_64-apple-darwin") - args(cargoArgs) - } - exec { - workingDir("${project.projectDir}/../bdk-ffi") - executable("cargo") - val cargoArgs: List = listOf("build", "--release", "--target", "aarch64-apple-darwin") - args(cargoArgs) - } - } else if(operatingSystem == OS.LINUX) { - exec { - workingDir("${project.projectDir}/../bdk-ffi") - executable("cargo") - val cargoArgs: List = listOf("build", "--release", "--target", "x86_64-unknown-linux-gnu") - args(cargoArgs) - } - } -} -// move the native libs build by cargo from bdk-ffi/target/.../release/ -// to their place in the bdk-jvm library -val moveNativeJvmLibs by tasks.register("moveNativeJvmLibs") { - - // dependsOn(buildJvmBinaryX86_64MacOS, buildJvmBinaryAarch64MacOS, buildJvmBinaryLinux) - dependsOn(buildJvmBinaries) - - data class CopyMetadata(val targetDir: String, val resDir: String, val ext: String) - val libsToCopy: MutableList = mutableListOf() - - if (operatingSystem == OS.MAC) { - libsToCopy.add( - CopyMetadata( - targetDir = "aarch64-apple-darwin", - resDir = "darwin-aarch64", - ext = "dylib" - ) - ) - libsToCopy.add( - CopyMetadata( - targetDir = "x86_64-apple-darwin", - resDir = "darwin-x86-64", - ext = "dylib" - ) - ) - } else if (operatingSystem == OS.LINUX) { - libsToCopy.add( - CopyMetadata( - targetDir = "x86_64-unknown-linux-gnu", - resDir = "linux-x86-64", - ext = "so" - ) - ) - } - - libsToCopy.forEach { - doFirst { - copy { - with(it) { - from("${project.projectDir}/../bdk-ffi/target/${this.targetDir}/release/libbdkffi.${this.ext}") - into("${project.projectDir}/../jvm/src/main/resources/${this.resDir}/") - } - } - } - } -} - -// generate the bindings using the bdk-ffi-bindgen tool created in the bdk-ffi submodule -val generateJvmBindings by tasks.register("generateJvmBindings") { - - dependsOn(moveNativeJvmLibs) - - workingDir("${project.projectDir}/../bdk-ffi") - executable("cargo") - args("run", "--package", "bdk-ffi-bindgen", "--", "--language", "kotlin", "--out-dir", "../jvm/src/main/kotlin") - - doLast { - println("JVM bindings file successfully created") - } -} - -// we need an aggregate task which will run the 3 required tasks to build the JVM libs in order -// the task will also appear in the printout of the ./gradlew tasks task with a group and description -tasks.register("buildJvmLib") { - group = "Bitcoindevkit" - description = "Aggregate task to build JVM library" - - dependsOn( - buildJvmBinaries, - moveNativeJvmLibs, - generateJvmBindings - ) -} diff --git a/buildSrc/README.md b/plugins/README.md similarity index 100% rename from buildSrc/README.md rename to plugins/README.md diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts new file mode 100644 index 0000000..0cf3c86 --- /dev/null +++ b/plugins/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("java-gradle-plugin") + `kotlin-dsl` +} + +gradlePlugin { + plugins { + create("uniFfiJvmBindings") { + id = "org.bitcoindevkit.plugins.generate-jvm-bindings" + implementationClass = "org.bitcoindevkit.plugins.UniFfiJvmPlugin" + } + create("uniFfiAndroidBindings") { + id = "org.bitcoindevkit.plugins.generate-android-bindings" + implementationClass = "org.bitcoindevkit.plugins.UniFfiAndroidPlugin" + } + } +} diff --git a/plugins/settings.gradle.kts b/plugins/settings.gradle.kts new file mode 100644 index 0000000..a17f99e --- /dev/null +++ b/plugins/settings.gradle.kts @@ -0,0 +1,8 @@ +dependencyResolutionManagement { + repositories { + mavenCentral() + google() + } +} + +include(":plugins") diff --git a/buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt b/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt similarity index 100% rename from buildSrc/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt rename to plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt diff --git a/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt b/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt new file mode 100644 index 0000000..308b82a --- /dev/null +++ b/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt @@ -0,0 +1,178 @@ +package org.bitcoindevkit.plugins + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.Exec +import org.gradle.kotlin.dsl.environment +import org.gradle.kotlin.dsl.getValue +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.kotlin.dsl.register + +internal class UniFfiAndroidPlugin : Plugin { + override fun apply(target: Project): Unit = target.run { + val llvmArchPath = when (operatingSystem) { + OS.MAC -> "darwin-x86_64" + OS.LINUX -> "linux-x86_64" + OS.OTHER -> throw Error("Cannot build Android library from current architecture") + } + + // arm64-v8a is the most popular hardware architecture for Android + val buildAndroidAarch64Binary by tasks.register("buildAndroidAarch64Binary") { + + workingDir("${projectDir}/../bdk-ffi") + val cargoArgs: MutableList = + mutableListOf("build", "--release", "--target", "aarch64-linux-android") + + executable("cargo") + args(cargoArgs) + + // if ANDROID_NDK_ROOT is not set then set it to github actions default + if (System.getenv("ANDROID_NDK_ROOT") == null) { + environment( + Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle") + ) + } + + environment( + // add build toolchain to PATH + Pair("PATH", + "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"), + + Pair("CFLAGS", "-D__ANDROID_API__=21"), + Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android21-clang"), + Pair("CC", "aarch64-linux-android21-clang") + ) + + doLast { + println("Native library for bdk-android on aarch64 built successfully") + } + } + + // the x86_64 version of the library is mostly used by emulators + val buildAndroidX86_64Binary by tasks.register("buildAndroidX86_64Binary") { + + workingDir("${project.projectDir}/../bdk-ffi") + val cargoArgs: MutableList = + mutableListOf("build", "--release", "--target", "x86_64-linux-android") + + executable("cargo") + args(cargoArgs) + + // if ANDROID_NDK_ROOT is not set then set it to github actions default + if (System.getenv("ANDROID_NDK_ROOT") == null) { + environment( + Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle") + ) + } + + environment( + // add build toolchain to PATH + Pair("PATH", + "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"), + + Pair("CFLAGS", "-D__ANDROID_API__=21"), + Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android21-clang"), + Pair("CC", "x86_64-linux-android21-clang") + ) + + doLast { + println("Native library for bdk-android on x86_64 built successfully") + } + } + + // armeabi-v7a version of the library for older 32-bit Android hardware + val buildAndroidArmv7Binary by tasks.register("buildAndroidArmv7Binary") { + + workingDir("${project.projectDir}/../bdk-ffi") + val cargoArgs: MutableList = + mutableListOf("build", "--release", "--target", "armv7-linux-androideabi") + + executable("cargo") + args(cargoArgs) + + // if ANDROID_NDK_ROOT is not set then set it to github actions default + if (System.getenv("ANDROID_NDK_ROOT") == null) { + environment( + Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle") + ) + } + + environment( + // add build toolchain to PATH + Pair("PATH", + "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"), + + Pair("CFLAGS", "-D__ANDROID_API__=21"), + Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER", + "armv7a-linux-androideabi21-clang"), + Pair("CC", "armv7a-linux-androideabi21-clang") + ) + + doLast { + println("Native library for bdk-android on armv7 built successfully") + } + } + + // move the native libs build by cargo from bdk-ffi/target//release/ + // to their place in the bdk-android library + // the task only copies the available binaries built using the buildAndroidBinary tasks + val moveNativeAndroidLibs by tasks.register("moveNativeAndroidLibs") { + + dependsOn(buildAndroidAarch64Binary) + + into("${project.projectDir}/../android/src/main/jniLibs/") + + into("arm64-v8a") { + from("${project.projectDir}/../bdk-ffi/target/aarch64-linux-android/release/libbdkffi.so") + } + + into("x86_64") { + from("${project.projectDir}/../bdk-ffi/target/x86_64-linux-android/release/libbdkffi.so") + } + + into("armeabi-v7a") { + from("${project.projectDir}/../bdk-ffi/target/armv7-linux-androideabi/release/libbdkffi.so") + } + + doLast { + println("Native binaries for Android moved to ./android/src/main/jniLibs/") + } + } + + // generate the bindings using the bdk-ffi-bindgen tool located in the bdk-ffi submodule + val generateAndroidBindings by tasks.register("generateAndroidBindings") { + dependsOn(moveNativeAndroidLibs) + + workingDir("${project.projectDir}/../bdk-ffi") + executable("cargo") + args("run", + "--package", + "bdk-ffi-bindgen", + "--", + "--language", + "kotlin", + "--out-dir", + "../android/src/main/kotlin") + + doLast { + println("Android bindings file successfully created") + } + } + + // create an aggregate task which will run the required tasks to build the Android libs in order + // the task will also appear in the printout of the ./gradlew tasks task with group and description + tasks.register("buildAndroidLib") { + group = "Bitcoindevkit" + description = "Aggregate task to build Android library" + + dependsOn( + buildAndroidAarch64Binary, + buildAndroidX86_64Binary, + buildAndroidArmv7Binary, + moveNativeAndroidLibs, + generateAndroidBindings + ) + } + } +} diff --git a/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt b/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt new file mode 100644 index 0000000..a811eff --- /dev/null +++ b/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt @@ -0,0 +1,114 @@ +package org.bitcoindevkit.plugins + +import org.gradle.api.DefaultTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.Exec +import org.gradle.kotlin.dsl.getValue +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.kotlin.dsl.register + +internal class UniFfiJvmPlugin : Plugin { + override fun apply(target: Project): Unit = target.run { + + // register a task called buildJvmBinaries which will run something like + // cargo build --release --target aarch64-apple-darwin + val buildJvmBinaries by tasks.register("buildJvmBinaries") { + if (operatingSystem == OS.MAC) { + exec { + workingDir("${project.projectDir}/../bdk-ffi") + executable("cargo") + val cargoArgs: List = listOf("build", "--release", "--target", "x86_64-apple-darwin") + args(cargoArgs) + } + exec { + workingDir("${project.projectDir}/../bdk-ffi") + executable("cargo") + val cargoArgs: List = listOf("build", "--release", "--target", "aarch64-apple-darwin") + args(cargoArgs) + } + } else if(operatingSystem == OS.LINUX) { + exec { + workingDir("${project.projectDir}/../bdk-ffi") + executable("cargo") + val cargoArgs: List = listOf("build", "--release", "--target", "x86_64-unknown-linux-gnu") + args(cargoArgs) + } + } + } + + // move the native libs build by cargo from bdk-ffi/target/.../release/ + // to their place in the bdk-jvm library + val moveNativeJvmLibs by tasks.register("moveNativeJvmLibs") { + + // dependsOn(buildJvmBinaryX86_64MacOS, buildJvmBinaryAarch64MacOS, buildJvmBinaryLinux) + dependsOn(buildJvmBinaries) + + data class CopyMetadata(val targetDir: String, val resDir: String, val ext: String) + val libsToCopy: MutableList = mutableListOf() + + if (operatingSystem == OS.MAC) { + libsToCopy.add( + CopyMetadata( + targetDir = "aarch64-apple-darwin", + resDir = "darwin-aarch64", + ext = "dylib" + ) + ) + libsToCopy.add( + CopyMetadata( + targetDir = "x86_64-apple-darwin", + resDir = "darwin-x86-64", + ext = "dylib" + ) + ) + } else if (operatingSystem == OS.LINUX) { + libsToCopy.add( + CopyMetadata( + targetDir = "x86_64-unknown-linux-gnu", + resDir = "linux-x86-64", + ext = "so" + ) + ) + } + + libsToCopy.forEach { + doFirst { + copy { + with(it) { + from("${project.projectDir}/../bdk-ffi/target/${this.targetDir}/release/libbdkffi.${this.ext}") + into("${project.projectDir}/../jvm/src/main/resources/${this.resDir}/") + } + } + } + } + } + + // generate the bindings using the bdk-ffi-bindgen tool created in the bdk-ffi submodule + val generateJvmBindings by tasks.register("generateJvmBindings") { + + dependsOn(moveNativeJvmLibs) + + workingDir("${project.projectDir}/../bdk-ffi") + executable("cargo") + args("run", "--package", "bdk-ffi-bindgen", "--", "--language", "kotlin", "--out-dir", "../jvm/src/main/kotlin") + + doLast { + println("JVM bindings file successfully created") + } + } + + // we need an aggregate task which will run the 3 required tasks to build the JVM libs in order + // the task will also appear in the printout of the ./gradlew tasks task with a group and description + tasks.register("buildJvmLib") { + group = "Bitcoindevkit" + description = "Aggregate task to build JVM library" + + dependsOn( + buildJvmBinaries, + moveNativeJvmLibs, + generateJvmBindings + ) + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index eed67d2..cf39893 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,4 @@ rootProject.name = "bdk-kotlin" include(":jvm", ":android") +includeBuild("plugins") From 3750a7ebd69ea0e3946115eb9240bb48f430e07d Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Mon, 29 Aug 2022 20:25:05 -0400 Subject: [PATCH 237/272] Add workflow to publish bdk-jvm (#76) --- .github/workflows/publish-jvm.yaml | 102 ++++++++++++++++++ .../{test.yaml => test-android.yaml} | 9 +- .github/workflows/test-jvm.yaml | 35 ++++++ 3 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/publish-jvm.yaml rename .github/workflows/{test.yaml => test-android.yaml} (90%) create mode 100644 .github/workflows/test-jvm.yaml diff --git a/.github/workflows/publish-jvm.yaml b/.github/workflows/publish-jvm.yaml new file mode 100644 index 0000000..7d1c269 --- /dev/null +++ b/.github/workflows/publish-jvm.yaml @@ -0,0 +1,102 @@ +name: Publish bdk-jvm to Maven Central +on: [workflow_dispatch] +#on: +# release: +# types: [published] + +jobs: + build-jvm-macOS-M1-native-lib: + name: Create M1 and x86_64 JVM native binaries + runs-on: macos-12 + steps: + - name: Checkout publishing branch + uses: actions/checkout@v2 + + - name: Update bdk-ffi git submodule + run: | + git submodule set-url bdk-ffi https://github.com/bitcoindevkit/bdk-ffi.git + git submodule update --init bdk-ffi + + - name: Cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ./bdk-ffi/target + key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} + + - name: Set up JDK + uses: actions/setup-java@v2 + with: + distribution: temurin + java-version: 11 + + - name: Install aarch64 Rust target + run: rustup target add aarch64-apple-darwin + + - name: Build bdk-jvm library + run: ./gradlew :jvm:buildJvmLib + + # build aarch64 + x86_64 native libraries and upload + - name: Upload macOS native libraries for reuse in publishing job + uses: actions/upload-artifact@v3 + with: + # name: no name is required because we upload the entire directory + # the default name "artifact" will be used + path: /Users/runner/work/bdk-kotlin/bdk-kotlin/jvm/src/main/resources/ + + build-jvm-full-library: + name: Create full bdk-jvm library + needs: [build-jvm-macOS-M1-native-lib] + runs-on: ubuntu-22.04 + steps: + - name: Checkout publishing branch + uses: actions/checkout@v2 + + - name: Update bdk-ffi git submodule + run: | + git submodule set-url bdk-ffi https://github.com/bitcoindevkit/bdk-ffi.git + git submodule update --init bdk-ffi + + - name: Cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ./bdk-ffi/target + key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} + + - name: Set up JDK + uses: actions/setup-java@v2 + with: + distribution: temurin + java-version: 11 + + - name: Build bdk-jvm library + run: ./gradlew :jvm:buildJvmLib + + - name: Download macOS native libraries from previous job + uses: actions/download-artifact@v3 + id: download + with: + # download the artifact created in the prior job (named "artifact") + name: artifact + path: ./jvm/src/main/resources/ + + - name: Upload everything in jvm/src/ + uses: actions/upload-artifact@v3 + with: + name: final-src-directory + path: /home/runner/work/bdk-kotlin/bdk-kotlin/jvm/ + + - name: Publish to MavenLocal + run: ./gradlew :jvm:publishToMavenLocal --exclude-task signMavenPublication + + # Copy/paste this artifact in your local Maven repository at ~/.m2/repository/ + - name: Upload library from MavenLocal + uses: actions/upload-artifact@v3 + with: + name: mavenlocal-bdk-jvm-artifact + path: ~/.m2/repository/ diff --git a/.github/workflows/test.yaml b/.github/workflows/test-android.yaml similarity index 90% rename from .github/workflows/test.yaml rename to .github/workflows/test-android.yaml index 0e214b9..833b014 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test-android.yaml @@ -1,5 +1,4 @@ -name: Tests - +name: Test Android on: [push, pull_request] env: @@ -49,9 +48,3 @@ jobs: - name: Run Android tests run: ./gradlew :android:test --console=rich - - - name: Build bdk-jvm library - run: ./gradlew :jvm:buildJvmLib - - - name: Run JVM tests - run: ./gradlew :jvm:test --console=rich diff --git a/.github/workflows/test-jvm.yaml b/.github/workflows/test-jvm.yaml new file mode 100644 index 0000000..821cb59 --- /dev/null +++ b/.github/workflows/test-jvm.yaml @@ -0,0 +1,35 @@ +name: Test JVM +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - name: Check out PR branch + uses: actions/checkout@v2 + + - name: Update bdk-ffi git submodule + run: | + git submodule set-url bdk-ffi https://github.com/bitcoindevkit/bdk-ffi.git + git submodule update --init bdk-ffi + + - name: cache + uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + bdk-ffi/target + key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} + + - name: Set up JDK + uses: actions/setup-java@v2 + with: + distribution: temurin + java-version: 11 + + - name: Build bdk-jvm library + run: ./gradlew :jvm:buildJvmLib + + - name: Run JVM tests + run: ./gradlew :jvm:test --console=rich From 9c485a952f78c199864a0abb66b71687d8707702 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 30 Aug 2022 16:29:36 -0400 Subject: [PATCH 238/272] Bump bdk-ffi submodule to v0.8.1 tag (#78) --- bdk-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-ffi b/bdk-ffi index 138200d..8114058 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 138200db078cbd2b8a8d32ca8f4360bcd4b1eccd +Subproject commit 811405879dd4bf635bc5077124b0b78db0255a01 From db4ad1a78d93b1fe6f8e64421e08735bcbd21ae4 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 31 Aug 2022 09:41:52 -0400 Subject: [PATCH 239/272] Sign bdk-jvm artifact in CI --- .github/workflows/publish-jvm.yaml | 12 +++++------- build.gradle.kts | 13 +++++++------ jvm/build.gradle.kts | 5 ++++- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/publish-jvm.yaml b/.github/workflows/publish-jvm.yaml index 7d1c269..5d14c1f 100644 --- a/.github/workflows/publish-jvm.yaml +++ b/.github/workflows/publish-jvm.yaml @@ -85,14 +85,12 @@ jobs: name: artifact path: ./jvm/src/main/resources/ - - name: Upload everything in jvm/src/ - uses: actions/upload-artifact@v3 - with: - name: final-src-directory - path: /home/runner/work/bdk-kotlin/bdk-kotlin/jvm/ - - name: Publish to MavenLocal - run: ./gradlew :jvm:publishToMavenLocal --exclude-task signMavenPublication + env: + ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }} + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.PGP_PASSPHRASE }} + run: ./gradlew :jvm:publishToMavenLocal # Copy/paste this artifact in your local Maven repository at ~/.m2/repository/ - name: Upload library from MavenLocal diff --git a/build.gradle.kts b/build.gradle.kts index 8583631..af94fa1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,12 +16,13 @@ plugins { id("org.jetbrains.dokka") version "1.6.10" } -signing { - val signingKey: String? by project - val signingPassword: String? by project - useInMemoryPgpKeys(signingKey, signingPassword) - sign(publishing.publications) -} +// signing { +// val signingKeyId: String? by project +// val signingKey: String? by project +// val signingPassword: String? by project +// useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) +// sign(publishing.publications) +// } // does this need to be defined here? Not sure // it used to be defined in the nexusPublishing block but is not required diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index 385fbac..effe260 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -93,7 +93,10 @@ afterEvaluate { } signing { - useGpgCmd() + val signingKeyId: String? by project + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) sign(publishing.publications) } From 8b042ef47018f42f4bff8872a22a8e7f39314ef7 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 31 Aug 2022 14:54:41 -0400 Subject: [PATCH 240/272] Fix bdk-android gradle script signing block --- android/build.gradle.kts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index e4a377c..9b80a2c 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -95,7 +95,12 @@ afterEvaluate { } signing { - useGpgCmd() + // useGpgCmd() + // sign(publishing.publications) + val signingKeyId: String? by project + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) sign(publishing.publications) } From a0adc8fc747c44f707ea1f05bf90189107593e71 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 31 Aug 2022 14:56:03 -0400 Subject: [PATCH 241/272] Remove unused Dokka blocks and imports --- android/build.gradle.kts | 13 ------------- build.gradle.kts | 1 - jvm/build.gradle.kts | 13 ------------- 3 files changed, 27 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 9b80a2c..ac55ad9 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -4,9 +4,6 @@ plugins { id("maven-publish") id("signing") - // API docs - id("org.jetbrains.dokka") - // Custom plugin to generate the native libs and bindings file id("org.bitcoindevkit.plugins.generate-android-bindings") } @@ -103,13 +100,3 @@ signing { useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) sign(publishing.publications) } - -// tasks.withType().configureEach { -// dokkaSourceSets { -// named("main") { -// moduleName.set("bdk-android") -// moduleVersion.set("0.8.0-SNAPSHOT") -// includes.from("Module.md") -// } -// } -// } diff --git a/build.gradle.kts b/build.gradle.kts index af94fa1..5f77266 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,6 @@ plugins { id("signing") id("maven-publish") id("io.github.gradle-nexus.publish-plugin") version "1.1.0" - id("org.jetbrains.dokka") version "1.6.10" } // signing { diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index effe260..eafe9c9 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -7,9 +7,6 @@ plugins { id("maven-publish") id("signing") - // API docs - id("org.jetbrains.dokka") - // Custom plugin to generate the native libs and bindings file id("org.bitcoindevkit.plugins.generate-jvm-bindings") } @@ -99,13 +96,3 @@ signing { useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) sign(publishing.publications) } - -// tasks.withType().configureEach { -// dokkaSourceSets { -// named("main") { -// moduleName.set("bdk-jvm") -// moduleVersion.set("0.8.0-SNAPSHOT") -// includes.from("Module.md") -// } -// } -// } From 7e25684399c6b7c5f3c5bda49a66cf39cc830e9f Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 31 Aug 2022 15:05:11 -0400 Subject: [PATCH 242/272] Add publish to staging repository task --- .github/workflows/publish-jvm.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-jvm.yaml b/.github/workflows/publish-jvm.yaml index 5d14c1f..6d05766 100644 --- a/.github/workflows/publish-jvm.yaml +++ b/.github/workflows/publish-jvm.yaml @@ -90,7 +90,7 @@ jobs: ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.PGP_PASSPHRASE }} - run: ./gradlew :jvm:publishToMavenLocal + run: ./gradlew :jvm:publishToMavenLocal :jvm:publishToSonatype # Copy/paste this artifact in your local Maven repository at ~/.m2/repository/ - name: Upload library from MavenLocal From fc288bc92bf05020042d31dbe0a817afcf740dae Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 31 Aug 2022 15:15:48 -0400 Subject: [PATCH 243/272] Add bdk-android publishing workflow --- .github/workflows/publish-android.yaml | 61 ++++++++++++++++++++++++++ .github/workflows/publish-jvm.yaml | 2 +- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/publish-android.yaml diff --git a/.github/workflows/publish-android.yaml b/.github/workflows/publish-android.yaml new file mode 100644 index 0000000..87a04bb --- /dev/null +++ b/.github/workflows/publish-android.yaml @@ -0,0 +1,61 @@ +name: Publish bdk-android to Maven Central +on: [workflow_dispatch] + +env: + ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/21.4.7075529 + # By default the new ubuntu-20.04 images use the following ANDROID_NDK_ROOT + # ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/25.0.8775105 + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - name: Install Android NDK 21.4.7075529 + run: | + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk + SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + + - name: Check out PR branch + uses: actions/checkout@v2 + + - name: Update bdk-ffi git submodule + run: | + git submodule set-url bdk-ffi https://github.com/bitcoindevkit/bdk-ffi.git + git submodule update --init bdk-ffi + + - name: cache + uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + bdk-ffi/target + key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} + + - name: Set up JDK + uses: actions/setup-java@v2 + with: + distribution: temurin + java-version: 11 + + - name: Install rust android targets + run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi + + - name: Build bdk-android library + run: ./gradlew :android:buildAndroidLib + + - name: Publish to Maven Local and Maven Central + env: + ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }} + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.PGP_PASSPHRASE }} + run: ./gradlew :android:publishToMavenLocal :android:publishToSonatype + + # Copy/paste this artifact in your local Maven repository at ~/.m2/repository/ + - name: Upload library from MavenLocal + uses: actions/upload-artifact@v3 + with: + name: mavenlocal-bdk-android-artifact + path: ~/.m2/repository/ \ No newline at end of file diff --git a/.github/workflows/publish-jvm.yaml b/.github/workflows/publish-jvm.yaml index 6d05766..0aeb985 100644 --- a/.github/workflows/publish-jvm.yaml +++ b/.github/workflows/publish-jvm.yaml @@ -85,7 +85,7 @@ jobs: name: artifact path: ./jvm/src/main/resources/ - - name: Publish to MavenLocal + - name: Publish to Maven Local and Maven Central env: ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }} From b259d376b03f8937e7c0d0e4b7094eb789114838 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 31 Aug 2022 15:55:43 -0400 Subject: [PATCH 244/272] Pick up Nexus credentials from GitHub secrets --- .github/workflows/publish-android.yaml | 2 ++ .github/workflows/publish-jvm.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/publish-android.yaml b/.github/workflows/publish-android.yaml index 87a04bb..a9014dc 100644 --- a/.github/workflows/publish-android.yaml +++ b/.github/workflows/publish-android.yaml @@ -51,6 +51,8 @@ jobs: ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.PGP_PASSPHRASE }} + ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.NEXUS_USERNAME }} + ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.NEXUS_PASSWORD }} run: ./gradlew :android:publishToMavenLocal :android:publishToSonatype # Copy/paste this artifact in your local Maven repository at ~/.m2/repository/ diff --git a/.github/workflows/publish-jvm.yaml b/.github/workflows/publish-jvm.yaml index 0aeb985..083d4dd 100644 --- a/.github/workflows/publish-jvm.yaml +++ b/.github/workflows/publish-jvm.yaml @@ -90,6 +90,8 @@ jobs: ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.PGP_PASSPHRASE }} + ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.NEXUS_USERNAME }} + ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.NEXUS_PASSWORD }} run: ./gradlew :jvm:publishToMavenLocal :jvm:publishToSonatype # Copy/paste this artifact in your local Maven repository at ~/.m2/repository/ From 3cd252f877ff6f2b609ec0fac7f346ced4d02631 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 1 Sep 2022 10:53:35 -0400 Subject: [PATCH 245/272] Remove dokka-related markdown files --- android/Module.md | 4 ---- jvm/Module.md | 4 ---- 2 files changed, 8 deletions(-) delete mode 100644 android/Module.md delete mode 100644 jvm/Module.md diff --git a/android/Module.md b/android/Module.md deleted file mode 100644 index d21953b..0000000 --- a/android/Module.md +++ /dev/null @@ -1,4 +0,0 @@ -# Module bdk-android -The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Android. - -# Package org.bitcoindevkit diff --git a/jvm/Module.md b/jvm/Module.md deleted file mode 100644 index 6873108..0000000 --- a/jvm/Module.md +++ /dev/null @@ -1,4 +0,0 @@ -# Module bdk-jvm -The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for the JVM. - -# Package org.bitcoindevkit From 222d1594ca505e1a28154b264a333d7b34c988db Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 8 Sep 2022 09:15:26 -0400 Subject: [PATCH 246/272] Enable full bdk-jvm and bdk-android publishing workflow --- .github/workflows/publish-android.yaml | 9 +-------- .github/workflows/publish-jvm.yaml | 11 ++--------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/.github/workflows/publish-android.yaml b/.github/workflows/publish-android.yaml index a9014dc..5a6ba2d 100644 --- a/.github/workflows/publish-android.yaml +++ b/.github/workflows/publish-android.yaml @@ -53,11 +53,4 @@ jobs: ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.PGP_PASSPHRASE }} ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.NEXUS_USERNAME }} ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.NEXUS_PASSWORD }} - run: ./gradlew :android:publishToMavenLocal :android:publishToSonatype - - # Copy/paste this artifact in your local Maven repository at ~/.m2/repository/ - - name: Upload library from MavenLocal - uses: actions/upload-artifact@v3 - with: - name: mavenlocal-bdk-android-artifact - path: ~/.m2/repository/ \ No newline at end of file + run: ./gradlew :android:publishToSonatype closeAndReleaseSonatypeStagingRepository diff --git a/.github/workflows/publish-jvm.yaml b/.github/workflows/publish-jvm.yaml index 083d4dd..ac8f82a 100644 --- a/.github/workflows/publish-jvm.yaml +++ b/.github/workflows/publish-jvm.yaml @@ -85,18 +85,11 @@ jobs: name: artifact path: ./jvm/src/main/resources/ - - name: Publish to Maven Local and Maven Central + - name: Publish to Maven Central env: ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.PGP_PASSPHRASE }} ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.NEXUS_USERNAME }} ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.NEXUS_PASSWORD }} - run: ./gradlew :jvm:publishToMavenLocal :jvm:publishToSonatype - - # Copy/paste this artifact in your local Maven repository at ~/.m2/repository/ - - name: Upload library from MavenLocal - uses: actions/upload-artifact@v3 - with: - name: mavenlocal-bdk-jvm-artifact - path: ~/.m2/repository/ + run: ./gradlew :jvm:publishToSonatype closeAndReleaseSonatypeStagingRepository From b5ff0a79145791392870f2baf503806f553047fa Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 8 Sep 2022 15:58:16 -0400 Subject: [PATCH 247/272] Bump bdk-ffi submodule version to 0.9.0 --- bdk-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-ffi b/bdk-ffi index 8114058..485f4f7 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 811405879dd4bf635bc5077124b0b78db0255a01 +Subproject commit 485f4f72ce4d625e988effa67701adab0039aa64 From b8f9d199a82fd43cb3406dba56d2ce4f3ed9bcbc Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 8 Sep 2022 18:21:04 -0400 Subject: [PATCH 248/272] Simplify tests --- .../org/bitcoindevkit/AndroidLibTest.kt | 86 +++------- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 149 +++--------------- 2 files changed, 42 insertions(+), 193 deletions(-) diff --git a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt index ced2f5e..1fb2132 100644 --- a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt +++ b/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt @@ -19,22 +19,29 @@ import java.io.File @RunWith(AndroidJUnit4::class) class AndroidLibTest { - fun getTestDataDir(): String { + private fun getTestDataDir(): String { val context = ApplicationProvider.getApplicationContext() return context.getDir("bdk-test", MODE_PRIVATE).toString() } - fun cleanupTestDataDir(testDataDir: String) { + private fun cleanupTestDataDir(testDataDir: String) { File(testDataDir).deleteRecursively() } - val log: Logger = LoggerFactory.getLogger(AndroidLibTest::class.java) + class LogProgress : Progress { + private val log: Logger = LoggerFactory.getLogger(AndroidLibTest::class.java) - val descriptor = + override fun update(progress: Float, message: String?) { + log.debug("Syncing...") + } + } + + private val descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val databaseConfig = DatabaseConfig.Memory - val blockchainConfig = BlockchainConfig.Electrum( + private val databaseConfig = DatabaseConfig.Memory + + private val blockchainConfig = BlockchainConfig.Electrum( ElectrumConfig( "ssl://electrum.blockstream.info:60002", null, @@ -48,78 +55,27 @@ class AndroidLibTest { fun memoryWalletNewAddress() { val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) val address = wallet.getAddress(AddressIndex.NEW).address - assertNotNull(address) assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) } - @Test(expected = BdkException.Descriptor::class) - fun invalidDescriptorExceptionIsThrown() { - Wallet("invalid-descriptor", null, Network.TESTNET, databaseConfig) - } - @Test - fun sledWalletNewAddress() { - val testDataDir = getTestDataDir() - val databaseConfig = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) + fun memoryWalletSyncGetBalance() { val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val address = wallet.getAddress(AddressIndex.NEW).address - assertNotNull(address) - assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) - cleanupTestDataDir(testDataDir) + val blockchain = Blockchain(blockchainConfig) + wallet.sync(blockchain, LogProgress()) + val balance: Balance = wallet.getBalance() + assertTrue(balance.total > 0u) } @Test fun sqliteWalletSyncGetBalance() { - val testDataDir = getTestDataDir()+"/bdk-wallet.sqlite" + val testDataDir = getTestDataDir() + "/bdk-wallet.sqlite" val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir)) val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) val blockchain = Blockchain(blockchainConfig) wallet.sync(blockchain, LogProgress()) - val balance = wallet.getBalance() - assertTrue(balance > 0u) + val balance: Balance = wallet.getBalance() + assertTrue(balance.total > 0u) cleanupTestDataDir(testDataDir) } - - @Test - fun onlineWalletInMemory() { - val blockchain = BlockchainConfig.Electrum( - ElectrumConfig( - "ssl://electrum.blockstream.info:60002", - null, - 5u, - null, - 100u - ) - ) - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - assertNotNull(wallet) - val network = wallet.getNetwork() - assertEquals(network, Network.TESTNET) - } - - class LogProgress : Progress { - val log: Logger = LoggerFactory.getLogger(AndroidLibTest::class.java) - - override fun update(progress: Float, message: String?) { - log.debug("Syncing...") - } - } - - @Test - fun onlineWalletSyncGetBalance() { - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val blockchain = Blockchain(blockchainConfig) - wallet.sync(blockchain, LogProgress()) - val balance = wallet.getBalance() - assertTrue(balance > 0u) - } - - @Test - fun validPsbtSerde() { - val validSerializedPsbt = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA" - val psbt = PartiallySignedBitcoinTransaction(validSerializedPsbt) - val psbtSerialized = psbt.serialize() - assertEquals(psbtSerialized, validSerializedPsbt) - } - } diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt index 6a02dec..a66f8cd 100644 --- a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt +++ b/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt @@ -9,26 +9,31 @@ import java.nio.file.Files /** * Library test, which will execute on linux host. - * */ class JvmLibTest { - fun getTestDataDir(): String { + private fun getTestDataDir(): String { return Files.createTempDirectory("bdk-test").toString() - //return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString() } - fun cleanupTestDataDir(testDataDir: String) { + private fun cleanupTestDataDir(testDataDir: String) { File(testDataDir).deleteRecursively() } - val log: Logger = LoggerFactory.getLogger(JvmLibTest::class.java) + class LogProgress : Progress { + private val log: Logger = LoggerFactory.getLogger(JvmLibTest::class.java) - val descriptor = + override fun update(progress: Float, message: String?) { + log.debug("Syncing...") + } + } + + private val descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val databaseConfig = DatabaseConfig.Memory - val blockchainConfig = BlockchainConfig.Electrum( + private val databaseConfig = DatabaseConfig.Memory + + private val blockchainConfig = BlockchainConfig.Electrum( ElectrumConfig( "ssl://electrum.blockstream.info:60002", null, @@ -42,139 +47,27 @@ class JvmLibTest { fun memoryWalletNewAddress() { val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) val address = wallet.getAddress(AddressIndex.NEW).address - assertNotNull(address) assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) } - @Test(expected = BdkException.Descriptor::class) - fun invalidDescriptorExceptionIsThrown() { - Wallet("invalid-descriptor", null, Network.TESTNET, databaseConfig) - } - @Test - fun sledWalletNewAddress() { - val testDataDir = getTestDataDir() - val databaseConfig = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb")) + fun memoryWalletSyncGetBalance() { val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val address = wallet.getAddress(AddressIndex.NEW).address - assertNotNull(address) - assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address) - cleanupTestDataDir(testDataDir) + val blockchain = Blockchain(blockchainConfig) + wallet.sync(blockchain, LogProgress()) + val balance: Balance = wallet.getBalance() + assertTrue(balance.total > 0u) } @Test fun sqliteWalletSyncGetBalance() { val testDataDir = getTestDataDir() + "/bdk-wallet.sqlite" val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir)) - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val blockchain = Blockchain(blockchainConfig); - wallet.sync(blockchain, LogProgress()) - val balance = wallet.getBalance() - assertTrue(balance > 0u) - cleanupTestDataDir(testDataDir) - } - - @Test - fun onlineWalletInMemory() { - val database = DatabaseConfig.Memory - val blockchain = BlockchainConfig.Electrum( - ElectrumConfig( - "ssl://electrum.blockstream.info:60002", - null, - 5u, - null, - 100u - ) - ) - val wallet = Wallet(descriptor, null, Network.TESTNET, database) - assertNotNull(wallet) - val network = wallet.getNetwork() - assertEquals(network, Network.TESTNET) - } - - class LogProgress : Progress { - val log: Logger = LoggerFactory.getLogger(JvmLibTest::class.java) - - override fun update(progress: Float, message: String?) { - log.debug("Syncing...") - } - } - - @Test - fun onlineWalletSyncGetBalance() { val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) val blockchain = Blockchain(blockchainConfig) wallet.sync(blockchain, LogProgress()) - val balance = wallet.getBalance() - assertTrue(balance > 0u) + val balance: Balance = wallet.getBalance() + assertTrue(balance.total > 0u) + cleanupTestDataDir(testDataDir) } - - @Test - fun validPsbtSerde() { - val validSerializedPsbt = - "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA" - val psbt = PartiallySignedBitcoinTransaction(validSerializedPsbt) - val psbtSerialized = psbt.serialize() - assertEquals(psbtSerialized, validSerializedPsbt) - } - - // TODO move tx_builder tests to bdk-ffi, especially this one -// @Test -// fun walletTxBuilderBroadcast() { -// val descriptor = -// "wpkh([c1ed86ca/84'/1'/0'/0]tprv8hTkxK6QT7fCQx1wbuHuwbNh4STr2Ruz8RwEX7ymk6qnpixtbRG4T99mHxJwKTHPuKQ61heWrrpxZ8jpHj4sbisrQhDxnyx3HoQEZebtraN/*)" -// val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) -// val blockchain = Blockchain(blockchainConfig); -// wallet.sync(blockchain, LogProgress()) -// val balance = wallet.getBalance() -// if (balance > 2000u) { -// println("balance $balance") -// // send coins back to https://bitcoinfaucet.uo1.net -// val faucetAddress = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" -// val txBuilder = TxBuilder().addRecipient(faucetAddress, 1000u).feeRate(1.2f) -// val psbt = txBuilder.finish(wallet) -// wallet.sign(psbt) -// blockchain.broadcast(psbt) -// val txid = psbt.txid() -// println("https://mempool.space/testnet/tx/$txid") -// assertNotNull(txid) -// } else { -// val depositAddress = wallet.getLastUnusedAddress() -// fail("Send more testnet coins to: $depositAddress") -// } -// } - - @Test(expected = BdkException.Generic::class) - fun walletTxBuilderInvalidAddress() { - val descriptor = - "wpkh([c1ed86ca/84'/1'/0'/0]tprv8hTkxK6QT7fCQx1wbuHuwbNh4STr2Ruz8RwEX7ymk6qnpixtbRG4T99mHxJwKTHPuKQ61heWrrpxZ8jpHj4sbisrQhDxnyx3HoQEZebtraN/*)" - val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - val txBuilder = TxBuilder().addRecipient("INVALID_ADDRESS", 1000u).feeRate(1.2f) - txBuilder.finish(wallet) - } - - // Comment this test in for local testing, you will need let it fail ones to get an address - // to pre-fund the test wallet before the test will pass. - // @Test - // fun walletTxBuilderDrainWallet() { - // val descriptor = "wpkh([8da6afbe/84'/1'/0'/0]tprv8hY7jbMbe17EH1cLw2feTyNDYvjcFYauLmbneBqVnDERBrV51LrxWjCYRZwWS5keYn3ijb7iHJuEzXQk7EzgPeKRBVNBgC4oFPDxGND5S3V/*)" - // val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig) - // val blockchain = Blockchain(blockchainConfig) - // wallet.sync(blockchain, LogProgress()) - // val balance = wallet.getBalance() - // if (balance > 2000u) { - // println("balance $balance") - // // send all coins back to https://bitcoinfaucet.uo1.net - // val faucetAddress = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt" - // val txBuilder = TxBuilder().drainWallet().drainTo(faucetAddress).feeRate(1.1f) - // val psbt = txBuilder.finish(wallet) - // wallet.sign(psbt) - // val txid = blockchain.broadcast(psbt) - // println("https://mempool.space/testnet/tx/$txid") - // assertNotNull(txid) - // } else { - // val depositAddress = wallet.getAddress(AddressIndex.LAST_UNUSED).address - // fail("Send more testnet coins to: $depositAddress") - // } - // } } From a3cbc4477fb56ab17b83186d8ef1e8564e66b273 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 9 Sep 2022 12:41:41 -0400 Subject: [PATCH 249/272] Bump SNAPSHOT version to 0.10.0-SNAPSHOT --- android/build.gradle.kts | 2 +- jvm/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index ac55ad9..eabbd8d 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -52,7 +52,7 @@ afterEvaluate { create("maven") { groupId = "org.bitcoindevkit" artifactId = "bdk-android" - version = "0.9.0-SNAPSHOT" + version = "0.10.0-SNAPSHOT" from(components["release"]) pom { name.set("bdk-android") diff --git a/jvm/build.gradle.kts b/jvm/build.gradle.kts index eafe9c9..9d857ae 100644 --- a/jvm/build.gradle.kts +++ b/jvm/build.gradle.kts @@ -48,7 +48,7 @@ afterEvaluate { groupId = "org.bitcoindevkit" artifactId = "bdk-jvm" - version = "0.9.0-SNAPSHOT" + version = "0.10.0-SNAPSHOT" from(components["java"]) From db64f372f241e285d71ef1fc61d6d252fcc0a949 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 13 Sep 2022 13:33:36 -0400 Subject: [PATCH 250/272] Split libraries into independent Gradle projects --- .github/workflows/test-android.yaml | 8 +- .github/workflows/test-jvm.yaml | 8 +- README.md | 45 ++-- bdk-android/build.gradle.kts | 26 ++ bdk-android/gradle.properties | 4 + .../gradle}/wrapper/gradle-wrapper.jar | Bin .../gradle}/wrapper/gradle-wrapper.properties | 0 gradlew => bdk-android/gradlew | 0 gradlew.bat => bdk-android/gradlew.bat | 0 {android => bdk-android/lib}/build.gradle.kts | 9 +- .../lib}/proguard-rules.pro | 0 .../lib}/src/androidTest/assets/logback.xml | 0 .../org/bitcoindevkit/AndroidLibTest.kt | 0 .../lib}/src/main/AndroidManifest.xml | 0 bdk-android/plugins/README.md | 17 ++ .../plugins}/build.gradle.kts | 4 - .../plugins}/settings.gradle.kts | 2 +- .../kotlin/org/bitcoindevkit/plugins/Enums.kt | 0 .../plugins/UniFfiAndroidPlugin.kt | 24 +- bdk-android/settings.gradle.kts | 4 + bdk-jvm/build.gradle.kts | 17 ++ bdk-jvm/gradle.properties | 3 + bdk-jvm/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + bdk-jvm/gradlew | 234 ++++++++++++++++++ bdk-jvm/gradlew.bat | 89 +++++++ {jvm => bdk-jvm/lib}/build.gradle.kts | 6 +- .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 0 bdk-jvm/plugins/README.md | 16 ++ bdk-jvm/plugins/build.gradle.kts | 13 + bdk-jvm/plugins/settings.gradle.kts | 8 + .../kotlin/org/bitcoindevkit/plugins/Enums.kt | 14 ++ .../bitcoindevkit/plugins/UniFfiJvmPlugin.kt | 23 +- bdk-jvm/settings.gradle.kts | 4 + build.gradle.kts | 50 ---- gradle.properties | 21 -- plugins/README.md | 22 -- settings.gradle.kts | 4 - 38 files changed, 524 insertions(+), 156 deletions(-) create mode 100644 bdk-android/build.gradle.kts create mode 100644 bdk-android/gradle.properties rename {gradle => bdk-android/gradle}/wrapper/gradle-wrapper.jar (100%) rename {gradle => bdk-android/gradle}/wrapper/gradle-wrapper.properties (100%) rename gradlew => bdk-android/gradlew (100%) rename gradlew.bat => bdk-android/gradlew.bat (100%) rename {android => bdk-android/lib}/build.gradle.kts (97%) rename {android => bdk-android/lib}/proguard-rules.pro (100%) rename {android => bdk-android/lib}/src/androidTest/assets/logback.xml (100%) rename {android => bdk-android/lib}/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt (100%) rename {android => bdk-android/lib}/src/main/AndroidManifest.xml (100%) create mode 100644 bdk-android/plugins/README.md rename {plugins => bdk-android/plugins}/build.gradle.kts (60%) rename {plugins => bdk-android/plugins}/settings.gradle.kts (81%) rename {plugins => bdk-android/plugins}/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt (100%) rename {plugins => bdk-android/plugins}/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt (89%) create mode 100644 bdk-android/settings.gradle.kts create mode 100644 bdk-jvm/build.gradle.kts create mode 100644 bdk-jvm/gradle.properties create mode 100644 bdk-jvm/gradle/wrapper/gradle-wrapper.jar create mode 100644 bdk-jvm/gradle/wrapper/gradle-wrapper.properties create mode 100755 bdk-jvm/gradlew create mode 100644 bdk-jvm/gradlew.bat rename {jvm => bdk-jvm/lib}/build.gradle.kts (97%) rename {jvm => bdk-jvm/lib}/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt (100%) create mode 100644 bdk-jvm/plugins/README.md create mode 100644 bdk-jvm/plugins/build.gradle.kts create mode 100644 bdk-jvm/plugins/settings.gradle.kts create mode 100644 bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt rename {plugins => bdk-jvm/plugins}/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt (83%) create mode 100644 bdk-jvm/settings.gradle.kts delete mode 100644 build.gradle.kts delete mode 100644 gradle.properties delete mode 100644 plugins/README.md delete mode 100644 settings.gradle.kts diff --git a/.github/workflows/test-android.yaml b/.github/workflows/test-android.yaml index 833b014..2b3f223 100644 --- a/.github/workflows/test-android.yaml +++ b/.github/workflows/test-android.yaml @@ -44,7 +44,11 @@ jobs: run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi - name: Build bdk-android library - run: ./gradlew :android:buildAndroidLib + run: | + cd bdk-android + ./gradlew buildAndroidLib - name: Run Android tests - run: ./gradlew :android:test --console=rich + run: | + cd bdk-android + ./gradlew test --console=rich diff --git a/.github/workflows/test-jvm.yaml b/.github/workflows/test-jvm.yaml index 821cb59..c9f5a4a 100644 --- a/.github/workflows/test-jvm.yaml +++ b/.github/workflows/test-jvm.yaml @@ -29,7 +29,11 @@ jobs: java-version: 11 - name: Build bdk-jvm library - run: ./gradlew :jvm:buildJvmLib + run: | + cd bdk-jvm + ./gradlew buildJvmLib - name: Run JVM tests - run: ./gradlew :jvm:test --console=rich + run: | + cd bdk-jvm + ./gradlew test --console=rich diff --git a/README.md b/README.md index 69b6c59..39e513c 100644 --- a/README.md +++ b/README.md @@ -79,10 +79,12 @@ rustup target add x86_64-apple-darwin aarch64-apple-darwin 7. Build kotlin bindings ```sh # build JVM library - ./gradlew :jvm:buildJvmLib + cd bdk-jvm + ./gradlew buildJvmLib # build Android library - ./gradlew :android:buildAndroidLib + cd bdk-android + ./gradlew buildAndroidLib ``` 8. Start android emulator (must be x86_64) and run tests ```sh @@ -92,38 +94,25 @@ rustup target add x86_64-apple-darwin aarch64-apple-darwin ## How to publish ### Publish to your local maven repo +```shell +# bdk-jvm +cd bdk-jvm +./gradlew publishToMavenLocal --exclude-task signMavenPublication -1. Set your `~/.gradle/gradle.properties` signing key values +# bdk-android +cd bdk-android +./gradlew publishToMavenLocal --exclude-task signMavenPublication +``` + +Note that the commands assume you don't need the local libraries to be signed. If you do wish to sign them, simply set your `~/.gradle/gradle.properties` signing key values like so: ```properties signing.gnupg.keyName= signing.gnupg.passphrase= ``` -2. Publish + +and use the `publishToMavenLocal` task without excluding the signing task: ```shell -./gradlew :jvm:publishToMavenLocal -./gradlew :android:publishToMavenLocal -``` - -Note that if you do not have gpg keys set up to sign the publication, the task will fail. If you wish to publish to your local Maven repository for local testing without signing the release, you can do so by excluding the `signMavenPublication` subtask like so: -```shell -./gradlew :jvm:publishToMavenLocal --exclude-task signMavenPublication -./gradlew :android:publishToMavenLocal --exclude-task signMavenPublication -``` - -### Publish to maven central with [Gradle Nexus Publish Plugin] (project maintainers only) - -1. Set your `~/.gradle/gradle.properties` signing key values and SONATYPE login -```properties -signing.gnupg.keyName= -signing.gnupg.passphrase= - -ossrhUserName= -ossrhPassword= -``` -2. Publish -```shell -./gradlew :jvm:publishToSonatype closeAndReleaseSonatypeStagingRepository -./gradlew :android:publishToSonatype closeAndReleaseSonatypeStagingRepository +./gradlew publishToMavenLocal ``` [Kotlin]: https://kotlinlang.org/ diff --git a/bdk-android/build.gradle.kts b/bdk-android/build.gradle.kts new file mode 100644 index 0000000..0894966 --- /dev/null +++ b/bdk-android/build.gradle.kts @@ -0,0 +1,26 @@ +buildscript { + repositories { + google() + } + dependencies { + classpath("com.android.tools.build:gradle:7.1.2") + } +} + +plugins { + id("io.github.gradle-nexus.publish-plugin") version "1.1.0" +} + +nexusPublishing { + repositories { + create("sonatype") { + nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + + val ossrhUsername: String? by project + val ossrhPassword: String? by project + username.set(ossrhUsername) + password.set(ossrhPassword) + } + } +} diff --git a/bdk-android/gradle.properties b/bdk-android/gradle.properties new file mode 100644 index 0000000..58f55b9 --- /dev/null +++ b/bdk-android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536m +android.useAndroidX=true +android.enableJetifier=true +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/bdk-android/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/wrapper/gradle-wrapper.jar rename to bdk-android/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/bdk-android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from gradle/wrapper/gradle-wrapper.properties rename to bdk-android/gradle/wrapper/gradle-wrapper.properties diff --git a/gradlew b/bdk-android/gradlew similarity index 100% rename from gradlew rename to bdk-android/gradlew diff --git a/gradlew.bat b/bdk-android/gradlew.bat similarity index 100% rename from gradlew.bat rename to bdk-android/gradlew.bat diff --git a/android/build.gradle.kts b/bdk-android/lib/build.gradle.kts similarity index 97% rename from android/build.gradle.kts rename to bdk-android/lib/build.gradle.kts index eabbd8d..12cb804 100644 --- a/android/build.gradle.kts +++ b/bdk-android/lib/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("com.android.library") - id("kotlin-android") + id("org.jetbrains.kotlin.android") version "1.6.10" id("maven-publish") id("signing") @@ -8,6 +8,11 @@ plugins { id("org.bitcoindevkit.plugins.generate-android-bindings") } +repositories { + mavenCentral() + google() +} + android { compileSdk = 31 @@ -92,8 +97,6 @@ afterEvaluate { } signing { - // useGpgCmd() - // sign(publishing.publications) val signingKeyId: String? by project val signingKey: String? by project val signingPassword: String? by project diff --git a/android/proguard-rules.pro b/bdk-android/lib/proguard-rules.pro similarity index 100% rename from android/proguard-rules.pro rename to bdk-android/lib/proguard-rules.pro diff --git a/android/src/androidTest/assets/logback.xml b/bdk-android/lib/src/androidTest/assets/logback.xml similarity index 100% rename from android/src/androidTest/assets/logback.xml rename to bdk-android/lib/src/androidTest/assets/logback.xml diff --git a/android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt similarity index 100% rename from android/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt rename to bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt diff --git a/android/src/main/AndroidManifest.xml b/bdk-android/lib/src/main/AndroidManifest.xml similarity index 100% rename from android/src/main/AndroidManifest.xml rename to bdk-android/lib/src/main/AndroidManifest.xml diff --git a/bdk-android/plugins/README.md b/bdk-android/plugins/README.md new file mode 100644 index 0000000..40025f9 --- /dev/null +++ b/bdk-android/plugins/README.md @@ -0,0 +1,17 @@ +# Readme +The purpose of this directory is to host the Gradle plugin that adds tasks for building the native binaries required by `bdk-android`, and building the language bindings files. + +The plugin is applied to the `build.gradle.kts` file in `bdk-android` through the `plugins` block: +```kotlin +// bdk-android +plugins { + id("org.bitcoindevkit.plugins.generate-android-bindings") +} +``` + +It adds a series of tasks which are brought together into an aggregate task called `buildAndroidLib`. + +This aggregate task: +1. Builds the native libraries using `bdk-ffi` +2. Places them in the correct resource directories +3. Builds the bindings file diff --git a/plugins/build.gradle.kts b/bdk-android/plugins/build.gradle.kts similarity index 60% rename from plugins/build.gradle.kts rename to bdk-android/plugins/build.gradle.kts index 0cf3c86..37737bc 100644 --- a/plugins/build.gradle.kts +++ b/bdk-android/plugins/build.gradle.kts @@ -5,10 +5,6 @@ plugins { gradlePlugin { plugins { - create("uniFfiJvmBindings") { - id = "org.bitcoindevkit.plugins.generate-jvm-bindings" - implementationClass = "org.bitcoindevkit.plugins.UniFfiJvmPlugin" - } create("uniFfiAndroidBindings") { id = "org.bitcoindevkit.plugins.generate-android-bindings" implementationClass = "org.bitcoindevkit.plugins.UniFfiAndroidPlugin" diff --git a/plugins/settings.gradle.kts b/bdk-android/plugins/settings.gradle.kts similarity index 81% rename from plugins/settings.gradle.kts rename to bdk-android/plugins/settings.gradle.kts index a17f99e..ca62a7a 100644 --- a/plugins/settings.gradle.kts +++ b/bdk-android/plugins/settings.gradle.kts @@ -5,4 +5,4 @@ dependencyResolutionManagement { } } -include(":plugins") +// include(":plugins") diff --git a/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt b/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt similarity index 100% rename from plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt rename to bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt diff --git a/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt b/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt similarity index 89% rename from plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt rename to bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt index 308b82a..af07265 100644 --- a/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt +++ b/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt @@ -20,7 +20,7 @@ internal class UniFfiAndroidPlugin : Plugin { // arm64-v8a is the most popular hardware architecture for Android val buildAndroidAarch64Binary by tasks.register("buildAndroidAarch64Binary") { - workingDir("${projectDir}/../bdk-ffi") + workingDir("${projectDir}/../../bdk-ffi") val cargoArgs: MutableList = mutableListOf("build", "--release", "--target", "aarch64-linux-android") @@ -52,7 +52,7 @@ internal class UniFfiAndroidPlugin : Plugin { // the x86_64 version of the library is mostly used by emulators val buildAndroidX86_64Binary by tasks.register("buildAndroidX86_64Binary") { - workingDir("${project.projectDir}/../bdk-ffi") + workingDir("${project.projectDir}/../../bdk-ffi") val cargoArgs: MutableList = mutableListOf("build", "--release", "--target", "x86_64-linux-android") @@ -84,7 +84,7 @@ internal class UniFfiAndroidPlugin : Plugin { // armeabi-v7a version of the library for older 32-bit Android hardware val buildAndroidArmv7Binary by tasks.register("buildAndroidArmv7Binary") { - workingDir("${project.projectDir}/../bdk-ffi") + workingDir("${project.projectDir}/../../bdk-ffi") val cargoArgs: MutableList = mutableListOf("build", "--release", "--target", "armv7-linux-androideabi") @@ -121,22 +121,22 @@ internal class UniFfiAndroidPlugin : Plugin { dependsOn(buildAndroidAarch64Binary) - into("${project.projectDir}/../android/src/main/jniLibs/") + into("${project.projectDir}/../lib/src/main/jniLibs/") into("arm64-v8a") { - from("${project.projectDir}/../bdk-ffi/target/aarch64-linux-android/release/libbdkffi.so") + from("${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release/libbdkffi.so") } into("x86_64") { - from("${project.projectDir}/../bdk-ffi/target/x86_64-linux-android/release/libbdkffi.so") + from("${project.projectDir}/../../bdk-ffi/target/x86_64-linux-android/release/libbdkffi.so") } into("armeabi-v7a") { - from("${project.projectDir}/../bdk-ffi/target/armv7-linux-androideabi/release/libbdkffi.so") + from("${project.projectDir}/../../bdk-ffi/target/armv7-linux-androideabi/release/libbdkffi.so") } doLast { - println("Native binaries for Android moved to ./android/src/main/jniLibs/") + println("Native binaries for Android moved to ./lib/src/main/jniLibs/") } } @@ -144,16 +144,18 @@ internal class UniFfiAndroidPlugin : Plugin { val generateAndroidBindings by tasks.register("generateAndroidBindings") { dependsOn(moveNativeAndroidLibs) - workingDir("${project.projectDir}/../bdk-ffi") + workingDir("${project.projectDir}/../../bdk-ffi") executable("cargo") - args("run", + args( + "run", "--package", "bdk-ffi-bindgen", "--", "--language", "kotlin", "--out-dir", - "../android/src/main/kotlin") + "../bdk-android/lib/src/main/kotlin" + ) doLast { println("Android bindings file successfully created") diff --git a/bdk-android/settings.gradle.kts b/bdk-android/settings.gradle.kts new file mode 100644 index 0000000..ef7d1eb --- /dev/null +++ b/bdk-android/settings.gradle.kts @@ -0,0 +1,4 @@ +rootProject.name = "bdk-android" + +include(":lib") +includeBuild("plugins") diff --git a/bdk-jvm/build.gradle.kts b/bdk-jvm/build.gradle.kts new file mode 100644 index 0000000..3e3984f --- /dev/null +++ b/bdk-jvm/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("io.github.gradle-nexus.publish-plugin") version "1.1.0" +} + +nexusPublishing { + repositories { + create("sonatype") { + nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + + val ossrhUsername: String? by project + val ossrhPassword: String? by project + username.set(ossrhUsername) + password.set(ossrhPassword) + } + } +} diff --git a/bdk-jvm/gradle.properties b/bdk-jvm/gradle.properties new file mode 100644 index 0000000..e12e896 --- /dev/null +++ b/bdk-jvm/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536m +android.enableJetifier=true +kotlin.code.style=official diff --git a/bdk-jvm/gradle/wrapper/gradle-wrapper.jar b/bdk-jvm/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/bdk-jvm/gradle/wrapper/gradle-wrapper.properties b/bdk-jvm/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d2880ba --- /dev/null +++ b/bdk-jvm/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/bdk-jvm/gradlew b/bdk-jvm/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/bdk-jvm/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/bdk-jvm/gradlew.bat b/bdk-jvm/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/bdk-jvm/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/jvm/build.gradle.kts b/bdk-jvm/lib/build.gradle.kts similarity index 97% rename from jvm/build.gradle.kts rename to bdk-jvm/lib/build.gradle.kts index 9d857ae..49625de 100644 --- a/jvm/build.gradle.kts +++ b/bdk-jvm/lib/build.gradle.kts @@ -2,7 +2,7 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat.* import org.gradle.api.tasks.testing.logging.TestLogEvent.* plugins { - id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.kotlin.jvm") version "1.6.10" id("java-library") id("maven-publish") id("signing") @@ -11,6 +11,10 @@ plugins { id("org.bitcoindevkit.plugins.generate-jvm-bindings") } +repositories { + mavenCentral() +} + java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 diff --git a/jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt similarity index 100% rename from jvm/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt rename to bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt diff --git a/bdk-jvm/plugins/README.md b/bdk-jvm/plugins/README.md new file mode 100644 index 0000000..c008327 --- /dev/null +++ b/bdk-jvm/plugins/README.md @@ -0,0 +1,16 @@ +# Readme +The purpose of this directory is to host the Gradle plugin that add tasks for building the native binaries required by bdk-jvm, and building the language bindings files. + +The plugin is applied to the `build.gradle.kts` file through the `plugins` block: +```kotlin +plugins { + id("org.bitcoindevkit.plugin.generate-jvm-bindings") +} +``` + +The plugin adds a series of tasks which are brought together into an aggregate task called `buildJvmLib` for `bdk-jvm`. + +This aggregate task: +1. Builds the native library(ies) using `bdk-ffi` +2. Places it in the correct resource directory +3. Builds the bindings file diff --git a/bdk-jvm/plugins/build.gradle.kts b/bdk-jvm/plugins/build.gradle.kts new file mode 100644 index 0000000..91dfb28 --- /dev/null +++ b/bdk-jvm/plugins/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("java-gradle-plugin") + `kotlin-dsl` +} + +gradlePlugin { + plugins { + create("uniFfiJvmBindings") { + id = "org.bitcoindevkit.plugins.generate-jvm-bindings" + implementationClass = "org.bitcoindevkit.plugins.UniFfiJvmPlugin" + } + } +} diff --git a/bdk-jvm/plugins/settings.gradle.kts b/bdk-jvm/plugins/settings.gradle.kts new file mode 100644 index 0000000..ca62a7a --- /dev/null +++ b/bdk-jvm/plugins/settings.gradle.kts @@ -0,0 +1,8 @@ +dependencyResolutionManagement { + repositories { + mavenCentral() + google() + } +} + +// include(":plugins") diff --git a/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt b/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt new file mode 100644 index 0000000..8f586b6 --- /dev/null +++ b/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt @@ -0,0 +1,14 @@ +package org.bitcoindevkit.plugins + + +val operatingSystem: OS = when { + System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC + System.getProperty("os.name").contains("linux", ignoreCase = true) -> OS.LINUX + else -> OS.OTHER +} + +enum class OS { + MAC, + LINUX, + OTHER, +} diff --git a/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt b/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt similarity index 83% rename from plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt rename to bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt index a811eff..aada4fb 100644 --- a/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt +++ b/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt @@ -16,20 +16,20 @@ internal class UniFfiJvmPlugin : Plugin { val buildJvmBinaries by tasks.register("buildJvmBinaries") { if (operatingSystem == OS.MAC) { exec { - workingDir("${project.projectDir}/../bdk-ffi") + workingDir("${project.projectDir}/../../bdk-ffi") executable("cargo") val cargoArgs: List = listOf("build", "--release", "--target", "x86_64-apple-darwin") args(cargoArgs) } exec { - workingDir("${project.projectDir}/../bdk-ffi") + workingDir("${project.projectDir}/../../bdk-ffi") executable("cargo") val cargoArgs: List = listOf("build", "--release", "--target", "aarch64-apple-darwin") args(cargoArgs) } } else if(operatingSystem == OS.LINUX) { exec { - workingDir("${project.projectDir}/../bdk-ffi") + workingDir("${project.projectDir}/../../bdk-ffi") executable("cargo") val cargoArgs: List = listOf("build", "--release", "--target", "x86_64-unknown-linux-gnu") args(cargoArgs) @@ -76,8 +76,8 @@ internal class UniFfiJvmPlugin : Plugin { doFirst { copy { with(it) { - from("${project.projectDir}/../bdk-ffi/target/${this.targetDir}/release/libbdkffi.${this.ext}") - into("${project.projectDir}/../jvm/src/main/resources/${this.resDir}/") + from("${project.projectDir}/../../bdk-ffi/target/${this.targetDir}/release/libbdkffi.${this.ext}") + into("${project.projectDir}/../../bdk-jvm/lib/src/main/resources/${this.resDir}/") } } } @@ -89,9 +89,18 @@ internal class UniFfiJvmPlugin : Plugin { dependsOn(moveNativeJvmLibs) - workingDir("${project.projectDir}/../bdk-ffi") + workingDir("${project.projectDir}/../../bdk-ffi") executable("cargo") - args("run", "--package", "bdk-ffi-bindgen", "--", "--language", "kotlin", "--out-dir", "../jvm/src/main/kotlin") + args( + "run", + "--package", + "bdk-ffi-bindgen", + "--", + "--language", + "kotlin", + "--out-dir", + "../bdk-jvm/lib/src/main/kotlin" + ) doLast { println("JVM bindings file successfully created") diff --git a/bdk-jvm/settings.gradle.kts b/bdk-jvm/settings.gradle.kts new file mode 100644 index 0000000..d4238f3 --- /dev/null +++ b/bdk-jvm/settings.gradle.kts @@ -0,0 +1,4 @@ +rootProject.name = "bdk-jvm" + +include(":lib") +includeBuild("plugins") diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 5f77266..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,50 +0,0 @@ -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath("com.android.tools.build:gradle:7.1.2") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") - } -} - -plugins { - id("signing") - id("maven-publish") - id("io.github.gradle-nexus.publish-plugin") version "1.1.0" -} - -// signing { -// val signingKeyId: String? by project -// val signingKey: String? by project -// val signingPassword: String? by project -// useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) -// sign(publishing.publications) -// } - -// does this need to be defined here? Not sure -// it used to be defined in the nexusPublishing block but is not required -// I think the group ID is defined in the specific publishing blocks in the respective build.gradle.kts -group = "org.bitcoindevkit" - -nexusPublishing { - repositories { - create("sonatype") { - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) - - val ossrhUsername: String? by project - val ossrhPassword: String? by project - username.set(ossrhUsername) - password.set(ossrhPassword) - } - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 23339e0..0000000 --- a/gradle.properties +++ /dev/null @@ -1,21 +0,0 @@ -# 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/plugins/README.md b/plugins/README.md deleted file mode 100644 index 15b2026..0000000 --- a/plugins/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Readme -The purpose of this directory is to host the Gradle plugins that add tasks for building the native binaries required by bdk-jvm and bdk-android, and building the language bindings files. - -The plugins are applied to the specific `build.gradle.kts` files in `bdk-jvm` and `bdk-android` through the `plugins` block: -```kotlin -// bdk-jvm -plugins { - id("org.bitcoindevkit.plugin.generate-jvm-bindings") -} - -// bdk-android -plugins { - id("org.bitcoindevkit.plugins.generate-android-bindings") -} -``` - -They add a series of tasks which are brought together into an aggregate task called `buildJvmLib` for `bdk-jvm` and `buildAndroidLib` for `bdk-android`. - -This aggregate task: -1. Builds the native library(ies) using `bdk-ffi` -2. Places it in the correct resource directory -3. Builds the bindings file diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index cf39893..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,4 +0,0 @@ -rootProject.name = "bdk-kotlin" - -include(":jvm", ":android") -includeBuild("plugins") From d603932e23f6b4ec7c718cb31e5178df445cbb1c Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 14 Sep 2022 14:07:03 -0400 Subject: [PATCH 251/272] Update publishing CI workflow --- .github/workflows/publish-android.yaml | 9 +++++++-- .github/workflows/publish-jvm.yaml | 17 ++++++++++++----- bdk-jvm/plugins/README.md | 2 +- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish-android.yaml b/.github/workflows/publish-android.yaml index 5a6ba2d..26ab381 100644 --- a/.github/workflows/publish-android.yaml +++ b/.github/workflows/publish-android.yaml @@ -44,7 +44,9 @@ jobs: run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi - name: Build bdk-android library - run: ./gradlew :android:buildAndroidLib + run: | + cd bdk-android + ./gradlew buildAndroidLib - name: Publish to Maven Local and Maven Central env: @@ -53,4 +55,7 @@ jobs: ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.PGP_PASSPHRASE }} ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.NEXUS_USERNAME }} ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.NEXUS_PASSWORD }} - run: ./gradlew :android:publishToSonatype closeAndReleaseSonatypeStagingRepository + run: | + cd bdk-android + ./gradlew publishToSonatype +# ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository diff --git a/.github/workflows/publish-jvm.yaml b/.github/workflows/publish-jvm.yaml index ac8f82a..56c14f1 100644 --- a/.github/workflows/publish-jvm.yaml +++ b/.github/workflows/publish-jvm.yaml @@ -36,7 +36,9 @@ jobs: run: rustup target add aarch64-apple-darwin - name: Build bdk-jvm library - run: ./gradlew :jvm:buildJvmLib + run: | + cd bdk-jvm + ./gradlew buildJvmLib # build aarch64 + x86_64 native libraries and upload - name: Upload macOS native libraries for reuse in publishing job @@ -44,7 +46,7 @@ jobs: with: # name: no name is required because we upload the entire directory # the default name "artifact" will be used - path: /Users/runner/work/bdk-kotlin/bdk-kotlin/jvm/src/main/resources/ + path: /Users/runner/work/bdk-kotlin/bdk-kotlin/bdk-jvm/lib/src/main/resources/ build-jvm-full-library: name: Create full bdk-jvm library @@ -75,7 +77,9 @@ jobs: java-version: 11 - name: Build bdk-jvm library - run: ./gradlew :jvm:buildJvmLib + run: | + cd bdk-jvm + ./gradlew buildJvmLib - name: Download macOS native libraries from previous job uses: actions/download-artifact@v3 @@ -83,7 +87,7 @@ jobs: with: # download the artifact created in the prior job (named "artifact") name: artifact - path: ./jvm/src/main/resources/ + path: ./bdk-jvm/lib/src/main/resources/ - name: Publish to Maven Central env: @@ -92,4 +96,7 @@ jobs: ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.PGP_PASSPHRASE }} ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.NEXUS_USERNAME }} ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.NEXUS_PASSWORD }} - run: ./gradlew :jvm:publishToSonatype closeAndReleaseSonatypeStagingRepository + run: | + cd bdk-jvm + ./gradlew publishToSonatype +# ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository diff --git a/bdk-jvm/plugins/README.md b/bdk-jvm/plugins/README.md index c008327..b80e5d2 100644 --- a/bdk-jvm/plugins/README.md +++ b/bdk-jvm/plugins/README.md @@ -1,5 +1,5 @@ # Readme -The purpose of this directory is to host the Gradle plugin that add tasks for building the native binaries required by bdk-jvm, and building the language bindings files. +The purpose of this directory is to host the Gradle plugin that adds tasks for building the native binaries required by bdk-jvm, and building the language bindings files. The plugin is applied to the `build.gradle.kts` file through the `plugins` block: ```kotlin From 4d973e7ab68a6d957f0633e801ea42d951d5ee62 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 8 Sep 2022 09:33:36 -0400 Subject: [PATCH 252/272] Add temporary API docs --- api-docs/Module.md | 6 + api-docs/build.gradle.kts | 37 +++ api-docs/deploy.sh | 8 + api-docs/gradle.properties | 1 + api-docs/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + api-docs/gradlew | 234 ++++++++++++++++++ api-docs/gradlew.bat | 89 +++++++ api-docs/settings.gradle.kts | 1 + 9 files changed, 381 insertions(+) create mode 100644 api-docs/Module.md create mode 100644 api-docs/build.gradle.kts create mode 100644 api-docs/deploy.sh create mode 100644 api-docs/gradle.properties create mode 100644 api-docs/gradle/wrapper/gradle-wrapper.jar create mode 100644 api-docs/gradle/wrapper/gradle-wrapper.properties create mode 100755 api-docs/gradlew create mode 100644 api-docs/gradlew.bat create mode 100644 api-docs/settings.gradle.kts diff --git a/api-docs/Module.md b/api-docs/Module.md new file mode 100644 index 0000000..b7efdc2 --- /dev/null +++ b/api-docs/Module.md @@ -0,0 +1,6 @@ +# Module bdk-android +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Android and the JVM. + +These docs are valid for both `bdk-jvm` and `bdk-android` libraries. + +# Package org.bitcoindevkit diff --git a/api-docs/build.gradle.kts b/api-docs/build.gradle.kts new file mode 100644 index 0000000..a31cb48 --- /dev/null +++ b/api-docs/build.gradle.kts @@ -0,0 +1,37 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.7.0" + + // API docs + id("org.jetbrains.dokka") version "1.7.10" +} + +// group = "org.example" +// version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} + +tasks.withType { + kotlinOptions.jvmTarget = "1.8" +} + +tasks.withType().configureEach { + dokkaSourceSets { + named("main") { + moduleName.set("bdk-android") + moduleVersion.set("0.8.2") + includes.from("Module.md") + } + } +} diff --git a/api-docs/deploy.sh b/api-docs/deploy.sh new file mode 100644 index 0000000..dd6e442 --- /dev/null +++ b/api-docs/deploy.sh @@ -0,0 +1,8 @@ +./gradlew dokkaHtml +cd build/dokka/html +git init . +git add . +git switch --create gh-pages +git commit -m "Deploy" +git remote add origin git@github.com:bitcoindevkit/bdk-kotlin.git +git push --set-upstream origin gh-pages --force diff --git a/api-docs/gradle.properties b/api-docs/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/api-docs/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/api-docs/gradle/wrapper/gradle-wrapper.jar b/api-docs/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/api-docs/gradle/wrapper/gradle-wrapper.properties b/api-docs/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..60c76b3 --- /dev/null +++ b/api-docs/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/api-docs/gradlew b/api-docs/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/api-docs/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/api-docs/gradlew.bat b/api-docs/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/api-docs/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/api-docs/settings.gradle.kts b/api-docs/settings.gradle.kts new file mode 100644 index 0000000..eefa331 --- /dev/null +++ b/api-docs/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "bdk-kotlin-docs" From 4259f260a9e536dd29673b0f69a2952cffbbfe6b Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 8 Sep 2022 10:41:51 -0400 Subject: [PATCH 253/272] Add samples for BlockchainConfig --- api-docs/build.gradle.kts | 4 +--- .../src/main/kotlin/org/bitcoindevkit/Samples.kt | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt diff --git a/api-docs/build.gradle.kts b/api-docs/build.gradle.kts index a31cb48..d9b586d 100644 --- a/api-docs/build.gradle.kts +++ b/api-docs/build.gradle.kts @@ -7,9 +7,6 @@ plugins { id("org.jetbrains.dokka") version "1.7.10" } -// group = "org.example" -// version = "1.0-SNAPSHOT" - repositories { mavenCentral() } @@ -32,6 +29,7 @@ tasks.withType().configureEach { moduleName.set("bdk-android") moduleVersion.set("0.8.2") includes.from("Module.md") + samples.from("src/main/kotlin/org/bitcoindevkit/Samples.kt") } } } diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt new file mode 100644 index 0000000..4e355fd --- /dev/null +++ b/api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt @@ -0,0 +1,14 @@ +package org.bitcoindevkit + +class Samples { + val blockchainConfig: BlockchainConfig = BlockchainConfig.Electrum( + ElectrumConfig( + url = "ssl://electrum.blockstream.info:60002", + socks5 = null, + retry = 5u, + timeout = null, + stopGap = 10u + ) + ) +} + From af89ebaeead08d4bc2326ac7b4ae147a15724694 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 13 Sep 2022 12:02:58 -0400 Subject: [PATCH 254/272] Update API docs to v0.9.0 --- .gitignore | 3 +- api-docs/Module.md | 2 +- api-docs/build.gradle.kts | 4 +- .../main/kotlin/org/bitcoindevkit/Samples.kt | 1 - .../src/main/kotlin/org/bitcoindevkit/bdk.kt | 427 ++++++++++++++++++ 5 files changed, 432 insertions(+), 5 deletions(-) create mode 100644 api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt diff --git a/.gitignore b/.gitignore index 277a164..2e06997 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ xcuserdata .lsp .clj-kondo .idea -bdk.kt +android/src/main/kotlin/org/bitcoindevkit/bdk.kt +jvm/src/main/kotlin/org/bitcoindevkit/bdk.kt diff --git a/api-docs/Module.md b/api-docs/Module.md index b7efdc2..345f22f 100644 --- a/api-docs/Module.md +++ b/api-docs/Module.md @@ -1,5 +1,5 @@ # Module bdk-android -The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Android and the JVM. +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings libraries for Android and the JVM. These docs are valid for both `bdk-jvm` and `bdk-android` libraries. diff --git a/api-docs/build.gradle.kts b/api-docs/build.gradle.kts index d9b586d..9394b59 100644 --- a/api-docs/build.gradle.kts +++ b/api-docs/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "1.7.0" + kotlin("jvm") version "1.7.10" // API docs id("org.jetbrains.dokka") version "1.7.10" @@ -27,7 +27,7 @@ tasks.withType().configureEach { dokkaSourceSets { named("main") { moduleName.set("bdk-android") - moduleVersion.set("0.8.2") + moduleVersion.set("0.9.0") includes.from("Module.md") samples.from("src/main/kotlin/org/bitcoindevkit/Samples.kt") } diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt index 4e355fd..012d5cd 100644 --- a/api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt +++ b/api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt @@ -11,4 +11,3 @@ class Samples { ) ) } - diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt new file mode 100644 index 0000000..8cf2cb9 --- /dev/null +++ b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt @@ -0,0 +1,427 @@ +package org.bitcoindevkit + +/** + * The cryptocurrency to act on. + */ +enum class Network { + /** Bitcoin's mainnet */ + BITCOIN, + + /** Bitcoin’s testnet */ + TESTNET, + + /** Bitcoin’s signet */ + SIGNET, + + /** Bitcoin’s regtest */ + REGTEST, +} + +/** + * A derived address and the index it was found at. + */ +data class AddressInfo ( + /** Child index of this address. */ + var index: UInt, + + /** Address. */ + var address: String +) + +/** + * The address index selection strategy to use to derive an address from the wallet’s external descriptor. + * + * If you’re unsure which one to use, use `AddressIndex.NEW`. + */ +enum class AddressIndex { + /** Return a new address after incrementing the current descriptor index. */ + NEW, + + /** + * Return the address for the current descriptor index if it has not been used in a received transaction. Otherwise return a new address as with `AddressIndex.NEW`. Use with caution, if the wallet has not yet detected an address has been used it could return an already used address. This function is primarily meant for situations where the caller is untrusted; for example when deriving donation addresses on-demand for a public web page. + */ + LAST_UNUSED, +} + +/** + * Type that can contain any of the database configurations defined by the library. + */ +sealed class DatabaseConfig { + /** Configuration for an in-memory database */ + object Memory : DatabaseConfig() + + /** Configuration for a Sled database */ + data class Sled(val config: SledDbConfiguration) : DatabaseConfig() + + /** Configuration for a SQLite database */ + data class Sqlite(val config: SqliteDbConfiguration) : DatabaseConfig() +} + +/** + * Configuration type for a SQLite database. + */ +data class SqliteDbConfiguration( + /** Main directory of the db */ + var path: String, +) + +/** + * Configuration type for a SledDB database. + */ +data class SledDbConfiguration( + /** Main directory of the db */ + var path: String, + + /** Name of the database tree, a separated namespace for the data */ + var treeName: String, +) + +/** + * Configuration for an Electrum blockchain. + * + * @sample org.bitcoindevkit.Samples.blockchainConfig + */ +data class ElectrumConfig ( + /** URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port, e.g. `ssl://electrum.blockstream.info:60002`. */ + var url: String, + + /** URL of the socks5 proxy server or a Tor service. */ + var socks5: String?, + + /** Request retry count. */ + var retry: UByte, + + /** Request timeout (seconds). */ + var timeout: UByte?, + + /** Stop searching addresses for transactions after finding an unused gap of this length. */ + var stopGap: ULong +) + +/** + * Configuration for an Esplora blockchain. + */ +data class EsploraConfig ( + /** Base URL of the esplora service, e.g. `https://blockstream.info/api/`. */ + var baseUrl: String, + + /** Optional URL of the proxy to use to make requests to the Esplora server. */ + var proxy: String?, + + /** Number of parallel requests sent to the esplora service (default: 4). */ + var concurrency: UByte?, + + /** Stop searching addresses for transactions after finding an unused gap of this length. */ + var stopGap: ULong, + + /** Socket timeout. */ + var timeout: ULong? +) + +/** + * Type that can contain any of the blockchain configurations defined by the library. + * + * @sample org.bitcoindevkit.Samples.blockchainConfig + */ +sealed class BlockchainConfig { + /** Electrum client */ + data class Electrum(val config: ElectrumConfig) : BlockchainConfig() + + /** Esplora client */ + data class Esplora(val config: EsploraConfig) : BlockchainConfig() +} + +/** + * A wallet transaction. + */ +data class TransactionDetails ( + /** Fee value (sats) if available. The availability of the fee depends on the backend. It’s never None with an Electrum server backend, but it could be None with a Bitcoin RPC node without txindex that receive funds while offline. */ + var fee: ULong?, + + /** Received value (sats) Sum of owned outputs of this transaction. */ + var received: ULong, + + /** Sent value (sats) Sum of owned inputs of this transaction. */ + var sent: ULong, + + /** Transaction id. */ + var txid: String + + /** If the transaction is confirmed, [BlockTime] contains height and timestamp of the block containing the transaction. This property is null for unconfirmed transactions. */ + var confirmationTime: BlockTime? +) + +/** + * A blockchain backend. + */ +class Blockchain( + config: BlockchainConfig +) { + /** Broadcast a transaction. */ + fun broadcast(psbt: PartiallySignedBitcoinTransaction): String {} + + /** Get the current height of the blockchain. */ + fun getHeight(): UInt {} + + /** Get the block hash of a given block. */ + fun getBlockHash(height: UInt): String {} +} + +/** + * A partially signed bitcoin transaction. + */ +class PartiallySignedBitcoinTransaction(psbtBase64: String) { + /** Return the PSBT in string format, using a base64 encoding. */ + fun serialize(): String {} + + /** Get the txid of the PSBT. */ + fun txid(): String {} +} + +/** + * A reference to a transaction output. + */ +data class OutPoint ( + /** The referenced transaction’s txid. */ + var txid: String, + + /** The index of the referenced output in its transaction’s vout. */ + var vout: UInt +) + +/** + * A transaction output, which defines new coins to be created from old ones. + */ +data class TxOut ( + /** The value of the output, in satoshis. */ + var value: ULong, + + /** The address of the output. */ + var address: String +) + +/** + * An unspent output owned by a [Wallet]. + */ +data class LocalUtxo ( + /** Reference to a transaction output. */ + var outpoint: OutPoint, + + /** Transaction output. */ + var txout: TxOut, + + /** Type of keychain. */ + var keychain: KeychainKind, + + /** Whether this UTXO is spent or not. */ + var isSpent: Boolean +) + +/** + * Types of keychains. + */ +enum class KeychainKind { + /** External */ + EXTERNAL, + + /** Internal, usually used for change outputs. */ + INTERNAL, +} + +/** + * Block height and timestamp of a block. + */ +data class BlockTime ( + /** confirmation block height */ + var height: UInt, + + /** confirmation block timestamp */ + var timestamp: ULong, +) + +/** + * A Bitcoin wallet. + * The Wallet acts as a way of coherently interfacing with output descriptors and related transactions. Its main components are: + * 1. Output descriptors from which it can derive addresses. + * 2. A Database where it tracks transactions and utxos related to the descriptors. + * 3. Signers that can contribute signatures to addresses instantiated from the descriptors. + */ +class Wallet( + descriptor: String, + changeDescriptor: String, + network: Network, + databaseConfig: DatabaseConfig, +) { + /** Return a derived address using the external descriptor, see [AddressIndex] for available address index selection strategies. If none of the keys in the descriptor are derivable (i.e. the descriptor does not end with a * character) then the same address will always be returned for any [AddressIndex]. */ + fun getAddress(addressIndex: AddressIndex): AddressInfo {} + + /** Return the balance, meaning the sum of this wallet’s unspent outputs’ values. Note that this method only operates on the internal database, which first needs to be [Wallet.sync] manually. */ + fun getBalance(): ULong {} + + /** Sign a transaction with all the wallet’s signers. */ + fun sign(psbt: PartiallySignedBitcoinTransaction): Boolean {} + + /** Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually. */ + fun listTransactions(): List {} + + /** Get the Bitcoin network the wallet is using. */ + fun network(): Network {} + + /** Sync the internal database with the blockchain. */ + fun sync(blockchain: Blockchain, progress: Progress?) {} + + /** Return the list of unspent outputs of this wallet. Note that this method only operates on the internal database, which first needs to be [Wallet.sync] manually. */ + fun listUnspent(): List {} +} + +/** + * Class that logs at level INFO every update received (if any). + */ +class Progress { + /** Send a new progress update. The progress value should be in the range 0.0 - 100.0, and the message value is an optional text message that can be displayed to the user. */ + fun update(progress: Float, message: String?) {} +} + +/** + * A transaction builder. + * + * After creating the TxBuilder, you set options on it until finally calling finish to consume the builder and generate the transaction. + * + * Each method on the TxBuilder returns an instance of a new TxBuilder with the option set/added. + */ +class TxBuilder() { + /** Add data as an output using OP_RETURN. */ + fun addData(data: List): TxBuilder {} + + /** Add a recipient to the internal list. */ + fun addRecipient(address: String, amount: ULong): TxBuilder {} + + /** Set the list of recipients by providing a list of [AddressAmount]. */ + fun setRecipients(recipients: List): TxBuilder {} + + /** Add a utxo to the internal list of unspendable utxos. It’s important to note that the "must-be-spent" utxos added with [TxBuilder.addUtxo] have priority over this. See the Rust docs of the two linked methods for more details. */ + fun addUnspendable(unspendable: OutPoint): TxBuilder {} + + /** Add an outpoint to the internal list of UTXOs that must be spent. These have priority over the "unspendable" utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent. */ + fun addUtxo(outpoint: OutPoint): TxBuilder {} + + /** Add the list of outpoints to the internal list of UTXOs that must be spent. If an error occurs while adding any of the UTXOs then none of them are added and the error is returned. These have priority over the "unspendable" utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent. */ + fun addUtxos(outpoints: List): TxBuilder {} + + /** Do not spend change outputs. This effectively adds all the change outputs to the "unspendable" list. See [TxBuilder.unspendable]. */ + fun doNotSpendChange(): TxBuilder {} + + /** Only spend utxos added by [add_utxo]. The wallet will not add additional utxos to the transaction even if they are needed to make the transaction valid. */ + fun manuallySelectedOnly(): TxBuilder {} + + /** Only spend change outputs. This effectively adds all the non-change outputs to the "unspendable" list. See [TxBuilder.unspendable]. */ + fun onlySpendChange(): TxBuilder {} + + /** Replace the internal list of unspendable utxos with a new list. It’s important to note that the "must-be-spent" utxos added with [TxBuilder.addUtxo] have priority over these. See the Rust docs of the two linked methods for more details. */ + fun unspendable(unspendable: List): TxBuilder {} + + /** Set a custom fee rate. */ + fun feeRate(satPerVbyte: Float): TxBuilder {} + + /** Set an absolute fee. */ + fun feeAbsolute(feeAmount: ULong): TxBuilder {} + + /** Spend all the available inputs. This respects filters like [TxBuilder.unspendable] and the change policy. */ + fun drainWallet(): TxBuilder {} + + /** Sets the address to drain excess coins to. Usually, when there are excess coins they are sent to a change address generated by the wallet. This option replaces the usual change address with an arbitrary ScriptPubKey of your choosing. Just as with a change output, if the drain output is not needed (the excess coins are too small) it will not be included in the resulting transaction. The only difference is that it is valid to use [drainTo] without setting any ordinary recipients with [addRecipient] (but it is perfectly fine to add recipients as well). If you choose not to set any recipients, you should either provide the utxos that the transaction should spend via [addUtxos], or set [drainWallet] to spend all of them. When bumping the fees of a transaction made with this option, you probably want to use [BumpFeeTxBuilder.allowShrinking] to allow this output to be reduced to pay for the extra fees. */ + fun drainTo(address: String): TxBuilder {} + + /** Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`. */ + fun enableRbf(): TxBuilder {} + + /** Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not be a valid nSequence to signal RBF. */ + fun enableRbfWithSequence(nsequence: UInt): TxBuilder {} + + /** Finish building the transaction. Returns the BIP174 PSBT. */ + fun finish(wallet: Wallet): PartiallySignedBitcoinTransaction {} +} + +/** + * A object holding an address and an amount. + */ +data class AddressAmount ( + /** The address. */ + var address: String, + + /** The amount. */ + var amount: ULong +) {} + +/** + * The BumpFeeTxBuilder is used to bump the fee on a transaction that has been broadcast and has its RBF flag set to true. + */ +class BumpFeeTxBuilder() { + /** Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this script_pubkey in order to bump the transaction fee. Without specifying this the wallet will attempt to find a change output to shrink instead. Note that the output may shrink to below the dust limit and therefore be removed. If it is preserved then it is currently not guaranteed to be in the same position as it was originally. Returns an error if script_pubkey can’t be found among the recipients of the transaction we are bumping. */ + fun allowShrinking(address: String): BumpFeeTxBuilder {} + + /** Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`. */ + fun enableRbf(): BumpFeeTxBuilder {} + + /** Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not be a valid nSequence to signal RBF. */ + fun enableRbfWithSequence(nsequence: UInt): BumpFeeTxBuilder {} + + /** Finish building the transaction. Returns the BIP174 PSBT. */ + fun finish(wallet: Wallet): PartiallySignedBitcoinTransaction {} +} + +/** + * Generates a new mnemonic using the English word list and the given number of words (12, 15, 18, 21, or 24). + */ +fun generateMnemonic(wordCount: WordCount): String {} + +/** + * + */ +class DescriptorSecretKey(network: Network, mnemonic: String, password: String?) { + /** Derive a private descriptor at a given path. */ + fun derive(path: DerivationPath): DescriptorSecretKey {} + + /** Extend the private descriptor with a custom path. */ + fun extend(path: DerivationPath): DescriptorSecretKey {} + + /** Return the public version of the descriptor. */ + fun asPublic(): DescriptorPublicKey {} + + /** Return the private descriptor as a string. */ + fun asString(): String {} +} + +/** + * + */ +class DescriptorPublicKey(network: Network, mnemonic: String, password: String?) { + /** Derive a public descriptor at a given path. */ + fun derive(path: DerivationPath): DescriptorSecretKey {} + + /** Extend the public descriptor with a custom path. */ + fun extend(path: DerivationPath): DescriptorSecretKey {} + + /** Return the public descriptor as a string. */ + fun asString(): String {} +} + +/** + * An enum describing entropy length (aka word count) in the mnemonic. + */ +enum class WordCount { + /** 12 words mnemonic (128 bits entropy) */ + WORDS12, + + /** 15 words mnemonic (160 bits entropy) */ + WORDS15, + + /** 18 words mnemonic (192 bits entropy) */ + WORDS18, + + /** 21 words mnemonic (224 bits entropy) */ + WORDS21, + + /** 24 words mnemonic (256 bits entropy) */ + WORDS24, +} From 157b1875c5ee3a6e8f93fda37f6040e39990abb5 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 13 Sep 2022 12:16:15 -0400 Subject: [PATCH 255/272] Remove unused docs patch --- docs.patch | 3225 ---------------------------------------------------- 1 file changed, 3225 deletions(-) delete mode 100644 docs.patch diff --git a/docs.patch b/docs.patch deleted file mode 100644 index 8fb620f..0000000 --- a/docs.patch +++ /dev/null @@ -1,3225 +0,0 @@ -*** tmp/bdkwithoutdocs.kt 2022-05-12 15:13:30.132174559 -0700 ---- jvm/src/main/kotlin/org/bitcoindevkit/bdk.kt 2022-05-12 15:36:05.744074453 -0700 -*************** -*** 16,131 **** - // now that means coming from the exact some version of `uniffi` that was used to - // compile the Rust component. The easiest way to ensure this is to bundle the Kotlin - // helpers directly inline like we're doing here. - - import com.sun.jna.Library - import com.sun.jna.Native - import com.sun.jna.Pointer - import com.sun.jna.Structure - import com.sun.jna.ptr.ByReference - import java.nio.ByteBuffer - import java.nio.ByteOrder - import java.util.concurrent.atomic.AtomicBoolean - import java.util.concurrent.atomic.AtomicLong - import java.util.concurrent.locks.ReentrantLock - import kotlin.concurrent.withLock - - // The Rust Buffer and 3 templated methods (alloc, free, reserve). - // This is a helper for safely working with byte buffers returned from the Rust code. - // A rust-owned buffer is represented by its capacity, its current length, and a - // pointer to the underlying data. -! - @Structure.FieldOrder("capacity", "len", "data") - open class RustBuffer : Structure() { - @JvmField var capacity: Int = 0 - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : RustBuffer(), Structure.ByValue - class ByReference : RustBuffer(), Structure.ByReference - - companion object { - internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_alloc(size, status).also { - if(it.data == null) { - throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") - } - } - } - - internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_free(buf, status) - } - - internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_reserve(buf, additional, status) - } - } - - @Suppress("TooGenericExceptionThrown") - fun asByteBuffer() = - this.data?.getByteBuffer(0, this.len.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - } - } - - /** - * The equivalent of the `*mut RustBuffer` type. - * Required for callbacks taking in an out pointer. - * - * Size is the sum of all values in the struct. - */ - class RustBufferByReference : ByReference(16) { - /** - * Set the pointed-to `RustBuffer` to the given value. - */ - fun setValue(value: RustBuffer.ByValue) { - // NOTE: The offsets are as they are in the C-like struct. - val pointer = getPointer() - pointer.setInt(0, value.capacity) - pointer.setInt(4, value.len) - pointer.setPointer(8, value.data) - } - } - - // This is a helper for safely passing byte references into the rust code. - // It's not actually used at the moment, because there aren't many things that you - // can take a direct pointer to in the JVM, and if we're going to copy something - // then we might as well copy it into a `RustBuffer`. But it's here for API - // completeness. - - @Structure.FieldOrder("len", "data") - open class ForeignBytes : Structure() { - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : ForeignBytes(), Structure.ByValue - } - - - // A helper for structured writing of data into a `RustBuffer`. - // This is very similar to `java.nio.ByteBuffer` but it knows how to grow - // the underlying `RustBuffer` on demand. - // - // TODO: we should benchmark writing things into a `RustBuffer` versus building - // up a bytearray and then copying it across. -! - class RustBufferBuilder() { - var rbuf = RustBuffer.ByValue() - var bbuf: ByteBuffer? = null - - init { - val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size - rbuf.writeField("len", 0) - this.setRustBuffer(rbuf) - } - - internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { - this.rbuf = rbuf - this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - it.position(rbuf.len) - } - } - - fun finalize() : RustBuffer.ByValue { - val rbuf = this.rbuf ---- 16,141 ---- - // now that means coming from the exact some version of `uniffi` that was used to - // compile the Rust component. The easiest way to ensure this is to bundle the Kotlin - // helpers directly inline like we're doing here. - - import com.sun.jna.Library - import com.sun.jna.Native - import com.sun.jna.Pointer - import com.sun.jna.Structure - import com.sun.jna.ptr.ByReference - import java.nio.ByteBuffer - import java.nio.ByteOrder - import java.util.concurrent.atomic.AtomicBoolean - import java.util.concurrent.atomic.AtomicLong - import java.util.concurrent.locks.ReentrantLock - import kotlin.concurrent.withLock - - // The Rust Buffer and 3 templated methods (alloc, free, reserve). - // This is a helper for safely working with byte buffers returned from the Rust code. - // A rust-owned buffer is represented by its capacity, its current length, and a - // pointer to the underlying data. -! /** -! * @suppress -! */ - @Structure.FieldOrder("capacity", "len", "data") - open class RustBuffer : Structure() { - @JvmField var capacity: Int = 0 - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : RustBuffer(), Structure.ByValue - class ByReference : RustBuffer(), Structure.ByReference - - companion object { - internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_alloc(size, status).also { - if(it.data == null) { - throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") - } - } - } - - internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_free(buf, status) - } - - internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_360_rustbuffer_reserve(buf, additional, status) - } - } - - @Suppress("TooGenericExceptionThrown") - fun asByteBuffer() = - this.data?.getByteBuffer(0, this.len.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - } - } - - /** - * The equivalent of the `*mut RustBuffer` type. - * Required for callbacks taking in an out pointer. - * - * Size is the sum of all values in the struct. - */ -+ /** -+ * @suppress -+ */ - class RustBufferByReference : ByReference(16) { - /** - * Set the pointed-to `RustBuffer` to the given value. - */ - fun setValue(value: RustBuffer.ByValue) { - // NOTE: The offsets are as they are in the C-like struct. - val pointer = getPointer() - pointer.setInt(0, value.capacity) - pointer.setInt(4, value.len) - pointer.setPointer(8, value.data) - } - } - - // This is a helper for safely passing byte references into the rust code. - // It's not actually used at the moment, because there aren't many things that you - // can take a direct pointer to in the JVM, and if we're going to copy something - // then we might as well copy it into a `RustBuffer`. But it's here for API - // completeness. - -+ /** -+ * @suppress -+ */ - @Structure.FieldOrder("len", "data") - open class ForeignBytes : Structure() { - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : ForeignBytes(), Structure.ByValue - } - - - // A helper for structured writing of data into a `RustBuffer`. - // This is very similar to `java.nio.ByteBuffer` but it knows how to grow - // the underlying `RustBuffer` on demand. - // - // TODO: we should benchmark writing things into a `RustBuffer` versus building - // up a bytearray and then copying it across. -! /** -! * @suppress -! */ - class RustBufferBuilder() { - var rbuf = RustBuffer.ByValue() - var bbuf: ByteBuffer? = null - - init { - val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size - rbuf.writeField("len", 0) - this.setRustBuffer(rbuf) - } - - internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { - this.rbuf = rbuf - this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - it.position(rbuf.len) - } - } - - fun finalize() : RustBuffer.ByValue { - val rbuf = this.rbuf -*************** -*** 232,345 **** - // This would be a good candidate for isolating in its own ffi-support lib. - // Error runtime. - @Structure.FieldOrder("code", "error_buf") - internal open class RustCallStatus : Structure() { - @JvmField var code: Int = 0 - @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - - fun isSuccess(): Boolean { - return code == 0 - } - - fun isError(): Boolean { - return code == 1 - } - - fun isPanic(): Boolean { - return code == 2 - } - } - - class InternalException(message: String) : Exception(message) - - // Each top-level error class has a companion object that can lift the error from the call status's rust buffer - interface CallStatusErrorHandler { - fun lift(error_buf: RustBuffer.ByValue): E; - } - - // Helpers for calling Rust - // In practice we usually need to be synchronized to call this safely, so it doesn't - // synchronize itself - - // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err - private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { - var status = RustCallStatus(); - val return_value = callback(status) - if (status.isSuccess()) { - return return_value - } else if (status.isError()) { - throw errorHandler.lift(status.error_buf) - } else if (status.isPanic()) { - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if (status.error_buf.len > 0) { - throw InternalException(String.lift(status.error_buf)) - } else { - throw InternalException("Rust panic") - } - } else { - throw InternalException("Unknown rust call status: $status.code") - } - } - - // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR - object NullCallStatusErrorHandler: CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): InternalException { - RustBuffer.free(error_buf) - return InternalException("Unexpected CALL_ERROR") - } - } - - // Call a rust function that returns a plain value - private inline fun rustCall(callback: (RustCallStatus) -> U): U { - return rustCallWithError(NullCallStatusErrorHandler, callback); - } - - // Contains loading, initialization code, - // and the FFI Function declarations in a com.sun.jna.Library. - @Synchronized - private fun findLibraryName(componentName: String): String { - val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") - if (libOverride != null) { - return libOverride - } - return "bdkffi" - } - - private inline fun loadIndirect( - componentName: String - ): Lib { - return Native.load(findLibraryName(componentName), Lib::class.java) - } - - // A JNA Library to expose the extern-C FFI definitions. - // This is an implementation detail which will be called internally by the public API. - - internal interface _UniFFILib : Library { - companion object { - internal val INSTANCE: _UniFFILib by lazy { - loadIndirect<_UniFFILib>(componentName = "bdk") - .also { lib: _UniFFILib -> - FfiConverterCallbackInterfaceProgress.register(lib) - } -! - } - } - - fun ffi_bdk_360_Blockchain_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_360_Blockchain_new(config: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_360_Blockchain_broadcast(ptr: Pointer,psbt: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_360_Wallet_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_360_Wallet_new(descriptor: RustBuffer.ByValue,change_descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, ---- 242,364 ---- - // This would be a good candidate for isolating in its own ffi-support lib. - // Error runtime. - @Structure.FieldOrder("code", "error_buf") - internal open class RustCallStatus : Structure() { - @JvmField var code: Int = 0 - @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - - fun isSuccess(): Boolean { - return code == 0 - } - - fun isError(): Boolean { - return code == 1 - } - - fun isPanic(): Boolean { - return code == 2 - } - } - -+ /** -+ * @suppress -+ */ - class InternalException(message: String) : Exception(message) - -+ /** -+ * @suppress -+ */ - // Each top-level error class has a companion object that can lift the error from the call status's rust buffer - interface CallStatusErrorHandler { - fun lift(error_buf: RustBuffer.ByValue): E; - } - - // Helpers for calling Rust - // In practice we usually need to be synchronized to call this safely, so it doesn't - // synchronize itself - - // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err - private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { - var status = RustCallStatus(); - val return_value = callback(status) - if (status.isSuccess()) { - return return_value - } else if (status.isError()) { - throw errorHandler.lift(status.error_buf) - } else if (status.isPanic()) { - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if (status.error_buf.len > 0) { - throw InternalException(String.lift(status.error_buf)) - } else { - throw InternalException("Rust panic") - } - } else { - throw InternalException("Unknown rust call status: $status.code") - } - } - -+ /** -+ * @suppress -+ */ - // CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR - object NullCallStatusErrorHandler: CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): InternalException { - RustBuffer.free(error_buf) - return InternalException("Unexpected CALL_ERROR") - } - } - - // Call a rust function that returns a plain value - private inline fun rustCall(callback: (RustCallStatus) -> U): U { - return rustCallWithError(NullCallStatusErrorHandler, callback); - } - - // Contains loading, initialization code, - // and the FFI Function declarations in a com.sun.jna.Library. - @Synchronized - private fun findLibraryName(componentName: String): String { - val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") - if (libOverride != null) { - return libOverride - } - return "bdkffi" - } - - private inline fun loadIndirect( - componentName: String - ): Lib { - return Native.load(findLibraryName(componentName), Lib::class.java) - } - - // A JNA Library to expose the extern-C FFI definitions. - // This is an implementation detail which will be called internally by the public API. - - internal interface _UniFFILib : Library { - companion object { - internal val INSTANCE: _UniFFILib by lazy { - loadIndirect<_UniFFILib>(componentName = "bdk") - .also { lib: _UniFFILib -> - FfiConverterCallbackInterfaceProgress.register(lib) - } -! - } - } - - fun ffi_bdk_360_Blockchain_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_360_Blockchain_new(config: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Pointer - - fun bdk_360_Blockchain_broadcast(ptr: Pointer,psbt: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_360_Wallet_object_free(ptr: Pointer, - uniffi_out_err: RustCallStatus - ): Unit - - fun bdk_360_Wallet_new(descriptor: RustBuffer.ByValue,change_descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, -*************** -*** 461,523 **** - fun bdk_360_restore_extended_key(network: RustBuffer.ByValue,mnemonic: RustBuffer.ByValue,password: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_360_rustbuffer_alloc(size: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_360_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_360_rustbuffer_free(buf: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_360_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - -! - } - - // Public interface members begin here. - - // Interface implemented by anything that can contain an object reference. - // - // Such types expose a `destroy()` method that must be called to cleanly - // dispose of the contained objects. Failure to call this method may result - // in memory leaks. - // - // The easiest way to ensure this method is called is to use the `.use` - // helper method to execute a block and destroy the object at the end. - interface Disposable { - fun destroy() - companion object { - fun destroy(vararg args: Any?) { - args.filterIsInstance() - .forEach(Disposable::destroy) - } - } - } - - inline fun T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } - - // The base class for all UniFFI Object types. - // - // This class provides core operations for working with the Rust `Arc` pointer to - // the live Rust struct on the other side of the FFI. - // - // There's some subtlety here, because we have to be careful not to operate on a Rust - // struct after it has been dropped, and because we must expose a public API for freeing - // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: ---- 480,548 ---- - fun bdk_360_restore_extended_key(network: RustBuffer.ByValue,mnemonic: RustBuffer.ByValue,password: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_360_rustbuffer_alloc(size: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_360_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_360_rustbuffer_free(buf: RustBuffer.ByValue, - uniffi_out_err: RustCallStatus - ): Unit - - fun ffi_bdk_360_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - -! - } - - // Public interface members begin here. - - // Interface implemented by anything that can contain an object reference. - // - // Such types expose a `destroy()` method that must be called to cleanly - // dispose of the contained objects. Failure to call this method may result - // in memory leaks. - // - // The easiest way to ensure this method is called is to use the `.use` - // helper method to execute a block and destroy the object at the end. -+ /** -+ * @suppress -+ */ - interface Disposable { - fun destroy() - companion object { - fun destroy(vararg args: Any?) { - args.filterIsInstance() - .forEach(Disposable::destroy) - } - } - } - -+ /** -+ * @suppress -+ */ - inline fun T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } - - // The base class for all UniFFI Object types. - // - // This class provides core operations for working with the Rust `Arc` pointer to - // the live Rust struct on the other side of the FFI. - // - // There's some subtlety here, because we have to be careful not to operate on a Rust - // struct after it has been dropped, and because we must expose a public API for freeing - // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -*************** -*** 577,638 **** ---- 602,672 ---- - // Otherwise we atomically decrement and check the counter. - // If it has reached zero then we destroy the underlying Rust struct. - // - // Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, - // and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. - // - // The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been - // called *and* all in-flight method calls have completed, avoiding violating any of the expectations - // of the underlying Rust code. - // - // In the future we may be able to replace some of this with automatic finalization logic, such as using - // the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is - // invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also - // possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], - // so there would still be some complexity here). - // - // Sigh...all of this for want of a robust finalization mechanism. - // - // [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 - // -+ /** -+ * @suppress -+ */ - abstract class FFIObject( - protected val pointer: Pointer - ): Disposable, AutoCloseable { - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - open protected fun freeRustArcPtr() { - // To be overridden in subclasses. - } - -+ /** -+ * @suppress -+ */ - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } - -+ /** -+ * @suppress -+ */ - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.pointer) -*************** -*** 663,1080 **** - } - } - - fun get(handle: Handle) = lock.withLock { - leftMap[handle] - } - - fun delete(handle: Handle) { - this.remove(handle) - } - - fun remove(handle: Handle): T? = - lock.withLock { - leftMap.remove(handle)?.let { obj -> - rightMap.remove(obj) - obj - } - } - } - - interface ForeignCallback : com.sun.jna.Callback { - public fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int - } - - // Magic number for the Rust proxy to call using the same mechanism as every other method, - // to free the callback once it's dropped by Rust. - internal const val IDX_CALLBACK_FREE = 0 - - internal abstract class FfiConverterCallbackInterface( - protected val foreignCallback: ForeignCallback - ) { - val handleMap = ConcurrentHandleMap() - - // Registers the foreign callback with the Rust side. - // This method is generated for each callback interface. - abstract fun register(lib: _UniFFILib) - - fun drop(handle: Handle): RustBuffer.ByValue { - return handleMap.remove(handle).let { RustBuffer.ByValue() } - } - - fun lift(n: Handle) = handleMap.get(n) - - fun read(buf: ByteBuffer) = lift(buf.getLong()) - - fun lower(v: CallbackInterface) = - handleMap.insert(v).also { - assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } - } - - fun write(v: CallbackInterface, buf: RustBufferBuilder) = - buf.putLong(lower(v)) - } - - - - enum class Network { - BITCOIN,TESTNET,SIGNET,REGTEST; - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): Network { - return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) } - } - - internal fun read(buf: ByteBuffer) = - try { values()[buf.getInt() - 1] } - catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - buf.putInt(this.ordinal + 1) - } - } - - - - - - - - sealed class DatabaseConfig { - object Memory : DatabaseConfig() -! - data class Sled( -! val config: SledDbConfiguration - ) : DatabaseConfig() -! - data class Sqlite( -! val config: SqliteDbConfiguration - ) : DatabaseConfig() -- - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig { - return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): DatabaseConfig { - return when(buf.getInt()) { - 1 -> DatabaseConfig.Memory - 2 -> DatabaseConfig.Sled( - SledDbConfiguration.read(buf) - ) - 3 -> DatabaseConfig.Sqlite( - SqliteDbConfiguration.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is DatabaseConfig.Memory -> { - buf.putInt(1) -! - } - is DatabaseConfig.Sled -> { - buf.putInt(2) - this.config.write(buf) -! - } - is DatabaseConfig.Sqlite -> { - buf.putInt(3) - this.config.write(buf) -! - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - -! -! - } - - - - - - - - sealed class Transaction { -! - data class Unconfirmed( -! val details: TransactionDetails - ) : Transaction() -! - data class Confirmed( -! val details: TransactionDetails, -! val confirmation: BlockTime - ) : Transaction() -- - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): Transaction { - return liftFromRustBuffer(rbuf) { buf -> Transaction.read(buf) } - } - - internal fun read(buf: ByteBuffer): Transaction { - return when(buf.getInt()) { - 1 -> Transaction.Unconfirmed( - TransactionDetails.read(buf) - ) - 2 -> Transaction.Confirmed( - TransactionDetails.read(buf), - BlockTime.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is Transaction.Unconfirmed -> { - buf.putInt(1) - this.details.write(buf) -! - } - is Transaction.Confirmed -> { - buf.putInt(2) - this.details.write(buf) - this.confirmation.write(buf) -! - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - -! -! - } - - - - - - - - sealed class BlockchainConfig { -! - data class Electrum( -! val config: ElectrumConfig - ) : BlockchainConfig() -! - data class Esplora( -! val config: EsploraConfig - ) : BlockchainConfig() -- - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig { - return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): BlockchainConfig { - return when(buf.getInt()) { - 1 -> BlockchainConfig.Electrum( - ElectrumConfig.read(buf) - ) - 2 -> BlockchainConfig.Esplora( - EsploraConfig.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is BlockchainConfig.Electrum -> { - buf.putInt(1) - this.config.write(buf) -! - } - is BlockchainConfig.Esplora -> { - buf.putInt(2) - this.config.write(buf) -! - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - -! -! - } - - - - - - enum class WordCount { - WORDS12,WORDS15,WORDS18,WORDS21,WORDS24; - - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): WordCount { - return liftFromRustBuffer(rbuf) { buf -> WordCount.read(buf) } - } - - internal fun read(buf: ByteBuffer) = - try { values()[buf.getInt() - 1] } - catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - buf.putInt(this.ordinal + 1) - } - } - - - - @Throws(BdkException::class) - - fun generateExtendedKey(network: Network, wordCount: WordCount, password: String? ): ExtendedKeyInfo { -! val _retval = - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_generate_extended_key(network.lower(), wordCount.lower(), lowerOptionalString(password) ,status) - } - return ExtendedKeyInfo.lift(_retval) - } - - - @Throws(BdkException::class) - - fun restoreExtendedKey(network: Network, mnemonic: String, password: String? ): ExtendedKeyInfo { -! val _retval = - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_restore_extended_key(network.lower(), mnemonic.lower(), lowerOptionalString(password) ,status) - } - return ExtendedKeyInfo.lift(_retval) - } - - - public interface BlockchainInterface { -! - @Throws(BdkException::class) - fun broadcast(psbt: PartiallySignedBitcoinTransaction ) -! - } - - class Blockchain( - pointer: Pointer - ) : FFIObject(pointer), BlockchainInterface { - constructor(config: BlockchainConfig ) : - this( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Blockchain_new(config.lower() ,status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { - rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_360_Blockchain_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - -! - @Throws(BdkException::class)override fun broadcast(psbt: PartiallySignedBitcoinTransaction ) = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Blockchain_broadcast(it, psbt.lower() , status) - } - } -- -- - - companion object { - internal fun lift(ptr: Pointer): Blockchain { - return Blockchain(ptr) - } - - internal fun read(buf: ByteBuffer): Blockchain { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return Blockchain.lift(Pointer(buf.getLong())) - } - -! - } - } - - public interface WalletInterface { -! - fun getNewAddress(): String -! - fun getLastUnusedAddress(): String -! - @Throws(BdkException::class) - fun getBalance(): ULong -! - @Throws(BdkException::class) - fun sign(psbt: PartiallySignedBitcoinTransaction ) -! - @Throws(BdkException::class) - fun getTransactions(): List -! - fun getNetwork(): Network -! - @Throws(BdkException::class) - fun sync(blockchain: Blockchain, progress: Progress? ) -! - } - - class Wallet( - pointer: Pointer - ) : FFIObject(pointer), WalletInterface { - constructor(descriptor: String, changeDescriptor: String?, network: Network, databaseConfig: DatabaseConfig ) : - this( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_new(descriptor.lower(), lowerOptionalString(changeDescriptor), network.lower(), databaseConfig.lower() ,status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { ---- 697,1130 ---- - } - } - - fun get(handle: Handle) = lock.withLock { - leftMap[handle] - } - - fun delete(handle: Handle) { - this.remove(handle) - } - - fun remove(handle: Handle): T? = - lock.withLock { - leftMap.remove(handle)?.let { obj -> - rightMap.remove(obj) - obj - } - } - } - -+ /** -+ * @suppress -+ */ - interface ForeignCallback : com.sun.jna.Callback { - public fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int - } - - // Magic number for the Rust proxy to call using the same mechanism as every other method, - // to free the callback once it's dropped by Rust. - internal const val IDX_CALLBACK_FREE = 0 - - internal abstract class FfiConverterCallbackInterface( - protected val foreignCallback: ForeignCallback - ) { - val handleMap = ConcurrentHandleMap() - - // Registers the foreign callback with the Rust side. - // This method is generated for each callback interface. - abstract fun register(lib: _UniFFILib) - - fun drop(handle: Handle): RustBuffer.ByValue { - return handleMap.remove(handle).let { RustBuffer.ByValue() } - } - - fun lift(n: Handle) = handleMap.get(n) - - fun read(buf: ByteBuffer) = lift(buf.getLong()) - - fun lower(v: CallbackInterface) = - handleMap.insert(v).also { - assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } - } - - fun write(v: CallbackInterface, buf: RustBufferBuilder) = - buf.putLong(lower(v)) - } - - - - enum class Network { - BITCOIN,TESTNET,SIGNET,REGTEST; - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): Network { - return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) } - } - - internal fun read(buf: ByteBuffer) = - try { values()[buf.getInt() - 1] } - catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - buf.putInt(this.ordinal + 1) - } - } - - - - - - - - sealed class DatabaseConfig { - object Memory : DatabaseConfig() -! - data class Sled( -! val config: SledDbConfiguration - ) : DatabaseConfig() -! - data class Sqlite( -! val config: SqliteDbConfiguration - ) : DatabaseConfig() - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig { - return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): DatabaseConfig { - return when(buf.getInt()) { - 1 -> DatabaseConfig.Memory - 2 -> DatabaseConfig.Sled( - SledDbConfiguration.read(buf) - ) - 3 -> DatabaseConfig.Sqlite( - SqliteDbConfiguration.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is DatabaseConfig.Memory -> { - buf.putInt(1) -! - } - is DatabaseConfig.Sled -> { - buf.putInt(2) - this.config.write(buf) -! - } - is DatabaseConfig.Sqlite -> { - buf.putInt(3) - this.config.write(buf) -! - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - -! -! - } - - - - - - - - sealed class Transaction { -! - data class Unconfirmed( -! val details: TransactionDetails - ) : Transaction() -! - data class Confirmed( -! val details: TransactionDetails, -! val confirmation: BlockTime - ) : Transaction() - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): Transaction { - return liftFromRustBuffer(rbuf) { buf -> Transaction.read(buf) } - } - - internal fun read(buf: ByteBuffer): Transaction { - return when(buf.getInt()) { - 1 -> Transaction.Unconfirmed( - TransactionDetails.read(buf) - ) - 2 -> Transaction.Confirmed( - TransactionDetails.read(buf), - BlockTime.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is Transaction.Unconfirmed -> { - buf.putInt(1) - this.details.write(buf) -! - } - is Transaction.Confirmed -> { - buf.putInt(2) - this.details.write(buf) - this.confirmation.write(buf) -! - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - -! -! - } - - - - - - - - sealed class BlockchainConfig { -! - data class Electrum( -! val config: ElectrumConfig - ) : BlockchainConfig() -! - data class Esplora( -! val config: EsploraConfig - ) : BlockchainConfig() - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig { - return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): BlockchainConfig { - return when(buf.getInt()) { - 1 -> BlockchainConfig.Electrum( - ElectrumConfig.read(buf) - ) - 2 -> BlockchainConfig.Esplora( - EsploraConfig.read(buf) - ) - else -> throw RuntimeException("invalid enum value, something is very wrong!!") - } - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - when(this) { - is BlockchainConfig.Electrum -> { - buf.putInt(1) - this.config.write(buf) -! - } - is BlockchainConfig.Esplora -> { - buf.putInt(2) - this.config.write(buf) -! - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - -! -! - } - - - - - - enum class WordCount { - WORDS12,WORDS15,WORDS18,WORDS21,WORDS24; - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): WordCount { - return liftFromRustBuffer(rbuf) { buf -> WordCount.read(buf) } - } - - internal fun read(buf: ByteBuffer) = - try { values()[buf.getInt() - 1] } - catch (e: IndexOutOfBoundsException) { - throw RuntimeException("invalid enum value, something is very wrong!!", e) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - buf.putInt(this.ordinal + 1) - } - } - - - - @Throws(BdkException::class) - - fun generateExtendedKey(network: Network, wordCount: WordCount, password: String? ): ExtendedKeyInfo { -! val _retval = - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_generate_extended_key(network.lower(), wordCount.lower(), lowerOptionalString(password) ,status) - } - return ExtendedKeyInfo.lift(_retval) - } - - - @Throws(BdkException::class) - - fun restoreExtendedKey(network: Network, mnemonic: String, password: String? ): ExtendedKeyInfo { -! val _retval = - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_restore_extended_key(network.lower(), mnemonic.lower(), lowerOptionalString(password) ,status) - } - return ExtendedKeyInfo.lift(_retval) - } - - - public interface BlockchainInterface { -! - @Throws(BdkException::class) - fun broadcast(psbt: PartiallySignedBitcoinTransaction ) -! - } - - class Blockchain( - pointer: Pointer - ) : FFIObject(pointer), BlockchainInterface { - constructor(config: BlockchainConfig ) : - this( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Blockchain_new(config.lower() ,status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { - rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_360_Blockchain_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - -! - @Throws(BdkException::class)override fun broadcast(psbt: PartiallySignedBitcoinTransaction ) = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Blockchain_broadcast(it, psbt.lower() , status) - } - } - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(ptr: Pointer): Blockchain { - return Blockchain(ptr) - } - - internal fun read(buf: ByteBuffer): Blockchain { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return Blockchain.lift(Pointer(buf.getLong())) - } - -! - } - } - - public interface WalletInterface { -! - fun getNewAddress(): String -! - fun getLastUnusedAddress(): String -! - @Throws(BdkException::class) - fun getBalance(): ULong -! - @Throws(BdkException::class) - fun sign(psbt: PartiallySignedBitcoinTransaction ) -! - @Throws(BdkException::class) - fun getTransactions(): List -! - fun getNetwork(): Network -! - @Throws(BdkException::class) - fun sync(blockchain: Blockchain, progress: Progress? ) -! - } - - class Wallet( - pointer: Pointer - ) : FFIObject(pointer), WalletInterface { - constructor(descriptor: String, changeDescriptor: String?, network: Network, databaseConfig: DatabaseConfig ) : - this( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_new(descriptor.lower(), lowerOptionalString(changeDescriptor), network.lower(), databaseConfig.lower() ,status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { -*************** -*** 1082,1199 **** - _UniFFILib.INSTANCE.ffi_bdk_360_Wallet_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun getNewAddress(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_get_new_address(it, status) - } - }.let { - String.lift(it) - } -! - override fun getLastUnusedAddress(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_get_last_unused_address(it, status) - } - }.let { - String.lift(it) - } -! -! - @Throws(BdkException::class)override fun getBalance(): ULong = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_get_balance(it, status) - } - }.let { - ULong.lift(it) - } -! -! - @Throws(BdkException::class)override fun sign(psbt: PartiallySignedBitcoinTransaction ) = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_sign(it, psbt.lower() , status) - } - } -! -! - @Throws(BdkException::class)override fun getTransactions(): List = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_get_transactions(it, status) - } - }.let { - liftSequenceEnumTransaction(it) - } -! - override fun getNetwork(): Network = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_get_network(it, status) - } - }.let { - Network.lift(it) - } -! -! - @Throws(BdkException::class)override fun sync(blockchain: Blockchain, progress: Progress? ) = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_sync(it, blockchain.lower(), lowerOptionalCallbackInterfaceProgress(progress) , status) - } - } -- -- - - companion object { - internal fun lift(ptr: Pointer): Wallet { - return Wallet(ptr) - } - - internal fun read(buf: ByteBuffer): Wallet { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return Wallet.lift(Pointer(buf.getLong())) - } - -! - } - } - - public interface PartiallySignedBitcoinTransactionInterface { -! - fun serialize(): String -! - fun txid(): String -! - } - - class PartiallySignedBitcoinTransaction( - pointer: Pointer - ) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface { - constructor(psbtBase64: String ) : - this( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_new(psbtBase64.lower() ,status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { ---- 1132,1250 ---- - _UniFFILib.INSTANCE.ffi_bdk_360_Wallet_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun getNewAddress(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_get_new_address(it, status) - } - }.let { - String.lift(it) - } -! - override fun getLastUnusedAddress(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_get_last_unused_address(it, status) - } - }.let { - String.lift(it) - } -! -! - @Throws(BdkException::class)override fun getBalance(): ULong = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_get_balance(it, status) - } - }.let { - ULong.lift(it) - } -! -! - @Throws(BdkException::class)override fun sign(psbt: PartiallySignedBitcoinTransaction ) = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_sign(it, psbt.lower() , status) - } - } -! -! - @Throws(BdkException::class)override fun getTransactions(): List = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_get_transactions(it, status) - } - }.let { - liftSequenceEnumTransaction(it) - } -! - override fun getNetwork(): Network = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_get_network(it, status) - } - }.let { - Network.lift(it) - } -! -! - @Throws(BdkException::class)override fun sync(blockchain: Blockchain, progress: Progress? ) = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_Wallet_sync(it, blockchain.lower(), lowerOptionalCallbackInterfaceProgress(progress) , status) - } - } - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(ptr: Pointer): Wallet { - return Wallet(ptr) - } - - internal fun read(buf: ByteBuffer): Wallet { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return Wallet.lift(Pointer(buf.getLong())) - } - -! - } - } - - public interface PartiallySignedBitcoinTransactionInterface { -! - fun serialize(): String -! - fun txid(): String -! - } - - class PartiallySignedBitcoinTransaction( - pointer: Pointer - ) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface { - constructor(psbtBase64: String ) : - this( - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_new(psbtBase64.lower() ,status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { -*************** -*** 1201,1284 **** - _UniFFILib.INSTANCE.ffi_bdk_360_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun serialize(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_serialize(it, status) - } - }.let { - String.lift(it) - } -! - override fun txid(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_txid(it, status) - } - }.let { - String.lift(it) - } -- -- - - companion object { - internal fun lift(ptr: Pointer): PartiallySignedBitcoinTransaction { - return PartiallySignedBitcoinTransaction(ptr) - } - - internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong())) - } - -! - } - } - - public interface TxBuilderInterface { -! - fun addRecipient(address: String, amount: ULong ): TxBuilder -! - fun feeRate(satPerVbyte: Float ): TxBuilder -! - fun drainWallet(): TxBuilder -! - fun drainTo(address: String ): TxBuilder -! - fun enableRbf(): TxBuilder -! - fun enableRbfWithSequence(nsequence: UInt ): TxBuilder -! - @Throws(BdkException::class) - fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction -! - } - - class TxBuilder( - pointer: Pointer - ) : FFIObject(pointer), TxBuilderInterface { - constructor() : - this( - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_new(status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { ---- 1252,1336 ---- - _UniFFILib.INSTANCE.ffi_bdk_360_PartiallySignedBitcoinTransaction_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun serialize(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_serialize(it, status) - } - }.let { - String.lift(it) - } -! - override fun txid(): String = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_PartiallySignedBitcoinTransaction_txid(it, status) - } - }.let { - String.lift(it) - } - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(ptr: Pointer): PartiallySignedBitcoinTransaction { - return PartiallySignedBitcoinTransaction(ptr) - } - - internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong())) - } - -! - } - } - - public interface TxBuilderInterface { -! - fun addRecipient(address: String, amount: ULong ): TxBuilder -! - fun feeRate(satPerVbyte: Float ): TxBuilder -! - fun drainWallet(): TxBuilder -! - fun drainTo(address: String ): TxBuilder -! - fun enableRbf(): TxBuilder -! - fun enableRbfWithSequence(nsequence: UInt ): TxBuilder -! - @Throws(BdkException::class) - fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction -! - } - - class TxBuilder( - pointer: Pointer - ) : FFIObject(pointer), TxBuilderInterface { - constructor() : - this( - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_new(status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { -*************** -*** 1286,1409 **** - _UniFFILib.INSTANCE.ffi_bdk_360_TxBuilder_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun addRecipient(address: String, amount: ULong ): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_add_recipient(it, address.lower(), amount.lower() , status) - } - }.let { - TxBuilder.lift(it) - } -! - override fun feeRate(satPerVbyte: Float ): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_fee_rate(it, satPerVbyte.lower() , status) - } - }.let { - TxBuilder.lift(it) - } -! - override fun drainWallet(): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_drain_wallet(it, status) - } - }.let { - TxBuilder.lift(it) - } -! - override fun drainTo(address: String ): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_drain_to(it, address.lower() , status) - } - }.let { - TxBuilder.lift(it) - } -! - override fun enableRbf(): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_enable_rbf(it, status) - } - }.let { - TxBuilder.lift(it) - } -! - override fun enableRbfWithSequence(nsequence: UInt ): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_enable_rbf_with_sequence(it, nsequence.lower() , status) - } - }.let { - TxBuilder.lift(it) - } -! -! - @Throws(BdkException::class)override fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_finish(it, wallet.lower() , status) - } - }.let { - PartiallySignedBitcoinTransaction.lift(it) - } -- -- - - companion object { - internal fun lift(ptr: Pointer): TxBuilder { - return TxBuilder(ptr) - } - - internal fun read(buf: ByteBuffer): TxBuilder { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return TxBuilder.lift(Pointer(buf.getLong())) - } - -! - } - } - - public interface BumpFeeTxBuilderInterface { -! - fun allowShrinking(address: String ): BumpFeeTxBuilder -! - fun enableRbf(): BumpFeeTxBuilder -! - fun enableRbfWithSequence(nsequence: UInt ): BumpFeeTxBuilder -! - @Throws(BdkException::class) - fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction -! - } - - class BumpFeeTxBuilder( - pointer: Pointer - ) : FFIObject(pointer), BumpFeeTxBuilderInterface { - constructor(txid: String, newFeeRate: Float ) : - this( - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_new(txid.lower(), newFeeRate.lower() ,status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { ---- 1338,1462 ---- - _UniFFILib.INSTANCE.ffi_bdk_360_TxBuilder_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun addRecipient(address: String, amount: ULong ): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_add_recipient(it, address.lower(), amount.lower() , status) - } - }.let { - TxBuilder.lift(it) - } -! - override fun feeRate(satPerVbyte: Float ): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_fee_rate(it, satPerVbyte.lower() , status) - } - }.let { - TxBuilder.lift(it) - } -! - override fun drainWallet(): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_drain_wallet(it, status) - } - }.let { - TxBuilder.lift(it) - } -! - override fun drainTo(address: String ): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_drain_to(it, address.lower() , status) - } - }.let { - TxBuilder.lift(it) - } -! - override fun enableRbf(): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_enable_rbf(it, status) - } - }.let { - TxBuilder.lift(it) - } -! - override fun enableRbfWithSequence(nsequence: UInt ): TxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_enable_rbf_with_sequence(it, nsequence.lower() , status) - } - }.let { - TxBuilder.lift(it) - } -! -! - @Throws(BdkException::class)override fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_TxBuilder_finish(it, wallet.lower() , status) - } - }.let { - PartiallySignedBitcoinTransaction.lift(it) - } - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(ptr: Pointer): TxBuilder { - return TxBuilder(ptr) - } - - internal fun read(buf: ByteBuffer): TxBuilder { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return TxBuilder.lift(Pointer(buf.getLong())) - } - -! - } - } - - public interface BumpFeeTxBuilderInterface { -! - fun allowShrinking(address: String ): BumpFeeTxBuilder -! - fun enableRbf(): BumpFeeTxBuilder -! - fun enableRbfWithSequence(nsequence: UInt ): BumpFeeTxBuilder -! - @Throws(BdkException::class) - fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction -! - } - - class BumpFeeTxBuilder( - pointer: Pointer - ) : FFIObject(pointer), BumpFeeTxBuilderInterface { - constructor(txid: String, newFeeRate: Float ) : - this( - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_new(txid.lower(), newFeeRate.lower() ,status) - }) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { -*************** -*** 1411,1750 **** - _UniFFILib.INSTANCE.ffi_bdk_360_BumpFeeTxBuilder_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun allowShrinking(address: String ): BumpFeeTxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_allow_shrinking(it, address.lower() , status) - } - }.let { - BumpFeeTxBuilder.lift(it) - } -! - override fun enableRbf(): BumpFeeTxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_enable_rbf(it, status) - } - }.let { - BumpFeeTxBuilder.lift(it) - } -! - override fun enableRbfWithSequence(nsequence: UInt ): BumpFeeTxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_enable_rbf_with_sequence(it, nsequence.lower() , status) - } - }.let { - BumpFeeTxBuilder.lift(it) - } -! -! - @Throws(BdkException::class)override fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_finish(it, wallet.lower() , status) - } - }.let { - PartiallySignedBitcoinTransaction.lift(it) - } -- -- - - companion object { - internal fun lift(ptr: Pointer): BumpFeeTxBuilder { - return BumpFeeTxBuilder(ptr) - } - - internal fun read(buf: ByteBuffer): BumpFeeTxBuilder { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return BumpFeeTxBuilder.lift(Pointer(buf.getLong())) - } - -! - } - } - - data class SledDbConfiguration ( -! var path: String, -! var treeName: String - ) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): SledDbConfiguration { - return liftFromRustBuffer(rbuf) { buf -> SledDbConfiguration.read(buf) } - } - - internal fun read(buf: ByteBuffer): SledDbConfiguration { - return SledDbConfiguration( - String.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.path.write(buf) -! - this.treeName.write(buf) -! - } - -! -! - } - - data class SqliteDbConfiguration ( -! var path: String - ) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): SqliteDbConfiguration { - return liftFromRustBuffer(rbuf) { buf -> SqliteDbConfiguration.read(buf) } - } - - internal fun read(buf: ByteBuffer): SqliteDbConfiguration { - return SqliteDbConfiguration( - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.path.write(buf) -! - } - -! -! - } - - data class TransactionDetails ( -! var fee: ULong?, -! var received: ULong, -! var sent: ULong, -! var txid: String - ) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): TransactionDetails { - return liftFromRustBuffer(rbuf) { buf -> TransactionDetails.read(buf) } - } - - internal fun read(buf: ByteBuffer): TransactionDetails { - return TransactionDetails( - readOptionalULong(buf), - ULong.read(buf), - ULong.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - writeOptionalULong(this.fee, buf) -! - this.received.write(buf) -! - this.sent.write(buf) -! - this.txid.write(buf) -! - } - -! -! - } - - data class BlockTime ( -! var height: UInt, -! var timestamp: ULong - ) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): BlockTime { - return liftFromRustBuffer(rbuf) { buf -> BlockTime.read(buf) } - } - - internal fun read(buf: ByteBuffer): BlockTime { - return BlockTime( - UInt.read(buf), - ULong.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.height.write(buf) -! - this.timestamp.write(buf) -! - } - -! -! - } - - data class ElectrumConfig ( -! var url: String, -! var socks5: String?, -! var retry: UByte, -! var timeout: UByte?, -! var stopGap: ULong - ) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): ElectrumConfig { - return liftFromRustBuffer(rbuf) { buf -> ElectrumConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): ElectrumConfig { - return ElectrumConfig( - String.read(buf), - readOptionalString(buf), - UByte.read(buf), - readOptionalUByte(buf), - ULong.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.url.write(buf) -! - writeOptionalString(this.socks5, buf) -! - this.retry.write(buf) -! - writeOptionalUByte(this.timeout, buf) -! - this.stopGap.write(buf) -! - } - -! -! - } - - data class EsploraConfig ( -! var baseUrl: String, -! var proxy: String?, -! var concurrency: UByte?, -! var stopGap: ULong, -! var timeout: ULong? - ) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): EsploraConfig { - return liftFromRustBuffer(rbuf) { buf -> EsploraConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): EsploraConfig { - return EsploraConfig( - String.read(buf), - readOptionalString(buf), - readOptionalUByte(buf), - ULong.read(buf), - readOptionalULong(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.baseUrl.write(buf) -! - writeOptionalString(this.proxy, buf) -! - writeOptionalUByte(this.concurrency, buf) -! - this.stopGap.write(buf) -! - writeOptionalULong(this.timeout, buf) -! - } - -! -! - } - - data class ExtendedKeyInfo ( -! var mnemonic: String, -! var xprv: String, -! var fingerprint: String - ) { - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): ExtendedKeyInfo { - return liftFromRustBuffer(rbuf) { buf -> ExtendedKeyInfo.read(buf) } - } - - internal fun read(buf: ByteBuffer): ExtendedKeyInfo { - return ExtendedKeyInfo( - String.read(buf), - String.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.mnemonic.write(buf) -! - this.xprv.write(buf) -! - this.fingerprint.write(buf) -! - } - -! -! - } - - - - sealed class BdkException(message: String): Exception(message) { - // Each variant is a nested class - // Flat enums carries a string error message, so no special implementation is necessary. - class InvalidU32Bytes(message: String) : BdkException(message) - class Generic(message: String) : BdkException(message) - class ScriptDoesntHaveAddressForm(message: String) : BdkException(message) - class NoRecipients(message: String) : BdkException(message) - class NoUtxosSelected(message: String) : BdkException(message) - class OutputBelowDustLimit(message: String) : BdkException(message) - class InsufficientFunds(message: String) : BdkException(message) - class BnBTotalTriesExceeded(message: String) : BdkException(message) - class BnBNoExactMatch(message: String) : BdkException(message) - class UnknownUtxo(message: String) : BdkException(message) - class TransactionNotFound(message: String) : BdkException(message) - class TransactionConfirmed(message: String) : BdkException(message) - class IrreplaceableTransaction(message: String) : BdkException(message) ---- 1464,1825 ---- - _UniFFILib.INSTANCE.ffi_bdk_360_BumpFeeTxBuilder_object_free(this.pointer, status) - } - } - - internal fun lower(): Pointer = callWithPointer { it } - - internal fun write(buf: RustBufferBuilder) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(this.lower())) - } - - override fun allowShrinking(address: String ): BumpFeeTxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_allow_shrinking(it, address.lower() , status) - } - }.let { - BumpFeeTxBuilder.lift(it) - } -! - override fun enableRbf(): BumpFeeTxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_enable_rbf(it, status) - } - }.let { - BumpFeeTxBuilder.lift(it) - } -! - override fun enableRbfWithSequence(nsequence: UInt ): BumpFeeTxBuilder = - callWithPointer { - rustCall() { status -> - _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_enable_rbf_with_sequence(it, nsequence.lower() , status) - } - }.let { - BumpFeeTxBuilder.lift(it) - } -! -! - @Throws(BdkException::class)override fun finish(wallet: Wallet ): PartiallySignedBitcoinTransaction = - callWithPointer { - rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_360_BumpFeeTxBuilder_finish(it, wallet.lower() , status) - } - }.let { - PartiallySignedBitcoinTransaction.lift(it) - } - -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(ptr: Pointer): BumpFeeTxBuilder { - return BumpFeeTxBuilder(ptr) - } - - internal fun read(buf: ByteBuffer): BumpFeeTxBuilder { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return BumpFeeTxBuilder.lift(Pointer(buf.getLong())) - } - -! - } - } - - data class SledDbConfiguration ( -! var path: String, -! var treeName: String - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): SledDbConfiguration { - return liftFromRustBuffer(rbuf) { buf -> SledDbConfiguration.read(buf) } - } - - internal fun read(buf: ByteBuffer): SledDbConfiguration { - return SledDbConfiguration( - String.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.path.write(buf) -! - this.treeName.write(buf) -! - } - -! -! - } - - data class SqliteDbConfiguration ( -! var path: String - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): SqliteDbConfiguration { - return liftFromRustBuffer(rbuf) { buf -> SqliteDbConfiguration.read(buf) } - } - - internal fun read(buf: ByteBuffer): SqliteDbConfiguration { - return SqliteDbConfiguration( - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.path.write(buf) -! - } - -! -! - } - - data class TransactionDetails ( -! var fee: ULong?, -! var received: ULong, -! var sent: ULong, -! var txid: String - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): TransactionDetails { - return liftFromRustBuffer(rbuf) { buf -> TransactionDetails.read(buf) } - } - - internal fun read(buf: ByteBuffer): TransactionDetails { - return TransactionDetails( - readOptionalULong(buf), - ULong.read(buf), - ULong.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - writeOptionalULong(this.fee, buf) -! - this.received.write(buf) -! - this.sent.write(buf) -! - this.txid.write(buf) -! - } - -! -! - } - - data class BlockTime ( -! var height: UInt, -! var timestamp: ULong - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): BlockTime { - return liftFromRustBuffer(rbuf) { buf -> BlockTime.read(buf) } - } - - internal fun read(buf: ByteBuffer): BlockTime { - return BlockTime( - UInt.read(buf), - ULong.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.height.write(buf) -! - this.timestamp.write(buf) -! - } - -! -! - } - - data class ElectrumConfig ( -! var url: String, -! var socks5: String?, -! var retry: UByte, -! var timeout: UByte?, -! var stopGap: ULong - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): ElectrumConfig { - return liftFromRustBuffer(rbuf) { buf -> ElectrumConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): ElectrumConfig { - return ElectrumConfig( - String.read(buf), - readOptionalString(buf), - UByte.read(buf), - readOptionalUByte(buf), - ULong.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.url.write(buf) -! - writeOptionalString(this.socks5, buf) -! - this.retry.write(buf) -! - writeOptionalUByte(this.timeout, buf) -! - this.stopGap.write(buf) -! - } - -! -! - } - - data class EsploraConfig ( -! var baseUrl: String, -! var proxy: String?, -! var concurrency: UByte?, -! var stopGap: ULong, -! var timeout: ULong? - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): EsploraConfig { - return liftFromRustBuffer(rbuf) { buf -> EsploraConfig.read(buf) } - } - - internal fun read(buf: ByteBuffer): EsploraConfig { - return EsploraConfig( - String.read(buf), - readOptionalString(buf), - readOptionalUByte(buf), - ULong.read(buf), - readOptionalULong(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.baseUrl.write(buf) -! - writeOptionalString(this.proxy, buf) -! - writeOptionalUByte(this.concurrency, buf) -! - this.stopGap.write(buf) -! - writeOptionalULong(this.timeout, buf) -! - } - -! -! - } - - data class ExtendedKeyInfo ( -! var mnemonic: String, -! var xprv: String, -! var fingerprint: String - ) { -+ /** -+ * @suppress -+ */ - companion object { - internal fun lift(rbuf: RustBuffer.ByValue): ExtendedKeyInfo { - return liftFromRustBuffer(rbuf) { buf -> ExtendedKeyInfo.read(buf) } - } - - internal fun read(buf: ByteBuffer): ExtendedKeyInfo { - return ExtendedKeyInfo( - String.read(buf), - String.read(buf), - String.read(buf) - ) - } - } - - internal fun lower(): RustBuffer.ByValue { - return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) - } - - internal fun write(buf: RustBufferBuilder) { - this.mnemonic.write(buf) -! - this.xprv.write(buf) -! - this.fingerprint.write(buf) -! - } - -! -! - } - - - - sealed class BdkException(message: String): Exception(message) { - // Each variant is a nested class - // Flat enums carries a string error message, so no special implementation is necessary. - class InvalidU32Bytes(message: String) : BdkException(message) - class Generic(message: String) : BdkException(message) - class ScriptDoesntHaveAddressForm(message: String) : BdkException(message) - class NoRecipients(message: String) : BdkException(message) - class NoUtxosSelected(message: String) : BdkException(message) - class OutputBelowDustLimit(message: String) : BdkException(message) - class InsufficientFunds(message: String) : BdkException(message) - class BnBTotalTriesExceeded(message: String) : BdkException(message) - class BnBNoExactMatch(message: String) : BdkException(message) - class UnknownUtxo(message: String) : BdkException(message) - class TransactionNotFound(message: String) : BdkException(message) - class TransactionConfirmed(message: String) : BdkException(message) - class IrreplaceableTransaction(message: String) : BdkException(message) -*************** -*** 1758,1806 **** - class InvalidPolicyPathException(message: String) : BdkException(message) - class Signer(message: String) : BdkException(message) - class InvalidNetwork(message: String) : BdkException(message) - class InvalidProgressValue(message: String) : BdkException(message) - class ProgressUpdateException(message: String) : BdkException(message) - class InvalidOutpoint(message: String) : BdkException(message) - class Descriptor(message: String) : BdkException(message) - class AddressValidator(message: String) : BdkException(message) - class Encode(message: String) : BdkException(message) - class Miniscript(message: String) : BdkException(message) - class Bip32(message: String) : BdkException(message) - class Secp256k1(message: String) : BdkException(message) - class Json(message: String) : BdkException(message) - class Hex(message: String) : BdkException(message) - class Psbt(message: String) : BdkException(message) - class PsbtParse(message: String) : BdkException(message) - class Electrum(message: String) : BdkException(message) - class Esplora(message: String) : BdkException(message) - class Sled(message: String) : BdkException(message) - class Rusqlite(message: String) : BdkException(message) -- - - companion object ErrorHandler : CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): BdkException { - return liftFromRustBuffer(error_buf) { error_buf -> read(error_buf) } - } - - fun read(error_buf: ByteBuffer): BdkException { -! - return when(error_buf.getInt()) { - 1 -> BdkException.InvalidU32Bytes(String.read(error_buf)) - 2 -> BdkException.Generic(String.read(error_buf)) - 3 -> BdkException.ScriptDoesntHaveAddressForm(String.read(error_buf)) - 4 -> BdkException.NoRecipients(String.read(error_buf)) - 5 -> BdkException.NoUtxosSelected(String.read(error_buf)) - 6 -> BdkException.OutputBelowDustLimit(String.read(error_buf)) - 7 -> BdkException.InsufficientFunds(String.read(error_buf)) - 8 -> BdkException.BnBTotalTriesExceeded(String.read(error_buf)) - 9 -> BdkException.BnBNoExactMatch(String.read(error_buf)) - 10 -> BdkException.UnknownUtxo(String.read(error_buf)) - 11 -> BdkException.TransactionNotFound(String.read(error_buf)) - 12 -> BdkException.TransactionConfirmed(String.read(error_buf)) - 13 -> BdkException.IrreplaceableTransaction(String.read(error_buf)) - 14 -> BdkException.FeeRateTooLow(String.read(error_buf)) - 15 -> BdkException.FeeTooLow(String.read(error_buf)) - 16 -> BdkException.FeeRateUnavailable(String.read(error_buf)) - 17 -> BdkException.MissingKeyOrigin(String.read(error_buf)) - 18 -> BdkException.Key(String.read(error_buf)) - 19 -> BdkException.ChecksumMismatch(String.read(error_buf)) ---- 1833,1884 ---- - class InvalidPolicyPathException(message: String) : BdkException(message) - class Signer(message: String) : BdkException(message) - class InvalidNetwork(message: String) : BdkException(message) - class InvalidProgressValue(message: String) : BdkException(message) - class ProgressUpdateException(message: String) : BdkException(message) - class InvalidOutpoint(message: String) : BdkException(message) - class Descriptor(message: String) : BdkException(message) - class AddressValidator(message: String) : BdkException(message) - class Encode(message: String) : BdkException(message) - class Miniscript(message: String) : BdkException(message) - class Bip32(message: String) : BdkException(message) - class Secp256k1(message: String) : BdkException(message) - class Json(message: String) : BdkException(message) - class Hex(message: String) : BdkException(message) - class Psbt(message: String) : BdkException(message) - class PsbtParse(message: String) : BdkException(message) - class Electrum(message: String) : BdkException(message) - class Esplora(message: String) : BdkException(message) - class Sled(message: String) : BdkException(message) - class Rusqlite(message: String) : BdkException(message) - -+ -+ /** -+ * @suppress -+ */ - companion object ErrorHandler : CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): BdkException { - return liftFromRustBuffer(error_buf) { error_buf -> read(error_buf) } - } - - fun read(error_buf: ByteBuffer): BdkException { -! - return when(error_buf.getInt()) { - 1 -> BdkException.InvalidU32Bytes(String.read(error_buf)) - 2 -> BdkException.Generic(String.read(error_buf)) - 3 -> BdkException.ScriptDoesntHaveAddressForm(String.read(error_buf)) - 4 -> BdkException.NoRecipients(String.read(error_buf)) - 5 -> BdkException.NoUtxosSelected(String.read(error_buf)) - 6 -> BdkException.OutputBelowDustLimit(String.read(error_buf)) - 7 -> BdkException.InsufficientFunds(String.read(error_buf)) - 8 -> BdkException.BnBTotalTriesExceeded(String.read(error_buf)) - 9 -> BdkException.BnBNoExactMatch(String.read(error_buf)) - 10 -> BdkException.UnknownUtxo(String.read(error_buf)) - 11 -> BdkException.TransactionNotFound(String.read(error_buf)) - 12 -> BdkException.TransactionConfirmed(String.read(error_buf)) - 13 -> BdkException.IrreplaceableTransaction(String.read(error_buf)) - 14 -> BdkException.FeeRateTooLow(String.read(error_buf)) - 15 -> BdkException.FeeTooLow(String.read(error_buf)) - 16 -> BdkException.FeeRateUnavailable(String.read(error_buf)) - 17 -> BdkException.MissingKeyOrigin(String.read(error_buf)) - 18 -> BdkException.Key(String.read(error_buf)) - 19 -> BdkException.ChecksumMismatch(String.read(error_buf)) -*************** -*** 1813,1911 **** - 26 -> BdkException.InvalidOutpoint(String.read(error_buf)) - 27 -> BdkException.Descriptor(String.read(error_buf)) - 28 -> BdkException.AddressValidator(String.read(error_buf)) - 29 -> BdkException.Encode(String.read(error_buf)) - 30 -> BdkException.Miniscript(String.read(error_buf)) - 31 -> BdkException.Bip32(String.read(error_buf)) - 32 -> BdkException.Secp256k1(String.read(error_buf)) - 33 -> BdkException.Json(String.read(error_buf)) - 34 -> BdkException.Hex(String.read(error_buf)) - 35 -> BdkException.Psbt(String.read(error_buf)) - 36 -> BdkException.PsbtParse(String.read(error_buf)) - 37 -> BdkException.Electrum(String.read(error_buf)) - 38 -> BdkException.Esplora(String.read(error_buf)) - 39 -> BdkException.Sled(String.read(error_buf)) - 40 -> BdkException.Rusqlite(String.read(error_buf)) - else -> throw RuntimeException("invalid error enum value, something is very wrong!!") - } - } - } - -! -! - } - - - // Declaration and FfiConverters for Progress Callback Interface - - public interface Progress { - fun update(progress: Float, message: String? ) -! - } - - // The ForeignCallback that is passed to Rust. - internal class ForeignCallbackCallbackInterfaceProgress : ForeignCallback { - @Suppress("TooGenericExceptionCaught") - override fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int { - val cb = FfiConverterCallbackInterfaceProgress.lift(handle) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") - return when (method) { - IDX_CALLBACK_FREE -> { - FfiConverterCallbackInterfaceProgress.drop(handle) - // No return value. - // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` - 0 - } - 1 -> { - val buffer = this.invokeUpdate(cb, args) - outBuf.setValue(buffer) - // Value written to out buffer. - // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` - 1 - } -! - // This should never happen, because an out of bounds method index won't - // ever be used. Once we can catch errors, we should return an InternalException. - // https://github.com/mozilla/uniffi-rs/issues/351 - else -> { - // An unexpected error happened. - // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` - -1 - } - } - } - -! - private fun invokeUpdate(kotlinCallbackInterface: Progress, args: RustBuffer.ByValue): RustBuffer.ByValue = - try { - val buf = args.asByteBuffer() ?: throw InternalException("No ByteBuffer in RustBuffer; this is a Uniffi bug") - kotlinCallbackInterface.update( -! Float.read(buf), -! readOptionalString(buf) - ) - .let { RustBuffer.ByValue() } - // TODO catch errors and report them back to Rust. - // https://github.com/mozilla/uniffi-rs/issues/351 - } finally { - RustBuffer.free(args) - } - -! - } - - // The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. - internal object FfiConverterCallbackInterfaceProgress: FfiConverterCallbackInterface( - foreignCallback = ForeignCallbackCallbackInterfaceProgress() - ) { - override fun register(lib: _UniFFILib) { - rustCall() { status -> - lib.ffi_bdk_360_Progress_init_callback(this.foreignCallback, status) - } - } - } - internal fun UByte.Companion.lift(v: Byte): UByte { - return v.toUByte() - } - - internal fun UByte.Companion.read(buf: ByteBuffer): UByte { - return UByte.lift(buf.get()) - } - ---- 1891,1989 ---- - 26 -> BdkException.InvalidOutpoint(String.read(error_buf)) - 27 -> BdkException.Descriptor(String.read(error_buf)) - 28 -> BdkException.AddressValidator(String.read(error_buf)) - 29 -> BdkException.Encode(String.read(error_buf)) - 30 -> BdkException.Miniscript(String.read(error_buf)) - 31 -> BdkException.Bip32(String.read(error_buf)) - 32 -> BdkException.Secp256k1(String.read(error_buf)) - 33 -> BdkException.Json(String.read(error_buf)) - 34 -> BdkException.Hex(String.read(error_buf)) - 35 -> BdkException.Psbt(String.read(error_buf)) - 36 -> BdkException.PsbtParse(String.read(error_buf)) - 37 -> BdkException.Electrum(String.read(error_buf)) - 38 -> BdkException.Esplora(String.read(error_buf)) - 39 -> BdkException.Sled(String.read(error_buf)) - 40 -> BdkException.Rusqlite(String.read(error_buf)) - else -> throw RuntimeException("invalid error enum value, something is very wrong!!") - } - } - } - -! -! - } - - - // Declaration and FfiConverters for Progress Callback Interface - - public interface Progress { - fun update(progress: Float, message: String? ) -! - } - - // The ForeignCallback that is passed to Rust. - internal class ForeignCallbackCallbackInterfaceProgress : ForeignCallback { - @Suppress("TooGenericExceptionCaught") - override fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int { - val cb = FfiConverterCallbackInterfaceProgress.lift(handle) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") - return when (method) { - IDX_CALLBACK_FREE -> { - FfiConverterCallbackInterfaceProgress.drop(handle) - // No return value. - // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` - 0 - } - 1 -> { - val buffer = this.invokeUpdate(cb, args) - outBuf.setValue(buffer) - // Value written to out buffer. - // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` - 1 - } -! - // This should never happen, because an out of bounds method index won't - // ever be used. Once we can catch errors, we should return an InternalException. - // https://github.com/mozilla/uniffi-rs/issues/351 - else -> { - // An unexpected error happened. - // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` - -1 - } - } - } - -! - private fun invokeUpdate(kotlinCallbackInterface: Progress, args: RustBuffer.ByValue): RustBuffer.ByValue = - try { - val buf = args.asByteBuffer() ?: throw InternalException("No ByteBuffer in RustBuffer; this is a Uniffi bug") - kotlinCallbackInterface.update( -! Float.read(buf), -! readOptionalString(buf) - ) - .let { RustBuffer.ByValue() } - // TODO catch errors and report them back to Rust. - // https://github.com/mozilla/uniffi-rs/issues/351 - } finally { - RustBuffer.free(args) - } - -! - } - - // The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. - internal object FfiConverterCallbackInterfaceProgress: FfiConverterCallbackInterface( - foreignCallback = ForeignCallbackCallbackInterfaceProgress() - ) { - override fun register(lib: _UniFFILib) { - rustCall() { status -> - lib.ffi_bdk_360_Progress_init_callback(this.foreignCallback, status) - } - } - } - internal fun UByte.Companion.lift(v: Byte): UByte { - return v.toUByte() - } - - internal fun UByte.Companion.read(buf: ByteBuffer): UByte { - return UByte.lift(buf.get()) - } - From a671c4f86b464975ac5dd8c4c85215f4bb8ec35d Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 16 Sep 2022 07:10:19 -0400 Subject: [PATCH 256/272] Move samples into tests --- .gitignore | 4 +- api-docs/build.gradle.kts | 2 +- .../main/kotlin/org/bitcoindevkit/Samples.kt | 13 ------ .../src/main/kotlin/org/bitcoindevkit/bdk.kt | 34 ++++++++++++-- .../test/kotlin/org/bitcoindevkit/Samples.kt | 44 +++++++++++++++++++ 5 files changed, 77 insertions(+), 20 deletions(-) delete mode 100644 api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt create mode 100644 api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt diff --git a/.gitignore b/.gitignore index 2e06997..7b20211 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,5 @@ xcuserdata .lsp .clj-kondo .idea -android/src/main/kotlin/org/bitcoindevkit/bdk.kt -jvm/src/main/kotlin/org/bitcoindevkit/bdk.kt +bdk-android/lib/src/main/kotlin/org/bitcoindevkit/bdk.kt +bdk-jvm/lib/src/main/kotlin/org/bitcoindevkit/bdk.kt diff --git a/api-docs/build.gradle.kts b/api-docs/build.gradle.kts index 9394b59..73fb6cf 100644 --- a/api-docs/build.gradle.kts +++ b/api-docs/build.gradle.kts @@ -29,7 +29,7 @@ tasks.withType().configureEach { moduleName.set("bdk-android") moduleVersion.set("0.9.0") includes.from("Module.md") - samples.from("src/main/kotlin/org/bitcoindevkit/Samples.kt") + samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt") } } } diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt deleted file mode 100644 index 012d5cd..0000000 --- a/api-docs/src/main/kotlin/org/bitcoindevkit/Samples.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.bitcoindevkit - -class Samples { - val blockchainConfig: BlockchainConfig = BlockchainConfig.Electrum( - ElectrumConfig( - url = "ssl://electrum.blockstream.info:60002", - socks5 = null, - retry = 5u, - timeout = null, - stopGap = 10u - ) - ) -} diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt index 8cf2cb9..12ce386 100644 --- a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt +++ b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt @@ -37,14 +37,40 @@ enum class AddressIndex { /** Return a new address after incrementing the current descriptor index. */ NEW, - /** - * Return the address for the current descriptor index if it has not been used in a received transaction. Otherwise return a new address as with `AddressIndex.NEW`. Use with caution, if the wallet has not yet detected an address has been used it could return an already used address. This function is primarily meant for situations where the caller is untrusted; for example when deriving donation addresses on-demand for a public web page. - */ + /** Return the address for the current descriptor index if it has not been used in a received transaction. Otherwise return a new address as with `AddressIndex.NEW`. Use with caution, if the wallet has not yet detected an address has been used it could return an already used address. This function is primarily meant for situations where the caller is untrusted; for example when deriving donation addresses on-demand for a public web page. */ LAST_UNUSED, } +/** + * Balance differentiated in various categories + * + * @sample org.bitcoindevkit.balanceSample + */ +data class Balance ( + /** All coinbase outputs not yet matured. */ + var immature: ULong, + + /** Unconfirmed UTXOs generated by a wallet tx. */ + var trustedPending: ULong, + + /** Unconfirmed UTXOs received from an external wallet. */ + var untrustedPending: ULong, + + /** Confirmed and immediately spendable balance. */ + var confirmed: ULong, + + /** The sum of trustedPending and confirmed coins. */ + var spendable: ULong, + + /** The whole balance visible to the wallet. */ + var total: ULong +) + /** * Type that can contain any of the database configurations defined by the library. + * + * @sample org.bitcoindevkit.memoryDatabaseConfigExample + * @sample org.bitcoindevkit.sqliteDatabaseConfigExample */ sealed class DatabaseConfig { /** Configuration for an in-memory database */ @@ -79,7 +105,7 @@ data class SledDbConfiguration( /** * Configuration for an Electrum blockchain. * - * @sample org.bitcoindevkit.Samples.blockchainConfig + * @sample org.bitcoindevkit.electrumBlockchainConfigExample */ data class ElectrumConfig ( /** URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port, e.g. `ssl://electrum.blockstream.info:60002`. */ diff --git a/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt b/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt new file mode 100644 index 0000000..e2f5fd8 --- /dev/null +++ b/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt @@ -0,0 +1,44 @@ +package org.bitcoindevkit + +fun balanceSample() { + object LogProgress : Progress { + override fun update(progress: Float, message: String?) {} + } + + val memoryDatabaseConfig = DatabaseConfig.Memory + private val blockchainConfig = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 200u + ) + ) + val wallet = Wallet(descriptor, null, Network.TESTNET, memoryDatabaseConfig) + val blockchain = Blockchain(blockchainConfig) + wallet.sync(blockchain, LogProgress) + + val balance: Balance = wallet.getBalance() + println("Total wallet balance is ${balance.total}") +} + +fun electrumBlockchainConfigExample() { + val blockchainConfig = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 200u + ) + ) +} + +fun memoryDatabaseConfigExample() { + val memoryDatabaseConfig = DatabaseConfig.Memory +} + +fun sqliteDatabaseConfigExample() { + val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration("bdk-sqlite")) +} From 3e96aad10e6454bd5fcbe0f61a0941cc7683f8c6 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 21 Sep 2022 09:01:51 -0400 Subject: [PATCH 257/272] Use idiomatic Kotlin/Java documentation and KDoc structure --- .../src/main/kotlin/org/bitcoindevkit/bdk.kt | 247 +++++++++++------- 1 file changed, 153 insertions(+), 94 deletions(-) diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt index 12ce386..e1b89c2 100644 --- a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt +++ b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt @@ -4,27 +4,27 @@ package org.bitcoindevkit * The cryptocurrency to act on. */ enum class Network { - /** Bitcoin's mainnet */ + /** Bitcoin's mainnet. */ BITCOIN, - /** Bitcoin’s testnet */ + /** Bitcoin’s testnet. */ TESTNET, - /** Bitcoin’s signet */ + /** Bitcoin’s signet. */ SIGNET, - /** Bitcoin’s regtest */ + /** Bitcoin’s regtest. */ REGTEST, } /** * A derived address and the index it was found at. + * + * @property index Child index of this address. + * @property address Address. */ data class AddressInfo ( - /** Child index of this address. */ var index: UInt, - - /** Address. */ var address: String ) @@ -37,32 +37,33 @@ enum class AddressIndex { /** Return a new address after incrementing the current descriptor index. */ NEW, - /** Return the address for the current descriptor index if it has not been used in a received transaction. Otherwise return a new address as with `AddressIndex.NEW`. Use with caution, if the wallet has not yet detected an address has been used it could return an already used address. This function is primarily meant for situations where the caller is untrusted; for example when deriving donation addresses on-demand for a public web page. */ + /** Return the address for the current descriptor index if it has not been used in a received transaction. + * Otherwise return a new address as with `AddressIndex.NEW`. Use with caution, if the wallet + * has not yet detected an address has been used it could return an already used address. + * This function is primarily meant for situations where the caller is untrusted; + * for example when deriving donation addresses on-demand for a public web page. + */ LAST_UNUSED, } /** - * Balance differentiated in various categories + * Balance differentiated in various categories. + * + * @property immature All coinbase outputs not yet matured. + * @property trustedPending Unconfirmed UTXOs generated by a wallet tx. + * @property untrustedPending Unconfirmed UTXOs received from an external wallet. + * @property confirmed Confirmed and immediately spendable balance. + * @property spendable The sum of trustedPending and confirmed coins. + * @property total The whole balance visible to the wallet. * * @sample org.bitcoindevkit.balanceSample */ data class Balance ( - /** All coinbase outputs not yet matured. */ var immature: ULong, - - /** Unconfirmed UTXOs generated by a wallet tx. */ var trustedPending: ULong, - - /** Unconfirmed UTXOs received from an external wallet. */ var untrustedPending: ULong, - - /** Confirmed and immediately spendable balance. */ var confirmed: ULong, - - /** The sum of trustedPending and confirmed coins. */ var spendable: ULong, - - /** The whole balance visible to the wallet. */ var total: ULong ) @@ -73,112 +74,106 @@ data class Balance ( * @sample org.bitcoindevkit.sqliteDatabaseConfigExample */ sealed class DatabaseConfig { - /** Configuration for an in-memory database */ + /** Configuration for an in-memory database. */ object Memory : DatabaseConfig() - /** Configuration for a Sled database */ + /** Configuration for a Sled database. */ data class Sled(val config: SledDbConfiguration) : DatabaseConfig() - /** Configuration for a SQLite database */ + /** Configuration for a SQLite database. */ data class Sqlite(val config: SqliteDbConfiguration) : DatabaseConfig() } /** * Configuration type for a SQLite database. + * + * @property path Main directory of the DB. */ data class SqliteDbConfiguration( - /** Main directory of the db */ var path: String, ) /** * Configuration type for a SledDB database. + * + * @property path Main directory of the DB. + * @property treeName Name of the database tree, a separated namespace for the data. */ data class SledDbConfiguration( - /** Main directory of the db */ var path: String, - - /** Name of the database tree, a separated namespace for the data */ var treeName: String, ) /** * Configuration for an Electrum blockchain. * + * @property url URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port, e.g. `ssl://electrum.blockstream.info:60002`. + * @property socks5 URL of the socks5 proxy server or a Tor service. + * @property retry Request retry count. + * @property timeout Request timeout (seconds). + * @property stopGap Stop searching addresses for transactions after finding an unused gap of this length. + * * @sample org.bitcoindevkit.electrumBlockchainConfigExample */ data class ElectrumConfig ( - /** URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port, e.g. `ssl://electrum.blockstream.info:60002`. */ var url: String, - - /** URL of the socks5 proxy server or a Tor service. */ var socks5: String?, - - /** Request retry count. */ var retry: UByte, - - /** Request timeout (seconds). */ var timeout: UByte?, - - /** Stop searching addresses for transactions after finding an unused gap of this length. */ var stopGap: ULong ) /** * Configuration for an Esplora blockchain. + * + * @property baseUrl Base URL of the esplora service, e.g. `https://blockstream.info/api/`. + * @property proxy Optional URL of the proxy to use to make requests to the Esplora server. + * @property concurrency Number of parallel requests sent to the esplora service (default: 4). + * @property stopGap Stop searching addresses for transactions after finding an unused gap of this length. + * @property timeout Socket timeout. */ data class EsploraConfig ( - /** Base URL of the esplora service, e.g. `https://blockstream.info/api/`. */ var baseUrl: String, - - /** Optional URL of the proxy to use to make requests to the Esplora server. */ var proxy: String?, - - /** Number of parallel requests sent to the esplora service (default: 4). */ var concurrency: UByte?, - - /** Stop searching addresses for transactions after finding an unused gap of this length. */ var stopGap: ULong, - - /** Socket timeout. */ var timeout: ULong? ) /** * Type that can contain any of the blockchain configurations defined by the library. - * - * @sample org.bitcoindevkit.Samples.blockchainConfig */ sealed class BlockchainConfig { - /** Electrum client */ + /** Electrum client. */ data class Electrum(val config: ElectrumConfig) : BlockchainConfig() - /** Esplora client */ + /** Esplora client. */ data class Esplora(val config: EsploraConfig) : BlockchainConfig() } /** * A wallet transaction. + * + * @property fee Fee value (sats) if available. The availability of the fee depends on the backend. It’s never None with an Electrum server backend, but it could be None with a Bitcoin RPC node without txindex that receive funds while offline. + * @property received Received value (sats) Sum of owned outputs of this transaction. + * @property sent Sent value (sats) Sum of owned inputs of this transaction. + * @property txid Transaction id. + * @property confirmationTime If the transaction is confirmed, [BlockTime] contains height and timestamp of the block containing the transaction. This property is null for unconfirmed transactions. */ data class TransactionDetails ( - /** Fee value (sats) if available. The availability of the fee depends on the backend. It’s never None with an Electrum server backend, but it could be None with a Bitcoin RPC node without txindex that receive funds while offline. */ var fee: ULong?, - - /** Received value (sats) Sum of owned outputs of this transaction. */ var received: ULong, - - /** Sent value (sats) Sum of owned inputs of this transaction. */ var sent: ULong, - - /** Transaction id. */ var txid: String - - /** If the transaction is confirmed, [BlockTime] contains height and timestamp of the block containing the transaction. This property is null for unconfirmed transactions. */ var confirmationTime: BlockTime? ) /** * A blockchain backend. + * + * @constructor Create the new blockchain client. + * + * @param config The blockchain configuration required. */ class Blockchain( config: BlockchainConfig @@ -195,6 +190,10 @@ class Blockchain( /** * A partially signed bitcoin transaction. + * + * @constructor Build a new Partially Signed Bitcoin Transaction. + * + * @param psbtBase64 The PSBT in base64 format. */ class PartiallySignedBitcoinTransaction(psbtBase64: String) { /** Return the PSBT in string format, using a base64 encoding. */ @@ -206,40 +205,38 @@ class PartiallySignedBitcoinTransaction(psbtBase64: String) { /** * A reference to a transaction output. + * + * @property txid The referenced transaction’s txid. + * @property vout The index of the referenced output in its transaction’s vout. */ data class OutPoint ( - /** The referenced transaction’s txid. */ var txid: String, - - /** The index of the referenced output in its transaction’s vout. */ var vout: UInt ) /** * A transaction output, which defines new coins to be created from old ones. + * + * @property value The value of the output, in satoshis. + * @property address The address of the output. */ data class TxOut ( - /** The value of the output, in satoshis. */ var value: ULong, - - /** The address of the output. */ var address: String ) /** * An unspent output owned by a [Wallet]. + * + * @property outpoint Reference to a transaction output. + * @property txout Transaction output. + * @property keychain Type of keychain. + * @property isSpent Whether this UTXO is spent or not. */ data class LocalUtxo ( - /** Reference to a transaction output. */ var outpoint: OutPoint, - - /** Transaction output. */ var txout: TxOut, - - /** Type of keychain. */ var keychain: KeychainKind, - - /** Whether this UTXO is spent or not. */ var isSpent: Boolean ) @@ -247,7 +244,7 @@ data class LocalUtxo ( * Types of keychains. */ enum class KeychainKind { - /** External */ + /** External. */ EXTERNAL, /** Internal, usually used for change outputs. */ @@ -256,21 +253,32 @@ enum class KeychainKind { /** * Block height and timestamp of a block. + * + * @property height Confirmation block height. + * @property timestamp Confirmation block timestamp. */ data class BlockTime ( - /** confirmation block height */ var height: UInt, - - /** confirmation block timestamp */ var timestamp: ULong, ) +/** + + */ + /** * A Bitcoin wallet. * The Wallet acts as a way of coherently interfacing with output descriptors and related transactions. Its main components are: * 1. Output descriptors from which it can derive addresses. * 2. A Database where it tracks transactions and utxos related to the descriptors. * 3. Signers that can contribute signatures to addresses instantiated from the descriptors. + * + * @constructor Create a BDK wallet. + * + * @param descriptor The main (or "external") descriptor. + * @param changeDescriptor The change (or "internal") descriptor. + * @param network The network to act on. + * @param databaseConfig The database configuration. */ class Wallet( descriptor: String, @@ -278,7 +286,11 @@ class Wallet( network: Network, databaseConfig: DatabaseConfig, ) { - /** Return a derived address using the external descriptor, see [AddressIndex] for available address index selection strategies. If none of the keys in the descriptor are derivable (i.e. the descriptor does not end with a * character) then the same address will always be returned for any [AddressIndex]. */ + /** + * Return a derived address using the external descriptor, see [AddressIndex] for available address index + * selection strategies. If none of the keys in the descriptor are derivable (i.e. the descriptor does not end + * with a * character) then the same address will always be returned for any [AddressIndex]. + */ fun getAddress(addressIndex: AddressIndex): AddressInfo {} /** Return the balance, meaning the sum of this wallet’s unspent outputs’ values. Note that this method only operates on the internal database, which first needs to be [Wallet.sync] manually. */ @@ -331,7 +343,12 @@ class TxBuilder() { /** Add an outpoint to the internal list of UTXOs that must be spent. These have priority over the "unspendable" utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent. */ fun addUtxo(outpoint: OutPoint): TxBuilder {} - /** Add the list of outpoints to the internal list of UTXOs that must be spent. If an error occurs while adding any of the UTXOs then none of them are added and the error is returned. These have priority over the "unspendable" utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent. */ + /** + * Add the list of outpoints to the internal list of UTXOs that must be spent. If an error + * occurs while adding any of the UTXOs then none of them are added and the error is returned. + * These have priority over the "unspendable" utxos, meaning that if a utxo is present both + * in the "utxos" and the "unspendable" list, it will be spent. + */ fun addUtxos(outpoints: List): TxBuilder {} /** Do not spend change outputs. This effectively adds all the change outputs to the "unspendable" list. See [TxBuilder.unspendable]. */ @@ -343,7 +360,10 @@ class TxBuilder() { /** Only spend change outputs. This effectively adds all the non-change outputs to the "unspendable" list. See [TxBuilder.unspendable]. */ fun onlySpendChange(): TxBuilder {} - /** Replace the internal list of unspendable utxos with a new list. It’s important to note that the "must-be-spent" utxos added with [TxBuilder.addUtxo] have priority over these. See the Rust docs of the two linked methods for more details. */ + /** + * Replace the internal list of unspendable utxos with a new list. It’s important to note that the "must-be-spent" utxos + * added with [TxBuilder.addUtxo] have priority over these. See the Rust docs of the two linked methods for more details. + */ fun unspendable(unspendable: List): TxBuilder {} /** Set a custom fee rate. */ @@ -355,13 +375,27 @@ class TxBuilder() { /** Spend all the available inputs. This respects filters like [TxBuilder.unspendable] and the change policy. */ fun drainWallet(): TxBuilder {} - /** Sets the address to drain excess coins to. Usually, when there are excess coins they are sent to a change address generated by the wallet. This option replaces the usual change address with an arbitrary ScriptPubKey of your choosing. Just as with a change output, if the drain output is not needed (the excess coins are too small) it will not be included in the resulting transaction. The only difference is that it is valid to use [drainTo] without setting any ordinary recipients with [addRecipient] (but it is perfectly fine to add recipients as well). If you choose not to set any recipients, you should either provide the utxos that the transaction should spend via [addUtxos], or set [drainWallet] to spend all of them. When bumping the fees of a transaction made with this option, you probably want to use [BumpFeeTxBuilder.allowShrinking] to allow this output to be reduced to pay for the extra fees. */ + /** + * Sets the address to drain excess coins to. Usually, when there are excess coins they are + * sent to a change address generated by the wallet. This option replaces the usual change address + * with an arbitrary ScriptPubKey of your choosing. Just as with a change output, if the + * drain output is not needed (the excess coins are too small) it will not be included in the resulting + * transaction. The only difference is that it is valid to use [drainTo] without setting any ordinary recipients + * with [addRecipient] (but it is perfectly fine to add recipients as well). If you choose not to set any + * recipients, you should either provide the utxos that the transaction should spend via [addUtxos], or set + * [drainWallet] to spend all of them. When bumping the fees of a transaction made with this option, + * you probably want to use [BumpFeeTxBuilder.allowShrinking] to allow this output to be reduced to pay for the extra fees. + */ fun drainTo(address: String): TxBuilder {} /** Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`. */ fun enableRbf(): TxBuilder {} - /** Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not be a valid nSequence to signal RBF. */ + /** + * Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors + * contain an "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence` + * is higher than `0xFFFFFFFD` an error will be thrown, since it would not be a valid nSequence to signal RBF. + */ fun enableRbfWithSequence(nsequence: UInt): TxBuilder {} /** Finish building the transaction. Returns the BIP174 PSBT. */ @@ -370,26 +404,36 @@ class TxBuilder() { /** * A object holding an address and an amount. + * + * @property address The address. + * @property amount The amount. */ data class AddressAmount ( - /** The address. */ var address: String, - - /** The amount. */ var amount: ULong -) {} +) /** * The BumpFeeTxBuilder is used to bump the fee on a transaction that has been broadcast and has its RBF flag set to true. */ class BumpFeeTxBuilder() { - /** Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this script_pubkey in order to bump the transaction fee. Without specifying this the wallet will attempt to find a change output to shrink instead. Note that the output may shrink to below the dust limit and therefore be removed. If it is preserved then it is currently not guaranteed to be in the same position as it was originally. Returns an error if script_pubkey can’t be found among the recipients of the transaction we are bumping. */ + /** + * Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this scriptPubKey + * in order to bump the transaction fee. Without specifying this the wallet will attempt to find a change output + * to shrink instead. Note that the output may shrink to below the dust limit and therefore be removed. If it is + * preserved then it is currently not guaranteed to be in the same position as it was originally. Returns an error + * if scriptPubkey can’t be found among the recipients of the transaction we are bumping. + */ fun allowShrinking(address: String): BumpFeeTxBuilder {} /** Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`. */ fun enableRbf(): BumpFeeTxBuilder {} - /** Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not be a valid nSequence to signal RBF. */ + /** + * Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors + * contain an "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence` + * is higher than `0xFFFFFFFD` an error will be thrown, since it would not be a valid nSequence to signal RBF. + */ fun enableRbfWithSequence(nsequence: UInt): BumpFeeTxBuilder {} /** Finish building the transaction. Returns the BIP174 PSBT. */ @@ -398,11 +442,20 @@ class BumpFeeTxBuilder() { /** * Generates a new mnemonic using the English word list and the given number of words (12, 15, 18, 21, or 24). + * + * @param wordCount The number of words to use for the mnemonic (also determines the amount of entropy that is used). + * @return The mnemonic words separated by a space in a String */ -fun generateMnemonic(wordCount: WordCount): String {} +fun generateMnemonic(wordCount: WordCount): String /** + * TODO * + * @constructor TODO + * + * @param network The network this DescriptorSecretKey is to be used on. + * @param mnemonic The mnemonic. + * @param password The optional passphrase that can be provided as per BIP-39. */ class DescriptorSecretKey(network: Network, mnemonic: String, password: String?) { /** Derive a private descriptor at a given path. */ @@ -414,12 +467,18 @@ class DescriptorSecretKey(network: Network, mnemonic: String, password: String?) /** Return the public version of the descriptor. */ fun asPublic(): DescriptorPublicKey {} - /** Return the private descriptor as a string. */ + /** Return the private descriptor as a string. */ fun asString(): String {} } /** + * TODO * + * @constructor TODO + * + * @param network The network this DescriptorPublicKey is to be used on. + * @param mnemonic The mnemonic. + * @param password The optional passphrase that can be provided as per BIP-39. */ class DescriptorPublicKey(network: Network, mnemonic: String, password: String?) { /** Derive a public descriptor at a given path. */ @@ -428,7 +487,7 @@ class DescriptorPublicKey(network: Network, mnemonic: String, password: String?) /** Extend the public descriptor with a custom path. */ fun extend(path: DerivationPath): DescriptorSecretKey {} - /** Return the public descriptor as a string. */ + /** Return the public descriptor as a string. */ fun asString(): String {} } @@ -436,18 +495,18 @@ class DescriptorPublicKey(network: Network, mnemonic: String, password: String?) * An enum describing entropy length (aka word count) in the mnemonic. */ enum class WordCount { - /** 12 words mnemonic (128 bits entropy) */ + /** 12 words mnemonic (128 bits entropy). */ WORDS12, - /** 15 words mnemonic (160 bits entropy) */ + /** 15 words mnemonic (160 bits entropy). */ WORDS15, - /** 18 words mnemonic (192 bits entropy) */ + /** 18 words mnemonic (192 bits entropy). */ WORDS18, - /** 21 words mnemonic (224 bits entropy) */ + /** 21 words mnemonic (224 bits entropy). */ WORDS21, - /** 24 words mnemonic (256 bits entropy) */ + /** 24 words mnemonic (256 bits entropy). */ WORDS24, } From b9c283c89b4cd40e8a97ff2782a1802759e87a9a Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 22 Sep 2022 09:40:39 -0400 Subject: [PATCH 258/272] Add samples for AddressIndex and AddressInfo --- .../src/main/kotlin/org/bitcoindevkit/bdk.kt | 10 ++++-- .../test/kotlin/org/bitcoindevkit/Samples.kt | 36 +++++++++++++++++-- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt index e1b89c2..01759aa 100644 --- a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt +++ b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt @@ -22,6 +22,8 @@ enum class Network { * * @property index Child index of this address. * @property address Address. + * + * @sample org.bitcoindevkit.addressInfoSample */ data class AddressInfo ( var index: UInt, @@ -32,6 +34,8 @@ data class AddressInfo ( * The address index selection strategy to use to derive an address from the wallet’s external descriptor. * * If you’re unsure which one to use, use `AddressIndex.NEW`. + * + * @sample org.bitcoindevkit.addressIndexSample */ enum class AddressIndex { /** Return a new address after incrementing the current descriptor index. */ @@ -70,8 +74,8 @@ data class Balance ( /** * Type that can contain any of the database configurations defined by the library. * - * @sample org.bitcoindevkit.memoryDatabaseConfigExample - * @sample org.bitcoindevkit.sqliteDatabaseConfigExample + * @sample org.bitcoindevkit.memoryDatabaseConfigSample + * @sample org.bitcoindevkit.sqliteDatabaseConfigSample */ sealed class DatabaseConfig { /** Configuration for an in-memory database. */ @@ -113,7 +117,7 @@ data class SledDbConfiguration( * @property timeout Request timeout (seconds). * @property stopGap Stop searching addresses for transactions after finding an unused gap of this length. * - * @sample org.bitcoindevkit.electrumBlockchainConfigExample + * @sample org.bitcoindevkit.electrumBlockchainConfigSample */ data class ElectrumConfig ( var url: String, diff --git a/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt b/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt index e2f5fd8..b160a68 100644 --- a/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt +++ b/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt @@ -23,7 +23,7 @@ fun balanceSample() { println("Total wallet balance is ${balance.total}") } -fun electrumBlockchainConfigExample() { +fun electrumBlockchainConfigSample() { val blockchainConfig = BlockchainConfig.Electrum( ElectrumConfig( "ssl://electrum.blockstream.info:60002", @@ -35,10 +35,40 @@ fun electrumBlockchainConfigExample() { ) } -fun memoryDatabaseConfigExample() { +fun memoryDatabaseConfigSample() { val memoryDatabaseConfig = DatabaseConfig.Memory } -fun sqliteDatabaseConfigExample() { +fun sqliteDatabaseConfigSample() { val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration("bdk-sqlite")) } + +fun addressIndexSample() { + val wallet: Wallet = Wallet( + descriptor = descriptor, + changeDescriptor = changeDescriptor, + network = Network.TESTNET, + databaseConfig = DatabaseConfig.Memory + ) + + fun getLastUnusedAddress(): AddressInfo { + return wallet.getAddress(AddressIndex.LAST_UNUSED) + } +} + +fun addressInfoSample() { + val wallet: Wallet = Wallet( + descriptor = descriptor, + changeDescriptor = changeDescriptor, + network = Network.TESTNET, + databaseConfig = DatabaseConfig.Memory + ) + + fun getLastUnusedAddress(): AddressInfo { + return wallet.getAddress(AddressIndex.NEW) + } + + val newAddress: AddressInfo = getLastUnusedAddress() + + println("New address at index ${newAddress.index} is ${newAddress.address}") +} From 3f35a18d41fe0a2f3e7fe9345aa9cb9908d3f0ba Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 22 Sep 2022 09:56:41 -0400 Subject: [PATCH 259/272] Add API docs samples for Network, BlockchainConfig, and Blockchain --- .../src/main/kotlin/org/bitcoindevkit/bdk.kt | 6 ++++ .../test/kotlin/org/bitcoindevkit/Samples.kt | 36 ++++++++++++++++--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt index 01759aa..74db8c2 100644 --- a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt +++ b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt @@ -2,6 +2,8 @@ package org.bitcoindevkit /** * The cryptocurrency to act on. + * + * @sample org.bitcoindevkit.networkSample */ enum class Network { /** Bitcoin's mainnet. */ @@ -146,6 +148,8 @@ data class EsploraConfig ( /** * Type that can contain any of the blockchain configurations defined by the library. + * + * @sample org.bitcoindevkit.electrumBlockchainConfigSample */ sealed class BlockchainConfig { /** Electrum client. */ @@ -178,6 +182,8 @@ data class TransactionDetails ( * @constructor Create the new blockchain client. * * @param config The blockchain configuration required. + * + * @sample org.bitcoindevkit.blockchainSample */ class Blockchain( config: BlockchainConfig diff --git a/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt b/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt index b160a68..36e7c33 100644 --- a/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt +++ b/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt @@ -1,5 +1,14 @@ package org.bitcoindevkit +fun networkSample() { + val wallet = Wallet( + descriptor = descriptor, + changeDescriptor = changeDescriptor, + network = Network.TESTNET, + databaseConfig = DatabaseConfig.Memory + ) +} + fun balanceSample() { object LogProgress : Progress { override fun update(progress: Float, message: String?) {} @@ -26,11 +35,11 @@ fun balanceSample() { fun electrumBlockchainConfigSample() { val blockchainConfig = BlockchainConfig.Electrum( ElectrumConfig( - "ssl://electrum.blockstream.info:60002", - null, - 5u, - null, - 200u + url = "ssl://electrum.blockstream.info:60002", + socks5 = null, + retry = 5u, + timeout = null, + stopGap = 200u ) ) } @@ -72,3 +81,20 @@ fun addressInfoSample() { println("New address at index ${newAddress.index} is ${newAddress.address}") } + +fun blockchainSample() { + val blockchainConfig: BlockchainConfig = BlockchainConfig.Electrum( + ElectrumConfig( + electrumURL, + null, + 5u, + null, + 10u + ) + ) + + val blockchain: Blockchain = Blockchain(blockchainConfig) + + blockchain.broadcast(signedPsbt) +} + From 6598df9ed9e52a629483fd21da1682c9f339d37c Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 23 Sep 2022 10:48:29 -0400 Subject: [PATCH 260/272] Add bindings PGP public key and documentation on how to verify signatures --- PGP-BDK-BINDINGS.asc | 14 ++++++++++++++ README.md | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 PGP-BDK-BINDINGS.asc diff --git a/PGP-BDK-BINDINGS.asc b/PGP-BDK-BINDINGS.asc new file mode 100644 index 0000000..6b5af63 --- /dev/null +++ b/PGP-BDK-BINDINGS.asc @@ -0,0 +1,14 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEYw6xkRYJKwYBBAHaRw8BAQdAg+VLXuidDqeP015H/QMlESJyQeIntTUoQkbk ++IFu+jO0M2JpdGNvaW5kZXZraXQtYmluZGluZ3MgPGJpbmRpbmdzQGJpdGNvaW5k +ZXZraXQub3JnPoiTBBMWCgA7FiEEiK2TrEWJ/QkP87jRJ2jEPogDxqMFAmMOsZEC +GwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQJ2jEPogDxqPQTgEA292D +RQaxDTJ4k91D0w50Vrd0NSNUwlsERz9XJ64abWABAP99vGMmq2pfrngTQqjLgLe8 +0YhQ+VML2x/B0LSN6MgNuDgEYw6xkRIKKwYBBAGXVQEFAQEHQEkUJv+/Wzx7nNiX +eti3HkeT6ZNAuCExPE4F7jxHNQ1TAwEIB4h4BBgWCgAgFiEEiK2TrEWJ/QkP87jR +J2jEPogDxqMFAmMOsZECGwwACgkQJ2jEPogDxqObPQEA/B0xNew03KM0JP630efG +QT/3Caq/jx86pLwnB7XqWI8BAOKmqrOEiwCBjhaIpzC3/1M+aZuPRUL3V91uPxpM +jFAJ +=vvmK +-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file diff --git a/README.md b/README.md index 39e513c..de2d928 100644 --- a/README.md +++ b/README.md @@ -115,9 +115,48 @@ and use the `publishToMavenLocal` task without excluding the signing task: ./gradlew publishToMavenLocal ``` +## Verifying Signatures +Both libraries and all their corresponding artifacts are signed with a PGP key you can find in the root of this repository. To verify the hashes and signatures, go through the following steps: + +1. Import the PGP key in your keyring +2. Download the artifact and its corresponding hash and signature files ([bdk-jvm] and [bdk-android]) +3. Verify the hashes +4. Verify the signature + +```shell +# 1. Navigate to the root of the repository and import the ./PGP-BDK-BINDINGS.asc public key +gpg --import ./PGP-BDK-BINDINGS.asc +# Alternatively, you can import it directly from a public key server +gpg --keyserver keyserver.ubuntu.com --receive-key 2768C43E8803C6A3 +# Verify that the correct key was imported +gpg --list-keys +# ------------------------------ +# pub ed25519 2022-08-31 [SC] +# 88AD93AC4589FD090FF3B8D12768C43E8803C6A3 +# uid [ unknown] bitcoindevkit-bindings +# sub cv25519 2022-08-31 [E] + +# 2. Add files and their corresponding signature and hash files in the same directory +# e.g. bdk-jvm-0.9.0.jar, bdk-jvm-0.9.0.jar.asc, bdk-jvm-0.9.0.jar.sha256 + +# 3. Verify that the hashes are the same +shasum --algorithm 256 bdk-android-0.9.0.aar && cat bdk-android-0.9.0.aar.sha256 + +# 4. Verify the signature +gpg --verify bdk-android-0.9.0.module.asc +``` + +### PGP Metadata +Full key ID: `88AD 93AC 4589 FD09 0FF3 B8D1 2768 C43E 8803 C6A3` +Fingerprint: `2768C43E8803C6A3` +Name: `bitcoindevkit-bindings` +Email: `bindings@bitcoindevkit.org` + [Kotlin]: https://kotlinlang.org/ [Android Studio]: https://developer.android.com/studio/ [`bdk`]: https://github.com/bitcoindevkit/bdk [`bdk-ffi`]: https://github.com/bitcoindevkit/bdk-ffi ["Getting Started (Developer)"]: https://github.com/bitcoindevkit/bdk-ffi#getting-started-developer [Gradle Nexus Publish Plugin]: https://github.com/gradle-nexus/publish-plugin +[bdk-jvm]: https://search.maven.org/artifact/org.bitcoindevkit/bdk-jvm/0.9.0/jar +[bdk-android]: https://search.maven.org/artifact/org.bitcoindevkit/bdk-android/0.9.0/aar From 3a0fe79dd825079857396f83a911bb7eb8c9209a Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Mon, 26 Sep 2022 09:01:05 -0400 Subject: [PATCH 261/272] Add bindings PGP public key and documentation on how to verify signatures --- README.md | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index de2d928..297d48f 100644 --- a/README.md +++ b/README.md @@ -116,34 +116,41 @@ and use the `publishToMavenLocal` task without excluding the signing task: ``` ## Verifying Signatures -Both libraries and all their corresponding artifacts are signed with a PGP key you can find in the root of this repository. To verify the hashes and signatures, go through the following steps: - -1. Import the PGP key in your keyring -2. Download the artifact and its corresponding hash and signature files ([bdk-jvm] and [bdk-android]) -3. Verify the hashes -4. Verify the signature +Both libraries and all their corresponding artifacts are signed with a PGP key you can find in the +root of this repository. To verify the signatures follow the below steps: +1. Import the PGP key in your keyring. ```shell -# 1. Navigate to the root of the repository and import the ./PGP-BDK-BINDINGS.asc public key +# Navigate to the root of the repository and import the ./PGP-BDK-BINDINGS.asc public key gpg --import ./PGP-BDK-BINDINGS.asc -# Alternatively, you can import it directly from a public key server + +# Alternatively, you can import the key directly from a public key server gpg --keyserver keyserver.ubuntu.com --receive-key 2768C43E8803C6A3 + # Verify that the correct key was imported gpg --list-keys -# ------------------------------ -# pub ed25519 2022-08-31 [SC] -# 88AD93AC4589FD090FF3B8D12768C43E8803C6A3 -# uid [ unknown] bitcoindevkit-bindings -# sub cv25519 2022-08-31 [E] +# You should see the below output +pub ed25519 2022-08-31 [SC] + 88AD93AC4589FD090FF3B8D12768C43E8803C6A3 +uid [ unknown] bitcoindevkit-bindings +sub cv25519 2022-08-31 [E] +``` -# 2. Add files and their corresponding signature and hash files in the same directory -# e.g. bdk-jvm-0.9.0.jar, bdk-jvm-0.9.0.jar.asc, bdk-jvm-0.9.0.jar.sha256 +2. Download the binary artifacts and corresponding signature files. +- from [bdk-jvm] + - `bdk-jvm-.jar` + - `bdk-jvm-.jar.asc` +- from [bdk-android] + - `bdk-android-.aar` + - `bdk-android-.aar.asc` -# 3. Verify that the hashes are the same -shasum --algorithm 256 bdk-android-0.9.0.aar && cat bdk-android-0.9.0.aar.sha256 +3. Verify the signatures. +```shell +gpg --verify bdk-jvm-.jar.asc +gpg --verify bdk-android-.aar.asc -# 4. Verify the signature -gpg --verify bdk-android-0.9.0.module.asc +# you should see a "Good signature" result +gpg: Good signature from "bitcoindevkit-bindings " [unknown] ``` ### PGP Metadata From 20134bb96a2fa1d8bfd3c0190277e1631aeb39ef Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 5 Oct 2022 10:10:16 -0400 Subject: [PATCH 262/272] Add option to build API docs into bdk-jvm website --- api-docs/{Module.md => Module1.md} | 4 +--- api-docs/Module2.md | 4 ++++ api-docs/build.gradle.kts | 13 ++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) rename api-docs/{Module.md => Module1.md} (51%) create mode 100644 api-docs/Module2.md diff --git a/api-docs/Module.md b/api-docs/Module1.md similarity index 51% rename from api-docs/Module.md rename to api-docs/Module1.md index 345f22f..d21953b 100644 --- a/api-docs/Module.md +++ b/api-docs/Module1.md @@ -1,6 +1,4 @@ # Module bdk-android -The [bitcoindevkit](https://bitcoindevkit.org/) language bindings libraries for Android and the JVM. - -These docs are valid for both `bdk-jvm` and `bdk-android` libraries. +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Android. # Package org.bitcoindevkit diff --git a/api-docs/Module2.md b/api-docs/Module2.md new file mode 100644 index 0000000..8a6eef7 --- /dev/null +++ b/api-docs/Module2.md @@ -0,0 +1,4 @@ +# Module bdk-jvm +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Kotlin and Java on the JVM. + +# Package org.bitcoindevkit diff --git a/api-docs/build.gradle.kts b/api-docs/build.gradle.kts index 73fb6cf..920ff8e 100644 --- a/api-docs/build.gradle.kts +++ b/api-docs/build.gradle.kts @@ -28,8 +28,19 @@ tasks.withType().configureEach { named("main") { moduleName.set("bdk-android") moduleVersion.set("0.9.0") - includes.from("Module.md") + includes.from("Module1.md") samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt") } } } + +// tasks.withType().configureEach { +// dokkaSourceSets { +// named("main") { +// moduleName.set("bdk-jvm") +// moduleVersion.set("0.9.0") +// includes.from("Module2.md") +// samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt") +// } +// } +// } From e5e38d7f77618a93a396b0cc9d0f61fd48770a63 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Wed, 5 Oct 2022 10:26:57 -0400 Subject: [PATCH 263/272] Fix missing comma in API docs for TransactionDetails --- api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt index 74db8c2..e444edd 100644 --- a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt +++ b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt @@ -172,7 +172,7 @@ data class TransactionDetails ( var fee: ULong?, var received: ULong, var sent: ULong, - var txid: String + var txid: String, var confirmationTime: BlockTime? ) From 65f2c0fcf1786cc1b8f271689f1ec8b718d3c68e Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 20 Oct 2022 11:52:59 -0400 Subject: [PATCH 264/272] Bump bdk-ffi submodule to v0.10.0 tag --- api-docs/build.gradle.kts | 4 +- api-docs/settings.gradle.kts | 2 +- .../src/main/kotlin/org/bitcoindevkit/bdk.kt | 92 ++++++++++--- .../test/kotlin/org/bitcoindevkit/Samples.kt | 126 ++++++++++++++++++ bdk-ffi | 2 +- 5 files changed, 200 insertions(+), 26 deletions(-) diff --git a/api-docs/build.gradle.kts b/api-docs/build.gradle.kts index 920ff8e..46a1921 100644 --- a/api-docs/build.gradle.kts +++ b/api-docs/build.gradle.kts @@ -27,7 +27,7 @@ tasks.withType().configureEach { dokkaSourceSets { named("main") { moduleName.set("bdk-android") - moduleVersion.set("0.9.0") + moduleVersion.set("0.10.0") includes.from("Module1.md") samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt") } @@ -38,7 +38,7 @@ tasks.withType().configureEach { // dokkaSourceSets { // named("main") { // moduleName.set("bdk-jvm") -// moduleVersion.set("0.9.0") +// moduleVersion.set("0.10.0") // includes.from("Module2.md") // samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt") // } diff --git a/api-docs/settings.gradle.kts b/api-docs/settings.gradle.kts index eefa331..3b7cdb5 100644 --- a/api-docs/settings.gradle.kts +++ b/api-docs/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "bdk-kotlin-docs" +rootProject.name = "BDK Android and BDK JVM API Docs" diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt index e444edd..d3bc6ce 100644 --- a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt +++ b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt @@ -211,6 +211,15 @@ class PartiallySignedBitcoinTransaction(psbtBase64: String) { /** Get the txid of the PSBT. */ fun txid(): String {} + + /** Return the transaction as bytes. */ + fun `extractTx`(): List + + /** + * Combines this PartiallySignedTransaction with another PSBT as described by BIP 174. + * In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)` + */ + fun combine(other: PartiallySignedBitcoinTransaction): PartiallySignedBitcoinTransaction } /** @@ -272,10 +281,6 @@ data class BlockTime ( var timestamp: ULong, ) -/** - - */ - /** * A Bitcoin wallet. * The Wallet acts as a way of coherently interfacing with output descriptors and related transactions. Its main components are: @@ -289,6 +294,8 @@ data class BlockTime ( * @param changeDescriptor The change (or "internal") descriptor. * @param network The network to act on. * @param databaseConfig The database configuration. + * + * @sample org.bitcoindevkit.walletSample */ class Wallet( descriptor: String, @@ -333,7 +340,7 @@ class Progress { /** * A transaction builder. * - * After creating the TxBuilder, you set options on it until finally calling finish to consume the builder and generate the transaction. + * After creating the TxBuilder, you set options on it until finally calling `.finish` to consume the builder and generate the transaction. * * Each method on the TxBuilder returns an instance of a new TxBuilder with the option set/added. */ @@ -342,10 +349,10 @@ class TxBuilder() { fun addData(data: List): TxBuilder {} /** Add a recipient to the internal list. */ - fun addRecipient(address: String, amount: ULong): TxBuilder {} + fun addRecipient(script: Script, amount: ULong): TxBuilder {} /** Set the list of recipients by providing a list of [AddressAmount]. */ - fun setRecipients(recipients: List): TxBuilder {} + fun setRecipients(recipients: List): TxBuilder {} /** Add a utxo to the internal list of unspendable utxos. It’s important to note that the "must-be-spent" utxos added with [TxBuilder.addUtxo] have priority over this. See the Rust docs of the two linked methods for more details. */ fun addUnspendable(unspendable: OutPoint): TxBuilder {} @@ -408,18 +415,18 @@ class TxBuilder() { */ fun enableRbfWithSequence(nsequence: UInt): TxBuilder {} - /** Finish building the transaction. Returns the BIP174 PSBT. */ - fun finish(wallet: Wallet): PartiallySignedBitcoinTransaction {} + /** Finish building the transaction. Returns a [TxBuilderResult]. */ + fun finish(wallet: Wallet): TxBuilderResult {} } /** - * A object holding an address and an amount. + * A object holding an ScriptPubKey and an amount. * - * @property address The address. + * @property script The ScriptPubKey. * @property amount The amount. */ data class AddressAmount ( - var address: String, + var script: Script, var amount: ULong ) @@ -446,8 +453,8 @@ class BumpFeeTxBuilder() { */ fun enableRbfWithSequence(nsequence: UInt): BumpFeeTxBuilder {} - /** Finish building the transaction. Returns the BIP174 PSBT. */ - fun finish(wallet: Wallet): PartiallySignedBitcoinTransaction {} + /** Finish building the transaction. Returns a [TxBuilderResult]. */ + fun finish(wallet: Wallet): TxBuilderResult {} } /** @@ -455,17 +462,28 @@ class BumpFeeTxBuilder() { * * @param wordCount The number of words to use for the mnemonic (also determines the amount of entropy that is used). * @return The mnemonic words separated by a space in a String + * + * @sample org.bitcoindevkit.generateMnemonicSample */ fun generateMnemonic(wordCount: WordCount): String /** - * TODO + * A BIP-32 derivation path. * - * @constructor TODO + * @param path The derivation path. Must start with `m`. Use this type to derive or extend a [DescriptorSecretKey] + * or [DescriptorPublicKey]. + */ +class DerivationPath(path: String) {} + +/** + * An extended secret key. * * @param network The network this DescriptorSecretKey is to be used on. * @param mnemonic The mnemonic. * @param password The optional passphrase that can be provided as per BIP-39. + * + * @sample org.bitcoindevkit.descriptorSecretKeyDeriveSample + * @sample org.bitcoindevkit.descriptorSecretKeyExtendSample */ class DescriptorSecretKey(network: Network, mnemonic: String, password: String?) { /** Derive a private descriptor at a given path. */ @@ -477,14 +495,15 @@ class DescriptorSecretKey(network: Network, mnemonic: String, password: String?) /** Return the public version of the descriptor. */ fun asPublic(): DescriptorPublicKey {} + /* Return the raw private key as bytes. */ + fun secretBytes(): List + /** Return the private descriptor as a string. */ fun asString(): String {} } /** - * TODO - * - * @constructor TODO + * An extended public key. * * @param network The network this DescriptorPublicKey is to be used on. * @param mnemonic The mnemonic. @@ -492,13 +511,13 @@ class DescriptorSecretKey(network: Network, mnemonic: String, password: String?) */ class DescriptorPublicKey(network: Network, mnemonic: String, password: String?) { /** Derive a public descriptor at a given path. */ - fun derive(path: DerivationPath): DescriptorSecretKey {} + fun derive(path: DerivationPath): DescriptorSecretKey /** Extend the public descriptor with a custom path. */ - fun extend(path: DerivationPath): DescriptorSecretKey {} + fun extend(path: DerivationPath): DescriptorSecretKey /** Return the public descriptor as a string. */ - fun asString(): String {} + fun asString(): String } /** @@ -520,3 +539,32 @@ enum class WordCount { /** 24 words mnemonic (256 bits entropy). */ WORDS24, } + +/** + * The value returned from calling the `.finish()` method on the [TxBuilder] or [BumpFeeTxBuilder]. + * + * @property psbt The PSBT + * @property transactionDetails The transaction details. + * + * @sample org.bitcoindevkit.txBuilderResultSample1 + * @sample org.bitcoindevkit.txBuilderResultSample2 + */ +data class TxBuilderResult ( + var psbt: PartiallySignedBitcoinTransaction, + var transactionDetails: TransactionDetails +) + +/** + * A bitcoin script. + */ +class Script(rawOutputScript: List) + +/** + * A bitcoin address. + * + * @param address The address in string format. + */ +class Address(address: String) { + /* Return the ScriptPubKey. */ + fun scriptPubkey(): Script +} diff --git a/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt b/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt index 36e7c33..198e896 100644 --- a/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt +++ b/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt @@ -98,3 +98,129 @@ fun blockchainSample() { blockchain.broadcast(signedPsbt) } + +fun txBuilderResultSample1() { + val faucetAddress = Address("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt") + // TxBuilderResult is a data class, which means you can use destructuring declarations on it to + // open it up in its component parts + val (psbt, txDetails) = TxBuilder() + .addRecipient(faucetAddress.scriptPubkey(), 1000u) + .feeRate(1.2f) + .finish(wallet) + + println("Txid is ${txDetails.txid}") + wallet.sign(psbt) +} + +fun txBuilderResultSample2() { + val faucetAddress = Address("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt") + val txBuilderResult: TxBuilderResult = TxBuilder() + .addRecipient(faucetAddress.scriptPubkey(), 1000u) + .feeRate(1.2f) + .finish(wallet) + + val psbt = txBuilderResult.psbt + val txDetails = txBuilderResult.transactionDetails + + println("Txid is ${txDetails.txid}") + wallet.sign(psbt) +} + +fun descriptorSecretKeyExtendSample() { + // The `DescriptorSecretKey.extend()` method allows you to extend a key to any given path. + + // val mnemonic: String = generateMnemonic(WordCount.WORDS12) + val mnemonic: String = "scene change clap smart together mind wheel knee clip normal trial unusual" + + // the initial DescriptorSecretKey will always be at the "master" node, + // i.e. the derivation path is empty + val bip32RootKey: DescriptorSecretKey = DescriptorSecretKey( + network = Network.TESTNET, + mnemonic = mnemonic, + password = "" + ) + println(bip32RootKey.asString()) + // tprv8ZgxMBicQKsPfM8Trx2apvdEkmxbJkYY3ZsmcgKb2bfnLNcBhtCstqQTeFesMRLEJXpjGDinAUJUHprXMwph8dQBdS1HAoxEis8Knimxovf/* + + // the derive method will also automatically apply the wildcard (*) to your path, + // i.e the following will generate the typical testnet BIP84 external wallet path + // m/84h/1h/0h/0/* + val bip84ExternalPath: DerivationPath = DerivationPath("m/84h/1h/0h/0") + val externalExtendedKey: DescriptorSecretKey = bip32RootKey.extend(bip84ExternalPath).asString() + println(externalExtendedKey) + // tprv8ZgxMBicQKsPfM8Trx2apvdEkmxbJkYY3ZsmcgKb2bfnLNcBhtCstqQTeFesMRLEJXpjGDinAUJUHprXMwph8dQBdS1HAoxEis8Knimxovf/84'/1'/0'/0/* + + // to create the descriptor you'll need to use this extended key in a descriptor function, + // i.e. wpkh(), tr(), etc. + val externalDescriptor = "wpkh($externalExtendedKey)" +} + +fun descriptorSecretKeyDeriveSample() { + // The DescriptorSecretKey.derive() method allows you to derive an extended key for a given + // node in the derivation tree (for example to create an xpub for a particular account) + + val mnemonic: String = "scene change clap smart together mind wheel knee clip normal trial unusual" + val bip32RootKey: DescriptorSecretKey = DescriptorSecretKey( + network = Network.TESTNET, + mnemonic = mnemonic, + password = "" + ) + + val bip84Account0: DerivationPath = DerivationPath("m/84h/1h/0h") + val xpubAccount0: DescriptorSecretKey = bip32RootKey.derive(bip84Account0) + println(xpubAccount0.asString()) + // [5512949b/84'/1'/0']tprv8ghw3FWfWTeLCEXcr8f8Q8Lz4QPCELYv3jhBXjAm7XagA6R5hreeWLTJeLBfMj7Ni6Q3PdV1o8NbvNBHE59W97EkRJSU4JkvTQjaNUmQubE/* + + val internalPath: DerivationPath = DerivationPath("m/0") + val externalExtendedKey = xpubAccount0.extend(internalPath).asString() + println(externalExtendedKey) + // [5512949b/84'/1'/0']tprv8ghw3FWfWTeLCEXcr8f8Q8Lz4QPCELYv3jhBXjAm7XagA6R5hreeWLTJeLBfMj7Ni6Q3PdV1o8NbvNBHE59W97EkRJSU4JkvTQjaNUmQubE/0/* + + // to create the descriptor you'll need to use this extended key in a descriptor function, + // i.e. wpkh(), tr(), etc. + val externalDescriptor = "wpkh($externalExtendedKey)" +} + +fun createTransaction() { + val wallet = BdkWallet( + descriptor = externalDescriptor, + changeDescriptor = internalDescriptor, + network = Network.TESTNET, + databaseConfig = memoryDatabaseConfig, + ) + val blockchainConfig = BlockchainConfig.Electrum( + ElectrumConfig( + "ssl://electrum.blockstream.info:60002", + null, + 5u, + null, + 200u + ) + ) + + val paymentAddress: Address = Address("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt") + val (psbt, txDetails) = TxBuilder() + .addRecipient(faucetAddress.scriptPubkey(), 1000u) + .feeRate(1.2f) + .finish(wallet) + + wallet.sign(psbt) + blockchain.broadcast(psbt) +} + +fun walletSample() { + val externalDescriptor = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEfVULesmhEfZYyBXdE/84h/1h/0h/0/*)" + val internalDescriptor = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEfVULesmhEfZYyBXdE/84h/1h/0h/1/*)" + val sqliteDatabaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration("bdk-sqlite")) + + val wallet = BdkWallet( + descriptor = externalDescriptor, + changeDescriptor = internalDescriptor, + network = Network.TESTNET, + databaseConfig = sqliteDatabaseConfig, + ) +} + +fun generateMnemonicSample() { + val mnemonic: String = generateMnemonic(WordCount.WORDS12) +} \ No newline at end of file diff --git a/bdk-ffi b/bdk-ffi index 485f4f7..c7d0803 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 485f4f72ce4d625e988effa67701adab0039aa64 +Subproject commit c7d0803000d31d8a6d731287ed5a9ab21d39828b From f820b4fd6fe5b54820f73b8a673d6bf8f7340c67 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Fri, 21 Oct 2022 15:02:46 -0400 Subject: [PATCH 265/272] Bump uniffi-rs dependency to 0.21.0 through latest bdk-ffi tag Signed-off-by: thunderbiscuit --- bdk-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-ffi b/bdk-ffi index c7d0803..e6cf423 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit c7d0803000d31d8a6d731287ed5a9ab21d39828b +Subproject commit e6cf4237218be9c6143936e7d0056e5270d4c227 From 5991b07385f43096997b26f0a04b3440a1dbb394 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Mon, 24 Oct 2022 10:19:56 -0400 Subject: [PATCH 266/272] Add group and version properties for nexus publishing plugin --- .github/workflows/publish-android.yaml | 3 +-- .github/workflows/publish-jvm.yaml | 3 +-- bdk-android/build.gradle.kts | 7 +++++++ bdk-android/lib/build.gradle.kts | 1 + bdk-jvm/build.gradle.kts | 7 +++++++ bdk-jvm/lib/build.gradle.kts | 2 -- 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-android.yaml b/.github/workflows/publish-android.yaml index 26ab381..bb86761 100644 --- a/.github/workflows/publish-android.yaml +++ b/.github/workflows/publish-android.yaml @@ -57,5 +57,4 @@ jobs: ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.NEXUS_PASSWORD }} run: | cd bdk-android - ./gradlew publishToSonatype -# ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository + ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository diff --git a/.github/workflows/publish-jvm.yaml b/.github/workflows/publish-jvm.yaml index 56c14f1..19668b3 100644 --- a/.github/workflows/publish-jvm.yaml +++ b/.github/workflows/publish-jvm.yaml @@ -98,5 +98,4 @@ jobs: ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.NEXUS_PASSWORD }} run: | cd bdk-jvm - ./gradlew publishToSonatype -# ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository + ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository diff --git a/bdk-android/build.gradle.kts b/bdk-android/build.gradle.kts index 0894966..2ac37fb 100644 --- a/bdk-android/build.gradle.kts +++ b/bdk-android/build.gradle.kts @@ -11,6 +11,13 @@ plugins { id("io.github.gradle-nexus.publish-plugin") version "1.1.0" } +// These properties are required here so that the nexus publish-plugin +// finds a staging profile with the correct group (group is otherwise set as "") +// and knows whether to publish to a SNAPSHOT repository or not +// https://github.com/gradle-nexus/publish-plugin#applying-the-plugin +group = "org.bitcoindevkit" +version = "0.10.0" + nexusPublishing { repositories { create("sonatype") { diff --git a/bdk-android/lib/build.gradle.kts b/bdk-android/lib/build.gradle.kts index 12cb804..7e2659e 100644 --- a/bdk-android/lib/build.gradle.kts +++ b/bdk-android/lib/build.gradle.kts @@ -58,6 +58,7 @@ afterEvaluate { groupId = "org.bitcoindevkit" artifactId = "bdk-android" version = "0.10.0-SNAPSHOT" + from(components["release"]) pom { name.set("bdk-android") diff --git a/bdk-jvm/build.gradle.kts b/bdk-jvm/build.gradle.kts index 3e3984f..32e01f1 100644 --- a/bdk-jvm/build.gradle.kts +++ b/bdk-jvm/build.gradle.kts @@ -2,6 +2,13 @@ plugins { id("io.github.gradle-nexus.publish-plugin") version "1.1.0" } +// These properties are required here so that the nexus publish-plugin +// finds a staging profile with the correct group (group is otherwise set as "") +// and knows whether to publish to a SNAPSHOT repository or not +// https://github.com/gradle-nexus/publish-plugin#applying-the-plugin +group = "org.bitcoindevkit" +version = "0.10.0" + nexusPublishing { repositories { create("sonatype") { diff --git a/bdk-jvm/lib/build.gradle.kts b/bdk-jvm/lib/build.gradle.kts index 49625de..06afea7 100644 --- a/bdk-jvm/lib/build.gradle.kts +++ b/bdk-jvm/lib/build.gradle.kts @@ -49,13 +49,11 @@ afterEvaluate { publishing { publications { create("maven") { - groupId = "org.bitcoindevkit" artifactId = "bdk-jvm" version = "0.10.0-SNAPSHOT" from(components["java"]) - pom { name.set("bdk-jvm") description.set("Bitcoin Dev Kit Kotlin language bindings.") From 929147f182741c63f186c22e2b137c4e1b31459c Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 27 Oct 2022 11:57:45 -0400 Subject: [PATCH 267/272] Bump versions to 0.11.0-SNAPSHOT --- bdk-android/build.gradle.kts | 2 +- bdk-android/lib/build.gradle.kts | 2 +- bdk-jvm/build.gradle.kts | 2 +- bdk-jvm/lib/build.gradle.kts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bdk-android/build.gradle.kts b/bdk-android/build.gradle.kts index 2ac37fb..be0d698 100644 --- a/bdk-android/build.gradle.kts +++ b/bdk-android/build.gradle.kts @@ -16,7 +16,7 @@ plugins { // and knows whether to publish to a SNAPSHOT repository or not // https://github.com/gradle-nexus/publish-plugin#applying-the-plugin group = "org.bitcoindevkit" -version = "0.10.0" +version = "0.11.0-SNAPSHOT" nexusPublishing { repositories { diff --git a/bdk-android/lib/build.gradle.kts b/bdk-android/lib/build.gradle.kts index 7e2659e..fe4e9fc 100644 --- a/bdk-android/lib/build.gradle.kts +++ b/bdk-android/lib/build.gradle.kts @@ -57,7 +57,7 @@ afterEvaluate { create("maven") { groupId = "org.bitcoindevkit" artifactId = "bdk-android" - version = "0.10.0-SNAPSHOT" + version = "0.11.0-SNAPSHOT" from(components["release"]) pom { diff --git a/bdk-jvm/build.gradle.kts b/bdk-jvm/build.gradle.kts index 32e01f1..3a4cdeb 100644 --- a/bdk-jvm/build.gradle.kts +++ b/bdk-jvm/build.gradle.kts @@ -7,7 +7,7 @@ plugins { // and knows whether to publish to a SNAPSHOT repository or not // https://github.com/gradle-nexus/publish-plugin#applying-the-plugin group = "org.bitcoindevkit" -version = "0.10.0" +version = "0.11.0-SNAPSHOT" nexusPublishing { repositories { diff --git a/bdk-jvm/lib/build.gradle.kts b/bdk-jvm/lib/build.gradle.kts index 06afea7..90fc7ff 100644 --- a/bdk-jvm/lib/build.gradle.kts +++ b/bdk-jvm/lib/build.gradle.kts @@ -51,7 +51,7 @@ afterEvaluate { create("maven") { groupId = "org.bitcoindevkit" artifactId = "bdk-jvm" - version = "0.10.0-SNAPSHOT" + version = "0.11.0-SNAPSHOT" from(components["java"]) pom { From 04aae0486a3b3203db103a397cfc56ec21a479ae Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 8 Nov 2022 11:50:54 -0500 Subject: [PATCH 268/272] Bump bdk-ffi submodule to v0.11.0 --- bdk-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-ffi b/bdk-ffi index e6cf423..0648075 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit e6cf4237218be9c6143936e7d0056e5270d4c227 +Subproject commit 0648075555b86365e2408fab484782a2b793b545 From ffd85e6bd14cf2791660c498e476a55ae1df1da6 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 8 Nov 2022 12:24:49 -0500 Subject: [PATCH 269/272] Use release-smaller profile for bdk-ffi in Gradle plugins --- .../org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt | 12 ++++++------ .../org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt b/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt index af07265..55b14bc 100644 --- a/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt +++ b/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt @@ -22,7 +22,7 @@ internal class UniFfiAndroidPlugin : Plugin { workingDir("${projectDir}/../../bdk-ffi") val cargoArgs: MutableList = - mutableListOf("build", "--release", "--target", "aarch64-linux-android") + mutableListOf("build", "--profile", "release-smaller", "--target", "aarch64-linux-android") executable("cargo") args(cargoArgs) @@ -54,7 +54,7 @@ internal class UniFfiAndroidPlugin : Plugin { workingDir("${project.projectDir}/../../bdk-ffi") val cargoArgs: MutableList = - mutableListOf("build", "--release", "--target", "x86_64-linux-android") + mutableListOf("build", "--profile", "release-smaller", "--target", "x86_64-linux-android") executable("cargo") args(cargoArgs) @@ -86,7 +86,7 @@ internal class UniFfiAndroidPlugin : Plugin { workingDir("${project.projectDir}/../../bdk-ffi") val cargoArgs: MutableList = - mutableListOf("build", "--release", "--target", "armv7-linux-androideabi") + mutableListOf("build", "--profile", "release-smaller", "--target", "armv7-linux-androideabi") executable("cargo") args(cargoArgs) @@ -124,15 +124,15 @@ internal class UniFfiAndroidPlugin : Plugin { into("${project.projectDir}/../lib/src/main/jniLibs/") into("arm64-v8a") { - from("${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release/libbdkffi.so") + from("${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release-smaller/libbdkffi.so") } into("x86_64") { - from("${project.projectDir}/../../bdk-ffi/target/x86_64-linux-android/release/libbdkffi.so") + from("${project.projectDir}/../../bdk-ffi/target/x86_64-linux-android/release-smaller/libbdkffi.so") } into("armeabi-v7a") { - from("${project.projectDir}/../../bdk-ffi/target/armv7-linux-androideabi/release/libbdkffi.so") + from("${project.projectDir}/../../bdk-ffi/target/armv7-linux-androideabi/release-smaller/libbdkffi.so") } doLast { diff --git a/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt b/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt index aada4fb..04e5b13 100644 --- a/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt +++ b/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt @@ -18,20 +18,20 @@ internal class UniFfiJvmPlugin : Plugin { exec { workingDir("${project.projectDir}/../../bdk-ffi") executable("cargo") - val cargoArgs: List = listOf("build", "--release", "--target", "x86_64-apple-darwin") + val cargoArgs: List = listOf("build", "--profile", "release-smaller", "--target", "x86_64-apple-darwin") args(cargoArgs) } exec { workingDir("${project.projectDir}/../../bdk-ffi") executable("cargo") - val cargoArgs: List = listOf("build", "--release", "--target", "aarch64-apple-darwin") + val cargoArgs: List = listOf("build", "--profile", "release-smaller", "--target", "aarch64-apple-darwin") args(cargoArgs) } } else if(operatingSystem == OS.LINUX) { exec { workingDir("${project.projectDir}/../../bdk-ffi") executable("cargo") - val cargoArgs: List = listOf("build", "--release", "--target", "x86_64-unknown-linux-gnu") + val cargoArgs: List = listOf("build", "--profile", "release-smaller", "--target", "x86_64-unknown-linux-gnu") args(cargoArgs) } } @@ -76,7 +76,7 @@ internal class UniFfiJvmPlugin : Plugin { doFirst { copy { with(it) { - from("${project.projectDir}/../../bdk-ffi/target/${this.targetDir}/release/libbdkffi.${this.ext}") + from("${project.projectDir}/../../bdk-ffi/target/${this.targetDir}/release-smaller/libbdkffi.${this.ext}") into("${project.projectDir}/../../bdk-jvm/lib/src/main/resources/${this.resDir}/") } } From 3be23ad7f90d5806be461106a84d5e6127298397 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 8 Nov 2022 13:53:49 -0500 Subject: [PATCH 270/272] Update API docs to version 0.11.0 --- api-docs/build.gradle.kts | 4 +-- .../src/main/kotlin/org/bitcoindevkit/bdk.kt | 34 +++++++++++++------ .../test/kotlin/org/bitcoindevkit/Samples.kt | 17 +++++++--- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/api-docs/build.gradle.kts b/api-docs/build.gradle.kts index 46a1921..bbd6ed7 100644 --- a/api-docs/build.gradle.kts +++ b/api-docs/build.gradle.kts @@ -27,7 +27,7 @@ tasks.withType().configureEach { dokkaSourceSets { named("main") { moduleName.set("bdk-android") - moduleVersion.set("0.10.0") + moduleVersion.set("0.11.0") includes.from("Module1.md") samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt") } @@ -38,7 +38,7 @@ tasks.withType().configureEach { // dokkaSourceSets { // named("main") { // moduleName.set("bdk-jvm") -// moduleVersion.set("0.10.0") +// moduleVersion.set("0.11.0") // includes.from("Module2.md") // samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt") // } diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt index d3bc6ce..6da4d18 100644 --- a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt +++ b/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt @@ -457,16 +457,6 @@ class BumpFeeTxBuilder() { fun finish(wallet: Wallet): TxBuilderResult {} } -/** - * Generates a new mnemonic using the English word list and the given number of words (12, 15, 18, 21, or 24). - * - * @param wordCount The number of words to use for the mnemonic (also determines the amount of entropy that is used). - * @return The mnemonic words separated by a space in a String - * - * @sample org.bitcoindevkit.generateMnemonicSample - */ -fun generateMnemonic(wordCount: WordCount): String - /** * A BIP-32 derivation path. * @@ -485,7 +475,7 @@ class DerivationPath(path: String) {} * @sample org.bitcoindevkit.descriptorSecretKeyDeriveSample * @sample org.bitcoindevkit.descriptorSecretKeyExtendSample */ -class DescriptorSecretKey(network: Network, mnemonic: String, password: String?) { +class DescriptorSecretKey(network: Network, mnemonic: Mnemonic, password: String?) { /** Derive a private descriptor at a given path. */ fun derive(path: DerivationPath): DescriptorSecretKey {} @@ -568,3 +558,25 @@ class Address(address: String) { /* Return the ScriptPubKey. */ fun scriptPubkey(): Script } + +/** + * Mnemonic phrases are a human-readable version of the private keys. Supported number of words are 12, 15, 18, 21 and 24. + * + * @constructor Generates Mnemonic with a random entropy. + * @param mnemonic The mnemonic as a string of space-separated words. + * + * @sample org.bitcoindevkit.mnemonicSample + */ +class Mnemonic(mnemonic: String) { + /* Returns Mnemonic as string */ + fun asString(): String + + /* Parse a Mnemonic from a given string. */ + fun fromString(): Mnemonic + + /* + * Create a new Mnemonic in the specified language from the given entropy. Entropy must be a + * multiple of 32 bits (4 bytes) and 128-256 bits in length. + */ + fun fromEntropy(): Mnemonic +} diff --git a/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt b/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt index 198e896..34840ec 100644 --- a/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt +++ b/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt @@ -130,7 +130,7 @@ fun descriptorSecretKeyExtendSample() { // The `DescriptorSecretKey.extend()` method allows you to extend a key to any given path. // val mnemonic: String = generateMnemonic(WordCount.WORDS12) - val mnemonic: String = "scene change clap smart together mind wheel knee clip normal trial unusual" + val mnemonic: Mnemonic = Mnemonic("scene change clap smart together mind wheel knee clip normal trial unusual") // the initial DescriptorSecretKey will always be at the "master" node, // i.e. the derivation path is empty @@ -159,7 +159,7 @@ fun descriptorSecretKeyDeriveSample() { // The DescriptorSecretKey.derive() method allows you to derive an extended key for a given // node in the derivation tree (for example to create an xpub for a particular account) - val mnemonic: String = "scene change clap smart together mind wheel knee clip normal trial unusual" + val mnemonic: Mnemonic = Mnemonic("scene change clap smart together mind wheel knee clip normal trial unusual") val bip32RootKey: DescriptorSecretKey = DescriptorSecretKey( network = Network.TESTNET, mnemonic = mnemonic, @@ -221,6 +221,13 @@ fun walletSample() { ) } -fun generateMnemonicSample() { - val mnemonic: String = generateMnemonic(WordCount.WORDS12) -} \ No newline at end of file +fun mnemonicSample() { + val mnemonic0: Mnemonic = Mnemonic(WordCount.WORDS12) + + val mnemonic1: Mnemonic = Mnemonic.fromString("scene change clap smart together mind wheel knee clip normal trial unusual") + + val entropy: List = listOf(0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u) + val mnemonic2: Mnemonic = Mnemonic.fromEntropy(entropy) + + println(mnemonic0.asString(), mnemonic1.asString(), mnemonic2.asString()) +} From f07473e1cae02345bdf64600dff68dcc6c1c3e90 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 8 Nov 2022 15:03:20 -0500 Subject: [PATCH 271/272] Bump versions to 0.12.0-SNAPSHOT --- bdk-android/build.gradle.kts | 2 +- bdk-android/lib/build.gradle.kts | 2 +- bdk-jvm/build.gradle.kts | 2 +- bdk-jvm/lib/build.gradle.kts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bdk-android/build.gradle.kts b/bdk-android/build.gradle.kts index be0d698..8304c9c 100644 --- a/bdk-android/build.gradle.kts +++ b/bdk-android/build.gradle.kts @@ -16,7 +16,7 @@ plugins { // and knows whether to publish to a SNAPSHOT repository or not // https://github.com/gradle-nexus/publish-plugin#applying-the-plugin group = "org.bitcoindevkit" -version = "0.11.0-SNAPSHOT" +version = "0.12.0-SNAPSHOT" nexusPublishing { repositories { diff --git a/bdk-android/lib/build.gradle.kts b/bdk-android/lib/build.gradle.kts index fe4e9fc..6164f29 100644 --- a/bdk-android/lib/build.gradle.kts +++ b/bdk-android/lib/build.gradle.kts @@ -57,7 +57,7 @@ afterEvaluate { create("maven") { groupId = "org.bitcoindevkit" artifactId = "bdk-android" - version = "0.11.0-SNAPSHOT" + version = "0.12.0-SNAPSHOT" from(components["release"]) pom { diff --git a/bdk-jvm/build.gradle.kts b/bdk-jvm/build.gradle.kts index 3a4cdeb..c9807ba 100644 --- a/bdk-jvm/build.gradle.kts +++ b/bdk-jvm/build.gradle.kts @@ -7,7 +7,7 @@ plugins { // and knows whether to publish to a SNAPSHOT repository or not // https://github.com/gradle-nexus/publish-plugin#applying-the-plugin group = "org.bitcoindevkit" -version = "0.11.0-SNAPSHOT" +version = "0.12.0-SNAPSHOT" nexusPublishing { repositories { diff --git a/bdk-jvm/lib/build.gradle.kts b/bdk-jvm/lib/build.gradle.kts index 90fc7ff..b8ab170 100644 --- a/bdk-jvm/lib/build.gradle.kts +++ b/bdk-jvm/lib/build.gradle.kts @@ -51,7 +51,7 @@ afterEvaluate { create("maven") { groupId = "org.bitcoindevkit" artifactId = "bdk-jvm" - version = "0.11.0-SNAPSHOT" + version = "0.12.0-SNAPSHOT" from(components["java"]) pom { From 0c1a9d7f1d4b8d4ed95fd2d7817160c2e0516542 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Mon, 14 Nov 2022 08:56:17 -0500 Subject: [PATCH 272/272] Move all bdk-kotlin into subdirectory to prepare for ffi merge Signed-off-by: thunderbiscuit --- bdk-ffi | 1 - .editorconfig => bdk-kotlin/.editorconfig | 0 .gitignore => bdk-kotlin/.gitignore | 0 .gitmodules => bdk-kotlin/.gitmodules | 0 LICENSE => bdk-kotlin/LICENSE | 0 LICENSE-APACHE => bdk-kotlin/LICENSE-APACHE | 0 LICENSE-MIT => bdk-kotlin/LICENSE-MIT | 0 .../PGP-BDK-BINDINGS.asc | 0 README.md => bdk-kotlin/README.md | 0 {api-docs => bdk-kotlin/api-docs}/Module1.md | 0 {api-docs => bdk-kotlin/api-docs}/Module2.md | 0 .../api-docs}/build.gradle.kts | 26 +++++++++--------- {api-docs => bdk-kotlin/api-docs}/deploy.sh | 0 .../api-docs}/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 {api-docs => bdk-kotlin/api-docs}/gradlew | 0 {api-docs => bdk-kotlin/api-docs}/gradlew.bat | 0 .../api-docs}/settings.gradle.kts | 0 .../src/main/kotlin/org/bitcoindevkit/bdk.kt | 0 .../test/kotlin/org/bitcoindevkit/Samples.kt | 0 .../bdk-android}/build.gradle.kts | 0 .../bdk-android}/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 .../bdk-android}/gradlew | 0 .../bdk-android}/gradlew.bat | 0 .../bdk-android}/lib/build.gradle.kts | 0 .../bdk-android}/lib/proguard-rules.pro | 0 .../lib/src/androidTest/assets/logback.xml | 0 .../org/bitcoindevkit/AndroidLibTest.kt | 0 .../lib/src/main/AndroidManifest.xml | 0 .../bdk-android}/plugins/README.md | 0 .../bdk-android}/plugins/build.gradle.kts | 0 .../bdk-android}/plugins/settings.gradle.kts | 0 .../kotlin/org/bitcoindevkit/plugins/Enums.kt | 0 .../plugins/UniFfiAndroidPlugin.kt | 0 .../bdk-android}/settings.gradle.kts | 0 .../bdk-jvm}/build.gradle.kts | 0 .../bdk-jvm}/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 {bdk-jvm => bdk-kotlin/bdk-jvm}/gradlew | 0 {bdk-jvm => bdk-kotlin/bdk-jvm}/gradlew.bat | 0 .../bdk-jvm}/lib/build.gradle.kts | 0 .../kotlin/org/bitcoindevkit/JvmLibTest.kt | 0 .../bdk-jvm}/plugins/README.md | 0 .../bdk-jvm}/plugins/build.gradle.kts | 0 .../bdk-jvm}/plugins/settings.gradle.kts | 0 .../kotlin/org/bitcoindevkit/plugins/Enums.kt | 0 .../bitcoindevkit/plugins/UniFfiJvmPlugin.kt | 0 .../bdk-jvm}/settings.gradle.kts | 0 52 files changed, 13 insertions(+), 14 deletions(-) delete mode 160000 bdk-ffi rename .editorconfig => bdk-kotlin/.editorconfig (100%) rename .gitignore => bdk-kotlin/.gitignore (100%) rename .gitmodules => bdk-kotlin/.gitmodules (100%) rename LICENSE => bdk-kotlin/LICENSE (100%) rename LICENSE-APACHE => bdk-kotlin/LICENSE-APACHE (100%) rename LICENSE-MIT => bdk-kotlin/LICENSE-MIT (100%) rename PGP-BDK-BINDINGS.asc => bdk-kotlin/PGP-BDK-BINDINGS.asc (100%) rename README.md => bdk-kotlin/README.md (100%) rename {api-docs => bdk-kotlin/api-docs}/Module1.md (100%) rename {api-docs => bdk-kotlin/api-docs}/Module2.md (100%) rename {api-docs => bdk-kotlin/api-docs}/build.gradle.kts (84%) rename {api-docs => bdk-kotlin/api-docs}/deploy.sh (100%) rename {api-docs => bdk-kotlin/api-docs}/gradle.properties (100%) rename {api-docs => bdk-kotlin/api-docs}/gradle/wrapper/gradle-wrapper.jar (100%) rename {api-docs => bdk-kotlin/api-docs}/gradle/wrapper/gradle-wrapper.properties (100%) rename {api-docs => bdk-kotlin/api-docs}/gradlew (100%) rename {api-docs => bdk-kotlin/api-docs}/gradlew.bat (100%) rename {api-docs => bdk-kotlin/api-docs}/settings.gradle.kts (100%) rename {api-docs => bdk-kotlin/api-docs}/src/main/kotlin/org/bitcoindevkit/bdk.kt (100%) rename {api-docs => bdk-kotlin/api-docs}/src/test/kotlin/org/bitcoindevkit/Samples.kt (100%) rename {bdk-android => bdk-kotlin/bdk-android}/build.gradle.kts (100%) rename {bdk-android => bdk-kotlin/bdk-android}/gradle.properties (100%) rename {bdk-android => bdk-kotlin/bdk-android}/gradle/wrapper/gradle-wrapper.jar (100%) rename {bdk-android => bdk-kotlin/bdk-android}/gradle/wrapper/gradle-wrapper.properties (100%) rename {bdk-android => bdk-kotlin/bdk-android}/gradlew (100%) rename {bdk-android => bdk-kotlin/bdk-android}/gradlew.bat (100%) rename {bdk-android => bdk-kotlin/bdk-android}/lib/build.gradle.kts (100%) rename {bdk-android => bdk-kotlin/bdk-android}/lib/proguard-rules.pro (100%) rename {bdk-android => bdk-kotlin/bdk-android}/lib/src/androidTest/assets/logback.xml (100%) rename {bdk-android => bdk-kotlin/bdk-android}/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt (100%) rename {bdk-android => bdk-kotlin/bdk-android}/lib/src/main/AndroidManifest.xml (100%) rename {bdk-android => bdk-kotlin/bdk-android}/plugins/README.md (100%) rename {bdk-android => bdk-kotlin/bdk-android}/plugins/build.gradle.kts (100%) rename {bdk-android => bdk-kotlin/bdk-android}/plugins/settings.gradle.kts (100%) rename {bdk-android => bdk-kotlin/bdk-android}/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt (100%) rename {bdk-android => bdk-kotlin/bdk-android}/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt (100%) rename {bdk-android => bdk-kotlin/bdk-android}/settings.gradle.kts (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/build.gradle.kts (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/gradle.properties (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/gradle/wrapper/gradle-wrapper.jar (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/gradle/wrapper/gradle-wrapper.properties (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/gradlew (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/gradlew.bat (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/lib/build.gradle.kts (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/plugins/README.md (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/plugins/build.gradle.kts (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/plugins/settings.gradle.kts (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt (100%) rename {bdk-jvm => bdk-kotlin/bdk-jvm}/settings.gradle.kts (100%) diff --git a/bdk-ffi b/bdk-ffi deleted file mode 160000 index 0648075..0000000 --- a/bdk-ffi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0648075555b86365e2408fab484782a2b793b545 diff --git a/.editorconfig b/bdk-kotlin/.editorconfig similarity index 100% rename from .editorconfig rename to bdk-kotlin/.editorconfig diff --git a/.gitignore b/bdk-kotlin/.gitignore similarity index 100% rename from .gitignore rename to bdk-kotlin/.gitignore diff --git a/.gitmodules b/bdk-kotlin/.gitmodules similarity index 100% rename from .gitmodules rename to bdk-kotlin/.gitmodules diff --git a/LICENSE b/bdk-kotlin/LICENSE similarity index 100% rename from LICENSE rename to bdk-kotlin/LICENSE diff --git a/LICENSE-APACHE b/bdk-kotlin/LICENSE-APACHE similarity index 100% rename from LICENSE-APACHE rename to bdk-kotlin/LICENSE-APACHE diff --git a/LICENSE-MIT b/bdk-kotlin/LICENSE-MIT similarity index 100% rename from LICENSE-MIT rename to bdk-kotlin/LICENSE-MIT diff --git a/PGP-BDK-BINDINGS.asc b/bdk-kotlin/PGP-BDK-BINDINGS.asc similarity index 100% rename from PGP-BDK-BINDINGS.asc rename to bdk-kotlin/PGP-BDK-BINDINGS.asc diff --git a/README.md b/bdk-kotlin/README.md similarity index 100% rename from README.md rename to bdk-kotlin/README.md diff --git a/api-docs/Module1.md b/bdk-kotlin/api-docs/Module1.md similarity index 100% rename from api-docs/Module1.md rename to bdk-kotlin/api-docs/Module1.md diff --git a/api-docs/Module2.md b/bdk-kotlin/api-docs/Module2.md similarity index 100% rename from api-docs/Module2.md rename to bdk-kotlin/api-docs/Module2.md diff --git a/api-docs/build.gradle.kts b/bdk-kotlin/api-docs/build.gradle.kts similarity index 84% rename from api-docs/build.gradle.kts rename to bdk-kotlin/api-docs/build.gradle.kts index bbd6ed7..b969511 100644 --- a/api-docs/build.gradle.kts +++ b/bdk-kotlin/api-docs/build.gradle.kts @@ -23,24 +23,24 @@ tasks.withType { kotlinOptions.jvmTarget = "1.8" } -tasks.withType().configureEach { - dokkaSourceSets { - named("main") { - moduleName.set("bdk-android") - moduleVersion.set("0.11.0") - includes.from("Module1.md") - samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt") - } - } -} - // tasks.withType().configureEach { // dokkaSourceSets { // named("main") { -// moduleName.set("bdk-jvm") +// moduleName.set("bdk-android") // moduleVersion.set("0.11.0") -// includes.from("Module2.md") +// includes.from("Module1.md") // samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt") // } // } // } + +tasks.withType().configureEach { + dokkaSourceSets { + named("main") { + moduleName.set("bdk-jvm") + moduleVersion.set("0.11.0") + includes.from("Module2.md") + samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt") + } + } +} diff --git a/api-docs/deploy.sh b/bdk-kotlin/api-docs/deploy.sh similarity index 100% rename from api-docs/deploy.sh rename to bdk-kotlin/api-docs/deploy.sh diff --git a/api-docs/gradle.properties b/bdk-kotlin/api-docs/gradle.properties similarity index 100% rename from api-docs/gradle.properties rename to bdk-kotlin/api-docs/gradle.properties diff --git a/api-docs/gradle/wrapper/gradle-wrapper.jar b/bdk-kotlin/api-docs/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from api-docs/gradle/wrapper/gradle-wrapper.jar rename to bdk-kotlin/api-docs/gradle/wrapper/gradle-wrapper.jar diff --git a/api-docs/gradle/wrapper/gradle-wrapper.properties b/bdk-kotlin/api-docs/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from api-docs/gradle/wrapper/gradle-wrapper.properties rename to bdk-kotlin/api-docs/gradle/wrapper/gradle-wrapper.properties diff --git a/api-docs/gradlew b/bdk-kotlin/api-docs/gradlew similarity index 100% rename from api-docs/gradlew rename to bdk-kotlin/api-docs/gradlew diff --git a/api-docs/gradlew.bat b/bdk-kotlin/api-docs/gradlew.bat similarity index 100% rename from api-docs/gradlew.bat rename to bdk-kotlin/api-docs/gradlew.bat diff --git a/api-docs/settings.gradle.kts b/bdk-kotlin/api-docs/settings.gradle.kts similarity index 100% rename from api-docs/settings.gradle.kts rename to bdk-kotlin/api-docs/settings.gradle.kts diff --git a/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt b/bdk-kotlin/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt similarity index 100% rename from api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt rename to bdk-kotlin/api-docs/src/main/kotlin/org/bitcoindevkit/bdk.kt diff --git a/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt b/bdk-kotlin/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt similarity index 100% rename from api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt rename to bdk-kotlin/api-docs/src/test/kotlin/org/bitcoindevkit/Samples.kt diff --git a/bdk-android/build.gradle.kts b/bdk-kotlin/bdk-android/build.gradle.kts similarity index 100% rename from bdk-android/build.gradle.kts rename to bdk-kotlin/bdk-android/build.gradle.kts diff --git a/bdk-android/gradle.properties b/bdk-kotlin/bdk-android/gradle.properties similarity index 100% rename from bdk-android/gradle.properties rename to bdk-kotlin/bdk-android/gradle.properties diff --git a/bdk-android/gradle/wrapper/gradle-wrapper.jar b/bdk-kotlin/bdk-android/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from bdk-android/gradle/wrapper/gradle-wrapper.jar rename to bdk-kotlin/bdk-android/gradle/wrapper/gradle-wrapper.jar diff --git a/bdk-android/gradle/wrapper/gradle-wrapper.properties b/bdk-kotlin/bdk-android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from bdk-android/gradle/wrapper/gradle-wrapper.properties rename to bdk-kotlin/bdk-android/gradle/wrapper/gradle-wrapper.properties diff --git a/bdk-android/gradlew b/bdk-kotlin/bdk-android/gradlew similarity index 100% rename from bdk-android/gradlew rename to bdk-kotlin/bdk-android/gradlew diff --git a/bdk-android/gradlew.bat b/bdk-kotlin/bdk-android/gradlew.bat similarity index 100% rename from bdk-android/gradlew.bat rename to bdk-kotlin/bdk-android/gradlew.bat diff --git a/bdk-android/lib/build.gradle.kts b/bdk-kotlin/bdk-android/lib/build.gradle.kts similarity index 100% rename from bdk-android/lib/build.gradle.kts rename to bdk-kotlin/bdk-android/lib/build.gradle.kts diff --git a/bdk-android/lib/proguard-rules.pro b/bdk-kotlin/bdk-android/lib/proguard-rules.pro similarity index 100% rename from bdk-android/lib/proguard-rules.pro rename to bdk-kotlin/bdk-android/lib/proguard-rules.pro diff --git a/bdk-android/lib/src/androidTest/assets/logback.xml b/bdk-kotlin/bdk-android/lib/src/androidTest/assets/logback.xml similarity index 100% rename from bdk-android/lib/src/androidTest/assets/logback.xml rename to bdk-kotlin/bdk-android/lib/src/androidTest/assets/logback.xml diff --git a/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/bdk-kotlin/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt similarity index 100% rename from bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt rename to bdk-kotlin/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt diff --git a/bdk-android/lib/src/main/AndroidManifest.xml b/bdk-kotlin/bdk-android/lib/src/main/AndroidManifest.xml similarity index 100% rename from bdk-android/lib/src/main/AndroidManifest.xml rename to bdk-kotlin/bdk-android/lib/src/main/AndroidManifest.xml diff --git a/bdk-android/plugins/README.md b/bdk-kotlin/bdk-android/plugins/README.md similarity index 100% rename from bdk-android/plugins/README.md rename to bdk-kotlin/bdk-android/plugins/README.md diff --git a/bdk-android/plugins/build.gradle.kts b/bdk-kotlin/bdk-android/plugins/build.gradle.kts similarity index 100% rename from bdk-android/plugins/build.gradle.kts rename to bdk-kotlin/bdk-android/plugins/build.gradle.kts diff --git a/bdk-android/plugins/settings.gradle.kts b/bdk-kotlin/bdk-android/plugins/settings.gradle.kts similarity index 100% rename from bdk-android/plugins/settings.gradle.kts rename to bdk-kotlin/bdk-android/plugins/settings.gradle.kts diff --git a/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt b/bdk-kotlin/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt similarity index 100% rename from bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt rename to bdk-kotlin/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt diff --git a/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt b/bdk-kotlin/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt similarity index 100% rename from bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt rename to bdk-kotlin/bdk-android/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiAndroidPlugin.kt diff --git a/bdk-android/settings.gradle.kts b/bdk-kotlin/bdk-android/settings.gradle.kts similarity index 100% rename from bdk-android/settings.gradle.kts rename to bdk-kotlin/bdk-android/settings.gradle.kts diff --git a/bdk-jvm/build.gradle.kts b/bdk-kotlin/bdk-jvm/build.gradle.kts similarity index 100% rename from bdk-jvm/build.gradle.kts rename to bdk-kotlin/bdk-jvm/build.gradle.kts diff --git a/bdk-jvm/gradle.properties b/bdk-kotlin/bdk-jvm/gradle.properties similarity index 100% rename from bdk-jvm/gradle.properties rename to bdk-kotlin/bdk-jvm/gradle.properties diff --git a/bdk-jvm/gradle/wrapper/gradle-wrapper.jar b/bdk-kotlin/bdk-jvm/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from bdk-jvm/gradle/wrapper/gradle-wrapper.jar rename to bdk-kotlin/bdk-jvm/gradle/wrapper/gradle-wrapper.jar diff --git a/bdk-jvm/gradle/wrapper/gradle-wrapper.properties b/bdk-kotlin/bdk-jvm/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from bdk-jvm/gradle/wrapper/gradle-wrapper.properties rename to bdk-kotlin/bdk-jvm/gradle/wrapper/gradle-wrapper.properties diff --git a/bdk-jvm/gradlew b/bdk-kotlin/bdk-jvm/gradlew similarity index 100% rename from bdk-jvm/gradlew rename to bdk-kotlin/bdk-jvm/gradlew diff --git a/bdk-jvm/gradlew.bat b/bdk-kotlin/bdk-jvm/gradlew.bat similarity index 100% rename from bdk-jvm/gradlew.bat rename to bdk-kotlin/bdk-jvm/gradlew.bat diff --git a/bdk-jvm/lib/build.gradle.kts b/bdk-kotlin/bdk-jvm/lib/build.gradle.kts similarity index 100% rename from bdk-jvm/lib/build.gradle.kts rename to bdk-kotlin/bdk-jvm/lib/build.gradle.kts diff --git a/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/bdk-kotlin/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt similarity index 100% rename from bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt rename to bdk-kotlin/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt diff --git a/bdk-jvm/plugins/README.md b/bdk-kotlin/bdk-jvm/plugins/README.md similarity index 100% rename from bdk-jvm/plugins/README.md rename to bdk-kotlin/bdk-jvm/plugins/README.md diff --git a/bdk-jvm/plugins/build.gradle.kts b/bdk-kotlin/bdk-jvm/plugins/build.gradle.kts similarity index 100% rename from bdk-jvm/plugins/build.gradle.kts rename to bdk-kotlin/bdk-jvm/plugins/build.gradle.kts diff --git a/bdk-jvm/plugins/settings.gradle.kts b/bdk-kotlin/bdk-jvm/plugins/settings.gradle.kts similarity index 100% rename from bdk-jvm/plugins/settings.gradle.kts rename to bdk-kotlin/bdk-jvm/plugins/settings.gradle.kts diff --git a/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt b/bdk-kotlin/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt similarity index 100% rename from bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt rename to bdk-kotlin/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/Enums.kt diff --git a/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt b/bdk-kotlin/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt similarity index 100% rename from bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt rename to bdk-kotlin/bdk-jvm/plugins/src/main/kotlin/org/bitcoindevkit/plugins/UniFfiJvmPlugin.kt diff --git a/bdk-jvm/settings.gradle.kts b/bdk-kotlin/bdk-jvm/settings.gradle.kts similarity index 100% rename from bdk-jvm/settings.gradle.kts rename to bdk-kotlin/bdk-jvm/settings.gradle.kts