]> Untitled Git - bdk/commitdiff
Change get_balance to return in categories.
authorwszdexdrf <piyushkumar2k02@kgpian.iitkgp.ac.in>
Wed, 22 Jun 2022 09:07:29 +0000 (14:37 +0530)
committerDaniela Brozzoni <danielabrozzoni@protonmail.com>
Thu, 4 Aug 2022 08:37:09 +0000 (10:37 +0200)
Add type balance with add, display traits. Change affected tests.
Update `CHANGELOG.md`

CHANGELOG.md
src/blockchain/electrum.rs
src/blockchain/mod.rs
src/testutils/blockchain_tests.rs
src/testutils/configurable_blockchain_tests.rs
src/types.rs
src/wallet/mod.rs

index 3fda3e0601ef53d49d5acfd7fd432a40b6a550be..2fd9022cd7c3fc8a8c15d471c8223f35dd1978ac 100644 (file)
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Consolidate params `fee_amount` and `amount_needed` in `target_amount` in `CoinSelectionAlgorithm::coin_select` signature.
 - Change the meaning of the `fee_amount` field inside `CoinSelectionResult`: from now on the `fee_amount` will represent only the fees asociated with the utxos in the `selected` field of `CoinSelectionResult`.
 - New `RpcBlockchain` implementation with various fixes.
+- Return balance in separate categories, namely `confirmed`, `trusted_pending`, `untrusted_pending` & `immature`.
 
 ## [v0.20.0] - [v0.19.0]
 
index faf7ea7565906b0cecca1cb3859fdadda73a23a1..c1e1c66cfa4072d3a2d7d5f9aba803117fd3d615 100644 (file)
@@ -385,7 +385,7 @@ mod test {
             .sync_wallet(&wallet, None, Default::default())
             .unwrap();
 
-        assert_eq!(wallet.get_balance().unwrap(), 50_000);
+        assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000);
     }
 
     #[test]
index 1dc5c95a1c508f3761fb4a41efe322b2a56d0571..2502f61b0cf3224e64d1c03f778168286c3b7e72 100644 (file)
@@ -194,7 +194,7 @@ This example shows how to sync multiple walles and return the sum of their balan
 # use bdk::database::*;
 # use bdk::wallet::*;
 # use bdk::*;
