Skip to content

Commit 13e9ce5

Browse files
committed
Change get_balance to return in categories.
Add type balance with add, display traits. Change affected tests.
1 parent 93ca3b9 commit 13e9ce5

File tree

3 files changed

+105
-15
lines changed

3 files changed

+105
-15
lines changed

src/blockchain/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ This example shows how to sync multiple walles and return the sum of their balan
187187
# use bdk::database::*;
188188
# use bdk::wallet::*;
189189
# use bdk::*;
190-
fn sum_of_balances<B: BlockchainFactory>(blockchain_factory: B, wallets: &[Wallet<MemoryDatabase>]) -> Result<u64, Error> {
190+
fn sum_of_balances<B: BlockchainFactory>(blockchain_factory: B, wallets: &[Wallet<MemoryDatabase>]) -> Result<Balance, Error> {
191191
Ok(wallets
192192
.iter()
193193
.map(|w| -> Result<_, Error> {

src/types.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,53 @@ impl BlockTime {
240240
}
241241
}
242242

243+
/// Balance differentiated in various categories
244+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
245+
pub struct Balance {
246+
/// All coinbase not matured yet
247+
pub immature: u64,
248+
/// In wallet change utxo, but not confirmed yet.
249+
pub trusted_pending: u64,
250+
/// Other wallet received utxo not confirmed yet.
251+
pub untrusted_pending: u64,
252+
/// All rest available balance
253+
pub available: u64,
254+
}
255+
256+
impl std::fmt::Display for Balance {
257+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258+
write!(
259+
f,
260+
"{{ immature: {}, trusted_pending: {}, untrusted_pending: {}, available: {} }}",
261+
self.immature, self.trusted_pending, self.untrusted_pending, self.available
262+
)
263+
}
264+
}
265+
266+
impl std::ops::Add for Balance {
267+
type Output = Self;
268+
269+
fn add(self, other: Self) -> Self {
270+
Self {
271+
immature: self.immature + other.immature,
272+
trusted_pending: self.trusted_pending + other.trusted_pending,
273+
untrusted_pending: self.untrusted_pending + other.untrusted_pending,
274+
available: self.available + other.available,
275+
}
276+
}
277+
}
278+
279+
impl std::iter::Sum for Balance {
280+
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
281+
iter.fold(
282+
Balance {
283+
..Default::default()
284+
},
285+
|a, b| a + b,
286+
)
287+
}
288+
}
289+
243290
#[cfg(test)]
244291
mod tests {
245292
use super::*;

src/wallet/mod.rs

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -446,15 +446,51 @@ where
446446
self.database.borrow().iter_txs(include_raw)
447447
}
448448

449-
/// Return the balance, meaning the sum of this wallet's unspent outputs' values
449+
/// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
450+
/// values.
450451
///
451452
/// Note that this methods only operate on the internal database, which first needs to be
452453
/// [`Wallet::sync`] manually.
453-
pub fn get_balance(&self) -> Result<u64, Error> {
454-
Ok(self
455-
.list_unspent()?
456-
.iter()
457-
.fold(0, |sum, i| sum + i.txout.value))
454+
pub fn get_balance(&self) -> Result<Balance, Error> {
455+
let mut immature = 0;
456+
let mut trusted_pending = 0;
457+
let mut untrusted_pending = 0;
458+
let mut available = 0;
459+
let utxos = self.list_unspent()?;
460+
let database = self.database.borrow();
461+
for u in utxos {
462+
// Unwrap used since utxo set is created from database
463+
let tx = database.get_tx(&u.outpoint.txid, true)?.unwrap();
464+
if let Some(tx_conf_time) = &tx.confirmation_time {
465+
if tx.transaction.expect("No transaction").is_coin_base() {
466+
let last_sync_height = self
467+
.database()
468+
.get_sync_time()?
469+
.map(|sync_time| sync_time.block_time.height);
470+
471+
if let Some(last_sync_height) = last_sync_height {
472+
if (last_sync_height - tx_conf_time.height) >= COINBASE_MATURITY {
473+
available += u.txout.value;
474+
} else {
475+
immature += u.txout.value;
476+
}
477+
}
478+
} else {
479+
available += u.txout.value;
480+
}
481+
} else if u.keychain == KeychainKind::Internal {
482+
trusted_pending += u.txout.value;
483+
} else {
484+
untrusted_pending += u.txout.value;
485+
}
486+
}
487+
488+
Ok(Balance {
489+
immature,
490+
trusted_pending,
491+
untrusted_pending,
492+
available,
493+
})
458494
}
459495

460496
/// Add an external signer
@@ -4686,23 +4722,30 @@ pub(crate) mod test {
46864722
Some(confirmation_time),
46874723
(@coinbase true)
46884724
);
4725+
let sync_time = SyncTime {
4726+
block_time: BlockTime {
4727+
height: confirmation_time,
4728+
timestamp: 0,
4729+
},
4730+
};
4731+
wallet
4732+
.database
4733+
.borrow_mut()
4734+
.set_sync_time(sync_time.clone())
4735+
.unwrap();
46894736

46904737
let not_yet_mature_time = confirmation_time + COINBASE_MATURITY - 1;
46914738
let maturity_time = confirmation_time + COINBASE_MATURITY;
46924739

4693-
// The balance is nonzero, even if we can't spend anything
4694-
// FIXME: we should differentiate the balance between immature,
4695-
// trusted, untrusted_pending
4696-
// See https://github.com/bitcoindevkit/bdk/issues/238
46974740
let balance = wallet.get_balance().unwrap();
4698-
assert!(balance != 0);
4741+
assert!(balance.immature != 0);
46994742

47004743
// We try to create a transaction, only to notice that all
47014744
// our funds are unspendable
47024745
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
47034746
let mut builder = wallet.build_tx();
47044747
builder
4705-
.add_recipient(addr.script_pubkey(), balance / 2)
4748+
.add_recipient(addr.script_pubkey(), balance.immature / 2)
47064749
.set_current_height(confirmation_time);
47074750
assert!(matches!(
47084751
builder.finish().unwrap_err(),
@@ -4715,7 +4758,7 @@ pub(crate) mod test {
47154758
// Still unspendable...
47164759
let mut builder = wallet.build_tx();
47174760
builder
4718-
.add_recipient(addr.script_pubkey(), balance / 2)
4761+
.add_recipient(addr.script_pubkey(), balance.immature / 2)
47194762
.set_current_height(not_yet_mature_time);
47204763
assert!(matches!(
47214764
builder.finish().unwrap_err(),
@@ -4728,7 +4771,7 @@ pub(crate) mod test {
47284771
// ...Now the coinbase is mature :)
47294772
let mut builder = wallet.build_tx();
47304773
builder
4731-
.add_recipient(addr.script_pubkey(), balance / 2)
4774+
.add_recipient(addr.script_pubkey(), balance.immature / 2)
47324775
.set_current_height(maturity_time);
47334776
builder.finish().unwrap();
47344777
}

0 commit comments

Comments
 (0)