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() }