-fn sum_of_balances<B: BlockchainFactory>(blockchain_factory: B, wallets: &[Wallet<MemoryDatabase>]) -> Result<u64, Error> {
+fn sum_of_balances<B: BlockchainFactory>(blockchain_factory: B, wallets: &[Wallet<MemoryDatabase>]) -> Result<Balance, Error> {
     Ok(wallets
         .iter()
         .map(|w| -> Result<_, Error> {
index 975c377dc0fcc272978de5e548d6c268fbfd61f3..a3d7c2b171b3dd0b29bf13d9508263f62e3b2c72 100644 (file)
@@ -454,7 +454,7 @@ macro_rules! bdk_blockchain_tests {
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
                 assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated");
 
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
                 assert_eq!(wallet.list_unspent().unwrap()[0].keychain, KeychainKind::External, "incorrect keychain kind");
 
                 let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
@@ -477,7 +477,7 @@ macro_rules! bdk_blockchain_tests {
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
 
-                assert_eq!(wallet.get_balance().unwrap(), 100_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 100_000, "incorrect balance");
                 assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
             }
 
@@ -486,7 +486,7 @@ macro_rules! bdk_blockchain_tests {
                 let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 0);
+                assert_eq!(wallet.get_balance().unwrap().get_total(), 0);
 
                 test_client.receive(testutils! {
                     @tx ( (@external descriptors, 0) => 50_000 )
@@ -494,8 +494,16 @@ macro_rules! bdk_blockchain_tests {
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
 
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
-                assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
+
+                test_client.receive(testutils! {
+                    @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
+                });
+
+                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
+
+                assert_eq!(wallet.get_balance().unwrap().confirmed, 100_000, "incorrect balance");
+                assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
             }
 
             #[test]
@@ -508,7 +516,7 @@ macro_rules! bdk_blockchain_tests {
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
 
-                assert_eq!(wallet.get_balance().unwrap(), 105_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 105_000, "incorrect balance");
                 assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
                 assert_eq!(wallet.list_unspent().unwrap().len(), 3, "incorrect number of unspents");
 
@@ -532,7 +540,7 @@ macro_rules! bdk_blockchain_tests {
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
 
-                assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 75_000, "incorrect balance");
                 assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
                 assert_eq!(wallet.list_unspent().unwrap().len(), 2, "incorrect number of unspent");
             }
@@ -546,14 +554,14 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000);
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000);
 
                 test_client.receive(testutils! {
                     @tx ( (@external descriptors, 0) => 25_000 )
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 75_000, "incorrect balance");
             }
 
             #[test]
@@ -566,7 +574,7 @@ macro_rules! bdk_blockchain_tests {
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
 
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
                 assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
                 assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect unspent");
 
@@ -580,7 +588,7 @@ macro_rules! bdk_blockchain_tests {
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
 
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after bump");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance after bump");
                 assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs after bump");
                 assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect unspent after bump");
 
@@ -603,8 +611,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000, "incorrect balance");
                 assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
                 assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect number of unspents");
 
@@ -617,7 +624,7 @@ macro_rules! bdk_blockchain_tests {
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
 
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance after invalidate");
 
                 let list_tx_item = &wallet.list_transactions(false).unwrap()[0];
                 assert_eq!(list_tx_item.txid, txid, "incorrect txid after invalidate");
@@ -635,7 +642,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
 
                 let mut builder = wallet.build_tx();
                 builder.add_recipient(node_addr.script_pubkey(), 25_000);
@@ -646,7 +653,12 @@ macro_rules! bdk_blockchain_tests {
                 println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
                 blockchain.broadcast(&tx).unwrap();
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send");
+                assert_eq!(wallet.get_balance().unwrap().trusted_pending, details.received, "incorrect balance after send");
+
+                test_client.generate(1, Some(node_addr));
+                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
+
+                assert_eq!(wallet.get_balance().unwrap().confirmed, details.received, "incorrect balance after send");
 
                 assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
                 assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect number of unspents");
@@ -720,7 +732,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).expect("sync");
-                assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 75_000, "incorrect balance");
                 let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
 
                 let tx1 = {
@@ -744,7 +756,7 @@ macro_rules! bdk_blockchain_tests {
                 blockchain.broadcast(&tx1).expect("broadcasting first");
                 blockchain.broadcast(&tx2).expect("broadcasting replacement");
                 receiver_wallet.sync(&blockchain, SyncOptions::default()).expect("syncing receiver");
-                assert_eq!(receiver_wallet.get_balance().expect("balance"), 49_000, "should have received coins once and only once");
+                assert_eq!(receiver_wallet.get_balance().expect("balance").untrusted_pending, 49_000, "should have received coins once and only once");
             }
 
             #[test]
@@ -770,7 +782,8 @@ macro_rules! bdk_blockchain_tests {
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
 
-                assert_eq!(wallet.get_balance().unwrap(), 100_000);
+                let balance = wallet.get_balance().unwrap();
+                assert_eq!(balance.untrusted_pending + balance.get_spendable(), 100_000);
             }
 
             #[test]
@@ -784,7 +797,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
 
                 let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
                 let details = tx_map.get(&received_txid).unwrap();
@@ -808,7 +821,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
 
                 let mut builder = wallet.build_tx();
                 builder.add_recipient(node_addr.script_pubkey(), 25_000);
@@ -820,7 +833,7 @@ macro_rules! bdk_blockchain_tests {
                 blockchain.broadcast(&sent_tx).unwrap();
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "incorrect balance after receive");
 
                 // empty wallet
                 let wallet = get_wallet_from_descriptors(&descriptors);
@@ -851,7 +864,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
 
                 let mut total_sent = 0;
                 for _ in 0..5 {
@@ -868,7 +881,7 @@ macro_rules! bdk_blockchain_tests {
                 }
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance after chain");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - total_sent, "incorrect balance after chain");
 
                 // empty wallet
 
@@ -878,7 +891,7 @@ macro_rules! bdk_blockchain_tests {
                 test_client.generate(1, Some(node_addr));
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - total_sent, "incorrect balance empty wallet");
 
             }
 
@@ -892,7 +905,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000, "incorrect balance");
 
                 let mut builder = wallet.build_tx();
                 builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf();
@@ -901,8 +914,8 @@ macro_rules! bdk_blockchain_tests {
                 assert!(finalized, "Cannot finalize transaction");
                 blockchain.broadcast(&psbt.extract_tx()).unwrap();
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees");
-                assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance from received");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "incorrect balance from received");
 
                 let mut builder = wallet.build_fee_bump(details.txid).unwrap();
                 builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
@@ -911,8 +924,8 @@ macro_rules! bdk_blockchain_tests {
                 assert!(finalized, "Cannot finalize transaction");
                 blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
-                assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance from received after bump");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), new_details.received, "incorrect balance from received after bump");
 
                 assert!(new_details.fee.unwrap_or(0) > details.fee.unwrap_or(0), "incorrect fees");
             }
@@ -927,7 +940,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000, "incorrect balance");
 
                 let mut builder = wallet.build_tx();
                 builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
@@ -936,8 +949,8 @@ macro_rules! bdk_blockchain_tests {
                 assert!(finalized, "Cannot finalize transaction");
                 blockchain.broadcast(&psbt.extract_tx()).unwrap();
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
-                assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect received after send");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "incorrect received after send");
 
                 let mut builder = wallet.build_fee_bump(details.txid).unwrap();
                 builder.fee_rate(FeeRate::from_sat_per_vb(5.1));
@@ -946,7 +959,7 @@ macro_rules! bdk_blockchain_tests {
                 assert!(finalized, "Cannot finalize transaction");
                 blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 0, "incorrect balance after change removal");
                 assert_eq!(new_details.received, 0, "incorrect received after change removal");
 
                 assert!(new_details.fee.unwrap_or(0) > details.fee.unwrap_or(0), "incorrect fees");
@@ -962,7 +975,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 75_000, "incorrect balance");
 
                 let mut builder = wallet.build_tx();
                 builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
@@ -971,7 +984,7 @@ macro_rules! bdk_blockchain_tests {
                 assert!(finalized, "Cannot finalize transaction");
                 blockchain.broadcast(&psbt.extract_tx()).unwrap();
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
                 assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
 
                 let mut builder = wallet.build_fee_bump(details.txid).unwrap();
@@ -982,7 +995,7 @@ macro_rules! bdk_blockchain_tests {
                 blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
                 assert_eq!(new_details.sent, 75_000, "incorrect sent");
-                assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), new_details.received, "incorrect balance after add input");
             }
 
             #[test]
@@ -995,7 +1008,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 75_000, "incorrect balance");
 
                 let mut builder = wallet.build_tx();
                 builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
@@ -1004,7 +1017,7 @@ macro_rules! bdk_blockchain_tests {
                 assert!(finalized, "Cannot finalize transaction");
                 blockchain.broadcast(&psbt.extract_tx()).unwrap();
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
                 assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
 
                 let mut builder = wallet.build_fee_bump(details.txid).unwrap();
@@ -1017,7 +1030,7 @@ macro_rules! bdk_blockchain_tests {
                 blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
                 assert_eq!(new_details.sent, 75_000, "incorrect sent");
-                assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 0, "incorrect balance after add input");
                 assert_eq!(new_details.received, 0, "incorrect received after add input");
             }
 
@@ -1031,7 +1044,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
 
                 let mut builder = wallet.build_tx();
                 let data = [42u8;80];
@@ -1046,7 +1059,7 @@ macro_rules! bdk_blockchain_tests {
                 blockchain.broadcast(&tx).unwrap();
                 test_client.generate(1, Some(node_addr));
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send");
 
                 let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
                 let _ = tx_map.get(&tx.txid()).unwrap();
@@ -1060,12 +1073,21 @@ macro_rules! bdk_blockchain_tests {
                 println!("wallet addr: {}", wallet_addr);
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().immature, 0, "incorrect balance");
 
                 test_client.generate(1, Some(wallet_addr));
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase");
+
+                assert!(wallet.get_balance().unwrap().immature > 0, "incorrect balance after receiving coinbase");
+
+                // make coinbase mature (100 blocks)
+                let node_addr = test_client.get_node_address(None);
+                test_client.generate(100, Some(node_addr));
+                wallet.sync(&blockchain, SyncOptions::default()).unwrap();
+
+                assert!(wallet.get_balance().unwrap().confirmed > 0, "incorrect balance after maturing coinbase");
+
             }
 
             #[test]
@@ -1142,7 +1164,7 @@ macro_rules! bdk_blockchain_tests {
                 });
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000, "wallet has incorrect balance");
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "wallet has incorrect balance");
 
                 // 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet
 
@@ -1154,7 +1176,7 @@ macro_rules! bdk_blockchain_tests {
                 let tx = psbt.extract_tx();
                 blockchain.broadcast(&tx).unwrap();
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), details.received, "wallet has incorrect balance after send");
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "wallet has incorrect balance after send");
                 assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "wallet has incorrect number of txs");
                 assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents");
                 test_client.generate(1, None);
