Merge bitcoindevkit/bdk#640: Get balance in categories
0f03831274d3aa69da6e89729c65d66530bbd752 Change get_balance to return in categories. (wszdexdrf) Pull request description: ### Description This changes `get_balance()` function so that it returns balance separated in 4 categories: - available - trusted-pending - untrusted-pending - immature Fixes #238 ### Notes to the reviewers Based on #614 ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [x] I've updated tests for the new feature * [x] I've added docs for the new feature * [x] I've updated `CHANGELOG.md` ACKs for top commit: afilini: ACK 0f03831274d3aa69da6e89729c65d66530bbd752 Tree-SHA512: 39f02c22c61b6c73dd8e6d27b1775a72e64ab773ee67c0ad00e817e555c52cdf648f482ca8be5fcc2f3d62134c35b720b1e61b311cb6debb3ad651e79c829b93
This commit is contained in:
commit
03d3c786f2
@ -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]
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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> {
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
61
src/types.rs
61
src/types.rs
@ -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::*;
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user