Return results as opaque structs from ffi calls

This commit is contained in:
Steve Myers 2021-06-19 22:53:26 -07:00
parent 87c823d497
commit 9e5aac759d
8 changed files with 447 additions and 112 deletions

View File

@ -6,9 +6,9 @@ plugins {
test {
environment "LD_LIBRARY_PATH", file("${projectDir}/libs/x86_64_linux").absolutePath
// testLogging {
// events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR"
// }
testLogging {
events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR"
}
}
//task buildRust(type: Exec) {

View File

@ -4,32 +4,71 @@ import com.sun.jna.*
interface Lib : Library {
// typedef struct WalletPtr WalletPtr_t;
class WalletPtr_t : PointerType {
constructor(): super()
constructor(pointer: Pointer): super(pointer)
// typedef struct VoidResult VoidResult_t;
class VoidResult_t : PointerType {
constructor() : super()
constructor(pointer: Pointer) : super(pointer)
}
// char * get_void_err (
// VoidResult_t const * void_result);
fun get_void_err(void_result: VoidResult_t): Pointer?
// void free_void_result (
// VoidResult_t * void_result);
fun free_void_result(void_result: VoidResult_t)
// typedef struct StringResult StringResult_t;
class StringResult_t : PointerType {
constructor() : super()
constructor(pointer: Pointer) : super(pointer)
}
// char * get_string_ok (
// StringResult_t const * string_result);
fun get_string_ok(string_result: StringResult_t): Pointer?
// char * get_string_err (
// StringResult_t const * string_result);
fun get_string_err(string_result: StringResult_t): Pointer?
// void free_string_result (
// StringResult_t * string_result);
fun free_string_result(string_result: StringResult_t)
// void free_string (
// char * string);
fun free_string(string: Pointer)
fun free_string(string: Pointer?)
// WalletPtr_t * new_wallet (
// typedef struct WalletResult WalletResult_t;
class WalletResult_t : PointerType {
constructor() : super()
constructor(pointer: Pointer) : super(pointer)
}
// WalletResult_t * new_wallet_result (
// char const * name,
// char const * descriptor,
// char const * change_descriptor);
fun new_wallet(name: String, descriptor: String, changeDescriptor: String?): WalletPtr_t
fun new_wallet_result(
name: String,
descriptor: String,
changeDescriptor: String?
): WalletResult_t
// void sync_wallet (
// WalletPtr_t * const * wallet);
//fun sync_wallet(wallet: WalletPtr_t)
fun sync_wallet(wallet: WalletPtr_t)
// char * get_wallet_err (
// WalletResult_t const * wallet_result);
// TODO
// char * new_address (
// WalletPtr_t * const * wallet);
fun new_address(wallet: WalletPtr_t): Pointer
// VoidResult_t * sync_wallet (
// WalletResult_t const * wallet_result);
fun sync_wallet(wallet_result: WalletResult_t): VoidResult_t
// void free_wallet (
// WalletPtr_t * wallet);
fun free_wallet(wallet: WalletPtr_t)
// StringResult_t * new_address (
// WalletResult_t const * wallet_result);
fun new_address(wallet_result: WalletResult_t): StringResult_t
// void free_wallet_result (
// WalletResult_t * wallet_result);
fun free_wallet_result(wallet_result: WalletResult_t)
}

View File

@ -3,6 +3,8 @@ package org.bitcoindevkit.bdk
import com.sun.jna.Native
import org.junit.*
import org.junit.Assert.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
/**
* Library test, which will execute on linux host.
@ -12,39 +14,56 @@ class LibTest {
companion object {
private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java)
private lateinit var wallet: Lib.WalletPtr_t
private lateinit var wallet_result: Lib.WalletResult_t
@BeforeClass
@JvmStatic
fun create_wallet() {
fun new_wallet() {
val name = "test_wallet"
val desc =
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
val change =
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
wallet = bdkFfi.new_wallet(name, desc, change)
println("wallet created")
wallet_result = bdkFfi.new_wallet_result(name, desc, change)
//println("wallet created")
}
@AfterClass
@JvmStatic
fun free_wallet() {
bdkFfi.free_wallet(wallet)
println("wallet freed")
bdkFfi.free_wallet_result(wallet_result)
//println("wallet freed")
}
}
@Test
fun wallet_sync_error() {
val bad_wallet_result = bdkFfi.new_wallet_result("test", "bad", null)
println("wallet result created")
val sync_result = bdkFfi.sync_wallet(bad_wallet_result)
val sync_err_pointer = bdkFfi.get_void_err(sync_result)
assertNotNull(sync_err_pointer)
val sync_err = sync_err_pointer.getString(0)
println("wallet sync error $sync_err")
}
@Test
fun sync() {
bdkFfi.sync_wallet(wallet)
val sync_result = bdkFfi.sync_wallet(wallet_result)
assertNull(bdkFfi.get_void_err(sync_result))
bdkFfi.free_void_result(sync_result)
}
@Test
fun new_newaddress_wallet() {
val pointer = bdkFfi.new_address(wallet)
val address = pointer.getString(0)
bdkFfi.free_string(pointer)
val address_result = bdkFfi.new_address(wallet_result)
assertNull(bdkFfi.get_string_err(address_result))
val address_pointer = bdkFfi.get_string_ok(address_result);
val address = address_pointer!!.getString(0)
bdkFfi.free_string_result(address_result)
bdkFfi.free_string(address_pointer)
//println("address created from kotlin: $address")
assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e")
}

View File

@ -15,38 +15,38 @@ cp target/debug/libbdk_ffi.so bdk-kotlin/jar/libs/x86_64_linux
(cd bdk-kotlin && gradle :jar:build)
# rust android
# If ANDROID_NDK_HOME is not set then set it to github actions default
[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle
# Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64
export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin
# Required for 'ring' dependency to cross-compile to Android platform, must be at least 21
export CFLAGS="-D__ANDROID_API__=21"
# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on
BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}"
mkdir -p bdk-kotlin/aar/src/main/jniLibs/ bdk-kotlin/aar/src/main/jniLibs/arm64-v8a bdk-kotlin/aar/src/main/jniLibs/x86_64 bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a bdk-kotlin/aar/src/main/jniLibs/x86
if echo $BUILD_TARGETS | grep "aarch64"; then
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android
cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/arm64-v8a
fi
if echo $BUILD_TARGETS | grep "x86_64"; then
CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android
cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86_64
fi
if echo $BUILD_TARGETS | grep "armv7"; then
CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi
cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a
fi
if echo $BUILD_TARGETS | grep "i686"; then
CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android
cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86
fi
# bdk-kotlin aar
(cd bdk-kotlin && gradle :aar:build)
## rust android
#
## If ANDROID_NDK_HOME is not set then set it to github actions default
#[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle
#
## Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64
#export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin
#
## Required for 'ring' dependency to cross-compile to Android platform, must be at least 21
#export CFLAGS="-D__ANDROID_API__=21"
#
## IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on
#BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}"
#
#mkdir -p bdk-kotlin/aar/src/main/jniLibs/ bdk-kotlin/aar/src/main/jniLibs/arm64-v8a bdk-kotlin/aar/src/main/jniLibs/x86_64 bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a bdk-kotlin/aar/src/main/jniLibs/x86
#
#if echo $BUILD_TARGETS | grep "aarch64"; then
# CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android
# cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/arm64-v8a
#fi
#if echo $BUILD_TARGETS | grep "x86_64"; then
# CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android
# cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86_64
#fi
#if echo $BUILD_TARGETS | grep "armv7"; then
# CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi
# cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a
#fi
#if echo $BUILD_TARGETS | grep "i686"; then
# CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android
# cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86
#fi
#
## bdk-kotlin aar
#(cd bdk-kotlin && gradle :aar:build)

View File

@ -6,40 +6,56 @@
int main (int argc, char const * const argv[])
{
char const * name = "test_wallet";
char const * desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)";
char const * change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)";
// test new_wallet
// test new wallet error
{
WalletPtr_t * wallet = new_wallet(name, desc, change);
assert(wallet != NULL);
WalletResult_t *wallet_result = new_wallet_result("bad", "bad", NULL);
assert(wallet_result != NULL);
char *wallet_error = get_wallet_err(wallet_result);
assert(wallet_error != NULL);
//printf("wallet error: %s\n", wallet_error);
free_string(wallet_error);
free_wallet_result(wallet_result);
}
// test new wallet
{
char const *name = "test_wallet";
char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)";
char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)";
WalletResult_t *wallet_result = new_wallet_result(name, desc, change);
assert(wallet_result != NULL);
// test sync_wallet
sync_wallet(wallet);
VoidResult_t *sync_result = sync_wallet(wallet_result);
free_void_result(sync_result);
// test new_address
char * address1 = new_address(wallet);
StringResult_t *address_result1 = new_address(wallet_result);
char *address1 = get_string_ok(address_result1);
//printf("address1: %s\n", address1);
assert( 0 == strcmp(address1,"tb1qgkhp034fyxeta00h0nne9tzfm0vsxq4prduzxp"));
free_string(address1);
free_string_result(address_result1);
char * address2 = new_address(wallet);
StringResult_t *address_result2 = new_address(wallet_result);
char *address2 = get_string_ok(address_result2);
//printf("address2: %s\n", address2);
assert(0 == strcmp(address2,"tb1qd6u9q327sru2ljvwzdtfrdg36sapax7udz97wf"));
free_string(address2);
free_string_result(address_result2);
// test free_wallet
free_wallet(wallet);
free_wallet_result(wallet_result);
// test free_wallet NULL doesn't crash
free_wallet(NULL);
free_wallet_result(NULL);
// verify free_wallet after free_wallet fails (core dumped)
////free_wallet(wallet);
////free_wallet_result(wallet_result);
// verify sync_wallet after free_wallet fails (core dumped)
////sync_wallet(wallet);
////VoidResult_t sync_result2 = sync_wallet(wallet_result);
}
return EXIT_SUCCESS;

197
src/error.rs Normal file
View File

@ -0,0 +1,197 @@
//use ::safer_ffi::prelude::*;
use bdk::Error;
pub fn error_name(error: &bdk::Error) -> &'static str {
match error {
Error::InvalidU32Bytes(_) => "InvalidU32Bytes",
Error::Generic(_) => "Generic",
Error::ScriptDoesntHaveAddressForm => "ScriptDoesntHaveAddressForm",
Error::SingleRecipientMultipleOutputs => "SingleRecipientMultipleOutputs",
Error::SingleRecipientNoInputs => "SingleRecipientNoInputs",
Error::NoRecipients => "NoRecipients",
Error::NoUtxosSelected => "NoUtxosSelected",
Error::OutputBelowDustLimit(_) => "OutputBelowDustLimit",
Error::InsufficientFunds { .. } => "InsufficientFunds",
Error::BnBTotalTriesExceeded => "BnBTotalTriesExceeded",
Error::BnBNoExactMatch => "BnBNoExactMatch",
Error::UnknownUtxo => "UnknownUtxo",
Error::TransactionNotFound => "TransactionNotFound",
Error::TransactionConfirmed => "TransactionConfirmed",
Error::IrreplaceableTransaction => "IrreplaceableTransaction",
Error::FeeRateTooLow { .. } => "FeeRateTooLow",
Error::FeeTooLow { .. } => "FeeTooLow",
Error::MissingKeyOrigin(_) => "MissingKeyOrigin",
Error::Key(_) => "Key",
Error::ChecksumMismatch => "ChecksumMismatch",
Error::SpendingPolicyRequired(_) => "SpendingPolicyRequired",
Error::InvalidPolicyPathError(_) => "InvalidPolicyPathError",
Error::Signer(_) => "Signer",
Error::InvalidProgressValue(_) => "InvalidProgressValue",
Error::ProgressUpdateError => "ProgressUpdateError",
Error::InvalidOutpoint(_) => "InvalidOutpoint",
Error::Descriptor(_) => "Descriptor",
Error::AddressValidator(_) => "AddressValidator",
Error::Encode(_) => "Encode",
Error::Miniscript(_) => "Miniscript",
Error::Bip32(_) => "Bip32",
Error::Secp256k1(_) => "Secp256k1",
Error::Json(_) => "Json",
Error::Hex(_) => "Hex",
Error::Psbt(_) => "Psbt",
Error::Electrum(_) => "Electrum",
// Error::Esplora(_) => {}
// Error::CompactFilters(_) => {}
Error::Sled(_) => "Sled",
_ => "Unknown",
}
}
//// Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet)
//// Simplified to work over the FFI interface
//#[derive_ReprC]
//#[repr(i16)]
//#[derive(Debug)]
//pub enum WalletError {
// None,
// InvalidU32Bytes,
// Generic,
// ScriptDoesntHaveAddressForm,
// SingleRecipientMultipleOutputs,
// SingleRecipientNoInputs,
// NoRecipients,
// NoUtxosSelected,
// OutputBelowDustLimit,
// InsufficientFunds,
// BnBTotalTriesExceeded,
// BnBNoExactMatch,
// UnknownUtxo,
// TransactionNotFound,
// TransactionConfirmed,
// IrreplaceableTransaction,
// FeeRateTooLow,
// FeeTooLow,
// FeeRateUnavailable,
// MissingKeyOrigin,
// Key,
// ChecksumMismatch,
// SpendingPolicyRequired,
// InvalidPolicyPathError,
// Signer,
// InvalidNetwork,
// InvalidProgressValue,
// ProgressUpdateError,
// InvalidOutpoint,
// Descriptor,
// AddressValidator,
// Encode,
// Miniscript,
// Bip32,
// Secp256k1,
// Json,
// Hex,
// Psbt,
// PsbtParse,
// //#[cfg(feature = "electrum")]
// Electrum,
// //#[cfg(feature = "esplora")]
// //Esplora,
// //#[cfg(feature = "compact_filters")]
// //CompactFilters,
// //#[cfg(feature = "key-value-db")]
// Sled,
//}
//impl From<bdk::Error> for WalletError {
// fn from(error: bdk::Error) -> Self {
// match error {
// Error::InvalidU32Bytes(_) => WalletError::InvalidNetwork,
// Error::Generic(_) => WalletError::Generic,
// Error::ScriptDoesntHaveAddressForm => WalletError::ScriptDoesntHaveAddressForm,
// Error::SingleRecipientMultipleOutputs => WalletError::SingleRecipientMultipleOutputs,
// Error::SingleRecipientNoInputs => WalletError::SingleRecipientNoInputs,
// Error::NoRecipients => WalletError::NoRecipients,
// Error::NoUtxosSelected => WalletError::NoUtxosSelected,
// Error::OutputBelowDustLimit(_) => WalletError::OutputBelowDustLimit,
// Error::InsufficientFunds { .. } => WalletError::InsufficientFunds,
// Error::BnBTotalTriesExceeded => WalletError::BnBTotalTriesExceeded,
// Error::BnBNoExactMatch => WalletError::BnBNoExactMatch,
// Error::UnknownUtxo => WalletError::UnknownUtxo,
// Error::TransactionNotFound => WalletError::TransactionNotFound,
// Error::TransactionConfirmed => WalletError::TransactionConfirmed,
// Error::IrreplaceableTransaction => WalletError::IrreplaceableTransaction,
// Error::FeeRateTooLow { .. } => WalletError::FeeRateTooLow,
// Error::FeeTooLow { .. } => WalletError::FeeTooLow,
// Error::MissingKeyOrigin(_) => WalletError::MissingKeyOrigin,
// Error::Key(_) => WalletError::Key,
// Error::ChecksumMismatch => WalletError::ChecksumMismatch,
// Error::SpendingPolicyRequired(_) => WalletError::SpendingPolicyRequired,
// Error::InvalidPolicyPathError(_) => WalletError::InvalidPolicyPathError,
// Error::Signer(_) => WalletError::Signer,
// Error::InvalidProgressValue(_) => WalletError::InvalidProgressValue,
// Error::ProgressUpdateError => WalletError::ProgressUpdateError,
// Error::InvalidOutpoint(_) => WalletError::InvalidOutpoint,
// Error::Descriptor(_) => WalletError::Descriptor,
// Error::AddressValidator(_) => WalletError::AddressValidator,
// Error::Encode(_) => WalletError::Encode,
// Error::Miniscript(_) => WalletError::Miniscript,
// Error::Bip32(_) => WalletError::Bip32,
// Error::Secp256k1(_) => WalletError::Secp256k1,
// Error::Json(_) => WalletError::Json,
// Error::Hex(_) => WalletError::Hex,
// Error::Psbt(_) => WalletError::Psbt,
// Error::Electrum(_) => WalletError::Electrum,
// //Error::Esplora(_) => WalletError::Esplora,
// //Error::CompactFilters(_) => {}
// Error::Sled(_) => WalletError::Sled,
// }
// }
//}
//type error_code = i16;
//
//impl From<bdk::Error> for error_code {
// fn from(error: bdk::Error) -> Self {
// match error {
// Error::InvalidU32Bytes(_) => 1,
// Error::Generic(_) => 2,
// Error::ScriptDoesntHaveAddressForm => 3,
// Error::SingleRecipientMultipleOutputs => 4,
// Error::SingleRecipientNoInputs => 5,
// Error::NoRecipients => 6,
// Error::NoUtxosSelected => 7,
// Error::OutputBelowDustLimit(_) => 8,
// Error::InsufficientFunds { .. } => 9,
// Error::BnBTotalTriesExceeded => 10,
// Error::BnBNoExactMatch => 11,
// Error::UnknownUtxo => 12,
// Error::TransactionNotFound => 13,
// Error::TransactionConfirmed => 14,
// Error::IrreplaceableTransaction => 15,
// Error::FeeRateTooLow { .. } => 16,
// Error::FeeTooLow { .. } => 17,
// Error::MissingKeyOrigin(_) => 18,
// Error::Key(_) => 19,
// Error::ChecksumMismatch => 20,
// Error::SpendingPolicyRequired(_) => 21,
// Error::InvalidPolicyPathError(_) => 22,
// Error::Signer(_) => 23,
// Error::InvalidProgressValue(_) => 24,
// Error::ProgressUpdateError => 25,
// Error::InvalidOutpoint(_) => 26,
// Error::Descriptor(_) => 27,
// Error::AddressValidator(_) => 28,
// Error::Encode(_) => 29,
// Error::Miniscript(_) => 30,
// Error::Bip32(_) => 31,
// Error::Secp256k1(_) => 32,
// Error::Json(_) => 33,
// Error::Hex(_) => 34,
// Error::Psbt(_) => 35,
// Error::Electrum(_) => 36,
// //Error::Esplora(_) => WalletError::Esplora,
// //Error::CompactFilters(_) => {}
// Error::Sled(_) => 37,
// _ => -1
// }
// }
//}

View File

@ -6,33 +6,84 @@ use bdk::blockchain::{
};
use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase};
use bdk::wallet::AddressIndex::New;
use bdk::Wallet;
use bdk::{Error, Wallet};
use safer_ffi::boxed::Box;
use safer_ffi::char_p::{char_p_boxed, char_p_ref};
#[derive_ReprC]
#[ReprC::opaque]
pub struct WalletPtr {
raw: Wallet<AnyBlockchain, AnyDatabase>,
}
impl From<Wallet<AnyBlockchain, AnyDatabase>> for WalletPtr {
fn from(wallet: Wallet<AnyBlockchain, AnyDatabase>) -> Self {
WalletPtr { raw: wallet }
}
pub struct VoidResult {
raw: Result<(), String>,
}
#[ffi_export]
fn new_wallet(
fn get_void_err(void_result: &VoidResult) -> Option<char_p_boxed> {
void_result
.raw
.as_ref()
.err()
.map(|s| char_p_boxed::try_from(s.clone()).unwrap())
}
#[ffi_export]
fn free_void_result(void_result: Option<Box<VoidResult>>) {
drop(void_result)
}
#[derive_ReprC]
#[ReprC::opaque]
pub struct StringResult {
raw: Result<String, String>,
}
#[ffi_export]
fn get_string_ok(string_result: &StringResult) -> Option<char_p_boxed> {
string_result
.raw
.as_ref()
.ok()
.map(|s| char_p_boxed::try_from(s.clone()).unwrap())
}
#[ffi_export]
fn get_string_err(string_result: &StringResult) -> Option<char_p_boxed> {
string_result
.raw
.as_ref()
.err()
.map(|s| char_p_boxed::try_from(s.clone()).unwrap())
}
#[ffi_export]
fn free_string_result(string_result: Option<Box<StringResult>>) {
drop(string_result)
}
#[derive_ReprC]
#[ReprC::opaque]
pub struct WalletResult {
raw: Result<Wallet<AnyBlockchain, AnyDatabase>, String>,
}
#[ffi_export]
fn new_wallet_result(
name: char_p_ref,
descriptor: char_p_ref,
change_descriptor: Option<char_p_ref>,
) -> Box<WalletPtr> {
let network = Testnet;
let _name = name.to_string();
) -> Box<WalletResult> {
let name = name.to_string();
let descriptor = descriptor.to_string();
let change_descriptor = change_descriptor.map(|s| s.to_string());
let wallet_result = new_wallet(name, descriptor, change_descriptor).map_err(|e| e.to_string());
Box::new(WalletResult { raw: wallet_result })
}
fn new_wallet(
name: String,
descriptor: String,
change_descriptor: Option<String>,
) -> Result<Wallet<AnyBlockchain, AnyDatabase>, Error> {
let network = Testnet;
let electrum_config = AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
url: "ssl://electrum.blockstream.info:60002".to_string(),
socks5: None,
@ -40,39 +91,52 @@ fn new_wallet(
timeout: None,
});
let blockchain_config = electrum_config;
let client = AnyBlockchain::from_config(&blockchain_config).unwrap();
let client = AnyBlockchain::from_config(&blockchain_config)?;
let database_config = AnyDatabaseConfig::Memory(());
let database = AnyDatabase::from_config(&database_config).unwrap();
let database = AnyDatabase::from_config(&database_config)?;
let descriptor: &str = descriptor.as_str();
let change_descriptor: Option<&str> = change_descriptor.as_deref();
let wallet = Wallet::new(descriptor, change_descriptor, network, database, client).unwrap();
Box::new(WalletPtr::from(wallet))
Wallet::new(descriptor, change_descriptor, network, database, client)
}
#[ffi_export]
fn sync_wallet(wallet: &WalletPtr) {
let _r = wallet.raw.sync(log_progress(), Some(100));
fn get_wallet_err(wallet_result: &WalletResult) -> Option<char_p_boxed> {
wallet_result
.raw
.as_ref()
.err()
.map(|s| char_p_boxed::try_from(s.clone()).unwrap())
}
#[ffi_export]
fn new_address(wallet: &WalletPtr) -> char_p_boxed {
let new_address = wallet.raw.get_address(New);
let new_address = new_address.unwrap();
let new_address = new_address.to_string();
new_address.try_into().unwrap()
fn sync_wallet(wallet_result: &WalletResult) -> Box<VoidResult> {
let wallet_result_ref = wallet_result.raw.as_ref().map_err(|error| error.clone());
let void_result = wallet_result_ref
.and_then(|w| w.sync(log_progress(), Some(100)).map_err(|e| e.to_string()));
Box::new(VoidResult { raw: void_result })
}
#[ffi_export]
fn new_address(wallet_result: &WalletResult) -> Box<StringResult> {
let new_address = wallet_result
.raw
.as_ref()
.map_err(|error| error.clone())
.and_then(|w| w.get_address(New).map_err(|error| error.to_string()));
let string_result = new_address.map(|a| a.to_string());
Box::new(StringResult { raw: string_result })
}
#[ffi_export]
fn free_wallet_result(wallet_result: Option<Box<WalletResult>>) {
drop(wallet_result)
}
/// Frees a Rust-allocated string
#[ffi_export]
fn free_string(string: char_p_boxed) {
fn free_string(string: Option<char_p_boxed>) {
drop(string)
}
#[ffi_export]
fn free_wallet(wallet: Option<Box<WalletPtr>>) {
drop(wallet)
}

View File

@ -6,9 +6,9 @@ cargo test --features c-headers -- generate_headers
# cc
export LD_LIBRARY_PATH=`pwd`/target/debug
valgrind --leak-check=full cc/bdk_ffi_test
#cc/bdk_ffi_test
#valgrind --leak-check=full cc/bdk_ffi_test
cc/bdk_ffi_test
# bdk-kotlin
(cd bdk-kotlin && gradle test)
(cd bdk-kotlin && gradle :aar:connectedDebugAndroidTest)
#(cd bdk-kotlin && gradle :aar:connectedDebugAndroidTest)