Simplify Kotlin Wallet api

This commit is contained in:
Steve Myers 2021-06-24 13:49:25 -07:00
parent 110dfbd13c
commit 757113c002
15 changed files with 125 additions and 117 deletions

View File

@ -1,9 +1,6 @@
package org.bitcoindevkit.bdk package org.bitcoindevkit.bdk
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.sun.jna.Native
import org.junit.*
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -16,5 +13,5 @@ import org.junit.Assert.*
*/ */
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class AndroidLibTest : LibTest() { class AndroidLibTest : LibTest() {
} }

View File

@ -17,8 +17,7 @@ dependencies {
api "org.slf4j:slf4j-api:1.7.30" api "org.slf4j:slf4j-api:1.7.30"
testImplementation "ch.qos.logback:logback-classic:1.2.3" testImplementation "ch.qos.logback:logback-classic:1.2.3"
testImplementation "ch.qos.logback:logback-core: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 { publishing {

View File

@ -1,6 +1,6 @@
package org.bitcoindevkit.bdk package org.bitcoindevkit.bdk
enum class Error { enum class JnaError {
InvalidU32Bytes, InvalidU32Bytes,
Generic, Generic,
ScriptDoesntHaveAddressForm, ScriptDoesntHaveAddressForm,
@ -37,7 +37,8 @@ enum class Error {
Hex, Hex,
Psbt, Psbt,
Electrum, Electrum,
// Esplora
// Esplora
// CompactFilters // CompactFilters
Sled, Sled,
} }

View File

@ -0,0 +1,3 @@
package org.bitcoindevkit.bdk
class JnaException internal constructor(val err: JnaError) : Exception()

View File

@ -3,7 +3,7 @@ package org.bitcoindevkit.bdk
import com.sun.jna.Native import com.sun.jna.Native
abstract class LibBase { abstract class LibBase {
protected val libJna: LibJna protected val libJna: LibJna
get() = Native.load("bdk_ffi", LibJna::class.java) get() = Native.load("bdk_ffi", LibJna::class.java)
} }

View File

@ -1,6 +1,8 @@
package org.bitcoindevkit.bdk 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 { interface LibJna : Library {
@ -35,13 +37,13 @@ interface LibJna : Library {
// void free_string_result ( // void free_string_result (
// StringResult_t * string_result); // StringResult_t * string_result);
fun free_string_result(string_result: StringResult_t) fun free_string_result(string_result: StringResult_t)
// typedef struct WalletRef WalletRef_t; // typedef struct WalletRef WalletRef_t;
class WalletRef_t : PointerType { class WalletRef_t : PointerType {
constructor() : super() constructor() : super()
constructor(pointer: Pointer) : super(pointer) constructor(pointer: Pointer) : super(pointer)
} }
// void free_wallet_ref ( // void free_wallet_ref (
// WalletRef_t * wallet_ref); // WalletRef_t * wallet_ref);
fun free_wallet_ref(wallet_ref: WalletRef_t) fun free_wallet_ref(wallet_ref: WalletRef_t)
@ -61,11 +63,11 @@ interface LibJna : Library {
descriptor: String, descriptor: String,
changeDescriptor: String? changeDescriptor: String?
): WalletResult_t ): WalletResult_t
// char * get_wallet_err ( // char * get_wallet_err (
// WalletResult_t const * wallet_result); // WalletResult_t const * wallet_result);
fun get_wallet_err(wallet_result: WalletResult_t): Pointer? fun get_wallet_err(wallet_result: WalletResult_t): Pointer?
// WalletRef_t * get_wallet_ok ( // WalletRef_t * get_wallet_ok (
// WalletResult_t const * wallet_result); // WalletResult_t const * wallet_result);
fun get_wallet_ok(wallet_result: WalletResult_t): WalletRef_t? fun get_wallet_ok(wallet_result: WalletResult_t): WalletRef_t?

View File

@ -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<PT : PointerType, RT : Any> 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")
}
}

View File

@ -1,32 +1,26 @@
package org.bitcoindevkit.bdk package org.bitcoindevkit.bdk
import com.sun.jna.Pointer
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
class StringResult internal constructor(private val stringResultT: LibJna.StringResult_t) : LibBase() { class StringResult internal constructor(stringResultT: LibJna.StringResult_t) :
ResultBase<LibJna.StringResult_t, String>(stringResultT) {
private val log: Logger = LoggerFactory.getLogger(StringResult::class.java) override val log: Logger = LoggerFactory.getLogger(StringResult::class.java)
fun isErr(): Boolean { override fun err(): Pointer? {
return libJna.get_string_err(stringResultT) != null return libJna.get_string_err(resultT)
} }
fun err(): Error? { override fun ok(): String {
val errPointer = libJna.get_string_err(stringResultT) val okPointer = libJna.get_string_ok(resultT)
val err = errPointer?.getString(0) val ok = okPointer!!.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)
libJna.free_string(okPointer) libJna.free_string(okPointer)
return ok return ok
} }
protected fun finalize() { override fun free(pointer: LibJna.StringResult_t) {
libJna.free_string_result(stringResultT) libJna.free_string_result(resultT)
log.debug("StringResult_t freed")
} }
} }

View File

@ -1,25 +1,23 @@
package org.bitcoindevkit.bdk package org.bitcoindevkit.bdk
import com.sun.jna.Pointer
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
class VoidResult internal constructor(private val voidResultT: LibJna.VoidResult_t) : LibBase() { class VoidResult internal constructor(voidResultT: LibJna.VoidResult_t) :
ResultBase<LibJna.VoidResult_t, Unit>(voidResultT) {
private val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) override val log: Logger = LoggerFactory.getLogger(VoidResult::class.java)
fun isErr(): Boolean { override fun err(): Pointer? {
return libJna.get_void_err(voidResultT) != null return libJna.get_void_err(resultT)
} }
fun err(): Error? { override fun ok() {
val errPointer = libJna.get_void_err(voidResultT) // Void
val err = errPointer?.getString(0)
libJna.free_string(errPointer)
return err?.let { Error.valueOf(it) }
} }
protected fun finalize() { override fun free(pointer: LibJna.VoidResult_t) {
libJna.free_void_result(voidResultT) libJna.free_void_result(resultT)
log.debug("VoidResult_t freed")
} }
} }