@@ -1265,7 +1287,7 @@ macro_rules! bdk_blockchain_tests {
 
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
 
-                assert_eq!(wallet.get_balance().unwrap(), 50_000);
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000);
 
                 let tx = {
                     let mut builder = wallet.build_tx();
@@ -1288,7 +1310,7 @@ macro_rules! bdk_blockchain_tests {
                     @tx ( (@external descriptors, 0) => 50_000 )
                 });
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000);
+                assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000);
 
                 let tx = {
                     let mut builder = wallet.build_tx();
@@ -1309,7 +1331,7 @@ macro_rules! bdk_blockchain_tests {
                     @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 6 )
                 });
                 wallet.sync(&blockchain, SyncOptions::default()).unwrap();
-                assert_eq!(wallet.get_balance().unwrap(), 50_000);
+                assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000);
 
                 let ext_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
                 let int_policy = wallet.policies(KeychainKind::Internal).unwrap().unwrap();
index b07fc9cdaed23bc1a7cfbea15a40168e35023ad5..8662844dd6a744f5a8163d95c496664307361aaf 100644 (file)
@@ -124,7 +124,7 @@ where
         // perform wallet sync
         wallet.sync(&blockchain, Default::default()).unwrap();
 
-        let wallet_balance = wallet.get_balance().unwrap();
+        let wallet_balance = wallet.get_balance().unwrap().get_total();
         println!(
             "max: {}, min: {}, actual: {}",
             max_balance, min_balance, wallet_balance
@@ -193,7 +193,7 @@ where
     wallet.sync(&blockchain, Default::default()).unwrap();
     println!("sync done!");
 
-    let balance = wallet.get_balance().unwrap();
+    let balance = wallet.get_balance().unwrap().get_total();
     assert_eq!(balance, expected_balance);
 }
 
