Refactor to return results by value, add wallet list_unspent and related types
This commit is contained in:
parent
f5dd87b02a
commit
273cad8318
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,7 +1,6 @@
|
|||||||
target
|
target
|
||||||
build
|
build
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
*.h
|
|
||||||
/bdk-kotlin/local.properties
|
/bdk-kotlin/local.properties
|
||||||
.gradle
|
.gradle
|
||||||
wallet_db
|
wallet_db
|
||||||
|
18
README.md
Normal file
18
README.md
Normal file
@ -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
|
@ -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
|
||||||
|
}
|
@ -1,52 +1,48 @@
|
|||||||
package org.bitcoindevkit.bdk
|
package org.bitcoindevkit.bdk
|
||||||
|
|
||||||
import com.sun.jna.Library
|
import com.sun.jna.*
|
||||||
import com.sun.jna.Pointer
|
|
||||||
import com.sun.jna.PointerType
|
|
||||||
|
|
||||||
interface LibJna : Library {
|
interface LibJna : Library {
|
||||||
|
|
||||||
// typedef struct VoidResult VoidResult_t;
|
// typedef struct {
|
||||||
class VoidResult_t : PointerType {
|
//
|
||||||
constructor() : super()
|
// char * * ok;
|
||||||
constructor(pointer: Pointer) : super(pointer)
|
//
|
||||||
|
// 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 (
|
// void free_string_result (
|
||||||
// StringResult_t * string_result);
|
// FfiResult_char_ptr_t string_result);
|
||||||
fun free_string_result(string_result: StringResult_t)
|
fun free_string_result(string_result: FfiResult_char_ptr_t.ByValue)
|
||||||
|
|
||||||
// typedef struct WalletRef WalletRef_t;
|
// typedef struct {
|
||||||
class WalletRef_t : PointerType {
|
//
|
||||||
constructor() : super()
|
// void * ok;
|
||||||
constructor(pointer: Pointer) : super(pointer)
|
//
|
||||||
|
// 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 (
|
// void free_void_result (
|
||||||
// WalletRef_t * wallet_ref);
|
// FfiResult_void_t void_result);
|
||||||
fun free_wallet_ref(wallet_ref: WalletRef_t)
|
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;
|
// typedef struct BlockchainConfig BlockchainConfig_t;
|
||||||
class BlockchainConfig_t : PointerType {
|
class BlockchainConfig_t : PointerType {
|
||||||
@ -54,54 +50,6 @@ interface LibJna : Library {
|
|||||||
constructor(pointer: Pointer) : super(pointer)
|
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 (
|
// BlockchainConfig_t * new_electrum_config (
|
||||||
// char const * url,
|
// char const * url,
|
||||||
// char const * socks5,
|
// char const * socks5,
|
||||||
@ -118,6 +66,193 @@ interface LibJna : Library {
|
|||||||
// BlockchainConfig_t * blockchain_config);
|
// BlockchainConfig_t * blockchain_config);
|
||||||
fun free_blockchain_config(blockchain_config: BlockchainConfig_t)
|
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);
|
// DatabaseConfig_t * new_memory_config (void);
|
||||||
fun new_memory_config(): DatabaseConfig_t
|
fun new_memory_config(): DatabaseConfig_t
|
||||||
|
|
||||||
|
37
bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Result.kt
Normal file
37
bdk-kotlin/jvm/src/main/java/org/bitcoindevkit/bdk/Result.kt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package org.bitcoindevkit.bdk
|
||||||
|
|
||||||
|
import com.sun.jna.Pointer
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
abstract class Result<T : FfiResult, RT : Any>(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")
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,15 @@
|
|||||||
package org.bitcoindevkit.bdk
|
package org.bitcoindevkit.bdk
|
||||||
|
|
||||||
import com.sun.jna.Pointer
|
import com.sun.jna.Pointer
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class StringResult internal constructor(stringResultT: LibJna.StringResult_t) :
|
class StringResult constructor(stringResultPtr: LibJna.FfiResult_char_ptr_t.ByValue) :
|
||||||
ResultBase<LibJna.StringResult_t, String>(stringResultT) {
|
Result<LibJna.FfiResult_char_ptr_t.ByValue, String>(stringResultPtr) {
|
||||||
|
|
||||||
override val log: Logger = LoggerFactory.getLogger(StringResult::class.java)
|
override fun getOkValue(pointer: Pointer): String {
|
||||||
|
return pointer.getPointer(0).getString(0)
|
||||||
override fun err(pointerT: LibJna.StringResult_t): Pointer? {
|
|
||||||
return libJna.get_string_err(pointerT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ok(pointerT: LibJna.StringResult_t): String {
|
override fun freeResult(ffiResult: LibJna.FfiResult_char_ptr_t.ByValue) {
|
||||||
val okPointer = libJna.get_string_ok(pointerT)
|
libJna.free_string_result(ffiResult)
|
||||||
val ok = okPointer!!.getString(0)
|
|
||||||
libJna.free_string(okPointer)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun free(pointerT: LibJna.StringResult_t) {
|
|
||||||
libJna.free_string_result(pointerT)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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<LibJna.LocalUtxo_t.ByReference> {
|
||||||
|
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<LibJna.LocalUtxo_t.ByReference>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun finalize() {
|
||||||
|
libJna.free_unspent_result(ffiResult)
|
||||||
|
log.debug("$ffiResult freed")
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,15 @@
|
|||||||
package org.bitcoindevkit.bdk
|
package org.bitcoindevkit.bdk
|
||||||
|
|
||||||
import com.sun.jna.Pointer
|
import com.sun.jna.Pointer
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class VoidResult internal constructor(voidResultT: LibJna.VoidResult_t) :
|
class VoidResult constructor(voidResultPtr: LibJna.FfiResult_void_t.ByValue) :
|
||||||
ResultBase<LibJna.VoidResult_t, Unit>(voidResultT) {
|
Result<LibJna.FfiResult_void_t.ByValue, Unit>(voidResultPtr) {
|
||||||
|
|
||||||
override val log: Logger = LoggerFactory.getLogger(VoidResult::class.java)
|
override fun getOkValue(pointer: Pointer) {
|
||||||
|
// No value
|
||||||
override fun err(pointerT: LibJna.VoidResult_t): Pointer? {
|
|
||||||
return libJna.get_void_err(pointerT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ok(pointerT: LibJna.VoidResult_t) {
|
override fun freeResult(ffiResult: LibJna.FfiResult_void_t.ByValue) {
|
||||||
// Void
|
libJna.free_void_result(ffiResult)
|
||||||
}
|
|
||||||
|
|
||||||
override fun free(pointerT: LibJna.VoidResult_t) {
|
|
||||||
libJna.free_void_result(pointerT)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,29 +12,28 @@ class Wallet constructor(
|
|||||||
|
|
||||||
val log: Logger = LoggerFactory.getLogger(Wallet::class.java)
|
val log: Logger = LoggerFactory.getLogger(Wallet::class.java)
|
||||||
|
|
||||||
private val walletResult =
|
private val walletResult = WalletResult(
|
||||||
WalletResult(
|
libJna.new_wallet_result(
|
||||||
libJna.new_wallet_result(
|
descriptor,
|
||||||
descriptor,
|
changeDescriptor,
|
||||||
changeDescriptor,
|
blockchainConfig.blockchainConfigT,
|
||||||
blockchainConfig.blockchainConfigT,
|
databaseConfig.databaseConfigT
|
||||||
databaseConfig.databaseConfigT
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
private val walletRefT = walletResult.value()
|
)
|
||||||
|
private val wallet = walletResult.value()
|
||||||
|
|
||||||
fun sync() {
|
fun sync() {
|
||||||
val voidResult = VoidResult(libJna.sync_wallet(walletRefT.pointer))
|
val voidResult = VoidResult(libJna.sync_wallet(wallet))
|
||||||
return voidResult.value()
|
return voidResult.value()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAddress(): String {
|
fun getAddress(): String {
|
||||||
val stringResult = StringResult(libJna.new_address(walletRefT.pointer))
|
val stringResult = StringResult(libJna.new_address(wallet))
|
||||||
return stringResult.value()
|
return stringResult.value()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun finalize(walletRefT: LibJna.WalletRef_t) {
|
fun listUnspent(): Array<LibJna.LocalUtxo_t.ByReference> {
|
||||||
libJna.free_wallet_ref(walletRefT)
|
val vecLocalUtxoResult = VecLocalUtxoResult(libJna.list_unspent(wallet))
|
||||||
log.debug("$walletRefT freed")
|
return vecLocalUtxoResult.value()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,23 +1,15 @@
|
|||||||
package org.bitcoindevkit.bdk
|
package org.bitcoindevkit.bdk
|
||||||
|
|
||||||
import com.sun.jna.Pointer
|
import com.sun.jna.Pointer
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class WalletResult internal constructor(walletResultT: LibJna.WalletResult_t) :
|
class WalletResult constructor(walletResultPtr: LibJna.FfiResult_OpaqueWallet_t.ByValue) :
|
||||||
ResultBase<LibJna.WalletResult_t, LibJna.WalletRef_t>(walletResultT) {
|
Result<LibJna.FfiResult_OpaqueWallet_t.ByValue, LibJna.OpaqueWallet_t>(walletResultPtr) {
|
||||||
|
|
||||||
override val log: Logger = LoggerFactory.getLogger(WalletResult::class.java)
|
override fun getOkValue(pointer: Pointer): LibJna.OpaqueWallet_t {
|
||||||
|
return LibJna.OpaqueWallet_t(pointer)
|
||||||
override fun err(pointerT: LibJna.WalletResult_t): Pointer? {
|
|
||||||
return libJna.get_wallet_err(pointerT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun ok(pointerT: LibJna.WalletResult_t): LibJna.WalletRef_t {
|
override fun freeResult(ffiResult: LibJna.FfiResult_OpaqueWallet_t.ByValue) {
|
||||||
return libJna.get_wallet_ok(pointerT)!!
|
libJna.free_wallet_result(ffiResult)
|
||||||
}
|
|
||||||
|
|
||||||
override fun free(pointerT: LibJna.WalletResult_t) {
|
|
||||||
libJna.free_wallet_result(pointerT)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,6 +19,9 @@ abstract class LibTest : LibBase() {
|
|||||||
val change =
|
val change =
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
|
"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
|
abstract fun getTestDataDir(): String
|
||||||
|
|
||||||
fun cleanupTestDataDir() {
|
fun cleanupTestDataDir() {
|
||||||
@ -27,48 +30,69 @@ abstract class LibTest : LibBase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun walletResultError() {
|
fun walletResultError() {
|
||||||
val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30)
|
|
||||||
val databaseConfig = MemoryConfig()
|
|
||||||
val jnaException = assertThrows(JnaException::class.java) {
|
val jnaException = assertThrows(JnaException::class.java) {
|
||||||
Wallet("bad", "bad", blockchainConfig, databaseConfig)
|
Wallet("bad", "bad", blockchainConfig, databaseConfig)
|
||||||
}
|
}
|
||||||
assertEquals(jnaException.err, JnaError.Descriptor)
|
assertEquals(jnaException.err, JnaError.Descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
// @Test
|
||||||
fun walletResultFinalize() {
|
// fun walletResultFinalize() {
|
||||||
val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30)
|
// run {
|
||||||
val databaseConfig = MemoryConfig()
|
// val desc =
|
||||||
val names = listOf("one", "two", "three")
|
// "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
|
||||||
names.map {
|
// val change =
|
||||||
val wallet = Wallet(desc, change, blockchainConfig, databaseConfig)
|
// "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
|
||||||
assertNotNull(wallet)
|
//
|
||||||
}
|
// val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30)
|
||||||
System.gc()
|
// val databaseConfig = MemoryConfig()
|
||||||
// The only way to verify wallets freed is by checking the log
|
// 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
|
@Test
|
||||||
fun walletSync() {
|
fun walletSync() {
|
||||||
val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30)
|
val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30)
|
||||||
val testDataDir = getTestDataDir()
|
val testDataDir = getTestDataDir()
|
||||||
log.debug("testDataDir = $testDataDir")
|
// log.debug("testDataDir = $testDataDir")
|
||||||
val databaseConfig = SledConfig(testDataDir, "steve-test")
|
val databaseConfig = SledConfig(testDataDir, "steve-test")
|
||||||
val wallet = Wallet(desc, change, blockchainConfig, databaseConfig)
|
val wallet = Wallet(desc, change, blockchainConfig, databaseConfig)
|
||||||
assertNotNull(wallet)
|
|
||||||
wallet.sync()
|
wallet.sync()
|
||||||
cleanupTestDataDir()
|
cleanupTestDataDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun walletNewAddress() {
|
fun walletNewAddress() {
|
||||||
val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30)
|
|
||||||
val databaseConfig = MemoryConfig()
|
|
||||||
val wallet = Wallet(desc, change, blockchainConfig, databaseConfig)
|
val wallet = Wallet(desc, change, blockchainConfig, databaseConfig)
|
||||||
assertNotNull(wallet)
|
|
||||||
val address = wallet.getAddress()
|
val address = wallet.getAddress()
|
||||||
assertNotNull(address)
|
assertNotNull(address)
|
||||||
log.debug("address created from kotlin: $address")
|
// log.debug("address created from kotlin: $address")
|
||||||
assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e")
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1
build.sh
1
build.sh
@ -2,6 +2,7 @@
|
|||||||
set -eo pipefail -o xtrace
|
set -eo pipefail -o xtrace
|
||||||
|
|
||||||
# rust
|
# rust
|
||||||
|
cargo fmt
|
||||||
cargo build
|
cargo build
|
||||||
cargo test --features c-headers -- generate_headers
|
cargo test --features c-headers -- generate_headers
|
||||||
|
|
||||||
|
154
cc/bdk_ffi.h
Normal file
154
cc/bdk_ffi.h
Normal file
@ -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 <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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<T>`][`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__ */
|
@ -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_sled_config("/home/steve/.bdk", "test_wallet");
|
||||||
DatabaseConfig_t *db_config = new_memory_config();
|
DatabaseConfig_t *db_config = new_memory_config();
|
||||||
|
|
||||||
WalletResult_t *wallet_result = new_wallet_result("bad", "bad", bc_config, db_config);
|
// new wallet with bad descriptor
|
||||||
assert(wallet_result != NULL);
|
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_blockchain_config(bc_config);
|
||||||
free_database_config(db_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(wallet_err != NULL);
|
||||||
assert( 0 == strcmp(wallet_err,"Descriptor") );
|
assert( 0 == strcmp(wallet_err,"Descriptor") );
|
||||||
//printf("wallet err: %s\n", wallet_err);
|
// 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);
|
free_wallet_result(wallet_result);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// test new wallet
|
// 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/*)";
|
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("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();
|
DatabaseConfig_t *db_config = new_memory_config();
|
||||||
|
|
||||||
WalletResult_t *wallet_result = new_wallet_result(desc, change, bc_config, db_config);
|
// new wallet
|
||||||
assert(wallet_result != NULL);
|
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_blockchain_config(bc_config);
|
||||||
free_database_config(db_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
|
OpaqueWallet_t *wallet = wallet_result.ok;
|
||||||
VoidResult_t *sync_result = sync_wallet(wallet_ref);
|
|
||||||
free_void_result(sync_result);
|
|
||||||
|
|
||||||
// test new_address
|
// test sync wallet
|
||||||
StringResult_t *address_result1 = new_address(wallet_ref);
|
FfiResult_void_t sync_result = sync_wallet(wallet);
|
||||||
char *address1 = get_string_ok(address_result1);
|
assert(sync_result.ok != NULL);
|
||||||
//printf("address1: %s\n", address1);
|
assert(sync_result.err == NULL);
|
||||||
assert( 0 == strcmp(address1,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp"));
|
free_void_result(sync_result);
|
||||||
free_string(address1);
|
|
||||||
|
// 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);
|
free_string_result(address_result1);
|
||||||
|
|
||||||
StringResult_t *address_result2 = new_address(wallet_ref);
|
FfiResult_char_ptr_t address_result2 = new_address(wallet);
|
||||||
char *address2 = get_string_ok(address_result2);
|
assert(address_result2.ok != NULL);
|
||||||
//printf("address2: %s\n", address2);
|
assert(address_result2.err == NULL);
|
||||||
assert(0 == strcmp(address2,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf"));
|
// printf("address2 = %s\n", *address_result2.ok);
|
||||||
free_string(address2);
|
assert( 0 == strcmp(*address_result2.ok,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf"));
|
||||||
free_string_result(address_result2);
|
free_string_result(address_result2);
|
||||||
|
|
||||||
free_wallet_ref(wallet_ref);
|
|
||||||
|
|
||||||
// test free_wallet
|
// test free_wallet
|
||||||
free_wallet_result(wallet_result);
|
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)
|
// 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)
|
// 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;
|
return EXIT_SUCCESS;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
mod blockchain;
|
mod blockchain;
|
||||||
mod database;
|
mod database;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod types;
|
||||||
mod wallet;
|
mod wallet;
|
||||||
|
|
||||||
/// The following test function is necessary for the header generation.
|
/// The following test function is necessary for the header generation.
|
||||||
|
34
src/types.rs
Normal file
34
src/types.rs
Normal file
@ -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<T> {
|
||||||
|
pub ok: Option<repr_c::Box<T>>,
|
||||||
|
pub err: Option<repr_c::Box<char_p_boxed>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive_ReprC]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FfiResultVec<T> {
|
||||||
|
pub ok: repr_c::Vec<T>,
|
||||||
|
pub err: Option<repr_c::Box<char_p_boxed>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn free_string_result(string_result: FfiResult<char_p_boxed>) {
|
||||||
|
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<char_p_boxed>) {
|
||||||
|
drop(string)
|
||||||
|
}
|
231
src/wallet.rs
231
src/wallet.rs
@ -1,82 +1,23 @@
|
|||||||
use crate::blockchain::BlockchainConfig;
|
use std::convert::TryFrom;
|
||||||
use crate::database::DatabaseConfig;
|
|
||||||
use crate::error::get_name;
|
|
||||||
use ::safer_ffi::prelude::*;
|
use ::safer_ffi::prelude::*;
|
||||||
use bdk::bitcoin::network::constants::Network::Testnet;
|
use bdk::bitcoin::network::constants::Network::Testnet;
|
||||||
use bdk::blockchain::{
|
use bdk::blockchain::{log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain};
|
||||||
log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain,
|
|
||||||
ElectrumBlockchainConfig,
|
|
||||||
};
|
|
||||||
use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase};
|
use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase};
|
||||||
use bdk::wallet::AddressIndex::New;
|
use bdk::wallet::AddressIndex::New;
|
||||||
use bdk::{Error, Wallet};
|
use bdk::{Error, Wallet};
|
||||||
use safer_ffi::boxed::Box;
|
use safer_ffi::boxed::Box;
|
||||||
use safer_ffi::char_p::{char_p_boxed, char_p_ref};
|
use safer_ffi::char_p::{char_p_boxed, char_p_ref};
|
||||||
|
|
||||||
#[derive_ReprC]
|
use crate::blockchain::BlockchainConfig;
|
||||||
#[ReprC::opaque]
|
use crate::database::DatabaseConfig;
|
||||||
pub struct VoidResult {
|
use crate::error::get_name;
|
||||||
raw: Result<(), Error>,
|
use crate::types::{FfiResult, FfiResultVec};
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn get_void_err(void_result: &VoidResult) -> Option<char_p_boxed> {
|
|
||||||
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<Box<VoidResult>>) {
|
|
||||||
drop(void_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
#[derive_ReprC]
|
||||||
#[ReprC::opaque]
|
#[ReprC::opaque]
|
||||||
pub struct StringResult {
|
pub struct OpaqueWallet {
|
||||||
raw: Result<String, Error>,
|
raw: Wallet<AnyBlockchain, AnyDatabase>,
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn get_string_ok(string_result: &StringResult) -> Option<char_p_boxed> {
|
|
||||||
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<char_p_boxed> {
|
|
||||||
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<Box<StringResult>>) {
|
|
||||||
drop(string_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[ReprC::opaque]
|
|
||||||
pub struct WalletRef<'lt> {
|
|
||||||
raw: &'lt Wallet<AnyBlockchain, AnyDatabase>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_wallet_ref(wallet_ref: Option<Box<WalletRef>>) {
|
|
||||||
drop(wallet_ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[ReprC::opaque]
|
|
||||||
pub struct WalletResult {
|
|
||||||
raw: Result<Wallet<AnyBlockchain, AnyDatabase>, Error>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
#[ffi_export]
|
||||||
@ -85,13 +26,83 @@ fn new_wallet_result(
|
|||||||
change_descriptor: Option<char_p_ref>,
|
change_descriptor: Option<char_p_ref>,
|
||||||
blockchain_config: &BlockchainConfig,
|
blockchain_config: &BlockchainConfig,
|
||||||
database_config: &DatabaseConfig,
|
database_config: &DatabaseConfig,
|
||||||
) -> Box<WalletResult> {
|
) -> FfiResult<OpaqueWallet> {
|
||||||
let descriptor = descriptor.to_string();
|
let descriptor = descriptor.to_string();
|
||||||
let change_descriptor = change_descriptor.map(|s| s.to_string());
|
let change_descriptor = change_descriptor.map(|s| s.to_string());
|
||||||
let bc_config = &blockchain_config.raw;
|
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);
|
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<OpaqueWallet>) {
|
||||||
|
drop(wallet_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn free_unspent_result(unspent_result: FfiResultVec<LocalUtxo>) {
|
||||||
|
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<char_p_boxed> {
|
||||||
|
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<LocalUtxo> {
|
||||||
|
let unspent_result = opaque_wallet.raw.list_unspent();
|
||||||
|
|
||||||
|
match unspent_result {
|
||||||
|
Ok(v) => FfiResultVec {
|
||||||
|
ok: {
|
||||||
|
let ve: Vec<LocalUtxo> = 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(
|
fn new_wallet(
|
||||||
@ -111,44 +122,64 @@ fn new_wallet(
|
|||||||
Wallet::new(descriptor, change_descriptor, network, database, client)
|
Wallet::new(descriptor, change_descriptor, network, database, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
// Non-opaque returned structs
|
||||||
fn get_wallet_err(wallet_result: &WalletResult) -> Option<char_p_boxed> {
|
|
||||||
wallet_result
|
#[derive_ReprC]
|
||||||
.raw
|
#[repr(C)]
|
||||||
.as_ref()
|
#[derive(Debug, Clone)]
|
||||||
.err()
|
pub struct OutPoint {
|
||||||
.map(|e| char_p_boxed::try_from(get_name(&e)).unwrap())
|
/// 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]
|
impl From<&bdk::bitcoin::OutPoint> for OutPoint {
|
||||||
fn get_wallet_ok<'lt>(wallet_result: &'lt WalletResult) -> Option<Box<WalletRef<'lt>>> {
|
fn from(op: &bdk::bitcoin::OutPoint) -> Self {
|
||||||
wallet_result
|
OutPoint {
|
||||||
.raw
|
txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(),
|
||||||
.as_ref()
|
vout: op.vout,
|
||||||
.ok()
|
}
|
||||||
.map(|w| Box::new(WalletRef { raw: w }))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
#[derive_ReprC]
|
||||||
fn sync_wallet<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box<VoidResult> {
|
#[repr(C)]
|
||||||
let void_result = wallet_ref.raw.sync(log_progress(), Some(100));
|
#[derive(Debug, Clone)]
|
||||||
Box::new(VoidResult { raw: void_result })
|
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]
|
impl From<&bdk::bitcoin::TxOut> for TxOut {
|
||||||
fn new_address<'lt>(wallet_ref: &'lt WalletRef<'lt>) -> Box<StringResult> {
|
fn from(to: &bdk::bitcoin::TxOut) -> Self {
|
||||||
let new_address = wallet_ref.raw.get_address(New);
|
TxOut {
|
||||||
let string_result = new_address.map(|a| a.to_string());
|
value: to.value,
|
||||||
Box::new(StringResult { raw: string_result })
|
script_pubkey: char_p_boxed::try_from(to.script_pubkey.to_string()).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
#[derive_ReprC]
|
||||||
fn free_wallet_result(wallet_result: Option<Box<WalletResult>>) {
|
#[repr(C)]
|
||||||
drop(wallet_result)
|
#[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
|
impl From<&bdk::LocalUtxo> for LocalUtxo {
|
||||||
#[ffi_export]
|
fn from(lu: &bdk::LocalUtxo) -> Self {
|
||||||
fn free_string(string: Option<char_p_boxed>) {
|
LocalUtxo {
|
||||||
drop(string)
|
outpoint: OutPoint::from(&lu.outpoint),
|
||||||
|
txout: TxOut::from(&lu.txout),
|
||||||
|
keychain: lu.keychain as u16,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
6
test.sh
6
test.sh
@ -6,9 +6,9 @@ cargo test --features c-headers -- generate_headers
|
|||||||
|
|
||||||
# cc
|
# cc
|
||||||
export LD_LIBRARY_PATH=`pwd`/target/debug
|
export LD_LIBRARY_PATH=`pwd`/target/debug
|
||||||
#valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test
|
valgrind --leak-check=full --show-leak-kinds=all cc/bdk_ffi_test
|
||||||
cc/bdk_ffi_test
|
#cc/bdk_ffi_test
|
||||||
|
|
||||||
## bdk-kotlin
|
# bdk-kotlin
|
||||||
(cd bdk-kotlin && gradle test)
|
(cd bdk-kotlin && gradle test)
|
||||||
(cd bdk-kotlin && gradle :android:connectedDebugAndroidTest)
|
(cd bdk-kotlin && gradle :android:connectedDebugAndroidTest)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user