2089 lines
70 KiB
Diff
2089 lines
70 KiB
Diff
*** ./temp-without-docs/org/bitcoindevkit/bdk.kt 2022-03-15 22:56:50.000000000 -0400
|
|
--- ./android/src/main/kotlin/org/bitcoindevkit/bdk.kt 2022-03-15 23:00:28.000000000 -0400
|
|
***************
|
|
*** 1,181 ****
|
|
--- 1,194 ----
|
|
// This file was autogenerated by some hot garbage in the `uniffi` crate.
|
|
// Trust me, you don't want to mess with it!
|
|
|
|
@file:Suppress("NAME_SHADOWING")
|
|
|
|
package org.bitcoindevkit;
|
|
|
|
// Common helper code.
|
|
//
|
|
// Ideally this would live in a separate .kt file where it can be unittested etc
|
|
// in isolation, and perhaps even published as a re-useable package.
|
|
//
|
|
// However, it's important that the detils of how this helper code works (e.g. the
|
|
// way that different builtin types are passed across the FFI) exactly match what's
|
|
// expected by the Rust code on the other side of the interface. In practice right
|
|
// now that means coming from the exact some version of `uniffi` that was used to
|
|
// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin
|
|
// helpers directly inline like we're doing here.
|
|
|
|
import com.sun.jna.Library
|
|
import com.sun.jna.Native
|
|
import com.sun.jna.Pointer
|
|
import com.sun.jna.Structure
|
|
import com.sun.jna.ptr.ByReference
|
|
import java.nio.ByteBuffer
|
|
import java.nio.ByteOrder
|
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
import java.util.concurrent.atomic.AtomicLong
|
|
import java.util.concurrent.locks.ReentrantLock
|
|
import kotlin.concurrent.withLock
|
|
|
|
// The Rust Buffer and 3 templated methods (alloc, free, reserve).
|
|
// This is a helper for safely working with byte buffers returned from the Rust code.
|
|
// A rust-owned buffer is represented by its capacity, its current length, and a
|
|
// pointer to the underlying data.
|
|
|
|
+
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
@Structure.FieldOrder("capacity", "len", "data")
|
|
open class RustBuffer : Structure() {
|
|
@JvmField var capacity: Int = 0
|
|
@JvmField var len: Int = 0
|
|
@JvmField var data: Pointer? = null
|
|
|
|
class ByValue : RustBuffer(), Structure.ByValue
|
|
class ByReference : RustBuffer(), Structure.ByReference
|
|
|
|
companion object {
|
|
internal fun alloc(size: Int = 0) = rustCall() { status ->
|
|
_UniFFILib.INSTANCE.ffi_bdk_6983_rustbuffer_alloc(size, status).also {
|
|
if(it.data == null) {
|
|
throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
|
|
}
|
|
}
|
|
}
|
|
|
|
internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
|
|
_UniFFILib.INSTANCE.ffi_bdk_6983_rustbuffer_free(buf, status)
|
|
}
|
|
|
|
internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status ->
|
|
_UniFFILib.INSTANCE.ffi_bdk_6983_rustbuffer_reserve(buf, additional, status)
|
|
}
|
|
}
|
|
|
|
@Suppress("TooGenericExceptionThrown")
|
|
fun asByteBuffer() =
|
|
this.data?.getByteBuffer(0, this.len.toLong())?.also {
|
|
it.order(ByteOrder.BIG_ENDIAN)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The equivalent of the `*mut RustBuffer` type.
|
|
* Required for callbacks taking in an out pointer.
|
|
*
|
|
* Size is the sum of all values in the struct.
|
|
*/
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
class RustBufferByReference : ByReference(16) {
|
|
/**
|
|
* Set the pointed-to `RustBuffer` to the given value.
|
|
*/
|
|
fun setValue(value: RustBuffer.ByValue) {
|
|
// NOTE: The offsets are as they are in the C-like struct.
|
|
val pointer = getPointer()
|
|
pointer.setInt(0, value.capacity)
|
|
pointer.setInt(4, value.len)
|
|
pointer.setPointer(8, value.data)
|
|
}
|
|
}
|
|
|
|
// This is a helper for safely passing byte references into the rust code.
|
|
// It's not actually used at the moment, because there aren't many things that you
|
|
// can take a direct pointer to in the JVM, and if we're going to copy something
|
|
// then we might as well copy it into a `RustBuffer`. But it's here for API
|
|
// completeness.
|
|
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
@Structure.FieldOrder("len", "data")
|
|
open class ForeignBytes : Structure() {
|
|
@JvmField var len: Int = 0
|
|
@JvmField var data: Pointer? = null
|
|
|
|
class ByValue : ForeignBytes(), Structure.ByValue
|
|
}
|
|
|
|
|
|
// A helper for structured writing of data into a `RustBuffer`.
|
|
// This is very similar to `java.nio.ByteBuffer` but it knows how to grow
|
|
// the underlying `RustBuffer` on demand.
|
|
//
|
|
// TODO: we should benchmark writing things into a `RustBuffer` versus building
|
|
// up a bytearray and then copying it across.
|
|
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
class RustBufferBuilder() {
|
|
var rbuf = RustBuffer.ByValue()
|
|
var bbuf: ByteBuffer? = null
|
|
|
|
init {
|
|
val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size
|
|
rbuf.writeField("len", 0)
|
|
this.setRustBuffer(rbuf)
|
|
}
|
|
|
|
internal fun setRustBuffer(rbuf: RustBuffer.ByValue) {
|
|
this.rbuf = rbuf
|
|
this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also {
|
|
it.order(ByteOrder.BIG_ENDIAN)
|
|
it.position(rbuf.len)
|
|
}
|
|
}
|
|
|
|
fun finalize() : RustBuffer.ByValue {
|
|
val rbuf = this.rbuf
|
|
// Ensure that the JVM-level field is written through to native memory
|
|
// before turning the buffer, in case its recipient uses it in a context
|
|
// JNA doesn't apply its automatic synchronization logic.
|
|
rbuf.writeField("len", this.bbuf!!.position())
|
|
this.setRustBuffer(RustBuffer.ByValue())
|
|
return rbuf
|
|
}
|
|
|
|
fun discard() {
|
|
if(this.rbuf.data != null) {
|
|
// Free the current `RustBuffer`
|
|
RustBuffer.free(this.rbuf)
|
|
// Replace it with an empty RustBuffer.
|
|
this.setRustBuffer(RustBuffer.ByValue())
|
|
}
|
|
}
|
|
|
|
internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) {
|
|
// TODO: this will perform two checks to ensure we're not overflowing the buffer:
|
|
// one here where we check if it needs to grow, and another when we call a write
|
|
// method on the ByteBuffer. It might be cheaper to use exception-driven control-flow
|
|
// here, trying the write and growing if it throws a `BufferOverflowException`.
|
|
// Benchmarking needed.
|
|
if (this.bbuf!!.position() + size > this.rbuf.capacity) {
|
|
rbuf.writeField("len", this.bbuf!!.position())
|
|
this.setRustBuffer(RustBuffer.reserve(this.rbuf, size))
|
|
}
|
|
write(this.bbuf!!)
|
|
}
|
|
|
|
fun putByte(v: Byte) {
|
|
this.reserve(1) { bbuf ->
|
|
bbuf.put(v)
|
|
}
|
|
}
|
|
|
|
fun putShort(v: Short) {
|
|
this.reserve(2) { bbuf ->
|
|
bbuf.putShort(v)
|
|
}
|
|
}
|
|
|
|
fun putInt(v: Int) {
|
|
this.reserve(4) { bbuf ->
|
|
bbuf.putInt(v)
|
|
}
|
|
}
|
|
|
|
fun putLong(v: Long) {
|
|
this.reserve(8) { bbuf ->
|
|
***************
|
|
*** 216,934 ****
|
|
}
|
|
}
|
|
|
|
internal fun<T> lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue {
|
|
// TODO: maybe we can calculate some sort of initial size hint?
|
|
val buf = RustBufferBuilder()
|
|
try {
|
|
writeItem(v, buf)
|
|
return buf.finalize()
|
|
} catch (e: Throwable) {
|
|
buf.discard()
|
|
throw e
|
|
}
|
|
}
|
|
|
|
// A handful of classes and functions to support the generated data structures.
|
|
// This would be a good candidate for isolating in its own ffi-support lib.
|
|
// Error runtime.
|
|
@Structure.FieldOrder("code", "error_buf")
|
|
internal open class RustCallStatus : Structure() {
|
|
@JvmField var code: Int = 0
|
|
@JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
|
|
|
|
fun isSuccess(): Boolean {
|
|
return code == 0
|
|
}
|
|
|
|
fun isError(): Boolean {
|
|
return code == 1
|
|
}
|
|
|
|
fun isPanic(): Boolean {
|
|
return code == 2
|
|
}
|
|
}
|
|
|
|
class InternalException(message: String) : Exception(message)
|
|
|
|
// Each top-level error class has a companion object that can lift the error from the call status's rust buffer
|
|
interface CallStatusErrorHandler<E> {
|
|
fun lift(error_buf: RustBuffer.ByValue): E;
|
|
}
|
|
|
|
// Helpers for calling Rust
|
|
// In practice we usually need to be synchronized to call this safely, so it doesn't
|
|
// synchronize itself
|
|
|
|
// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err
|
|
private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U {
|
|
var status = RustCallStatus();
|
|
val return_value = callback(status)
|
|
if (status.isSuccess()) {
|
|
return return_value
|
|
} else if (status.isError()) {
|
|
throw errorHandler.lift(status.error_buf)
|
|
} else if (status.isPanic()) {
|
|
// when the rust code sees a panic, it tries to construct a rustbuffer
|
|
// with the message. but if that code panics, then it just sends back
|
|
// an empty buffer.
|
|
if (status.error_buf.len > 0) {
|
|
throw InternalException(String.lift(status.error_buf))
|
|
} else {
|
|
throw InternalException("Rust panic")
|
|
}
|
|
} else {
|
|
throw InternalException("Unknown rust call status: $status.code")
|
|
}
|
|
}
|
|
|
|
// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
|
|
object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
|
|
override fun lift(error_buf: RustBuffer.ByValue): InternalException {
|
|
RustBuffer.free(error_buf)
|
|
return InternalException("Unexpected CALL_ERROR")
|
|
}
|
|
}
|
|
|
|
// Call a rust function that returns a plain value
|
|
private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U {
|
|
return rustCallWithError(NullCallStatusErrorHandler, callback);
|
|
}
|
|
|
|
// Contains loading, initialization code,
|
|
// and the FFI Function declarations in a com.sun.jna.Library.
|
|
@Synchronized
|
|
private fun findLibraryName(componentName: String): String {
|
|
val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride")
|
|
if (libOverride != null) {
|
|
return libOverride
|
|
}
|
|
return "bdkffi"
|
|
}
|
|
|
|
private inline fun <reified Lib : Library> loadIndirect(
|
|
componentName: String
|
|
): Lib {
|
|
return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
|
|
}
|
|
|
|
// A JNA Library to expose the extern-C FFI definitions.
|
|
// This is an implementation detail which will be called internally by the public API.
|
|
|
|
internal interface _UniFFILib : Library {
|
|
companion object {
|
|
internal val INSTANCE: _UniFFILib by lazy {
|
|
loadIndirect<_UniFFILib>(componentName = "bdk")
|
|
.also { lib: _UniFFILib ->
|
|
FfiConverterCallbackInterfaceBdkProgress.register(lib)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
fun ffi_bdk_6983_Wallet_object_free(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun bdk_6983_Wallet_new(descriptor: RustBuffer.ByValue,change_descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): Pointer
|
|
|
|
fun bdk_6983_Wallet_get_new_address(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun bdk_6983_Wallet_get_last_unused_address(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun bdk_6983_Wallet_get_balance(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): Long
|
|
|
|
fun bdk_6983_Wallet_sign(ptr: Pointer,psbt: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun bdk_6983_Wallet_get_transactions(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun bdk_6983_Wallet_get_network(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun bdk_6983_Wallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun bdk_6983_Wallet_broadcast(ptr: Pointer,psbt: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun ffi_bdk_6983_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun bdk_6983_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long,fee_rate: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): Pointer
|
|
|
|
fun bdk_6983_PartiallySignedBitcoinTransaction_deserialize(psbt_base64: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): Pointer
|
|
|
|
fun bdk_6983_PartiallySignedBitcoinTransaction_serialize(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun ffi_bdk_6983_BdkProgress_init_callback(callback_stub: ForeignCallback,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun bdk_6983_generate_extended_key(network: RustBuffer.ByValue,word_count: RustBuffer.ByValue,password: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun bdk_6983_restore_extended_key(network: RustBuffer.ByValue,mnemonic: RustBuffer.ByValue,password: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun ffi_bdk_6983_rustbuffer_alloc(size: Int,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun ffi_bdk_6983_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun ffi_bdk_6983_rustbuffer_free(buf: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun ffi_bdk_6983_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
|
|
}
|
|
|
|
// Public interface members begin here.
|
|
|
|
// Interface implemented by anything that can contain an object reference.
|
|
//
|
|
// Such types expose a `destroy()` method that must be called to cleanly
|
|
// dispose of the contained objects. Failure to call this method may result
|
|
// in memory leaks.
|
|
//
|
|
// The easiest way to ensure this method is called is to use the `.use`
|
|
// helper method to execute a block and destroy the object at the end.
|
|
interface Disposable {
|
|
fun destroy()
|
|
companion object {
|
|
fun destroy(vararg args: Any?) {
|
|
args.filterIsInstance<Disposable>()
|
|
.forEach(Disposable::destroy)
|
|
}
|
|
}
|
|
}
|
|
|
|
inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
|
|
try {
|
|
block(this)
|
|
} finally {
|
|
try {
|
|
// N.B. our implementation is on the nullable type `Disposable?`.
|
|
this?.destroy()
|
|
} catch (e: Throwable) {
|
|
// swallow
|
|
}
|
|
}
|
|
|
|
// The base class for all UniFFI Object types.
|
|
//
|
|
// This class provides core operations for working with the Rust `Arc<T>` pointer to
|
|
// the live Rust struct on the other side of the FFI.
|
|
//
|
|
// There's some subtlety here, because we have to be careful not to operate on a Rust
|
|
// struct after it has been dropped, and because we must expose a public API for freeing
|
|
// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
|
|
//
|
|
// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct.
|
|
// Method calls need to read this pointer from the object's state and pass it in to
|
|
// the Rust FFI.
|
|
//
|
|
// * When an `FFIObject` is no longer needed, its pointer should be passed to a
|
|
// special destructor function provided by the Rust FFI, which will drop the
|
|
// underlying Rust struct.
|
|
//
|
|
// * Given an `FFIObject` instance, calling code is expected to call the special
|
|
// `destroy` method in order to free it after use, either by calling it explicitly
|
|
// or by using a higher-level helper like the `use` method. Failing to do so will
|
|
// leak the underlying Rust struct.
|
|
//
|
|
// * We can't assume that calling code will do the right thing, and must be prepared
|
|
// to handle Kotlin method calls executing concurrently with or even after a call to
|
|
// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
|
|
//
|
|
// * We must never allow Rust code to operate on the underlying Rust struct after
|
|
// the destructor has been called, and must never call the destructor more than once.
|
|
// Doing so may trigger memory unsafety.
|
|
//
|
|
// If we try to implement this with mutual exclusion on access to the pointer, there is the
|
|
// possibility of a race between a method call and a concurrent call to `destroy`:
|
|
//
|
|
// * Thread A starts a method call, reads the value of the pointer, but is interrupted
|
|
// before it can pass the pointer over the FFI to Rust.
|
|
// * Thread B calls `destroy` and frees the underlying Rust struct.
|
|
// * Thread A resumes, passing the already-read pointer value to Rust and triggering
|
|
// a use-after-free.
|
|
//
|
|
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
|
|
// a read lock (and thus allowed to run concurrently) and the special `destroy` method
|
|
// taking a write lock (and thus blocking on live method calls). However, we aim not to
|
|
// generate methods with any hidden blocking semantics, and a `destroy` method that might
|
|
// block if called incorrectly seems to meet that bar.
|
|
//
|
|
// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track
|
|
// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
|
|
// has been called. These are updated according to the following rules:
|
|
//
|
|
// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
|
|
// The initial value for the flag is false.
|
|
//
|
|
// * At the start of each method call, we atomically check the counter.
|
|
// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
|
|
// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
|
|
//
|
|
// * At the end of each method call, we atomically decrement and check the counter.
|
|
// If it has reached zero then we destroy the underlying Rust struct.
|
|
//
|
|
// * When `destroy` is called, we atomically flip the flag from false to true.
|
|
// If the flag was already true we silently fail.
|
|
// Otherwise we atomically decrement and check the counter.
|
|
// If it has reached zero then we destroy the underlying Rust struct.
|
|
//
|
|
// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
|
|
// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
|
|
//
|
|
// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
|
|
// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
|
|
// of the underlying Rust code.
|
|
//
|
|
// In the future we may be able to replace some of this with automatic finalization logic, such as using
|
|
// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is
|
|
// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also
|
|
// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1],
|
|
// so there would still be some complexity here).
|
|
//
|
|
// Sigh...all of this for want of a robust finalization mechanism.
|
|
//
|
|
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
|
|
//
|
|
abstract class FFIObject(
|
|
protected val pointer: Pointer
|
|
): Disposable, AutoCloseable {
|
|
|
|
private val wasDestroyed = AtomicBoolean(false)
|
|
private val callCounter = AtomicLong(1)
|
|
|
|
open protected fun freeRustArcPtr() {
|
|
// To be overridden in subclasses.
|
|
}
|
|
|
|
override fun destroy() {
|
|
// Only allow a single call to this method.
|
|
// TODO: maybe we should log a warning if called more than once?
|
|
if (this.wasDestroyed.compareAndSet(false, true)) {
|
|
// This decrement always matches the initial count of 1 given at creation time.
|
|
if (this.callCounter.decrementAndGet() == 0L) {
|
|
this.freeRustArcPtr()
|
|
}
|
|
}
|
|
}
|
|
|
|
@Synchronized
|
|
override fun close() {
|
|
this.destroy()
|
|
}
|
|
|
|
internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
|
|
// Check and increment the call counter, to keep the object alive.
|
|
// This needs a compare-and-set retry loop in case of concurrent updates.
|
|
do {
|
|
val c = this.callCounter.get()
|
|
if (c == 0L) {
|
|
throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
|
|
}
|
|
if (c == Long.MAX_VALUE) {
|
|
throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
|
|
}
|
|
} while (! this.callCounter.compareAndSet(c, c + 1L))
|
|
// Now we can safely do the method call without the pointer being freed concurrently.
|
|
try {
|
|
return block(this.pointer)
|
|
} finally {
|
|
// This decrement aways matches the increment we performed above.
|
|
if (this.callCounter.decrementAndGet() == 0L) {
|
|
this.freeRustArcPtr()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 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: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int
|
|
}
|
|
|
|
// 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 FfiConverterCallbackInterface<CallbackInterface>(
|
|
protected 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: Handle): RustBuffer.ByValue {
|
|
return handleMap.remove(handle).let { RustBuffer.ByValue() }
|
|
}
|
|
|
|
fun lift(n: Handle) = 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))
|
|
}
|
|
|
|
|
|
|
|
enum class Network {
|
|
BITCOIN,TESTNET,SIGNET,REGTEST;
|
|
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): Network {
|
|
return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer) =
|
|
try { values()[buf.getInt() - 1] }
|
|
catch (e: IndexOutOfBoundsException) {
|
|
throw RuntimeException("invalid enum value, something is very wrong!!", e)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
buf.putInt(this.ordinal + 1)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sealed class DatabaseConfig {
|
|
object Memory : DatabaseConfig()
|
|
|
|
data class Sled(
|
|
val config: SledDbConfiguration
|
|
) : DatabaseConfig()
|
|
|
|
data class Sqlite(
|
|
val config: SqliteDbConfiguration
|
|
) : DatabaseConfig()
|
|
-
|
|
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig {
|
|
return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): DatabaseConfig {
|
|
return when(buf.getInt()) {
|
|
1 -> DatabaseConfig.Memory
|
|
2 -> DatabaseConfig.Sled(
|
|
SledDbConfiguration.read(buf)
|
|
)
|
|
3 -> DatabaseConfig.Sqlite(
|
|
SqliteDbConfiguration.read(buf)
|
|
)
|
|
else -> throw RuntimeException("invalid enum value, something is very wrong!!")
|
|
}
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
when(this) {
|
|
is DatabaseConfig.Memory -> {
|
|
buf.putInt(1)
|
|
|
|
}
|
|
is DatabaseConfig.Sled -> {
|
|
buf.putInt(2)
|
|
this.config.write(buf)
|
|
|
|
}
|
|
is DatabaseConfig.Sqlite -> {
|
|
buf.putInt(3)
|
|
this.config.write(buf)
|
|
|
|
}
|
|
}.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sealed class Transaction {
|
|
|
|
data class Unconfirmed(
|
|
val details: TransactionDetails
|
|
) : Transaction()
|
|
|
|
data class Confirmed(
|
|
val details: TransactionDetails,
|
|
val confirmation: BlockTime
|
|
) : Transaction()
|
|
|
|
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): Transaction {
|
|
return liftFromRustBuffer(rbuf) { buf -> Transaction.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): Transaction {
|
|
return when(buf.getInt()) {
|
|
1 -> Transaction.Unconfirmed(
|
|
TransactionDetails.read(buf)
|
|
)
|
|
2 -> Transaction.Confirmed(
|
|
TransactionDetails.read(buf),
|
|
BlockTime.read(buf)
|
|
)
|
|
else -> throw RuntimeException("invalid enum value, something is very wrong!!")
|
|
}
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
when(this) {
|
|
is Transaction.Unconfirmed -> {
|
|
buf.putInt(1)
|
|
this.details.write(buf)
|
|
|
|
}
|
|
is Transaction.Confirmed -> {
|
|
buf.putInt(2)
|
|
this.details.write(buf)
|
|
this.confirmation.write(buf)
|
|
|
|
}
|
|
}.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sealed class BlockchainConfig {
|
|
|
|
data class Electrum(
|
|
val config: ElectrumConfig
|
|
) : BlockchainConfig()
|
|
|
|
data class Esplora(
|
|
val config: EsploraConfig
|
|
) : BlockchainConfig()
|
|
|
|
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig {
|
|
return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): BlockchainConfig {
|
|
return when(buf.getInt()) {
|
|
1 -> BlockchainConfig.Electrum(
|
|
ElectrumConfig.read(buf)
|
|
)
|
|
2 -> BlockchainConfig.Esplora(
|
|
EsploraConfig.read(buf)
|
|
)
|
|
else -> throw RuntimeException("invalid enum value, something is very wrong!!")
|
|
}
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
when(this) {
|
|
is BlockchainConfig.Electrum -> {
|
|
buf.putInt(1)
|
|
this.config.write(buf)
|
|
|
|
}
|
|
is BlockchainConfig.Esplora -> {
|
|
buf.putInt(2)
|
|
this.config.write(buf)
|
|
|
|
}
|
|
}.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum class WordCount {
|
|
WORDS12,WORDS15,WORDS18,WORDS21,WORDS24;
|
|
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): WordCount {
|
|
return liftFromRustBuffer(rbuf) { buf -> WordCount.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer) =
|
|
try { values()[buf.getInt() - 1] }
|
|
catch (e: IndexOutOfBoundsException) {
|
|
throw RuntimeException("invalid enum value, something is very wrong!!", e)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
buf.putInt(this.ordinal + 1)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Throws(BdkException::class)
|
|
|
|
fun generateExtendedKey(network: Network, wordCount: WordCount, password: String? ): ExtendedKeyInfo {
|
|
val _retval =
|
|
rustCallWithError(BdkException) { status ->
|
|
_UniFFILib.INSTANCE.bdk_6983_generate_extended_key(network.lower(), wordCount.lower(), lowerOptionalString(password) ,status)
|
|
}
|
|
return ExtendedKeyInfo.lift(_retval)
|
|
}
|
|
|
|
|
|
@Throws(BdkException::class)
|
|
|
|
fun restoreExtendedKey(network: Network, mnemonic: String, password: String? ): ExtendedKeyInfo {
|
|
val _retval =
|
|
rustCallWithError(BdkException) { status ->
|
|
_UniFFILib.INSTANCE.bdk_6983_restore_extended_key(network.lower(), mnemonic.lower(), lowerOptionalString(password) ,status)
|
|
}
|
|
return ExtendedKeyInfo.lift(_retval)
|
|
}
|
|
|
|
|
|
public interface WalletInterface {
|
|
|
|
fun getNewAddress(): String
|
|
|
|
fun getLastUnusedAddress(): String
|
|
|
|
@Throws(BdkException::class)
|
|
fun getBalance(): ULong
|
|
|
|
@Throws(BdkException::class)
|
|
fun sign(psbt: PartiallySignedBitcoinTransaction )
|
|
|
|
@Throws(BdkException::class)
|
|
fun getTransactions(): List<Transaction>
|
|
|
|
fun getNetwork(): Network
|
|
|
|
@Throws(BdkException::class)
|
|
fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? )
|
|
|
|
@Throws(BdkException::class)
|
|
fun broadcast(psbt: PartiallySignedBitcoinTransaction ): Transaction
|
|
|
|
}
|
|
|
|
--- 229,980 ----
|
|
}
|
|
}
|
|
|
|
internal fun<T> lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue {
|
|
// TODO: maybe we can calculate some sort of initial size hint?
|
|
val buf = RustBufferBuilder()
|
|
try {
|
|
writeItem(v, buf)
|
|
return buf.finalize()
|
|
} catch (e: Throwable) {
|
|
buf.discard()
|
|
throw e
|
|
}
|
|
}
|
|
|
|
// A handful of classes and functions to support the generated data structures.
|
|
// This would be a good candidate for isolating in its own ffi-support lib.
|
|
// Error runtime.
|
|
@Structure.FieldOrder("code", "error_buf")
|
|
internal open class RustCallStatus : Structure() {
|
|
@JvmField var code: Int = 0
|
|
@JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
|
|
|
|
fun isSuccess(): Boolean {
|
|
return code == 0
|
|
}
|
|
|
|
fun isError(): Boolean {
|
|
return code == 1
|
|
}
|
|
|
|
fun isPanic(): Boolean {
|
|
return code == 2
|
|
}
|
|
}
|
|
|
|
class InternalException(message: String) : Exception(message)
|
|
|
|
// Each top-level error class has a companion object that can lift the error from the call status's rust buffer
|
|
interface CallStatusErrorHandler<E> {
|
|
fun lift(error_buf: RustBuffer.ByValue): E;
|
|
}
|
|
|
|
// Helpers for calling Rust
|
|
// In practice we usually need to be synchronized to call this safely, so it doesn't
|
|
// synchronize itself
|
|
|
|
// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err
|
|
private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U {
|
|
var status = RustCallStatus();
|
|
val return_value = callback(status)
|
|
if (status.isSuccess()) {
|
|
return return_value
|
|
} else if (status.isError()) {
|
|
throw errorHandler.lift(status.error_buf)
|
|
} else if (status.isPanic()) {
|
|
// when the rust code sees a panic, it tries to construct a rustbuffer
|
|
// with the message. but if that code panics, then it just sends back
|
|
// an empty buffer.
|
|
if (status.error_buf.len > 0) {
|
|
throw InternalException(String.lift(status.error_buf))
|
|
} else {
|
|
throw InternalException("Rust panic")
|
|
}
|
|
} else {
|
|
throw InternalException("Unknown rust call status: $status.code")
|
|
}
|
|
}
|
|
|
|
// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
|
|
override fun lift(error_buf: RustBuffer.ByValue): InternalException {
|
|
RustBuffer.free(error_buf)
|
|
return InternalException("Unexpected CALL_ERROR")
|
|
}
|
|
}
|
|
|
|
// Call a rust function that returns a plain value
|
|
private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U {
|
|
return rustCallWithError(NullCallStatusErrorHandler, callback);
|
|
}
|
|
|
|
// Contains loading, initialization code,
|
|
// and the FFI Function declarations in a com.sun.jna.Library.
|
|
@Synchronized
|
|
private fun findLibraryName(componentName: String): String {
|
|
val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride")
|
|
if (libOverride != null) {
|
|
return libOverride
|
|
}
|
|
return "bdkffi"
|
|
}
|
|
|
|
private inline fun <reified Lib : Library> loadIndirect(
|
|
componentName: String
|
|
): Lib {
|
|
return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
|
|
}
|
|
|
|
// A JNA Library to expose the extern-C FFI definitions.
|
|
// This is an implementation detail which will be called internally by the public API.
|
|
|
|
internal interface _UniFFILib : Library {
|
|
companion object {
|
|
internal val INSTANCE: _UniFFILib by lazy {
|
|
loadIndirect<_UniFFILib>(componentName = "bdk")
|
|
.also { lib: _UniFFILib ->
|
|
FfiConverterCallbackInterfaceBdkProgress.register(lib)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
fun ffi_bdk_6983_Wallet_object_free(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun bdk_6983_Wallet_new(descriptor: RustBuffer.ByValue,change_descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): Pointer
|
|
|
|
fun bdk_6983_Wallet_get_new_address(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun bdk_6983_Wallet_get_last_unused_address(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun bdk_6983_Wallet_get_balance(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): Long
|
|
|
|
fun bdk_6983_Wallet_sign(ptr: Pointer,psbt: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun bdk_6983_Wallet_get_transactions(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun bdk_6983_Wallet_get_network(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun bdk_6983_Wallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun bdk_6983_Wallet_broadcast(ptr: Pointer,psbt: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun ffi_bdk_6983_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun bdk_6983_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long,fee_rate: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): Pointer
|
|
|
|
fun bdk_6983_PartiallySignedBitcoinTransaction_deserialize(psbt_base64: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): Pointer
|
|
|
|
fun bdk_6983_PartiallySignedBitcoinTransaction_serialize(ptr: Pointer,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun ffi_bdk_6983_BdkProgress_init_callback(callback_stub: ForeignCallback,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun bdk_6983_generate_extended_key(network: RustBuffer.ByValue,word_count: RustBuffer.ByValue,password: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun bdk_6983_restore_extended_key(network: RustBuffer.ByValue,mnemonic: RustBuffer.ByValue,password: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun ffi_bdk_6983_rustbuffer_alloc(size: Int,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun ffi_bdk_6983_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
fun ffi_bdk_6983_rustbuffer_free(buf: RustBuffer.ByValue,
|
|
uniffi_out_err: RustCallStatus
|
|
): Unit
|
|
|
|
fun ffi_bdk_6983_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int,
|
|
uniffi_out_err: RustCallStatus
|
|
): RustBuffer.ByValue
|
|
|
|
|
|
}
|
|
|
|
// Public interface members begin here.
|
|
|
|
// Interface implemented by anything that can contain an object reference.
|
|
//
|
|
// Such types expose a `destroy()` method that must be called to cleanly
|
|
// dispose of the contained objects. Failure to call this method may result
|
|
// in memory leaks.
|
|
//
|
|
// The easiest way to ensure this method is called is to use the `.use`
|
|
// helper method to execute a block and destroy the object at the end.
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
interface Disposable {
|
|
fun destroy()
|
|
companion object {
|
|
fun destroy(vararg args: Any?) {
|
|
args.filterIsInstance<Disposable>()
|
|
.forEach(Disposable::destroy)
|
|
}
|
|
}
|
|
}
|
|
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
|
|
try {
|
|
block(this)
|
|
} finally {
|
|
try {
|
|
// N.B. our implementation is on the nullable type `Disposable?`.
|
|
this?.destroy()
|
|
} catch (e: Throwable) {
|
|
// swallow
|
|
}
|
|
}
|
|
|
|
// The base class for all UniFFI Object types.
|
|
//
|
|
// This class provides core operations for working with the Rust `Arc<T>` pointer to
|
|
// the live Rust struct on the other side of the FFI.
|
|
//
|
|
// There's some subtlety here, because we have to be careful not to operate on a Rust
|
|
// struct after it has been dropped, and because we must expose a public API for freeing
|
|
// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
|
|
//
|
|
// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct.
|
|
// Method calls need to read this pointer from the object's state and pass it in to
|
|
// the Rust FFI.
|
|
//
|
|
// * When an `FFIObject` is no longer needed, its pointer should be passed to a
|
|
// special destructor function provided by the Rust FFI, which will drop the
|
|
// underlying Rust struct.
|
|
//
|
|
// * Given an `FFIObject` instance, calling code is expected to call the special
|
|
// `destroy` method in order to free it after use, either by calling it explicitly
|
|
// or by using a higher-level helper like the `use` method. Failing to do so will
|
|
// leak the underlying Rust struct.
|
|
//
|
|
// * We can't assume that calling code will do the right thing, and must be prepared
|
|
// to handle Kotlin method calls executing concurrently with or even after a call to
|
|
// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
|
|
//
|
|
// * We must never allow Rust code to operate on the underlying Rust struct after
|
|
// the destructor has been called, and must never call the destructor more than once.
|
|
// Doing so may trigger memory unsafety.
|
|
//
|
|
// If we try to implement this with mutual exclusion on access to the pointer, there is the
|
|
// possibility of a race between a method call and a concurrent call to `destroy`:
|
|
//
|
|
// * Thread A starts a method call, reads the value of the pointer, but is interrupted
|
|
// before it can pass the pointer over the FFI to Rust.
|
|
// * Thread B calls `destroy` and frees the underlying Rust struct.
|
|
// * Thread A resumes, passing the already-read pointer value to Rust and triggering
|
|
// a use-after-free.
|
|
//
|
|
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
|
|
// a read lock (and thus allowed to run concurrently) and the special `destroy` method
|
|
// taking a write lock (and thus blocking on live method calls). However, we aim not to
|
|
// generate methods with any hidden blocking semantics, and a `destroy` method that might
|
|
// block if called incorrectly seems to meet that bar.
|
|
//
|
|
// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track
|
|
// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
|
|
// has been called. These are updated according to the following rules:
|
|
//
|
|
// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
|
|
// The initial value for the flag is false.
|
|
//
|
|
// * At the start of each method call, we atomically check the counter.
|
|
// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
|
|
// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
|
|
//
|
|
// * At the end of each method call, we atomically decrement and check the counter.
|
|
// If it has reached zero then we destroy the underlying Rust struct.
|
|
//
|
|
// * When `destroy` is called, we atomically flip the flag from false to true.
|
|
// If the flag was already true we silently fail.
|
|
// Otherwise we atomically decrement and check the counter.
|
|
// If it has reached zero then we destroy the underlying Rust struct.
|
|
//
|
|
// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
|
|
// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
|
|
//
|
|
// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
|
|
// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
|
|
// of the underlying Rust code.
|
|
//
|
|
// In the future we may be able to replace some of this with automatic finalization logic, such as using
|
|
// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is
|
|
// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also
|
|
// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1],
|
|
// so there would still be some complexity here).
|
|
//
|
|
// Sigh...all of this for want of a robust finalization mechanism.
|
|
//
|
|
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
|
|
//
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
abstract class FFIObject(
|
|
protected val pointer: Pointer
|
|
): Disposable, AutoCloseable {
|
|
|
|
private val wasDestroyed = AtomicBoolean(false)
|
|
private val callCounter = AtomicLong(1)
|
|
|
|
open protected fun freeRustArcPtr() {
|
|
// To be overridden in subclasses.
|
|
}
|
|
|
|
override fun destroy() {
|
|
// Only allow a single call to this method.
|
|
// TODO: maybe we should log a warning if called more than once?
|
|
if (this.wasDestroyed.compareAndSet(false, true)) {
|
|
// This decrement always matches the initial count of 1 given at creation time.
|
|
if (this.callCounter.decrementAndGet() == 0L) {
|
|
this.freeRustArcPtr()
|
|
}
|
|
}
|
|
}
|
|
|
|
@Synchronized
|
|
override fun close() {
|
|
this.destroy()
|
|
}
|
|
|
|
internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
|
|
// Check and increment the call counter, to keep the object alive.
|
|
// This needs a compare-and-set retry loop in case of concurrent updates.
|
|
do {
|
|
val c = this.callCounter.get()
|
|
if (c == 0L) {
|
|
throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
|
|
}
|
|
if (c == Long.MAX_VALUE) {
|
|
throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
|
|
}
|
|
} while (! this.callCounter.compareAndSet(c, c + 1L))
|
|
// Now we can safely do the method call without the pointer being freed concurrently.
|
|
try {
|
|
return block(this.pointer)
|
|
} finally {
|
|
// This decrement aways matches the increment we performed above.
|
|
if (this.callCounter.decrementAndGet() == 0L) {
|
|
this.freeRustArcPtr()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 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
|
|
}
|
|
}
|
|
}
|
|
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
interface ForeignCallback : com.sun.jna.Callback {
|
|
public fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int
|
|
}
|
|
|
|
// 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 FfiConverterCallbackInterface<CallbackInterface>(
|
|
protected 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: Handle): RustBuffer.ByValue {
|
|
return handleMap.remove(handle).let { RustBuffer.ByValue() }
|
|
}
|
|
|
|
fun lift(n: Handle) = 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))
|
|
}
|
|
|
|
|
|
|
|
enum class Network {
|
|
BITCOIN,TESTNET,SIGNET,REGTEST;
|
|
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): Network {
|
|
return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer) =
|
|
try { values()[buf.getInt() - 1] }
|
|
catch (e: IndexOutOfBoundsException) {
|
|
throw RuntimeException("invalid enum value, something is very wrong!!", e)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
buf.putInt(this.ordinal + 1)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sealed class DatabaseConfig {
|
|
object Memory : DatabaseConfig()
|
|
|
|
data class Sled(
|
|
val config: SledDbConfiguration
|
|
) : DatabaseConfig()
|
|
|
|
data class Sqlite(
|
|
val config: SqliteDbConfiguration
|
|
) : DatabaseConfig()
|
|
|
|
+
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig {
|
|
return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): DatabaseConfig {
|
|
return when(buf.getInt()) {
|
|
1 -> DatabaseConfig.Memory
|
|
2 -> DatabaseConfig.Sled(
|
|
SledDbConfiguration.read(buf)
|
|
)
|
|
3 -> DatabaseConfig.Sqlite(
|
|
SqliteDbConfiguration.read(buf)
|
|
)
|
|
else -> throw RuntimeException("invalid enum value, something is very wrong!!")
|
|
}
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
when(this) {
|
|
is DatabaseConfig.Memory -> {
|
|
buf.putInt(1)
|
|
|
|
}
|
|
is DatabaseConfig.Sled -> {
|
|
buf.putInt(2)
|
|
this.config.write(buf)
|
|
|
|
}
|
|
is DatabaseConfig.Sqlite -> {
|
|
buf.putInt(3)
|
|
this.config.write(buf)
|
|
|
|
}
|
|
}.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sealed class Transaction {
|
|
|
|
data class Unconfirmed(
|
|
val details: TransactionDetails
|
|
) : Transaction()
|
|
|
|
data class Confirmed(
|
|
val details: TransactionDetails,
|
|
val confirmation: BlockTime
|
|
) : Transaction()
|
|
|
|
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): Transaction {
|
|
return liftFromRustBuffer(rbuf) { buf -> Transaction.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): Transaction {
|
|
return when(buf.getInt()) {
|
|
1 -> Transaction.Unconfirmed(
|
|
TransactionDetails.read(buf)
|
|
)
|
|
2 -> Transaction.Confirmed(
|
|
TransactionDetails.read(buf),
|
|
BlockTime.read(buf)
|
|
)
|
|
else -> throw RuntimeException("invalid enum value, something is very wrong!!")
|
|
}
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
when(this) {
|
|
is Transaction.Unconfirmed -> {
|
|
buf.putInt(1)
|
|
this.details.write(buf)
|
|
|
|
}
|
|
is Transaction.Confirmed -> {
|
|
buf.putInt(2)
|
|
this.details.write(buf)
|
|
this.confirmation.write(buf)
|
|
|
|
}
|
|
}.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
+ * Sealed class that can be of either blockchain configuration defined by the library.
|
|
+ */
|
|
sealed class BlockchainConfig {
|
|
|
|
data class Electrum(
|
|
val config: ElectrumConfig
|
|
) : BlockchainConfig()
|
|
|
|
data class Esplora(
|
|
val config: EsploraConfig
|
|
) : BlockchainConfig()
|
|
|
|
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig {
|
|
return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): BlockchainConfig {
|
|
return when(buf.getInt()) {
|
|
1 -> BlockchainConfig.Electrum(
|
|
ElectrumConfig.read(buf)
|
|
)
|
|
2 -> BlockchainConfig.Esplora(
|
|
EsploraConfig.read(buf)
|
|
)
|
|
else -> throw RuntimeException("invalid enum value, something is very wrong!!")
|
|
}
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
when(this) {
|
|
is BlockchainConfig.Electrum -> {
|
|
buf.putInt(1)
|
|
this.config.write(buf)
|
|
|
|
}
|
|
is BlockchainConfig.Esplora -> {
|
|
buf.putInt(2)
|
|
this.config.write(buf)
|
|
|
|
}
|
|
}.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum class WordCount {
|
|
WORDS12,WORDS15,WORDS18,WORDS21,WORDS24;
|
|
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): WordCount {
|
|
return liftFromRustBuffer(rbuf) { buf -> WordCount.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer) =
|
|
try { values()[buf.getInt() - 1] }
|
|
catch (e: IndexOutOfBoundsException) {
|
|
throw RuntimeException("invalid enum value, something is very wrong!!", e)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
buf.putInt(this.ordinal + 1)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Throws(BdkException::class)
|
|
|
|
fun generateExtendedKey(network: Network, wordCount: WordCount, password: String? ): ExtendedKeyInfo {
|
|
val _retval =
|
|
rustCallWithError(BdkException) { status ->
|
|
_UniFFILib.INSTANCE.bdk_6983_generate_extended_key(network.lower(), wordCount.lower(), lowerOptionalString(password) ,status)
|
|
}
|
|
return ExtendedKeyInfo.lift(_retval)
|
|
}
|
|
|
|
|
|
@Throws(BdkException::class)
|
|
|
|
fun restoreExtendedKey(network: Network, mnemonic: String, password: String? ): ExtendedKeyInfo {
|
|
val _retval =
|
|
rustCallWithError(BdkException) { status ->
|
|
_UniFFILib.INSTANCE.bdk_6983_restore_extended_key(network.lower(), mnemonic.lower(), lowerOptionalString(password) ,status)
|
|
}
|
|
return ExtendedKeyInfo.lift(_retval)
|
|
}
|
|
|
|
|
|
public interface WalletInterface {
|
|
|
|
fun getNewAddress(): String
|
|
|
|
fun getLastUnusedAddress(): String
|
|
|
|
@Throws(BdkException::class)
|
|
fun getBalance(): ULong
|
|
|
|
@Throws(BdkException::class)
|
|
fun sign(psbt: PartiallySignedBitcoinTransaction )
|
|
|
|
@Throws(BdkException::class)
|
|
fun getTransactions(): List<Transaction>
|
|
|
|
fun getNetwork(): Network
|
|
|
|
@Throws(BdkException::class)
|
|
fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? )
|
|
|
|
@Throws(BdkException::class)
|
|
fun broadcast(psbt: PartiallySignedBitcoinTransaction ): Transaction
|
|
|
|
}
|
|
|
|
***************
|
|
*** 1034,1418 ****
|
|
--- 1080,1491 ----
|
|
}
|
|
}.let {
|
|
Transaction.lift(it)
|
|
}
|
|
|
|
|
|
|
|
companion object {
|
|
internal fun lift(ptr: Pointer): Wallet {
|
|
return Wallet(ptr)
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): Wallet {
|
|
// The Rust code always writes pointers as 8 bytes, and will
|
|
// fail to compile if they don't fit.
|
|
return Wallet.lift(Pointer(buf.getLong()))
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
public interface PartiallySignedBitcoinTransactionInterface {
|
|
|
|
fun serialize(): String
|
|
|
|
}
|
|
|
|
class PartiallySignedBitcoinTransaction(
|
|
pointer: Pointer
|
|
) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface {
|
|
constructor(wallet: Wallet, recipient: String, amount: ULong, feeRate: Float? ) :
|
|
this(
|
|
rustCallWithError(BdkException) { status ->
|
|
_UniFFILib.INSTANCE.bdk_6983_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower(), lowerOptionalFloat(feeRate) ,status)
|
|
})
|
|
|
|
/**
|
|
* Disconnect the object from the underlying Rust object.
|
|
*
|
|
* It can be called more than once, but once called, interacting with the object
|
|
* causes an `IllegalStateException`.
|
|
*
|
|
* Clients **must** call this method once done with the object, or cause a memory leak.
|
|
*/
|
|
override protected fun freeRustArcPtr() {
|
|
rustCall() { status ->
|
|
_UniFFILib.INSTANCE.ffi_bdk_6983_PartiallySignedBitcoinTransaction_object_free(this.pointer, status)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): Pointer = callWithPointer { it }
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
// The Rust code always expects pointers written as 8 bytes,
|
|
// and will fail to compile if they don't fit.
|
|
buf.putLong(Pointer.nativeValue(this.lower()))
|
|
}
|
|
|
|
override fun serialize(): String =
|
|
callWithPointer {
|
|
rustCall() { status ->
|
|
_UniFFILib.INSTANCE.bdk_6983_PartiallySignedBitcoinTransaction_serialize(it, status)
|
|
}
|
|
}.let {
|
|
String.lift(it)
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
companion object {
|
|
internal fun lift(ptr: Pointer): PartiallySignedBitcoinTransaction {
|
|
return PartiallySignedBitcoinTransaction(ptr)
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction {
|
|
// The Rust code always writes pointers as 8 bytes, and will
|
|
// fail to compile if they don't fit.
|
|
return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong()))
|
|
}
|
|
|
|
fun deserialize(psbtBase64: String ): PartiallySignedBitcoinTransaction =
|
|
PartiallySignedBitcoinTransaction(
|
|
rustCallWithError(BdkException) { status ->
|
|
_UniFFILib.INSTANCE.bdk_6983_PartiallySignedBitcoinTransaction_deserialize(psbtBase64.lower() ,status)
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
data class SledDbConfiguration (
|
|
var path: String,
|
|
var treeName: String
|
|
) {
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): SledDbConfiguration {
|
|
return liftFromRustBuffer(rbuf) { buf -> SledDbConfiguration.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): SledDbConfiguration {
|
|
return SledDbConfiguration(
|
|
String.read(buf),
|
|
String.read(buf)
|
|
)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
this.path.write(buf)
|
|
|
|
this.treeName.write(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
data class SqliteDbConfiguration (
|
|
var path: String
|
|
) {
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): SqliteDbConfiguration {
|
|
return liftFromRustBuffer(rbuf) { buf -> SqliteDbConfiguration.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): SqliteDbConfiguration {
|
|
return SqliteDbConfiguration(
|
|
String.read(buf)
|
|
)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
this.path.write(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
data class TransactionDetails (
|
|
var fees: ULong?,
|
|
var received: ULong,
|
|
var sent: ULong,
|
|
var txid: String
|
|
) {
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): TransactionDetails {
|
|
return liftFromRustBuffer(rbuf) { buf -> TransactionDetails.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): TransactionDetails {
|
|
return TransactionDetails(
|
|
readOptionalULong(buf),
|
|
ULong.read(buf),
|
|
ULong.read(buf),
|
|
String.read(buf)
|
|
)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
writeOptionalULong(this.fees, buf)
|
|
|
|
this.received.write(buf)
|
|
|
|
this.sent.write(buf)
|
|
|
|
this.txid.write(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
+ /**
|
|
+ * Block height and timestamp of a block
|
|
+ */
|
|
data class BlockTime (
|
|
var height: UInt,
|
|
var timestamp: ULong
|
|
) {
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): BlockTime {
|
|
return liftFromRustBuffer(rbuf) { buf -> BlockTime.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): BlockTime {
|
|
return BlockTime(
|
|
UInt.read(buf),
|
|
ULong.read(buf)
|
|
)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
this.height.write(buf)
|
|
|
|
this.timestamp.write(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
+ /**
|
|
+ * Configuration for an ElectrumBlockchain
|
|
+ *
|
|
+ * @property url URL of the Electrum server (such as ElectrumX, Esplora, BWT). May start with `ssl://` or `tcp://` and include a port, `e.g. ssl://electrum.blockstream.info:60002`
|
|
+ * @property socks5 URL of the socks5 proxy server or a Tor service
|
|
+ * @property retry Request retry count
|
|
+ * @property timeout Request timeout in seconds
|
|
+ * @property stopGap Stop searching addresses for transactions after finding an unused gap of this length
|
|
+ */
|
|
data class ElectrumConfig (
|
|
var url: String,
|
|
var socks5: String?,
|
|
var retry: UByte,
|
|
var timeout: UByte?,
|
|
var stopGap: ULong
|
|
) {
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): ElectrumConfig {
|
|
return liftFromRustBuffer(rbuf) { buf -> ElectrumConfig.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): ElectrumConfig {
|
|
return ElectrumConfig(
|
|
String.read(buf),
|
|
readOptionalString(buf),
|
|
UByte.read(buf),
|
|
readOptionalUByte(buf),
|
|
ULong.read(buf)
|
|
)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
this.url.write(buf)
|
|
|
|
writeOptionalString(this.socks5, buf)
|
|
|
|
this.retry.write(buf)
|
|
|
|
writeOptionalUByte(this.timeout, buf)
|
|
|
|
this.stopGap.write(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
data class EsploraConfig (
|
|
var baseUrl: String,
|
|
var proxy: String?,
|
|
var timeoutRead: ULong,
|
|
var timeoutWrite: ULong,
|
|
var stopGap: ULong
|
|
) {
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): EsploraConfig {
|
|
return liftFromRustBuffer(rbuf) { buf -> EsploraConfig.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): EsploraConfig {
|
|
return EsploraConfig(
|
|
String.read(buf),
|
|
readOptionalString(buf),
|
|
ULong.read(buf),
|
|
ULong.read(buf),
|
|
ULong.read(buf)
|
|
)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
this.baseUrl.write(buf)
|
|
|
|
writeOptionalString(this.proxy, buf)
|
|
|
|
this.timeoutRead.write(buf)
|
|
|
|
this.timeoutWrite.write(buf)
|
|
|
|
this.stopGap.write(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
data class ExtendedKeyInfo (
|
|
var mnemonic: String,
|
|
var xprv: String,
|
|
var fingerprint: String
|
|
) {
|
|
+ /**
|
|
+ * @suppress
|
|
+ */
|
|
companion object {
|
|
internal fun lift(rbuf: RustBuffer.ByValue): ExtendedKeyInfo {
|
|
return liftFromRustBuffer(rbuf) { buf -> ExtendedKeyInfo.read(buf) }
|
|
}
|
|
|
|
internal fun read(buf: ByteBuffer): ExtendedKeyInfo {
|
|
return ExtendedKeyInfo(
|
|
String.read(buf),
|
|
String.read(buf),
|
|
String.read(buf)
|
|
)
|
|
}
|
|
}
|
|
|
|
internal fun lower(): RustBuffer.ByValue {
|
|
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
|
|
}
|
|
|
|
internal fun write(buf: RustBufferBuilder) {
|
|
this.mnemonic.write(buf)
|
|
|
|
this.xprv.write(buf)
|
|
|
|
this.fingerprint.write(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
sealed class BdkException(message: String): Exception(message) {
|
|
// Each variant is a nested class
|
|
// Flat enums carries a string error message, so no special implementation is necessary.
|
|
class InvalidU32Bytes(message: String) : BdkException(message)
|
|
class Generic(message: String) : BdkException(message)
|
|
class ScriptDoesntHaveAddressForm(message: String) : BdkException(message)
|
|
class NoRecipients(message: String) : BdkException(message)
|
|
class NoUtxosSelected(message: String) : BdkException(message)
|
|
class OutputBelowDustLimit(message: String) : BdkException(message)
|
|
class InsufficientFunds(message: String) : BdkException(message)
|
|
class BnBTotalTriesExceeded(message: String) : BdkException(message)
|
|
class BnBNoExactMatch(message: String) : BdkException(message)
|
|
class UnknownUtxo(message: String) : BdkException(message)
|
|
class TransactionNotFound(message: String) : BdkException(message)
|
|
class TransactionConfirmed(message: String) : BdkException(message)
|
|
class IrreplaceableTransaction(message: String) : BdkException(message)
|
|
class FeeRateTooLow(message: String) : BdkException(message)
|
|
class FeeTooLow(message: String) : BdkException(message)
|
|
class FeeRateUnavailable(message: String) : BdkException(message)
|
|
class MissingKeyOrigin(message: String) : BdkException(message)
|
|
class Key(message: String) : BdkException(message)
|
|
class ChecksumMismatch(message: String) : BdkException(message)
|
|
class SpendingPolicyRequired(message: String) : BdkException(message)
|
|
class InvalidPolicyPathException(message: String) : BdkException(message)
|
|
class Signer(message: String) : BdkException(message)
|
|
class InvalidNetwork(message: String) : BdkException(message)
|
|
class InvalidProgressValue(message: String) : BdkException(message)
|
|
class ProgressUpdateException(message: String) : BdkException(message)
|
|
class InvalidOutpoint(message: String) : BdkException(message)
|
|
class Descriptor(message: String) : BdkException(message)
|
|
class AddressValidator(message: String) : BdkException(message)
|
|
class Encode(message: String) : BdkException(message)
|
|
class Miniscript(message: String) : BdkException(message)
|
|
class Bip32(message: String) : BdkException(message)
|
|
class Secp256k1(message: String) : BdkException(message)
|
|
class Json(message: String) : BdkException(message)
|
|
class Hex(message: String) : BdkException(message)
|