diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e9d0d15..0f48b3e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ - Singlesig: Remove is_segwit from GA_get_unspent_outputs returned json - GA_register_user: Change interface to match GA_login_user. +### Fixed + +- GA_get_unspent_outputs: fix bug returning utxos from replaced transactions. + ### Removed - Removed GA_get_mnemonic_passphrase, callers should use GA_get_credentials. diff --git a/subprojects/gdk_rust/gdk_electrum/src/account.rs b/subprojects/gdk_rust/gdk_electrum/src/account.rs index dc0735cb3..e2d8a3d59 100644 --- a/subprojects/gdk_rust/gdk_electrum/src/account.rs +++ b/subprojects/gdk_rust/gdk_electrum/src/account.rs @@ -625,7 +625,11 @@ impl Account { let mut inputs = HashSet::new(); let store_read = self.store.read()?; let acc_store = store_read.account_cache(self.account_num)?; - for txe in acc_store.all_txs.values() { + for (txid, txe) in acc_store.all_txs.iter() { + if !acc_store.heights.contains_key(&txid) { + // transaction has been replaced or dropped out of mempool + continue; + } inputs.extend(txe.tx.previous_outputs()); for vout in 0..(txe.tx.output_len() as u32) { let script_pubkey = txe.tx.output_script(vout); diff --git a/subprojects/gdk_rust/gdk_rust/tests/integration.rs b/subprojects/gdk_rust/gdk_rust/tests/integration.rs index 61d6cdb67..5926222f3 100644 --- a/subprojects/gdk_rust/gdk_rust/tests/integration.rs +++ b/subprojects/gdk_rust/gdk_rust/tests/integration.rs @@ -1258,6 +1258,9 @@ fn rbf() { let txid1 = test_session.session.broadcast_transaction(&signed_tx.hex).unwrap(); test_session.wait_tx(vec![1], &txid1, Some(signed_tx.fee), Some(TransactionType::Redeposit)); let txitem = test_session.get_tx_from_list(1, &txid1); + assert!(test_session.utxos(1).0.get("btc").unwrap().iter().any(|e| e.txhash == txid1)); + assert_eq!(test_session.balance_account(1, None, None), sat - txitem.fee); + assert_eq!(txitem.fee_rate / 1000, 25); // Replace it @@ -1279,6 +1282,8 @@ fn rbf() { for i in 0..60 { std::thread::sleep(std::time::Duration::from_secs(1)); if test_session.get_tx_list(1).iter().all(|e| e.txhash != txid1) { + assert!(test_session.utxos(1).0.get("btc").unwrap().iter().all(|e| e.txhash != txid1)); + assert_eq!(test_session.balance_account(1, None, None), sat - txitem.fee); break; } assert!(i < 59, "timeout waiting for replaced transaction to disappear");