View File

@ -3,23 +3,30 @@ package org.bitcoindevkit.bdk
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
class Wallet internal constructor( class Wallet constructor(
private val walletRefT: LibJna.WalletRef_t, name: String,
descriptor: String,
changeDescriptor: String?,
) : LibBase() { ) : LibBase() {
private val log: Logger = LoggerFactory.getLogger(Wallet::class.java) val log: Logger = LoggerFactory.getLogger(Wallet::class.java)
fun sync(): VoidResult { private val walletResult =
return VoidResult(libJna.sync_wallet(walletRefT.pointer)) WalletResult(libJna.new_wallet_result(name, descriptor, changeDescriptor))
} private val walletRefT = walletResult.value()
fun getAddress(): StringResult { fun sync() {
return StringResult(libJna.new_address(walletRefT.pointer)) 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) libJna.free_wallet_ref(walletRefT)
log.debug("WalletRef_t freed") log.debug("$walletRefT freed")
} }
} }

View File

@ -1,36 +1,23 @@
package org.bitcoindevkit.bdk package org.bitcoindevkit.bdk
import com.sun.jna.Pointer
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
class WalletResult( class WalletResult internal constructor(walletResultT: LibJna.WalletResult_t) :
name: String, ResultBase<LibJna.WalletResult_t, LibJna.WalletRef_t>(walletResultT) {
descriptor: String,
changeDescriptor: String?, override val log: Logger = LoggerFactory.getLogger(WalletResult::class.java)
) : LibBase() {
override fun err(): Pointer? {
private val log: Logger = LoggerFactory.getLogger(WalletResult::class.java) return libJna.get_wallet_err(resultT)
private val walletResultT = libJna.new_wallet_result(name, descriptor, changeDescriptor)
fun isErr(): Boolean {
return libJna.get_wallet_err(walletResultT) != null
} }
fun err(): Error? { override fun ok(): LibJna.WalletRef_t {
val errPointer = libJna.get_wallet_err(walletResultT) return libJna.get_wallet_ok(resultT)!!
val err = errPointer?.getString(0)
libJna.free_string(errPointer)
return err?.let { Error.valueOf(it) }
} }
fun ok(): Wallet? { override fun free(pointer: LibJna.WalletResult_t) {
val okWalletRef = libJna.get_wallet_ok(walletResultT) libJna.free_wallet_result(resultT)
return if (okWalletRef != null) Wallet(okWalletRef) else null
} }
protected fun finalize() {
libJna.free_wallet_result(walletResultT)
log.debug("WalletResult_t freed")
}
} }

