diff --git a/.github/workflows/precompile_binaries.yml b/.github/workflows/precompile_binaries.yml index 0f6bca95..0804c5b0 100644 --- a/.github/workflows/precompile_binaries.yml +++ b/.github/workflows/precompile_binaries.yml @@ -1,6 +1,6 @@ on: push: - branches: [0.31.2, master, main] + branches: '*' name: Precompile Binaries @@ -32,6 +32,12 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' + - name: Set up Android SDK + if: (matrix.os == 'ubuntu-20.04') + uses: android-actions/setup-android@v2 + - name: Install Specific NDK + if: (matrix.os == 'ubuntu-20.04') + run: sdkmanager --install "ndk;24.0.8215888" - name: Precompile (with iOS) if: (matrix.os == 'macOS-latest') run: dart run build_tool precompile-binaries -v --manifest-dir=../../rust --repository=LtbLightning/bdk-flutter @@ -45,4 +51,4 @@ jobs: working-directory: cargokit/build_tool env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} - PRIVATE_KEY: ${{ secrets.CARGOKIT_PRIVATE_KEY }} \ No newline at end of file + PRIVATE_KEY: ${{ secrets.CARGOKIT_PRIVATE_KEY }} diff --git a/CHANGELOG.md b/CHANGELOG.md index c2f08588..1c19c3e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Updated `flutter_rust_bridge` to `2.0.0`. - `PartiallySignedTransaction`, `ScriptBuf` & `Transaction`. #### Changed - `partiallySignedTransaction.serialize()` serialize the data as raw binary. +#### Fixed +- Thread `frb_workerpool` panicked on Sql database access. + ## [0.31.2-dev.2] #### Fixed diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 0bfa73ba..e59e280d 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1 +1 @@ - + diff --git a/example/android/build.gradle b/example/android/build.gradle index 0ce09934..5c8d9b8f 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.6.10' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4a..b6363034 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 55a89e94..2d921409 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -14,9 +14,9 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral SPEC CHECKSUMS: - bdk_flutter: f31096ce6d28094dbbb43d2a3fb130f7c54683df + bdk_flutter: d0437c6116753242241fed48270587542a636d40 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 PODFILE CHECKSUM: 6acf97521436d16fc31cd5e1a02000905acdb3ae -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef643..8e02df28 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/example/pubspec.lock b/example/pubspec.lock index 42f35cef..64ae97c4 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -230,18 +230,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -278,18 +278,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mockito: dependency: transitive description: @@ -387,10 +387,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" typed_data: dependency: transitive description: @@ -419,10 +419,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/lib/bdk_flutter.dart b/lib/bdk_flutter.dart index 48f3898f..04948352 100644 --- a/lib/bdk_flutter.dart +++ b/lib/bdk_flutter.dart @@ -40,4 +40,11 @@ export './src/generated/api/types.dart' export './src/generated/api/wallet.dart' hide BdkWallet, finishBumpFeeTxBuilder, txBuilderFinish; export './src/root.dart'; -export 'src/utils/exceptions.dart' hide mapBdkError, BdkFfiException; +export 'src/utils/exceptions.dart' + hide + mapBdkError, + mapAddressError, + mapConsensusError, + mapDescriptorError, + mapHexError, + BdkFfiException; diff --git a/lib/src/generated/api/error.dart b/lib/src/generated/api/error.dart index c02c6f53..03733e53 100644 --- a/lib/src/generated/api/error.dart +++ b/lib/src/generated/api/error.dart @@ -10,7 +10,7 @@ import 'package:freezed_annotation/freezed_annotation.dart' hide protected; import 'types.dart'; part 'error.freezed.dart'; -// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from` @freezed sealed class AddressError with _$AddressError { diff --git a/lib/src/generated/api/wallet.dart b/lib/src/generated/api/wallet.dart index b08d5dc4..b518c8b8 100644 --- a/lib/src/generated/api/wallet.dart +++ b/lib/src/generated/api/wallet.dart @@ -12,7 +12,6 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; import 'psbt.dart'; import 'types.dart'; -// These functions are ignored because they are not marked as `pub`: `get_wallet` // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `fmt` Future<(BdkPsbt, TransactionDetails)> finishBumpFeeTxBuilder( @@ -109,7 +108,6 @@ class BdkWallet { onlyWitnessUtxo: onlyWitnessUtxo, sighashType: sighashType); - /// Return whether or not a script is part of this wallet (either internal or external). bool isMine({required BdkScriptBuf script}) => core.instance.api .crateApiWalletBdkWalletIsMine(that: this, script: script); diff --git a/lib/src/generated/frb_generated.dart b/lib/src/generated/frb_generated.dart index 81416fde..cd85e554 100644 --- a/lib/src/generated/frb_generated.dart +++ b/lib/src/generated/frb_generated.dart @@ -1358,7 +1358,7 @@ class coreApiImpl extends coreApiImplPlatform implements coreApi { }, codec: DcoCodec( decodeSuccessData: dco_decode_String, - decodeErrorData: null, + decodeErrorData: dco_decode_bdk_error, ), constMeta: kCrateApiPsbtBdkPsbtAsStringConstMeta, argValues: [that], @@ -1428,7 +1428,7 @@ class coreApiImpl extends coreApiImplPlatform implements coreApi { }, codec: DcoCodec( decodeSuccessData: dco_decode_opt_box_autoadd_u_64, - decodeErrorData: null, + decodeErrorData: dco_decode_bdk_error, ), constMeta: kCrateApiPsbtBdkPsbtFeeAmountConstMeta, argValues: [that], @@ -1451,7 +1451,7 @@ class coreApiImpl extends coreApiImplPlatform implements coreApi { }, codec: DcoCodec( decodeSuccessData: dco_decode_opt_box_autoadd_fee_rate, - decodeErrorData: null, + decodeErrorData: dco_decode_bdk_error, ), constMeta: kCrateApiPsbtBdkPsbtFeeRateConstMeta, argValues: [that], @@ -1495,7 +1495,7 @@ class coreApiImpl extends coreApiImplPlatform implements coreApi { }, codec: DcoCodec( decodeSuccessData: dco_decode_String, - decodeErrorData: null, + decodeErrorData: dco_decode_bdk_error, ), constMeta: kCrateApiPsbtBdkPsbtJsonSerializeConstMeta, argValues: [that], @@ -1518,7 +1518,7 @@ class coreApiImpl extends coreApiImplPlatform implements coreApi { }, codec: DcoCodec( decodeSuccessData: dco_decode_list_prim_u_8_strict, - decodeErrorData: null, + decodeErrorData: dco_decode_bdk_error, ), constMeta: kCrateApiPsbtBdkPsbtSerializeConstMeta, argValues: [that], @@ -1541,7 +1541,7 @@ class coreApiImpl extends coreApiImplPlatform implements coreApi { }, codec: DcoCodec( decodeSuccessData: dco_decode_String, - decodeErrorData: null, + decodeErrorData: dco_decode_bdk_error, ), constMeta: kCrateApiPsbtBdkPsbtTxidConstMeta, argValues: [that], @@ -2411,7 +2411,7 @@ class coreApiImpl extends coreApiImplPlatform implements coreApi { }, codec: DcoCodec( decodeSuccessData: dco_decode_network, - decodeErrorData: null, + decodeErrorData: dco_decode_bdk_error, ), constMeta: kCrateApiWalletBdkWalletNetworkConstMeta, argValues: [that], diff --git a/lib/src/root.dart b/lib/src/root.dart index 760afe41..d39ff998 100644 --- a/lib/src/root.dart +++ b/lib/src/root.dart @@ -117,13 +117,13 @@ class Blockchain extends BdkBlockchain { } /// [Blockchain] constructor for creating `Esplora` blockchain in `Mutinynet` - /// Esplora url: https://mutinynet.ltbl.io/api + /// Esplora url: https://mutinynet.com/api/ static Future createMutinynet({ int stopGap = 20, }) async { final config = BlockchainConfig.esplora( config: EsploraConfig( - baseUrl: 'https://mutinynet.ltbl.io/api', + baseUrl: 'https://mutinynet.com/api/', stopGap: BigInt.from(stopGap), ), ); diff --git a/macos/bdk_flutter.podspec b/macos/bdk_flutter.podspec index 5d5b3899..c1ff53ea 100644 --- a/macos/bdk_flutter.podspec +++ b/macos/bdk_flutter.podspec @@ -27,8 +27,10 @@ Pod::Spec.new do |s| } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', - # Flutter.framework does not contain a i386 slice. - 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libbdk_flutter.a', + 'DEAD_CODE_STRIPPING' => 'YES', + 'STRIP_INSTALLED_PRODUCT[config=Release][sdk=*][arch=*]' => "YES", + 'STRIP_STYLE[config=Release][sdk=*][arch=*]' => "non-global", + 'DEPLOYMENT_POSTPROCESSING[config=Release][sdk=*][arch=*]' => "YES", } end diff --git a/pubspec.lock b/pubspec.lock index b05e769c..6902f64e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -327,18 +327,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -375,18 +375,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: "direct main" description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -540,10 +540,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" timing: dependency: transitive description: @@ -580,10 +580,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index aab02e5f..04a0e4c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_rust_bridge: ">2.0.0-dev.41 <= 2.0.0" + flutter_rust_bridge: ">=2.0.0 < 2.1.0" ffi: ^2.0.1 freezed_annotation: ^2.2.0 mockito: ^5.4.0 diff --git a/rust/cargokit.yaml b/rust/cargokit.yaml index 6056237a..4657395e 100644 --- a/rust/cargokit.yaml +++ b/rust/cargokit.yaml @@ -3,5 +3,4 @@ cargo: toolchain: stable precompiled_binaries: url_prefix: https://github.com/LtbLightning/bdk-flutter/releases/download/precompiled_ - public_key: 0e43d5e8452d00db7f3000c18fb1ba796babfcb5dc6306bb0629eff24f8be85b - + public_key: 0e43d5e8452d00db7f3000c18fb1ba796babfcb5dc6306bb0629eff24f8be85b \ No newline at end of file diff --git a/rust/src/api/blockchain.rs b/rust/src/api/blockchain.rs index c80362e1..ea297051 100644 --- a/rust/src/api/blockchain.rs +++ b/rust/src/api/blockchain.rs @@ -33,7 +33,7 @@ impl BdkBlockchain { socks5: config.socks5, timeout: config.timeout, url: config.url, - stop_gap: usize::try_from(config.stop_gap).unwrap(), + stop_gap: config.stop_gap as usize, validate_domain: config.validate_domain, }) } @@ -42,7 +42,7 @@ impl BdkBlockchain { base_url: config.base_url, proxy: config.proxy, concurrency: config.concurrency, - stop_gap: usize::try_from(config.stop_gap).unwrap(), + stop_gap: config.stop_gap as usize, timeout: config.timeout, }) } diff --git a/rust/src/api/error.rs b/rust/src/api/error.rs index df88e2ed..9a986ab0 100644 --- a/rust/src/api/error.rs +++ b/rust/src/api/error.rs @@ -361,3 +361,8 @@ impl From for BdkError { BdkError::PsbtParse(value.to_string()) } } +impl From for BdkError { + fn from(value: bdk::keys::bip39::Error) -> Self { + BdkError::Bip39(value.to_string()) + } +} diff --git a/rust/src/api/key.rs b/rust/src/api/key.rs index 9d106e0a..ef55b4c2 100644 --- a/rust/src/api/key.rs +++ b/rust/src/api/key.rs @@ -25,7 +25,12 @@ impl BdkMnemonic { /// Generates Mnemonic with a random entropy pub fn new(word_count: WordCount) -> Result { let generated_key: keys::GeneratedKey<_, BareCtx> = - keys::bip39::Mnemonic::generate((word_count.into(), Language::English)).unwrap(); + (match keys::bip39::Mnemonic::generate((word_count.into(), Language::English)) { + Ok(value) => Ok(value), + Err(Some(err)) => Err(BdkError::Bip39(err.to_string())), + Err(None) => Err(BdkError::Generic("".to_string())), // If + })?; + keys::bip39::Mnemonic::parse_in(Language::English, generated_key.to_string()) .map(|e| e.into()) .map_err(|e| BdkError::Bip39(e.to_string())) @@ -93,10 +98,19 @@ impl BdkDescriptorSecretKey { password: Option, ) -> Result { let mnemonic = (*mnemonic.ptr).clone(); - let xkey: keys::ExtendedKey = (mnemonic, password).into_extended_key().unwrap(); + let xkey: keys::ExtendedKey = (mnemonic, password) + .into_extended_key() + .map_err(|e| BdkError::Key(e.to_string()))?; + let xpriv = if let Some(e) = xkey.into_xprv(network.into()) { + Ok(e) + } else { + Err(BdkError::Generic( + "private data not found in the key!".to_string(), + )) + }; let descriptor_secret_key = keys::DescriptorSecretKey::XPrv(DescriptorXKey { origin: None, - xkey: xkey.into_xprv(network.into()).unwrap(), + xkey: xpriv?, derivation_path: bitcoin::bip32::DerivationPath::master(), wildcard: Wildcard::Unhardened, }); @@ -163,7 +177,10 @@ impl BdkDescriptorSecretKey { #[frb(sync)] pub fn as_public(ptr: BdkDescriptorSecretKey) -> Result { let secp = Secp256k1::new(); - let descriptor_public_key = ptr.ptr.to_public(&secp).unwrap(); + let descriptor_public_key = ptr + .ptr + .to_public(&secp) + .map_err(|e| BdkError::Generic(e.to_string()))?; Ok(descriptor_public_key.into()) } #[frb(sync)] @@ -184,7 +201,8 @@ impl BdkDescriptorSecretKey { } pub fn from_string(secret_key: String) -> Result { - let key = keys::DescriptorSecretKey::from_str(&*secret_key).unwrap(); + let key = keys::DescriptorSecretKey::from_str(&*secret_key) + .map_err(|e| BdkError::Generic(e.to_string()))?; Ok(key.into()) } #[frb(sync)] diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index 73e4799d..03f77c7d 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -1,3 +1,7 @@ +use std::{fmt::Debug, sync::Mutex}; + +use error::BdkError; + pub mod blockchain; pub mod descriptor; pub mod error; @@ -5,3 +9,17 @@ pub mod key; pub mod psbt; pub mod types; pub mod wallet; + +pub(crate) fn handle_mutex(lock: &Mutex, operation: F) -> Result +where + T: Debug, + F: FnOnce(&mut T) -> R, +{ + match lock.lock() { + Ok(mut mutex_guard) => Ok(operation(&mut *mutex_guard)), + Err(poisoned) => { + drop(poisoned.into_inner()); + Err(BdkError::Generic("Poison Error!".to_string())) + } + } +} diff --git a/rust/src/api/psbt.rs b/rust/src/api/psbt.rs index 30c1f6c6..780fae47 100644 --- a/rust/src/api/psbt.rs +++ b/rust/src/api/psbt.rs @@ -8,6 +8,8 @@ use std::str::FromStr; use flutter_rust_bridge::frb; +use super::handle_mutex; + #[derive(Debug)] pub struct BdkPsbt { pub ptr: RustOpaque>, @@ -28,43 +30,51 @@ impl BdkPsbt { } #[frb(sync)] - pub fn as_string(&self) -> String { - let psbt = self.ptr.lock().unwrap().clone(); - psbt.to_string() + pub fn as_string(&self) -> Result { + handle_mutex(&self.ptr, |psbt| psbt.to_string()) } ///Computes the `Txid`. /// Hashes the transaction excluding the segwit data (i. e. the marker, flag bytes, and the witness fields themselves). /// For non-segwit transactions which do not have any segwit data, this will be equal to transaction.wtxid(). #[frb(sync)] - pub fn txid(&self) -> String { - let tx = self.ptr.lock().unwrap().clone().extract_tx(); - let txid = tx.txid(); - txid.to_string() + pub fn txid(&self) -> Result { + handle_mutex(&self.ptr, |psbt| { + psbt.to_owned().extract_tx().txid().to_string() + }) } /// Return the transaction. #[frb(sync)] pub fn extract_tx(ptr: BdkPsbt) -> Result { - let tx = ptr.ptr.lock().unwrap().clone().extract_tx(); - tx.try_into() + handle_mutex(&ptr.ptr, |psbt| { + let tx = psbt.to_owned().extract_tx(); + tx.try_into() + })? } /// Combines this PartiallySignedTransaction with other PSBT as described by BIP 174. /// /// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)` pub fn combine(ptr: BdkPsbt, other: BdkPsbt) -> Result { - let other_psbt = other.ptr.lock().unwrap().clone(); - let mut original_psbt = ptr.ptr.lock().unwrap().clone(); + let other_psbt = other + .ptr + .lock() + .map_err(|_| BdkError::Generic("Poison Error!".to_string()))? + .clone(); + let mut original_psbt = ptr + .ptr + .lock() + .map_err(|_| BdkError::Generic("Poison Error!".to_string()))?; original_psbt.combine(other_psbt)?; - Ok(original_psbt.into()) + Ok(original_psbt.to_owned().into()) } /// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats. /// If the PSBT is missing a TxOut for an input returns None. #[frb(sync)] - pub fn fee_amount(&self) -> Option { - self.ptr.lock().unwrap().fee_amount() + pub fn fee_amount(&self) -> Result, BdkError> { + handle_mutex(&self.ptr, |psbt| psbt.fee_amount()) } /// The transaction's fee rate. This value will only be accurate if calculated AFTER the @@ -72,20 +82,20 @@ impl BdkPsbt { /// transaction. /// If the PSBT is missing a TxOut for an input returns None. #[frb(sync)] - pub fn fee_rate(&self) -> Option { - self.ptr.lock().unwrap().fee_rate().map(|e| e.into()) + pub fn fee_rate(&self) -> Result, BdkError> { + handle_mutex(&self.ptr, |psbt| psbt.fee_rate().map(|e| e.into())) } ///Serialize as raw binary data #[frb(sync)] - pub fn serialize(&self) -> Vec { - let psbt = self.ptr.lock().unwrap().clone(); - psbt.serialize() + pub fn serialize(&self) -> Result, BdkError> { + handle_mutex(&self.ptr, |psbt| psbt.serialize()) } /// Serialize the PSBT data structure as a String of JSON. #[frb(sync)] - pub fn json_serialize(&self) -> String { - let psbt = self.ptr.lock().unwrap(); - serde_json::to_string(psbt.deref()).unwrap() + pub fn json_serialize(&self) -> Result { + handle_mutex(&self.ptr, |psbt| { + serde_json::to_string(psbt.deref()).map_err(|e| BdkError::Generic(e.to_string())) + })? } } diff --git a/rust/src/api/wallet.rs b/rust/src/api/wallet.rs index fccf5eaa..ec593ada 100644 --- a/rust/src/api/wallet.rs +++ b/rust/src/api/wallet.rs @@ -1,8 +1,21 @@ use crate::api::descriptor::BdkDescriptor; use crate::api::types::{ - AddressIndex, Balance, BdkAddress, BdkScriptBuf, ChangeSpendPolicy, DatabaseConfig, Input, - KeychainKind, LocalUtxo, Network, OutPoint, PsbtSigHashType, RbfValue, ScriptAmount, - SignOptions, TransactionDetails, + AddressIndex, + Balance, + BdkAddress, + BdkScriptBuf, + ChangeSpendPolicy, + DatabaseConfig, + Input, + KeychainKind, + LocalUtxo, + Network, + OutPoint, + PsbtSigHashType, + RbfValue, + ScriptAmount, + SignOptions, + TransactionDetails, }; use std::ops::Deref; use std::str::FromStr; @@ -12,13 +25,14 @@ use crate::api::error::BdkError; use crate::api::psbt::BdkPsbt; use crate::frb_generated::RustOpaque; use bdk::bitcoin::script::PushBytesBuf; -use bdk::bitcoin::{Sequence, Txid}; +use bdk::bitcoin::{ Sequence, Txid }; pub use bdk::blockchain::GetTx; use bdk::database::ConfigurableDatabase; -use std::sync::MutexGuard; use flutter_rust_bridge::frb; +use super::handle_mutex; + #[derive(Debug)] pub struct BdkWallet { pub ptr: RustOpaque>>, @@ -28,7 +42,7 @@ impl BdkWallet { descriptor: BdkDescriptor, change_descriptor: Option, network: Network, - database_config: DatabaseConfig, + database_config: DatabaseConfig ) -> Result { let database = bdk::database::AnyDatabase::from_config(&database_config.into())?; let descriptor: String = descriptor.to_string_private(); @@ -38,27 +52,25 @@ impl BdkWallet { &descriptor, change_descriptor.as_ref(), network.into(), - database, + database )?; Ok(BdkWallet { ptr: RustOpaque::new(std::sync::Mutex::new(wallet)), }) } - pub(crate) fn get_wallet(&self) -> MutexGuard> { - self.ptr.lock().expect("") - } /// Get the Bitcoin network the wallet is using. - #[frb(sync)] - pub fn network(&self) -> Network { - self.get_wallet().network().into() + #[frb(sync)] + pub fn network(&self) -> Result { + handle_mutex(&self.ptr, |w| w.network().into()) } - /// Return whether or not a script is part of this wallet (either internal or external). #[frb(sync)] pub fn is_mine(&self, script: BdkScriptBuf) -> Result { - self.get_wallet() - .is_mine(>::into(script).as_script()) - .map_err(|e| e.into()) + handle_mutex(&self.ptr, |w| { + w.is_mine( + >::into(script).as_script() + ).map_err(|e| e.into()) + })? } /// Return a derived address using the external descriptor, see AddressIndex for available address index selection /// strategies. If none of the keys in the descriptor are derivable (i.e. the descriptor does not end with a * character) @@ -66,12 +78,13 @@ impl BdkWallet { #[frb(sync)] pub fn get_address( ptr: BdkWallet, - address_index: AddressIndex, + address_index: AddressIndex ) -> Result<(BdkAddress, u32), BdkError> { - ptr.get_wallet() - .get_address(address_index.into()) - .map(|e| (e.address.into(), e.index)) - .map_err(|e| e.into()) + handle_mutex(&ptr.ptr, |w| { + w.get_address(address_index.into()) + .map(|e| (e.address.into(), e.index)) + .map_err(|e| e.into()) + })? } /// Return a derived address using the internal (change) descriptor. @@ -84,46 +97,51 @@ impl BdkWallet { #[frb(sync)] pub fn get_internal_address( ptr: BdkWallet, - address_index: AddressIndex, + address_index: AddressIndex ) -> Result<(BdkAddress, u32), BdkError> { - ptr.get_wallet() - .get_internal_address(address_index.into()) - .map(|e| (e.address.into(), e.index)) - .map_err(|e| e.into()) + handle_mutex(&ptr.ptr, |w| { + w.get_internal_address(address_index.into()) + .map(|e| (e.address.into(), e.index)) + .map_err(|e| e.into()) + })? } /// Return the balance, meaning the sum of this wallet’s unspent outputs’ values. Note that this method only operates /// on the internal database, which first needs to be Wallet.sync manually. #[frb(sync)] pub fn get_balance(&self) -> Result { - self.get_wallet() - .get_balance() - .map(|b| b.into()) - .map_err(|e| e.into()) + handle_mutex(&self.ptr, |w| { + w.get_balance() + .map(|b| b.into()) + .map_err(|e| e.into()) + })? } /// Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually. #[frb(sync)] pub fn list_transactions( &self, - include_raw: bool, + include_raw: bool ) -> Result, BdkError> { - let mut transaction_details = vec![]; - for e in self - .get_wallet() - .list_transactions(include_raw)? - .into_iter() - { - transaction_details.push(e.try_into()?); - } - Ok(transaction_details) + handle_mutex(&self.ptr, |wallet| { + let mut transaction_details = vec![]; + + // List transactions and convert them using try_into + for e in wallet.list_transactions(include_raw)?.into_iter() { + transaction_details.push(e.try_into()?); + } + + Ok(transaction_details) + })? } /// Return the list of unspent outputs of this wallet. Note that this method only operates on the internal database, /// which first needs to be Wallet.sync manually. #[frb(sync)] pub fn list_unspent(&self) -> Result, BdkError> { - let unspent: Vec = self.get_wallet().list_unspent()?; - Ok(unspent.into_iter().map(LocalUtxo::from).collect()) + handle_mutex(&self.ptr, |w| { + let unspent: Vec = w.list_unspent()?; + Ok(unspent.into_iter().map(LocalUtxo::from).collect()) + })? } /// Sign a transaction with all the wallet's signers. This function returns an encapsulated bool that @@ -136,91 +154,48 @@ impl BdkWallet { pub fn sign( ptr: BdkWallet, psbt: BdkPsbt, - sign_options: Option, + sign_options: Option ) -> Result { - let mut psbt = psbt.ptr.lock().unwrap(); - ptr.get_wallet() - .sign( - &mut psbt, - sign_options.map(SignOptions::into).unwrap_or_default(), + let mut psbt = psbt.ptr.lock().map_err(|_| BdkError::Generic("Poison Error!".to_string()))?; + handle_mutex(&ptr.ptr, |w| { + w.sign(&mut psbt, sign_options.map(SignOptions::into).unwrap_or_default()).map_err(|e| + e.into() ) - .map_err(|e| e.into()) + })? } /// Sync the internal database with the blockchain. pub fn sync(ptr: BdkWallet, blockchain: &BdkBlockchain) -> Result<(), BdkError> { - let blockchain = blockchain.get_blockchain(); - ptr.get_wallet() - .sync(blockchain.deref(), bdk::SyncOptions::default()) - .map_err(|e| e.into()) + handle_mutex(&ptr.ptr, |w| { + w.sync(blockchain.ptr.deref(), bdk::SyncOptions::default()).map_err(|e| e.into()) + })? } - //TODO recreate verify_tx properly - // pub fn verify_tx(ptr: BdkWallet, tx: BdkTransaction) -> Result<(), BdkError> { - // let serialized_tx = tx.serialize()?; - // let tx: Transaction = (&tx).try_into()?; - // let locked_wallet = ptr.get_wallet(); - // // Loop through all the inputs - // for (index, input) in tx.input.iter().enumerate() { - // let input = input.clone(); - // let txid = input.previous_output.txid; - // let prev_tx = match locked_wallet.database().get_raw_tx(&txid){ - // Ok(prev_tx) => Ok(prev_tx), - // Err(e) => Err(BdkError::VerifyTransaction(format!("The transaction {:?} being spent is not available in the wallet database: {:?} ", txid,e))) - // }; - // if let Some(prev_tx) = prev_tx? { - // let spent_output = match prev_tx.output.get(input.previous_output.vout as usize) { - // Some(output) => Ok(output), - // None => Err(BdkError::VerifyTransaction(format!( - // "Failed to verify transaction: missing output {:?} in tx {:?}", - // input.previous_output.vout, txid - // ))), - // }; - // let spent_output = spent_output?; - // return match bitcoinconsensus::verify( - // &spent_output.clone().script_pubkey.to_bytes(), - // spent_output.value, - // &serialized_tx, - // None, - // index, - // ) { - // Ok(()) => Ok(()), - // Err(e) => Err(BdkError::VerifyTransaction(e.to_string())), - // }; - // } else { - // if tx.is_coin_base() { - // continue; - // } else { - // return Err(BdkError::VerifyTransaction(format!( - // "Failed to verify transaction: missing previous transaction {:?}", - // txid - // ))); - // } - // } - // } - // Ok(()) - // } + ///get the corresponding PSBT Input for a LocalUtxo pub fn get_psbt_input( &self, utxo: LocalUtxo, only_witness_utxo: bool, - sighash_type: Option, + sighash_type: Option ) -> anyhow::Result { - let input = self.get_wallet().get_psbt_input( - utxo.try_into()?, - sighash_type.map(|e| e.into()), - only_witness_utxo, - )?; - input.try_into() + handle_mutex(&self.ptr, |w| { + let input = w.get_psbt_input( + utxo.try_into()?, + sighash_type.map(|e| e.into()), + only_witness_utxo + )?; + input.try_into() + })? } ///Returns the descriptor used to create addresses for a particular keychain. #[frb(sync)] pub fn get_descriptor_for_keychain( ptr: BdkWallet, - keychain: KeychainKind, + keychain: KeychainKind ) -> anyhow::Result { - let wallet = ptr.get_wallet(); - let extended_descriptor = wallet.get_descriptor_for_keychain(keychain.into()); - BdkDescriptor::new(extended_descriptor.to_string(), wallet.network().into()) + handle_mutex(&ptr.ptr, |w| { + let extended_descriptor = w.get_descriptor_for_keychain(keychain.into()); + BdkDescriptor::new(extended_descriptor.to_string(), w.network().into()) + })? } } @@ -230,28 +205,28 @@ pub fn finish_bump_fee_tx_builder( allow_shrinking: Option, wallet: BdkWallet, enable_rbf: bool, - n_sequence: Option, + n_sequence: Option ) -> anyhow::Result<(BdkPsbt, TransactionDetails), BdkError> { - let txid = Txid::from_str(txid.as_str()).unwrap(); - let bdk_wallet = wallet.get_wallet(); - - let mut tx_builder = bdk_wallet.build_fee_bump(txid)?; - tx_builder.fee_rate(bdk::FeeRate::from_sat_per_vb(fee_rate)); - if let Some(allow_shrinking) = &allow_shrinking { - let address = allow_shrinking.ptr.clone(); - let script = address.script_pubkey(); - tx_builder.allow_shrinking(script).unwrap(); - } - if let Some(n_sequence) = n_sequence { - tx_builder.enable_rbf_with_sequence(Sequence(n_sequence)); - } - if enable_rbf { - tx_builder.enable_rbf(); - } - return match tx_builder.finish() { - Ok(e) => Ok((e.0.into(), TransactionDetails::try_from(e.1)?)), - Err(e) => Err(e.into()), - }; + let txid = Txid::from_str(txid.as_str()).map_err(|e| BdkError::PsbtParse(e.to_string()))?; + handle_mutex(&wallet.ptr, |w| { + let mut tx_builder = w.build_fee_bump(txid)?; + tx_builder.fee_rate(bdk::FeeRate::from_sat_per_vb(fee_rate)); + if let Some(allow_shrinking) = &allow_shrinking { + let address = allow_shrinking.ptr.clone(); + let script = address.script_pubkey(); + tx_builder.allow_shrinking(script)?; + } + if let Some(n_sequence) = n_sequence { + tx_builder.enable_rbf_with_sequence(Sequence(n_sequence)); + } + if enable_rbf { + tx_builder.enable_rbf(); + } + return match tx_builder.finish() { + Ok(e) => Ok((e.0.into(), TransactionDetails::try_from(e.1)?)), + Err(e) => Err(e.into()), + }; + })? } pub fn tx_builder_finish( @@ -267,70 +242,71 @@ pub fn tx_builder_finish( drain_wallet: bool, drain_to: Option, rbf: Option, - data: Vec, + data: Vec ) -> anyhow::Result<(BdkPsbt, TransactionDetails), BdkError> { - let binding = wallet.get_wallet(); - - let mut tx_builder = binding.build_tx(); + handle_mutex(&wallet.ptr, |w| { + let mut tx_builder = w.build_tx(); - for e in recipients { - tx_builder.add_recipient(e.script.into(), e.amount); - } - tx_builder.change_policy(change_policy.into()); + for e in recipients { + tx_builder.add_recipient(e.script.into(), e.amount); + } + tx_builder.change_policy(change_policy.into()); - if !utxos.is_empty() { - let bdk_utxos = utxos - .iter() - .map(|e| bdk::bitcoin::OutPoint::try_from(e)) - .collect::, BdkError>>()?; - tx_builder - .add_utxos(bdk_utxos.as_slice()) - .map_err(|e| >::into(e))?; - } - if !un_spendable.is_empty() { - let bdk_unspendable = un_spendable - .iter() - .map(|e| bdk::bitcoin::OutPoint::try_from(e)) - .collect::, BdkError>>()?; - tx_builder.unspendable(bdk_unspendable); - } - if manually_selected_only { - tx_builder.manually_selected_only(); - } - if let Some(sat_per_vb) = fee_rate { - tx_builder.fee_rate(bdk::FeeRate::from_sat_per_vb(sat_per_vb)); - } - if let Some(fee_amount) = fee_absolute { - tx_builder.fee_absolute(fee_amount); - } - if drain_wallet { - tx_builder.drain_wallet(); - } - if let Some(script_) = drain_to { - tx_builder.drain_to(script_.into()); - } - if let Some(utxo) = foreign_utxo { - let foreign_utxo: bdk::bitcoin::psbt::Input = utxo.1.try_into()?; - tx_builder.add_foreign_utxo((&utxo.0).try_into()?, foreign_utxo, utxo.2)?; - } - if let Some(rbf) = &rbf { - match rbf { - RbfValue::RbfDefault => { - tx_builder.enable_rbf(); - } - RbfValue::Value(nsequence) => { - tx_builder.enable_rbf_with_sequence(Sequence(nsequence.to_owned())); + if !utxos.is_empty() { + let bdk_utxos = utxos + .iter() + .map(|e| bdk::bitcoin::OutPoint::try_from(e)) + .collect::, BdkError>>()?; + tx_builder + .add_utxos(bdk_utxos.as_slice()) + .map_err(|e| >::into(e))?; + } + if !un_spendable.is_empty() { + let bdk_unspendable = un_spendable + .iter() + .map(|e| bdk::bitcoin::OutPoint::try_from(e)) + .collect::, BdkError>>()?; + tx_builder.unspendable(bdk_unspendable); + } + if manually_selected_only { + tx_builder.manually_selected_only(); + } + if let Some(sat_per_vb) = fee_rate { + tx_builder.fee_rate(bdk::FeeRate::from_sat_per_vb(sat_per_vb)); + } + if let Some(fee_amount) = fee_absolute { + tx_builder.fee_absolute(fee_amount); + } + if drain_wallet { + tx_builder.drain_wallet(); + } + if let Some(script_) = drain_to { + tx_builder.drain_to(script_.into()); + } + if let Some(utxo) = foreign_utxo { + let foreign_utxo: bdk::bitcoin::psbt::Input = utxo.1.try_into()?; + tx_builder.add_foreign_utxo((&utxo.0).try_into()?, foreign_utxo, utxo.2)?; + } + if let Some(rbf) = &rbf { + match rbf { + RbfValue::RbfDefault => { + tx_builder.enable_rbf(); + } + RbfValue::Value(nsequence) => { + tx_builder.enable_rbf_with_sequence(Sequence(nsequence.to_owned())); + } } } - } - if !data.is_empty() { - let push_bytes = PushBytesBuf::try_from(data.clone()) - .map_err(|_| BdkError::Generic("Failed to convert data to PushBytes".to_string()))?; - tx_builder.add_data(&push_bytes); - } + if !data.is_empty() { + let push_bytes = PushBytesBuf::try_from(data.clone()).map_err(|_| { + BdkError::Generic("Failed to convert data to PushBytes".to_string()) + })?; + tx_builder.add_data(&push_bytes); + } - return match tx_builder.finish() { - Ok(e) => Ok((e.0.into(), TransactionDetails::try_from(&e.1)?)), - Err(e) => Err(e.into()), - }; + return match tx_builder.finish() { + Ok(e) => Ok((e.0.into(), TransactionDetails::try_from(&e.1)?)), + Err(e) => Err(e.into()), + }; + })? } diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 032d2b13..0cabd3ad 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -869,9 +869,8 @@ fn wire__crate__api__psbt__bdk_psbt_as_string_impl( }, move || { let api_that = that.cst_decode(); - transform_result_dco::<_, _, ()>((move || { - let output_ok = - Result::<_, ()>::Ok(crate::api::psbt::BdkPsbt::as_string(&api_that))?; + transform_result_dco::<_, _, crate::api::error::BdkError>((move || { + let output_ok = crate::api::psbt::BdkPsbt::as_string(&api_that)?; Ok(output_ok) })()) }, @@ -929,9 +928,8 @@ fn wire__crate__api__psbt__bdk_psbt_fee_amount_impl( }, move || { let api_that = that.cst_decode(); - transform_result_dco::<_, _, ()>((move || { - let output_ok = - Result::<_, ()>::Ok(crate::api::psbt::BdkPsbt::fee_amount(&api_that))?; + transform_result_dco::<_, _, crate::api::error::BdkError>((move || { + let output_ok = crate::api::psbt::BdkPsbt::fee_amount(&api_that)?; Ok(output_ok) })()) }, @@ -948,9 +946,8 @@ fn wire__crate__api__psbt__bdk_psbt_fee_rate_impl( }, move || { let api_that = that.cst_decode(); - transform_result_dco::<_, _, ()>((move || { - let output_ok = - Result::<_, ()>::Ok(crate::api::psbt::BdkPsbt::fee_rate(&api_that))?; + transform_result_dco::<_, _, crate::api::error::BdkError>((move || { + let output_ok = crate::api::psbt::BdkPsbt::fee_rate(&api_that)?; Ok(output_ok) })()) }, @@ -988,9 +985,8 @@ fn wire__crate__api__psbt__bdk_psbt_json_serialize_impl( }, move || { let api_that = that.cst_decode(); - transform_result_dco::<_, _, ()>((move || { - let output_ok = - Result::<_, ()>::Ok(crate::api::psbt::BdkPsbt::json_serialize(&api_that))?; + transform_result_dco::<_, _, crate::api::error::BdkError>((move || { + let output_ok = crate::api::psbt::BdkPsbt::json_serialize(&api_that)?; Ok(output_ok) })()) }, @@ -1007,9 +1003,8 @@ fn wire__crate__api__psbt__bdk_psbt_serialize_impl( }, move || { let api_that = that.cst_decode(); - transform_result_dco::<_, _, ()>((move || { - let output_ok = - Result::<_, ()>::Ok(crate::api::psbt::BdkPsbt::serialize(&api_that))?; + transform_result_dco::<_, _, crate::api::error::BdkError>((move || { + let output_ok = crate::api::psbt::BdkPsbt::serialize(&api_that)?; Ok(output_ok) })()) }, @@ -1026,8 +1021,8 @@ fn wire__crate__api__psbt__bdk_psbt_txid_impl( }, move || { let api_that = that.cst_decode(); - transform_result_dco::<_, _, ()>((move || { - let output_ok = Result::<_, ()>::Ok(crate::api::psbt::BdkPsbt::txid(&api_that))?; + transform_result_dco::<_, _, crate::api::error::BdkError>((move || { + let output_ok = crate::api::psbt::BdkPsbt::txid(&api_that)?; Ok(output_ok) })()) }, @@ -1772,9 +1767,8 @@ fn wire__crate__api__wallet__bdk_wallet_network_impl( }, move || { let api_that = that.cst_decode(); - transform_result_dco::<_, _, ()>((move || { - let output_ok = - Result::<_, ()>::Ok(crate::api::wallet::BdkWallet::network(&api_that))?; + transform_result_dco::<_, _, crate::api::error::BdkError>((move || { + let output_ok = crate::api::wallet::BdkWallet::network(&api_that)?; Ok(output_ok) })()) },