@@ -245,13 +245,13 @@ where
 
     // actually test the wallet
     wallet.sync(&blockchain, Default::default()).unwrap();
-    let balance = wallet.get_balance().unwrap();
+    let balance = wallet.get_balance().unwrap().get_total();
     assert_eq!(balance, expected_balance);
 
     // now try with a fresh wallet
     let fresh_wallet =
         Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new()).unwrap();
     fresh_wallet.sync(&blockchain, Default::default()).unwrap();
-    let fresh_balance = fresh_wallet.get_balance().unwrap();
+    let fresh_balance = fresh_wallet.get_balance().unwrap().get_total();
     assert_eq!(fresh_balance, expected_balance);
 }
index edebd28da24587605c083e234b5f79e136a468ea..5e54a3dcdee51c656a678ea951714d5c549e60e3 100644 (file)
@@ -227,7 +227,7 @@ pub struct TransactionDetails {
     /// Sent value (sats)
     /// Sum of owned inputs of this transaction.
     pub sent: u64,
-    /// Fee value (sats) if available.
+    /// Fee value (sats) if confirmed.
     /// The availability of the fee depends on the backend. It's never `None` with an Electrum
     /// Server backend, but it could be `None` with a Bitcoin RPC node without txindex that receive
     /// funds while offline.
@@ -262,6 +262,65 @@ impl BlockTime {
     }
 }
 