View File

@ -1,15 +1,9 @@
package org.bitcoindevkit.bdk 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. * Library test, which will execute on linux host.
* *
*/ */
class JvmLibTest : LibTest() { class JvmLibTest : LibTest() {
} }

View File

@ -8,7 +8,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "net.java.dev.jna:jna:5.8.0" implementation "net.java.dev.jna:jna:5.8.0"
implementation (project(':jvm')) 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" //implementation "org.mockito.kotlin:mockito-kotlin:3.2.0"
api "org.slf4j:slf4j-api:1.7.30" api "org.slf4j:slf4j-api:1.7.30"
} }

View File

@ -4,6 +4,7 @@ import org.junit.*
import org.junit.Assert.* import org.junit.Assert.*
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import javax.management.Descriptor
/** /**
* Library test, which will execute on linux host. * Library test, which will execute on linux host.
@ -21,21 +22,17 @@ abstract class LibTest : LibBase() {
@Test @Test
fun walletResultError() { fun walletResultError() {
val badWalletResult = WalletResult("bad", "bad", "bad") val jnaException = assertThrows(JnaException::class.java) {
assertTrue(badWalletResult.isErr()) Wallet("bad", "bad", "bad")
val walletErr = badWalletResult.err() }
assertNotNull(walletErr) assertEquals(jnaException.err, JnaError.Descriptor)
log.debug("wallet error $walletErr")
assertEquals(Error.Descriptor, walletErr)
val wallet = badWalletResult.ok()
assertNull(wallet)
} }
@Test @Test
fun walletResultFinalize() { fun walletResultFinalize() {
val names = listOf("one", "two", "three") val names = listOf("one", "two", "three")
names.map { names.map {
val wallet = WalletResult(it, desc, change) val wallet = Wallet(it, desc, change)
assertNotNull(wallet) assertNotNull(wallet)
} }
System.gc() System.gc()
@ -44,23 +41,16 @@ abstract class LibTest : LibBase() {
@Test @Test
fun walletSync() { fun walletSync() {
val walletResult = WalletResult(name, desc, change) val wallet = Wallet(name, desc, change)
val wallet = walletResult.ok()
assertNotNull(wallet) assertNotNull(wallet)
val syncResult = wallet!!.sync(); wallet.sync()
assertFalse(syncResult.isErr())
assertNull(syncResult.err())
} }
@Test @Test
fun walletNewAddress() { fun walletNewAddress() {
val walletResult = WalletResult(name, desc, change) val wallet = Wallet(name, desc, change)
val wallet = walletResult.ok()
assertNotNull(wallet) assertNotNull(wallet)
val addressResult = wallet!!.getAddress() val address = wallet.getAddress()
assertFalse(addressResult.isErr())
assertNull(addressResult.err())
val address = addressResult.ok()
assertNotNull(address) assertNotNull(address)
log.debug("address created from kotlin: $address") log.debug("address created from kotlin: $address")
assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e")

View File

@ -129,13 +129,12 @@ fn get_wallet_ok<'lt>(wallet_result: &'lt WalletResult) -> Option<Box<WalletRef<
.raw .raw
.as_ref() .as_ref()
.ok() .ok()
.map(|w| Box::new(WalletRef { raw: w})) .map(|w| Box::new(WalletRef { raw: w }))
} }
#[ffi_export] #[ffi_export]
fn sync_wallet<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box<VoidResult> { fn sync_wallet<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box<VoidResult> {
let void_result = wallet_ref let void_result = wallet_ref.raw.sync(log_progress(), Some(100));
.raw.sync(log_progress(), Some(100));
Box::new(VoidResult { raw: void_result }) Box::new(VoidResult { raw: void_result })
} }