diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0f36985..226342c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,6 +7,23 @@ jobs: test: name: "Build library and run unit tests" runs-on: ubuntu-24.04 + + services: + bitcoin-regtest: + image: ghcr.io/thunderbiscuit/podman-regtest-infinity-pro:0.3.2 + ports: + - 18443:18443 + - 18444:18444 + - 3002:3002 + - 3003:3003 + - 60401:60401 + # The container initialization step will wait until everything is ready before allowing other steps to run + options: >- + --health-cmd "test $(bitcoin-cli -regtest -rpcuser=regtest -rpcpassword=password getblockcount) -gt 0" + --health-interval 10s + --health-timeout 5s + --health-retries 6 + steps: - name: "Check out PR branch" uses: actions/checkout@v4 diff --git a/bdk-ffi b/bdk-ffi index 08ec19b..ecfc0b9 160000 --- a/bdk-ffi +++ b/bdk-ffi @@ -1 +1 @@ -Subproject commit 08ec19b9bfb5cbc2c1fb6d003c58a25b01fa25e6 +Subproject commit ecfc0b9ca68d01d503db78488381a066696a9be6 diff --git a/build.gradle.kts b/build.gradle.kts index 098a469..9d90651 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,13 +1,5 @@ plugins { - id("org.jetbrains.kotlin.jvm").version("2.1.10").apply(false) - id("org.gradle.java-library") - id("org.gradle.maven-publish") - id("org.gradle.signing") - id("org.jetbrains.dokka").version("2.0.0").apply(false) - id("org.jetbrains.dokka-javadoc").version("2.0.0").apply(false) + id("org.jetbrains.kotlin.jvm").version("2.2.0").apply(false) + id("org.jetbrains.dokka").version("2.1.0").apply(false) + id("com.vanniktech.maven.publish").version("0.36.0").apply(false) } - -// library version is defined in gradle.properties -val libraryVersion: String by project -group = "org.bitcoindevkit" -version = libraryVersion diff --git a/lib/README.md b/docs/DOKKA_LANDING.md similarity index 92% rename from lib/README.md rename to docs/DOKKA_LANDING.md index 1d28d65..2b9e751 100644 --- a/lib/README.md +++ b/docs/DOKKA_LANDING.md @@ -1,7 +1,9 @@ # Module bdk-jvm -The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Kotlin on the JVM. +The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Kotlin on the JVM. -# Package org.bitcoindevkit +
The functionality exposed in this package is in fact a combination of the [bdk_wallet](https://crates.io/crates/bdk_wallet), [bdk_core](https://crates.io/crates/bdk_core), [bdk_electrum](https://crates.io/crates/bdk_electrum), [bdk_esplora](https://crates.io/crates/bdk_esplora), [bitcoin](https://crates.io/crates/bitcoin), and [miniscript](https://crates.io/crates/miniscript) crates. + +# Package org.bitcoindevkit diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index ce3ac04..9104e7a 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -1,8 +1,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id("org.jetbrains.kotlin.jvm") version "2.1.10" - id("application") + id("org.jetbrains.kotlin.jvm") } repositories { @@ -11,16 +11,11 @@ repositories { dependencies { implementation(project(":lib")) - testImplementation(kotlin("test")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") implementation("org.slf4j:slf4j-api:2.0.16") implementation("ch.qos.logback:logback-classic:1.5.13") } -tasks.test { - useJUnitPlatform() -} - java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -28,7 +23,7 @@ java { withJavadocJar() } -tasks.withType { +tasks.withType { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } diff --git a/gradle.properties b/gradle.properties index 50fb5a3..03d74fb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,5 @@ org.gradle.jvmargs=-Xmx1536m android.enableJetifier=true kotlin.code.style=official -libraryVersion=2.3.0 org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135..e644113 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e7646de..5f38436 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # 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"' -# 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. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/justfile b/justfile index 6eaa68e..20a1028 100644 --- a/justfile +++ b/justfile @@ -16,7 +16,7 @@ docs: [group("Repo")] [doc("Publish the library to your local Maven repository.")] publish-local: - ./gradlew publishToMavenLocal -P localBuild + ./gradlew publishToMavenLocal [group("Submodule")] [doc("Initialize bdk-ffi submodule to committed hash.")] @@ -58,6 +58,8 @@ clean: rm -rf ./lib/build/ rm -rf ./examples/build/ rm -rf ./examples/data/ + rm -rf ./lib/src/main/kotlin/org/bitcoindevkit/* + rm -rf ./lib/src/main/resources/* [group("Test")] [doc("Run all tests, unless a specific test is provided.")] diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 546245a..0c9d927 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -1,44 +1,37 @@ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat.* -import org.gradle.api.tasks.testing.logging.TestLogEvent.* +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.KotlinJvm +import com.vanniktech.maven.publish.SourcesJar +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -// library version is defined in gradle.properties -val libraryVersion: String by project - plugins { id("org.jetbrains.kotlin.jvm") - id("org.gradle.java-library") - id("org.gradle.maven-publish") - id("org.gradle.signing") + id("com.vanniktech.maven.publish") id("org.jetbrains.dokka") - id("org.jetbrains.dokka-javadoc") } +group = "org.bitcoindevkit" +version = "2.3.0-SNAPSHOT" + java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 - withSourcesJar() - withJavadocJar() } -tasks.withType { - kotlinOptions { - jvmTarget = "11" +tasks.withType { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) } } -testing { - suites { - val test by getting(JvmTestSuite::class) { - useKotlinTest("1.9.23") - } - } -} +tasks.test { + useJUnitPlatform() -tasks.withType { testLogging { - events(PASSED, SKIPPED, FAILED, STANDARD_OUT, STANDARD_ERROR) - exceptionFormat = FULL + events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED, TestLogEvent.STANDARD_OUT) + exceptionFormat = TestExceptionFormat.FULL showExceptions = true showStackTraces = true showCauses = true @@ -46,63 +39,69 @@ tasks.withType { } dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") + // JNA implementation("net.java.dev.jna:jna:5.14.0") + + // Coroutines + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") + + // Tests + testImplementation(kotlin("test")) + testImplementation("org.kotlinbitcointools:regtest-toolbox:0.1.0") } -afterEvaluate { - publishing { - publications { - create("maven") { - groupId = "org.bitcoindevkit" - artifactId = "bdk-jvm" - version = libraryVersion +mavenPublishing { + coordinates( + groupId = group.toString(), + artifactId = "bdk-jvm", + version = version.toString() + ) - 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("bdkdevelopers") - name.set("Bitcoin Dev Kit Developers") - email.set("dev@bitcoindevkit.org") - } - } - 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") - } - } + pom { + name.set("bdk-jvm") + description.set("Bitcoin Dev Kit Kotlin language bindings.") + url.set("https://bitcoindevkit.org") + inceptionYear.set("2021") + 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("bdkdevelopers") + name.set("Bitcoin Dev Kit Developers") + email.set("dev@bitcoindevkit.org") } } + scm { + url.set("https://github.com/bitcoindevkit/bdk-ffi/") + connection.set("scm:git:github.com/bitcoindevkit/bdk-ffi.git") + developerConnection.set("scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git") + } } -} -signing { - if (project.hasProperty("localBuild")) { - isRequired = false - } - sign(publishing.publications) + configure( + KotlinJvm( + javadocJar = JavadocJar.Dokka("dokkaGeneratePublicationHtml"), + sourcesJar = SourcesJar.Sources(), + ) + ) + + publishToMavenCentral() + signAllPublications() } dokka { moduleName.set("bdk-jvm") - moduleVersion.set(libraryVersion) + moduleVersion.set(version.toString()) dokkaSourceSets.main { - includes.from("README.md") + includes.from("../docs/DOKKA_LANDING.md") sourceLink { localDirectory.set(file("src/main/kotlin")) remoteUrl("https://bitcoindevkit.org/") diff --git a/lib/src/test/kotlin/org/bitcoindevkit/CbfSyncTest.kt b/lib/src/test/kotlin/org/bitcoindevkit/CbfSyncTest.kt new file mode 100644 index 0000000..0ea6c0f --- /dev/null +++ b/lib/src/test/kotlin/org/bitcoindevkit/CbfSyncTest.kt @@ -0,0 +1,55 @@ +package org.bitcoindevkit + +import org.junit.jupiter.api.Nested +import org.kotlinbitcointools.regtesttoolbox.regenv.RegEnv +import kotlinx.coroutines.runBlocking +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.test.Test + +class CbfSyncTest { + private val conn: Persister = Persister.newInMemory() + + @Nested + inner class Success { + @Test + fun `Successful sync using a Kyoto client`() { + runBlocking { + val regtestEnv = RegEnv.connectTo(walletName = "faucet", username = "regtest", password = "password") + + val wallet: Wallet = Wallet.createSingle( + descriptor = TEST_BIP86_DESCRIPTOR, + network = Network.REGTEST, + persister = conn + ) + val newAddress = wallet.revealNextAddress(KeychainKind.EXTERNAL).address + + regtestEnv.send(newAddress.toString(), 0.12345678, 2.0) + regtestEnv.mine(2) + + val persistenceDir: Path = Paths.get("src/test/data").toAbsolutePath().normalize() + Files.createDirectories(persistenceDir) + val persistenceFilePath: Path = Files.createTempDirectory(persistenceDir, "kyoto_data_") + + val ip: IpAddress = IpAddress.fromIpv4(127u, 0u, 0u, 1u) + val peer1: Peer = Peer(ip, 18444u, false) + val peers: List = listOf(peer1) + val (client, node) = CbfBuilder() + .peers(peers) + .connections(1u) + .scanType(ScanType.Sync) + .dataDir(persistenceFilePath.toString()) + .build(wallet) + + node.run() + val update: Update = client.update() + wallet.applyUpdate(update) + + val balance = wallet.balance().total.toSat() + assert(balance > 0uL) + client.shutdown() + } + } + } +} \ No newline at end of file