+/// Balance differentiated in various categories
+#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
+pub struct Balance {
+    /// All coinbase outputs not yet matured
+    pub immature: u64,
+    /// Unconfirmed UTXOs generated by a wallet tx
+    pub trusted_pending: u64,
+    /// Unconfirmed UTXOs received from an external wallet
+    pub untrusted_pending: u64,
+    /// Confirmed and immediately spendable balance
+    pub confirmed: u64,
+}
+
+impl Balance {
+    /// Get sum of trusted_pending and confirmed coins
+    pub fn get_spendable(&self) -> u64 {
+        self.confirmed + self.trusted_pending
+    }
+
+    /// Get the whole balance visible to the wallet
+    pub fn get_total(&self) -> u64 {
+        self.confirmed + self.trusted_pending + self.untrusted_pending + self.immature
+    }
+}
+
+impl std::fmt::Display for Balance {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{{ immature: {}, trusted_pending: {}, untrusted_pending: {}, confirmed: {} }}",
+            self.immature, self.trusted_pending, self.untrusted_pending, self.confirmed
+        )
+    }
+}
+
+impl std::ops::Add for Balance {
+    type Output = Self;
+
+    fn add(self, other: Self) -> Self {
+        Self {
+            immature: self.immature + other.immature,
+            trusted_pending: self.trusted_pending + other.trusted_pending,
+            untrusted_pending: self.untrusted_pending + other.untrusted_pending,
+            confirmed: self.confirmed + other.confirmed,
+        }
+    }
+}
+
+impl std::iter::Sum for Balance {
+    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
+        iter.fold(
+            Balance {
+                ..Default::default()
+            },
+            |a, b| a + b,
+        )
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
index dc1f3724a174f8678f5d6630186e152033dadea4..aa7ef202e6be77d5600a37c4ce774ad3510b177b 100644 (file)
@@ -460,15 +460,52 @@ where
         self.database.borrow().iter_txs(include_raw)
     }
 
-    /// Return the balance, meaning the sum of this wallet's unspent outputs' values
+    /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
+    /// values.
     ///
     /// Note that this methods only operate on the internal database, which first needs to be
     /// [`Wallet::sync`] manually.
