Test a callback

This commit is contained in:
artfuldev 2021-10-15 01:54:32 +05:30
parent 40a4b58757
commit 2907eb074d
4 changed files with 314 additions and 26 deletions

View File

@ -44,15 +44,15 @@ open class RustBuffer : Structure() {
companion object { companion object {
internal fun alloc(size: Int = 0) = rustCall() { status -> 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 -> 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 -> 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 @ExperimentalUnsignedTypes
internal fun ULong.Companion.lift(v: Long): ULong { internal fun ULong.Companion.lift(v: Long): ULong {
return v.toULong() 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 { internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String {
try { try {
val byteArr = ByteArray(rbuf.len) 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? // Helper functions for pasing values of type String?
internal fun liftOptionalstring(rbuf: RustBuffer.ByValue): String? { internal fun liftOptionalstring(rbuf: RustBuffer.ByValue): String? {
@ -446,48 +535,58 @@ internal interface _UniFFILib : Library {
companion object { companion object {
internal val INSTANCE: _UniFFILib by lazy { internal val INSTANCE: _UniFFILib by lazy {
loadIndirect<_UniFFILib>(componentName = "bdk") 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 uniffi_out_err: RustCallStatus
): Unit ): 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 uniffi_out_err: RustCallStatus
): Pointer ): Pointer
fun bdk_14a1_OfflineWallet_get_new_address(ptr: Pointer, fun bdk_d1e_OfflineWallet_get_new_address(ptr: Pointer,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): RustBuffer.ByValue ): RustBuffer.ByValue
fun ffi_bdk_14a1_OnlineWallet_object_free(ptr: Pointer, fun ffi_bdk_d1e_OnlineWallet_object_free(ptr: Pointer,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): Unit ): 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 uniffi_out_err: RustCallStatus
): Pointer ): Pointer
fun bdk_14a1_OnlineWallet_get_network(ptr: Pointer, fun bdk_d1e_OnlineWallet_get_network(ptr: Pointer,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): RustBuffer.ByValue ): RustBuffer.ByValue
fun ffi_bdk_14a1_rustbuffer_alloc(size: Int, fun bdk_d1e_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue,
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,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): Unit ): 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 uniffi_out_err: RustCallStatus
): RustBuffer.ByValue ): RustBuffer.ByValue
@ -657,6 +756,82 @@ abstract class FFIObject(
internal typealias Handle = Long
internal class ConcurrentHandleMap<T>(
private val leftMap: MutableMap<Handle, T> = mutableMapOf(),
private val rightMap: MutableMap<T, Handle> = 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 <R> 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<CallbackInterface>(
val foreignCallback: ForeignCallback
) {
val handleMap = ConcurrentHandleMap<CallbackInterface>()
// 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. // Public interface members begin here.
@ -1121,7 +1296,7 @@ class OfflineWallet(
constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) :
this( this(
rustCallWithError(BdkException) { status -> 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() { override protected fun freeRustArcPtr() {
rustCall() { status -> 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 = override fun getNewAddress(): String =
callWithPointer { callWithPointer {
rustCall() { status -> rustCall() { status ->
_UniFFILib.INSTANCE.bdk_14a1_OfflineWallet_get_new_address(it, status) _UniFFILib.INSTANCE.bdk_d1e_OfflineWallet_get_new_address(it, status)
} }
}.let { }.let {
String.lift(it) String.lift(it)
@ -1175,6 +1350,7 @@ class OfflineWallet(
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
public interface OnlineWalletInterface { public interface OnlineWalletInterface {
fun getNetwork(): Network fun getNetwork(): Network
fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? )
} }
@ -1185,7 +1361,7 @@ class OnlineWallet(
constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) :
this( this(
rustCallWithError(BdkException) { status -> 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() { override protected fun freeRustArcPtr() {
rustCall() { status -> 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 = override fun getNetwork(): Network =
callWithPointer { callWithPointer {
rustCall() { status -> rustCall() { status ->
_UniFFILib.INSTANCE.bdk_14a1_OnlineWallet_get_network(it, status) _UniFFILib.INSTANCE.bdk_d1e_OnlineWallet_get_network(it, status)
} }
}.let { }.let {
Network.lift(it) 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 { companion object {
@ -1240,3 +1423,54 @@ class OnlineWallet(
// Callback Interfaces // 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<BdkProgress>(
foreignCallback = CallbackInterfaceBdkProgressFFI()
) {
override fun register(lib: _UniFFILib) {
rustCall() { status ->
lib.ffi_bdk_d1e_BdkProgress_init_callback(this.foreignCallback, status)
}
}
}

View File

@ -4,6 +4,13 @@ import uniffi.bdk.OfflineWallet
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Test 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. * Library tests which will execute for jvm and android modules.
*/ */
@ -45,4 +52,12 @@ class LibTest {
val network = wallet.getNetwork() val network = wallet.getNetwork()
assertEquals(network, Network.TESTNET) 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)
}
} }

View File

@ -91,8 +91,14 @@ interface BlockchainConfig {
Esplora(EsploraConfig config); Esplora(EsploraConfig config);
}; };
callback interface BdkProgress {
void update(f32 progress, string? message);
};
interface OnlineWallet { interface OnlineWallet {
[Throws=BdkError] [Throws=BdkError]
constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config); constructor(string descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config);
Network get_network(); Network get_network();
[Throws=BdkError]
void sync(BdkProgress progress_update, u32? max_address_param);
}; };

View File

@ -1,5 +1,6 @@
use bdk::bitcoin::Network; use bdk::bitcoin::Network;
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
use bdk::blockchain::Progress;
use bdk::blockchain::{ use bdk::blockchain::{
electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, ConfigurableBlockchain, electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, ConfigurableBlockchain,
}; };
@ -75,6 +76,24 @@ struct OnlineWallet {
wallet: Mutex<Wallet<AnyBlockchain, AnyDatabase>>, wallet: Mutex<Wallet<AnyBlockchain, AnyDatabase>>,
} }
pub trait BdkProgress: Send {
fn update(&self, progress: f32, message: Option<String>);
}
struct BdkProgressHolder {
progress_update: Mutex<Box<dyn BdkProgress>>,
}
impl Progress for BdkProgressHolder {
fn update(&self, progress: f32, message: Option<String>) -> Result<(), Error> {
self.progress_update
.lock()
.unwrap()
.update(progress, message);
Ok(())
}
}
impl OnlineWallet { impl OnlineWallet {
fn new( fn new(
descriptor: String, descriptor: String,
@ -121,6 +140,20 @@ impl OnlineWallet {
fn get_network(&self) -> Network { fn get_network(&self) -> Network {
self.wallet.lock().unwrap().network() self.wallet.lock().unwrap().network()
} }
fn sync(
&self,
progress_update: Box<dyn BdkProgress>,
max_address_param: Option<u32>,
) -> 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); uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send);