From f6c10da805064106ff0e767230673e415144feb2 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sat, 19 Jun 2021 22:53:26 -0700 Subject: [PATCH] Return results as opaque structs from ffi calls --- build.sh | 70 +++++++++--------- src/error.rs | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/wallet.rs | 126 ++++++++++++++++++++++++-------- test.sh | 6 +- 4 files changed, 330 insertions(+), 69 deletions(-) create mode 100644 src/error.rs diff --git a/build.sh b/build.sh index 60f3bef..b8b5c01 100755 --- a/build.sh +++ b/build.sh @@ -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) diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..fba774c --- /dev/null +++ b/src/error.rs @@ -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 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 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 +// } +// } +//} diff --git a/src/wallet.rs b/src/wallet.rs index 1a9156b..87b1fcc 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -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, -} - -impl From> for WalletPtr { - fn from(wallet: Wallet) -> Self { - WalletPtr { raw: wallet } - } +pub struct VoidResult { + raw: Result<(), String>, } #[ffi_export] -fn new_wallet( +fn get_void_err(void_result: &VoidResult) -> Option { + 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>) { + drop(void_result) +} + +#[derive_ReprC] +#[ReprC::opaque] +pub struct StringResult { + raw: Result, +} + +#[ffi_export] +fn get_string_ok(string_result: &StringResult) -> Option { + 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 { + 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>) { + drop(string_result) +} + +#[derive_ReprC] +#[ReprC::opaque] +pub struct WalletResult { + raw: Result, String>, +} + +#[ffi_export] +fn new_wallet_result( name: char_p_ref, descriptor: char_p_ref, change_descriptor: Option, -) -> Box { - let network = Testnet; - let _name = name.to_string(); +) -> Box { + 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, +) -> Result, 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 { + 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 { + 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 { + 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>) { + drop(wallet_result) } /// Frees a Rust-allocated string #[ffi_export] -fn free_string(string: char_p_boxed) { +fn free_string(string: Option) { drop(string) } - -#[ffi_export] -fn free_wallet(wallet: Option>) { - drop(wallet) -} diff --git a/test.sh b/test.sh index da24835..145cdca 100755 --- a/test.sh +++ b/test.sh @@ -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)