-    pub fn get_balance(&self) -> Result<u64, Error> {
-        Ok(self
-            .list_unspent()?
-            .iter()
-            .fold(0, |sum, i| sum + i.txout.value))
+    pub fn get_balance(&self) -> Result<Balance, Error> {
+        let mut immature = 0;
+        let mut trusted_pending = 0;
+        let mut untrusted_pending = 0;
+        let mut confirmed = 0;
+        let utxos = self.list_unspent()?;
+        let database = self.database.borrow();
+        let last_sync_height = match database
+            .get_sync_time()?
+            .map(|sync_time| sync_time.block_time.height)
+        {
+            Some(height) => height,
+            // None means database was never synced
+            None => return Ok(Balance::default()),
+        };
+        for u in utxos {
+            // Unwrap used since utxo set is created from database
+            let tx = database
+                .get_tx(&u.outpoint.txid, true)?
+                .expect("Transaction not found in database");
+            if let Some(tx_conf_time) = &tx.confirmation_time {
+                if tx.transaction.expect("No transaction").is_coin_base()
+                    && (last_sync_height - tx_conf_time.height) < COINBASE_MATURITY
+                {
+                    immature += u.txout.value;
+                } else {
+                    confirmed += u.txout.value;
+                }
+            } else if u.keychain == KeychainKind::Internal {
+                trusted_pending += u.txout.value;
+            } else {
+                untrusted_pending += u.txout.value;
+            }
+        }
+
+        Ok(Balance {
+            immature,
+            trusted_pending,
+            untrusted_pending,
+            confirmed,
+        })
     }
 
     /// Add an external signer
@@ -5232,23 +5269,38 @@ pub(crate) mod test {
             Some(confirmation_time),
             (@coinbase true)
         );
+        let sync_time = SyncTime {
+            block_time: BlockTime {
+                height: confirmation_time,
+                timestamp: 0,
+            },
+        };
+        wallet
+            .database
+            .borrow_mut()
+            .set_sync_time(sync_time)
+            .unwrap();
 
         let not_yet_mature_time = confirmation_time + COINBASE_MATURITY - 1;
         let maturity_time = confirmation_time + COINBASE_MATURITY;
 
-        // The balance is nonzero, even if we can't spend anything
-        // FIXME: we should differentiate the balance between immature,
-        // trusted, untrusted_pending
-        // See https://github.com/bitcoindevkit/bdk/issues/238
         let balance = wallet.get_balance().unwrap();
-        assert!(balance != 0);
+        assert_eq!(
+            balance,
+            Balance {
+                immature: 25_000,
+                trusted_pending: 0,
+                untrusted_pending: 0,
+                confirmed: 0
+            }
+        );
 
         // We try to create a transaction, only to notice that all
         // our funds are unspendable
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let mut builder = wallet.build_tx();
         builder
-            .add_recipient(addr.script_pubkey(), balance / 2)
+            .add_recipient(addr.script_pubkey(), balance.immature / 2)
             .current_height(confirmation_time);
         assert!(matches!(
             builder.finish().unwrap_err(),
@@ -5261,7 +5313,7 @@ pub(crate) mod test {
         // Still unspendable...
         let mut builder = wallet.build_tx();
         builder
-            .add_recipient(addr.script_pubkey(), balance / 2)
+            .add_recipient(addr.script_pubkey(), balance.immature / 2)
             .current_height(not_yet_mature_time);
         assert!(matches!(
             builder.finish().unwrap_err(),
@@ -5272,9 +5324,31 @@ pub(crate) mod test {
         ));
 
         // ...Now the coinbase is mature :)
+        let sync_time = SyncTime {
+            block_time: BlockTime {
+                height: maturity_time,
+                timestamp: 0,
+            },
+        };
+        wallet
+            .database
+            .borrow_mut()
+            .set_sync_time(sync_time)
+            .unwrap();
+
+        let balance = wallet.get_balance().unwrap();
+        assert_eq!(
+            balance,
+            Balance {
+                immature: 0,
+                trusted_pending: 0,
+                untrusted_pending: 0,
+                confirmed: 25_000
+            }
+        );
         let mut builder = wallet.build_tx();
         builder
-            .add_recipient(addr.script_pubkey(), balance / 2)
+            .add_recipient(addr.script_pubkey(), balance.confirmed / 2)
             .current_height(maturity_time);
         builder.finish().unwrap();
     }