From 2907eb074d24ec94a8f841159d9b3faf13e8d7df Mon Sep 17 00:00:00 2001 From: artfuldev Date: Fri, 15 Oct 2021 01:54:32 +0530 Subject: [PATCH] Test a callback --- .../src/main/kotlin/uniffi/bdk/bdk.kt | 286 ++++++++++++++++-- .../src/test/kotlin/uniffi/bdk/LibTest.kt | 15 + src/bdk.udl | 6 + src/lib.rs | 33 ++ 4 files changed, 314 insertions(+), 26 deletions(-) diff --git a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt index 437b142..c1b79cc 100644 --- a/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt +++ b/bindings/bdk-kotlin/src/main/kotlin/uniffi/bdk/bdk.kt @@ -44,15 +44,15 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_14a1_rustbuffer_alloc(size, status) + _UniFFILib.INSTANCE.ffi_bdk_d1e_rustbuffer_alloc(size, status) } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_14a1_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_bdk_d1e_rustbuffer_free(buf, status) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_14a1_rustbuffer_reserve(buf, additional, status) + _UniFFILib.INSTANCE.ffi_bdk_d1e_rustbuffer_reserve(buf, additional, status) } } @@ -232,6 +232,30 @@ internal fun UByte.write(buf: RustBufferBuilder) { +@ExperimentalUnsignedTypes +internal fun UInt.Companion.lift(v: Int): UInt { + return v.toUInt() +} + +@ExperimentalUnsignedTypes +internal fun UInt.Companion.read(buf: ByteBuffer): UInt { + return UInt.lift(buf.getInt()) +} + +@ExperimentalUnsignedTypes +internal fun UInt.lower(): Int { + return this.toInt() +} + +@ExperimentalUnsignedTypes +internal fun UInt.write(buf: RustBufferBuilder) { + buf.putInt(this.toInt()) +} + + + + + @ExperimentalUnsignedTypes internal fun ULong.Companion.lift(v: Long): ULong { return v.toULong() @@ -256,6 +280,26 @@ internal fun ULong.write(buf: RustBufferBuilder) { +internal fun Float.Companion.lift(v: Float): Float { + return v +} + +internal fun Float.Companion.read(buf: ByteBuffer): Float { + return buf.getFloat() +} + +internal fun Float.lower(): Float { + return this +} + +internal fun Float.write(buf: RustBufferBuilder) { + buf.putFloat(this) +} + + + + + internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String { try { val byteArr = ByteArray(rbuf.len) @@ -342,6 +386,12 @@ internal fun String.write(buf: RustBufferBuilder) { + + + + + + @@ -388,6 +438,45 @@ internal fun writeOptionalu8(v: UByte?, buf: RustBufferBuilder) { +// Helper functions for pasing values of type UInt? +@ExperimentalUnsignedTypes +internal fun liftOptionalu32(rbuf: RustBuffer.ByValue): UInt? { + return liftFromRustBuffer(rbuf) { buf -> + readOptionalu32(buf) + } +} + +@ExperimentalUnsignedTypes +internal fun readOptionalu32(buf: ByteBuffer): UInt? { + if (buf.get().toInt() == 0) { + return null + } + return UInt.read(buf) +} + +@ExperimentalUnsignedTypes +internal fun lowerOptionalu32(v: UInt?): RustBuffer.ByValue { + return lowerIntoRustBuffer(v) { v, buf -> + writeOptionalu32(v, buf) + } +} + +@ExperimentalUnsignedTypes +internal fun writeOptionalu32(v: UInt?, buf: RustBufferBuilder) { + if (v == null) { + buf.putByte(0) + } else { + buf.putByte(1) + v.write(buf) + } +} + + + + + + + // Helper functions for pasing values of type String? internal fun liftOptionalstring(rbuf: RustBuffer.ByValue): String? { @@ -446,48 +535,58 @@ internal interface _UniFFILib : Library { companion object { internal val INSTANCE: _UniFFILib by lazy { loadIndirect<_UniFFILib>(componentName = "bdk") - + .also { lib: _UniFFILib -> + CallbackInterfaceBdkProgressInternals.register(lib) + } } } - fun ffi_bdk_14a1_OfflineWallet_object_free(ptr: Pointer, + fun ffi_bdk_d1e_OfflineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_14a1_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, + fun bdk_d1e_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_14a1_OfflineWallet_get_new_address(ptr: Pointer, + fun bdk_d1e_OfflineWallet_get_new_address(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_14a1_OnlineWallet_object_free(ptr: Pointer, + fun ffi_bdk_d1e_OnlineWallet_object_free(ptr: Pointer, uniffi_out_err: RustCallStatus ): Unit - fun bdk_14a1_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, + fun bdk_d1e_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Pointer - fun bdk_14a1_OnlineWallet_get_network(ptr: Pointer, + fun bdk_d1e_OnlineWallet_get_network(ptr: Pointer, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_bdk_14a1_rustbuffer_alloc(size: Int, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_14a1_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, - uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun ffi_bdk_14a1_rustbuffer_free(buf: RustBuffer.ByValue, + fun bdk_d1e_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, uniffi_out_err: RustCallStatus ): Unit - fun ffi_bdk_14a1_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, + fun ffi_bdk_d1e_BdkProgress_init_callback(callback_stub: ForeignCallback, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_d1e_rustbuffer_alloc(size: Int, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_d1e_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, + uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_bdk_d1e_rustbuffer_free(buf: RustBuffer.ByValue, + uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_bdk_d1e_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -657,6 +756,82 @@ abstract class FFIObject( +internal typealias Handle = Long +internal class ConcurrentHandleMap( + private val leftMap: MutableMap = mutableMapOf(), + private val rightMap: MutableMap = mutableMapOf() +) { + private val lock = java.util.concurrent.locks.ReentrantLock() + private val currentHandle = AtomicLong(0L) + private val stride = 1L + + fun insert(obj: T): Handle = + lock.withLock { + rightMap[obj] ?: + currentHandle.getAndAdd(stride) + .also { handle -> + leftMap[handle] = obj + rightMap[obj] = handle + } + } + + fun callWithResult(handle: Handle, fn: (T) -> R): R = + lock.withLock { + leftMap[handle] ?: throw RuntimeException("Panic: handle not in handlemap") + }.let { obj -> + fn.invoke(obj) + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } +} + +interface ForeignCallback : com.sun.jna.Callback { + public fun invoke(handle: Long, method: Int, args: RustBuffer.ByValue): RustBuffer.ByValue +} + +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +internal const val IDX_CALLBACK_FREE = 0 + +internal abstract class CallbackInternals( + val foreignCallback: ForeignCallback +) { + val handleMap = ConcurrentHandleMap() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + abstract fun register(lib: _UniFFILib) + + fun drop(handle: Long): RustBuffer.ByValue { + return handleMap.remove(handle).let { RustBuffer.ByValue() } + } + + fun lift(n: Long) = handleMap.get(n) + + fun read(buf: ByteBuffer) = lift(buf.getLong()) + + fun lower(v: CallbackInterface) = + handleMap.insert(v).also { + assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + fun write(v: CallbackInterface, buf: RustBufferBuilder) = + buf.putLong(lower(v)) +} // Public interface members begin here. @@ -1121,7 +1296,7 @@ class OfflineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_14a1_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_d1e_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) }) /** @@ -1134,7 +1309,7 @@ class OfflineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_14a1_OfflineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_d1e_OfflineWallet_object_free(this.pointer, status) } } @@ -1149,7 +1324,7 @@ class OfflineWallet( override fun getNewAddress(): String = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_14a1_OfflineWallet_get_new_address(it, status) + _UniFFILib.INSTANCE.bdk_d1e_OfflineWallet_get_new_address(it, status) } }.let { String.lift(it) @@ -1175,6 +1350,7 @@ class OfflineWallet( @ExperimentalUnsignedTypes public interface OnlineWalletInterface { fun getNetwork(): Network + fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) } @@ -1185,7 +1361,7 @@ class OnlineWallet( constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : this( rustCallWithError(BdkException) { status -> - _UniFFILib.INSTANCE.bdk_14a1_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) + _UniFFILib.INSTANCE.bdk_d1e_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) }) /** @@ -1198,7 +1374,7 @@ class OnlineWallet( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_bdk_14a1_OnlineWallet_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_bdk_d1e_OnlineWallet_object_free(this.pointer, status) } } @@ -1213,12 +1389,19 @@ class OnlineWallet( override fun getNetwork(): Network = callWithPointer { rustCall() { status -> - _UniFFILib.INSTANCE.bdk_14a1_OnlineWallet_get_network(it, status) + _UniFFILib.INSTANCE.bdk_d1e_OnlineWallet_get_network(it, status) } }.let { Network.lift(it) } + override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = + callWithPointer { + rustCallWithError(BdkException) { status -> + _UniFFILib.INSTANCE.bdk_d1e_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) +} + } + companion object { @@ -1240,3 +1423,54 @@ class OnlineWallet( // Callback Interfaces +public interface BdkProgress { + fun update(progress: Float, message: String? ) + +} + + +internal class CallbackInterfaceBdkProgressFFI : ForeignCallback { + @Suppress("TooGenericExceptionCaught") + override fun invoke(handle: Long, method: Int, args: RustBuffer.ByValue): RustBuffer.ByValue { + return CallbackInterfaceBdkProgressInternals.handleMap.callWithResult(handle) { cb -> + when (method) { + IDX_CALLBACK_FREE -> CallbackInterfaceBdkProgressInternals.drop(handle) + 1 -> this.invokeUpdate(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalException. + // https://github.com/mozilla/uniffi-rs/issues/351 + else -> RustBuffer.ByValue() + } + } + } + + + private fun invokeUpdate(kotlinCallbackInterface: BdkProgress, args: RustBuffer.ByValue): RustBuffer.ByValue = + try { + val buf = args.asByteBuffer() ?: throw InternalException("No ByteBuffer in RustBuffer; this is a Uniffi bug") + kotlinCallbackInterface.update( + Float.read(buf), + readOptionalstring(buf) + ) + .let { RustBuffer.ByValue() } + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + } finally { + RustBuffer.free(args) + } + + +} + +internal object CallbackInterfaceBdkProgressInternals: CallbackInternals( + foreignCallback = CallbackInterfaceBdkProgressFFI() +) { + override fun register(lib: _UniFFILib) { + rustCall() { status -> + lib.ffi_bdk_d1e_BdkProgress_init_callback(this.foreignCallback, status) + } + } +} + + diff --git a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt index 1db8bc9..92c257d 100644 --- a/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt +++ b/bindings/bdk-kotlin/src/test/kotlin/uniffi/bdk/LibTest.kt @@ -4,6 +4,13 @@ import uniffi.bdk.OfflineWallet import org.junit.Assert.* import org.junit.Test +class LogProgress: BdkProgress { + override fun update(progress: Float, message: String? ) { + println(progress); + println(message); + } +} + /** * Library tests which will execute for jvm and android modules. */ @@ -45,4 +52,12 @@ class LibTest { val network = wallet.getNetwork() assertEquals(network, Network.TESTNET) } + + @Test + fun onlineWalletSync() { + val db = DatabaseConfig.Memory("") + val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:50002", null, 5u, null, 100u)) + val wallet = OnlineWallet(desc, Network.TESTNET, db, client) + wallet.sync(LogProgress(), null) + } } diff --git a/src/bdk.udl b/src/bdk.udl index 593d4e8..cbc79a6 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -91,8 +91,14 @@ interface BlockchainConfig { Esplora(EsploraConfig config); }; +callback interface BdkProgress { + void update(f32 progress, string? message); +}; + interface OnlineWallet { [Throws=BdkError] constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); Network get_network(); + [Throws=BdkError] + void sync(BdkProgress progress_update, u32? max_address_param); }; diff --git a/src/lib.rs b/src/lib.rs index a1add54..94cc219 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use bdk::bitcoin::Network; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; +use bdk::blockchain::Progress; use bdk::blockchain::{ electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, ConfigurableBlockchain, }; @@ -75,6 +76,24 @@ struct OnlineWallet { wallet: Mutex>, } +pub trait BdkProgress: Send { + fn update(&self, progress: f32, message: Option); +} + +struct BdkProgressHolder { + progress_update: Mutex>, +} + +impl Progress for BdkProgressHolder { + fn update(&self, progress: f32, message: Option) -> Result<(), Error> { + self.progress_update + .lock() + .unwrap() + .update(progress, message); + Ok(()) + } +} + impl OnlineWallet { fn new( descriptor: String, @@ -121,6 +140,20 @@ impl OnlineWallet { fn get_network(&self) -> Network { self.wallet.lock().unwrap().network() } + + fn sync( + &self, + progress_update: Box, + max_address_param: Option, + ) -> Result<(), BdkError> { + progress_update.update(21.0, Some("message".to_string())); + self.wallet.lock().unwrap().sync( + BdkProgressHolder { + progress_update: Mutex::new(progress_update), + }, + max_address_param, + ) + } } uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send);