Skip to content

Commit 0b82c2d

Browse files
committed
Introduce balance_cache to avoid blocking on retrieving balances
Unfortunately BDK's current wallet design requires us to have it live in `Mutex` that is locked for long periods of time during syncing. This is especially painful for short-lived operations that just operate locally, such as retrieving the current balance, which we now do in several places to be able to check Anchor channels limitations, e.g., in event handling. In order to avoid blocking during balance retrieval, we introduce a `balance` cache that will be refreshed whenever we're done with syncing *or* when we can successfully get the wallet lock. Otherwise, we'll just return the cached value, allowing us to make progress even though a background sync of the wallet might be in-progress.
1 parent 1ae9733 commit 0b82c2d

File tree

1 file changed

+44
-12
lines changed

1 file changed

+44
-12
lines changed

src/wallet.rs

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use lightning::util::message_signing;
1717
use bdk::blockchain::EsploraBlockchain;
1818
use bdk::database::BatchDatabase;
1919
use bdk::wallet::AddressIndex;
20-
use bdk::FeeRate;
20+
use bdk::{Balance, FeeRate};
2121
use bdk::{SignOptions, SyncOptions};
2222

2323
use bitcoin::address::{Payload, WitnessVersion};
@@ -34,7 +34,7 @@ use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing};
3434
use bitcoin::{ScriptBuf, Transaction, TxOut, Txid};
3535

3636
use std::ops::Deref;
37-
use std::sync::{Arc, Condvar, Mutex};
37+
use std::sync::{Arc, Condvar, Mutex, RwLock};
3838
use std::time::Duration;
3939

4040
pub struct Wallet<D, B: Deref, E: Deref, L: Deref>
@@ -52,6 +52,8 @@ where
5252
broadcaster: B,
5353
fee_estimator: E,
5454
sync_lock: (Mutex<()>, Condvar),
55+
// TODO: Drop this workaround after BDK 1.0 upgrade.
56+
balance_cache: RwLock<Balance>,
5557
logger: L,
5658
}
5759

@@ -66,9 +68,17 @@ where
6668
blockchain: EsploraBlockchain, wallet: bdk::Wallet<D>, broadcaster: B, fee_estimator: E,
6769
logger: L,
6870
) -> Self {
71+
let start_balance = wallet.get_balance().unwrap_or(Balance {
72+
immature: 0,
73+
trusted_pending: 0,
74+
untrusted_pending: 0,
75+
confirmed: 0,
76+
});
77+
6978
let inner = Mutex::new(wallet);
7079
let sync_lock = (Mutex::new(()), Condvar::new());
71-
Self { blockchain, inner, broadcaster, fee_estimator, sync_lock, logger }
80+
let balance_cache = RwLock::new(start_balance);
81+
Self { blockchain, inner, broadcaster, fee_estimator, sync_lock, balance_cache, logger }
7282
}
7383

7484
pub(crate) async fn sync(&self) -> Result<(), Error> {
@@ -88,18 +98,29 @@ where
8898
let sync_options = SyncOptions { progress: None };
8999
let wallet_lock = self.inner.lock().unwrap();
90100
let res = match wallet_lock.sync(&self.blockchain, sync_options).await {
91-
Ok(()) => Ok(()),
101+
Ok(()) => {
102+
// TODO: Drop this workaround after BDK 1.0 upgrade.
103+
// Update balance cache after syncing.
104+
if let Ok(balance) = wallet_lock.get_balance() {
105+
*self.balance_cache.write().unwrap() = balance;
106+
}
107+
Ok(())
108+
},
92109
Err(e) => match e {
93110
bdk::Error::Esplora(ref be) => match **be {
94111
bdk::blockchain::esplora::EsploraError::Reqwest(_) => {
112+
// Drop lock, sleep for a second, retry.
113+
drop(wallet_lock);
95114
tokio::time::sleep(Duration::from_secs(1)).await;
96115
log_error!(
97116
self.logger,
98117
"Sync failed due to HTTP connection error, retrying: {}",
99118
e
100119
);
101120
let sync_options = SyncOptions { progress: None };
102-
wallet_lock
121+
self.inner
122+
.lock()
123+
.unwrap()
103124
.sync(&self.blockchain, sync_options)
104125
.await
105126
.map_err(|e| From::from(e))
@@ -172,13 +193,24 @@ where
172193
pub(crate) fn get_balances(
173194
&self, total_anchor_channels_reserve_sats: u64,
174195
) -> Result<(u64, u64), Error> {
175-
let wallet_lock = self.inner.lock().unwrap();
176-
let (total, spendable) = wallet_lock.get_balance().map(|bal| {
177-
(
178-
bal.get_total(),
179-
bal.get_spendable().saturating_sub(total_anchor_channels_reserve_sats),
180-
)
181-
})?;
196+
// TODO: Drop this workaround after BDK 1.0 upgrade.
197+
// We get the balance and update our cache if we can do so without blocking on the wallet
198+
// Mutex. Otherwise, we return a cached value.
199+
let balance = match self.inner.try_lock() {
200+
Ok(wallet_lock) => {
201+
// Update balance cache if we can.
202+
let balance = wallet_lock.get_balance()?;
203+
*self.balance_cache.write().unwrap() = balance.clone();
204+
balance
205+
},
206+
Err(_) => self.balance_cache.read().unwrap().clone(),
207+
};
208+
209+
let (total, spendable) = (
210+
balance.get_total(),
211+
balance.get_spendable().saturating_sub(total_anchor_channels_reserve_sats),
212+
);
213+
182214
Ok((total, spendable))
183215
}
184216

0 commit comments

Comments
 (0)