diff --git a/Cargo.toml b/Cargo.toml index 354a1fb08..9ee866328 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,8 @@ readme = "README.md" license = "MIT OR Apache-2.0" [dependencies] -bdk-macros = "^0.6" +# bdk-macros = "^0.6" +bdk-macros = { path = "macros", version = "^0.6" } log = "^0.4" miniscript = { version = "8.0", features = ["serde"] } bitcoin = { version = "0.29.1", features = ["serde", "base64", "rand"] } @@ -111,6 +112,7 @@ electrsd = "0.21" # Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released base64 = "^0.13" assert_matches = "1.5.0" +tokio = "1" [[example]] name = "compact_filters_balance" diff --git a/examples/utils/mod.rs b/examples/utils/mod.rs index 25249fa7e..583106957 100644 --- a/examples/utils/mod.rs +++ b/examples/utils/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod tx { use bdk::{database::BatchDatabase, SignOptions, Wallet}; use bitcoin::{Address, Transaction}; - pub fn build_signed_tx( + pub fn build_signed_tx( wallet: &Wallet, recipient_address: &str, amount: u64, diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 74eda5cf4..034008d69 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -33,7 +33,7 @@ fn add_async_trait(mut parsed: ItemTrait) -> TokenStream { #output #[cfg(any(target_arch = "wasm32", feature = "async-interface"))] - #[async_trait(?Send)] + #[async_trait] #parsed }; @@ -74,7 +74,7 @@ fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream { #output #[cfg(any(target_arch = "wasm32", feature = "async-interface"))] - #[async_trait(?Send)] + #[async_trait] #parsed }; diff --git a/src/blockchain/any.rs b/src/blockchain/any.rs index 1d1a407dd..9559e27f1 100644 --- a/src/blockchain/any.rs +++ b/src/blockchain/any.rs @@ -129,7 +129,7 @@ impl GetBlockHash for AnyBlockchain { #[maybe_async] impl WalletSync for AnyBlockchain { - fn wallet_sync( + fn wallet_sync( &self, database: &mut D, progress_update: Box, @@ -142,7 +142,7 @@ impl WalletSync for AnyBlockchain { )) } - fn wallet_setup( + fn wallet_setup( &self, database: &mut D, progress_update: Box, diff --git a/src/blockchain/esplora/async.rs b/src/blockchain/esplora/async.rs index 900d95376..f5182a41d 100644 --- a/src/blockchain/esplora/async.rs +++ b/src/blockchain/esplora/async.rs @@ -133,7 +133,7 @@ impl GetBlockHash for EsploraBlockchain { #[maybe_async] impl WalletSync for EsploraBlockchain { - fn wallet_setup( + fn wallet_setup( &self, database: &mut D, _progress_update: Box, @@ -248,3 +248,40 @@ impl ConfigurableBlockchain for EsploraBlockchain { Ok(blockchain) } } + +#[cfg(test)] +mod test { + use std::sync::Arc; + + use bitcoin::Network; + + use crate::database::{AnyDatabase, MemoryDatabase}; + use crate::wallet::test::get_test_wpkh; + use crate::SyncOptions; + use super::*; + + /// A quick test that doesn't actually do anything, but will throw a compile + /// error if bdk::Wallet or EsploraBlockchain are not thread-safe. + #[tokio::test] + async fn esplora_async_wallet_is_thread_safe() { + tokio::task::spawn(async move { + let descriptors = testutils!(@descriptors (get_test_wpkh())); + let wallet = Wallet::new( + &descriptors.0, + None, + Network::Regtest, + AnyDatabase::Memory(MemoryDatabase::new()), + ) + .unwrap(); + let wrapped_wallet = Arc::new(tokio::sync::Mutex::new(wallet)); + + let esplora = EsploraBlockchain::new("localhost:8000", 20); + let sync_options = SyncOptions { progress: None }; + let _ = wrapped_wallet + .lock() + .await + .sync(&esplora, sync_options) + .await; + }); + } +} diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 2502f61b0..8a0ec51dc 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -131,7 +131,7 @@ pub trait WalletSync { /// For types that do not have that distinction, only this method can be implemented, since /// [`WalletSync::wallet_sync`] defaults to calling this internally if not overridden. /// Populate the internal database with transactions and UTXOs - fn wallet_setup( + fn wallet_setup( &self, database: &mut D, progress_update: Box, @@ -154,7 +154,7 @@ pub trait WalletSync { /// [`BatchOperations::set_tx`]: crate::database::BatchOperations::set_tx /// [`BatchOperations::set_utxo`]: crate::database::BatchOperations::set_utxo /// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo - fn wallet_sync( + fn wallet_sync( &self, database: &mut D, progress_update: Box, @@ -210,7 +210,7 @@ fn sum_of_balances(blockchain_factory: B, wallets: &[Walle )] pub trait BlockchainFactory { /// The type returned when building a blockchain from this factory - type Inner: Blockchain; + type Inner: Blockchain + Send + Sync; /// Build a new blockchain for the given descriptor wallet_name /// @@ -227,7 +227,7 @@ pub trait BlockchainFactory { /// /// Internally uses [`wallet_name_from_descriptor`] to derive the name, and then calls /// [`BlockchainFactory::build`] to create the blockchain instance. - fn build_for_wallet( + fn build_for_wallet( &self, wallet: &Wallet, override_skip_blocks: Option, @@ -253,7 +253,7 @@ pub trait BlockchainFactory { docsrs, doc(cfg(not(any(target_arch = "wasm32", feature = "async-interface")))) )] - fn sync_wallet( + fn sync_wallet( &self, wallet: &Wallet, override_skip_blocks: Option, @@ -264,7 +264,7 @@ pub trait BlockchainFactory { } } -impl BlockchainFactory for Arc { +impl BlockchainFactory for Arc { type Inner = Self; fn build(&self, _wallet_name: &str, _override_skip_blocks: Option) -> Result { @@ -338,7 +338,7 @@ impl Progress for LogProgress { } #[maybe_async] -impl Blockchain for Arc { +impl Blockchain for Arc { fn get_capabilities(&self) -> HashSet { maybe_await!(self.deref().get_capabilities()) } @@ -353,29 +353,29 @@ impl Blockchain for Arc { } #[maybe_async] -impl GetTx for Arc { +impl GetTx for Arc { fn get_tx(&self, txid: &Txid) -> Result, Error> { maybe_await!(self.deref().get_tx(txid)) } } #[maybe_async] -impl GetHeight for Arc { +impl GetHeight for Arc { fn get_height(&self) -> Result { maybe_await!(self.deref().get_height()) } } #[maybe_async] -impl GetBlockHash for Arc { +impl GetBlockHash for Arc { fn get_block_hash(&self, height: u64) -> Result { maybe_await!(self.deref().get_block_hash(height)) } } #[maybe_async] -impl WalletSync for Arc { - fn wallet_setup( +impl WalletSync for Arc { + fn wallet_setup( &self, database: &mut D, progress_update: Box, @@ -383,7 +383,7 @@ impl WalletSync for Arc { maybe_await!(self.deref().wallet_setup(database, progress_update)) } - fn wallet_sync( + fn wallet_sync( &self, database: &mut D, progress_update: Box, diff --git a/src/database/mod.rs b/src/database/mod.rs index 7f26a1320..9e6477b02 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -165,7 +165,7 @@ pub trait Database: BatchOperations { /// This trait defines the methods to start and apply a batch of operations. pub trait BatchDatabase: Database { /// Container for the operations - type Batch: BatchOperations; + type Batch: BatchOperations + Send; /// Create a new batch container fn begin_batch(&self) -> Self::Batch; diff --git a/src/wallet/export.rs b/src/wallet/export.rs index d808f0ba1..90a67db1f 100644 --- a/src/wallet/export.rs +++ b/src/wallet/export.rs @@ -116,7 +116,7 @@ impl FullyNodedExport { /// /// If the database is empty or `include_blockheight` is false, the `blockheight` field /// returned will be `0`. - pub fn export_wallet( + pub fn export_wallet( wallet: &Wallet, label: &str, include_blockheight: bool, diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index f78e09f12..090e1b776 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -169,7 +169,7 @@ pub struct SyncOptions { impl Wallet where - D: BatchDatabase, + D: BatchDatabase + Send + Sync, { #[deprecated = "Just use Wallet::new -- all wallets are offline now!"] /// Create a new "offline" wallet @@ -1687,7 +1687,7 @@ where /// Sync the internal database with the blockchain #[maybe_async] - pub fn sync( + pub fn sync( &self, blockchain: &B, sync_opts: SyncOptions, diff --git a/src/wallet/tx_builder.rs b/src/wallet/tx_builder.rs index 6d52b8d90..8ca179b52 100644 --- a/src/wallet/tx_builder.rs +++ b/src/wallet/tx_builder.rs @@ -179,7 +179,7 @@ impl<'a, Cs: Clone, Ctx, D> Clone for TxBuilder<'a, D, Cs, Ctx> { } // methods supported by both contexts, for any CoinSelectionAlgorithm -impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> +impl<'a, D: BatchDatabase + Send + Sync, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D, Cs, Ctx> { /// Set a custom fee rate